gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03828
[Merge] lp:~izidor/gtg/collaborative-gtg into lp:gtg
Izidor Matušov has proposed merging lp:~izidor/gtg/collaborative-gtg into lp:gtg.
Requested reviews:
Gtg developers (gtg)
Related bugs:
Bug #339324 in Getting Things GNOME!: "Collaborative GTG between multiple users"
https://bugs.launchpad.net/gtg/+bug/339324
For more details, see:
https://code.launchpad.net/~izidor/gtg/collaborative-gtg/+merge/165007
My implementation of cooperative project planning in GTG. Multiple users can share tasks among themselves over PubSub (extension of XMPP). In nutshell, this patch adds:
* synchronization of tasks over XMPP
* concept of contacts in GTG
* adding estimation and time tracking support for shared tasks
--
https://code.launchpad.net/~izidor/gtg/collaborative-gtg/+merge/165007
Your team Gtg developers is requested to review the proposed merge of lp:~izidor/gtg/collaborative-gtg into lp:gtg.
=== added file 'GTG/backends/backend_pubsub.py'
--- GTG/backends/backend_pubsub.py 1970-01-01 00:00:00 +0000
+++ GTG/backends/backend_pubsub.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,615 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Gettings Things Gnome! - a personal organizer for the GNOME desktop
+# Copyright (c) 2012-2013 - Izidor Matušov
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+"""
+PubSub backends which synchronizes tasks over XMPP
+"""
+
+import glob
+import logging
+import os
+import re
+import uuid
+import xml.dom.minidom
+
+from GTG import _
+from GTG.backends.backendsignals import BackendSignals
+from GTG.backends.genericbackend import GenericBackend
+from GTG.tools.taskxml import task_from_xml, task_to_xml
+from GTG.tools.logger import Log
+
+from sleekxmpp import ClientXMPP
+from sleekxmpp.exceptions import IqError
+from sleekxmpp.plugins import xep_0004
+from sleekxmpp.xmlstream import ET, tostring
+
+from xdg.BaseDirectory import xdg_cache_home
+
+# How much logs do you want to see?
+if False:
+ logging.basicConfig(level=logging.DEBUG,
+ format='%(levelname)-8s %(message)s')
+else:
+ logging.basicConfig(level=logging.ERROR,
+ format='%(levelname)-8s %(message)s')
+
+
+class Backend(GenericBackend):
+ """
+ PubSub backend
+ """
+ _general_description = {
+ GenericBackend.BACKEND_NAME: "backend_pubsub",
+ GenericBackend.BACKEND_HUMAN_NAME: _("PubSub"),
+ GenericBackend.BACKEND_AUTHORS: ["Izidor Matušov"],
+ GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE,
+ GenericBackend.BACKEND_DESCRIPTION:
+ _("Synchronize your tasks over XMPP using PubSub"),
+ }
+
+ _static_parameters = {
+ "username": {
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING,
+ GenericBackend.PARAM_DEFAULT_VALUE:
+ 'user@xxxxxxxxxxx', },
+ "password": {
+ GenericBackend.PARAM_TYPE: GenericBackend.TYPE_PASSWORD,
+ GenericBackend.PARAM_DEFAULT_VALUE: '', },
+ }
+
+ def __init__(self, params):
+ """ Constructor of the object """
+ super(Backend, self).__init__(params)
+ self._xmpp = None
+ self._sync_tasks = set()
+ self._changed_locally = set()
+ self._changed_remotely = set()
+
+ def initialize(self):
+ """ This is called when a backend is enabled """
+ super(Backend, self).initialize()
+
+ if "state" not in self._parameters:
+ self._parameters["state"] = "start"
+
+ # Prepare parameters
+ jid = self._parameters["username"]
+ password = self._parameters["password"]
+ server = "pubsub." + jid.split('@', 1)[-1]
+
+ self._xmpp = PubsubClient(jid, password, server)
+ self._xmpp.register_callback("failed_auth", self.on_failed_auth)
+ self._xmpp.register_callback("connected", self.on_connected)
+ self._xmpp.register_callback("disconnected", self.on_disconnected)
+
+ if self._xmpp.connect(reattempt=False):
+ self._xmpp.process()
+ else:
+ Log.error("Can't connect to XMPP")
+ if self._parameters["state"] == "start":
+ self.on_failed_auth()
+
+ BackendSignals().connect('sharing-changed', self.on_sharing_changed)
+
+ self._xmpp.register_callback("project_tag", self.on_new_remote_project)
+ self._xmpp.register_callback("set_task", self.on_remote_set_task)
+ self._xmpp.register_callback("rm_task", self.on_remote_rm_task)
+
+ def on_failed_auth(self):
+ """ Provided credencials are not valid.
+
+ Disable this instance and show error to user """
+ Log.error('Failed to authenticate')
+ BackendSignals().backend_failed(self.get_id(),
+ BackendSignals.ERRNO_AUTHENTICATION)
+ self.quit(disable=True)
+
+ def on_connected(self):
+ """ Get the initial set of the tasks from the XMPP """
+ if self._parameters["state"] != "onine":
+ self._parameters["state"] = "online"
+
+ # Ensure all teams
+ tag_tree = self.datastore.get_tagstore().get_main_view()
+ for tag_id in tag_tree.get_all_nodes():
+ tag = self.datastore.get_tag(tag_id)
+ team = tag.get_people_shared_with()
+ if len(team) > 0:
+ self._xmpp.ensure_team(tag_id, team)
+
+ # Fetch initial tasks
+ for task_id, tag, raw_xml in self._xmpp.get_tasks():
+ # Parse the raw_xml by DOM
+ doc = xml.dom.minidom.parseString(raw_xml)
+ task_xml = doc.getElementsByTagName("task")[0]
+
+ # Create a new task or return the existing one with the same id
+ task = self.datastore.task_factory(task_id)
+ task = task_from_xml(task, task_xml)
+ task.add_tag(tag)
+ self.datastore.push_task(task)
+
+ self._sync_tasks.add(task_id)
+ Log.info("(init) PubSub --set--> GTG: [%s] '%s'" %
+ (task_id, task.get_title()))
+
+ def on_disconnected(self):
+ """ When disconnected """
+ self._parameters["state"] = "offline"
+
+ def on_sharing_changed(self, sender, tag_id):
+ """ Changed sharing settings """
+ tag = self.datastore.get_tag(tag_id)
+ team = tag.get_people_shared_with()
+ if self._xmpp:
+ self._xmpp.ensure_team(tag_id, team)
+
+ #### OVERRIDDEN METHODS ###################################################
+ def get_contacts(self):
+ """ Return all contacts to whom the user can share tasks """
+ if self._xmpp:
+ return self._xmpp.get_contacts()
+ else:
+ return []
+
+ def get_user_person_id(self):
+ """ Return person_id for this user """
+ if self._xmpp:
+ return self._xmpp.get_user_person_id()
+ else:
+ return None
+
+ def save_state(self):
+ """ The last function before shutting this backend down.
+
+ Disconnect XMPP and store picked_file """
+ Log.info("Quitting backend")
+ self._xmpp.disconnect()
+ Log.info("Backend is shut down")
+
+ #### LOCAL CHANGES ########################################################
+ def set_task(self, task):
+ """ Propagate a change in local tasks into server """
+ if self._parameters["state"] != "online":
+ return
+
+ sync_tags = self._xmpp.get_synchronized_tags()
+ tags = task.get_tags_name()
+ tag_overlap = set(sync_tags) & set(tags)
+ if not tag_overlap:
+ return
+
+ if task.get_id() in self._changed_remotely:
+ self._changed_remotely.remove(task.get_id())
+ return
+
+ Log.info("GTG --set--> PubSub: [%s] '%s'" % (task.get_id(),
+ task.get_title()))
+
+ doc = xml.dom.minidom.parseString("<task></task>")
+ task_id = task.get_id()
+ task_xml = task_to_xml(doc, task).toxml()
+ tags = task.get_tags_name()
+
+ self._xmpp.set_task(task_id, tags, task_xml)
+ self._sync_tasks.add(task_id)
+ self._changed_locally.add(task_id)
+
+ def remove_task(self, task_id):
+ """ After removing local task remove tasks from the server """
+ if self._parameters["state"] != "online":
+ return
+ if task_id in self._sync_tasks:
+ Log.info("GTG --del--> PubSub: [%s]" % task_id)
+ self._xmpp.delete_task(task_id)
+ self._sync_tasks.remove(task_id)
+ if task_id in self._changed_locally:
+ self._changed_locally.remove(task_id)
+ if task_id in self._changed_remotely:
+ self._changed_remotely.remove(task_id)
+
+ #### REMOTE CHANGES #######################################################
+ def on_new_remote_project(self, tag_id, team_jids):
+ """ New project was added on server """
+ if self._parameters["state"] != "online":
+ return
+ team = [unicode(member) for member in team_jids]
+ Log.info("Pubsub --new project--> GTG: %s, teammates: %s" % (tag_id,
+ team))
+
+ tag = self.datastore.get_tag(tag_id)
+ if tag is None:
+ tag = self.datastore.new_tag(tag_id)
+ tag.set_people_shared_with(team)
+
+ def on_remote_set_task(self, task_id, raw_xml):
+ """ Task was set on server """
+ if self._parameters["state"] != "online":
+ return
+
+ if task_id in self._changed_locally:
+ self._changed_locally.remove(task_id)
+ return
+
+ Log.info("PubSub --set--> GTG: [%s]" % task_id)
+
+ # Parse XML string into <task> node
+ xmldoc = xml.dom.minidom.parseString(raw_xml)
+ task_xml = xmldoc.getElementsByTagName("task")[0]
+
+ self._changed_remotely.add(task_id)
+ task = self.datastore.get_task(task_id)
+ if task:
+ # Already exists
+ task = task_from_xml(task, task_xml)
+ else:
+ # New task
+ task = self.datastore.task_factory(task_id)
+ task = task_from_xml(task, task_xml)
+ self.datastore.push_task(task)
+
+ def on_remote_rm_task(self, task_id):
+ """ Task was removed on server """
+ if task_id not in self._sync_tasks:
+ # This task is not synchronized via this synchronization service
+ return
+
+ if task_id in self._changed_locally:
+ self._changed_locally.remove(task_id)
+ if task_id in self._changed_remotely:
+ self._changed_remotely.remove(task_id)
+
+ Log.info("PubSub --del--> GTG: [%s]" % task_id)
+ self.datastore.request_task_deletion(task_id)
+ if task_id in self._sync_tasks:
+ self._sync_tasks.remove(task_id)
+
+
+class PubsubClient(ClientXMPP):
+ """ Client which deals with the underlying XMPP.
+
+ It provides a nice, higher level interface. You can either connect to
+ signals using register_callback() to receive events or call methods
+ to change tasks. """
+
+ # Cache dir for avatars
+ AVATARS_DIR = os.path.join(xdg_cache_home, "gtg", "avatars")
+
+ # How many items do you want to share over XMPP?
+ MAX_ITEMS = 9999
+
+ def __init__(self, jid, password, server):
+ super(PubsubClient, self).__init__(jid, password)
+
+ # Register plugins for XMPP
+ # vCards
+ self.register_plugin('xep_0054')
+ # PubSub
+ self.register_plugin('xep_0060')
+
+ self._my_jid = jid
+ self._pubsub = server
+
+ # List of subscribed nodes
+ self._nodes = {}
+ # Register callbacks for events
+ self._callbacks = {}
+
+ # Hold teams for nodes
+ self._teams = {}
+
+ self.add_event_handler('session_start', self._on_start, threaded=True)
+ self.add_event_handler('failed_auth', self._on_failed_auth)
+ self.add_event_handler('pubsub_publish', self._on_published)
+ self.add_event_handler('pubsub_retract', self._on_retract)
+
+ def register_callback(self, name, func):
+ """ Register a function for given callback
+
+ There are possible callbacks:
+ * connected() -- successfully connected
+ * failed_auth() -- failed authentication
+ * project_tag(name, teammates) -- a project was added or updated
+ * set_task(task_id, task_xml) -- a task was modified
+ * rm_task(task_id) -- a task was removed
+ """
+ self._callbacks[name] = func
+
+ def _callback(self, name, *args):
+ """ Trigger a callback defined by its name and pass arguments """
+ if name in self._callbacks:
+ self._callbacks[name](*args)
+ else:
+ Log.error("Unknown callback '%s'(%s)" % (name, args))
+
+ #### Helper methods #######################################################
+ def _get_avatar(self, jid):
+ """ Return avatar for jid if it is possible.
+
+ The avatar is cached for the future runs.
+ """
+ if not os.path.exists(self.AVATARS_DIR):
+ os.makedirs(self.AVATARS_DIR)
+
+ # If avatar was cached, return it
+ avatars = glob.glob(os.path.join(self.AVATARS_DIR, jid + ".*"))
+ if len(avatars) > 0:
+ return avatars[0]
+
+ # Download vCard and avatar in it
+ vcard = self['xep_0054'].get_vcard(jid)
+ img_type = vcard['vcard_temp']['PHOTO']['TYPE']
+ photo = vcard['vcard_temp']['PHOTO']['BINVAL']
+
+ # Determine a name for the file
+ if not img_type.startswith("image/") or " " in img_type:
+ return None
+ suffix = img_type[len("image/"):]
+ name = os.path.join(self.AVATARS_DIR, "%s.%s" % (jid, suffix))
+
+ Log.info("Saving avatar for '%s'" % jid)
+ with open(name, 'wb') as avatar_file:
+ avatar_file.write(photo)
+
+ return name
+
+ def _get_subscribed_nodes(self):
+ """ Return list of subscribed nodes """
+ result = self['xep_0060'].get_subscriptions(self._pubsub)
+ return [subscription['node'] for subscription
+ in result['pubsub']['subscriptions']['substanzas']
+ if subscription['subscription'] == 'subscribed']
+
+ def _discover_nodes(self):
+ """ Discover all nodes user can access """
+ subscriptions = self._get_subscribed_nodes()
+ self._nodes = {}
+
+ affiliations = self['xep_0060'].get_affiliations(self._pubsub)
+ affiliations = affiliations['pubsub']['affiliations']
+ if 'substanzas' not in affiliations.values:
+ # No nodes available
+ return
+ for affiliation in affiliations.values['substanzas']:
+ affiliation, node = affiliation['affiliation'], affiliation['node']
+ if affiliation == 'owner' and node.startswith('GTG_'):
+ # Check node config
+ config = self['xep_0060'].get_node_config(self._pubsub, node)
+ values = config['pubsub_owner']['configure']['form']['values']
+
+ form = xep_0004.Form()
+ form.add_field(var='FORM_TYPE', type='hidden',
+ value='http://jabber.org/protocol/pubsub#node_config')
+
+ if int(values['pubsub#max_items']) < self.MAX_ITEMS:
+ Log.info("Max items is set only to %s" %
+ values['pubsub#max_items'])
+ form.add_field(var='pubsub#max_items',
+ value=str(self.MAX_ITEMS))
+
+ if values['pubsub#access_model'] != 'whitelist':
+ form.add_field(var='pubsub#access_model',
+ value='whitelist')
+
+ if not values['pubsub#notify_delete']:
+ form.add_field(var='pubsub#notify_delete', value='1')
+
+ if not values['pubsub#notify_config']:
+ form.add_field(var='pubsub#notify_config', value='1')
+
+ m = re.match('Project (@\w+)', values['pubsub#title'])
+ if not m:
+ Log.warning("Malformed node name '%s'" %
+ values['pubsub#title'])
+ continue
+
+ project_name = m.group(1)
+ self._nodes[node] = project_name
+ Log.info("Discovered project '%s'" % project_name)
+
+ if len(form.field) > 1:
+ form['type'] = 'submit'
+ self['xep_0060'].set_node_config(self._pubsub, node, form)
+
+ if node not in subscriptions:
+ self['xep_0060'].subscribe(self._pubsub, node)
+
+ # Find teammates for cache
+ self._teams[node] = self._get_teammates(node)
+
+ def _create_node(self, tag):
+ """ Create a new node for tag """
+ name = 'GTG_%s' % uuid.uuid4()
+ form = xep_0004.Form()
+ form.add_field(var='pubsub#max_items', value=str(self.MAX_ITEMS))
+ form.add_field(var='pubsub#access_model', value='whitelist')
+ form.add_field(var='pubsub#notify_delete', value='1')
+ form.add_field(var='pubsub#notify_config', value='1')
+ title = "Project %s" % tag
+ form.add_field(var='pubsub#title', value=title)
+
+ Log.info("Creating node '%s' for tag %s" % (name, tag))
+ self['xep_0060'].create_node(self._pubsub, name, config=form)
+ self['xep_0060'].subscribe(self._pubsub, name)
+ self._nodes[name] = tag
+ return name
+
+ def _get_teammates(self, node):
+ """ Return a simple list of teammembers JID given the node """
+ result = self['xep_0060'].get_node_affiliations(self._pubsub, node)
+ affiliations = result['pubsub_owner']['affiliations']['substanzas']
+ self._teams[node] = [aff['jid'] for aff in affiliations]
+ return self._teams[node]
+
+ def _set_teammates(self, node, new_teammates):
+ """ Ensure that the list of teammates is this one """
+ if len(new_teammates) > 0:
+ former = set(self._get_teammates(node))
+ new_teammates = set(new_teammates)
+
+ to_add = [(jid, 'owner') for jid in (new_teammates - former)]
+ to_remove = [(jid, 'none') for jid in (former - new_teammates)]
+ changes = to_add + to_remove
+
+ if len(changes) > 0:
+ self['xep_0060'].modify_affiliations(self._pubsub, node,
+ changes)
+
+ self._teams[node] = new_teammates
+ else:
+ # For empty teams delete node
+ self['xep_0060'].delete_node(self._pubsub, node)
+ self._nodes.pop(node)
+ self._teams.pop(node)
+
+ #### Handling events ######################################################
+ def _on_failed_auth(self, stanza):
+ """ Let know the backend about failed authentication """
+ self._callback('failed_auth')
+
+ def _on_start(self, event):
+ """ Do stuff after connection
+
+ Fetch a list of assigned nodes, create a home node if needed. Get
+ avatars if there are not available. """
+
+ Log.info("Connected to PubSub")
+
+ # Get roster notifications
+ self.get_roster()
+ self.send_presence()
+
+ # Discover nodes and notify GTG about project nodes
+ self._discover_nodes()
+ for node, name in self._nodes.items():
+ self._callback("project_tag", name, self._get_teammates(node))
+
+ self._callback("connected")
+
+ def _on_published(self, msg):
+ """ A task was modified by a teammate """
+ task_id = msg['pubsub_event']['items']['item']['id']
+ raw_xml = tostring(msg['pubsub_event']['items']['item']['payload'])
+ self._callback("set_task", task_id, raw_xml)
+
+ def _on_retract(self, msg):
+ """ Task was deleted by a teammate """
+ task_id = msg['pubsub_event']['items']['retract']['id']
+ self._callback("rm_task", task_id)
+
+ #### PUBLIC INTERFACE #####################################################
+ def get_synchronized_tags(self):
+ """ Return list of all synchronized tags """
+ return self._nodes.values()
+
+ def get_contacts(self):
+ """ Return available contacts """
+ used_contacts = set()
+
+ contacts = [(self._my_jid, _("Me"),
+ self._get_avatar(self._my_jid))]
+ used_contacts.add(self._my_jid)
+
+ for contact in list(self.client_roster.keys()):
+ if contact != self._my_jid:
+ # Use either name or JID if name not available
+ name = self.client_roster[contact]['name']
+ if name.strip() == "":
+ name = contact
+
+ avatar = self._get_avatar(contact)
+ contacts.append((contact, name, avatar))
+ used_contacts.add(contact)
+
+ for team in self._teams.values():
+ for jid in team:
+ if jid not in used_contacts:
+ contacts.append((jid, jid, None))
+ used_contacts.add(jid)
+
+ return contacts
+
+ def get_user_person_id(self):
+ """ Return person_id for this user """
+ return self._my_jid
+
+ def get_calendar(self, node):
+ """ Return calendar for node """
+ item = self['xep_0060'].get_item(self._pubsub, node, 'calendar')
+ item = ['pubsub']['items']['substanzas'][0]['payload']
+ return tostring(item)
+
+ def set_calendar(self, node, calendar_str):
+ """ Set calendar of node """
+ payload = ET.fromstring(calendar_str)
+ self['xep_0060'].publish(self._pubsub, node, id='calendar',
+ payload=payload)
+
+ def get_tasks(self):
+ """ Return list of all available tasks """
+ Log.info("Looking for available tasks")
+ for node in self._nodes:
+ project_tag = self._nodes[node]
+ result_items = self['xep_0060'].get_items(self._pubsub, node,
+ max_items=self.MAX_ITEMS, block=True)
+ items = result_items['pubsub']['items']['substanzas']
+ for item in items:
+ yield item['id'], project_tag, tostring(item['payload'])
+
+ def set_task(self, task_id, tags, task):
+ """ Publish task to teammates """
+ payload = ET.fromstring(task)
+
+ for node, project_tag in self._nodes.items():
+ if project_tag in tags:
+ self['xep_0060'].publish(self._pubsub, node, id=task_id,
+ payload=payload)
+ else:
+ # Has this node still this task?
+ item_req = self['xep_0060'].get_item(self._pubsub, node,
+ task_id)
+ items = item_req['pubsub']['items']['substanzas']
+ if len(items) > 0:
+ self['xep_0060'].retract(self._pubsub, node, task_id)
+
+ def delete_task(self, task_id):
+ """ Delete task also for teammates """
+ for node in self._nodes:
+ try:
+ self['xep_0060'].retract(self._pubsub, node, task_id,
+ notify="true")
+ except IqError:
+ # Task was not in the node
+ pass
+
+ def ensure_team(self, tag, team):
+ """ Set the team members of the tag
+
+ If a node for the tag doesn't exists, create it. """
+ Log.info("Set team for tag '%s' to '%s'" % (tag, team))
+ team_node = None
+ for node, associated_tag in self._nodes.items():
+ if associated_tag == tag:
+ team_node = node
+ break
+
+ if team_node is None:
+ team_node = self._create_node(tag)
+
+ self._set_teammates(team_node, team)
=== modified file 'GTG/backends/backendsignals.py'
--- GTG/backends/backendsignals.py 2013-02-25 07:35:07 +0000
+++ GTG/backends/backendsignals.py 2013-05-22 05:15:33 +0000
@@ -80,6 +80,10 @@
BACKEND_SYNC_ENDED = 'backend-sync-ended'
INTERACTION_REQUESTED = 'user-interaction-requested'
+ # Emitted when sharing settings for a tag were changed like
+ # adding/removing a teammate
+ SHARING_CHANGED = 'sharing-changed'
+
INTERACTION_CONFIRM = 'confirm'
INTERACTION_TEXT = 'text'
@@ -90,6 +94,7 @@
BACKEND_SYNC_STARTED: signal_type_factory(str),
BACKEND_SYNC_ENDED: signal_type_factory(str),
DEFAULT_BACKEND_LOADED: signal_type_factory(),
+ SHARING_CHANGED: signal_type_factory(str),
BACKEND_FAILED: signal_type_factory(str, str),
INTERACTION_REQUESTED: signal_type_factory(str, str,
str, str)}
@@ -143,3 +148,9 @@
def is_backend_syncing(self, backend_id):
return backend_id in self.backends_currently_syncing
+
+ def sharing_changed(self, tag_id):
+ """ Emit when sharing settings of a Tag changed
+
+ For example, a teammate is added/removed """
+ self._emit_signal(self.SHARING_CHANGED, tag_id)
=== modified file 'GTG/backends/genericbackend.py'
--- GTG/backends/genericbackend.py 2013-02-25 08:12:02 +0000
+++ GTG/backends/genericbackend.py 2013-05-22 05:15:33 +0000
@@ -703,3 +703,17 @@
pass
self.launch_setting_thread(bypass_quit_request=True)
self.save_state()
+
+ def get_contacts(self):
+ """ Returns list of contacts to share tasks with
+
+ Services can override this method and return:
+ * a list of tupples: (person_id, name, filepath with avatar/None)
+ * None if not supported """
+ return None
+
+ def get_user_person_id(self):
+ """ Return person_id for this user
+
+ Services can override this method and return person_id """
+ return None
=== modified file 'GTG/core/datastore.py'
--- GTG/core/datastore.py 2013-02-25 07:35:07 +0000
+++ GTG/core/datastore.py 2013-05-22 05:15:33 +0000
@@ -607,6 +607,27 @@
"""
return self._backend_mutex
+ def get_sharable_contacts(self):
+ """ Return list of persons a project can be shared
+
+ Result is based on of all backends """
+ contacts = []
+ for backend in self.get_all_backends():
+ backend_contacts = backend.get_contacts()
+ if backend_contacts is not None:
+ contacts += backend_contacts
+
+ return contacts
+
+ def get_user_person_ids(self):
+ """ Return list of person_id of the user """
+ ids = []
+ for service in self.get_all_backends():
+ person_id = service.get_user_person_id()
+ if person_id is not None:
+ ids.append(person_id)
+ return ids
+
class TaskSource():
"""
@@ -859,9 +880,14 @@
'has_task',
'get_all_tasks',
'get_tasks_tree',
+ 'get_tagstore',
+ 'get_tag',
+ 'new_tag',
'get_backend_mutex',
'flush_all_tasks',
- 'request_task_deletion']:
+ 'request_task_deletion',
+ 'get_requester',
+ ]:
return getattr(self.datastore, attr)
elif attr in ['get_all_tags']:
return self.datastore.requester.get_all_tags
=== modified file 'GTG/core/requester.py'
--- GTG/core/requester.py 2013-02-25 07:35:07 +0000
+++ GTG/core/requester.py 2013-05-22 05:15:33 +0000
@@ -233,6 +233,18 @@
def save_datastore(self):
return self.ds.save()
+ def get_sharable_contacts(self):
+ """ Propagate list of contacts to share tasks with from DataStore """
+ return self.ds.get_sharable_contacts()
+
+ def get_user_person_ids(self):
+ """ Propagate list of person_id for this user """
+ return self.ds.get_user_person_ids()
+
+ def is_user_person_id(self, person_id):
+ """ Is this a user person_id? """
+ return person_id in self.get_user_person_ids()
+
############## Config ############################
##################################################
def get_global_config(self):
=== modified file 'GTG/core/tag.py'
--- GTG/core/tag.py 2013-02-25 07:35:07 +0000
+++ GTG/core/tag.py 2013-05-22 05:15:33 +0000
@@ -53,12 +53,51 @@
self._name = saxutils.unescape(str(name))
self.req = req
self._save = None
+
+ teammembers = attributes.get('people_shared_with', '')
+ self._people_shared_with = [member for member in teammembers.split(';')
+ if member]
+ # Remove duplicates
+ self._people_shared_with = list(set(self._people_shared_with))
+
self._attributes = {'name': self._name}
for key, value in attributes.iteritems():
self.set_attribute(key, value)
+ self.calendar = [(8 if day < 5 else 0) for day in range(7)]
+ self.exceptions = []
+
self.viewcount = None
+ def get_people_shared_with(self):
+ """ Return list of person_ids this tag is shared with """
+ return self._people_shared_with
+
+ def set_people_shared_with(self, people):
+ """ Set list of person_ids this tag is shared with """
+ self._people_shared_with = people
+
+ # Serialize into attribute
+ if len(self._people_shared_with) > 0:
+ self.set_attribute('people_shared_with',
+ ";".join(self._people_shared_with))
+ else:
+ self.del_attribute('people_shared_with')
+
+ def is_shared(self):
+ """ Return True if the tag is shared with somebody """
+ return len(self._people_shared_with) > 0
+
+ def set_buddy_sharing(self, buddy, is_shared):
+ """ Change setting for only one member of team """
+ if is_shared and buddy not in self._people_shared_with:
+ self._people_shared_with.append(buddy)
+ elif not is_shared and buddy in self._people_shared_with:
+ self._people_shared_with.remove(buddy)
+
+ # Serialize the change into attribute
+ self.set_people_shared_with(self._people_shared_with)
+
def __get_viewcount(self):
if not self.viewcount and self.get_name() != "gtg-tags-sep":
basetree = self.req.get_basetree()
@@ -246,6 +285,29 @@
def __str__(self):
return "Tag: %s" % self.get_name()
+ def get_calendar(self, day):
+ """ Get day from calendar """
+ return self.calendar[day]
+
+ def set_calendar(self, day, value):
+ """ Set value for calendar """
+ self.calendar[day] = value
+
+ def add_calendar_exception(self, start, end):
+ """ Add a new calendar exception """
+ self.exceptions.append((start, end))
+
+ def remove_calendar_exception(self, start, end):
+ """ Remove the calendar exception """
+ try:
+ self.exceptions.remove((start, end))
+ except:
+ pass
+
+ def get_exceptions(self):
+ """ Return all exceptions """
+ return self.exceptions
+
class Set_Name_Attribute_Error(Exception):
"""Exception raised when try to set attribute to name"""
=== modified file 'GTG/core/task.py'
--- GTG/core/task.py 2013-02-25 07:35:07 +0000
+++ GTG/core/task.py 2013-05-22 05:15:33 +0000
@@ -20,7 +20,7 @@
"""
task.py contains the Task class which represents (guess what) a task
"""
-from datetime import datetime
+from datetime import datetime, date
import cgi
import re
import uuid
@@ -69,8 +69,12 @@
# if self.loaded:
# self.req._task_loaded(self.tid)
self.attributes = {}
+ self.time_tracking = {}
+ self.estimations = {}
self._modified_update()
+ self.assignee = None
+
def is_loaded(self):
return self.loaded
@@ -621,14 +625,37 @@
'''
self.last_modified = datetime.now()
+### SHARING FUNCTIONS ########################################################
+ def get_assignee(self):
+ """ Return id of person who is assigned to this task """
+ return self.assignee
+
+ def set_assignee(self, person_id):
+ """ Set person as assignee
+
+ person_id is a string which identifies person like jabber id
+ Reset assignee by passing value None
+ """
+ self.assignee = person_id
+
+ def is_assigned_to_me(self):
+ """ Task is assigned to the user """
+ return self.req.is_user_person_id(self.assignee)
+
+ def is_shared(self):
+ """ Return True if this task is shared with somebody """
+ for tag in self.get_tags():
+ if len(tag.get_people_shared_with()) > 0:
+ return True
+ return False
+
### TAG FUNCTIONS ############################################################
-#
def get_tags_name(self):
- # Return a copy of the list of tags. Not the original object.
+ """ Return a copy of the list of tags. Not the original object """
return list(self.tags)
- # return a copy of the list of tag objects
def get_tags(self):
+ """ Return a copy of the list of tag objects """
l = []
for tname in self.tags:
tag = self.req.get_tag(tname)
@@ -739,11 +766,13 @@
# don't forget a space a the end
.replace('%s ' % (tagname), newtag))
- # tag_list is a list of tags names
- # return true if at least one of the list is in the task
def has_tags(self, tag_list=None, notag_only=False):
- # recursive function to explore the tags and its children
+ """ Return true if at least one of the list is in the task
+
+ tag_list is a list of tags names """
+
def children_tag(tagname):
+ """ Recursive function to explore the tags and its children """
toreturn = False
if tagname in self.tags:
toreturn = True
@@ -779,3 +808,84 @@
s = s + "Status: " + self.status + "\n"
s = s + "Tags: " + str(self.tags)
return s
+
+ #### TIMING SUPPORT #######################################################
+ def get_my_person_id(self):
+ """ Return my person_id associated with this task """
+ me = set(self.req.get_user_person_ids())
+ for tag in self.get_tags():
+ people = set(tag.get_people_shared_with())
+ overlap = me & people
+ if overlap:
+ return list(overlap)[0]
+
+ raise Exception("No suitable person_id")
+
+ def get_time_spent(self, day=None):
+ """ Return time spent today on this task """
+ if day is None:
+ day = date.today().strftime('%Y-%m-%d')
+ return sum(time
+ for person_id, time in self.time_tracking.get(day, []))
+
+ def set_time_spent(self, time, day=None, person=None):
+ """ Set time spent on task given day """
+ if day is None:
+ day = date.today().strftime('%Y-%m-%d')
+
+ if day not in self.time_tracking:
+ self.time_tracking[day] = []
+
+ is_updated = False
+ for i, (person_id, minutes) in enumerate(self.time_tracking[day]):
+ if person is None or person == person_id:
+ if time == 0:
+ self.time_tracking[day].pop(i)
+ else:
+ self.time_tracking[day][i] = (person_id, int(time))
+ is_updated = True
+ break
+
+ if person is None:
+ person = self.get_my_person_id()
+
+ if not is_updated:
+ self.time_tracking[day].append((person, time))
+
+ def get_total_time(self):
+ """ Return total time spent on this task """
+ return sum(minutes
+ for records in self.time_tracking.values()
+ for person_id, minutes in records)
+
+ def get_all_tracking_records(self):
+ """ Return all information for reports """
+ return [(person_id, day, minutes)
+ for day, records in self.time_tracking.items()
+ for person_id, minutes in records]
+
+ def get_estimation_time(self):
+ """ Return estimated time for this task """
+ est = self.estimations.values()
+ if len(est) > 0:
+ return sum(est) / len(est)
+ else:
+ return 8 * 60
+
+ def set_estimation(self, minutes, person=None):
+ """ Set my estimation in minutes """
+ if person is None:
+ person = self.get_my_person_id()
+
+ self.estimations[person] = minutes
+
+ def get_all_estimations(self):
+ """ Return all estimations for this task in format (person, est) """
+ return self.estimations.items()
+
+ def has_my_estimation(self):
+ """ Return True if the user estimated duration of this task """
+ for person_id in self.req.get_user_person_ids():
+ if person_id in self.estimations:
+ return True
+ return False
=== modified file 'GTG/gtk/backends_dialog/parameters_ui/__init__.py'
--- GTG/gtk/backends_dialog/parameters_ui/__init__.py 2013-04-28 08:37:38 +0000
+++ GTG/gtk/backends_dialog/parameters_ui/__init__.py 2013-05-22 05:15:33 +0000
@@ -77,6 +77,9 @@
"parameter_name": "username"})),
("password", self.UI_generator(PasswordUI)),
("period", self.UI_generator(PeriodUI)),
+ ("server", self.UI_generator(TextUI,
+ {"description": _("Server"),
+ "parameter_name": "server"})),
("service-url", self.UI_generator(TextUI,
{"description": _("Service URL"),
"parameter_name": "service-url"
@@ -90,14 +93,8 @@
"import-from-replies"
})),
("import-from-direct-messages", self.UI_generator(CheckBoxUI,
- {"text":
- _("Import tasks "
- "from direct "
- "messages"),
- "parameter":
- "import-from-\
- direct-messages\
- "})),
+ {"text": _("Import tasks " "from direct messages"),
+ "parameter": "import-from-direct-messages"})),
("import-from-my-tweets", self.UI_generator(CheckBoxUI,
{"text": _("Import \
tasks from \
=== modified file 'GTG/gtk/browser/browser.py'
--- GTG/gtk/browser/browser.py 2013-02-25 08:29:31 +0000
+++ GTG/gtk/browser/browser.py 2013-05-22 05:15:33 +0000
@@ -135,7 +135,6 @@
self._update_window_title()
### INIT HELPER FUNCTIONS #####################################################
-#
def _init_icon_theme(self):
"""
sets the deafault theme for icon and its directory
@@ -1244,6 +1243,7 @@
def on_close(self, widget=None):
"""Closing the window."""
+ self.vmanager.close_tag_editor()
# Saving is now done in main.py
self.quit()
=== modified file 'GTG/gtk/browser/tag_context_menu.py'
--- GTG/gtk/browser/tag_context_menu.py 2013-02-25 08:29:31 +0000
+++ GTG/gtk/browser/tag_context_menu.py 2013-05-22 05:15:33 +0000
@@ -58,6 +58,21 @@
self.mi_cc.set_label(_("Edit Tag..."))
self.append(self.mi_cc)
self.mi_cc.connect('activate', self.on_mi_cc_activate)
+
+ self.mi_st = gtk.MenuItem()
+ self.mi_st.set_label(_("Share Tag"))
+ self.append(self.mi_st)
+ self.mi_st.connect('activate', self.on_mi_st_activate)
+ if self.tag.is_shared():
+ self.mi_ca = gtk.MenuItem()
+ self.mi_ca.set_label(_("Calendar"))
+ self.append(self.mi_ca)
+ self.mi_ca.connect('activate', self.on_mi_ca_activate)
+ self.mi_rr = gtk.MenuItem()
+ self.mi_rr.set_label(_("Reports"))
+ self.append(self.mi_rr)
+ self.mi_rr.connect('activate', self.on_mi_rr_activate)
+
if self.tag.is_search_tag():
self.mi_del = gtk.MenuItem()
self.mi_del.set_label(_("Delete"))
@@ -77,6 +92,18 @@
"""Callback: show the tag editor upon request"""
self.vmanager.open_tag_editor(self.tag)
+ def on_mi_st_activate(self, widget):
+ """Callback: open sharing settings for tag """
+ self.vmanager.open_tag_editor(self.tag, 'sharing')
+
+ def on_mi_ca_activate(self, widget):
+ """Callback: open calendar for tag """
+ self.vmanager.open_tag_editor(self.tag, 'calendar')
+
+ def on_mi_rr_activate(self, widget):
+ """Callback: show the tag editor upon request"""
+ self.vmanager.open_tag_editor(self.tag, 'reports')
+
def on_mi_del_activate(self, widget):
""" delete a selected search """
self.req.remove_tag(self.tag.get_name())
=== modified file 'GTG/gtk/browser/treeview_factory.py'
--- GTG/gtk/browser/treeview_factory.py 2013-02-25 07:35:07 +0000
+++ GTG/gtk/browser/treeview_factory.py 2013-05-22 05:15:33 +0000
@@ -114,6 +114,13 @@
elif node.get_status() == Task.STA_DISMISSED:
title = "<span color='%s'>%s</span>" % (self.unactive_color, title)
+ if node.get_assignee():
+ assignee = node.get_assignee()
+ for person_id, name, avatar in self.req.get_sharable_contacts():
+ if assignee == person_id:
+ title += " <i>assigned to %s</i>" % (name)
+ break
+
if self.config.get("contents_preview_enable"):
excerpt = saxutils.escape(node.get_excerpt(lines=1,
strip_tags=True,
=== modified file 'GTG/gtk/editor/__init__.py'
--- GTG/gtk/editor/__init__.py 2013-02-25 07:35:07 +0000
+++ GTG/gtk/editor/__init__.py 2013-05-22 05:15:33 +0000
@@ -40,6 +40,7 @@
DELETE_TOOLTIP = _("Permanently remove this task")
SUBTASK_TOOLTIP = _("Insert a subtask in this task")
TAG_TOOLTIP = _("Insert a tag in this task")
+ REPORT_TOOLTIP = _("Show time report for this task")
# Number of second between to save in the task editor
SAVETIME = 7
=== modified file 'GTG/gtk/editor/editor.py'
--- GTG/gtk/editor/editor.py 2013-02-25 08:29:31 +0000
+++ GTG/gtk/editor/editor.py 2013-05-22 05:15:33 +0000
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2012 - Lionel Dricot & Bertrand Rousseau
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
#
# 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
@@ -27,6 +27,7 @@
import pango
import gtk
+import gobject
from GTG import _, ngettext
from GTG.gtk.editor import GnomeConfig
@@ -34,8 +35,9 @@
from GTG.core.plugins.engine import PluginEngine
from GTG.core.plugins.api import PluginAPI
from GTG.core.task import Task
-from GTG.tools.dates import Date
+from GTG.tools.dates import Date, display_tracking_short, parse_estimation
from GTG.gtk.editor.calendar import GTGCalendar
+from GTG.gtk.editor.report import TaskReport
class TaskEditor:
@@ -56,6 +58,7 @@
self.req = requester
self.browser_config = self.req.get_config('browser')
self.vmanager = vmanager
+ self.task = task
self.config = taskconfig
self.time = None
self.clipboard = clipboard
@@ -70,6 +73,13 @@
self.inserttag_button = self.builder.get_object("inserttag")
self.inserttag_button.set_tooltip_text(GnomeConfig.TAG_TOOLTIP)
+ self.report_button = self.builder.get_object("report_button")
+ self.report_button.set_tooltip_text(GnomeConfig.REPORT_TOOLTIP)
+ if not task.is_shared():
+ report_separator = self.builder.get_object("report_separator")
+ report_separator.hide()
+ self.report_button.hide()
+
# Create our dictionary and connect it
dic = {
"mark_as_done_clicked": self.change_status,
@@ -96,6 +106,9 @@
GTGCalendar.DATE_KIND_CLOSED),
"on_insert_subtask_clicked": self.insert_subtask,
"on_inserttag_clicked": self.inserttag_clicked,
+ "on_report_button_clicked": self.on_report_button_clicked,
+ "on_estimation_changed": self.on_estimation_changed,
+ "on_estimation_focus_out": self.on_estimation_focus_out,
"on_move": self.on_move,
}
self.builder.connect_signals(dic)
@@ -123,10 +136,23 @@
self.closeddate_widget = self.builder.get_object("closeddate_entry")
self.dayleft_label = self.builder.get_object("dayleft")
self.tasksidebar = self.builder.get_object("tasksidebar")
- # Define accelerator keys
- self.init_accelerators()
-
- self.task = task
+ self.assignmenu = self.builder.get_object("assignee")
+ self.tracking_start = None
+ self.tracking_timer = None
+ self.tracker_today = self.builder.get_object("tracker_today")
+ self.tracker_total = self.builder.get_object("tracker_total")
+ self.tracker_est = self.builder.get_object("tracker_est")
+
+ if not task.is_shared() or task.has_my_estimation():
+ self.builder.get_object("estimation_separator").hide()
+ self.builder.get_object("estimationbar").hide()
+
+ if task.is_assigned_to_me():
+ self.start_tracking_time()
+ else:
+ self.builder.get_object("tracker_separator").hide()
+ self.builder.get_object("timetrackerbar").hide()
+
tags = task.get_tags()
self.textview.subtasks_callback(task.get_children)
self.textview.removesubtask_callback(task.remove_child)
@@ -135,6 +161,9 @@
self.textview.set_remove_tag_callback(task.remove_tag)
self.textview.save_task_callback(self.light_save)
+ self.init_accelerators()
+ self.init_assignmenu()
+
texte = self.task.get_text()
title = self.task.get_title()
# the first line is the title
@@ -222,6 +251,142 @@
dismiss_editor.add_accelerator('clicked', agr, key, mod,
gtk.ACCEL_VISIBLE)
+ def init_assignmenu(self):
+ """ Init assignee field
+
+ If task is shared with somebody, fill assignee with contacts.
+ Otherwise hide it. """
+ # Model: person_id, name, image
+ model = gtk.ListStore(str, str, gtk.gdk.Pixbuf)
+ self.assignmenu.set_model(model)
+
+ contacts = set()
+ for tag in self.task.get_tags():
+ contacts.update(tag.get_people_shared_with())
+
+ if len(contacts) == 0:
+ self.builder.get_object("assignee_label").hide()
+ self.assignmenu.hide()
+ return
+
+ default_person = 0
+ model.append((None, "Nobody", None))
+ avatar_size = 16
+ contact_list = self.req.get_sharable_contacts()
+ for pos, (person_id, name, avatar) in enumerate(contact_list, 1):
+ # Skip persons who don't see the task
+ if person_id not in contacts:
+ continue
+
+ if avatar is None:
+ pixbuf = None
+ else:
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(avatar,
+ avatar_size, avatar_size)
+ if person_id == self.task.get_assignee():
+ default_person = pos
+ model.append((person_id, name, pixbuf))
+
+ self.assignmenu.set_active(default_person)
+ self.assignmenu.connect("changed", self.on_assignee_change)
+
+ # Avatar
+ avatar_cell = gtk.CellRendererPixbuf()
+ self.assignmenu.pack_start(avatar_cell, False)
+ self.assignmenu.add_attribute(avatar_cell, "pixbuf", 2)
+
+ # Name
+ name_cell = gtk.CellRendererText()
+ self.assignmenu.pack_start(name_cell, True)
+ self.assignmenu.add_attribute(name_cell, "text", 1)
+
+ def on_assignee_change(self, combobox):
+ """ Assignee was changed """
+ pos = combobox.get_active()
+ model = self.assignmenu.get_model()
+ person_id = model[pos][0]
+ self.task.set_assignee(person_id)
+
+ separator = self.builder.get_object("tracker_separator")
+ bar = self.builder.get_object("timetrackerbar")
+ if self.task.is_assigned_to_me():
+ separator.show()
+ bar.show()
+ self.start_tracking_time()
+ else:
+ separator.hide()
+ bar.hide()
+
+ def _modify_error_style(self, widget, has_error):
+ """ Set widget into red when it has error, otherwise normal values """
+ if has_error:
+ # We should write in red in the entry on error
+ widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F00"))
+ widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))
+ else:
+ # If valid, use default color in the widget
+ # "none" will set the default color
+ widget.modify_text(gtk.STATE_NORMAL, None)
+ widget.modify_base(gtk.STATE_NORMAL, None)
+
+ def on_estimation_changed(self, widget):
+ """ Update style of estimation widget """
+ try:
+ parse_estimation(widget.get_text())
+ valid = True
+ except ValueError:
+ valid = False
+ self._modify_error_style(widget, not valid)
+
+ def on_estimation_focus_out(self, widget, event):
+ """ Save estimation if it is good """
+ try:
+ minutes = parse_estimation(widget.get_text())
+ self.task.set_estimation(minutes)
+ except ValueError:
+ pass
+
+ def start_tracking_time(self):
+ """ Start tracking time """
+ if self.tracking_timer is not None:
+ return
+
+ already_tracked = 60 * self.task.get_time_spent()
+ self.tracking_start = time.time() - already_tracked
+ self.tracking_timer = gobject.timeout_add_seconds(30,
+ self.on_update_tracking_time)
+ self.on_update_tracking_time()
+
+ def on_update_tracking_time(self):
+ """ Update time tracking """
+ if self.tracking_timer is None:
+ return False
+
+ minutes = (time.time() - self.tracking_start) / 60
+ self.task.set_time_spent(minutes)
+
+ today_time = self.task.get_time_spent()
+ today = self.builder.get_object("tracker_today")
+ today.set_text(display_tracking_short(today_time))
+
+ total_time = self.task.get_total_time()
+ total = self.builder.get_object("tracker_total")
+ total.set_text(display_tracking_short(total_time))
+
+ est_time = self.task.get_estimation_time()
+ est = self.builder.get_object("tracker_est")
+ est.set_text(display_tracking_short(est_time))
+
+ # Repeat update after some while when called from GObject
+ return True
+
+ def end_tracking_time(self):
+ """ Finish tracking time """
+ if self.tracking_timer is not None:
+ gobject.source_remove(self.tracking_timer)
+ self.on_update_tracking_time()
+ self.tracking_start, self.tracking_timer = None, None
+
# Can be called at any time to reflect the status of the Task
# Refresh should never interfere with the TaskView.
# If a title is passed as a parameter, it will become
@@ -365,21 +530,13 @@
self.light_save()
def date_changed(self, widget, data):
+ """ Indicate if the text is valid date or not """
try:
Date.parse(widget.get_text())
valid = True
except ValueError:
valid = False
-
- if valid:
- # If the date is valid, we write with default color in the widget
- # "none" will set the default color.
- widget.modify_text(gtk.STATE_NORMAL, None)
- widget.modify_base(gtk.STATE_NORMAL, None)
- else:
- # We should write in red in the entry if the date is not valid
- widget.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F00"))
- widget.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse("#F88"))
+ self._modify_error_style(widget, not valid)
def date_focus_out(self, widget, event, data):
try:
@@ -494,6 +651,12 @@
self.textview.insert_text(" @", itera)
self.textview.grab_focus()
+ def on_report_button_clicked(self, widget):
+ """ Open time reports dialog for the task """
+ self.on_update_tracking_time()
+ report = TaskReport(self.task, self.req)
+ report.show_all()
+
def inserttag(self, widget, tag):
self.textview.insert_tags([tag])
self.textview.grab_focus()
@@ -544,10 +707,12 @@
self.config[tid]["position"] = self.get_position()
self.config[tid]["size"] = self.window.get_size()
- # We define dummy variable for when close is called from a callback
def close(self, window=None, a=None, b=None, c=None):
+ """ Method to close the editor
- # We should also destroy the whole taskeditor object.
+ We stop time tracking and destroy taskeditor object.
+ (Dummy arguments are for when calling from a callback) """
+ self.end_tracking_time()
if self.window:
self.window.destroy()
self.window = None
=== added file 'GTG/gtk/editor/report.py'
--- GTG/gtk/editor/report.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/editor/report.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,307 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+"""
+Display time reports and estimations for given tasks
+"""
+
+from datetime import datetime, date
+import gtk
+
+from GTG import _
+from GTG.gtk.editor import GnomeConfig
+from GTG.gtk.editor.calendar import GTGCalendar
+from GTG.gtk.tag_editor.calendar_page import CellRendererDate
+from GTG.tools.dates import convert_datetime_to_date
+from GTG.tools.dates import display_tracking_long, Date
+
+
+class TaskReport(gtk.Window):
+ """ Window displaying work done on the task and its estimations """
+
+ def __init__(self, task, req):
+ gtk.Window.__init__(self)
+ self.req = req
+ self.task = task
+
+ self.set_title(_("Time Reports"))
+ self.set_border_width(5)
+ self.set_position(gtk.WIN_POS_CENTER)
+ self.resize(300, 250)
+
+ vbox = gtk.VBox()
+ vbox.set_spacing(5)
+
+ self.title_label = gtk.Label()
+ self.title_label.set_alignment(0, 0)
+ self.title_label.set_line_wrap(True)
+ vbox.pack_start(self.title_label, False, False)
+
+ self.total_label = gtk.Label()
+ self.total_label.set_alignment(0, 0)
+ vbox.pack_start(self.total_label, False, False)
+
+ self.est_label = gtk.Label()
+ self.est_label.set_alignment(0, 0)
+ vbox.pack_start(self.est_label, False, False)
+
+ notebook = gtk.Notebook()
+ vbox.pack_start(notebook, True, True)
+
+ self.worked_hours = gtk.ListStore(str, gtk.gdk.Pixbuf, str, str, float)
+ tv_work = gtk.TreeView(self.worked_hours)
+
+ who_column = gtk.TreeViewColumn('Who')
+ who_column.set_expand(True)
+
+ avatar = gtk.CellRendererPixbuf()
+ who_column.pack_start(avatar, False)
+ who_column.add_attribute(avatar, "pixbuf", 1)
+
+ who_cell = gtk.CellRendererText()
+ who_column.pack_start(who_cell, True)
+ who_column.add_attribute(who_cell, 'text', 2)
+ tv_work.append_column(who_column)
+
+ day_column = gtk.TreeViewColumn('Day')
+ day_cell = CellRendererDate()
+ day_column.pack_start(day_cell, True)
+ day_column.add_attribute(day_cell, 'text', 3)
+ tv_work.append_column(day_column)
+
+ hours_column = gtk.TreeViewColumn('Hours')
+ hours_cell = gtk.CellRendererText()
+ hours_cell.set_property('editable', True)
+ hours_cell.connect('edited', self.on_worked_hours_changed)
+ hours_column.pack_start(hours_cell, False)
+ hours_column.add_attribute(hours_cell, 'text', 4)
+ hours_column.set_cell_data_func(hours_cell, self.render_hours)
+ tv_work.append_column(hours_column)
+
+ add_work = gtk.HBox()
+ add_work.pack_start(gtk.Label(_("I worked ")), False, False)
+ self.add_work_entry = gtk.Entry()
+ self.add_work_entry.set_width_chars(5)
+ add_work.pack_start(self.add_work_entry, False, False)
+ add_work.pack_start(gtk.Label(_(" hours on ")), False, False)
+ self.add_work_date = gtk.Entry()
+ self.add_work_date.set_width_chars(9)
+ add_work.pack_start(self.add_work_date, False, False)
+ arrow_button = gtk.Button()
+ arrow_button.add(gtk.Arrow(gtk.ARROW_UP, gtk.SHADOW_OUT))
+ arrow_button.connect('clicked', self.on_arrow_button)
+ add_work.pack_start(arrow_button, False, False)
+ add_work_button = gtk.Button(_("Add"))
+ add_work_button.connect('clicked', self.on_add_work)
+ add_work.pack_start(add_work_button)
+
+ builder = gtk.Builder()
+ builder.add_from_file(GnomeConfig.GLADE_FILE)
+ self.calendar = GTGCalendar(builder)
+ self.calendar.connect("date-changed", self.on_date_changed)
+
+ box_work = gtk.VBox()
+ box_work.pack_start(tv_work, True, True)
+ box_work.pack_start(add_work, False, False)
+ notebook.append_page(box_work, gtk.Label(_("Work")))
+
+ self.estimations = gtk.ListStore(str, gtk.gdk.Pixbuf, str, float)
+ tv_est = gtk.TreeView(self.estimations)
+
+ who_column = gtk.TreeViewColumn('Who')
+ who_column.set_expand(True)
+
+ avatar = gtk.CellRendererPixbuf()
+ who_column.pack_start(avatar, False)
+ who_column.add_attribute(avatar, "pixbuf", 1)
+
+ who_cell = gtk.CellRendererText()
+ who_column.pack_start(who_cell, True)
+ who_column.add_attribute(who_cell, 'text', 2)
+ tv_est.append_column(who_column)
+
+ hours_column = gtk.TreeViewColumn('Hours')
+ hours_cell = gtk.CellRendererText()
+ hours_cell.set_property('editable', False)
+ hours_cell.connect('edited', self.on_estimated_hours_changed)
+ hours_column.pack_start(hours_cell, False)
+ hours_column.add_attribute(hours_cell, 'text', 3)
+ hours_column.set_cell_data_func(hours_cell, self.render_hours)
+ tv_est.append_column(hours_column)
+
+ notebook.append_page(tv_est, gtk.Label(_("Estimations")))
+ self.add(vbox)
+
+ # Fill it with data
+ self.update()
+
+ def render_hours(self, column, cell, model, iter):
+ """ Render hours with only one decimal place """
+ hour_column = model.get_n_columns() - 1
+ value = model.get_value(iter, hour_column) / 60.0
+ cell.set_property('text', "%.1f" % value)
+
+ def on_worked_hours_changed(self, widget, path, value):
+ """ Hours were changed """
+ path = int(path)
+ try:
+ value = float(value)
+ except ValueError:
+ # Unacceptable value
+ return
+
+ if value >= 0:
+ day_str = self.worked_hours[path][3]
+ minutes = int(value * 60)
+
+ self.task.set_time_spent(minutes, day_str)
+ self.worked_hours[path][4] = minutes
+ self.update_general_info()
+
+ def on_estimated_hours_changed(self, widget, path, value):
+ """ Hours were changed """
+ path = int(path)
+ try:
+ value = float(value)
+ except ValueError:
+ # Unacceptable value
+ return
+ person_id = self.estimations[path][0]
+ is_me = person_id in self.req.get_user_person_ids()
+
+ if is_me and value >= 0:
+ minutes = int(value * 60)
+ self.task.set_estimation(minutes)
+ self.estimations[path][3] = minutes
+ self.update_general_info()
+
+ def on_arrow_button(self, widget):
+ """ Pop up the calendar window """
+ date_kind = GTGCalendar.DATE_KIND_START
+ try:
+ used_date = Date.parse(self.add_work_date.get_text())
+ except ValueError:
+ used_date = Date.no_day()
+ self.calendar.set_date(used_date, date_kind)
+ # we show the calendar at the right position
+ rect = widget.get_allocation()
+ x, y = widget.window.get_origin()
+ self.calendar.show_at_position(x + rect.x + rect.width,
+ y + rect.y)
+
+ def on_date_changed(self, widget):
+ """ User selected some date """
+ used_date, date_kind = self.calendar.get_selected_date()
+ self.add_work_date.set_text(str(used_date))
+
+ def on_add_work(self, widget):
+ """ Manually add new work """
+ try:
+ hours = float(self.add_work_entry.get_text().strip())
+ except ValueError:
+ return
+
+ if hours < 0:
+ return
+ minutes = int(hours * 60)
+
+ try:
+ day_str = self.add_work_date.get_text().strip()
+ day = datetime.strptime(day_str, '%Y-%m-%d')
+ day = convert_datetime_to_date(day)
+ except ValueError:
+ return
+
+ # find similar day
+ already_entered = False
+ for person_id, avatar, name, entry_date, hours in self.worked_hours:
+ if entry_date == day_str:
+ already_entered = True
+ break
+
+ if already_entered or day > date.today():
+ return
+
+ # find my identity
+ contacts = dict((person_id, (name, avatar))
+ for person_id, name, avatar in self.req.get_sharable_contacts())
+ person_id = self.task.get_my_person_id()
+ name, avatar = contacts.get(person_id, (person_id, None))
+
+ # enter into model
+ pixbuf = self.load_avatar(avatar)
+ self.worked_hours.append((person_id, pixbuf, name, day_str, minutes))
+ self.task.set_time_spent(minutes, day_str, person_id)
+
+ # Reset the form
+ self.add_work_entry.set_text("")
+ self.add_work_date.set_text("")
+ self.update_general_info()
+
+ def load_avatar(self, avatar):
+ """ Load avatar from file """
+ if avatar is None:
+ return None
+ else:
+ size = 24
+ return gtk.gdk.pixbuf_new_from_file_at_size(avatar,
+ size, size)
+
+ def update_general_info(self):
+ """ Update general info like task title and totals """
+ total_time = self.task.get_total_time()
+ est_time = self.task.get_estimation_time()
+ left_time = est_time - total_time
+
+ self.title_label.set_markup('<span size="xx-large">%s</span>' %
+ self.task.get_title())
+
+ self.total_label.set_markup(_("<b>Work done:</b> %s") %
+ display_tracking_long(total_time))
+ est_text = _("<b>Estimated:</b> %s") % \
+ display_tracking_long(est_time)
+
+ if left_time > 0:
+ left_text = _("%s left") % display_tracking_long(left_time)
+ else:
+ msg = _("%s over the estimation") % \
+ display_tracking_long(-left_time)
+ left_text = '<span color="red">%s overtime</span>' % msg
+
+ self.est_label.set_markup("%s (%s)" % (est_text, left_text))
+
+ def update(self):
+ """ Update information in window """
+ self.update_general_info()
+
+ contacts = dict((person_id, (name, avatar))
+ for person_id, name, avatar in self.req.get_sharable_contacts()
+ )
+
+ self.worked_hours.clear()
+ for person_id, date, minutes in self.task.get_all_tracking_records():
+ name, avatar = contacts.get(person_id, (person_id, None))
+ pixbuf = self.load_avatar(avatar)
+ self.worked_hours.append((person_id, pixbuf, name, date, minutes))
+
+ self.estimations.clear()
+ for person_id, minutes in self.task.get_all_estimations():
+ name, avatar = contacts.get(person_id, (person_id, None))
+ pixbuf = self.load_avatar(avatar)
+ self.estimations.append((person_id, pixbuf, name, minutes))
=== modified file 'GTG/gtk/editor/taskeditor.glade'
--- GTG/gtk/editor/taskeditor.glade 2013-02-11 10:48:10 +0000
+++ GTG/gtk/editor/taskeditor.glade 2013-05-22 05:15:33 +0000
@@ -1,26 +1,31 @@
-<?xml version="1.0"?>
+<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="TaskEditor">
+ <property name="can_focus">False</property>
<property name="title" translatable="yes">Task</property>
<property name="default_width">450</property>
<property name="default_height">400</property>
- <signal name="configure_event" handler="on_move"/>
+ <signal name="configure-event" handler="on_move" swapped="no"/>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="extension_events">cursor</property>
<child>
<object class="GtkToolbar" id="task_tb1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkToolButton" id="mark_as_done_editor">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="is_important">True</property>
<property name="label" translatable="yes">Mark Done</property>
<property name="icon_name">gtg-task-done</property>
- <signal name="clicked" handler="mark_as_done_clicked"/>
+ <signal name="clicked" handler="mark_as_done_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -30,9 +35,11 @@
<child>
<object class="GtkToolButton" id="dismiss_editor">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Dismiss</property>
<property name="icon_name">gtg-task-dismiss</property>
- <signal name="clicked" handler="on_dismiss"/>
+ <signal name="clicked" handler="on_dismiss" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -42,9 +49,11 @@
<child>
<object class="GtkToolButton" id="delete_editor">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Delete</property>
<property name="icon_name">edit-delete</property>
- <signal name="clicked" handler="delete_clicked"/>
+ <signal name="clicked" handler="delete_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -54,6 +63,7 @@
<child>
<object class="GtkSeparatorToolItem" id="toolbutton1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -63,9 +73,11 @@
<child>
<object class="GtkToolButton" id="insert_subtask">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Insert subtask</property>
<property name="stock_id">gtk-indent</property>
- <signal name="clicked" handler="on_insert_subtask_clicked"/>
+ <signal name="clicked" handler="on_insert_subtask_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -75,9 +87,35 @@
<child>
<object class="GtkMenuToolButton" id="inserttag">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
<property name="label" translatable="yes">Insert tag</property>
<property name="icon_name">gtg-tag-new</property>
- <signal name="clicked" handler="on_inserttag_clicked"/>
+ <signal name="clicked" handler="on_inserttag_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="report_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="report_button">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="is_important">True</property>
+ <property name="label" translatable="yes">Report</property>
+ <signal name="clicked" handler="on_report_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -87,6 +125,7 @@
<child>
<object class="GtkSeparatorToolItem" id="separator_note">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -96,12 +135,14 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow" id="scrolledtask">
<property name="width_request">400</property>
@@ -122,17 +163,22 @@
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@@ -141,11 +187,99 @@
</packing>
</child>
<child>
+ <object class="GtkHBox" id="estimationbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="estimation_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">My estimation for the task:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="estimation_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="on_estimation_changed" swapped="no"/>
+ <signal name="focus-out-event" handler="on_estimation_focus_out" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="estimation_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkHBox" id="tasksidebar">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="assignee_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Assignee:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="assignee">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Starting on</property>
<attributes>
@@ -154,21 +288,27 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">10</property>
- <property name="position">0</property>
+ <property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="startdate_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="invisible_char">●</property>
+ <property name="invisible_char">●</property>
<property name="width_chars">10</property>
- <signal name="changed" handler="startingdate_changed"/>
- <signal name="focus-out-event" handler="startdate_focus_out"/>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="startingdate_changed" swapped="no"/>
+ <signal name="focus-out-event" handler="startdate_focus_out" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -181,28 +321,33 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <signal name="clicked" handler="on_startdate_pressed"/>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_startdate_pressed" swapped="no"/>
<child>
<object class="GtkArrow" id="arrow2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="arrow_type">up</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
- <property name="position">1</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Due for</property>
<attributes>
@@ -213,20 +358,25 @@
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">10</property>
- <property name="position">2</property>
+ <property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="duedate_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="invisible_char">●</property>
+ <property name="invisible_char">●</property>
<property name="width_chars">10</property>
- <signal name="changed" handler="duedate_changed"/>
- <signal name="focus-out-event" handler="duedate_focus_out"/>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="duedate_changed" swapped="no"/>
+ <signal name="focus-out-event" handler="duedate_focus_out" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -239,28 +389,33 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <signal name="clicked" handler="on_duedate_pressed"/>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_duedate_pressed" swapped="no"/>
<child>
<object class="GtkArrow" id="arrow1">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="arrow_type">up</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
- <property name="position">3</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Closed on</property>
<attributes>
@@ -271,20 +426,25 @@
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">10</property>
- <property name="position">4</property>
+ <property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox4">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="closeddate_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="invisible_char">●</property>
+ <property name="invisible_char">●</property>
<property name="width_chars">10</property>
- <signal name="changed" handler="closeddate_changed"/>
- <signal name="focus-out-event" handler="closeddate_focus_out"/>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ <signal name="changed" handler="closeddate_changed" swapped="no"/>
+ <signal name="focus-out-event" handler="closeddate_focus_out" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -297,28 +457,33 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <signal name="clicked" handler="on_closeddate_pressed"/>
+ <property name="use_action_appearance">False</property>
+ <signal name="clicked" handler="on_closeddate_pressed" swapped="no"/>
<child>
<object class="GtkArrow" id="arrow3">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="arrow_type">up</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
- <property name="position">5</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="dayleft">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="xalign">1</property>
<property name="use_markup">True</property>
<property name="justify">right</property>
@@ -326,20 +491,132 @@
</object>
<packing>
<property name="expand">False</property>
+ <property name="fill">True</property>
<property name="padding">10</property>
- <property name="position">6</property>
- </packing>
- </child>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="position">3</property>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHSeparator" id="tracker_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHBox" id="timetrackerbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="tracker_today_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes"><b>Today:</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tracker_today">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tracker_total_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes"><b>Total:</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tracker_total">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tracker_est_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes"><b>Est:</b></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="tracker_est">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="use_markup">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">7</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkWindow" id="calendar">
+ <property name="can_focus">False</property>
<property name="events">GDK_STRUCTURE_MASK | GDK_PROXIMITY_OUT_MASK</property>
<property name="type">popup</property>
<property name="resizable">False</property>
@@ -352,18 +629,22 @@
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<child>
<object class="GtkCalendar" id="calendar1">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="fuzzydate_btns">
<property name="visible">True</property>
+ <property name="can_focus">False</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkButton" id="button_now">
@@ -371,8 +652,11 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
@@ -382,8 +666,11 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -393,13 +680,18 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
@@ -409,6 +701,7 @@
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
+ <property name="use_action_appearance">False</property>
<property name="use_stock">True</property>
</object>
<packing>
=== modified file 'GTG/gtk/manager.py'
--- GTG/gtk/manager.py 2013-02-25 08:29:31 +0000
+++ GTG/gtk/manager.py 2013-05-22 05:15:33 +0000
@@ -41,7 +41,7 @@
from GTG.tools.logger import Log
from GTG.gtk.backends_dialog import BackendsDialog
from GTG.backends.backendsignals import BackendSignals
-from GTG.gtk.browser.tag_editor import TagEditor
+from GTG.gtk.tag_editor.tag_editor import TagEditor
class Manager(object):
@@ -219,6 +219,13 @@
if backend_id is not None:
self.edit_backends_dialog.show_config_for_backend(backend_id)
+ def open_add_service(self, sender=None):
+ """ Open dialog for adding a new service """
+ if not self.edit_backends_dialog:
+ self.edit_backends_dialog = BackendsDialog(self.req)
+ self.edit_backends_dialog.activate()
+ self.edit_backends_dialog.on_add_button()
+
def configure_backend(self, backend_id):
self.open_edit_backends(None, backend_id)
@@ -236,13 +243,15 @@
if t.get_id() in self.opened_task:
self.close_task(t.get_id())
- def open_tag_editor(self, tag):
+ def open_tag_editor(self, tag, tab=None):
if not self.tag_editor_dialog:
self.tag_editor_dialog = TagEditor(self.req, self, tag)
else:
self.tag_editor_dialog.set_tag(tag)
self.tag_editor_dialog.show()
self.tag_editor_dialog.present()
+ if tab is not None:
+ self.tag_editor_dialog.set_tab(tab)
def close_tag_editor(self):
self.tag_editor_dialog.hide()
=== added file 'GTG/gtk/offline_mode.py'
--- GTG/gtk/offline_mode.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/offline_mode.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import gtk
+
+from GTG import _
+
+
+class OfflineMode(gtk.Window):
+ """ Resolve conflicts """
+
+ def __init__(self, req, vmanager, tag=None):
+ gtk.Window.__init__(self)
+ self.__gobject_init__()
+ self.req = req
+ self.vmanager = vmanager
+
+ self.set_position(gtk.WIN_POS_CENTER)
+ self.set_title('Oops!')
+ self.set_border_width(5)
+ self.set_resizable(False)
+
+ vbox = gtk.VBox()
+
+ vbox.pack_start(gtk.Label(_("Oops!")))
+ vbox.pack_start(gtk.Label(
+ _("Your updates conflict with the content stored online")))
+
+ hbox = gtk.HBox()
+
+ left = gtk.VBox()
+ tv = gtk.TextView()
+ tv.get_buffer().set_text("Remote task\n\n\n\n\n\n")
+ tv.set_editable(False)
+ left.pack_start(tv)
+ left.pack_start(gtk.Button(_("Use remote version")))
+
+ hbox.pack_start(left)
+
+ right = gtk.VBox()
+ tv = gtk.TextView()
+ tv.get_buffer().set_text("Local task\n\n\n\n\n\n")
+ right.pack_start(tv)
+ right.pack_start(gtk.Button(_("Use my version")))
+
+ hbox.pack_start(right)
+ hbox.set_spacing(10)
+
+ vbox.pack_start(hbox)
+
+ self.add(vbox)
+ # Make it visible
+ self.show_all()
=== added directory 'GTG/gtk/tag_editor'
=== added file 'GTG/gtk/tag_editor/__init__.py'
=== added file 'GTG/gtk/tag_editor/calendar_page.py'
--- GTG/gtk/tag_editor/calendar_page.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/tag_editor/calendar_page.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,312 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+"""
+TagCalendarPage implements settings for user's calendar for team projects
+"""
+
+import os
+import datetime as dt
+import time
+from webbrowser import open as openurl
+
+import gtk
+import gobject
+
+from GTG import _
+from GTG.tools.logger import Log
+
+# Default date format to display
+DATE_FORMAT = '%Y-%m-%d'
+
+
+class CellRendererDate(gtk.CellRendererText):
+ """ Render a date in cell and allow its editing
+
+ Inspired by http://faq.pygtk.org/index.py?req=edit&file=faq13.056.htp """
+
+ __gtype_name__ = 'CellRendererDate'
+
+ def __init__(self):
+ gtk.CellRendererText.__init__(self)
+ self.calendar_window = None
+ self.calendar = None
+
+ def _create_calendar(self, treeview):
+ """ Create a calendar to edit """
+ self.calendar_window = gtk.Dialog(parent=treeview.get_toplevel())
+ self.calendar_window.action_area.hide()
+ self.calendar_window.set_decorated(False)
+ self.calendar_window.set_property('skip-taskbar-hint', True)
+
+ self.calendar = gtk.Calendar()
+ self.calendar.display_options(
+ gtk.CALENDAR_SHOW_DAY_NAMES | gtk.CALENDAR_SHOW_HEADING)
+ self.calendar.connect('day-selected', self._day_selected, None)
+ self.calendar.connect('key-press-event', self._day_selected)
+ self.calendar.connect('focus-out-event', self._selection_cancelled)
+ # cancel the modality of dialog
+ self.calendar_window.set_transient_for(None)
+ self.calendar_window.vbox.pack_start(self.calendar)
+
+ # necessary for getting the (width, height) of calendar_window
+ self.calendar.show()
+ self.calendar_window.realize()
+
+ def do_start_editing(self, event, treeview, path, background_area,
+ cell_area, flags):
+ """ Allow user to edit the date """
+ if not self.get_property('editable'):
+ return
+
+ if self.calendar_window is None:
+ self._create_calendar(treeview)
+
+ # select cell's previously stored date if any exists - or today
+ if self.get_property('text'):
+ date = dt.datetime.strptime(self.get_property('text'),
+ DATE_FORMAT)
+ else:
+ date = dt.datetime.today()
+ # prevent flicker
+ self.calendar.freeze()
+ # datetime's month starts from one
+ (year, month, day) = (date.year, date.month - 1, date.day)
+ self.calendar.select_month(int(month), int(year))
+ self.calendar.select_day(int(day))
+ self.calendar.thaw()
+
+ # position the popup below the edited cell
+ # (try hard to keep the popup within the toplevel window)
+ tree_x, tree_y = treeview.get_bin_window().get_origin()
+ tree_w, tree_h = treeview.window.get_geometry()[2:4]
+ cal_w, cal_h = self.calendar_window.window.get_geometry()[2:4]
+ vis_rect = treeview.get_visible_rect()
+ vis_x, vis_y = vis_rect.x, vis_rect.y
+ x = tree_x + min(cell_area.x, tree_w - cal_w + vis_x)
+ y = tree_y + min(cell_area.y, tree_h - cal_h + vis_y)
+ self.calendar_window.move(x, y)
+
+ response = self.calendar_window.run()
+ if response == gtk.RESPONSE_OK:
+ (year, month, day) = self.calendar.get_date()
+ # gtk.Calendar's month starts from zero
+ date = dt.date(year, month + 1, day).strftime(DATE_FORMAT)
+ self.emit('edited', path, date)
+ self.calendar_window.hide()
+
+ # don't return any editable, our gtk.Dialog did the work already
+ return None
+
+ def _day_selected(self, calendar, event):
+ """ Handle the event of selected event """
+ if event is None or event.type == gtk.gdk.KEY_PRESS and \
+ gtk.gdk.keyval_name(event.keyval) == 'Return':
+ self.calendar_window.response(gtk.RESPONSE_OK)
+ return True
+
+ def _selection_cancelled(self, calendar, event):
+ """ Handle when selection was cancelled """
+ self.calendar_window.response(gtk.RESPONSE_CANCEL)
+ return True
+
+gobject.type_register(CellRendererDate)
+
+
+class TagCalendarPage(gtk.VBox):
+ """ Page for tag editor for settings Sharing settings """
+
+ DAY_NAMES = [_("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"),
+ _("Friday"), _("Saturday"), _("Sunday")]
+
+ # Usually 8-hour workday
+ STANDARD_DAY = 8
+
+ def __init__(self):
+ super(TagCalendarPage, self).__init__()
+ self.set_border_width(10)
+
+ self.top_label = gtk.Label()
+ self.top_label.set_alignment(0, 0)
+ self.pack_start(self.top_label, False, False)
+
+ first_weekday = self.get_first_weekday()
+ self.day_map = [(first_weekday + i) % 7 for i in range(7)]
+
+ grid = gtk.Table(2, 7, False)
+ self.day_buttons = [None] * 7
+ for i, day_ord in enumerate(self.day_map):
+ day_str = self.DAY_NAMES[day_ord]
+ label = gtk.Label(day_str)
+ label.set_alignment(0, 0)
+ grid.attach(label, 0, 1, i, i + 1, xoptions=gtk.FILL | gtk.EXPAND)
+ is_workday = day_ord < 5
+ day_hours = self.STANDARD_DAY if is_workday else 0
+ adjustment = gtk.Adjustment(day_hours, 0, 24, 1, 10, 0)
+ button = gtk.SpinButton()
+ button.connect('value-changed', self.on_hours_changed, day_ord)
+ button.set_adjustment(adjustment)
+ self.day_buttons[day_ord] = button
+ grid.attach(button, 1, 2, i, i + 1)
+ self.pack_start(grid, False, False)
+
+ exception_label = gtk.Label(_("Exceptions: "))
+ exception_label.set_alignment(0, 0)
+ self.pack_start(exception_label, False, False)
+
+ self.exceptions = gtk.ListStore(str, str)
+ self.exceptions_view = gtk.TreeView(self.exceptions)
+ exceptions_scroll = gtk.ScrolledWindow()
+ exceptions_scroll.add(self.exceptions_view)
+ exceptions_scroll.set_size_request(-1, 125)
+ self.pack_start(exceptions_scroll)
+
+ start_column = gtk.TreeViewColumn(_('From'))
+ start_column.set_expand(True)
+ start_cell = gtk.CellRendererText()
+ start_cell.set_property('editable', True)
+ start_cell.connect('edited', self.on_start_changed)
+ start_column.pack_start(start_cell, True)
+ start_column.add_attribute(start_cell, 'text', 0)
+ self.exceptions_view.append_column(start_column)
+
+ end_column = gtk.TreeViewColumn(_('Until'))
+ end_column.set_expand(True)
+ end_cell = gtk.CellRendererText()
+ end_cell.set_property('editable', True)
+ end_cell.connect('edited', self.on_end_changed)
+ end_column.pack_start(end_cell, True)
+ end_column.add_attribute(end_cell, 'text', 1)
+ self.exceptions_view.append_column(end_column)
+
+ hbox = gtk.HBox()
+ add_button = gtk.Button(_("Add"))
+ add_button.connect('clicked', self.on_add_exception)
+ hbox.pack_start(add_button)
+
+ self.remove_button = gtk.Button(_("Remove"))
+ self.remove_button.set_sensitive(False)
+ self.remove_button.connect('clicked', self.on_remove_exception)
+ exception_sel = self.exceptions_view.get_selection()
+ exception_sel.connect('changed', self.on_exception_sel_changed)
+ hbox.pack_start(self.remove_button)
+ self.pack_start(hbox, False, False)
+
+ buttonbox = gtk.HButtonBox()
+ buttonbox.set_layout(gtk.BUTTONBOX_START)
+ help_btn = gtk.Button(_("Help"))
+ help_btn.connect("clicked",
+ lambda w: openurl("help:gtg/gtg-calendar"))
+ buttonbox.pack_start(help_btn)
+ self.pack_start(buttonbox, False, False)
+
+ @classmethod
+ def get_first_weekday(cls):
+ """ Return first weekday where Monday=0 (ISO convention)
+
+ There is no direct way how to find the first day of week, see bug:
+ http://bugs.python.org/msg186280
+
+ The following workaround comes from hamster (GPL)
+ """
+ # set fallback to Sunday
+ first_weekday = 6
+
+ try:
+ process = os.popen("locale first_weekday week-1stday")
+ week_offset, week_start = process.read().split('\n')[:2]
+ process.close()
+ week_start = dt.date(*time.strptime(week_start, "%Y%m%d")[:3])
+ week_offset = dt.timedelta(int(week_offset) - 1)
+ beginning = week_start + week_offset
+ first_weekday = int(beginning.strftime("%w"))
+ except:
+ Log.warn("Failed to get first weekday from locale")
+
+ return first_weekday
+
+ def _update_top_label(self):
+ """ Update top label to correspond hours a week """
+ hoursweek = sum(button.get_value() for button in self.day_buttons)
+ self.top_label.set_text(_(
+ "I usually work on tasks with this tag on: (%d hours a week)") %
+ hoursweek)
+
+ def set_tag(self, tag):
+ """ Update page to reflect tag """
+ self.show_all()
+ self.tag = tag
+
+ for i in range(7):
+ hours = self.tag.get_calendar(i)
+ self.day_buttons[i].set_value(hours)
+ self._update_top_label()
+
+ self.exceptions.clear()
+ for start, end in self.tag.get_exceptions():
+ self.exceptions.append(start, end)
+
+ def on_hours_changed(self, widget, day_ord):
+ """ Hours were changed """
+ hours = int(widget.get_value())
+ self.tag.set_calendar(day_ord, int(hours))
+ self._update_top_label()
+
+ def on_add_exception(self, widget):
+ """ Add a new exception """
+ today_str = dt.date.today().strftime(DATE_FORMAT)
+ self.exceptions.append((today_str, today_str))
+ self.tag.add_calendar_exception(today_str, today_str)
+
+ def on_remove_exception(self, widget):
+ """ Remove the selected exception """
+ sel = self.exceptions_view.get_selection()
+ model, row_iter = sel.get_selected()
+ self.tag.remove_calendar_exception(
+ (model.get_value(row_iter, 0), model.get_value(row_iter, 1)))
+ model.remove(row_iter)
+
+ def on_exception_sel_changed(self, selection):
+ """ Remove button is sensitive only when there's a selected row """
+ is_sensitive = selection.count_selected_rows() > 0
+ self.remove_button.set_sensitive(is_sensitive)
+
+ def on_start_changed(self, widget, path, value):
+ """ Start value of an exception changed """
+ start = dt.datetime.strptime(value, DATE_FORMAT)
+ end = dt.datetime.strptime(self.exceptions[path][1], DATE_FORMAT)
+
+ self.exceptions[path][0] = value
+ # End always needs to be after start
+ if start > end:
+ self.remove_calendar_exception(start, end)
+ self.add_calendar_exception(value, end)
+ self.exceptions[path][1] = value
+
+ def on_end_changed(self, widget, path, value):
+ """ End value of an exception changed """
+ start = dt.datetime.strptime(self.exceptions[path][0], DATE_FORMAT)
+ end = dt.datetime.strptime(value, DATE_FORMAT)
+
+ # End always needs to be after start
+ if end < start:
+ self.remove_calendar_exception(start, end)
+ self.add_calendar_exception(start, value)
+ self.exceptions[path][0] = value
+ self.exceptions[path][1] = value
=== added file 'GTG/gtk/tag_editor/reports_page.py'
--- GTG/gtk/tag_editor/reports_page.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/tag_editor/reports_page.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,218 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+from webbrowser import open as openurl
+from datetime import datetime, timedelta, date
+import gtk
+import os
+
+from GTG import _
+
+from GTG.tools.dates import display_tracking_long, convert_datetime_to_date
+
+
+class TagReportsPage(gtk.VBox):
+ """ Page for tag editor for settings Sharing settings """
+
+ # Indeces for combobox
+ LAST_WEEK = 0
+ LAST_MONTH = 1
+ ALL_TIME = 2
+
+ def __init__(self, req):
+ super(TagReportsPage, self).__init__()
+ self.set_border_width(10)
+ empty = gtk.VBox()
+
+ self.req = req
+ self._chooser = None
+
+ hbox = gtk.HBox()
+ range_label = gtk.Label(_("Range: "))
+ range_label.set_alignment(0, 0)
+ hbox.pack_start(range_label)
+
+ self.combobox = gtk.combo_box_new_text()
+ self.combobox.append_text(_("Last 7 days"))
+ self.combobox.append_text(_("This month"))
+ self.combobox.append_text(_("All time"))
+ self.combobox.set_active(0)
+ self.combobox.connect('changed', self.on_range_changed)
+ hbox.pack_start(self.combobox)
+
+ empty.pack_start(hbox, False, False)
+
+ self.textview = gtk.TextView()
+ self.textview.set_editable(False)
+ empty.pack_start(self.textview)
+
+ self.pack_start(empty)
+
+ buttonbox = gtk.HButtonBox()
+ buttonbox.set_layout(gtk.BUTTONBOX_EDGE)
+ help_btn = gtk.Button(_("Help"))
+ help_btn.connect("clicked",
+ lambda w: openurl("help:gtg/gtg-reports"))
+ buttonbox.pack_start(help_btn)
+
+ export_button = gtk.Button(_("Export"))
+ export_button.connect('clicked', self.on_export_button)
+ buttonbox.pack_start(export_button)
+ self.pack_start(buttonbox, False, False)
+
+ def set_tag(self, tag):
+ """ Set values for dialog """
+ self.show_all()
+ self.tag = tag
+ self.on_range_changed()
+
+ def generate_report(self, range_type):
+ """ Generate a report """
+ human = {}
+ worked_tasks = {}
+ map_id_title = {}
+
+ if range_type == self.LAST_WEEK:
+ limit = date.today() - timedelta(days=7)
+ elif range_type == self.LAST_MONTH:
+ limit = date.today().replace(day=1)
+ else:
+ limit = None
+
+ tasktree = self.req.get_main_view()
+ tasks = tasktree.get_nodes(withfilters=[self.tag.get_name()])
+
+ active_minutes_left = 0
+
+ for task_id in tasks:
+ task = self.req.get_task(task_id)
+ if task.get_status() == task.STA_ACTIVE:
+ map_id_title[task_id] = task.get_title()
+ left_time = task.get_estimation_time() - task.get_total_time()
+ active_minutes_left += max(0, left_time)
+ elif task.get_status() == task.STA_DONE:
+ map_id_title[task_id] = _("%s (completed)") % task.get_title()
+ else:
+ continue
+
+ for person_id, day, minutes in task.get_all_tracking_records():
+ day = datetime.strptime(day, '%Y-%m-%d')
+ day = convert_datetime_to_date(day)
+
+ if limit is not None and day < limit:
+ # Filter tracking
+ continue
+
+ if person_id not in human:
+ human[person_id] = {}
+ if task_id not in human[person_id]:
+ human[person_id][task_id] = 0
+ human[person_id][task_id] += minutes
+
+ if task_id not in worked_tasks:
+ worked_tasks[task_id] = {}
+ if person_id not in worked_tasks[task_id]:
+ worked_tasks[task_id][person_id] = 0
+ worked_tasks[task_id][person_id] += minutes
+
+ dest = date.today() + timedelta(active_minutes_left / (8 * 60))
+
+ report = _("\nEstimated delivery: %s\n\n") % dest.strftime('%Y-%m-%d')
+
+ if len(human) == 0:
+ # No tasks available
+ if range_type == self.LAST_WEEK:
+ report += "Nothing was done over the last 7 days"
+ elif range_type == self.LAST_MONTH:
+ report += "Nothing was done this month"
+ else:
+ report += "No work was done yet"
+ return report
+
+ if range_type == self.LAST_WEEK:
+ report += "Work over the last 7 days:\n"
+ elif range_type == self.LAST_MONTH:
+ report += "Work this month:\n"
+ else:
+ report += "Work records:\n"
+
+ names = {person_id: name
+ for person_id, name, avatar in self.req.get_sharable_contacts()}
+
+ for person_id in sorted(human.keys()):
+ together = sum(human[person_id].values())
+ report += "\t%s -- %s\n" % (names.get(person_id, person_id),
+ display_tracking_long(together))
+
+ for task_id in human[person_id]:
+ report += "\t\t%s -- %s\n" % (map_id_title[task_id],
+ display_tracking_long(human[person_id][task_id]))
+
+ report += "\nTasks: \n"
+ tasks = [(map_id_title[task_id], task_id) for task_id in worked_tasks]
+ for title, task_id in sorted(tasks):
+ together = sum(worked_tasks[task_id].values())
+ report += "\t%s -- %s\n" % (title,
+ display_tracking_long(together))
+
+ for person_id in worked_tasks[task_id]:
+ report += "\t\t%s -- %s\n" % (names.get(person_id, person_id),
+ display_tracking_long(worked_tasks[task_id][person_id]))
+
+ return report
+
+ def on_range_changed(self, combobox=None):
+ """ Change range of report """
+ range_type = self.combobox.get_active()
+ buf = self.textview.get_buffer()
+ buf.set_text(self.generate_report(range_type))
+
+ def on_export_button(self, widget):
+ """ Store text of the report into a textfile """
+ if self._chooser is None:
+ self._chooser = gtk.FileChooserDialog(
+ title=_("Export report"),
+ parent=self.get_toplevel(),
+ action=gtk.FILE_CHOOSER_ACTION_SAVE,
+ buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+ self._chooser.set_do_overwrite_confirmation(True)
+ self._chooser.set_default_response(gtk.RESPONSE_OK)
+ self._chooser.set_current_folder(os.path.expanduser('~'))
+ self._chooser.set_do_overwrite_confirmation(True)
+
+ range_type = self.combobox.get_active()
+ if range_type == self.LAST_WEEK:
+ name_pattern = _("week-report-%Y-%m-%d.txt")
+ elif range_type == self.LAST_MONTH:
+ name_pattern = _("month-report-%Y-%m-%d.txt")
+ elif range_type == self.ALL_TIME:
+ name_pattern = _("report-%Y-%m-%d.txt")
+ else:
+ assert False, "Unknown range type"
+ name = datetime.today().strftime(name_pattern)
+ self._chooser.set_current_name(name)
+ response = self._chooser.run()
+ filename = self._chooser.get_filename()
+ self._chooser.hide()
+ if response == gtk.RESPONSE_OK:
+ with open(filename, 'w') as report_file:
+ buf = self.textview.get_buffer()
+ text = buf.get_text(buf.get_start_iter(), buf.get_end_iter())
+ report_file.write(text.strip() + '\n')
=== added file 'GTG/gtk/tag_editor/sharing_page.py'
--- GTG/gtk/tag_editor/sharing_page.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/tag_editor/sharing_page.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+"""
+TagSharingPage implements general settings for sharing tasks
+"""
+
+import gtk
+import gobject
+from webbrowser import open as openurl
+
+from GTG import _
+from GTG.backends.backendsignals import BackendSignals
+
+
+class TagSharingPage(gtk.VBox):
+ """ Page for tag editor for settings Sharing settings """
+
+ def __init__(self, req, manager):
+ super(TagSharingPage, self).__init__()
+ self.req = req
+ self.manager = manager
+ self.set_border_width(10)
+
+ # Widget when no sharing is available
+ self.empty = gtk.VBox()
+ empty_label = gtk.Label(_("None of your synchronization "
+ "services support sharing."))
+ empty_label.set_alignment(0, 0)
+ self.empty.pack_start(empty_label, False, False)
+ btn = gtk.Button(_("Add synchronization service"))
+ btn.connect("clicked", self.on_add_synchronization_service)
+ self.empty.pack_start(btn, True, False)
+ self.pack_start(self.empty)
+
+ # Widget when no sharing is available
+ self.start_sharing = gtk.VBox()
+ btn = gtk.Button(_("Share this tag"))
+ btn.connect("clicked", self.on_share_this_tag)
+ self.start_sharing.pack_start(btn, True, False)
+ self.pack_start(self.start_sharing)
+
+ # Widget for contacts
+ self.sharing = gtk.VBox()
+ sharing_label = gtk.Label(_("Tasks with this tag are shared with:"))
+ sharing_label.set_alignment(0, 0)
+ self.sharing.pack_start(sharing_label, False, False)
+ self.pack_start(self.sharing)
+
+ self.model = gtk.ListStore(str, 'gboolean', str, gtk.gdk.Pixbuf)
+ sw = gtk.ScrolledWindow()
+ self.sharing.pack_start(sw)
+
+ self.treeview = gtk.TreeView(self.model)
+ sw.add(self.treeview)
+
+ share_column = gtk.TreeViewColumn("Shared?")
+ self.treeview.append_column(share_column)
+ share_toggle = gtk.CellRendererToggle()
+ share_toggle.connect("toggled", self.on_share_toggled)
+ share_column.pack_start(share_toggle, True)
+ share_column.add_attribute(share_toggle, "active", 1)
+
+ person_column = gtk.TreeViewColumn("Person")
+ self.treeview.append_column(person_column)
+
+ person_name = gtk.CellRendererText()
+ person_column.pack_start(person_name, True)
+ person_column.add_attribute(person_name, "text", 2)
+
+ person_avatar = gtk.CellRendererPixbuf()
+ person_column.pack_start(person_avatar, False)
+ person_column.add_attribute(person_avatar, "pixbuf", 3)
+
+ # Confirmation dialog when unsharing
+ self.confirmation = None
+
+ buttonbox = gtk.HButtonBox()
+ buttonbox.set_layout(gtk.BUTTONBOX_START)
+ help_btn = gtk.Button(_("Help"))
+ help_btn.connect("clicked",
+ lambda w: openurl("help:gtg/gtg-sharing-settings"))
+ buttonbox.pack_start(help_btn)
+ self.pack_start(buttonbox, False, False)
+
+ def on_add_synchronization_service(self, widget):
+ """ Open dialog for selecting synchronization services
+ and hide this one"""
+ self.manager.open_add_service()
+ self.manager.close_tag_editor()
+
+ def set_tag(self, tag):
+ """ Refresh sharing page to reflect sharing for tag """
+ self.tag = tag
+ self.model.clear()
+ contacts = self.req.get_sharable_contacts()
+ teammates = list(self.tag.get_people_shared_with())
+ is_already_shared = False
+
+ # Hide all tabs
+ self.sharing.hide()
+ self.start_sharing.hide()
+ self.empty.hide()
+
+ if len(contacts) > 0:
+ for person_id, name, avatar in contacts:
+ if avatar is None:
+ pixbuf = None
+ else:
+ size = 48
+ pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(avatar,
+ size, size)
+
+ if person_id in teammates:
+ share = True
+ is_already_shared = True
+ teammates.remove(person_id)
+ else:
+ share = False
+
+ self.model.append((person_id, share, name, pixbuf))
+
+ # Add people user doesn't have in contacts
+ for person_id in teammates:
+ self.model.append((person_id, True, person_id, None))
+
+ if is_already_shared:
+ self.sharing.show_all()
+ else:
+ self.start_sharing.show_all()
+ else:
+ self.empty.show_all()
+
+ def on_share_this_tag(self, widget):
+ """ Enable sharing with me """
+ # User is on the first line
+ path = 0
+
+ # Enable sharing for user
+ self.model[path][1] = True
+ person_id = self.model[path][0]
+ self.tag.set_buddy_sharing(person_id, True)
+ BackendSignals().sharing_changed(self.tag.get_id())
+
+ # Change widget to contact widget
+ self.start_sharing.hide()
+ self.sharing.show_all()
+
+ def on_share_toggled(self, renderer, path):
+ """ Sharing settings for buddy should be toggled """
+ person_id, sharing_status, name, pixbuf = self.model[path]
+ sharing_status = not sharing_status
+ this_is_me = self.req.is_user_person_id(person_id)
+
+ if sharing_status is False:
+ if self.confirmation is None:
+ self.confirmation = gtk.MessageDialog(self.get_toplevel(),
+ type=gtk.MESSAGE_QUESTION,
+ buttons=gtk.BUTTONS_OK_CANCEL)
+
+ self.confirmation.set_title("Unsharing confirmation")
+
+ if this_is_me:
+ question = _("Are you sure you want to remove yourself? "
+ "You will <b>loose</b> all of your tasks with this tag.")
+ else:
+ question = _(
+ "Are you sure you want to unshare tasks with %s?") % name
+ self.confirmation.set_markup(question)
+ response = self.confirmation.run()
+ self.confirmation.hide()
+ # User doesn't want to do that
+ if response != gtk.RESPONSE_OK:
+ return
+
+ self.model[path][1] = sharing_status
+ self.tag.set_buddy_sharing(person_id, sharing_status)
+ BackendSignals().sharing_changed(self.tag.get_id())
+ if not sharing_status:
+ gobject.idle_add(self.unassign_tasks, person_id)
+
+ # Delete tag as announced
+ if this_is_me and sharing_status is False:
+ self.req.remove_tag(self.tag.get_id())
+ self.manager.close_tag_editor()
+
+ def unassign_tasks(self, person_id):
+ """ Unassign tasks from that person """
+ for task_id in self.tag.get_related_tasks():
+ task = self.req.get_task(task_id)
+ if task.get_assignee() == person_id:
+ task.set_assignee(None)
+ task.modified()
=== renamed file 'GTG/gtk/browser/simple_color_selector.py' => 'GTG/gtk/tag_editor/simple_color_selector.py'
=== renamed file 'GTG/gtk/browser/tag_editor.py' => 'GTG/gtk/tag_editor/tag_editor.py'
--- GTG/gtk/browser/tag_editor.py 2013-02-25 08:29:31 +0000
+++ GTG/gtk/tag_editor/tag_editor.py 2013-05-22 05:15:33 +0000
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Getting Things GNOME! - a personal organizer for the GNOME desktop
-# Copyright (c) 2008-2012 - Lionel Dricot & Bertrand Rousseau
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
#
# 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
@@ -16,164 +16,23 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------------------
-
"""
-tag_editor: this module contains two classes: TagIconSelector and TagEditor.
+tag_editor: this module definiton of tag editor
TagEditor implement a dialog window that can be used to edit the properties
of a tag.
-
-TagIconSelector is intended as a floating window that allows to select an icon
-for a tag.
"""
-import pygtk
-pygtk.require('2.0')
import gobject
import gtk
-import gtk.gdk as gdk
from GTG import _
-from GTG.tools.logger import Log
-from GTG.gtk.browser.simple_color_selector import SimpleColorSelector
-
-
-class TagIconSelector(gtk.Window):
- """
- TagIconSelector is intended as a floating window that allows to select
- an icon for a tag. It display a list of icon in a popup window.
- """
-
- def __init__(self):
- self.__gobject_init__(type=gtk.WINDOW_POPUP)
- gtk.Window.__init__(self)
- self.loaded = False
- self.selected_icon = None
- self.symbol_model = None
- # Build up the window
- self.__build_window()
- # Make it visible
- self.hide_all()
-
- def __build_window(self):
- """Build up the widget"""
- self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_POPUP_MENU)
- vbox = gtk.VBox()
- self.add(vbox)
- # icon list
- scld_win = gtk.ScrolledWindow()
- scld_win.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- scld_win.set_shadow_type(gtk.SHADOW_ETCHED_IN)
- vbox.pack_start(scld_win, expand=True, fill=True)
- self.symbol_iv = gtk.IconView()
- self.symbol_iv.set_pixbuf_column(0)
- self.symbol_iv.set_property("columns", 7)
- self.symbol_iv.set_property("item-width", 32)
- # IconView size:
- # --------------
- # it seems that with the above parameters, a row width is about:
- # item_count * (32px (item) + 6px (dflt padding) + 2px (spacing?)) \
- # + 2*6px (dflt widget margin)
- # The same goes for row height, but being right for this value is less
- # important due to the vertical scrollbar.
- # The IcVw size should fit the width of 7 cols and height of ~4 lines.
- self.symbol_iv.set_size_request(40 * 7 + 12, 38 * 4)
- scld_win.add(self.symbol_iv)
- # icon remove button
- img = gtk.Image()
- img.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON)
- self.remove_bt = gtk.Button()
- self.remove_bt.set_image(img)
- self.remove_bt.set_label(_("Remove selected icon"))
- vbox.pack_start(self.remove_bt, fill=False, expand=False)
- # set the callbacks
- self.symbol_iv.connect("selection-changed", self.on_selection_changed)
- self.remove_bt.connect("clicked", self.on_remove_bt_clicked)
-
- def __focus_out(self, widget, event):
- """Hides the window if the user clicks out of it"""
- win_ptr = self.window.get_pointer()
- win_size = self.get_size()
- if not(0 <= win_ptr[0] <= win_size[0] and
- 0 <= win_ptr[1] <= win_size[1]):
- self.close_selector()
-
- def __load_icon(self):
- """
- Loads emblem icons from the current icon theme
-
- Sometimes an icon can't be loaded because of a bug in system
- libraries, e.g. bug #1079587. Gracefuly degradate and skip
- the loading of a corrupted icon.
- """
- self.symbol_model = gtk.ListStore(gtk.gdk.Pixbuf, str)
- for icon in gtk.icon_theme_get_default().list_icons(context="Emblems"):
- try:
- img = gtk.icon_theme_get_default().load_icon(icon, 16, 0)
- self.symbol_model.append([img, icon])
- except gobject.GError:
- Log.error("Failed to load icon '%s'" % icon)
- self.symbol_iv.set_model(self.symbol_model)
- self.loaded = True
-
- ### PUBLIC IF ###
- def set_remove_enabled(self, enable):
- """Disable/enable the remove button"""
- self.remove_bt.set_sensitive(enable)
-
- ### callbacks ###
- def on_selection_changed(self, widget):
- """Callback: update the model according to the selected icon. Also
- notifies the parent widget."""
- my_path = self.symbol_iv.get_selected_items()
- if len(my_path) > 0:
- my_iter = self.symbol_model.get_iter(my_path[0])
- self.selected_icon = self.symbol_model.get_value(my_iter, 1)
- else:
- self.selected_icon = None
- self.emit('selection-changed')
- self.close_selector()
-
- def on_remove_bt_clicked(self, widget):
- """Callback: unselect the current icon"""
- self.selected_icon = None
- self.emit('selection-changed')
- self.close_selector()
-
- ### PUBLIC IF ###
- def show_at_position(self, pos_x, pos_y):
- """Displays the window at a specific point on the screen"""
- if not self.loaded:
- self.__load_icon()
- self.move(pos_x, pos_y)
- self.show_all()
- # some window managers ignore move before you show a window. (which
- # ones? question by invernizzi)
- self.move(pos_x, pos_y)
- self.grab_add()
- # We grab the pointer in the calendar
- gdk.pointer_grab(self.window, True,
- gdk.BUTTON1_MASK | gdk.MOD2_MASK)
- self.connect('button-press-event', self.__focus_out)
-
- def close_selector(self):
- """Hides the window"""
- self.hide()
- gtk.gdk.pointer_ungrab()
- self.grab_remove()
-
- def get_selected_icon(self):
- """Returns the selected icon. None if no icon is selected."""
- return self.selected_icon
-
- def unselect_icon(self):
- """Unselects all icon in the iconview."""
- self.symbol_iv.unselect_all()
-
-
-gobject.type_register(TagIconSelector)
-gobject.signal_new("selection-changed", TagIconSelector,
- gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
+from GTG.backends.backendsignals import BackendSignals
+from GTG.gtk.tag_editor.calendar_page import TagCalendarPage
+from GTG.gtk.tag_editor.reports_page import TagReportsPage
+from GTG.gtk.tag_editor.sharing_page import TagSharingPage
+from GTG.gtk.tag_editor.simple_color_selector import SimpleColorSelector
+from GTG.gtk.tag_editor.tag_icon_selector import TagIconSelector
class TagEditor(gtk.Window):
@@ -195,22 +54,36 @@
# Build up the window
self.set_position(gtk.WIN_POS_CENTER)
self.set_title('Edit tag')
- self.set_border_width(10)
+ self.set_border_width(5)
self.set_resizable(False)
+
+ self.notebook = gtk.Notebook()
+ self.add(self.notebook)
self.__build_window()
+
+ self.sharing_page = TagSharingPage(self.req, self.vmanager)
+ self.notebook.append_page(self.sharing_page, gtk.Label(_("Sharing")))
+
+ self.calendar_page = TagCalendarPage()
+ self.reports_page = TagReportsPage(self.req)
+
self.__set_callbacks()
+
+ # Make it visible
+ self.show_all()
+
+ # Set tag might hide sharing if no contact available
self.set_tag(tag)
- # Make it visible
- self.show_all()
def __build_window(self):
"""Build up the widget"""
# toplevel widget
self.top_vbox = gtk.VBox()
- self.add(self.top_vbox)
+ self.top_vbox.set_border_width(5)
+ self.notebook.append_page(self.top_vbox, gtk.Label(_("General")))
# header line: icon, table with name and "hide in wv"
self.hdr_align = gtk.Alignment()
- self.top_vbox.pack_start(self.hdr_align)
+ self.top_vbox.pack_start(self.hdr_align, False, False)
self.hdr_align.set_padding(0, 25, 0, 0)
self.hdr_hbox = gtk.HBox()
self.hdr_align.add(self.hdr_hbox)
@@ -243,7 +116,7 @@
self.tp_table.attach(self.tn_cb, 1, 2, 1, 2)
# Tag color
self.tc_vbox = gtk.VBox()
- self.top_vbox.pack_start(self.tc_vbox)
+ self.top_vbox.pack_start(self.tc_vbox, False, False)
self.tc_label_align = gtk.Alignment()
self.tc_vbox.pack_start(self.tc_label_align)
self.tc_label_align.set_padding(0, 0, 0, 0)
@@ -276,6 +149,8 @@
self.tc_cc_colsel.connect('color-added', self.on_tc_colsel_added)
self.connect('delete-event', self.on_close)
+ BackendSignals().connect('sharing-changed', self.on_sharing_changed)
+
# allow fast closing by Escape key
agr = gtk.AccelGroup()
self.add_accel_group(agr)
@@ -367,7 +242,48 @@
self.tn_cb.handler_unblock(self.tn_cb_clicked_hid)
self.tn_entry.handler_unblock(self.tn_entry_clicked_hid)
+ self.sharing_page.set_tag(self.tag)
+ self.update_sharing_widgets()
+
+ # Select the first Tab "General" in notebook
+ self.notebook.set_current_page(0)
+
+ def set_tab(self, tab):
+ """ Switch to tab """
+ index = 0
+ if tab == 'sharing':
+ index = 1
+ elif self.tag.is_shared():
+ if tab == 'calendar':
+ index = 2
+ elif tab == 'reports':
+ index = 3
+
+ if self.notebook.get_n_pages() <= index:
+ index = 0
+
+ self.notebook.set_current_page(index)
+
+ def update_sharing_widgets(self):
+ """ Show/hide widgets accessible only after sharing """
+ if self.tag.is_shared():
+ if self.notebook.get_n_pages() <= 2:
+ self.notebook.append_page(self.calendar_page,
+ gtk.Label(_("Calendar")))
+ self.notebook.append_page(self.reports_page,
+ gtk.Label(_("Reports")))
+ self.calendar_page.set_tag(self.tag)
+ self.reports_page.set_tag(self.tag)
+ else:
+ for i in range(2, self.notebook.get_n_pages()):
+ self.notebook.remove_page(i)
+
### CALLBACKS ###
+ def on_sharing_changed(self, sender, tag_id):
+ """ Update visibility of widgets after sharing changed """
+ if tag_id == self.tag.get_id():
+ self.update_sharing_widgets()
+
def watch_tn_entry_changes(self):
"""Monitors the value changes in the tag name entry. If no updates have
been noticed after 1 second, request an update."""
=== added file 'GTG/gtk/tag_editor/tag_icon_selector.py'
--- GTG/gtk/tag_editor/tag_icon_selector.py 1970-01-01 00:00:00 +0000
+++ GTG/gtk/tag_editor/tag_icon_selector.py 2013-05-22 05:15:33 +0000
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2013 - Lionel Dricot & Bertrand Rousseau
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+"""
+TagIconSelector is intended as a floating window that allows to select
+an icon for a tag.
+"""
+
+import gobject
+import gtk
+import gtk.gdk as gdk
+
+from GTG import _
+from GTG.tools.logger import Log
+
+
+class TagIconSelector(gtk.Window):
+ """
+ TagIconSelector is intended as a floating window that allows to select
+ an icon for a tag. It display a list of icon in a popup window.
+ """
+
+ def __init__(self):
+ self.__gobject_init__(type=gtk.WINDOW_POPUP)
+ gtk.Window.__init__(self)
+ self.loaded = False
+ self.selected_icon = None
+ self.symbol_model = None
+ # Build up the window
+ self.__build_window()
+ # Make it visible
+ self.hide_all()
+
+ def __build_window(self):
+ """Build up the widget"""
+ self.set_type_hint(gdk.WINDOW_TYPE_HINT_POPUP_MENU)
+ vbox = gtk.VBox()
+ self.add(vbox)
+ # icon list
+ scld_win = gtk.ScrolledWindow()
+ scld_win.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
+ scld_win.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+ vbox.pack_start(scld_win, expand=True, fill=True)
+ self.symbol_iv = gtk.IconView()
+ self.symbol_iv.set_pixbuf_column(0)
+ self.symbol_iv.set_property("columns", 7)
+ self.symbol_iv.set_property("item-width", 32)
+ # IconView size:
+ # --------------
+ # it seems that with the above parameters, a row width is about:
+ # item_count * (32px (item) + 6px (dflt padding) + 2px (spacing?)) \
+ # + 2*6px (dflt widget margin)
+ # The same goes for row height, but being right for this value is less
+ # important due to the vertical scrollbar.
+ # The IcVw size should fit the width of 7 cols and height of ~4 lines.
+ self.symbol_iv.set_size_request(40 * 7 + 12, 38 * 4)
+ scld_win.add(self.symbol_iv)
+ # icon remove button
+ img = gtk.Image()
+ img.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_BUTTON)
+ self.remove_bt = gtk.Button()
+ self.remove_bt.set_image(img)
+ self.remove_bt.set_label(_("Remove selected icon"))
+ vbox.pack_start(self.remove_bt, fill=False, expand=False)
+ # set the callbacks
+ self.symbol_iv.connect("selection-changed", self.on_selection_changed)
+ self.remove_bt.connect("clicked", self.on_remove_bt_clicked)
+
+ def __focus_out(self, widget, event):
+ """Hides the window if the user clicks out of it"""
+ win_ptr = self.window.get_pointer()
+ win_size = self.get_size()
+ if not(0 <= win_ptr[0] <= win_size[0] and
+ 0 <= win_ptr[1] <= win_size[1]):
+ self.close_selector()
+
+ def __load_icon(self):
+ """
+ Loads emblem icons from the current icon theme
+
+ Sometimes an icon can't be loaded because of a bug in system
+ libraries, e.g. bug #1079587. Gracefuly degradate and skip
+ the loading of a corrupted icon.
+ """
+ self.symbol_model = gtk.ListStore(gdk.Pixbuf, str)
+ for icon in gtk.icon_theme_get_default().list_icons(context="Emblems"):
+ try:
+ img = gtk.icon_theme_get_default().load_icon(icon, 16, 0)
+ self.symbol_model.append([img, icon])
+ except gobject.GError:
+ Log.error("Failed to load icon '%s'" % icon)
+ self.symbol_iv.set_model(self.symbol_model)
+ self.loaded = True
+
+ ### PUBLIC IF ###
+ def set_remove_enabled(self, enable):
+ """Disable/enable the remove button"""
+ self.remove_bt.set_sensitive(enable)
+
+ ### callbacks ###
+ def on_selection_changed(self, widget):
+ """Callback: update the model according to the selected icon. Also
+ notifies the parent widget."""
+ my_path = self.symbol_iv.get_selected_items()
+ if len(my_path) > 0:
+ my_iter = self.symbol_model.get_iter(my_path[0])
+ self.selected_icon = self.symbol_model.get_value(my_iter, 1)
+ else:
+ self.selected_icon = None
+ self.emit('selection-changed')
+ self.close_selector()
+
+ def on_remove_bt_clicked(self, widget):
+ """Callback: unselect the current icon"""
+ self.selected_icon = None
+ self.emit('selection-changed')
+ self.close_selector()
+
+ ### PUBLIC IF ###
+ def show_at_position(self, pos_x, pos_y):
+ """Displays the window at a specific point on the screen"""
+ if not self.loaded:
+ self.__load_icon()
+ self.move(pos_x, pos_y)
+ self.show_all()
+ # some window managers ignore move before you show a window. (which
+ # ones? question by invernizzi)
+ self.move(pos_x, pos_y)
+ self.grab_add()
+ # We grab the pointer in the calendar
+ gdk.pointer_grab(self.window, True,
+ gdk.BUTTON1_MASK | gdk.MOD2_MASK)
+ self.connect('button-press-event', self.__focus_out)
+
+ def close_selector(self):
+ """Hides the window"""
+ self.hide()
+ gdk.pointer_ungrab()
+ self.grab_remove()
+
+ def get_selected_icon(self):
+ """Returns the selected icon. None if no icon is selected."""
+ return self.selected_icon
+
+ def unselect_icon(self):
+ """Unselects all icon in the iconview."""
+ self.symbol_iv.unselect_all()
+
+
+gobject.type_register(TagIconSelector)
+gobject.signal_new("selection-changed", TagIconSelector,
+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
=== modified file 'GTG/tools/dates.py'
--- GTG/tools/dates.py 2013-02-25 08:29:31 +0000
+++ GTG/tools/dates.py 2013-05-22 05:15:33 +0000
@@ -79,6 +79,63 @@
return datetime.date(aday.year, aday.month, aday.day)
+def display_tracking_short(minutes):
+ """ Represent tracked minutes in a short format """
+ if minutes <= 59:
+ return _("%d min") % int(minutes)
+ else:
+ hours = minutes / 60.0
+ return _("%.1f h") % hours
+
+
+def parse_estimation(query):
+ """ Parse estimation return representing minutes
+ 1d = 8h, 1h = 60m """
+ # Replace full words by shortcuts
+ query = query.replace(_('days'), 'd')
+ query = query.replace(_('hours'), 'h')
+ query = query.replace(_('mins'), 'm')
+ query = query.replace(_('minutes'), 'm')
+ # Get rid of case "1 d 8 h" => "1d 8h"
+ query = query.replace(' d', 'd').replace(' h', 'h').replace(' m', 'm')
+ result = 0
+ for part in query.split():
+ if part.endswith('d'):
+ days = float(part[:-1])
+ if days < 0:
+ raise ValueError("Negative days")
+ result += int(days * 8 * 60)
+ elif part.endswith('h'):
+ hours = float(part[:-1])
+ if hours < 0:
+ raise ValueError("Negative hours")
+ result += int(hours * 60)
+ elif part.endswith('m'):
+ minutes = float(part[:-1])
+ if minutes < 0:
+ raise ValueError("Negative minutes")
+ result += int(minutes * 60)
+ else:
+ raise ValueError("Unknown member '%s'" % part)
+ return result
+
+
+def display_tracking_long(minutes):
+ """ Represent tracked minutes in a long format """
+ hours = minutes / 60.0
+ if minutes <= 59:
+ return _("%d minutes") % int(minutes)
+ elif hours < 8:
+ if hours == int(hours):
+ # Get rid of the decimal point
+ return ("%d hours") % int(hours)
+ else:
+ return _("%.1f hours") % hours
+ else:
+ days = float(hours / 8.0)
+ return _("%.1f days") % days
+
+
class Date(object):
"""A date class that supports fuzzy dates.
=== modified file 'GTG/tools/logger.py'
--- GTG/tools/logger.py 2013-02-25 07:35:07 +0000
+++ GTG/tools/logger.py 2013-05-22 05:15:33 +0000
@@ -54,6 +54,11 @@
ch.setFormatter(formatter)
Debug.__logger.addHandler(ch)
+ # Don't propagate to regular logging module
+ # This is important when a library like sleekxmpp uses logging
+ # and logs are written twice
+ self.propagate = False
+
def __getattr__(self, attr):
""" Delegates to the real logger """
return getattr(Debug.__logger, attr)
=== modified file 'GTG/tools/taskxml.py'
--- GTG/tools/taskxml.py 2013-02-25 08:29:31 +0000
+++ GTG/tools/taskxml.py 2013-05-22 05:15:33 +0000
@@ -44,6 +44,7 @@
# Take an empty task, an XML node and return a Task.
def task_from_xml(task, xmlnode):
+ """ Load task from XML node into Task object """
# print "********************************"
# print xmlnode.toprettyxml()
@@ -60,6 +61,10 @@
startdate = Date(read_node(xmlnode, "startdate"))
task.set_start_date(startdate)
+ assignee = read_node(xmlnode, "assignee")
+ if assignee != "":
+ task.set_assignee(assignee)
+
modified = read_node(xmlnode, "modified")
if modified != "":
modified = datetime.strptime(modified, "%Y-%m-%dT%H:%M:%S")
@@ -90,6 +95,31 @@
namespace = attr.getAttribute("namespace")
task.set_attribute(key, value, namespace=namespace)
+ time_tracking = xmlnode.getElementsByTagName("time-tracking")
+ if len(time_tracking) > 0:
+ for record in time_tracking[0].getElementsByTagName("record"):
+ person = record.getAttribute('person')
+ day = record.getAttribute('day')
+ try:
+ minutes = int(record.getAttribute('minutes'))
+ except ValueError:
+ minutes = 0
+
+ if person and day and minutes > 0:
+ task.set_time_spent(minutes, day, person)
+
+ estimations = xmlnode.getElementsByTagName("estimations")
+ if len(estimations) > 0:
+ for estimation in estimations[0].getElementsByTagName("estimation"):
+ person = estimation.getAttribute('person')
+ try:
+ minutes = int(estimation.getAttribute('minutes'))
+ except ValueError:
+ minutes = 0
+
+ if person and minutes > 0:
+ task.set_estimation(minutes, person)
+
# FIXME do we need remote task ids? I don't think so
# FIXME if so => rework them into a more usable structure!!!
# (like attributes)
@@ -111,6 +141,7 @@
def task_to_xml(doc, task):
+ """ Convert Task object into XML node"""
t_xml = doc.createElement("task")
t_xml.setAttribute("id", task.get_id())
t_xml.setAttribute("status", task.get_status())
@@ -126,6 +157,9 @@
task.get_start_date().xml_str())
cleanxml.addTextNode(doc, t_xml, "donedate",
task.get_closed_date().xml_str())
+ assignee = task.get_assignee()
+ if assignee:
+ cleanxml.addTextNode(doc, t_xml, "assignee", assignee)
childs = task.get_children()
for c in childs:
cleanxml.addTextNode(doc, t_xml, "subtask", c)
@@ -149,6 +183,27 @@
cleanxml.addTextNode(doc, t_xml, "content", desc)
# self.__write_textnode(doc,t_xml,"content",t.get_text())
+ records = task.get_all_tracking_records()
+ if len(records) > 0:
+ time_tracking = doc.createElement("time-tracking")
+ t_xml.appendChild(time_tracking)
+ for person_id, day, minutes in records:
+ record = doc.createElement("record")
+ record.setAttribute("person", person_id)
+ record.setAttribute("day", day)
+ record.setAttribute("minutes", str(minutes))
+ time_tracking.appendChild(record)
+
+ estimations = task.get_all_estimations()
+ if len(estimations) > 0:
+ estimations_node = doc.createElement("estimations")
+ t_xml.appendChild(estimations_node)
+ for person_id, minutes in estimations:
+ est = doc.createElement("estimation")
+ est.setAttribute("person", person_id)
+ est.setAttribute("minutes", str(minutes))
+ estimations_node.appendChild(est)
+
# REMOTE TASK IDS
remote_ids_element = doc.createElement("task-remote-ids")
t_xml.appendChild(remote_ids_element)
=== added file 'data/icons/hicolor/scalable/apps/backend_pubsub.svg'
--- data/icons/hicolor/scalable/apps/backend_pubsub.svg 1970-01-01 00:00:00 +0000
+++ data/icons/hicolor/scalable/apps/backend_pubsub.svg 2013-05-22 05:15:33 +0000
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="176.486px" height="181.437px" viewBox="0 0 176.486 181.437" enable-background="new 0 0 176.486 181.437" xml:space="preserve">
+<pattern x="-307.662" y="395.593" width="69" height="69" patternUnits="userSpaceOnUse" id="Polka_Dot_Pattern" viewBox="2.125 -70.896 69 69" overflow="visible">
+ <g>
+ <polygon fill="none" points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896"/>
+ <polygon fill="#F7BC60" points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896"/>
+ <g>
+ <path fill="#FFFFFF" d="M61.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19C0.361-71.362,0.3-71.4,0.248-71.335 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <path fill="#FFFFFF" d="M0.495-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224C0.5-71.68,0.503-71.744,0.51-71.626 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143C2-61.45,2.217-61.397,2.391-61.46c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-56.374,0.503-56.438,0.51-56.32 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C8.15-41.004,8.149-41.02,8.14-41.04"/>
+ <path fill="#FFFFFF" d="M0.495-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-33.416,0.503-33.48,0.51-33.362 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-18.11,0.503-18.175,0.51-18.057 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362C69-9.692,69.159-9.523,69.154-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053C17.933-7.969,17.839-8.227,18-8.34 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M8.156-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C7.915-10.05,7.866-9.836,7.886-9.75C7.717-9.692,7.876-9.523,7.871-9.4C7.868-9.351,7.83-9.295,7.826-9.239 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114-7.652,9.321-7.799,9.48-7.837c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M0.495-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C0.254-10.05,0.205-9.836,0.225-9.75C0.056-9.692,0.215-9.523,0.21-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-8.671,0.501-8.456,0.668-8.325c0.19,0.148,0.365,0.572,0.608,0.631 C1.454-7.652,1.66-7.799,1.819-7.837C2-7.88,2.217-7.827,2.391-7.89c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46C3.477-8.933,3.471-8.995,3.5-9.071 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ </g>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M69.439-2.778c0.018,0.072,0.008,0.127-0.026,0.19C69.361-2.487,69.3-2.525,69.248-2.46 c-0.051,0.062-0.099,0.276-0.079,0.362C69-2.04,69.159-1.871,69.154-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C70.397,0,70.604-0.146,70.763-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M61.778-2.778c0.018,0.072,0.007,0.127-0.026,0.19C61.7-2.487,61.64-2.525,61.587-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C62.737,0,62.943-0.146,63.103-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C61.915-3.117,61.78-3.02,61.781-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M54.118-2.778c0.018,0.072,0.007,0.127-0.026,0.19C54.04-2.487,53.98-2.525,53.927-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C55.077,0,55.283-0.146,55.442-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C54.255-3.117,54.12-3.02,54.121-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M46.458-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C47.416,0,47.623-0.146,47.782-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C46.594-3.117,46.459-3.02,46.46-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M38.797-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C39.756,0,39.962-0.146,40.122-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C38.934-3.117,38.799-3.02,38.8-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M31.137-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C32.095,0,32.302-0.146,32.461-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C31.273-3.117,31.139-3.02,31.14-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M23.477-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C24.435,0,24.642-0.146,24.801-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"/>
+ <path fill="#FFFFFF" d="M15.816-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C16.774,0,16.981-0.146,17.14-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C15.81-2.74,15.809-2.756,15.8-2.776"/>
+ <path fill="#FFFFFF" d="M8.156-2.778c0.018,0.072,0.007,0.127-0.026,0.19C8.077-2.487,8.018-2.525,7.965-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35C7.868-1.698,7.83-1.643,7.826-1.587 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114,0,9.321-0.146,9.48-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C8.954-3.54,8.847-3.448,8.692-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C8.292-3.117,8.158-3.02,8.159-2.92C8.16-2.805,8.164-2.869,8.17-2.751 C8.15-2.74,8.149-2.756,8.14-2.776"/>
+ <path fill="#FFFFFF" d="M0.495-2.778c0.018,0.072,0.008,0.127-0.026,0.19C0.417-2.487,0.356-2.525,0.304-2.46 C0.253-2.397,0.205-2.184,0.225-2.098C0.056-2.04,0.215-1.871,0.21-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-1.019,0.501-0.804,0.668-0.673c0.19,0.148,0.365,0.572,0.608,0.631 C1.454,0,1.66-0.146,1.819-0.185C2-0.228,2.217-0.175,2.391-0.237c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46C3.477-1.28,3.471-1.343,3.5-1.419 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C1.293-3.54,1.187-3.448,1.031-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C0.632-3.117,0.498-3.02,0.498-2.92C0.5-2.805,0.503-2.869,0.51-2.751 C0.489-2.74,0.488-2.756,0.479-2.776"/>
+ </g>
+ </g>
+</pattern>
+<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="-1807.2036" y1="125.8584" x2="-1807.2036" y2="4.882812e-04" gradientTransform="matrix(1, 0, 0, 1, 1915.98, 0)">
+ <stop offset="0.011" style="stop-color: rgb(27, 57, 103);"/>
+ <stop offset="0.467" style="stop-color: rgb(19, 181, 234);"/>
+ <stop offset="0.9945" style="stop-color: rgb(0, 43, 92);"/>
+</linearGradient>
+<path fill="url(#SVGID_1_)" d="M136.293,14.189c0.077,1.313-1.786,0.968-1.786,2.293c0,38.551-44.72,96.831-89.847,108.194v1.182 C104.613,120.342,171.384,59.05,172.89,0L136.293,14.189z"/>
+<path fill="#E96D1F" d="M120.229,17.96c0.077,1.313,0.121,2.633,0.121,3.958c0,38.551-30.7,90.497-75.827,101.861v1.637 c59.065-3.823,105.807-63.023,105.807-109.195c0-2.375-0.125-4.729-0.371-7.056L120.229,17.96z"/>
+<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-1073.228" y1="126.8516" x2="-1073.228" y2="1.278977e-13" gradientTransform="matrix(-1, 0, 0, 1, -1008.19, 0)">
+ <stop offset="0.011" style="stop-color: rgb(27, 57, 103);"/>
+ <stop offset="0.467" style="stop-color: rgb(19, 181, 234);"/>
+ <stop offset="0.9945" style="stop-color: rgb(0, 43, 92);"/>
+</linearGradient>
+<path fill="url(#SVGID_2_)" d="M36.599,14.189c-0.077,1.313,1.787,0.968,1.787,2.293c0,38.551,46.558,97.366,91.688,108.729v1.639 C70.117,121.334,1.51,59.05,0,0L36.599,14.189z"/>
+<path fill="#A0CE67" d="M54.731,18.932c-0.076,1.313-0.12,2.63-0.12,3.957c0,38.551,30.699,90.497,75.827,101.861v1.639 C71.396,123.597,24.63,63.366,24.63,17.192c0-2.375,0.128-4.729,0.371-7.056L54.731,18.932z"/>
+<path fill="#439639" d="M24.713,9.583l7.617,2.722c-0.041,0.962-0.066,2.254-0.066,3.225c0,41.219,37.271,98.204,87.272,107.12 c3.245,1.088,7.538,2.077,10.932,2.931v1.638C65.216,121.659,19.368,55.354,24.713,9.583z"/>
+<path fill="#D9541E" d="M150.338,8.76l-7.833,2.625c0.041,0.963,0.191,2.203,0.191,3.173c0,41.219-37.272,98.205-87.274,107.12 c-3.243,1.089-7.538,2.077-10.93,2.932v1.639C112.838,117.593,155.684,54.531,150.338,8.76z"/>
+<g>
+ <path d="M14.576,166.709L1.188,152.062h11.643l9.127,10.268l9.129-10.268H42.73l-13.387,14.646l14.401,14.728H31.654l-9.697-10.67 l-9.693,10.67H0.172L14.576,166.709z"/>
+ <path d="M47.096,152.062h13.836l10.183,18.905l10.183-18.905H95.13v29.374h-8.762v-21.096h-0.08l-11.807,21.096h-6.733 l-11.807-21.096h-0.082v21.096h-8.764V152.062z"/>
+ <path d="M101.246,152.062h24.546c8.559,0,10.628,4.302,10.628,10.063v2.516c0,4.381-1.908,9.41-8.275,9.41h-17.894v7.385h-9.005 V152.062z M110.251,166.75h13.997c2.109,0,2.924-1.377,2.924-3.123v-1.135c0-1.99-0.976-3.127-3.694-3.127h-13.227V166.75z"/>
+ <path d="M141.311,152.062h24.546c8.561,0,10.63,4.302,10.63,10.063v2.516c0,4.381-1.907,9.41-8.275,9.41h-17.893v7.385h-9.008 V152.062z M150.318,166.75h13.996c2.11,0,2.922-1.377,2.922-3.123v-1.135c0-1.99-0.974-3.127-3.693-3.127h-13.225V166.75z"/>
+</g>
+</svg>
Follow ups