pyface-4.1.0/0000755000175100001440000000000011674464650013761 5ustar ischnellusers00000000000000pyface-4.1.0/TODO.txt0000644000175100001440000000112011674463363015261 0ustar ischnellusers00000000000000* Remove the import of pyface.resource for core functionality in resource_manager.py and i_image_resource.py. * Remove the dependency on etsdevtools.developer AND apptools.io by refactoring dock features modules. * Remove the dock dependence on TraitsBackendWX by removing the direct imports of traitsui.wx. * Need to make use of drag and drop features optional in this project's code (see dock, grid, sheet, tree, and viewer/tree_viewer) or help make traits.util's drag_and_drop module backend agnostic. Right now, it has a runtime dependency that wx be installed. pyface-4.1.0/LICENSE.txt0000644000175100001440000000312011674463363015600 0ustar ischnellusers00000000000000This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. Copyright (c) 2006, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Enthought, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyface-4.1.0/image_LICENSE_Eclipse.txt0000644000175100001440000002604311674463363020417 0ustar ischnellusers00000000000000Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENTS ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i)changes to the Program, and ii)additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributors behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipients responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributors responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipients patent(s), then such Recipients rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipients rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipients rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipients obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. pyface-4.1.0/pyface/0000755000175100001440000000000011674463363015230 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/timer/0000755000175100001440000000000011674463363016350 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/timer/timer.py0000644000175100001440000000046611674463363020050 0ustar ischnellusers00000000000000"""A timer that invokes a specified callback periodically. """ # Author: Prabhu Ramachandran # Copyright (c) 2006-2007, Enthought, Inc. # License: BSD Style. # Import the toolkit specific version. from pyface.toolkit import toolkit_object Timer = toolkit_object('timer.timer:Timer') pyface-4.1.0/pyface/timer/api.py0000644000175100001440000000131611674463363017474 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from timer import Timer from do_later import do_later, do_after, DoLaterTimer pyface-4.1.0/pyface/timer/do_later.py0000644000175100001440000000407211674463363020516 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ #------------------------------------------------------------------------------- # # Provides a simple function for scheduling some code to run at some time in # the future (assumes application is wxPython based). # # Written by: David C. Morrill # # Date: 05/18/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- # Import the toolkit specific version. from pyface.toolkit import toolkit_object DoLaterTimer = toolkit_object('timer.do_later:DoLaterTimer') #------------------------------------------------------------------------------- # Does something 50 milliseconds from now: #------------------------------------------------------------------------------- def do_later ( callable, *args, **kw_args ): """ Does something 50 milliseconds from now. """ DoLaterTimer( 50, callable, args, kw_args ) #------------------------------------------------------------------------------- # Does something after some specified time interval: #------------------------------------------------------------------------------- def do_after ( interval, callable, *args, **kw_args ): """ Does something after some specified time interval. """ DoLaterTimer( interval, callable, args, kw_args ) pyface-4.1.0/pyface/timer/__init__.py0000644000175100001440000000000011674463363020447 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/viewer/0000755000175100001440000000000011674463363016531 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/viewer/tree_viewer.py0000644000175100001440000005121311674463363021425 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A viewer based on a tree control. """ # Major package imports. import wx # Enthought library imports. from pyface.image_list import ImageList from traits.api import Any, Bool, Enum, Event, Instance, List from pyface.wx.drag_and_drop import PythonDropSource # Local imports. from content_viewer import ContentViewer from tree_content_provider import TreeContentProvider from tree_label_provider import TreeLabelProvider class TreeViewer(ContentViewer): """ A viewer based on a tree control. """ # The default tree style. STYLE = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN #### 'TreeViewer' interface ############################################### # The content provider provides the actual tree data. content_provider = Instance(TreeContentProvider) # The label provider provides, err, the labels for the items in the tree # (a label can have text and/or an image). label_provider = Instance(TreeLabelProvider, ()) # Selection mode (must be either of 'single' or 'extended'). selection_mode = Enum('single', 'extended') # The currently selected elements. selection = List # Should an image be shown for each element? show_images = Bool(True) # Should the root of the tree be shown? show_root = Bool(True) #### Events #### # An element has been activated (ie. double-clicked). element_activated = Event # A drag operation was started on an element. element_begin_drag = Event # An element that has children has been collapsed. element_collapsed = Event # An element that has children has been expanded. element_expanded = Event # A left-click occurred on an element. element_left_clicked = Event # A right-click occurred on an element. element_right_clicked = Event # A key was pressed while the tree is in focus. key_pressed = Event ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, image_size=(16, 16), **traits): """ Creates a new tree viewer. 'parent' is the toolkit-specific control that is the tree's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the label images (if any) displayed in the tree. """ # Base class constructor. super(TreeViewer, self).__init__(**traits) # Create the toolkit-specific control. self.control = tree = wx.TreeCtrl(parent, -1, style=self._get_style()) # Get our actual Id. wxid = tree.GetId() # Wire up the wx tree events. wx.EVT_CHAR(tree, self._on_char) wx.EVT_LEFT_DOWN(tree, self._on_left_down) wx.EVT_RIGHT_DOWN(tree, self._on_right_down) wx.EVT_TREE_ITEM_ACTIVATED(tree, wxid, self._on_tree_item_activated) wx.EVT_TREE_ITEM_COLLAPSED(tree, wxid, self._on_tree_item_collapsed) wx.EVT_TREE_ITEM_COLLAPSING(tree, wxid, self._on_tree_item_collapsing) wx.EVT_TREE_ITEM_EXPANDED(tree, wxid, self._on_tree_item_expanded) wx.EVT_TREE_ITEM_EXPANDING(tree, wxid, self._on_tree_item_expanding) wx.EVT_TREE_BEGIN_LABEL_EDIT(tree, wxid,self._on_tree_begin_label_edit) wx.EVT_TREE_END_LABEL_EDIT(tree, wxid, self._on_tree_end_label_edit) wx.EVT_TREE_BEGIN_DRAG(tree, wxid, self._on_tree_begin_drag) wx.EVT_TREE_SEL_CHANGED(tree, wxid, self._on_tree_sel_changed) # The image list is a wxPython-ism that caches all images used in the # control. self._image_list = ImageList(image_size[0], image_size[1]) if self.show_images: tree.AssignImageList(self._image_list) # Mapping from element to wx tree item Ids. self._element_to_id_map = {} # Add the root item. if self.input is not None: self._add_element(None, self.input) return ########################################################################### # 'TreeViewer' interface. ########################################################################### def is_expanded(self, element): """ Returns True if the element is expanded, otherwise False. """ key = self._get_key(element) if key in self._element_to_id_map: is_expanded = self.control.IsExpanded(self._element_to_id_map[key]) else: is_expanded = False return is_expanded def is_selected(self, element): """ Returns True if the element is selected, otherwise False. """ key = self._get_key(element) if key in self._element_to_id_map: is_selected = self.control.IsSelected(self._element_to_id_map[key]) else: is_selected = False return is_selected def refresh(self, element): """ Refresh the tree starting from the specified element. Call this when the STRUCTURE of the content has changed. """ # Has the element actually appeared in the tree yet? pid = self._element_to_id_map.get(self._get_key(element), None) if pid is not None: # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. populated, element = self.control.GetPyData(pid) # fixme: We should find a cleaner way other than deleting all of # the element's children and re-adding them! self._delete_children(pid) self.control.SetPyData(pid, (False, element)) # Does the element have any children? has_children = self.content_provider.has_children(element) self.control.SetItemHasChildren(pid, has_children) # Expand it. self.control.Expand(pid) else: print '**** pid is None!!! ****' return def update(self, element): """ Update the tree starting from the specified element. Call this when the APPEARANCE of the content has changed. """ pid = self._element_to_id_map.get(self._get_key(element), None) if pid is not None: self._refresh_element(pid, element) for child in self.content_provider.get_children(element): cid = self._element_to_id_map.get(self._get_key(child), None) if cid is not None: self._refresh_element(cid, child) return ########################################################################### # Private interface. ########################################################################### def _get_style(self): """ Returns the wx style flags for creating the tree control. """ # Start with the default flags. style = self.STYLE if not self.show_root: style = style | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT if self.selection_mode != 'single': style = style | wx.TR_MULTIPLE | wx.TR_EXTENDED return style def _add_element(self, pid, element): """ Adds 'element' as a child of the element identified by 'pid'. If 'pid' is None then we are adding the root element. """ # Get the tree item image index and text. image_index = self._get_image_index(element) text = self._get_text(element) # Add the element. if pid is None: wxid = self.control.AddRoot(text, image_index, image_index) else: wxid = self.control.AppendItem(pid, text, image_index, image_index) # If we are adding the root element but the root is hidden, get its # children. if pid is None and not self.show_root: children = self.content_provider.get_children(element) for child in children: self._add_element(wxid, child) # Does the element have any children? has_children = self.content_provider.has_children(element) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. if pid is None: if self.show_root: self.control.SetPyData(wxid, (False, element)) else: self.control.SetPyData(wxid, (False, element)) # Make sure that we can find the element's Id later. self._element_to_id_map[self._get_key(element)] = wxid # If we are adding the root item then automatically expand it. if pid is None and self.show_root: self.control.Expand(wxid) return def _get_image_index(self, element): """ Returns the tree item image index for an element. """ # Get the image used to represent the element. image = self.label_provider.get_image(self, element) if image is not None: image_index = self._image_list.GetIndex(image.absolute_path) else: image_index = -1 return image_index def _get_key(self, element): """ Generate the key for the element to id map. """ try: key = hash(element) except: key = id(element) return key def _get_text(self, element): """ Returns the tree item text for an element. """ text = self.label_provider.get_text(self, element) if text is None: text = '' return text def _refresh_element(self, wxid, element): """ Refreshes the image and text of the specified element. """ # Get the tree item image index. image_index = self._get_image_index(element) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Normal) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Selected) # Get the tree item text. text = self._get_text(element) self.control.SetItemText(wxid, text) # Does the item have any children? has_children = self.content_provider.has_children(element) self.control.SetItemHasChildren(wxid, has_children) return def _unpack_event(self, event): """ Unpacks the event to see whether a tree element was involved. """ try: point = event.GetPosition() except: point = event.GetPoint() wxid, flags = self.control.HitTest(point) # Warning: On GTK we have to check the flags before we call 'GetPyData' # because if we call it when the hit test returns 'nowhere' it will # barf (on Windows it simply returns 'None' 8^() if flags & wx.TREE_HITTEST_NOWHERE: data = None else: data = self.control.GetPyData(wxid) return data, wxid, flags, point def _get_selection(self): """ Returns a list of the selected elements. """ elements = [] for wxid in self.control.GetSelections(): data = self.control.GetPyData(wxid) if data is not None: populated, element = data elements.append(element) # 'data' can be None here if (for example) the element has been # deleted. # # fixme: Can we stop this happening?!?!? else: pass return elements def _delete_children(self, pid): """ Recursively deletes the children of the specified element. """ cookie = 0 (cid, cookie) = self.control.GetFirstChild(pid, cookie) while cid.IsOk(): # Recursively delete the child's children. self._delete_children(cid) # Remove the reference to the item's data. populated, element = self.control.GetPyData(cid) del self._element_to_id_map[self._get_key(element)] self.control.SetPyData(cid, None) # Next! (cid, cookie) = self.control.GetNextChild(pid, cookie) self.control.DeleteChildren(pid) return #### Trait event handlers ################################################# def _input_changed(self): """ Called when the tree's input has been changed. """ # Delete everything... if self.control is not None: self.control.DeleteAllItems() self._element_to_id_map = {} # ... and then add the root item back in. if self.input is not None: self._add_element(None, self.input) return def _element_begin_drag_changed(self, element): """ Called when a drag is started on a element. """ # We ask the label provider for the actual value to drag. drag_value = self.label_provider.get_drag_value(self, element) # Start the drag. PythonDropSource(self.control, drag_value) return #### wx event handlers #################################################### def _on_right_down(self, event): """ Called when the right mouse button is clicked on the tree. """ data, id, flags, point = self._unpack_event(event) # Did the right click occur on a tree item? if data is not None: populated, element = data # Trait notification. self.element_right_clicked = (element, point) # Give other event handlers a chance. event.Skip() return def _on_left_down(self, event): """ Called when the left mouse button is clicked on the tree. """ data, wxid, flags, point = self._unpack_event(event) # Save point for tree_begin_drag method to workaround a bug in ?? when # wx.TreeEvent.GetPoint returns only (0,0). This happens under linux # when using wx-2.4.2.4, for instance. self._point_left_clicked = point # Did the left click occur on a tree item? if data is not None: populated, element = data # Trait notification. self.element_left_clicked = (element, point) # Give other event handlers a chance. event.Skip() return def _on_tree_item_expanding(self, event): """ Called when a tree item is about to expand. """ # Which item is expanding? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Give the label provider a chance to veto the expansion. if self.label_provider.is_expandable(self, element): # Lazily populate the item's children. if not populated: children = self.content_provider.get_children(element) # Sorting... if self.sorter is not None: self.sorter.sort(self, element, children) # Filtering.... for child in children: for filter in self.filters: if not filter.select(self, element, child): break else: self._add_element(wxid, child) # The element is now populated! self.control.SetPyData(wxid, (True, element)) else: event.Veto() return def _on_tree_item_expanded(self, event): """ Called when a tree item has been expanded. """ # Which item was expanded? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Make sure that the element's 'open' icon is displayed etc. self._refresh_element(wxid, element) # Trait notification. self.element_expanded = element return def _on_tree_item_collapsing(self, event): """ Called when a tree item is about to collapse. """ # Which item is collapsing? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Give the label provider a chance to veto the collapse. if not self.label_provider.is_collapsible(self, element): event.Veto() return def _on_tree_item_collapsed(self, event): """ Called when a tree item has been collapsed. """ # Which item was collapsed? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Make sure that the element's 'closed' icon is displayed etc. self._refresh_element(wxid, element) # Trait notification. self.element_collapsed = element return def _on_tree_item_activated(self, event): """ Called when a tree item is activated (i.e., double clicked). """ # Which item was activated? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Trait notification. self.element_activated = element return def _on_tree_sel_changed(self, event): """ Called when the selection is changed. """ # Trait notification. self.selection = self._get_selection() return def _on_tree_begin_drag(self, event): """ Called when a drag operation is starting on a tree item. """ # Get the element, its id and the point where the event occurred. data, wxid, flags, point = self._unpack_event(event) if point == (0,0): # Apply workaround. point = self._point_left_clicked wxid, flags = self.control.HitTest(point) data = self.control.GetPyData(wxid) if data is not None: populated, element = data # Trait notification. self.element_begin_drag = element return def _on_tree_begin_label_edit(self, event): """ Called when the user has started editing an item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Give the label provider a chance to veto the edit. if not self.label_provider.is_editable(self, element): event.Veto() return def _on_tree_end_label_edit(self, event): """ Called when the user has finished editing an item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, element = self.control.GetPyData(wxid) # Give the label provider a chance to veto the edit. label = event.GetLabel() if not self.label_provider.set_text(self, element, label): event.Veto() return def _on_char(self, event): """ Called when a key is pressed when the tree has focus. """ # Trait notification. self.key_pressed = event.GetKeyCode() return #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/table_column_provider.py0000644000175100001440000000305311674463363023462 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all table column providers. """ # Enthought library imports. from traits.api import Int # Local imports. from column_provider import ColumnProvider class TableColumnProvider(ColumnProvider): """ Base class for all table label providers. By default an item has no icon, and 'str' is used to generate its label. """ # This class currently does not specialize the base class in any way. # It is here to (hopefully) make the APIs for the viewer, content and label # provider classes more consistent. In particular, if you are building a # table viewer you sub-class 'TableContentProvider' and # 'TableLabelProvider'. For a tree viewer you sub-class # 'TreeContentProvider' and 'TreeLabelProvider' instead of some # combination of the specific and generic viewer classes as in JFace. pass #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/table_viewer.py0000644000175100001440000002766211674463363021570 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A viewer for tabular data. """ # Major package imports. import wx # Enthought library imports. from pyface.image_list import ImageList from traits.api import Color, Event, Instance, Trait # Local imports. from content_viewer import ContentViewer from table_column_provider import TableColumnProvider from table_content_provider import TableContentProvider from table_label_provider import TableLabelProvider class TableViewer(ContentViewer): """ A viewer for tabular data. """ # The content provider provides the actual table data. content_provider = Instance(TableContentProvider) # The label provider provides, err, the labels for the items in the table # (a label can have text and/or an image). label_provider = Instance(TableLabelProvider, factory = TableLabelProvider) # The column provider provides information about the columns in the table # (column headers, width etc). column_provider=Trait(TableColumnProvider(),Instance(TableColumnProvider)) # The colours used to render odd and even numbered rows. even_row_background = Color("white") odd_row_background = Color((245, 245, 255)) # A row has been selected. row_selected = Event # A row has been activated. row_activated = Event # A drag operation was started on a node. row_begin_drag = Event def __init__(self, parent, image_size=(16, 16), **traits): """ Creates a new table viewer. 'parent' is the toolkit-specific control that is the table's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the images (if any) displayed in the table. """ # Base-class constructor. super(TableViewer, self).__init__(**traits) # Create the toolkit-specific control. self.control = table = _Table(parent, image_size, self) # Get our actual id. wxid = table.GetId() # Table events. wx.EVT_LIST_ITEM_SELECTED(table, wxid, self._on_item_selected) wx.EVT_LIST_ITEM_ACTIVATED(table, wxid, self._on_item_activated) wx.EVT_LIST_BEGIN_DRAG(table, wxid, self._on_list_begin_drag) wx.EVT_LIST_BEGIN_RDRAG(table, wxid, self._on_list_begin_rdrag) wx.EVT_LIST_BEGIN_LABEL_EDIT( table, wxid, self._on_list_begin_label_edit ) wx.EVT_LIST_END_LABEL_EDIT( table, wxid, self._on_list_end_label_edit ) # fixme: Bug[732104] indicates that this event does not get fired # in a virtual list control (it *does* get fired in a regular list # control 8^(). wx.EVT_LIST_ITEM_DESELECTED(table, wxid, self._on_item_deselected) # Create the widget! self._create_widget(parent) # We use a dynamic handler instead of a static handler here, as we # don't want to react if the input is set in the constructor. self.on_trait_change(self._on_input_changed, 'input') return ########################################################################### # 'TableViewer' interface. ########################################################################### def select_row(self, row): """ Select the specified row. """ self.control.SetItemState( row, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED ) self.control.SetItemState( row, wx.LIST_STATE_FOCUSED, wx.LIST_STATE_FOCUSED ) # Make sure that the selected row is visible. fudge = max(0, row - 5) self.EnsureVisible(fudge) # Trait event notification. self.row_selected = row return ########################################################################### # Trait event handlers. ########################################################################### def _on_input_changed(self, obj, trait_name, old, new): """ Called when the input is changed. """ # Update the table contents. self._update_contents() if old is None: self._update_column_widths() return ########################################################################### # wx event handlers. ########################################################################### def _on_item_selected(self, event): """ Called when an item in the list is selected. """ # Get the index of the row that was selected (nice wx interface huh?!). row = event.m_itemIndex # Trait event notification. self.row_selected = row return # fixme: Bug[732104] indicates that this event does not get fired in a # virtual list control (it *does* get fired in a regular list control 8^(). def _on_item_deselected(self, event): """ Called when an item in the list is selected. """ # Trait event notification. self.row_selected = -1 return def _on_item_activated(self, event): """ Called when an item in the list is activated. """ # Get the index of the row that was activated (nice wx interface!). row = event.m_itemIndex # Trait event notification. self.row_activated = row return def _on_list_begin_drag(self, event=None, is_rdrag=False): """ Called when a drag operation is starting on a list item. """ # Trait notification. self.row_begin_drag = event.GetIndex() return def _on_list_begin_rdrag(self, event=None): """ Called when a drag operation is starting on a list item. """ self._on_list_begin_drag(event, True) return def _on_list_begin_label_edit(self, event=None): """ Called when a label edit is started. """ event.Veto() return def _on_list_end_label_edit(self, event=None): """ Called when a label edit is completed. """ return ########################################################################### # Private interface. ########################################################################### FORMAT_MAP = { 'left' : wx.LIST_FORMAT_LEFT, 'right' : wx.LIST_FORMAT_RIGHT, 'center' : wx.LIST_FORMAT_CENTRE, 'centre' : wx.LIST_FORMAT_CENTRE } def _create_widget(self, parent): """ Creates the widget. """ # Set up a default list item descriptor. info = wx.ListItem() info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT # Set the column headers. for index in range(self.column_provider.column_count): # Header text. info.m_text = self.column_provider.get_label(self, index) # Alignment of header text AND ALL cells in the column. alignment = self.column_provider.get_alignment(self, index) info.m_format = self.FORMAT_MAP.get(alignment, wx.LIST_FORMAT_LEFT) self.control.InsertColumnInfo(index, info) # Update the table contents and the column widths. self._update_contents() self._update_column_widths() return def _update_contents(self): """ Updates the table content. """ self._elements = [] if self.input is not None: # Filtering... for element in self.content_provider.get_elements(self.input): for filter in self.filters: if not filter.select(self, self.input, element): break else: self._elements.append(element) # Sorting... if self.sorter is not None: self.sorter.sort(self, self.input, self._elements) # Setting this causes a refresh! self.control.SetItemCount(len(self._elements)) return def _update_column_widths(self): """ Updates the column widths. """ # Set all columns to be the size of their largest item, or the size of # their header whichever is the larger. for column in range(self.control.GetColumnCount()): width = self.column_provider.get_width(self, column) if width == -1: width = self._get_column_width(column) self.control.SetColumnWidth(column, width) return def _get_column_width(self, column): """ Return an appropriate width for the specified column. """ self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE_USEHEADER) header_width = self.control.GetColumnWidth(column) if self.control.GetItemCount() == 0: width = header_width else: self.control.SetColumnWidth(column, wx.LIST_AUTOSIZE) data_width = self.control.GetColumnWidth(column) width = max(header_width, data_width) return width class _Table(wx.ListCtrl): """ The wx control that we use to implement the table viewer. """ # Default style. STYLE = wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES | wx.STATIC_BORDER \ | wx.LC_SINGLE_SEL | wx.LC_VIRTUAL | wx.LC_EDIT_LABELS \ | wx.CLIP_CHILDREN def __init__(self, parent, image_size, viewer): """ Creates a new table viewer. 'parent' is the toolkit-specific control that is the table's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the icons (if any) displayed in the table. """ # The vierer that we are providing the control for. self._viewer = viewer # Base-class constructor. wx.ListCtrl.__init__(self, parent, -1, style=self.STYLE) # Table item images. self._image_list = ImageList(image_size[0], image_size[1]) self.AssignImageList(self._image_list, wx.IMAGE_LIST_SMALL) # Set up attributes to show alternate rows with a different background # colour. self._even_row_attribute = wx.ListItemAttr() self._even_row_attribute.SetBackgroundColour( self._viewer.even_row_background ) self._odd_row_attribute = wx.ListItemAttr() self._odd_row_attribute.SetBackgroundColour( self._viewer.odd_row_background ) return ########################################################################### # Virtual 'ListCtrl' interface. ########################################################################### def OnGetItemText(self, row, column_index): """ Returns the text for the specified CELL. """ viewer = self._viewer element = viewer._elements[row] return viewer.label_provider.get_text(viewer, element, column_index) def OnGetItemImage(self, row): """ Returns the image for the specified ROW. """ viewer = self._viewer element = viewer._elements[row] # Get the icon used to represent the node. image = viewer.label_provider.get_image(viewer, element) if image is not None: image_index = self._image_list.GetIndex(image.absolute_path) else: image_index = -1 return image_index def OnGetItemAttr(self, row): """ Returns the attribute for the specified row. """ if row % 2 == 0: attribute = self._even_row_attribute else: attribute = self._odd_row_attribute return attribute #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/api.py0000644000175100001440000000251011674463363017652 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from column_provider import ColumnProvider from content_provider import ContentProvider from content_viewer import ContentViewer from default_tree_content_provider import DefaultTreeContentProvider from label_provider import LabelProvider from table_column_provider import TableColumnProvider from table_content_provider import TableContentProvider from table_label_provider import TableLabelProvider from table_viewer import TableViewer from tree_content_provider import TreeContentProvider from tree_label_provider import TreeLabelProvider from tree_item import TreeItem from tree_viewer import TreeViewer from viewer import Viewer from viewer_filter import ViewerFilter from viewer_sorter import ViewerSorter pyface-4.1.0/pyface/viewer/content_viewer.py0000644000175100001440000000421211674463363022135 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all content viewers. """ # Enthought library imports. from traits.api import Any, Instance, List # Local imports. from viewer import Viewer from viewer_filter import ViewerFilter from viewer_sorter import ViewerSorter class ContentViewer(Viewer): """ Abstract base class for all content viewers. A content viewer is a model-based adapter on some underlying toolkit-specific widget that acceses the model via a content provider and a label provider. The content provider provides the actual elements in the model. The label provider provides a label for each element consisting of text and/or an image. """ # The domain object that is the root of the viewer's data. input = Any # The content provider provides the data elements for the viewer. # # Derived classes specialize this trait with the specific type of the # content provider that they require (e.g. the tree viewer MUST have a # 'TreeContentProvider'). content_provider = Any # The label provider provides labels for each element. # # Derived classes specialize this trait with the specific type of the label # provider that they require (e.g. the table viewer MUST have a # 'TableLabelProvider'). label_provider = Any # The viewer's sorter (None if no sorting is required). sorter = Instance(ViewerSorter) # The viewer's filters. filters = List(ViewerFilter) #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/tree_item.py0000644000175100001440000001010111674463363021051 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A generic base-class for items in a tree data structure. An example:- root = TreeItem(data='Root') fruit = TreeItem(data='Fruit') fruit.append(TreeItem(data='Apple', allows_children=False)) fruit.append(TreeItem(data='Orange', allows_children=False)) fruit.append(TreeItem(data='Pear', allows_children=False)) root.append(fruit) veg = TreeItem(data='Veg') veg.append(TreeItem(data='Carrot', allows_children=False)) veg.append(TreeItem(data='Cauliflower', allows_children=False)) veg.append(TreeItem(data='Sprout', allows_children=False)) root.append(veg) """ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, List, Property class TreeItem(HasTraits): """ A generic base-class for items in a tree data structure. """ #### 'TreeItem' interface ################################################# # Does this item allow children? allows_children = Bool(True) # The item's children. children = List(Instance('TreeItem')) # Arbitrary data associated with the item. data = Any # Does the item have any children? has_children = Property(Bool) # The item's parent. parent = Instance('TreeItem') ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the informal string representation of the object. """ if self.data is None: s = '' else: s = str(self.data) return s ########################################################################### # 'TreeItem' interface. ########################################################################### #### Properties ########################################################### # has_children def _get_has_children(self): """ True iff the item has children. """ return len(self.children) != 0 #### Methods ############################################################## def append(self, child): """ Appends a child to this item. This removes the child from its current parent (if it has one). """ return self.insert(len(self.children), child) def insert(self, index, child): """ Inserts a child into this item at the specified index. This removes the child from its current parent (if it has one). """ if child.parent is not None: child.parent.remove(child) child.parent = self self.children.insert(index, child) return child def remove(self, child): """ Removes a child from this item. """ child.parent = None self.children.remove(child) return child def insert_before(self, before, child): """ Inserts a child into this item before the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(before) self.insert(index, child) return (index, child) def insert_after(self, after, child): """ Inserts a child into this item after the specified item. This removes the child from its current parent (if it has one). """ index = self.children.index(after) self.insert(index + 1, child) return (index, child) #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/tree_label_provider.py0000644000175100001440000000411611674463363023115 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all tree label providers. """ # Local imports. from label_provider import LabelProvider class TreeLabelProvider(LabelProvider): """ Base class for all tree label providers. By default an element has no label image, and 'str' is used to generate its label text. """ ########################################################################### # 'LabelProvider' interface. ########################################################################### def set_text(self, viewer, element, text): """ Sets the text representation of a node. Returns True if setting the text succeeded, otherwise False. """ return len(text.strip()) > 0 ########################################################################### # 'TreeLabelProvider' interface. ########################################################################### def get_drag_value(self, viewer, element): """ Get the value that is dragged for an element. By default the drag value is the element itself. """ return element def is_collapsible(self, viewer, element): """ Returns True is the element is collapsible, otherwise False. """ return True def is_expandable(self, viewer, node): """ Returns True is the node is expandanble, otherwise False. """ return True #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/content_provider.py0000644000175100001440000000267111674463363022475 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for content providers. """ # Enthought library imports. from traits.api import HasTraits class ContentProvider(HasTraits): """ Abstract base class for content providers. """ ######################################################################### # 'ContentProvider' interface. ######################################################################### def get_elements(self, element): """ Returns a list of the elements to display in a viewer. Returns a list of elements to display in a viewer when its (ie. the viewer's) input is set to the given element. The returned list should not be modified by the viewer. """ raise NotImplementedError #### EOF #################################################################### pyface-4.1.0/pyface/viewer/viewer.py0000644000175100001440000000175511674463363020414 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all viewers. """ # Enthought library imports. from pyface.widget import Widget class Viewer(Widget): """ Abstract base class for all viewers. A viewer is a model-based adapter on some underlying toolkit-specific widget. """ pass #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/viewer_sorter.py0000644000175100001440000001005211674463363022000 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all viewer sorters. """ # Enthought library imports. from traits.api import HasTraits class ViewerSorter(HasTraits): """ Abstract base class for all viewer sorters. """ ########################################################################### # 'ViewerSorter' interface. ########################################################################### def sort(self, viewer, parent, elements): """ Sorts a list of elements IN PLACE. 'viewer' is the viewer that we are sorting elements for. 'parent' is the parent element. 'elements' is the list of elements to sort. Returns the list that was sorted IN PLACE (for convenience). """ # This creates a comparison function with the names 'viewer' and # 'parent' bound to the corresponding arguments to this method. def comparator(element_a, element_b): """ Comparator. """ return self.compare(viewer, parent, element_a, element_b) elements.sort(comparator) return elements def compare(self, viewer, parent, element_a, element_b): """ Returns the result of comparing two elements. 'viewer' is the viewer that we are sorting elements for. 'parent' is the parent element. 'element_a' is the the first element to compare. 'element_b' is the the second element to compare. """ # Get the category for each element. category_a = self.category(viewer, parent, element_a) category_b = self.category(viewer, parent, element_b) # If they are not in the same category then return the result of # comparing the categories. if category_a != category_b: result = cmp(category_a, category_b) else: # Get the label text for each element. # # fixme: This is a hack until we decide whethwe we like the # JFace(ish) or Swing(ish) models! if hasattr(viewer, 'label_provider'): label_a = viewer.label_provider.get_text(viewer, element_a) label_b = viewer.label_provider.get_text(viewer, element_b) else: label_a = viewer.node_model.get_text(viewer, element_a) label_b = viewer.node_model.get_text(viewer, element_b) # Compare the label text. result = cmp(label_a, label_b) return result def category(self, viewer, parent, element): """ Returns the category (an integer) for an element. 'parent' is the parent element. 'elements' is the element to return the category for. Categories are used to sort elements into bins. The bins are arranged in ascending numeric order. The elements within a bin are arranged as dictated by the sorter's 'compare' method. By default all elements are given the same category (0). """ return 0 def is_sorter_trait(self, element, trait_name): """ Is the sorter affected by changes to an element's trait? 'element' is the element. 'trait_name' is the name of the trait. Returns True if the sorter would be affected by changes to the trait named 'trait_name' on the specified element. By default we return False. """ return False #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/table_label_provider.py0000644000175100001440000000300111674463363023235 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all table label providers. """ # Local imports. from label_provider import LabelProvider class TableLabelProvider(LabelProvider): """ Base class for all table label providers. By default an item has no icon, and 'str' is used to generate its label. """ ########################################################################### # 'TableLabelProvider' interface. ########################################################################### def get_image(self, viewer, element, column_index=0): """ Returns the filename of the label image for an element. """ return None def get_text(self, viewer, element, column_index=0): """ Returns the label text for an element. """ return '%s column %d' % (str(element), column_index) #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/viewer_filter.py0000644000175100001440000000440611674463363021755 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all viewer filters. """ # Enthought library imports. from traits.api import HasTraits class ViewerFilter(HasTraits): """ Abstract base class for all viewer filters. """ ########################################################################### # 'ViewerFilter' interface. ########################################################################### def filter(self, viewer, parent, elements): """ Filters a list of elements. 'viewer' is the viewer that we are filtering elements for. 'parent' is the parent element. 'elements' is the list of elements to filter. Returns a list containing only those elements for which 'select' returns True. """ return [e for e in elements if self.select(viewer, parent, e)] def select(self, viewer, parent, element): """ Returns True if the element is 'allowed' (ie. NOT filtered). 'viewer' is the viewer that we are filtering elements for. 'parent' is the parent element. 'element' is the element to select. By default we return True. """ return True def is_filter_trait(self, element, trait_name): """ Is the filter affected by changes to an element's trait? 'element' is the element. 'trait_name' is the name of the trait. Returns True if the filter would be affected by changes to the trait named 'trait_name' on the specified element. By default we return False. """ return False #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/__init__.py0000644000175100001440000000120111674463363020634 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/viewer/default_tree_content_provider.py0000644000175100001440000000560711674463363025222 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The default tree content provider. """ # Local imports. from tree_content_provider import TreeContentProvider class DefaultTreeContentProvider(TreeContentProvider): """ The default tree content provider. """ ######################################################################### # 'TreeContentProvider' interface. ######################################################################### def get_parent(self, item): """ Returns the parent of an item. """ return item.parent def get_children(self, item): """ Returns the children of an item. """ return item.children def has_children(self, item): """ True iff the item has children. """ return item.has_children ######################################################################### # 'DefaultTreeContentProvider' interface. ######################################################################### def append(self, parent, child): """ Appends 'child' to the 'parent' item. """ return self.insert(parent, len(parent.children), child) def insert_before(self, parent, before, child): """ Inserts 'child' into 'parent' item before 'before'. """ index, child = parent.insert_before(before, child) # Trait notification. #self.items_inserted(parent, [index], [child]) return (index, child) def insert(self, parent, index, child): """ Inserts 'child' into the 'parent' item at 'index'. """ parent.insert(index, child) # Trait notification. #self.items_inserted(parent, [index], [child]) return child def remove(self, parent, child): """ Removes 'child' from the 'parent' item. """ index = parent.children.index(child) parent.remove(child) # Trait notification. #self.items_removed(parent, [index], [child]) return child ######################################################################### # Protected interface. ######################################################################### def _create_item(self, **kw): """ Creates a new item. """ return TreeItem(**kw) #### EOF #################################################################### pyface-4.1.0/pyface/viewer/tree_content_provider.py0000644000175100001440000000436311674463363023514 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for tree content providers. """ # Local imports. from content_provider import ContentProvider class TreeContentProvider(ContentProvider): """ Abstract base class for tree content providers. Tree content providers are used by (surprise, surprise) tree viewers! """ ######################################################################### # 'ContentProvider' interface. ######################################################################### def get_elements(self, element): """ Returns a list of the elements to display in a viewer. Returns a list of elements to display in a viewer when its (ie. the viewer's) input is set to the given element. The returned list should not be modified by the viewer. """ return get_children(element) ######################################################################### # 'TreeContentProvider' interface. ######################################################################### def get_parent(self, element): """ Returns the parent of an element. Returns None if the element either has no parent (ie. it is the root of the tree), or if the parent cannot be computed. """ return None def get_children(self, element): """ Returns the children of an element. """ raise NotImplementedError def has_children(self, element): """ Returns True iff the element has children, otherwise False. """ raise NotImplementedError #### EOF #################################################################### pyface-4.1.0/pyface/viewer/table_content_provider.py0000644000175100001440000000300011674463363023627 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for table content providers. """ # Local imports. from content_provider import ContentProvider class TableContentProvider(ContentProvider): """ Abstract base class for table content providers. Table content providers are used by (surprise, surprise) table viewers! """ # This class currently does not specialize the base class in any way. # It is here to (hopefully) make the APIs for the viewer, content and label # provider classes more consistent. In particular, if you are building a # table viewer you sub-class 'TableContentProvider' and # 'TableLabelProvider'. For a tree viewer you sub-class # 'TreeContentProvider' and 'TreeLabelProvider' instead of some # combination of the specific and generic viewer classes as in JFace. pass #### EOF #################################################################### pyface-4.1.0/pyface/viewer/label_provider.py0000644000175100001440000000340211674463363022073 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for label providers. """ # Enthought library imports. from traits.api import HasTraits class LabelProvider(HasTraits): """ Abstract base class for label providers. By default an element has no label image, and 'str' is used to generate its label text. """ ########################################################################### # 'LabelProvider' interface. ########################################################################### def get_image(self, viewer, element): """ Returns the label image for an element. """ return None def get_text(self, viewer, element): """ Returns the label text for an element. """ return str(element) def set_text(self, tree, element, text): """ Sets the text representation of a node. Returns True if setting the text succeeded, otherwise False. """ return True def is_editable(self, viewer, element): """ Can the label text be changed via the viewer? """ return False #### EOF ###################################################################### pyface-4.1.0/pyface/viewer/column_provider.py0000644000175100001440000000356311674463363022321 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all column providers. """ # Enthought library imports. from traits.api import HasTraits, Int class ColumnProvider(HasTraits): """ Base class for all column providers. By default a column's label is 'Column n' and is 100 pixels wide. """ # The number of columns. column_count = Int ########################################################################### # 'TableColumnProvider' interface. ########################################################################### def get_label(self, viewer, column_index): """ Returns the label for a column. """ return 'Column %d' % column_index def get_width(self, viewer, column_index): """ Returns the width of a column. Returning -1 (the default) means that the column will be sized to fit its longest item (or its column header if it is longer than any item). """ return -1 def get_alignment(self, viewer, column_index): """ Returns the alignment of the column header and cells. Returns, 'left', 'right', 'centre' or 'center' ('left' by default). """ return 'left' #### EOF ###################################################################### pyface-4.1.0/pyface/i_about_dialog.py0000644000175100001440000000265211674463363020550 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a simple 'About' dialog. """ # Enthought library imports. from traits.api import Instance, List, Unicode # Local imports. from i_dialog import IDialog from image_resource import ImageResource class IAboutDialog(IDialog): """ The interface for a simple 'About' dialog. """ #### 'IAboutDialog' interface ############################################# # Additional strings to be added to the dialog. additions = List(Unicode) # The image displayed in the dialog. image = Instance(ImageResource, ImageResource('about')) class MAboutDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IAboutDialog interface. """ ### EOF ####################################################################### pyface-4.1.0/pyface/i_window.py0000644000175100001440000001404711674463363017427 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The abstract interface for all pyface top-level windows. """ # Enthought library imports. from traits.api import Event, Tuple, Unicode # Local imports. from constant import NO from key_pressed_event import KeyPressedEvent from i_widget import IWidget class IWindow(IWidget): """ The abstract interface for all pyface top-level windows. A pyface top-level window has no visual representation until it is opened (ie. its 'control' trait will be None until it is opened). """ #### 'IWindow' interface ################################################## # The position of the window. position = Tuple # The size of the window. size = Tuple # The window title. title = Unicode #### Events ##### # The window has been activated. activated = Event # The window has been closed. closed = Event # The window is about to be closed. closing = Event # The window has been deactivated. deactivated = Event # A key was pressed while the window had focus. # FIXME v3: This smells of a hack. What's so special about key presses? # FIXME v3: Unicode key_pressed = Event(KeyPressedEvent) # The window has been opened. opened = Event # The window is about to open. opening = Event ########################################################################### # 'IWindow' interface. ########################################################################### def open(self): """ Opens the window. """ def close(self): """ Closes the window. """ def activate(self): """ Activates the window. """ def show(self, visible): """ Show or hide the window. visible is set if the window should be shown. """ def confirm(self, message, title=None, cancel=False, default=NO): """ Convenience method to show a confirmation dialog. message is the text of the message to display. title is the text of the window title. cancel is set if the dialog should contain a Cancel button. default is the default button. """ def information(self, message, title='Information'): """ Convenience method to show an information message dialog. message is the text of the message to display. title is the text of the window title. """ def warning(self, message, title='Warning'): """ Convenience method to show a warning message dialog. message is the text of the message to display. title is the text of the window title. """ def error(self, message, title='Error'): """ Convenience method to show an error message dialog. message is the text of the message to display. title is the text of the window title. """ ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): """ Adds any event listeners required by the window. """ class MWindow(object): """ The mixin class that contains common code for toolkit specific implementations of the IWindow interface. Implements: close(), confirm(), open() Reimplements: _create() """ ########################################################################### # 'IWindow' interface. ########################################################################### def open(self): """ Opens the window. """ # Trait notification. self.opening = self if self.control is None: self._create() self.show(True) # Trait notification. self.opened = self return def close(self): """ Closes the window. """ if self.control is not None: # Trait notification. self.closing = self # Cleanup the toolkit-specific control. self.destroy() # Trait notification. self.closed = self return def confirm(self, message, title=None, cancel=False, default=NO): """ Convenience method to show a confirmation dialog. """ from confirmation_dialog import confirm return confirm(self.control, message, title, cancel, default) def information(self, message, title='Information'): """ Convenience method to show an information message dialog. """ from message_dialog import information return information(self.control, message, title) def warning(self, message, title='Warning'): """ Convenience method to show a warning message dialog. """ from message_dialog import warning return warning(self.control, message, title) def error(self, message, title='Error'): """ Convenience method to show an error message dialog. """ from message_dialog import error return error(self.control, message, title) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): """ Creates the window's widget hierarchy. """ # Create the toolkit-specific control. super(MWindow, self)._create() # Wire up event any event listeners required by the window. self._add_event_listeners() pyface-4.1.0/pyface/image_resource.py0000644000175100001440000000161511674463363020576 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of an image resource. """ # Import the toolkit specific version. from toolkit import toolkit_object ImageResource = toolkit_object('image_resource:ImageResource') #### EOF ###################################################################### pyface-4.1.0/pyface/resource/0000755000175100001440000000000011674463363017057 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/resource/resource_factory.py0000644000175100001440000000245211674463363023012 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Default base-class for resource factories. """ class ResourceFactory: """ Default base-class for resource factories. """ ########################################################################### # 'ResourceFactory' interface. ########################################################################### def image_from_file(self, filename): """ Creates an image from the data in the specified filename. """ raise NotImplemented def image_from_data(self, data): """ Creates an image from the specified data. """ raise NotImplemented #### EOF ###################################################################### pyface-4.1.0/pyface/resource/api.py0000644000175100001440000000140711674463363020204 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from resource_factory import ResourceFactory from resource_manager import ResourceManager from resource_path import resource_path pyface-4.1.0/pyface/resource/resource_manager.py0000644000175100001440000002156511674463363022763 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005-2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! #------------------------------------------------------------------------------ """ The default resource manager. A resource manager locates and loads application resources such as images and sounds etc. """ # Standard library imports. import glob, inspect, operator, os, sys, types from os.path import join from zipfile import is_zipfile, ZipFile # Enthought library imports. from traits.api import HasTraits, Instance, List from traits.util.resource import get_path # Local imports. from resource_factory import ResourceFactory from resource_reference import ImageReference class ResourceManager(HasTraits): """ The default resource manager. A resource manager locates and loads application resources such as images and sounds etc. """ # Allowed extensions for image resources. IMAGE_EXTENSIONS = ['.png', '.jpg', '.bmp', '.gif', '.ico'] # A list of additional search paths. These paths are fallbacks, and hence # have lower priority than the paths provided by resource objects. extra_paths = List # The resource factory is responsible for actually creating resources. # This is used so that (for example) different GUI toolkits can create # a images in the format that they require. resource_factory = Instance(ResourceFactory) ########################################################################### # 'ResourceManager' interface. ########################################################################### def locate_image(self, image_name, path, size=None): """ Locates an image. """ if not operator.isSequenceType(path): path = [path] resource_path = [] for item in list(path) + self.extra_paths: if isinstance(item, basestring): resource_path.append(item) elif isinstance(item, types.ModuleType): resource_path.append(item) else: resource_path.extend(self._get_resource_path(item)) return self._locate_image(image_name, resource_path, size) def load_image(self, image_name, path, size=None): """ Loads an image. """ reference = self.locate_image(image_name, path, size) if reference is not None: image = reference.load() else: image = None return image ########################################################################### # Private interface. ########################################################################### def _locate_image(self, image_name, resource_path, size): """ Attempts to locate an image resource. If the image is found, an image resource reference is returned. If the image is NOT found None is returned. """ # If the image name contains a file extension (eg. '.jpg') then we will # only accept an an EXACT filename match. basename, extension = os.path.splitext(image_name) if len(extension) > 0: extensions = [extension] pattern = image_name # Otherwise, we will search for common image suffixes. else: extensions = self.IMAGE_EXTENSIONS pattern = image_name + '.*' # Try the 'images' sub-directory first (since that is commonly # where we put them!). If the image is not found there then look # in the directory itself. if size is None: subdirs = ['images', ''] else: subdirs = ['images/%dx%d' % (size[0], size[1]), 'images', ''] for dirname in resource_path: # If we come across a reference to a module, use pkg_resources # to try and find the image inside of an .egg, .zip, etc. if isinstance(dirname, types.ModuleType): from pkg_resources import resource_string for path in subdirs: for extension in extensions: searchpath = '%s/%s%s' % (path, basename, extension) try: data = resource_string(dirname.__name__, searchpath) return ImageReference(self.resource_factory, data = data) except IOError: pass else: continue # Is there anything resembling the image name in the directory? for path in subdirs: filenames = glob.glob(join(dirname, path, pattern)) for filename in filenames: not_used, extension = os.path.splitext(filename) if extension in extensions: reference = ImageReference( self.resource_factory, filename=filename ) return reference # Is there an 'images' zip file in the directory? zip_filename = join(dirname, 'images.zip') if os.path.isfile(zip_filename): zip_file = ZipFile(zip_filename, 'r') # Try the image name itself, and then the image name with # common images suffixes. for extension in extensions: try: image_data = zip_file.read(basename + extension) reference = ImageReference( self.resource_factory, data=image_data ) return reference except: pass # is this a path within a zip file? # first, find the zip file in the path filepath = dirname zippath = '' while not is_zipfile(filepath) and \ os.path.splitdrive(filepath)[1].startswith('\\') and \ os.path.splitdrive(filepath)[1].startswith('/'): filepath, tail = os.path.split(filepath) if zippath != '': zippath = tail + '/' + zippath else: zippath = tail # if we found a zipfile, then look inside it for the image! if is_zipfile(filepath): zip_file = ZipFile(filepath) for subpath in ['images', '']: for extension in extensions: try: # this is a little messy. since zip files don't # recognize a leading slash, we have to be very # particular about how we build this path when # there are empty strings if zippath != '': path = zippath + '/' else: path = '' if subpath != '': path = path + subpath + '/' path = path + basename + extension # now that we have the path we can attempt to load # the image image_data = zip_file.read(path) reference = ImageReference( self.resource_factory, data=image_data ) # if there was no exception then return the result return reference except: pass return None def _get_resource_path(self, object): """ Returns the resource path for an object. """ if hasattr(object, 'resource_path'): resource_path = object.resource_path else: resource_path = self._get_default_resource_path(object) return resource_path def _get_default_resource_path(self, object): """ Returns the default resource path for an object. """ resource_path = [] for klass in inspect.getmro(object.__class__): try: resource_path.append(get_path(klass)) # We get an attribute error when we get to a C extension type (in # our case it will most likley be 'CHasTraits'. We simply ignore # everything after this point! except AttributeError: break return resource_path #### EOF ###################################################################### pyface-4.1.0/pyface/resource/resource_reference.py0000644000175100001440000000515511674463363023304 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Resource references. """ # Enthought library imports. from traits.api import Any, HasTraits, Instance # Local imports. from resource_factory import ResourceFactory class ResourceReference(HasTraits): """ Abstract base class for resource references. Resource references are returned from calls to 'locate_reference' on the resource manager. """ # The resource factory that will be used to load the resource. resource_factory = Instance(ResourceFactory) # ReadOnly ########################################################################### # 'ResourceReference' interface. ########################################################################### def load(self): """ Loads the resource. """ raise NotImplementedError class ImageReference(ResourceReference): """ A reference to an image resource. """ # Iff the image was found in a file then this is the name of that file. filename = Any # ReadOnly # Iff the image was found in a zip file then this is the image data that # was read from the zip file. data = Any # ReadOnly def __init__(self, resource_factory, filename=None, data=None): """ Creates a new image reference. """ self.resource_factory = resource_factory self.filename = filename self.data = data return ########################################################################### # 'ResourceReference' interface. ########################################################################### def load(self): """ Loads the resource. """ if self.filename is not None: image = self.resource_factory.image_from_file(self.filename) elif self.data is not None: image = self.resource_factory.image_from_data(self.data) else: raise ValueError("Image reference has no filename OR data") return image #### EOF ###################################################################### pyface-4.1.0/pyface/resource/resource_path.py0000644000175100001440000000412211674463363022273 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005-2009 Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! #------------------------------------------------------------------------------ """ Functions to determine resource locations from the call stack. This type of resource location is normally requested from the constructor for an object whose resources are relative to the module constructing the object. """ import sys from os import getcwd from os.path import dirname, exists def resource_module(level = 2): """Return a module reference calculated from the caller's stack. Note that what we want is the reference to the package containing the module in the stack. This is because we need a directory to search for our default resource sub-dirs as children. """ module_name = sys._getframe(level).f_globals.get('__name__', '__main__') if '.' in module_name: module_name = '.'.join(module_name.split('.')[:-1]) module = sys.modules.get(module_name) return module def resource_path(level = 2): """Return a resource path calculated from the caller's stack. """ module = sys._getframe(level).f_globals.get('__name__', '__main__') if module != '__main__': # Return the path to the module: try: return dirname(getattr(sys.modules.get(module), '__file__')) except: # Apparently 'module' is not a registered module...treat it like # '__main__': pass # '__main__' is not a real module, so we need a work around: for path in [ dirname(sys.argv[0] ), getcwd()]: if exists(path): break return path #### EOF ###################################################################### pyface-4.1.0/pyface/resource/__init__.py0000644000175100001440000000030311674463363021164 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. """ Support for managing resources such as images and sounds. Part of the TraitsGUI project of the Enthought Tool Suite. """ pyface-4.1.0/pyface/i_clipboard.py0000644000175100001440000000566511674463363020065 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Evan Patterson # Date: 06/26/09 #------------------------------------------------------------------------------ """ The interface for manipulating the toolkit clipboard. """ # ETS imports from traits.api import HasStrictTraits, Interface, Property from traitsui.ui_traits import SequenceTypes class IClipboard(Interface): """ The interface for manipulating the toolkit clipboard. """ data_type = Property # The type of data in the clipboard (string) data = Property # Arbitrary Python data has_data = Property # Arbitrary Python data is available object_type = Property # Name of the class of object in the clipboard object_data = Property # Python object data has_object_data = Property # Python object data is available text_data = Property # Text data has_text_data = Property # Text data is available file_data = Property # File name data has_file_data = Property # File name data is available class BaseClipboard(HasStrictTraits): """ An abstract base class that contains common code for toolkit specific implementations of IClipboard. """ data_type = Property # The type of data in the clipboard (string) data = Property # Arbitrary Python data has_data = Property # Arbitrary Python data is available object_type = Property # Name of the class of object in the clipboard object_data = Property # Python object data has_object_data = Property # Python object data is available text_data = Property # Text data has_text_data = Property # Text data is available file_data = Property # File name data has_file_data = Property # File name data is available def _get_data(self): if self.has_text_data: return self.text_data if self.has_file_data: return self.file_data if self.has_object_data: return self.object_data return None def _set_data(self, data): if isinstance(data, basestring): self.text_data = data elif type(data) in SequenceTypes: self.file_data = data else: self.object_data = data def _get_data_type ( self ): if self.has_text_data: return 'str' if self.has_file_data: return 'file' if self.has_object_data: return self.object_type return '' pyface-4.1.0/pyface/system_metrics.py0000644000175100001440000000166211674463363020661 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of access to system metrics (screen width and height etc). """ # Import the toolkit specific version. from toolkit import toolkit_object SystemMetrics = toolkit_object('system_metrics:SystemMetrics') #### EOF ###################################################################### pyface-4.1.0/pyface/single_choice_dialog.py0000644000175100001440000000634111674463363021720 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A dialog that allows the user to chose a single item from a list. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports. from traits.api import List, Str, Any # Local imports. from .dialog import Dialog class SingleChoiceDialog(Dialog): """ A dialog that allows the user to chose a single item from a list. choices is the list of things to choose from. If name_attribute is set then we assume it is a list of objects getattr(obj, name_attribute) gives us the string to put in the dialog. Otherwise we just call str() on the choices to get the strings. """ choices = List(Any) choice = Any name_attribute = Str caption = Str ########################################################################### # 'Window' interface. ########################################################################### def close(self): """ Closes the window. """ # Get the chosen object. if self.control is not None: self.choice = self.choices[self.control.GetSelection()] # Let the window close as normal. super(SingleChoiceDialog, self).close() return ########################################################################### # Protected 'Window' interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the window. """ dialog = wx.SingleChoiceDialog( parent, self.title, self.caption, self._get_string_choices(), self.STYLE ) return dialog def _create_contents(self, parent): """ Creates the window contents. """ # In this case, wx does it all for us in 'wx.SingleChoiceDialog' pass ########################################################################### # 'Private' interface. ########################################################################### def _get_string_choices(self): """ Returns the list of strings to display in the dialog. """ if len(self.name_attribute) > 0: # We asssume choices is a list of objects with this attribute choices = [ getattr(obj, self.name_attribute) for obj in self.choices ] else: # We just convert to strings choices = [str(obj) for obj in self.choices] return choices #### EOF ###################################################################### pyface-4.1.0/pyface/mdi_application_window.py0000644000175100001440000001357011674463363022333 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ An MDI top-level application window. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports. from traits.api import Bool, Instance, Int, Tuple # Local imports. from .application_window import ApplicationWindow from .image_resource import ImageResource try: import wx.aui AUI = True except: AUI = False class MDIApplicationWindow(ApplicationWindow): """ An MDI top-level application window. The application window has support for a menu bar, tool bar and a status bar (all of which are optional). Usage: Create a sub-class of this class and override the protected '_create_contents' method. """ #### 'MDIApplicationWindow' interface ##################################### # The workarea background image. background_image = Instance(ImageResource, ImageResource('background')) # Should we tile the workarea background image? The alternative is to # scale it. Be warned that scaling the image allows for 'pretty' images, # but is MUCH slower than tiling. tile_background_image = Bool(True) # WX HACK FIXME # UPDATE: wx 2.6.1 does NOT fix this issue. _wx_offset = Tuple(Int, Int) ########################################################################### # 'MDIApplicationWindow' interface. ########################################################################### def create_child_window(self, title=None, is_mdi=True, float=True): """ Create a child window. """ if title is None: title = self.title if is_mdi: return wx.MDIChildFrame(self.control, -1, title) else: if float: style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT else: style = wx.DEFAULT_FRAME_STYLE return wx.Frame(self.control, -1, title, style=style) ########################################################################### # Protected 'Window' interface. ########################################################################### def _create_contents(self, parent): """ Create the contents of the MDI window. """ # Create the 'trim' widgets (menu, tool and status bars etc). self._create_trim_widgets(self.control) # The work-area background image (it can be tiled or scaled). self._image = self.background_image.create_image() self._bmp = self._image.ConvertToBitmap() # Frame events. # # We respond to size events to layout windows around the MDI frame. wx.EVT_SIZE(self.control, self._on_size) # Client window events. client_window = self.control.GetClientWindow() wx.EVT_ERASE_BACKGROUND(client_window, self._on_erase_background) self._wx_offset = client_window.GetPositionTuple() if AUI: # Let the AUI manager look after the frame. self._aui_manager.SetManagedWindow(self.control) contents = super(MDIApplicationWindow, self)._create_contents(parent) return contents def _create_control(self, parent): """ Create the toolkit-specific control that represents the window. """ control = wx.MDIParentFrame( parent, -1, self.title, style=wx.DEFAULT_FRAME_STYLE, size=self.size, pos=self.position ) return control ########################################################################### # Private interface. ########################################################################### def _tile_background_image(self, dc, width, height): """ Tiles the background image. """ w = self._bmp.GetWidth() h = self._bmp.GetHeight() x = 0 while x < width: y = 0 while y < height: dc.DrawBitmap(self._bmp, x, y) y = y + h x = x + w return def _scale_background_image(self, dc, width, height): """ Scales the background image. """ # Scale the image (if necessary). image = self._image if image.GetWidth() != width or image.GetHeight()!= height: image = self._image.Copy() image.Rescale(width, height) # Convert it to a bitmap and draw it. dc.DrawBitmap(image.ConvertToBitmap(), 0, 0) return ##### wx event handlers ################################################### def _on_size(self, event): """ Called when the frame is resized. """ wx.LayoutAlgorithm().LayoutMDIFrame(self.control) return def _on_erase_background(self, event): """ Called when the background of the MDI client window is erased. """ # fixme: Close order... if self.control is None: return frame = self.control dc = event.GetDC() if not dc: dc = wx.ClientDC(frame.GetClientWindow()) size = frame.GetClientSize() # Currently you have two choices, tile the image or scale it. Be # warned that scaling is MUCH slower than tiling. if self.tile_background_image: self._tile_background_image(dc, size.width, size.height) else: self._scale_background_image(dc, size.width, size.height) pyface-4.1.0/pyface/preference/0000755000175100001440000000000011674463363017346 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/preference/preference_dialog.py0000644000175100001440000001606311674463363023363 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The preference dialog. """ # Major package imports. import wx # Enthought library imports. from pyface.api import HeadingText, LayeredPanel, SplitDialog from pyface.util.font_helper import new_font_like from pyface.viewer.api import TreeViewer, DefaultTreeContentProvider from traits.api import Any, Dict, Float, Instance, Str # Local imports. from preference_node import PreferenceNode class PreferenceDialog(SplitDialog): """ The preference dialog. """ #### 'Dialog' interface ################################################### # The dialog title. title = Str('Preferences') #### 'SplitDialog' interface ############################################## # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.25) #### 'PreferenceDialog' interface ######################################### # The root of the preference hierarchy. root = Instance(PreferenceNode) #### Private interface #################################################### # The preference pages in the dialog (they are created lazily). _pages = Dict # The current visible preference page. _current_page = Any ########################################################################### # Protected 'Dialog' interface. ########################################################################### def _create_buttons(self, parent): """ Creates the buttons. """ sizer = wx.BoxSizer(wx.HORIZONTAL) # 'Done' button. done = wx.Button(parent, wx.ID_OK, "Done") done.SetDefault() wx.EVT_BUTTON(parent, wx.ID_OK, self._wx_on_ok) sizer.Add(done) return sizer ########################################################################### # Protected 'SplitDialog' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the panel containing the preference page tree. """ return self._create_tree(parent) def _create_rhs(self, parent): """ Creates the panel containing the selected preference page. """ panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) # The 'pretty' title bar ;^) self.__title = HeadingText(panel) sizer.Add( self.__title.control, 0, wx.EXPAND | wx.BOTTOM | wx.LEFT | wx.RIGHT, 5 ) # The preference page of the node currently selected in the tree. self._layered_panel = LayeredPanel(panel, min_width=-1, min_height=-1) sizer.Add( self._layered_panel.control, 1, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5 ) # The 'Restore Defaults' button etc. buttons = self._create_page_buttons(panel) sizer.Add(buttons, 0, wx.ALIGN_RIGHT | wx.TOP | wx.RIGHT, 5) # A separator. line = wx.StaticLine(panel, -1) sizer.Add(line, 0, wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, 5) # Resize the panel to fit the sizer's minimum size. sizer.Fit(panel) return panel ########################################################################### # Private interface. ########################################################################### def _create_tree(self, parent): """ Creates the preference page tree. """ tree_viewer = TreeViewer( parent, input = self.root, show_images = False, show_root = False, content_provider = DefaultTreeContentProvider() ) tree_viewer.on_trait_change(self._on_selection_changed, 'selection') return tree_viewer.control def _create_page_buttons(self, parent): """ Creates the 'Restore Defaults' button, etc. At the moment the "etc." is an optional 'Help' button. """ self._button_sizer = sizer = wx.BoxSizer(wx.HORIZONTAL) # 'Help' button. Comes first so 'Restore Defaults' doesn't jump around. self._help = help = wx.Button(parent, -1, "Help") wx.EVT_BUTTON(parent, help.GetId(), self._on_help) sizer.Add(help, 0, wx.RIGHT, 5) # 'Restore Defaults' button. restore = wx.Button(parent, -1, "Restore Defaults") wx.EVT_BUTTON(parent, restore.GetId(), self._on_restore_defaults) sizer.Add(restore) return sizer ########################################################################### # wx event handlers. ########################################################################### def _on_restore_defaults(self, event): """ Called when the 'Restore Defaults' button is pressed. """ page = self._pages[self._layered_panel.current_layer_name] page.restore_defaults() return def _on_help(self, event): """ Called when the 'Help' button is pressed. """ page = self._pages[self._layered_panel.current_layer_name] page.show_help_topic() return ########################################################################### # Trait event handlers. ########################################################################### def _on_selection_changed(self, selection): """ Called when a node in the tree is selected. """ if len(selection) > 0: # The tree is in single selection mode. node = selection[0] # We only show the help button if the selected node has a help # topic Id. if len(node.help_id) > 0: self._button_sizer.Show(self._help, True) else: self._button_sizer.Show(self._help, False) # Show the selected preference page. layered_panel = self._layered_panel parent = self._layered_panel.control # If we haven't yet displayed the node's preference page during the # lifetime of this dialog, then we have to create it. if not layered_panel.has_layer(node.name): page = node.create_page() layered_panel.add_layer(node.name, page.create_control(parent)) self._pages[node.name] = page layered_panel.show_layer(node.name) self.__title.text = node.name return #### EOF ###################################################################### pyface-4.1.0/pyface/preference/api.py0000644000175100001440000000140511674463363020471 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from preference_page import PreferencePage from preference_dialog import PreferenceDialog from preference_node import PreferenceNode pyface-4.1.0/pyface/preference/preference_node.py0000644000175100001440000000463211674463363023050 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for a node in a preference dialog. A preference node has a label and an image which are used to represent the node in a preference dialog (usually in the form of a tree). """ # Enthought library imports. from pyface.viewer.tree_item import TreeItem from traits.api import Str class PreferenceNode(TreeItem): """ Abstract base class for a node in a preference dialog. A preference node has a name and an image which are used to represent the node in a preference dialog (usually in the form of a tree). """ #### 'PreferenceNode' interface ########################################### # The node's unique Id. id = Str # The node's image. image = Str # The node name. name = Str # The Id of the help topic for the node. help_id = Str ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Returns the string representation of the item. """ return self.name ########################################################################### # 'PreferenceNode' interface. ########################################################################### def create_page(self): """ Creates the preference page for this node. """ raise NotImplementedError def lookup(self, id): """ Returns the child of this node with the specified Id. Returns None if no such child exists. """ for node in self.children: if node.id == id: break else: node = None return node #### EOF ###################################################################### pyface-4.1.0/pyface/preference/__init__.py0000644000175100001440000000120111674463363021451 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/preference/preference_page.py0000644000175100001440000000301111674463363023025 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all preference pages. """ # Enthought library imports. from traits.api import HasTraits # fixme: in JFace this extends from 'DialogPage' which we don't have yet! class PreferencePage(HasTraits): """ Abstract base class for all preference pages. """ ########################################################################### # 'PreferencePage' interface. ########################################################################### def create_control(self, parent): """ Creates the toolkit-specific control for the page. """ raise NotImplementedError def restore_defaults(self): """ Restore the default preferences. """ pass def show_help_topic(self): """ Show the help topic for this preference page.""" pass #### EOF ###################################################################### pyface-4.1.0/pyface/confirmation_dialog.py0000644000175100001440000000254511674463363021617 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a dialog that prompts the user for confirmation. """ # Local imports. from constant import NO def confirm(parent, message, title=None, cancel=False, default=NO): """ Convenience function to show a confirmation dialog. """ if title is None: title = "Confirmation" dialog = ConfirmationDialog( parent = parent, message = message, cancel = cancel, default = default, title = title ) return dialog.open() # Import the toolkit specific version. from toolkit import toolkit_object ConfirmationDialog = toolkit_object('confirmation_dialog:ConfirmationDialog') #### EOF ###################################################################### pyface-4.1.0/pyface/list_box_model.py0000644000175100001440000000170511674463363020610 0ustar ischnellusers00000000000000""" The model for list boxes. """ # Enthought library imports from traits.api import Event, HasTraits # Classes for event traits. class ListModelEvent(object): """ Information about list model changes. """ def __init__(self): """ Creates a new list model event. """ return class ListBoxModel(HasTraits): """ The model for list boxes. """ #### Events #### # Fired when the contents of the list have changed. list_changed = Event def get_item_count(self): """ Returns the number of items in the list. """ raise NotImplementedError def get_item_at(self, index): """ Returns the item at the specified index. """ raise NotImplementedError def fire_list_changed(self): """ Invoke this method when the list has changed. """ self.list_changed = ListModelEvent() return #### EOF ###################################################################### pyface-4.1.0/pyface/i_image_cache.py0000644000175100001440000000361411674463363020323 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for an image cache. """ # Enthought library imports. from traits.api import Interface class IImageCache(Interface): """ The interface for an image cache. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, width, height): """ Creates a new image cache for images of the given size. """ ########################################################################### # 'ImageCache' interface. ########################################################################### def get_image(self, filename): """ Returns the specified image. """ # FIXME v3: The need to distinguish between bitmaps and images is toolkit # specific so, strictly speaking, the conversion to a bitmap should be done # wherever the toolkit actually needs it. def get_bitmap(self, filename): """ Returns the specified image as a bitmap. """ class MImageCache(object): """ The mixin class that contains common code for toolkit specific implementations of the IImageCache interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/wx/0000755000175100001440000000000011674463363015666 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/wx/image_cache.py0000644000175100001440000000461511674463363020453 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ An image cache. """ # Major package imports. import wx class ImageCache: """ An image cache. """ def __init__(self, width, height): """ Creates a new image cache. """ self._width = width self._height = height # The images in the cache! self._images = {} # {filename : bitmap} return ########################################################################### # 'ImageCache' interface. ########################################################################### def get_image(self, filename): """ Returns the specified image (currently as a bitmap). """ # Try the cache first. bmp = self._images.get(filename) if bmp is None: # Load the image from the file and add it to the list. # # N.B 'wx.BITMAP_TYPE_ANY' tells wxPython to attempt to autodetect # --- the image format. image = wx.Image(filename, wx.BITMAP_TYPE_ANY) # We force all images in the cache to be the same size. self._scale(image) # We also force them to be bitmaps! bmp = image.ConvertToBitmap() # Add the bitmap to the cache! self._images[filename] = bmp return bmp ########################################################################### # Private interface. ########################################################################### def _scale(self, image): """ Scales the specified image (if necessary). """ if image.GetWidth() != self._width or image.GetHeight()!= self._height: image.Rescale(self._width, self._height) return image #### EOF ###################################################################### pyface-4.1.0/pyface/wx/divider.py0000644000175100001440000000261511674463363017672 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A thin visual divider. """ # Major package imports. import wx class Divider(wx.StaticLine): """ A thin visual divider. """ def __init__(self, parent, id, **kw): """ Creates a divider. """ # Base-class constructor. wx.StaticLine.__init__(self, parent, id, style=wx.LI_HORIZONTAL, **kw) # Create the widget! self._create_widget() return ########################################################################### # Private interface. ########################################################################### def _create_widget(self): """ Creates the widget. """ self.SetSize((1, 1)) return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/image_list.py0000644000175100001440000000721711674463363020364 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A cached image list. """ # Major package imports. import wx # fixme: rename to 'CachedImageList'?!? class ImageList(wx.ImageList): """ A cached image list. """ def __init__(self, width, height): """ Creates a new cached image list. """ # Base-class constructor. wx.ImageList.__init__(self, width, height) self._width = width self._height = height # Cache of the indexes of the images in the list! self._cache = {} # {filename : index} return ########################################################################### # 'ImageList' interface. ########################################################################### def GetIndex(self, filename): """ Returns the index of the specified image. The image will be loaded and added to the image list if it is not already there. """ # If the icon is a string then it is the filename of some kind of # image (e.g 'foo.gif', 'image/foo.png' etc). if isinstance(filename, basestring): # Try the cache first. index = self._cache.get(filename) if index is None: # Load the image from the file and add it to the list. # # N.B 'wx.BITMAP_TYPE_ANY' tells wxPython to attempt to # ---- autodetect the image format. image = wx.Image(filename, wx.BITMAP_TYPE_ANY) # We force all images in the cache to be the same size. self._scale(image) # We also force them to be bitmaps! bmp = image.ConvertToBitmap() # Add the bitmap to the actual list... index = self.Add(bmp) # ... and update the cache. self._cache[filename] = index # Otherwise the icon is *actually* an icon (in our case, probably # related to a MIME type). else: #image = filename #self._scale(image) #bmp = image.ConvertToBitmap() #index = self.Add(bmp) #return index icon = filename # We also force them to be bitmaps! bmp = wx.EmptyBitmap(self._width, self._height) bmp.CopyFromIcon(icon) # We force all images in the cache to be the same size. image = wx.ImageFromBitmap(bmp) self._scale(image) bmp = image.ConvertToBitmap() index = self.Add(bmp) return index ########################################################################### # Private interface. ########################################################################### def _scale(self, image): """ Scales the specified image (if necessary). """ if image.GetWidth() != self._width or image.GetHeight()!= self._height: image.Rescale(self._width, self._height) return image #### EOF ###################################################################### pyface-4.1.0/pyface/wx/scrolled_message_dialog.py0000644000175100001440000000313511674463363023074 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx from wx.lib.layoutf import Layoutf class ScrolledMessageDialog(wx.Dialog): def __init__(self, parent, msg, caption, pos = wx.DefaultPosition, size = (500,300)): wx.Dialog.__init__(self, parent, -1, caption, pos, size) x, y = pos if x == -1 and y == -1: self.CenterOnScreen(wx.BOTH) text = wx.TextCtrl(self, -1, msg, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY | wx.TE_MULTILINE | wx.HSCROLL | wx.TE_RICH2 ) font = wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL) text.SetStyle(0, len(msg), wx.TextAttr(font=font)) ok = wx.Button(self, wx.ID_OK, "OK") text.SetConstraints(Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self,ok))) ok.SetConstraints(Layoutf('b=b5#1;x%w50#1;w!80;h!25', (self,))) self.SetAutoLayout(1) self.Layout() pyface-4.1.0/pyface/wx/font.py0000644000175100001440000000444111674463363017211 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Font utilities. """ # Major package imports. import wx def clone_font(font): """ Clones the specified font. """ point_size = font.GetPointSize() family = font.GetFamily() style = font.GetStyle() weight = font.GetWeight() underline = font.GetUnderlined() face_name = font.GetFaceName() clone = wx.Font( point_size, family, style, weight, underline, face_name, ) return clone def set_font_size(window, size): """ Recursively sets the font size starting from 'window'. """ font = window.GetFont() clone = clone_font(font) clone.SetPointSize(size) window.SetFont(clone) sizer = window.GetSizer() if sizer is not None: sizer.Layout() window.Refresh() for child in window.GetChildren(): set_font_size(child, size) return def increase_font_size(window, delta=2): """ Recursively increases the font size starting from 'window'. """ font = window.GetFont() clone = clone_font(font) clone.SetPointSize(font.GetPointSize() + delta) window.SetFont(clone) sizer = window.GetSizer() if sizer is not None: sizer.Layout() window.Refresh() for child in window.GetChildren(): increase_font_size(child, delta) return def decrease_font_size(window, delta=2): """ Recursively decreases the font size starting from 'window'. """ increase_font_size(window, delta=-2) return def set_bold_font(window): """ Set 'window's font to be bold. """ font = window.GetFont() font.SetWeight(wx.BOLD) window.SetFont(font) return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/switcher.py0000644000175100001440000002065711674463363020102 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Classes to provide a switcher. """ # paranoid checkin in case Mr Chilver's changes break the distribution code # todo - it wasn't paranoia - reconcile this with lazy_switcher.py at some point # Major package imports. import wx from wx.lib.scrolledpanel import wxScrolledPanel # Enthought library imports. from traits.api import HasTraits class SwitcherModel(HasTraits): """ Base class for switcher models. """ __traits__ = { # The index of the selected 'page'. 'selected' : -1, } def __init__(self): """ Creates a new switcher model. """ # The items to display in the switcher control. self.items = [] # (str label, object value) return ########################################################################### # 'SwitcherModel' interface. ########################################################################### def create_page(self, parent, index): """ Creates a page for the switcher panel. """ raise NotImplementedError class SwitcherControl(wx.Panel): """ The default switcher control (a combo box). """ def __init__(self, parent, id, model, label=None, **kw): """ Create a new switcher control. """ # Base-class constructor. wx.Panel.__init__(self, parent, id, **kw) # The switcher model that we are a controller for. self.model = model # The optional label. self.label = label # Create the widget! self._create_widget(model, label) # Listen for when the selected item in the model is changed. model.on_trait_change(self._on_selected_changed, 'selected') return ########################################################################### # Trait event handlers. ########################################################################### def _on_selected_changed(self, selected): """ Called when the selected item in the model is changed. """ self.combo.SetSelection(selected) return ########################################################################### # wx event handlers. ########################################################################### def _on_combobox(self, event): """ Called when the combo box selection is changed. """ combo = event.GetEventObject() # Update the model. self.model.selected = combo.GetSelection() return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget. """ self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) ##self.SetBackgroundColour("light grey") # Switcher combo. sizer.Add(self._combo(self, model, label), 1, wx.EXPAND) # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return def _combo(self, parent, model, label): """ Creates the switcher combo box. """ sizer = wx.BoxSizer(wx.HORIZONTAL) # Label. if label is not None: text = wx.StaticText(parent, -1, label) sizer.Add(text, 0, wx.ALIGN_CENTER | wx.ALL, 5) # Combo. self.combo = combo = wx.ComboBox( parent, -1, style=wx.CB_DROPDOWN | wx.CB_READONLY ) sizer.Add(combo, 1, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5) # Ask the model for the available options. items = model.items if len(items) > 0: for name, data in model.items: combo.Append(name, data) # Listen for changes to the selected item. wx.EVT_COMBOBOX(self, combo.GetId(), self._on_combobox) # If the model's selected variable has been set ... if model.selected != -1: combo.SetSelection(model.selected) return sizer class SwitcherPanel(wxScrolledPanel): """ The default switcher panel. """ def __init__(self, parent, id, model, label=None, cache=True, **kw): # Base-class constructor. wxScrolledPanel.__init__(self, parent, id, **kw) self.SetupScrolling() # The switcher model that we are a panel for. self.model = model # Should we cache pages as we create them? self.cache = cache # The page cache (if caching was requested). self._page_cache = {} # The currently displayed page. self.current = None # Create the widget! self._create_widget(model, label) # Listen for when the selected item in the model is changed. model.on_trait_change(self._on_selected_changed, 'selected') return ########################################################################### # Trait event handlers. ########################################################################### def _on_selected_changed(self, selected): """ Called when the selected item in the model is changed. """ self._show_page(selected) return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget. """ self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) if model.selected != -1: self._show_page(model.selected) # Nothing to add here as we add the panel contents lazily! pass # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return def _show_page(self, index): """ Shows the page at the specified index. """ # If a page is already displayed then hide it. if self.current is not None: self.current.Show(False) self.sizer.Remove(self.current) # Is the page in the cache? page = self._page_cache.get(index) if not self.cache or page is None: # If not then ask our panel factory to create it. page = self.model.create_page(self, index) # Add it to the cache! self._page_cache[index] = page # Display the page. self.sizer.Add(page, 1, wx.EXPAND) page.Show(True) self.current = page # Force a new layout of the sizer's children but KEEPING the current # dimension. self.sizer.Layout() return class Switcher(wx.Panel): """ A switcher. """ def __init__(self, parent, id, model, label=None, **kw): # Base-class constructor. wx.Panel.__init__(self, parent, id, **kw) # The model that we are a switcher for. self.model = model # Create the widget! self._create_widget(model, label) return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget. """ self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) # Switcher control. self.control = control = SwitcherControl(self, -1, model, label) sizer.Add(control, 0, wx.EXPAND) # Switcher panel. self.panel = panel = SwitcherPanel(self, -1, model, label) sizer.Add(panel, 1, wx.EXPAND) # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/spacer.py0000644000175100001440000000304411674463363017516 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A panel used as a spacer. It is in a separate class so that you can set the background color in a single place to give visual feedback on sizer layouts (in particular, FlexGridSizer layouts). """ # Major package imports. import wx class Spacer(wx.Panel): """ A panel used as a spacer. """ def __init__(self, parent, id, **kw): """ Creates a spacer. """ # Base-class constructor. wx.Panel.__init__(self, parent, id, **kw) # Create the widget! self._create_widget() return ########################################################################### # Private interface. ########################################################################### def _create_widget(self): """ Create the widget! """ #self.SetBackgroundColour("brown") return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/drag_and_drop.py0000644000175100001440000002353511674463363021033 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Drag and drop utilities. """ # Standard library imports. import inspect # Major package imports. import wx class Clipboard: """ The clipboard is used when dragging and dropping Python objects. """ # fixme: This obviously only works within a single process! pass clipboard = Clipboard() clipboard.drop_source = None clipboard.source = None clipboard.data = None class FileDropTarget(wx.FileDropTarget): """ Drop target for files. """ def __init__(self, handler): """ Constructor. """ # Base-class constructor. wx.FileDropTarget.__init__(self) self.handler = handler return def OnDropFiles(self, x, y, filenames): """ Called when the files have been dropped. """ for filename in filenames: self.handler(x, y, filename) # Return True to accept the data, False to veto it. return True # The data format for Python objects! PythonObject = wx.CustomDataFormat('PythonObject') class PythonDropSource(wx.DropSource): """ Drop source for Python objects. """ def __init__(self, source, data, handler=None, allow_move=True): """ Creates a new drop source. A drop source should be created for *every* drag operation. If allow_move is False then the operation will default to a copy and only copy operations will be allowed. """ # The handler can either be a function that will be called when # the data has been dropped onto the target, or an instance that # supports the 'on_dropped' method. self.handler = handler self.allow_move = allow_move # Put the data to be dragged on the clipboard. clipboard.data = data clipboard.source = source clipboard.drop_source = self # Create our own data format and use it in a custom data object. data_object = wx.CustomDataObject(PythonObject) data_object.SetData('dummy') # And finally, create the drop source and begin the drag # and drop opperation. wx.DropSource.__init__(self, source) self.SetData(data_object) if allow_move: flags = wx.Drag_DefaultMove | wx.Drag_AllowMove else: flags = wx.Drag_CopyOnly self.result = self.DoDragDrop(flags) return def on_dropped(self, drag_result): """ Called when the data has been dropped. """ if self.handler is not None: if hasattr(self.handler, 'on_dropped'): # For backward compatibility we accept handler functions # with either 1 or 3 args, including self. If there are # 3 args then we pass the data and the drag_result. args = inspect.getargspec(self.handler.on_dropped)[0] if len(args) == 3: self.handler.on_dropped(clipboard.data, drag_result) else: self.handler.on_dropped() else: #print self.handler # In this case we assume handler is a function. # For backward compatibility we accept handler functions # with either 0 or 2 args. If there are 2 args then # we pass the data and drag_result args = inspect.getargspec(self.handler)[0] if len(args)==2: self.handler(clipboard.data, drag_result) else: self.handler() return class PythonDropTarget(wx.PyDropTarget): """ Drop target for Python objects. """ def __init__(self, handler): """ Constructor The handler can be either a function that will be called when *any* data is dropped onto the target, or an instance that supports the 'wx_drag_over' and 'wx_dropped_on' methods. The latter case allows the target to veto the drop. """ # Base-class constructor. super(PythonDropTarget, self).__init__() # The handler can either be a function that will be called when # any data is dropped onto the target, or an instance that supports # the 'wx_drag_over' and 'wx_dropped_on' methods. The latter case # allows the target to veto the drop. self.handler = handler # Specify the type of data we will accept. self.data_object = wx.DataObjectComposite() self.data = wx.CustomDataObject(PythonObject) self.data_object.Add(self.data, preferred = True) self.file_data = wx.FileDataObject() self.data_object.Add(self.file_data) self.SetDataObject(self.data_object) return def OnData(self, x, y, default_drag_result): """ Called when OnDrop returns True. """ # First, if we have a source in the clipboard and the source # doesn't allow moves then change the default to copy if clipboard.drop_source is not None and \ not clipboard.drop_source.allow_move: default_drag_result = wx.DragCopy elif clipboard.drop_source is None: # This means we might be receiving a file; try to import # the right packages to nicely handle a file drop. If those # packages can't be imported, then just pass through. if self.GetData(): try: from apptools.io import File from apptools.naming.api import Binding names = self.file_data.GetFilenames() files = [] bindings = [] for name in names: f = File(name) files.append(f) bindings.append(Binding(name = name, obj = f)) clipboard.data = files clipboard.node = bindings except ImportError: pass # Pass the object on the clipboard it to the handler. # # fixme: We allow 'wx_dropped_on' and 'on_drop' because both Dave # and Martin want different things! Unify! if hasattr(self.handler, 'wx_dropped_on'): drag_result = self.handler.wx_dropped_on( x, y, clipboard.data, default_drag_result ) elif hasattr(self.handler, 'on_drop'): drag_result = self.handler.on_drop( x, y, clipboard.data, default_drag_result ) else: self.handler(x, y, clipboard.data) drag_result = default_drag_result # Let the source of the drag/drop know that the operation is complete. drop_source = clipboard.drop_source if drop_source is not None: drop_source.on_dropped(drag_result) # Clean out the drop source! clipboard.drop_source = None # The return value tells the source what to do with the original data # (move, copy, etc.). In this case we just return the suggested value # given to us. return default_drag_result # Some virtual methods that track the progress of the drag. def OnDragOver(self, x, y, default_drag_result): """ Called when a data object is being dragged over the target. """ # First, if we have a source in the clipboard and the source # doesn't allow moves then change the default to copy data = clipboard.data if clipboard.drop_source is None: if not hasattr(self.handler, 'wx_drag_any'): # this is probably a file being dragged in, so just return return default_drag_result data = None elif not clipboard.drop_source.allow_move: default_drag_result = wx.DragCopy # The value returned here tells the source what kind of visual feedback # to give. For example, if wxDragCopy is returned then only the copy # cursor will be shown, even if the source allows moves. You can use # the passed in (x,y) to determine what kind of feedback to give. # In this case we return the suggested value which is based on whether # the Ctrl key is pressed. # # fixme: We allow 'wx_drag_over' and 'on_drag_over' because both Dave # and Martin want different things! Unify! if hasattr(self.handler, 'wx_drag_any'): drag_result = self.handler.wx_drag_any( x, y, data, default_drag_result ) elif hasattr(self.handler, 'wx_drag_over'): drag_result = self.handler.wx_drag_over( x, y, data, default_drag_result ) elif hasattr(self.handler, 'on_drag_over'): drag_result = self.handler.on_drag_over( x, y, data, default_drag_result ) else: drag_result = default_drag_result return drag_result def OnLeave(self): """ Called when the mouse leaves the drop target. """ if hasattr(self.handler, 'wx_drag_leave'): self.handler.wx_drag_leave(clipboard.data) return def OnDrop(self, x, y): """ Called when the user drops a data object on the target. Return 'False' to veto the operation. """ return True #### EOF ##################################################################### pyface-4.1.0/pyface/wx/progress_meter.py0000644000175100001440000000174411674463363021306 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx class ProgressDialog(wx.ProgressDialog): def __init__(self, *args, **kwds): wx.ProgressDialog.__init__(self, *args, **kwds) def SetButtonLabel(self, title): """ Change the Cancel button label to something else eg Stop.""" button = self.FindWindowById(wx.ID_CANCEL) button.SetLabel(title) return pyface-4.1.0/pyface/wx/shell.py0000644000175100001440000015176711674463363017370 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """The PyCrust Shell is an interactive text control in which a user types in commands to be sent to the interpreter. This particular shell is based on wxPython's wxStyledTextCtrl. The latest files are always available at the SourceForge project page at http://sourceforge.net/projects/pycrust/. Sponsored by Orbtech - Your source for Python programming expertise.""" __author__ = "Patrick K. O'Brien " __cvsid__ = "$Id: shell.py,v 1.2 2003/06/13 17:59:34 dmorrill Exp $" __revision__ = "$Revision: 1.2 $"[11:-2] from wx.wx import * from wx.stc import * import keyword import os import sys from wx.py.pseudo import PseudoFileIn, PseudoFileOut, PseudoFileErr from wx.py.version import VERSION # local imports from drag_and_drop import PythonObject from drag_and_drop import clipboard as enClipboard sys.ps3 = '<-- ' # Input prompt. NAVKEYS = (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, WXK_PRIOR, WXK_NEXT) if wxPlatform == '__WXMSW__': faces = { 'times' : 'Times New Roman', 'mono' : 'Courier New', 'helv' : 'Lucida Console', 'lucida' : 'Lucida Console', 'other' : 'Comic Sans MS', 'size' : 10, 'lnsize' : 9, 'backcol': '#FFFFFF', } # Versions of wxPython prior to 2.3.2 had a sizing bug on Win platform. # The font was 2 points too large. So we need to reduce the font size. if (wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER) < (2, 3, 2): faces['size'] -= 2 faces['lnsize'] -= 2 else: # GTK faces = { 'times' : 'Times', 'mono' : 'Courier', 'helv' : 'Helvetica', 'other' : 'new century schoolbook', 'size' : 12, 'lnsize' : 10, 'backcol': '#FFFFFF', } class ShellFacade: """Simplified interface to all shell-related functionality. This is a semi-transparent facade, in that all attributes of other are still accessible, even though only some are visible to the user.""" name = 'PyCrust Shell Interface' revision = __revision__ def __init__(self, other): """Create a ShellFacade instance.""" methods = ['ask', 'clear', 'pause', 'prompt', 'quit', 'redirectStderr', 'redirectStdin', 'redirectStdout', 'run', 'runfile', 'wrap', 'zoom', ] for method in methods: self.__dict__[method] = getattr(other, method) d = self.__dict__ d['other'] = other d['helpText'] = \ """ * Key bindings: Home Go to the beginning of the command or line. Shift+Home Select to the beginning of the command or line. Shift+End Select to the end of the line. End Go to the end of the line. Ctrl+C Copy selected text, removing prompts. Ctrl+Shift+C Copy selected text, retaining prompts. Ctrl+X Cut selected text. Ctrl+V Paste from clipboard. Ctrl+Shift+V Paste and run multiple commands from clipboard. Ctrl+Up Arrow Retrieve Previous History item. Alt+P Retrieve Previous History item. Ctrl+Down Arrow Retrieve Next History item. Alt+N Retrieve Next History item. Shift+Up Arrow Insert Previous History item. Shift+Down Arrow Insert Next History item. F8 Command-completion of History item. (Type a few characters of a previous command and then press F8.) F9 Pop-up window of matching History items. (Type a few characters of a previous command and then press F9.) """ def help(self): """Display some useful information about how to use the shell.""" self.write(self.helpText) def __getattr__(self, name): if hasattr(self.other, name): return getattr(self.other, name) else: raise AttributeError, name def __setattr__(self, name, value): if self.__dict__.has_key(name): self.__dict__[name] = value elif hasattr(self.other, name): return setattr(self.other, name, value) else: raise AttributeError, name def _getAttributeNames(self): """Return list of magic attributes to extend introspection.""" list = ['autoCallTip', 'autoComplete', 'autoCompleteCaseInsensitive', 'autoCompleteIncludeDouble', 'autoCompleteIncludeMagic', 'autoCompleteIncludeSingle', ] list.sort() return list class Shell(wxStyledTextCtrl): """PyCrust Shell based on wxStyledTextCtrl.""" name = 'PyCrust Shell' revision = __revision__ def __init__(self, parent, id=-1, pos=wxDefaultPosition, \ size=wxDefaultSize, style=wxCLIP_CHILDREN, introText='', \ locals=None, InterpClass=None, *args, **kwds): """Create a PyCrust Shell instance.""" wxStyledTextCtrl.__init__(self, parent, id, pos, size, style) # Grab these so they can be restored by self.redirect* methods. self.stdin = sys.stdin self.stdout = sys.stdout self.stderr = sys.stderr self.handlers = [] self.python_obj_paste_handler = None # Add the current working directory "." to the search path. sys.path.insert(0, os.curdir) # Import a default interpreter class if one isn't provided. if InterpClass == None: from PyCrust.interpreter import Interpreter else: Interpreter = InterpClass # Create default locals so we have something interesting. shellLocals = {'__name__': 'PyCrust-Shell', '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.', '__version__': VERSION, } # Add the dictionary that was passed in. if locals: shellLocals.update(locals) # Create a replacement for stdin. self.reader = PseudoFileIn(self.readline) self.reader.input = '' self.reader.isreading = 0 # Set up the interpreter. self.interp = Interpreter(locals=shellLocals, \ rawin=self.raw_input, \ stdin=self.reader, \ stdout=PseudoFileOut(self.writeOut), \ stderr=PseudoFileErr(self.writeErr), \ *args, **kwds) # Find out for which keycodes the interpreter will autocomplete. self.autoCompleteKeys = self.interp.getAutoCompleteKeys() # Keep track of the last non-continuation prompt positions. self.promptPosStart = 0 self.promptPosEnd = 0 # Keep track of multi-line commands. self.more = 0 # Create the command history. Commands are added into the front of # the list (ie. at index 0) as they are entered. self.historyIndex # is the current position in the history; it gets incremented as you # retrieve the previous command, decremented as you retrieve the # next, and reset when you hit Enter. self.historyIndex == -1 means # you're on the current command, not in the history. self.history = [] self.historyIndex = -1 self.historyPrefix = 0 # Assign handlers for keyboard events. EVT_KEY_DOWN(self, self.OnKeyDown) EVT_CHAR(self, self.OnChar) # Assign handlers for wxSTC events. EVT_STC_UPDATEUI(self, id, self.OnUpdateUI) EVT_STC_USERLISTSELECTION(self, id, self.OnHistorySelected) # Configure various defaults and user preferences. self.config() # Display the introductory banner information. try: self.showIntro(introText) except: pass # Assign some pseudo keywords to the interpreter's namespace. try: self.setBuiltinKeywords() except: pass # Add 'shell' to the interpreter's local namespace. try: self.setLocalShell() except: pass # Do this last so the user has complete control over their # environment. They can override anything they want. try: self.execStartupScript(self.interp.startupScript) except: pass def destroy(self): # del self.interp pass def config(self): """Configure shell based on user preferences.""" self.SetMarginType(1, wxSTC_MARGIN_NUMBER) self.SetMarginWidth(1, 40) self.SetLexer(wxSTC_LEX_PYTHON) self.SetKeyWords(0, ' '.join(keyword.kwlist)) self.setStyles(faces) self.SetViewWhiteSpace(0) self.SetTabWidth(4) self.SetUseTabs(0) # Do we want to automatically pop up command completion options? self.autoComplete = 1 self.autoCompleteIncludeMagic = 1 self.autoCompleteIncludeSingle = 1 self.autoCompleteIncludeDouble = 1 self.autoCompleteCaseInsensitive = 1 self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive) self.AutoCompSetSeparator(ord('\n')) # Do we want to automatically pop up command argument help? self.autoCallTip = 1 self.CallTipSetBackground(wxColour(255, 255, 232)) self.wrap() try: self.SetEndAtLastLine(false) except AttributeError: pass def showIntro(self, text=''): """Display introductory text in the shell.""" if text: if not text.endswith(os.linesep): text += os.linesep self.write(text) try: self.write(self.interp.introText) except AttributeError: pass wxCallAfter(self.ScrollToLine, 0) def setBuiltinKeywords(self): """Create pseudo keywords as part of builtins. This simply sets "close", "exit" and "quit" to a helpful string. """ import __builtin__ __builtin__.close = __builtin__.exit = __builtin__.quit = \ 'Click on the close button to leave the application.' def quit(self): """Quit the application.""" # XXX Good enough for now but later we want to send a close event. # In the close event handler we can make sure they want to quit. # Other applications, like PythonCard, may choose to hide rather than # quit so we should just post the event and let the surrounding app # decide what it wants to do. self.write('Click on the close button to leave the application.') def setLocalShell(self): """Add 'shell' to locals as reference to ShellFacade instance.""" self.interp.locals['shell'] = ShellFacade(other=self) def execStartupScript(self, startupScript): """Execute the user's PYTHONSTARTUP script if they have one.""" if startupScript and os.path.isfile(startupScript): startupText = 'Startup script executed: ' + startupScript self.push('print %s;execfile(%s)' % \ (`startupText`, `startupScript`)) else: self.push('') def setStyles(self, faces): """Configure font size, typeface and color for lexer.""" # Default style self.StyleSetSpec(wxSTC_STYLE_DEFAULT, "face:%(mono)s,size:%(size)d,back:%(backcol)s" % faces) self.StyleClearAll() # Built in styles self.StyleSetSpec(wxSTC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces) self.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR, "face:%(mono)s" % faces) self.StyleSetSpec(wxSTC_STYLE_BRACELIGHT, "fore:#0000FF,back:#FFFF88") self.StyleSetSpec(wxSTC_STYLE_BRACEBAD, "fore:#FF0000,back:#FFFF88") # Python styles self.StyleSetSpec(wxSTC_P_DEFAULT, "face:%(mono)s" % faces) self.StyleSetSpec(wxSTC_P_COMMENTLINE, "fore:#007F00,face:%(mono)s" % faces) self.StyleSetSpec(wxSTC_P_NUMBER, "") self.StyleSetSpec(wxSTC_P_STRING, "fore:#7F007F,face:%(mono)s" % faces) self.StyleSetSpec(wxSTC_P_CHARACTER, "fore:#7F007F,face:%(mono)s" % faces) self.StyleSetSpec(wxSTC_P_WORD, "fore:#00007F,bold") self.StyleSetSpec(wxSTC_P_TRIPLE, "fore:#7F0000") self.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE, "fore:#000033,back:#FFFFE8") self.StyleSetSpec(wxSTC_P_CLASSNAME, "fore:#0000FF,bold") self.StyleSetSpec(wxSTC_P_DEFNAME, "fore:#007F7F,bold") self.StyleSetSpec(wxSTC_P_OPERATOR, "") self.StyleSetSpec(wxSTC_P_IDENTIFIER, "") self.StyleSetSpec(wxSTC_P_COMMENTBLOCK, "fore:#7F7F7F") self.StyleSetSpec(wxSTC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces) def OnUpdateUI(self, evt): """Check for matching braces.""" braceAtCaret = -1 braceOpposite = -1 charBefore = None caretPos = self.GetCurrentPos() if caretPos > 0: charBefore = self.GetCharAt(caretPos - 1) #*** Patch to fix bug in wxSTC for wxPython < 2.3.3. if charBefore < 0: charBefore = 32 # Mimic a space. #*** styleBefore = self.GetStyleAt(caretPos - 1) # Check before. if charBefore and chr(charBefore) in '[]{}()' \ and styleBefore == wxSTC_P_OPERATOR: braceAtCaret = caretPos - 1 # Check after. if braceAtCaret < 0: charAfter = self.GetCharAt(caretPos) #*** Patch to fix bug in wxSTC for wxPython < 2.3.3. if charAfter < 0: charAfter = 32 # Mimic a space. #*** styleAfter = self.GetStyleAt(caretPos) if charAfter and chr(charAfter) in '[]{}()' \ and styleAfter == wxSTC_P_OPERATOR: braceAtCaret = caretPos if braceAtCaret >= 0: braceOpposite = self.BraceMatch(braceAtCaret) if braceAtCaret != -1 and braceOpposite == -1: self.BraceBadLight(braceAtCaret) else: self.BraceHighlight(braceAtCaret, braceOpposite) def OnChar(self, event): """Keypress event handler. Only receives an event if OnKeyDown calls event.Skip() for the corresponding event.""" # Prevent modification of previously submitted commands/responses. if not self.CanEdit(): return key = event.KeyCode() currpos = self.GetCurrentPos() stoppos = self.promptPosEnd # Return (Enter) needs to be ignored in this handler. if key == WXK_RETURN: pass elif key in self.autoCompleteKeys: # Usually the dot (period) key activates auto completion. # Get the command between the prompt and the cursor. # Add the autocomplete character to the end of the command. command = self.GetTextRange(stoppos, currpos) if command == '': self.historyShow() else: command += chr(key) self.write(chr(key)) if self.autoComplete: self.autoCompleteShow(command) elif key == ord('('): # The left paren activates a call tip and cancels # an active auto completion. if self.AutoCompActive(): self.AutoCompCancel() # Get the command between the prompt and the cursor. # Add the '(' to the end of the command. self.ReplaceSelection('') command = self.GetTextRange(stoppos, currpos) + '(' self.write('(') if self.autoCallTip: self.autoCallTipShow(command) else: # Allow the normal event handling to take place. event.Skip() def OnKeyDown(self, event): """Key down event handler.""" # Prevent modification of previously submitted commands/responses. key = event.KeyCode() controlDown = event.ControlDown() altDown = event.AltDown() shiftDown = event.ShiftDown() currpos = self.GetCurrentPos() endpos = self.GetTextLength() selecting = self.GetSelectionStart() != self.GetSelectionEnd() # Return (Enter) is used to submit a command to the interpreter. if not controlDown and key == WXK_RETURN: if self.AutoCompActive(): event.Skip() return if self.CallTipActive(): self.CallTipCancel() self.processLine() # Ctrl+Return (Cntrl+Enter) is used to insert a line break. elif controlDown and key == WXK_RETURN: if self.AutoCompActive(): self.AutoCompCancel() if self.CallTipActive(): self.CallTipCancel() if (not self.more and (self.GetTextRange(self.promptPosEnd, self.GetCurrentPos()) == '')): self.historyShow() else: self.insertLineBreak() # If the auto-complete window is up let it do its thing. elif self.AutoCompActive(): event.Skip() # Let Ctrl-Alt-* get handled normally. elif controlDown and altDown: event.Skip() # Clear the current, unexecuted command. elif key == WXK_ESCAPE: if self.CallTipActive(): event.Skip() else: self.clearCommand() # Cut to the clipboard. elif (controlDown and key in (ord('X'), ord('x'))) \ or (shiftDown and key == WXK_DELETE): self.Cut() # Copy to the clipboard. elif controlDown and not shiftDown \ and key in (ord('C'), ord('c'), WXK_INSERT): self.Copy() # Copy to the clipboard, including prompts. elif controlDown and shiftDown \ and key in (ord('C'), ord('c'), WXK_INSERT): self.CopyWithPrompts() # Home needs to be aware of the prompt. elif key == WXK_HOME: home = self.promptPosEnd if currpos > home: self.SetCurrentPos(home) if not selecting and not shiftDown: self.SetAnchor(home) self.EnsureCaretVisible() else: event.Skip() # # The following handlers modify text, so we need to see if there # is a selection that includes text prior to the prompt. # # Don't modify a selection with text prior to the prompt. elif selecting and key not in NAVKEYS and not self.CanEdit(): pass # Paste from the clipboard. elif (controlDown and not shiftDown \ and key in (ord('V'), ord('v'))) \ or (shiftDown and not controlDown and key == WXK_INSERT): self.Paste() # Paste from the clipboard, run commands. elif controlDown and shiftDown \ and key in (ord('V'), ord('v')): self.PasteAndRun() # Replace with the previous command from the history buffer. elif (controlDown and key == WXK_UP) \ or (altDown and key in (ord('P'), ord('p'))): self.OnHistoryReplace(step=+1) # Replace with the next command from the history buffer. elif (controlDown and key == WXK_DOWN) \ or (altDown and key in (ord('N'), ord('n'))): self.OnHistoryReplace(step=-1) # Insert the previous command from the history buffer. elif (shiftDown and key == WXK_UP) and self.CanEdit(): self.OnHistoryInsert(step=+1) # Insert the next command from the history buffer. elif (shiftDown and key == WXK_DOWN) and self.CanEdit(): self.OnHistoryInsert(step=-1) # Search up the history for the text in front of the cursor. elif key == WXK_F8: self.OnHistorySearch() # Show all history entries that match the command typed so far: elif key == WXK_F9: self.historyShow(self.getCommand(rstrip=0)) # Don't backspace over the latest non-continuation prompt. elif key == WXK_BACK: if selecting and self.CanEdit(): event.Skip() elif currpos > self.promptPosEnd: event.Skip() # Only allow these keys after the latest prompt. elif key == WXK_DELETE: if self.CanEdit(): event.Skip() elif key == WXK_TAB: if self.CanEdit() and not self.topLevelComplete(): event.Skip() # Don't toggle between insert mode and overwrite mode. elif key == WXK_INSERT: pass # Don't allow line deletion. elif controlDown and key in (ord('L'), ord('l')): pass # Don't allow line transposition. elif controlDown and key in (ord('T'), ord('t')): pass # Basic navigation keys should work anywhere. elif key in NAVKEYS: event.Skip() # Protect the readonly portion of the shell. elif not self.CanEdit(): pass else: event.Skip() def clearCommand(self): """Delete the current, unexecuted command.""" startpos = self.promptPosEnd endpos = self.GetTextLength() self.SetSelection(startpos, endpos) self.ReplaceSelection('') self.more = 0 def OnHistoryReplace(self, step): """Replace with the previous/next command from the history buffer.""" if not self.historyPrefix: self.historyPrefix = 1 self.historyMatches = None prefix = self.getCommand(rstrip=0) n = len(prefix) if n > 0: self.historyMatches = matches = [] for command in self.history: if command[:n] == prefix and command not in matches: matches.append(command) self.clearCommand() self.replaceFromHistory(step, self.historyMatches) def replaceFromHistory(self, step, history=None): """Replace selection with command from the history buffer.""" self.ReplaceSelection('') if history is None: history = self.history newindex = self.historyIndex + step if -1 <= newindex <= len(history): self.historyIndex = newindex if 0 <= newindex <= len(history)-1: command = history[self.historyIndex] command = command.replace('\n', os.linesep + sys.ps2) self.ReplaceSelection(command) def OnHistoryInsert(self, step): """Insert the previous/next command from the history buffer.""" if not self.CanEdit(): return startpos = self.GetCurrentPos() self.replaceFromHistory(step) endpos = self.GetCurrentPos() self.SetSelection(endpos, startpos) def OnHistorySearch(self): """Search up the history buffer for the text in front of the cursor.""" if not self.CanEdit(): return startpos = self.GetCurrentPos() # The text up to the cursor is what we search for. numCharsAfterCursor = self.GetTextLength() - startpos searchText = self.getCommand(rstrip=0) if numCharsAfterCursor > 0: searchText = searchText[:-numCharsAfterCursor] if not searchText: return # Search upwards from the current history position and loop back # to the beginning if we don't find anything. if (self.historyIndex <= -1) \ or (self.historyIndex >= len(self.history)-2): searchOrder = range(len(self.history)) else: searchOrder = range(self.historyIndex+1, len(self.history)) + \ range(self.historyIndex) for i in searchOrder: command = self.history[i] if command[:len(searchText)] == searchText: # Replace the current selection with the one we've found. self.ReplaceSelection(command[len(searchText):]) endpos = self.GetCurrentPos() self.SetSelection(endpos, startpos) # We've now warped into middle of the history. self.historyIndex = i break def setStatusText(self, text): """Display status information.""" # This method will most likely be replaced by the enclosing app # to do something more interesting, like write to a status bar. print text def insertLineBreak(self): """Insert a new line break.""" if self.CanEdit(): self.write(os.linesep) self.more = 1 self.prompt() def processLine(self): """Process the line of text at which the user hit Enter.""" # The user hit ENTER and we need to decide what to do. They could be # sitting on any line in the shell. thepos = self.GetCurrentPos() startpos = self.promptPosEnd endpos = self.GetTextLength() # If they hit RETURN inside the current command, execute the command. if self.CanEdit(): self.SetCurrentPos(endpos) self.interp.more = 0 command = self.GetTextRange(startpos, endpos) lines = command.split(os.linesep + sys.ps2) lines = [line.rstrip() for line in lines] command = '\n'.join(lines) if self.reader.isreading: if not command: # Match the behavior of the standard Python shell when # the user hits return without entering a value. command = '\n' self.reader.input = command self.write(os.linesep) else: self.push(command) # Or replace the current command with the other command. else: # If the line contains a command (even an invalid one). if self.getCommand(rstrip=0): command = self.getMultilineCommand() self.clearCommand() self.write(command) # Otherwise, put the cursor back where we started. else: self.SetCurrentPos(thepos) self.SetAnchor(thepos) def getMultilineCommand(self, rstrip=1): """Extract a multi-line command from the editor. The command may not necessarily be valid Python syntax.""" # XXX Need to extract real prompts here. Need to keep track of the # prompt every time a command is issued. ps1 = str(sys.ps1) ps1size = len(ps1) ps2 = str(sys.ps2) ps2size = len(ps2) # This is a total hack job, but it works. text = self.GetCurLine()[0] line = self.GetCurrentLine() while text[:ps2size] == ps2 and line > 0: line -= 1 self.GotoLine(line) text = self.GetCurLine()[0] if text[:ps1size] == ps1: line = self.GetCurrentLine() self.GotoLine(line) startpos = self.GetCurrentPos() + ps1size line += 1 self.GotoLine(line) while self.GetCurLine()[0][:ps2size] == ps2: line += 1 self.GotoLine(line) stoppos = self.GetCurrentPos() command = self.GetTextRange(startpos, stoppos) command = command.replace(os.linesep + sys.ps2, '\n') command = command.rstrip() command = command.replace('\n', os.linesep + sys.ps2) else: command = '' if rstrip: command = command.rstrip() return command def getCommand(self, text=None, rstrip=1): """Extract a command from text which may include a shell prompt. The command may not necessarily be valid Python syntax.""" if not text: text = self.GetCurLine()[0] # Strip the prompt off the front of text leaving just the command. command = self.lstripPrompt(text) if command == text: command = '' # Real commands have prompts. if rstrip: command = command.rstrip() return command def lstripPrompt(self, text): """Return text without a leading prompt.""" ps1 = str(sys.ps1) ps1size = len(ps1) ps2 = str(sys.ps2) ps2size = len(ps2) # Strip the prompt off the front of text. if text[:ps1size] == ps1: text = text[ps1size:] elif text[:ps2size] == ps2: text = text[ps2size:] return text def push(self, command): """Send command to the interpreter for execution.""" self.write(os.linesep) busy = wxBusyCursor() self.more = self.interp.push(command) del busy if not self.more: self.addHistory(command.rstrip()) for handler in self.handlers: handler() self.prompt() def addHistory(self, command): """Add command to the command history.""" # Reset the history position. self.historyIndex = -1 self.historyPrefix = 0 # Insert this command into the history, unless it's a blank # line or the same as the last command. if command != '' \ and (len(self.history) == 0 or command != self.history[0]): self.history.insert(0, command) def write(self, text): """Display text in the shell. Replace line endings with OS-specific endings.""" text = self.fixLineEndings(text) self.AddText(text) self.EnsureCaretVisible() def fixLineEndings(self, text): """Return text with line endings replaced by OS-specific endings.""" lines = text.split('\r\n') for l in range(len(lines)): chunks = lines[l].split('\r') for c in range(len(chunks)): chunks[c] = os.linesep.join(chunks[c].split('\n')) lines[l] = os.linesep.join(chunks) text = os.linesep.join(lines) return text def prompt(self): """Display appropriate prompt for the context, either ps1, ps2 or ps3. If this is a continuation line, autoindent as necessary.""" isreading = self.reader.isreading skip = 0 if isreading: prompt = str(sys.ps3) elif self.more: prompt = str(sys.ps2) else: prompt = str(sys.ps1) pos = self.GetCurLine()[1] if pos > 0: if isreading: skip = 1 else: self.write(os.linesep) if not self.more: self.promptPosStart = self.GetCurrentPos() if not skip: self.write(prompt) if not self.more: self.promptPosEnd = self.GetCurrentPos() # Keep the undo feature from undoing previous responses. self.EmptyUndoBuffer() # XXX Add some autoindent magic here if more. if self.more: self.write(' '*4) # Temporary hack indentation. self.EnsureCaretVisible() self.ScrollToColumn(0) def readline(self): """Replacement for stdin.readline().""" input = '' reader = self.reader reader.isreading = 1 self.prompt() try: while not reader.input: wxYield() input = reader.input finally: reader.input = '' reader.isreading = 0 return input def raw_input(self, prompt=''): """Return string based on user input.""" if prompt: self.write(prompt) return self.readline() def ask(self, prompt='Please enter your response:'): """Get response from the user using a dialog box.""" dialog = wxTextEntryDialog(None, prompt, \ 'Input Dialog (Raw)', '') try: if dialog.ShowModal() == wxID_OK: text = dialog.GetValue() return text finally: dialog.Destroy() return '' def pause(self): """Halt execution pending a response from the user.""" self.ask('Press enter to continue:') def clear(self): """Delete all text from the shell.""" self.ClearAll() def run(self, command, prompt=1, verbose=1): """Execute command within the shell as if it was typed in directly. >>> shell.run('print "this"') >>> print "this" this >>> """ # Go to the very bottom of the text. endpos = self.GetTextLength() self.SetCurrentPos(endpos) command = command.rstrip() if prompt: self.prompt() if verbose: self.write(command) self.push(command) def runfile(self, filename): """Execute all commands in file as if they were typed into the shell.""" file = open(filename) try: self.prompt() for command in file.readlines(): if command[:6] == 'shell.': # Run shell methods silently. self.run(command, prompt=0, verbose=0) else: self.run(command, prompt=0, verbose=1) finally: file.close() def autoCompleteShow(self, command): """Display auto-completion popup list.""" list = self.interp.getAutoCompleteList(command, includeMagic=self.autoCompleteIncludeMagic, includeSingle=self.autoCompleteIncludeSingle, includeDouble=self.autoCompleteIncludeDouble) if list: options = '\n'.join(list) offset = 0 self.AutoCompShow(offset, options) def autoCallTipShow(self, command): """Display argument spec and docstring in a popup bubble thingie.""" if self.CallTipActive: self.CallTipCancel() (name, argspec, tip) = self.interp.getCallTip(command) if argspec: startpos = self.GetCurrentPos() self.write(argspec + ')') endpos = self.GetCurrentPos() self.SetSelection(endpos, startpos) if tip: curpos = self.GetCurrentPos() tippos = curpos - (len(name) + 1) fallback = curpos - self.GetColumn(curpos) # In case there isn't enough room, only go back to the fallback. tippos = max(tippos, fallback) self.CallTipShow(tippos, tip) def historyShow(self, prefix=''): items = [] for item in self.history: item = item.replace( '\n', '\\n' ) if (prefix == item[:len(prefix)]) and item not in items: items.append(item) self.UserListShow(1, '\n'.join(items)) def OnHistorySelected(self, event): command = event.GetText() if command.find('\\n') >= 0: command += '\\n' command = command.replace( '\\n', os.linesep + sys.ps2) self.clearCommand() self.write(command) # Process the command if the 'Enter' key was pressed: key = event.GetKey() if key == 28 or key == 1241712: # Is there a 'name' for the Enter key? self.processLine() def topLevelComplete(self): command = self.getCommand(rstrip=0) completions = self.interp.getTopLevelCompletions(command) if len(completions) == 0: return 0 if len(completions) == 1: self.write(completions[0][len(command):]) else: self.AutoCompShow(len(command), '\n'.join(completions)) return 1 def writeOut(self, text): """Replacement for stdout.""" self.write(text) def writeErr(self, text): """Replacement for stderr.""" self.write(text) def redirectStdin(self, redirect=1): """If redirect is true then sys.stdin will come from the shell.""" if redirect: sys.stdin = self.reader else: sys.stdin = self.stdin def redirectStdout(self, redirect=1): """If redirect is true then sys.stdout will go to the shell.""" if redirect: sys.stdout = PseudoFileOut(self.writeOut) else: sys.stdout = self.stdout def redirectStderr(self, redirect=1): """If redirect is true then sys.stderr will go to the shell.""" if redirect: sys.stderr = PseudoFileErr(self.writeErr) else: sys.stderr = self.stderr def CanCut(self): """Return true if text is selected and can be cut.""" if self.GetSelectionStart() != self.GetSelectionEnd() \ and self.GetSelectionStart() >= self.promptPosEnd \ and self.GetSelectionEnd() >= self.promptPosEnd: return 1 else: return 0 def CanCopy(self): """Return true if text is selected and can be copied.""" return self.GetSelectionStart() != self.GetSelectionEnd() def CanPaste(self): """Return true if a paste should succeed.""" if self.CanEdit() and \ (wxStyledTextCtrl.CanPaste(self) or \ wxTheClipboard.IsSupported(PythonObject)): return 1 else: return 0 def CanEdit(self): """Return true if editing should succeed.""" if self.GetSelectionStart() != self.GetSelectionEnd(): if self.GetSelectionStart() >= self.promptPosEnd \ and self.GetSelectionEnd() >= self.promptPosEnd: return 1 else: return 0 else: return self.GetCurrentPos() >= self.promptPosEnd def Cut(self): """Remove selection and place it on the clipboard.""" if self.CanCut() and self.CanCopy(): if self.AutoCompActive(): self.AutoCompCancel() if self.CallTipActive: self.CallTipCancel() self.Copy() self.ReplaceSelection('') def Copy(self): """Copy selection and place it on the clipboard.""" if self.CanCopy(): command = self.GetSelectedText() command = command.replace(os.linesep + sys.ps2, os.linesep) command = command.replace(os.linesep + sys.ps1, os.linesep) command = self.lstripPrompt(text=command) data = wxTextDataObject(command) if wxTheClipboard.Open(): wxTheClipboard.SetData(data) wxTheClipboard.Close() def CopyWithPrompts(self): """Copy selection, including prompts, and place it on the clipboard.""" if self.CanCopy(): command = self.GetSelectedText() data = wxTextDataObject(command) if wxTheClipboard.Open(): wxTheClipboard.SetData(data) wxTheClipboard.Close() def Paste(self): """Replace selection with clipboard contents.""" if self.CanPaste() and wxTheClipboard.Open(): try: if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)): data = wxTextDataObject() if wxTheClipboard.GetData(data): self.ReplaceSelection('') command = data.GetText() command = command.rstrip() command = self.fixLineEndings(command) command = self.lstripPrompt(text=command) command = command.replace(os.linesep + sys.ps2, '\n') command = command.replace(os.linesep, '\n') command = command.replace('\n', os.linesep + sys.ps2) self.write(command) if wxTheClipboard.IsSupported(PythonObject) and \ self.python_obj_paste_handler is not None: # note that the presence of a PythonObject on the # clipboard is really just a signal to grab the data # from our singleton clipboard instance data = enClipboard.data self.python_obj_paste_handler(data) finally: wxTheClipboard.Close() return def PasteAndRun(self): """Replace selection with clipboard contents, run commands.""" if wxTheClipboard.Open(): if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)): data = wxTextDataObject() if wxTheClipboard.GetData(data): endpos = self.GetTextLength() self.SetCurrentPos(endpos) startpos = self.promptPosEnd self.SetSelection(startpos, endpos) self.ReplaceSelection('') text = data.GetText() text = text.strip() text = self.fixLineEndings(text) text = self.lstripPrompt(text=text) text = text.replace(os.linesep + sys.ps1, '\n') text = text.replace(os.linesep + sys.ps2, '\n') text = text.replace(os.linesep, '\n') lines = text.split('\n') commands = [] command = '' for line in lines: if line.strip() != '' and line.lstrip() == line: # New command. if command: # Add the previous command to the list. commands.append(command) # Start a new command, which may be multiline. command = line else: # Multiline command. Add to the command. command += '\n' command += line commands.append(command) for command in commands: command = command.replace('\n', os.linesep + sys.ps2) self.write(command) self.processLine() wxTheClipboard.Close() def wrap(self, wrap=1): """Sets whether text is word wrapped.""" try: self.SetWrapMode(wrap) except AttributeError: return 'Wrapping is not available in this version of PyCrust.' def zoom(self, points=0): """Set the zoom level. This number of points is added to the size of all fonts. It may be positive to magnify or negative to reduce.""" self.SetZoom(points) wxID_SELECTALL = wxNewId() ID_AUTOCOMP = wxNewId() ID_AUTOCOMP_SHOW = wxNewId() ID_AUTOCOMP_INCLUDE_MAGIC = wxNewId() ID_AUTOCOMP_INCLUDE_SINGLE = wxNewId() ID_AUTOCOMP_INCLUDE_DOUBLE = wxNewId() ID_CALLTIPS = wxNewId() ID_CALLTIPS_SHOW = wxNewId() ID_FILLING = wxNewId() ID_FILLING_AUTO_UPDATE = wxNewId() ID_FILLING_SHOW_METHODS = wxNewId() ID_FILLING_SHOW_CLASS = wxNewId() ID_FILLING_SHOW_DICT = wxNewId() ID_FILLING_SHOW_DOC = wxNewId() ID_FILLING_SHOW_MODULE = wxNewId() class ShellMenu: """Mixin class to add standard menu items.""" def createMenus(self): m = self.fileMenu = wxMenu() m.AppendSeparator() m.Append(wxID_EXIT, 'E&xit', 'Exit PyCrust') m = self.editMenu = wxMenu() m.Append(wxID_UNDO, '&Undo \tCtrl+Z', 'Undo the last action') m.Append(wxID_REDO, '&Redo \tCtrl+Y', 'Redo the last undone action') m.AppendSeparator() m.Append(wxID_CUT, 'Cu&t \tCtrl+X', 'Cut the selection') m.Append(wxID_COPY, '&Copy \tCtrl+C', 'Copy the selection') m.Append(wxID_PASTE, '&Paste \tCtrl+V', 'Paste') m.AppendSeparator() m.Append(wxID_CLEAR, 'Cle&ar', 'Delete the selection') m.Append(wxID_SELECTALL, 'Select A&ll', 'Select all text') m = self.autocompMenu = wxMenu() m.Append(ID_AUTOCOMP_SHOW, 'Show Auto Completion', \ 'Show auto completion during dot syntax', 1) m.Append(ID_AUTOCOMP_INCLUDE_MAGIC, 'Include Magic Attributes', \ 'Include attributes visible to __getattr__ and __setattr__', 1) m.Append(ID_AUTOCOMP_INCLUDE_SINGLE, 'Include Single Underscores', \ 'Include attibutes prefixed by a single underscore', 1) m.Append(ID_AUTOCOMP_INCLUDE_DOUBLE, 'Include Double Underscores', \ 'Include attibutes prefixed by a double underscore', 1) m = self.calltipsMenu = wxMenu() m.Append(ID_CALLTIPS_SHOW, 'Show Call Tips', \ 'Show call tips with argument specifications', 1) m = self.optionsMenu = wxMenu() m.AppendMenu(ID_AUTOCOMP, '&Auto Completion', self.autocompMenu, \ 'Auto Completion Options') m.AppendMenu(ID_CALLTIPS, '&Call Tips', self.calltipsMenu, \ 'Call Tip Options') if hasattr( self, 'crust' ): fm = self.fillingMenu = wxMenu() fm.Append(ID_FILLING_AUTO_UPDATE, 'Automatic Update', 'Automatically update tree view after each command', 1) fm.Append(ID_FILLING_SHOW_METHODS, 'Show Methods', 'Show methods and functions in the tree view', 1) fm.Append(ID_FILLING_SHOW_CLASS, 'Show __class__', 'Show __class__ entries in the tree view', 1) fm.Append(ID_FILLING_SHOW_DICT, 'Show __dict__', 'Show __dict__ entries in the tree view', 1) fm.Append(ID_FILLING_SHOW_DOC, 'Show __doc__', 'Show __doc__ entries in the tree view', 1) fm.Append(ID_FILLING_SHOW_MODULE, 'Show __module__', 'Show __module__ entries in the tree view', 1) m.AppendMenu(ID_FILLING, '&Filling', fm, 'Filling Options') m = self.helpMenu = wxMenu() m.AppendSeparator() m.Append(wxID_ABOUT, '&About...', 'About PyCrust') b = self.menuBar = wxMenuBar() b.Append(self.fileMenu, '&File') b.Append(self.editMenu, '&Edit') b.Append(self.optionsMenu, '&Options') b.Append(self.helpMenu, '&Help') self.SetMenuBar(b) EVT_MENU(self, wxID_EXIT, self.OnExit) EVT_MENU(self, wxID_UNDO, self.OnUndo) EVT_MENU(self, wxID_REDO, self.OnRedo) EVT_MENU(self, wxID_CUT, self.OnCut) EVT_MENU(self, wxID_COPY, self.OnCopy) EVT_MENU(self, wxID_PASTE, self.OnPaste) EVT_MENU(self, wxID_CLEAR, self.OnClear) EVT_MENU(self, wxID_SELECTALL, self.OnSelectAll) EVT_MENU(self, wxID_ABOUT, self.OnAbout) EVT_MENU(self, ID_AUTOCOMP_SHOW, \ self.OnAutoCompleteShow) EVT_MENU(self, ID_AUTOCOMP_INCLUDE_MAGIC, \ self.OnAutoCompleteIncludeMagic) EVT_MENU(self, ID_AUTOCOMP_INCLUDE_SINGLE, \ self.OnAutoCompleteIncludeSingle) EVT_MENU(self, ID_AUTOCOMP_INCLUDE_DOUBLE, \ self.OnAutoCompleteIncludeDouble) EVT_MENU(self, ID_CALLTIPS_SHOW, \ self.OnCallTipsShow) EVT_UPDATE_UI(self, wxID_UNDO, self.OnUpdateMenu) EVT_UPDATE_UI(self, wxID_REDO, self.OnUpdateMenu) EVT_UPDATE_UI(self, wxID_CUT, self.OnUpdateMenu) EVT_UPDATE_UI(self, wxID_COPY, self.OnUpdateMenu) EVT_UPDATE_UI(self, wxID_PASTE, self.OnUpdateMenu) EVT_UPDATE_UI(self, wxID_CLEAR, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_AUTOCOMP_SHOW, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_MAGIC, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_SINGLE, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_AUTOCOMP_INCLUDE_DOUBLE, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_CALLTIPS_SHOW, self.OnUpdateMenu) if hasattr( self, 'crust' ): EVT_MENU(self, ID_FILLING_AUTO_UPDATE, self.OnFillingAutoUpdate) EVT_MENU(self, ID_FILLING_SHOW_METHODS, self.OnFillingShowMethods) EVT_MENU(self, ID_FILLING_SHOW_CLASS, self.OnFillingShowClass) EVT_MENU(self, ID_FILLING_SHOW_DICT, self.OnFillingShowDict) EVT_MENU(self, ID_FILLING_SHOW_DOC, self.OnFillingShowDoc) EVT_MENU(self, ID_FILLING_SHOW_MODULE, self.OnFillingShowModule) EVT_UPDATE_UI(self, ID_FILLING_AUTO_UPDATE, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_FILLING_SHOW_METHODS, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_FILLING_SHOW_CLASS, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_FILLING_SHOW_DICT, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_FILLING_SHOW_DOC, self.OnUpdateMenu) EVT_UPDATE_UI(self, ID_FILLING_SHOW_MODULE, self.OnUpdateMenu) def OnExit(self, event): self.Close(True) def OnUndo(self, event): self.shell.Undo() def OnRedo(self, event): self.shell.Redo() def OnCut(self, event): self.shell.Cut() def OnCopy(self, event): self.shell.Copy() def OnPaste(self, event): self.shell.Paste() def OnClear(self, event): self.shell.Clear() def OnSelectAll(self, event): self.shell.SelectAll() def OnAbout(self, event): """Display an About PyCrust window.""" import sys title = 'About PyCrust' text = 'PyCrust %s\n\n' % VERSION + \ 'Yet another Python shell, only flakier.\n\n' + \ 'Half-baked by Patrick K. O\'Brien,\n' + \ 'the other half is still in the oven.\n\n' + \ 'Shell Revision: %s\n' % self.shell.revision + \ 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \ 'Python Version: %s\n' % sys.version.split()[0] + \ 'wxPython Version: %s\n' % wx.__version__ + \ 'Platform: %s\n' % sys.platform dialog = wxMessageDialog(self, text, title, wxOK | wxICON_INFORMATION) dialog.ShowModal() dialog.Destroy() def OnAutoCompleteShow(self, event): self.shell.autoComplete = event.IsChecked() def OnAutoCompleteIncludeMagic(self, event): self.shell.autoCompleteIncludeMagic = event.IsChecked() def OnAutoCompleteIncludeSingle(self, event): self.shell.autoCompleteIncludeSingle = event.IsChecked() def OnAutoCompleteIncludeDouble(self, event): self.shell.autoCompleteIncludeDouble = event.IsChecked() def OnCallTipsShow(self, event): self.shell.autoCallTip = event.IsChecked() def OnFillingAutoUpdate(self, event): tree = self.crust.filling.fillingTree tree.autoUpdate = event.IsChecked() tree.if_autoUpdate() def OnFillingShowMethods(self, event): tree = self.crust.filling.fillingTree tree.showMethods = event.IsChecked() tree.update() def OnFillingShowClass(self, event): tree = self.crust.filling.fillingTree tree.showClass = event.IsChecked() tree.update() def OnFillingShowDict(self, event): tree = self.crust.filling.fillingTree tree.showDict = event.IsChecked() tree.update() def OnFillingShowDoc(self, event): tree = self.crust.filling.fillingTree tree.showDoc = event.IsChecked() tree.update() def OnFillingShowModule(self, event): tree = self.crust.filling.fillingTree tree.showModule = event.IsChecked() tree.update() def OnUpdateMenu(self, event): """Update menu items based on current status.""" id = event.GetId() if id == wxID_UNDO: event.Enable(self.shell.CanUndo()) elif id == wxID_REDO: event.Enable(self.shell.CanRedo()) elif id == wxID_CUT: event.Enable(self.shell.CanCut()) elif id == wxID_COPY: event.Enable(self.shell.CanCopy()) elif id == wxID_PASTE: event.Enable(self.shell.CanPaste()) elif id == wxID_CLEAR: event.Enable(self.shell.CanCut()) elif id == ID_AUTOCOMP_SHOW: event.Check(self.shell.autoComplete) elif id == ID_AUTOCOMP_INCLUDE_MAGIC: event.Check(self.shell.autoCompleteIncludeMagic) elif id == ID_AUTOCOMP_INCLUDE_SINGLE: event.Check(self.shell.autoCompleteIncludeSingle) elif id == ID_AUTOCOMP_INCLUDE_DOUBLE: event.Check(self.shell.autoCompleteIncludeDouble) elif id == ID_CALLTIPS_SHOW: event.Check(self.shell.autoCallTip) elif id == ID_FILLING_AUTO_UPDATE: event.Check(self.crust.filling.fillingTree.autoUpdate) elif id == ID_FILLING_SHOW_METHODS: event.Check(self.crust.filling.fillingTree.showMethods) elif id == ID_FILLING_SHOW_CLASS: event.Check(self.crust.filling.fillingTree.showClass) elif id == ID_FILLING_SHOW_DICT: event.Check(self.crust.filling.fillingTree.showDict) elif id == ID_FILLING_SHOW_DOC: event.Check(self.crust.filling.fillingTree.showDoc) elif id == ID_FILLING_SHOW_MODULE: event.Check(self.crust.filling.fillingTree.showModule) pyface-4.1.0/pyface/wx/spreadsheet/0000755000175100001440000000000011674463363020175 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/wx/spreadsheet/default_renderer.py0000644000175100001440000001055311674463363024065 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ #------------------------------------------------------------------------------- # #------------------------------------------------------------------------------- import types from string import atof import wx from wx.grid import PyGridCellRenderer #------------------------------------------------------------------------------- class DefaultRenderer(PyGridCellRenderer): """ This renderer provides the default representation of an Enthought spreadsheet cell. """ selected_cells = wx.Brush(wx.Colour(255,255,200), wx.SOLID) normal_cells = wx.Brush("white", wx.SOLID) odd_cells = wx.Brush(wx.Colour(240,240,240), wx.SOLID) error_cells = wx.Brush(wx.Colour(255,122,122), wx.SOLID) warn_cells = wx.Brush(wx.Colour(255,242,0), wx.SOLID) def __init__(self, color="black", font="ARIAL", fontsize=8): PyGridCellRenderer.__init__(self) self.color = color self.foundary = font self.fontsize = fontsize self.font = wx.Font(fontsize, wx.DEFAULT, wx.NORMAL, wx.NORMAL,0, font) def Clone(self): return DefaultRenderer(self.color, self.foundary, self.fontsize) def Draw(self, grid, attr, dc, rect, row, col, isSelected): self.DrawBackground(grid, attr, dc, rect, row, col, isSelected); self.DrawForeground(grid, attr, dc, rect, row, col, isSelected); dc.DestroyClippingRegion() return def DrawBackground(self, grid, attr, dc, rect, row, col, isSelected): """ Erases whatever is already in the cell by drawing over it. """ # We have to set the clipping region on the grid's DC, # otherwise the text will spill over to the next cell dc.SetClippingRect(rect) # overwrite anything currently in the cell ... dc.SetBackgroundMode(wx.SOLID) dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID)) if isSelected: dc.SetBrush(DefaultRenderer.selected_cells) elif row%2: dc.SetBrush(DefaultRenderer.normal_cells) else: dc.SetBrush(DefaultRenderer.odd_cells) dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) return def DrawForeground(self, grid, attr, dc, rect, row, col, isSelected): """ Draws the cell (text) on top of the existing background color. """ dc.SetBackgroundMode(wx.TRANSPARENT) text = grid.model.GetValue(row, col) dc.SetTextForeground(self.color) dc.SetFont(self.font) dc.DrawText(self.FormatText(text), rect.x+1, rect.y+1) self.DrawEllipses(grid, attr, dc, rect, row, col, isSelected); return def FormatText(self, text): """ Formats numbers to 3 decimal places. """ try: text = '%0.3f' % atof(text) except: pass return text def DrawEllipses(self, grid, attr, dc, rect, row, col, isSelected): """ Adds three dots "..." to indicate the cell is truncated. """ text = grid.model.GetValue(row, col) if not isinstance(text, basestring): msg = 'Problem appending "..." to cell: %d %d' % (row, col) raise TypeError, msg width, height = dc.GetTextExtent(text) if width > rect.width-2: width, height = dc.GetTextExtent("...") x = rect.x+1 + rect.width-2 - width dc.DrawRectangle(x, rect.y+1, width+1, height) dc.DrawText("...", x, rect.y+1) return def GetBestSize88(self, grid, attr, dc, row, col): """ This crashes the app - hmmmm. """ size = PyGridCellRenderer.GetBestSize(self, grid, attr, dc, row, col) print '-----------------------------', size return size #------------------------------------------------------------------------------- pyface-4.1.0/pyface/wx/spreadsheet/unit_renderer.py0000644000175100001440000001032411674463363023414 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx try: from scimath.units.unit_parser import unit_parser except ImportError: unit_parser = None from default_renderer import DefaultRenderer class UnitRenderer(DefaultRenderer): def DrawForeground(self, grid, attr, dc, rect, row, col, isSelected): dc.SetBackgroundMode(wx.TRANSPARENT) text = grid.model.GetValue(row, col) #print 'Rendering ', row, col, text, text.__class__ dc.SetFont(self.font) dc.DrawText(text, rect.x+1, rect.y+1) return def DrawBackground(self, grid, attr, dc, rect, row, col, isSelected): """ Erases whatever is already in the cell by drawing over it. """ # We have to set the clipping region on the grid's DC, # otherwise the text will spill over to the next cell dc.SetClippingRect(rect) # overwrite anything currently in the cell ... dc.SetBackgroundMode(wx.SOLID) dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID)) text = grid.model.GetValue(row, col) if isSelected: dc.SetBrush(DefaultRenderer.selected_cells) elif unit_parser and unit_parser.parse_unit(text).is_valid(): dc.SetBrush(DefaultRenderer.normal_cells) else: dc.SetBrush(DefaultRenderer.error_cells) dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) return #------------------------------------------------------------------------------- class MultiUnitRenderer(DefaultRenderer): def __init__( self, color="black", font="ARIAL", fontsize=8, suppress_warnings=False): self.suppress_warnings = suppress_warnings DefaultRenderer.__init__( self, color, font, fontsize ) def DrawForeground(self, grid, attr, dc, rect, row, col, isSelected): dc.SetBackgroundMode(wx.TRANSPARENT) text = grid.model.GetValue(row, col) dc.SetFont(self.font) dc.DrawText(text, rect.x+1, rect.y+1) return def DrawBackground(self, grid, attr, dc, rect, row, col, isSelected): """ Erases whatever is already in the cell by drawing over it. """ # We have to set the clipping region on the grid's DC, # otherwise the text will spill over to the next cell dc.SetClippingRect(rect) # overwrite anything currently in the cell ... dc.SetBackgroundMode(wx.SOLID) dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID)) text = grid.model.GetValue(row, col) if unit_parser: this_unit = unit_parser.parse_unit(text, self.suppress_warnings) else: this_unit = None # Todo - clean up this hardcoded logic/column position mess family = grid.model.GetValue(row, 6) # AI units of 'impedance ((kg/s)*(g/cc))' creates list of 3, not 2! try: family, other_text = family[:-1].split('(') except: family, other_text = family[:-1].split(' ') if unit_parser: other_unit = unit_parser.parse_unit(other_text, self.suppress_warnings) dimensionally_equivalent = this_unit.can_convert(other_unit) else: other_unit = None dimensionally_equivalent = False if isSelected: dc.SetBrush(DefaultRenderer.selected_cells) elif not this_unit or not this_unit.is_valid(): dc.SetBrush(DefaultRenderer.error_cells) elif not dimensionally_equivalent: dc.SetBrush(DefaultRenderer.warn_cells) else: dc.SetBrush(DefaultRenderer.normal_cells) dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) return pyface-4.1.0/pyface/wx/spreadsheet/font_renderer.py0000644000175100001440000000645611674463363023416 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx from default_renderer import DefaultRenderer class FontRenderer(DefaultRenderer): """Render data in the specified color and font and fontsize. """ def DrawForeground(self, grid, attr, dc, rect, row, col, isSelected): text = grid.model.GetValue(row, col) dc.SetTextForeground(self.color) dc.SetFont(self.font) dc.DrawText(text, rect.x+1, rect.y+1) return def DrawOld(self, grid, attr, dc, rect, row, col, isSelected): # Here we draw text in a grid cell using various fonts # and colors. We have to set the clipping region on # the grid's DC, otherwise the text will spill over # to the next cell dc.SetClippingRect(rect) # clear the background dc.SetBackgroundMode(wx.SOLID) if isSelected: dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID)) dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID)) else: dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID)) dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID)) dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) text = grid.model.GetValue(row, col) dc.SetBackgroundMode(wx.SOLID) # change the text background based on whether the grid is selected # or not if isSelected: dc.SetBrush(self.selectedBrush) dc.SetTextBackground("blue") else: dc.SetBrush(self.normalBrush) dc.SetTextBackground("white") dc.SetTextForeground(self.color) dc.SetFont(self.font) dc.DrawText(text, rect.x+1, rect.y+1) # Okay, now for the advanced class :) # Let's add three dots "..." # to indicate that that there is more text to be read # when the text is larger than the grid cell width, height = dc.GetTextExtent(text) if width > rect.width-2: width, height = dc.GetTextExtent("...") x = rect.x+1 + rect.width-2 - width dc.DrawRectangle(x, rect.y+1, width+1, height) dc.DrawText("...", x, rect.y+1) dc.DestroyClippingRegion() return class FontRendererFactory88: """ I don't grok why this Factory (which I copied from the wx demo) was ever necessary? """ def __init__(self, color, font, fontsize): """ (color, font, fontsize) -> set of a factory to generate renderers when called. func = MegaFontRenderFactory(color, font, fontsize) renderer = func(table) """ self.color = color self.font = font self.fontsize = fontsize def __call__(self, table): return FontRenderer(table, self.color, self.font, self.fontsize) pyface-4.1.0/pyface/wx/spreadsheet/__init__.py0000644000175100001440000000117611674463363022313 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/wx/spreadsheet/abstract_grid_view.py0000644000175100001440000001774611674463363024430 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from numpy import arange import wx from wx.grid import Grid, PyGridTableBase from wx.grid import PyGridCellRenderer from wx.grid import GridCellTextEditor, GridCellStringRenderer from wx.grid import GridCellFloatRenderer, GridCellFloatEditor from wx.lib.mixins.grid import GridAutoEditMixin class ComboboxFocusHandler(wx.EvtHandler): """ Workaround for combobox focus problems in wx 2.6.""" # This is copied from enthought/pyface/grid.combobox_focus_handler.py. # Since this was the only thing that pyface.wx needed from pyface, # and it's a temporary workaround for an outdated version of wx, we're just # copying it here instead of introducing a dependency on a large package. def __init__(self): wx.EvtHandler.__init__(self) wx.EVT_KILL_FOCUS(self, self._on_kill_focus) return def _on_kill_focus(self, evt): # this is pretty egregious. somehow the combobox gives up focus # as soon as it starts up, causing the grid to remove the editor. # so we just don't let it give up focus. i suspect this will cause # some other problem down the road, but it seems to work for now. # fixme: remove this h*ck once the bug is fixed in wx. editor = evt.GetEventObject() if isinstance(editor, wx._controls.ComboBox) and \ evt.GetWindow() is None: return evt.Skip() return class AbstractGridView(Grid): """ Enthought's default spreadsheet view. Uses a virtual data source. THIS CLASS IS NOT LIMITED TO ONLY DISPLAYING LOG DATA! """ def __init__(self, parent, ID=-1, **kw): Grid.__init__(self, parent, ID, **kw) # We have things set up to edit on a single click - so we have to select # an initial cursor location that is off of the screen otherwise a cell # will be in edit mode as soon as the grid fires up. self.moveTo = [1000,1] self.edit = False # this seems like a busy idle ... wx.EVT_IDLE(self, self.OnIdle) # Enthought specific display controls ... self.init_labels() self.init_data_types() self.init_handlers() wx.grid.EVT_GRID_EDITOR_CREATED(self, self._on_editor_created) return # needed to handle problem in wx 2.6 with combobox cell editors def _on_editor_created(self, evt): editor = evt.GetControl() editor.PushEventHandler(ComboboxFocusHandler()) evt.Skip() return def init_labels(self): self.SetLabelFont(wx.Font(self.GetFont().GetPointSize(), wx.SWISS, wx.NORMAL, wx.BOLD)) self.SetGridLineColour("blue") self.SetColLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.SetRowLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) return def init_data_types(self): """ If the model says a cell is of a specified type, the grid uses the specific renderer and editor set in this method. """ self.RegisterDataType("LogData", GridCellFloatRenderer(precision=3), GridCellFloatEditor()) return def init_handlers(self): wx.grid.EVT_GRID_CELL_LEFT_CLICK(self, self.OnCellLeftClick) wx.grid.EVT_GRID_CELL_RIGHT_CLICK(self, self.OnCellRightClick) wx.grid.EVT_GRID_CELL_LEFT_DCLICK(self, self.OnCellLeftDClick) wx.grid.EVT_GRID_CELL_RIGHT_DCLICK(self, self.OnCellRightDClick) wx.grid.EVT_GRID_LABEL_LEFT_CLICK(self, self.OnLabelLeftClick) wx.grid.EVT_GRID_LABEL_RIGHT_CLICK(self, self.OnLabelRightClick) wx.grid.EVT_GRID_LABEL_LEFT_DCLICK(self, self.OnLabelLeftDClick) wx.grid.EVT_GRID_LABEL_RIGHT_DCLICK(self, self.OnLabelRightDClick) wx.grid.EVT_GRID_ROW_SIZE(self, self.OnRowSize) wx.grid.EVT_GRID_COL_SIZE(self, self.OnColSize) wx.grid.EVT_GRID_RANGE_SELECT(self, self.OnRangeSelect) wx.grid.EVT_GRID_CELL_CHANGE(self, self.OnCellChange) wx.grid.EVT_GRID_SELECT_CELL(self, self.OnSelectCell) wx.grid.EVT_GRID_EDITOR_SHOWN(self, self.OnEditorShown) wx.grid.EVT_GRID_EDITOR_HIDDEN(self, self.OnEditorHidden) wx.grid.EVT_GRID_EDITOR_CREATED(self, self.OnEditorCreated) return def SetColLabelsVisible(self, show=True): """ This only works if you 'hide' then 'show' the labels. """ if not show: self._default_col_label_size = self.GetColLabelSize() self.SetColLabelSize(0) else: self.SetColLabelSize(self._default_col_label_size) return def SetRowLabelsVisible(self, show=True): """ This only works if you 'hide' then 'show' the labels. """ if not show: self._default_row_label_size = self.GetRowLabelSize() self.SetRowLabelSize(0) else: self.SetRowLabelSize(self._default_row_label_size) return def SetTable(self, table, *args): """ Some versions of wxPython do not return the correct table - hence we store our own copy here - weak ref? todo - does this apply to Enthought? """ self._table = table return Grid.SetTable(self, table, *args) def GetTable(self): # Terminate editing of the current cell to force an update of the table self.DisableCellEditControl() return self._table def Reset(self): """ Resets the view based on the data in the table. Call this when rows are added or destroyed. """ self._table.ResetView(self) def OnCellLeftClick(self, evt): evt.Skip() def OnCellRightClick(self, evt): #print self.GetDefaultRendererForCell(evt.GetRow(), evt.GetCol()) evt.Skip() def OnCellLeftDClick(self, evt): if self.CanEnableCellControl(): self.EnableCellEditControl() evt.Skip() def OnCellRightDClick(self, evt): evt.Skip() def OnLabelLeftClick(self, evt): evt.Skip() def OnLabelRightClick(self, evt): evt.Skip() def OnLabelLeftDClick(self, evt): evt.Skip() def OnLabelRightDClick(self, evt): evt.Skip() def OnRowSize(self, evt): evt.Skip() def OnColSize(self, evt): evt.Skip() def OnRangeSelect(self, evt): #if evt.Selecting(): # print "OnRangeSelect: top-left %s, bottom-right %s\n" % (evt.GetTopLeftCoords(), evt.GetBottomRightCoords()) evt.Skip() def OnCellChange(self, evt): evt.Skip() def OnIdle(self, evt): """ Immediately jumps into editing mode, bypassing the usual select mode of a spreadsheet. See also self.OnSelectCell(). """ if self.edit == True: if self.CanEnableCellControl(): self.EnableCellEditControl() self.edit = False if self.moveTo != None: self.SetGridCursor(self.moveTo[0], self.moveTo[1]) self.moveTo = None evt.Skip() def OnSelectCell(self, evt): """ Immediately jumps into editing mode, bypassing the usual select mode of a spreadsheet. See also self.OnIdle(). """ self.edit = True evt.Skip() def OnEditorShown(self, evt): evt.Skip() def OnEditorHidden(self, evt): evt.Skip() def OnEditorCreated(self, evt): evt.Skip() #------------------------------------------------------------------------------- pyface-4.1.0/pyface/wx/spreadsheet/virtual_model.py0000644000175100001440000002177011674463363023424 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from wx.grid import Grid, PyGridTableBase, GridCellAttr, GridTableMessage, GridCellFloatRenderer from wx.grid import GRIDTABLE_NOTIFY_ROWS_DELETED, GRIDTABLE_NOTIFY_ROWS_APPENDED from wx.grid import GRIDTABLE_NOTIFY_COLS_DELETED, GRIDTABLE_NOTIFY_COLS_APPENDED from wx.grid import GRIDTABLE_REQUEST_VIEW_GET_VALUES from wx.grid import GRID_VALUE_BOOL from wx import ALIGN_LEFT, ALIGN_CENTRE, Colour from default_renderer import DefaultRenderer class VirtualModel(PyGridTableBase): """ A custom wxGrid Table that expects a user supplied data source. THIS CLASS IS NOT LIMITED TO ONLY DISPLAYING LOG DATA! """ def __init__(self, data, column_names): """data is currently a list of the form [(rowname, dictionary), dictionary.get(colname, None) returns the data for a cell """ ##print 'Initializing virtual model' PyGridTableBase.__init__(self) self.set_data_source(data) self.colnames = column_names #self.renderers = {"DEFAULT_RENDERER":DefaultRenderer()} #self.editors = {} # we need to store the row length and col length to see if the table has changed size self._rows = self.GetNumberRows() self._cols = self.GetNumberCols() #------------------------------------------------------------------------------- # Implement/override the methods from PyGridTableBase #------------------------------------------------------------------------------- def GetNumberCols(self): return len(self.colnames) def GetNumberRows(self): return len(self._data) def GetColLabelValue(self, col): return self.colnames[col] def GetRowLabelValue(self, row): return self._data[row][0] def GetValue(self, row, col): return str(self._data[row][1].get(self.GetColLabelValue(col), "")) def GetRawValue(self, row, col): return self._data[row][1].get(self.GetColLabelValue(col), "") def SetValue(self, row, col, value): print 'Setting value %d %d %s' % (row, col, value) print 'Before ', self.GetValue(row, col) self._data[row][1][self.GetColLabelValue(col)] = value print 'After ', self.GetValue(row, col) ''' def GetTypeName(self, row, col): if col == 2 or col == 6: res = "MeasurementUnits" elif col == 7: res = GRID_VALUE_BOOL else: res = self.base_GetTypeName(row, col) # print 'asked for type of col ', col, ' ' ,res return res''' #------------------------------------------------------------------------------- # Accessors for the Enthought data model (a dict of dicts) #------------------------------------------------------------------------------- def get_data_source(self): """ The data structure we provide the data in. """ return self._data def set_data_source(self, source): self._data = source return #------------------------------------------------------------------------------- # Methods controlling updating and editing of cells in grid #------------------------------------------------------------------------------- def ResetView(self, grid): """ (wxGrid) -> Reset the grid view. Call this to update the grid if rows and columns have been added or deleted """ ##print 'VirtualModel.reset_view' grid.BeginBatch() for current, new, delmsg, addmsg in [ (self._rows, self.GetNumberRows(), GRIDTABLE_NOTIFY_ROWS_DELETED, GRIDTABLE_NOTIFY_ROWS_APPENDED), (self._cols, self.GetNumberCols(), GRIDTABLE_NOTIFY_COLS_DELETED, GRIDTABLE_NOTIFY_COLS_APPENDED), ]: if new < current: msg = GridTableMessage(self,delmsg,new,current-new) grid.ProcessTableMessage(msg) elif new > current: msg = GridTableMessage(self,addmsg,new-current) grid.ProcessTableMessage(msg) self.UpdateValues(grid) grid.EndBatch() self._rows = self.GetNumberRows() self._cols = self.GetNumberCols() # update the renderers # self._updateColAttrs(grid) # self._updateRowAttrs(grid) too expensive to use on a large grid # update the scrollbars and the displayed part of the grid grid.AdjustScrollbars() grid.ForceRefresh() def UpdateValues(self, grid): """Update all displayed values""" # This sends an event to the grid table to update all of the values msg = GridTableMessage(self, GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) def GetAttr88(self, row, col, someExtraParameter ): print 'Overridden GetAttr ', row, col """Part of a workaround to avoid use of attributes, queried by _PropertyGrid's IsCurrentCellReadOnly""" #property = self.GetPropertyForCoordinate( row, col ) #object = self.GetObjectForCoordinate( row, col ) #if property.ReadOnly( object ): attr = GridCellAttr() attr.SetReadOnly( 1 ) return attr #return None def _updateColAttrs88(self, grid): """ wxGrid -> update the column attributes to add the appropriate renderer given the column name. """ for col, colname in enumerate(self.colnames): attr = GridCellAttr() #attr.SetAlignment(ALIGN_LEFT, ALIGN_CENTRE) if colname in self.renderers: # renderer = self.plugins[colname](self) renderer = self.renderers[colname] #if renderer.colSize: # grid.SetColSize(col, renderer.colSize) #if renderer.rowSize: # grid.SetDefaultRowSize(renderer.rowSize) # attr.SetReadOnly(False) # attr.SetRenderer(renderer) else: renderer = self.renderers["DEFAULT_RENDERER"] # .Clone() attr.SetRenderer(renderer) """else: #renderer = GridCellFloatRenderer(6,2) #attr.SetReadOnly(True) #attr.SetRenderer(renderer)""" if colname in self.editors: editor = self.editors[colname] attr.SetEditor(editor) grid.SetColAttr(col, attr) return #------------------------------------------------------------------------------ # code to manipulate the table (non wx related) #------------------------------------------------------------------------------ def AppendRow(self, row): """ Append a tupe containing (name, data) """ name, data = row print 'Appending ', name self._data.append(row) '''entry = {} for name in self.colnames: entry[name] = "Appended_%i"%row return''' def DeleteCols88(self, cols): """ cols -> delete the columns from the dataset cols hold the column indices """ # we'll cheat here and just remove the name from the # list of column names. The data will remain but # it won't be shown deleteCount = 0 cols = cols[:] cols.sort() for i in cols: self.colnames.pop(i-deleteCount) # we need to advance the delete count # to make sure we delete the right columns deleteCount += 1 if not len(self.colnames): self.data = [] def DeleteRow(self, row): name, data = row print 'Deleting ', name self._data.remove(row) def DeleteRows88(self, rows): """ rows -> delete the rows from the dataset rows hold the row indices """ deleteCount = 0 rows = rows[:] rows.sort() for i in rows: self._data.pop(i-deleteCount) # we need to advance the delete count # to make sure we delete the right rows deleteCount += 1 def SortColumn88(self, col): """ to do - never tested tried to rename data to _data and _data to _tmp_data col -> sort the data based on the column indexed by col """ name = self.colnames[col] _tmp_data = [] for row in self._data: rowname, entry = row _tmp_data.append((entry.get(name, None), row)) _tmp_data.sort() self._data = [] for sortvalue, row in _tmp_data: self._data.append(row) pyface-4.1.0/pyface/wx/pager.py0000644000175100001440000000636211674463363017345 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A pager contains a set of pages, but only shows one at a time. """ # Major package imports. import wx from wx.lib.scrolledpanel import wxScrolledPanel class Pager(wxScrolledPanel): """ A pager contains a set of pages, but only shows one at a time. """ def __init__(self, parent, wxid, **kw): """ Creates a new pager. """ # Base-class constructor. wxScrolledPanel.__init__(self, parent, wxid, **kw) self.SetupScrolling() # The pages in the pager! self._pages = {} # { str name : wx.Window page } # The page that is currently displayed. self._current_page = None # Create the widget! self._create_widget() return ########################################################################### # 'Pager' interface. ########################################################################### def add_page(self, name, page): """ Adds a page with the specified name. """ self._pages[name] = page # Make the pager panel big enought ot hold the biggest page. # # fixme: I have a feeling this needs some testing! sw, sh = self.GetSize() pw, ph = page.GetSize() self.SetSize((max(sw, pw), max(sh, ph))) # All pages are added as hidden. Use 'show_page' to make a page # visible. page.Show(False) return page def show_page(self, name): """ Shows the page with the specified name. """ # Hide the current page (if one is displayed). if self._current_page is not None: self._hide_page(self._current_page) # Show the specified page. page = self._show_page(self._pages[name]) # Resize the panel to match the sizer's minimal size. self._sizer.Fit(self) return page ########################################################################### # Private interface. ########################################################################### def _create_widget(self): """ Creates the widget. """ self._sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) return def _hide_page(self, page): """ Hides the specified page. """ page.Show(False) self._sizer.Remove(page) return def _show_page(self, page): """ Shows the specified page. """ page.Show(True) self._sizer.Add(page, 1, wx.EXPAND) self._current_page = page return page #### EOF ###################################################################### pyface-4.1.0/pyface/wx/lazy_switcher.py0000644000175100001440000002154411674463363021135 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Classes to provide a switcher. """ # Major package imports. import wx from wx.lib.scrolledpanel import ScrolledPanel as wxScrolledPanel # Enthought library imports. from traits.api import HasTraits, Int class SwitcherModel(HasTraits): """ Base class for switcher models. """ # The index of the selected 'page'. selected = Int(-1) def __init__(self): """ Creates a new switcher model. """ # The items to display in the switcher control. self.items = [] # (str label, object value) return ########################################################################### # 'SwitcherModel' interface. ########################################################################### def create_page(self, parent, index): """ Creates a page for the switcher panel. """ raise NotImplementedError class SwitcherControl(wx.Panel): """ The default switcher control (a combo box). """ def __init__(self, parent, id, model, label=None, **kw): """ Creates a new switcher control. """ # Base-class constructor. wx.Panel.__init__(self, parent, id, **kw) # The switcher model that we are a controller for. self.model = model # The optional label. self.label = label # Create the widget! self._create_widget(model, label) # Listen for when the selected item in the model is changed. model.on_trait_change(self._on_selected_changed, 'selected') return ########################################################################### # Trait event handlers. ########################################################################### def _on_selected_changed(self, selected): """ Called when the selected item in the model is changed. """ self.combo.SetSelection(selected) return ########################################################################### # wx event handlers. ########################################################################### def _on_combobox(self, event): """ Called when the combo box selection is changed. """ combo = event.GetEventObject() # Update the model. self.model.selected = combo.GetSelection() return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget.""" self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) ##self.SetBackgroundColour("light grey") # Switcher combo. sizer.Add(self._combo(self, model, label), 1, wx.EXPAND) # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return def _combo(self, parent, model, label): """ Creates the switcher combo. """ sizer = wx.BoxSizer(wx.HORIZONTAL) # Label. if label is not None: text = wx.StaticText(parent, -1, label) sizer.Add(text, 0, wx.ALIGN_CENTER | wx.ALL, 5) # Combo. self.combo = combo = wx.ComboBox( parent, -1, style=wx.CB_DROPDOWN | wx.CB_READONLY ) sizer.Add(combo, 1, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5) # Ask the model for the available options. items = model.items if len(items) > 0: for name, data in model.items: combo.Append(name, data) # Listen for changes to the selected item. wx.EVT_COMBOBOX(self, combo.GetId(), self._on_combobox) # If the model's selected variable has been set ... if model.selected != -1: combo.SetSelection(model.selected) return sizer class SwitcherPanel(wxScrolledPanel): """ The default switcher panel. """ def __init__(self, parent, id, model, label=None, cache=True, **kw): # Base-class constructor. wxScrolledPanel.__init__(self, parent, id, **kw) self.SetupScrolling() # The switcher model that we are a panel for. self.model = model # Should we cache pages as we create them? self.cache = cache # The page cache (if caching was requested). self._page_cache = {} # The currently displayed page. self.current = None # Create the widget! self._create_widget(model, label) # Listen for when the selected item in the model is changed. #model.on_trait_change(self._on_selected_changed, 'selected') return ########################################################################### # 'SwitcherPanel' interface. ########################################################################### def show_page(self, index): """ Shows the page at the specified index. """ self._show_page(index) return ########################################################################### # Trait event handlers. ########################################################################### def _on_selected_changed(self, selected): """ Called when the selected item in the model is changed. """ self._show_page(selected) return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget. """ self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) ##self.SetBackgroundColour('red') #if model.selected != -1: # self._show_page(model.selected) # Nothing to add here as we add the panel contents lazily! pass # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return def _show_page(self, index): """ Shows the page at the specified index. """ # If a page is already displayed then hide it. if self.current is not None: current_size = self.current.GetSize() self.current.Show(False) self.sizer.Remove(self.current) # Is the page in the cache? page = self._page_cache.get(index) if not self.cache or page is None: # If not then ask our panel factory to create it. page = self.model.create_page(self, index) # Add it to the cache! self._page_cache[index] = page #if self.current is not None: # page.SetSize(current_size) # Display the page. self.sizer.Add(page, 15, wx.EXPAND, 5) page.Show(True) self.current = page # Force a new layout of the sizer's children but KEEPING the current # dimension. self.sizer.Layout() #self.sizer.Fit(self) #self.SetupScrolling() return class Switcher(wx.Panel): """ A switcher. """ def __init__(self, parent, id, model, label=None, **kw): """ Create a new switcher. """ # Base-class constructor. wx.Panel.__init__(self, parent, id, **kw) # The model that we are a switcher for. self.model = model # Create the widget! self._create_widget(model, label) return ########################################################################### # Private interface. ########################################################################### def _create_widget(self, model, label): """ Creates the widget. """ self.sizer = sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(sizer) self.SetAutoLayout(True) # Switcher control. self.control = control = SwitcherControl(self, -1, model, label) sizer.Add(control, 0, wx.EXPAND) # Switcher panel. self.panel = panel = SwitcherPanel(self, -1, model, label) sizer.Add(panel, 1, wx.EXPAND) # Resize the panel to match the sizer's minimal size. sizer.Fit(self) return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/__init__.py0000644000175100001440000000117711674463363020005 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/wx/color.py0000644000175100001440000000201711674463363017356 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Color utilities. """ from numpy import asarray, array # fixme: This should move into enable. def wx_to_enable_color(color): """ Convert a wx color spec. to an enable color spec. """ enable_color = array((1.0,1.0,1.0,1.0)) enable_color[:3] = asarray(color.Get())/255. return tuple(enable_color) #### EOF ###################################################################### pyface-4.1.0/pyface/wx/dialog.py0000644000175100001440000000770611674463363017511 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Dialog utilities. """ # Major package imports. import wx # A file dialog wildcard for Python files. WILDCARD_PY = "Python files (*.py)|*.py|" # A file dialog wildcard for text files. WILDCARD_TXT = "Text files (*.txt)|*.txt|" # A file dialog wildcard for all files. WILDCARD_ALL = "All files (*.*)|*.*" # A file dialog wildcard for Zip archives. WILDCARD_ZIP = "Zip files (*.zip)|*.zip|" class OpenFileDialog(wx.FileDialog): """ An open-file dialog. """ def __init__(self, parent=None, **kw): """ Constructor. """ style = wx.OPEN | wx.HIDE_READONLY # Base-class constructor. wx.FileDialog.__init__(self, parent, "Open", style=style, **kw) return class OpenDirDialog(wx.DirDialog): """ An open-directory dialog. """ def __init__(self, parent=None, **kw): """ Constructor. """ style = wx.OPEN | wx.HIDE_READONLY | wx.DD_NEW_DIR_BUTTON # Base-class constructor. wx.DirDialog.__init__(self, parent, "Open", style=style, **kw) return class SaveFileAsDialog(wx.FileDialog): """ A save-file dialog. """ def __init__(self, parent=None, **kw): """ Constructor. """ style = wx.SAVE | wx.OVERWRITE_PROMPT # Base-class constructor. wx.FileDialog.__init__(self, parent, "Save As", style=style, **kw) return def confirmation(parent, message, title=None, default=wx.NO_DEFAULT): """ Displays a confirmation dialog. """ dialog = wx.MessageDialog( parent, message, _get_title(title, parent, 'Confirmation'), wx.YES_NO | default | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP ) result = dialog.ShowModal() dialog.Destroy() return result def yes_no_cancel(parent, message, title=None, default=wx.NO_DEFAULT): """ Displays a Yes/No/Cancel dialog. """ dialog = wx.MessageDialog( parent, message, _get_title(title, parent, 'Confirmation'), wx.YES_NO | wx.CANCEL | default | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP ) result = dialog.ShowModal() dialog.Destroy() return result def information(parent, message, title=None): """ Displays a modal information dialog. """ dialog = wx.MessageDialog( parent, message, _get_title(title, parent, 'Information'), wx.OK | wx.ICON_INFORMATION | wx.STAY_ON_TOP ) dialog.ShowModal() dialog.Destroy() return def warning(parent, message, title=None): """ Displays a modal warning dialog. """ dialog = wx.MessageDialog( parent, message, _get_title(title, parent, 'Warning'), wx.OK | wx.ICON_EXCLAMATION | wx.STAY_ON_TOP ) dialog.ShowModal() dialog.Destroy() return def error(parent, message, title=None): """ Displays a modal error dialog. """ dialog = wx.MessageDialog( parent, message, _get_title(title, parent, 'Error'), wx.OK | wx.ICON_ERROR | wx.STAY_ON_TOP ) dialog.ShowModal() dialog.Destroy() return def _get_title(title, parent, default): """ Get a sensible title for a dialog! """ if title is None: if parent is not None: title = parent.GetTitle() else: title = default return title #### EOF ###################################################################### pyface-4.1.0/pyface/wx/clipboard.py0000644000175100001440000000134111674463363020176 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # #------------------------------------------------------------------------------ import logging logging.warn('DEPRECATED: pyface.wx.clipboard, ' 'use pyface.api instead.') from pyface.ui.wx.clipboard import Clipboard clipboard = Clipboard() pyface-4.1.0/pyface/wx/sized_panel.py0000644000175100001440000000333011674463363020534 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A panel sized by a sizer. """ # Major package imports. import wx class SizedPanel(wx.Panel): """ A panel sized by a sizer. """ def __init__(self, parent, wxid, sizer, **kw): """ Creates a new sized panel. """ # Base-class constructor. wx.Panel.__init__(self, parent, wxid, **kw) # Set up the panel's sizer. self.SetSizer(sizer) self.SetAutoLayout(True) # A quick reference to our sizer (at least quicker than using # 'self.GetSizer()' ;^). self.sizer = sizer return ########################################################################### # 'SizedPanel' interface. ########################################################################### def Fit(self): """ Resizes the panel to match the sizer's minimal size. """ self.sizer.Fit(self) return def Layout(self): """ Lays out the sizer without changing the panel geometry. """ self.sizer.Layout() return #### EOF ###################################################################### pyface-4.1.0/pyface/wx/image.py0000644000175100001440000000172511674463363017327 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import os, wx from traits.util.resource import get_path def get_bitmap(root, name): """ Convenience function that returns a bitmap root - either an instance of a class or a path name - name of png file to load """ path = os.path.join(get_path(root), name) bmp = wx.Bitmap(path, wx.BITMAP_TYPE_PNG) return bmp pyface-4.1.0/pyface/i_heading_text.py0000644000175100001440000000271711674463363020564 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Heading text. """ # Enthought library imports. from traits.api import Instance, Int, Interface, Unicode # Local imports. from image_resource import ImageResource class IHeadingText(Interface): """ Heading text. """ #### 'IHeadingText' interface ############################################# # Heading level. # # fixme: Currently we ignore anything but one, but in future we could # have different visualizations based on the level. level = Int(1) # The heading text. text = Unicode('Default') # The background image. image = Instance(ImageResource) class MHeadingText(object): """ The mixin class that contains common code for toolkit specific implementations of the IHeadingText interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/expandable_header.py0000644000175100001440000001752611674463363021230 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A header for an entry in a collection of expandables. The header provides a visual indicator of the current state, a text label, and a 'remove' button. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports. from traits.api import Instance, Event, Str, Bool # local imports from .image_resource import ImageResource from .widget import Widget from .util.font_helper import new_font_like class ExpandableHeader(Widget): """ A header for an entry in a collection of expandables. The header provides a visual indicator of the current state, a text label, and a 'remove' button. """ # The title of the panel. title = Str('Panel') # The carat image to show when the panel is collapsed. collapsed_carat_image = Instance(ImageResource, ImageResource('carat_closed')) # The carat image to show when the panel is expanded. expanded_carat_image = Instance(ImageResource, ImageResource('carat_open')) # The backing header image when the mouse is elsewhere header_bar_image = Instance(ImageResource, ImageResource('panel_gradient')) # The backing header image when the mouse is over header_mouseover_image = Instance(ImageResource, ImageResource('panel_gradient_over')) # The carat image to show when the panel is expanded. remove_image = Instance(ImageResource, ImageResource('close')) # Represents the current state of the button. True means pressed. state = Bool(False) #### Events #### # The panel has been expanded or collapsed panel_expanded = Event _CARAT_X = 4 _CARAT_Y = 2 _TEXT_Y = 0 _TEXT_X_OFFSET = 10 ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, container, **traits): """ Creates the panel. """ # Base class constructor. super(ExpandableHeader, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) self._container = container return ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ collapsed_carat = self.collapsed_carat_image.create_image() self._collapsed_bmp = collapsed_carat.ConvertToBitmap() self._carat_w = self._collapsed_bmp.GetWidth() expanded_carat = self.expanded_carat_image.create_image() self._expanded_bmp = expanded_carat.ConvertToBitmap() header_bar = self.header_bar_image.create_image() self._header_bmp = header_bar.ConvertToBitmap() header_bar_over = self.header_mouseover_image.create_image() self._header_mouseover_bmp = header_bar_over.ConvertToBitmap() self._background_bmp = self._header_bmp close_image = self.remove_image.create_image() self._remove_bmp = close_image.ConvertToBitmap() # create our panel and initialize it appropriately sizer = wx.BoxSizer(wx.VERTICAL) panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) panel.SetSizer(sizer) panel.SetAutoLayout(True) # needed on GTK systems for EVT_ERASE_BACKGROUND to work panel.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) # create the remove button remove = wx.BitmapButton(panel, -1, self._remove_bmp, style=0, pos=(-1, 3)) sizer.Add(remove, 0, wx.ALIGN_RIGHT, 5) # Create a suitable font. self._font = new_font_like(wx.NORMAL_FONT, point_size=wx.NORMAL_FONT.GetPointSize()- 1) height = self._get_preferred_height(parent, self.title, self._font) panel.SetSize((-1, height)) wx.EVT_ERASE_BACKGROUND(panel, self._on_erase_background) wx.EVT_ENTER_WINDOW(panel, self._on_enter_leave) wx.EVT_LEAVE_WINDOW(panel, self._on_enter_leave) wx.EVT_LEFT_DOWN(panel, self._on_down) wx.EVT_RIGHT_DOWN(panel, self._on_down) wx.EVT_BUTTON(panel, remove.GetId(), self._on_remove) return panel def _get_preferred_height(self, parent, text, font): """ Calculates the preferred height of the widget. """ dc = wx.MemoryDC() dc.SetFont(font) text_w, text_h = dc.GetTextExtent(text) text_h = text_h + self._TEXT_Y # add in width of buttons carat_h = self._collapsed_bmp.GetHeight() + self._CARAT_Y return max(text_h, carat_h) def _draw_carat_button(self, dc): """ Draws the button at the correct coordinates. """ if self.state: bmp = self._expanded_bmp else: bmp = self._collapsed_bmp dc.DrawBitmap(bmp, self._CARAT_X, self._CARAT_Y, True) return def _tile_background_image(self, dc, width, height): """ Tiles the background image. """ w = self._background_bmp.GetWidth() h = self._background_bmp.GetHeight() x = 0 while x < width: y = 0 while y < height: dc.DrawBitmap(self._background_bmp, x, y) y = y + h x = x + w return def _draw_title(self, dc): """ Draws the text label for the header. """ dc.SetFont(self._font) # Render the text. dc.DrawText(self.title, self._carat_w + self._TEXT_X_OFFSET, self._TEXT_Y) def _draw(self, dc): """ Draws the control. """ size = self.control.GetClientSize() # Tile the background image. self._tile_background_image(dc, size.width, size.height) self._draw_title(dc) # Draw the carat button self._draw_carat_button(dc) return ########################################################################### # wx event handlers. ########################################################################### def _on_erase_background(self, event): """ Called when the background of the panel is erased. """ #print 'ImageButton._on_erase_background' dc = event.GetDC() self._draw(dc) return def _on_enter_leave(self, event): """ Called when button is pressed. """ #print 'ExpandableHeader._on_enter_leave' if event.Entering(): self._background_bmp = self._header_mouseover_bmp else: self._background_bmp = self._header_bmp self.control.Refresh() event.Skip() def _on_down(self, event): """ Called when button is pressed. """ #print 'ImageButton._on_down' self.state = not self.state self.control.Refresh() # fire an event so any listeners can pick up the change self.panel_expanded = self event.Skip() def _on_remove(self, event): """ Called when remove button is pressed. """ self._container.remove_panel(self.title) pyface-4.1.0/pyface/image_cache.py0000644000175100001440000000160111674463363020005 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of an image cache. """ # Import the toolkit specific version. from toolkit import toolkit_object ImageCache = toolkit_object('image_cache:ImageCache') #### EOF ###################################################################### pyface-4.1.0/pyface/i_dialog.py0000644000175100001440000001147011674463363017354 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The abstract interface for all pyface dialogs. """ # Enthought library imports. from traits.api import Bool, Enum, Int, Str, Unicode # Local imports. from constant import OK from i_window import IWindow class IDialog(IWindow): """ The abstract interface for all pyface dialogs. Usage: Sub-class this class and either override '_create_contents' or more simply, just override the two methods that do the real work:- 1) '_create_dialog_area' creates the main content of the dialog. 2) '_create_buttons' creates the dialog buttons. """ #### 'IDialog' interface ################################################## # The label for the 'cancel' button. The default is toolkit specific. cancel_label = Unicode # The context sensitive help Id (the 'Help' button is only shown iff this # is set). help_id = Str # The label for the 'help' button. The default is toolkit specific. help_label = Unicode # The label for the 'ok' button. The default is toolkit specific. ok_label = Unicode # Is the dialog resizeable? resizeable = Bool(True) # The return code after the window is closed to indicate whether the dialog # was closed via 'Ok' or 'Cancel'). return_code = Int(OK) # The dialog style (is it modal or not). # FIXME v3: It doesn't seem possible to use non-modal dialogs. (How do you # get access to the buttons?) style = Enum('modal', 'nonmodal') ########################################################################### # 'IDialog' interface. ########################################################################### def open(self): """ Opens the dialog. If the dialog is modal then the dialog's event loop is entered and the dialog closed afterwards. The 'return_code' trait is updated according to the button the user pressed and this value is also returned. If the dialog is non-modal 'OK' is returned. """ ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_buttons(self, parent): """ Create and return the buttons. parent is the parent control. """ def _create_contents(self, parent): """ Create the dialog contents. parent is the parent control. """ def _create_dialog_area(self, parent): """ Create and return the main content of the dialog. parent is the parent control. """ def _show_modal(self): """ Opens the dialog as a modal dialog and returns the return code. """ class MDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IDialog interface. Implements: open() Reimplements: _add_event_listeners(), _create() """ ########################################################################### # 'IDialog' interface. ########################################################################### def open(self): """ Opens the dialog. """ if self.control is None: self._create() if self.style == 'modal': self.return_code = self._show_modal() self.close() else: self.show(True) self.return_code = OK return self.return_code ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): """ Creates the window's widget hierarchy. """ super(MDialog, self)._create() self._create_contents(self.control) ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): """ Adds any event listeners required by the window. """ # We don't bother for dialogs. pass #### EOF ###################################################################### pyface-4.1.0/pyface/ui/0000755000175100001440000000000011674463363015645 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/0000755000175100001440000000000011674463363016355 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/timer/0000755000175100001440000000000011674463363017475 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/timer/timer.py0000644000175100001440000000472411674463363021176 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore class Timer(QtCore.QTimer): """Simple subclass of QTimer that allows the user to have a function called periodically. Some code assumes that this is a sub-class of wx.Timer, so we add a few wrapper methods to pretend it is. Any exceptions raised in the callable are caught. If `StopIteration` is raised the timer stops. If other exceptions are encountered the timer is stopped and the exception re-raised. """ def __init__(self, millisecs, callable, *args, **kw_args): """ Initialize instance to invoke the given `callable` with given arguments and keyword args after every `millisecs` (milliseconds). """ QtCore.QTimer.__init__(self) self.callable = callable self.args = args self.kw_args = kw_args self.connect(self, QtCore.SIGNAL('timeout()'), self.Notify) self._is_active = True self.start(millisecs) def Notify(self): """ Call the given callable. Exceptions raised in the callable are caught. If `StopIteration` is raised the timer stops. If other exceptions are encountered the timer is stopped and the exception re-raised. Note that the name of this method is part of the API because some code expects this to be a wx.Timer sub-class. """ try: self.callable(*self.args, **self.kw_args) except StopIteration: self.stop() except: self.stop() raise def Start(self, millisecs=None): """ Emulate wx.Timer. """ self._is_active = True if millisecs is None: self.start() else: self.start(millisecs) def Stop(self): """ Emulate wx.Timer. """ self._is_active = False self.stop() def IsRunning(self): """ Emulate wx.Timer. """ return self._is_active pyface-4.1.0/pyface/ui/qt4/timer/do_later.py0000644000175100001440000000347611674463363021652 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore class DoLaterTimer(QtCore.QTimer): # List of currently active timers: active_timers = [] #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__(self, interval, callable, args, kw_args): QtCore.QTimer.__init__(self) global active_timers for timer in self.active_timers: if ((timer.callable == callable) and (timer.args == args) and (timer.kw_args == kw_args)): timer.start(interval) return self.active_timers.append(self) self.callable = callable self.args = args self.kw_args = kw_args self.connect(self, QtCore.SIGNAL('timeout()'), self.Notify) self.setSingleShot(True) self.start(interval) #--------------------------------------------------------------------------- # Handles the timer pop event: #--------------------------------------------------------------------------- def Notify(self): global active_timers self.active_timers.remove(self) self.callable(*self.args, **self.kw_args) pyface-4.1.0/pyface/ui/qt4/timer/__init__.py0000644000175100001440000000010411674463363021601 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/ui/qt4/image_resource.py0000644000175100001440000000516611674463363021730 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import os # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import Any, HasTraits, implements, List, Property from traits.api import Unicode # Local imports. from pyface.i_image_resource import IImageResource, MImageResource class ImageResource(MImageResource, HasTraits): """ The toolkit specific implementation of an ImageResource. See the IImageResource interface for the API documentation. """ implements(IImageResource) #### Private interface #################################################### # The resource manager reference for the image. _ref = Any #### 'ImageResource' interface ############################################ absolute_path = Property(Unicode) name = Unicode search_path = List ########################################################################### # 'ImageResource' interface. ########################################################################### # Qt doesn't specifically require bitmaps anywhere so just use images. create_bitmap = MImageResource.create_image def create_icon(self, size=None): ref = self._get_ref(size) if ref is not None: image = ref.load() else: image = self._get_image_not_found_image() return QtGui.QIcon(image) ########################################################################### # Private interface. ########################################################################### def _get_absolute_path(self): # FIXME: This doesn't quite work the new notion of image size. We # should find out who is actually using this trait, and for what! # (AboutDialog uses it to include the path name in some HTML.) ref = self._get_ref() if ref is not None: absolute_path = os.path.abspath(self._ref.filename) else: absolute_path = self._get_image_not_found().absolute_path return absolute_path #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/system_metrics.py0000644000175100001440000000351611674463363022006 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import HasTraits, implements, Int, Property, Tuple # Local imports. from pyface.i_system_metrics import ISystemMetrics, MSystemMetrics class SystemMetrics(MSystemMetrics, HasTraits): """ The toolkit specific implementation of a SystemMetrics. See the ISystemMetrics interface for the API documentation. """ implements(ISystemMetrics) #### 'ISystemMetrics' interface ########################################### screen_width = Property(Int) screen_height = Property(Int) dialog_background_color = Property(Tuple) ########################################################################### # Private interface. ########################################################################### def _get_screen_width(self): return QtGui.QApplication.instance().desktop().screenGeometry().width() def _get_screen_height(self): return QtGui.QApplication.instance().desktop().screenGeometry().height() def _get_dialog_background_color(self): color = QtGui.QApplication.instance().palette().color(QtGui.QPalette.Window) return (color.redF(), color.greenF(), color.blueF()) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/init.py0000644000175100001440000000226111674463363017673 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import sys # Major package imports. from pyface.qt import QtCore, QtGui, qt_api if qt_api == 'pyqt': # Check the version numbers are late enough. if QtCore.QT_VERSION < 0x040200: raise RuntimeError, "Need Qt v4.2 or higher, but got v%s" % QtCore.QT_VERSION_STR if QtCore.PYQT_VERSION < 0x040100: raise RuntimeError, "Need PyQt v4.1 or higher, but got v%s" % QtCore.PYQT_VERSION_STR # It's possible that it has already been initialised. _app = QtGui.QApplication.instance() if _app is None: _app = QtGui.QApplication(sys.argv) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/confirmation_dialog.py0000644000175100001440000001017311674463363022740 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, Dict, Enum, implements, Instance, Unicode # Local imports. from pyface.i_confirmation_dialog import IConfirmationDialog, MConfirmationDialog from pyface.constant import CANCEL, YES, NO from pyface.image_resource import ImageResource from dialog import Dialog class ConfirmationDialog(MConfirmationDialog, Dialog): """ The toolkit specific implementation of a ConfirmationDialog. See the IConfirmationDialog interface for the API documentation. """ implements(IConfirmationDialog) #### 'IConfirmationDialog' interface ###################################### cancel = Bool(False) default = Enum(NO, YES, CANCEL) image = Instance(ImageResource) message = Unicode informative = Unicode detail = Unicode no_label = Unicode yes_label = Unicode # If we create custom buttons with the various roles, then we need to # keep track of the buttons so we can see what the user clicked. It's # not correct nor sufficient to check the return result from QMessageBox.exec_(). # (As of Qt 4.5.1, even clicking a button with the YesRole would lead to # exec_() returning QDialog.Rejected. _button_result_map = Dict() ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In PyQt this is a canned dialog. pass ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): dlg = QtGui.QMessageBox(parent) dlg.setWindowTitle(self.title) dlg.setText(self.message) dlg.setInformativeText(self.informative) dlg.setDetailedText(self.detail) if self.image is None: dlg.setIcon(QtGui.QMessageBox.Warning) else: dlg.setIconPixmap(self.image.create_image()) # The 'Yes' button. if self.yes_label: btn = dlg.addButton(self.yes_label, QtGui.QMessageBox.YesRole) else: btn = dlg.addButton(QtGui.QMessageBox.Yes) self._button_result_map[btn] = YES if self.default == YES: dlg.setDefaultButton(btn) # The 'No' button. if self.no_label: btn = dlg.addButton(self.no_label, QtGui.QMessageBox.NoRole) else: btn = dlg.addButton(QtGui.QMessageBox.No) self._button_result_map[btn] = NO if self.default == NO: dlg.setDefaultButton(btn) # The 'Cancel' button. if self.cancel: if self.cancel_label: btn = dlg.addButton(self.cancel_label, QtGui.QMessageBox.RejectRole) else: btn = dlg.addButton(QtGui.QMessageBox.Cancel) self._button_result_map[btn] = CANCEL if self.default == CANCEL: dlg.setDefaultButton(btn) return dlg def _show_modal(self): self.control.setWindowModality(QtCore.Qt.ApplicationModal) retval = self.control.exec_() clicked_button = self.control.clickedButton() if clicked_button in self._button_result_map: retval = self._button_result_map[clicked_button] else: retval = _RESULT_MAP[retval] return retval #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/image_cache.py0000644000175100001440000000542311674463363021140 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from pyface.i_image_cache import IImageCache, MImageCache class ImageCache(MImageCache, HasTraits): """ The toolkit specific implementation of an ImageCache. See the IImageCache interface for the API documentation. """ implements(IImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, width, height): self._width = width self._height = height ########################################################################### # 'ImageCache' interface. ########################################################################### def get_image(self, filename): image = QtGui.QPixmap(self._width, self._height) if QtGui.QPixmapCache.find(filename, image): scaled = self._qt4_scale(image) if scaled is not image: # The Qt cache is application wide so we only keep the last # size asked for. QtGui.QPixmapCache.remove(filename); QtGui.QPixmapCache.insert(filename, scaled) else: # Load the image from the file and add it to the cache. image.load(filename) scaled = self._qt4_scale(image) QtGui.QPixmapCache.insert(filename, scaled) return scaled # Qt doesn't distinguish between bitmaps and images. get_bitmap = get_image ########################################################################### # Private 'ImageCache' interface. ########################################################################### def _qt4_scale(self, image): """ Scales the given image if necessary. """ # Although Qt won't scale the image if it doesn't need to, it will make # a deep copy which we don't need. if image.width() != self._width or image.height()!= self._height: image = image.scaled(self._width, self._height) return image #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/images/0000755000175100001440000000000011674463363017622 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/images/heading_level_1.png0000644000175100001440000000424011674463363023336 0ustar ischnellusers00000000000000PNG  IHDRv;lgAMA7tEXtSoftwareAdobe ImageReadyqe<2IDATxb?(%@u0/# b\ t0VyY:@'ssHa50nV)n$ٽx***\0,28 a fП:|ۜUtBxd|,d` 2GOV;CMv:cPL'@Pz#; FӚWķ3u*;)DRsnBK-!JyLRnb*EWj4I_} @E0ś&+a: 8"'7nxywV81xK0 [RtNmu4nz27ŋsh³X<xT3yGE_2`?썶vmKAeUi +?3\. -XGiSY#),.[~S Òc9K_<ڶȕ-e@PYE)'Ժ (i;*pм{ ==*0 |~YwOCM6mhkc0Zdqr8#_<'_ \T0@]k|s 21G/R E-9X#XuϖG+hRgjI躧Q`;3\q%`)#\0`r;_LLV}' /T6>26p lːbwγ]kxK)R/DA+3rx:Kޏ:9za'0lB>jh;UbgV)'"0$g JYR(B~.^6.MkM1[0Bvgpك 6Tכ`.Kne"+]i`5!`c=›]1]iK5 IwA#7 ˞Bm W߻0K#f>?0B3@)fL4X:$R|NnD;Ptehe?rBgGWݽHĺ&A&yqD/ҭPLN; Gb P K@#:\[E nPZFH DBP[VKP;0!+$G#<  0~ep}GG%F*X'RDeꀘzQd{ L@@ \%0U?j:EEfp q8wG‘WS2 t`(Yr"6e.7 ͡buDQ|?u/*Uz,q#s͜[?F )}..pJr@be?Ф g!IhY+ iFHox^2BZ?5m\AAQt:#z[`YYIcaRv| (a`XaM lkDn7'57L0.ТIDrT2ɡ`q)IENDB`pyface-4.1.0/pyface/ui/qt4/images/application.png0000644000175100001440000000651611674463363022643 0ustar ischnellusers00000000000000PNG  IHDR00W IDATxkp]usKWWҕ,ɲ$lyMhz 2ҁN:L3~ 3mg )i !fM a(jKeY}{{Wlikf^{{ . "i&?fEc̗-1cLVD P5LȻZׯ?tm)}%ֿa\dɉ@UDc0|{ _ <3aEVcLu]$a6Q!Et2|h|%~qHӟ1|*d27~s=jzzzP(1QEJ)1l6Y\\T*غu+m0 E2"RMREf%ٴqFlقyLbbb#G4M!a'p<,CݍZ233y$lݽy6yg'cDzΩSc|KwuשI`޽r`"/f||yG/P.Wb2C(X,~%Peqq͛7fZ!33u @ 1Emz{~6r(PJ1==RH6^/=\ev = p_e1AjH: m+:]ͷ/|E\%MΤ)j->Zi! Nh.(lJz2f{%B'QݸqFoΉ'x[o/u7i$ 7`8?ca.RfgǾCx4c̞8 DuJ@lA o a5VX֭Ckl޾_.$wA\f?V#cC+6֚i/sG?c5d7yU2ԑ!QH]R~l!N8qSׯ۹s^~*j?6GGGqz^W3Gzjq?jxn7~|" )7;E2MOoQɦN s4's5݈HA)uzꩧ('NȍY $*_/dttI^]h1ڴF Umbk :q6ר*Pd24ڵiT*_PJ Q9v2#[@}AibB/Q1u$q"=[lTFioEǂ֚Lg*&:溓0le2cFlZ8 277ć]"9:dIb;00y'ޜ$ς?%3%dˁs~Q]`7֭rrSlO4MDS'?pum#;I;0ES3t}Bj5xcб&.b9"qħ\ zD٨ Xny"rE1fyAHfL0zl$-8Nޠ KeE+Lw [/8̚+$lu6Ƙ6˲e" Vi#us'pH$l8&hx=f`tc-,"mP)'XV@:t/i_2n2'y`ɖ8gu_y8[q88*3hqb(nb5:Pv5Ơ,ƴhg} ;luEv< C!Mͺ\#" f1۶H[9 H6۟0 ÚjAE Zyߞ?G|,*/r2~ٮ Dy[YKnfEDh`ɩSHcu0DD{3 +=AWk71/EkІ!yoLȺ:m h=`oD >l ۶_nd2bc l \!̗QR1ή,1aJ$K-h4W$XNʊ<\.SI$;mwyL&S*Zfa}ȩ,b%D Ϝ4_nm&u"+dGI\&I1R%tF0c)QW18~.x^SQT셮u9kK&vj ͋NHl k>ÇS-W:c1kĿAM<[nerrJz1JyotT>fDC蔏ќߍ A{r,O3d)t4)xl8zcG:9sYG^ ڎH\uvs=zRD*x<\s!qnjrRFAjb h0h0F-8 slgmt0m%=;Ag" E$b2hb1#_GCRNX\q8pb(_O{침Rt]w`hh(D`uhJ>;M=}ev~rmS,k>`T*p]˶^&N$mZJ:bm9r JqOf2/=b#U{{鉢DWEdR ˲"r8k?xp}e}߿IDln,+NLƘdՇ~ip] rA.ŕpYZAIENDB`pyface-4.1.0/pyface/ui/qt4/images/image_LICENSE.txt0000644000175100001440000000121411674463363022605 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Enthought BSD 3-Clause LICENSE.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/ui/qt4/images: application.png | Enthought heading_level_1.png | Enthought pyface-4.1.0/pyface/ui/qt4/python_editor.py0000644000175100001440000001412111674463363021615 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import sys # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, Event, implements, Unicode # Local imports. from pyface.i_python_editor import IPythonEditor, MPythonEditor from pyface.key_pressed_event import KeyPressedEvent from code_editor.code_widget import AdvancedCodeWidget from widget import Widget class PythonEditor(MPythonEditor, Widget): """ The toolkit specific implementation of a PythonEditor. See the IPythonEditor interface for the API documentation. """ implements(IPythonEditor) #### 'IPythonEditor' interface ############################################ dirty = Bool(False) path = Unicode show_line_numbers = Bool(True) #### Events #### changed = Event key_pressed = Event(KeyPressedEvent) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): super(PythonEditor, self).__init__(**traits) self.control = self._create_control(parent) ########################################################################### # 'PythonEditor' interface. ########################################################################### def load(self, path=None): """ Loads the contents of the editor. """ if path is None: path = self.path # We will have no path for a new script. if len(path) > 0: f = open(self.path, 'r') text = f.read() f.close() else: text = '' self.control.code.setPlainText(text) self.dirty = False def save(self, path=None): """ Saves the contents of the editor. """ if path is None: path = self.path f = file(path, 'w') f.write(self.control.code.toPlainText()) f.close() self.dirty = False def select_line(self, lineno): """ Selects the specified line. """ self.control.code.set_line_column(lineno, 0) self.control.code.moveCursor(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) ########################################################################### # Trait handlers. ########################################################################### def _path_changed(self): self._changed_path() def _show_line_numbers_changed(self): if self.control is not None: self.control.code.line_number_widget.setVisible( self.show_line_numbers) self.control.code.update_line_number_width() ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Creates the toolkit-specific control for the widget. """ self.control = control = AdvancedCodeWidget(parent) self._show_line_numbers_changed() # Install event filter to trap key presses. event_filter = PythonEditorEventFilter(self, self.control) self.control.installEventFilter(event_filter) self.control.code.installEventFilter(event_filter) # Connect signals for text changes. control.code.modificationChanged.connect(self._on_dirty_changed) control.code.textChanged.connect(self._on_text_changed) # Load the editor's contents. self.load() return control def _on_dirty_changed(self, dirty): """ Called whenever a change is made to the dirty state of the document. """ self.dirty = dirty def _on_text_changed(self): """ Called whenever a change is made to the text of the document. """ self.changed = True class PythonEditorEventFilter(QtCore.QObject): """ A thin wrapper around the advanced code widget to handle the key_pressed Event. """ def __init__(self, editor, parent): super(PythonEditorEventFilter, self).__init__(parent) self.__editor = editor def eventFilter(self, obj, event): """ Reimplemented to trap key presses. """ if self.__editor.control and obj == self.__editor.control and \ event.type() == QtCore.QEvent.FocusOut: # Hack for Traits UI compatibility. self.control.emit(QtCore.SIGNAL('lostFocus')) elif self.__editor.control and obj == self.__editor.control.code and \ event.type() == QtCore.QEvent.KeyPress: # Pyface doesn't seem to be Unicode aware. Only keep the key code # if it corresponds to a single Latin1 character. kstr = event.text() try: kcode = ord(str(kstr)) except: kcode = 0 mods = event.modifiers() self.key_pressed = KeyPressedEvent( alt_down = ((mods & QtCore.Qt.AltModifier) == QtCore.Qt.AltModifier), control_down = ((mods & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier), shift_down = ((mods & QtCore.Qt.ShiftModifier) == QtCore.Qt.ShiftModifier), key_code = kcode, event = event) return super(PythonEditorEventFilter, self).eventFilter(obj, event) pyface-4.1.0/pyface/ui/qt4/application_window.py0000644000175100001440000001474411674463363022633 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described # in the PyQt GPL exception also apply. # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from pyface.action.api import MenuBarManager, StatusBarManager from pyface.action.api import ToolBarManager from traits.api import Instance, List, Unicode, implements, \ on_trait_change # Local imports. from pyface.i_application_window import IApplicationWindow, \ MApplicationWindow from pyface.image_resource import ImageResource from window import Window class ApplicationWindow(MApplicationWindow, Window): """ The toolkit specific implementation of an ApplicationWindow. See the IApplicationWindow interface for the API documentation. """ implements(IApplicationWindow) #### 'IApplicationWindow' interface ####################################### icon = Instance(ImageResource) menu_bar_manager = Instance(MenuBarManager) status_bar_manager = Instance(StatusBarManager) tool_bar_manager = Instance(ToolBarManager) tool_bar_managers = List(ToolBarManager) #### 'IWindow' interface ################################################## title = Unicode("Pyface") ########################################################################### # Protected 'IApplicationWindow' interface. ########################################################################### def _create_contents(self, parent): panel = QtGui.QWidget(parent) palette = QtGui.QPalette(panel.palette()) palette.setColor(QtGui.QPalette.Window, QtGui.QColor('blue')) panel.setPalette(palette) panel.setAutoFillBackground(True) return panel def _create_menu_bar(self, parent): if self.menu_bar_manager is not None: menu_bar = self.menu_bar_manager.create_menu_bar(parent) self.control.setMenuBar(menu_bar) def _create_status_bar(self, parent): if self.status_bar_manager is not None: status_bar = self.status_bar_manager.create_status_bar(parent) # QMainWindow automatically makes the status bar visible, but we # have delegated this responsibility to the status bar manager. self.control.setStatusBar(status_bar) status_bar.setVisible(self.status_bar_manager.visible) def _create_tool_bar(self, parent): tool_bar_managers = self._get_tool_bar_managers() visible = self.control.isVisible() for tool_bar_manager in tool_bar_managers: # Add the tool bar and make sure it is visible. tool_bar = tool_bar_manager.create_tool_bar(parent) self.control.addToolBar(tool_bar) tool_bar.show() # Make sure that the tool bar has a name so its state can be saved. if len(tool_bar.objectName()) == 0: tool_bar.setObjectName(tool_bar_manager.name) # Work around bug in Qt on OS X where creating a tool bar with a # QMainWindow parent hides the window. See # http://bugreports.qt.nokia.com/browse/QTBUG-5069 for more info. self.control.setVisible(visible) def _set_window_icon(self): if self.icon is None: icon = ImageResource('application.png') else: icon = self.icon if self.control is not None: self.control.setWindowIcon(icon.create_icon()) ########################################################################### # 'Window' interface. ########################################################################### def _size_default(self): """ Trait initialiser. """ return (800, 600) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): super(ApplicationWindow, self)._create() contents = self._create_contents(self.control) self.control.setCentralWidget(contents) self._create_trim_widgets(self.control) def _create_control(self, parent): control = QtGui.QMainWindow(parent) control.setObjectName('ApplicationWindow') if self.position != (-1, -1): control.move(*self.position) if self.size != (-1, -1): control.resize(*self.size) control.setAnimated(False) control.setDockNestingEnabled(True) control.setWindowTitle(self.title) return control ########################################################################### # Private interface. ########################################################################### def _get_tool_bar_managers(self): """ Return all tool bar managers specified for the window. """ # fixme: V3 remove the old-style single toolbar option! if self.tool_bar_manager is not None: tool_bar_managers = [self.tool_bar_manager] else: tool_bar_managers = self.tool_bar_managers return tool_bar_managers #### Trait change handlers ################################################ # QMainWindow takes ownership of the menu bar and the status bar upon # assignment. For this reason, it is unnecessary to delete the old controls # in the following two handlers. def _menu_bar_manager_changed(self): if self.control is not None: self._create_menu_bar(self.control) def _status_bar_manager_changed(self): if self.control is not None: self._create_status_bar(self.control) @on_trait_change('tool_bar_manager, tool_bar_managers') def _update_tool_bar_managers(self): if self.control is not None: # Remove the old toolbars. for child in self.control.children(): if isinstance(child, QtGui.QToolBar): self.control.removeToolBar(child) child.deleteLater() # Add the new toolbars. self._create_tool_bar(self.control) def _icon_changed(self): self._set_window_icon() pyface-4.1.0/pyface/ui/qt4/file_dialog.py0000644000175100001440000001254111674463363021170 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import os # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Enum, implements, Int, Unicode, List # Local imports. from pyface.i_file_dialog import IFileDialog, MFileDialog from dialog import Dialog class FileDialog(MFileDialog, Dialog): """ The toolkit specific implementation of a FileDialog. See the IFileDialog interface for the API documentation. """ implements(IFileDialog) #### 'IFileDialog' interface ############################################## action = Enum('open', 'open files', 'save as') default_directory = Unicode default_filename = Unicode default_path = Unicode directory = Unicode filename = Unicode path = Unicode paths = List(Unicode) wildcard = Unicode wildcard_index = Int(0) ########################################################################### # 'MFileDialog' *CLASS* interface. ########################################################################### # In Windows, Qt needs only a * while wx needs a *.* WILDCARD_ALL = "All files (*)|*" @classmethod def create_wildcard(cls, description, extension): """ Creates a wildcard for a given extension. """ if isinstance(extension, basestring): pattern = extension else: pattern = ' '.join(extension) return "%s (%s)|%s|" % (description, pattern, pattern) ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In PyQt this is a canned dialog. pass ########################################################################### # 'IWindow' interface. ########################################################################### def close(self): # Get the path of the chosen directory. files = self.control.selectedFiles() if files: self.path = unicode(files[0]) self.paths = [unicode(file) for file in files] else: self.path = '' self.paths = [''] # Extract the directory and filename. self.directory, self.filename = os.path.split(self.path) # Get the index of the selected filter. self.wildcard_index = self.control.nameFilters().index( self.control.selectedNameFilter()) # Let the window close as normal. super(FileDialog, self).close() ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # If the caller provided a default path instead of a default directory # and filename, split the path into it directory and filename # components. if len(self.default_path) != 0 and len(self.default_directory) == 0 \ and len(self.default_filename) == 0: default_directory, default_filename = os.path.split(self.default_path) else: default_directory = self.default_directory default_filename = self.default_filename # Convert the filter. filters = [] for filter_list in self.wildcard.split('|')[::2]: # Qt uses spaces instead of semicolons for extension separators filter_list = filter_list.replace(';', ' ') filters.append(filter_list) # Set the default directory. if not default_directory: default_directory = QtCore.QDir.currentPath() dlg = QtGui.QFileDialog(parent, self.title, default_directory) dlg.setViewMode(QtGui.QFileDialog.Detail) dlg.selectFile(default_filename) dlg.setNameFilters(filters) if self.wildcard_index < len(filters): dlg.selectNameFilter(filters[self.wildcard_index]) if self.action == 'open': dlg.setAcceptMode(QtGui.QFileDialog.AcceptOpen) dlg.setFileMode(QtGui.QFileDialog.ExistingFile) elif self.action == 'open files': dlg.setAcceptMode(QtGui.QFileDialog.AcceptOpen) dlg.setFileMode(QtGui.QFileDialog.ExistingFiles) else: dlg.setAcceptMode(QtGui.QFileDialog.AcceptSave) dlg.setFileMode(QtGui.QFileDialog.AnyFile) return dlg ########################################################################### # Trait handlers. ########################################################################### def _wildcard_default(self): """ Return the default wildcard. """ return self.WILDCARD_ALL #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/resource_manager.py0000644000175100001440000000355011674463363022253 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui, QtSvg # Enthought library imports. from pyface.resource.api import ResourceFactory class PyfaceResourceFactory(ResourceFactory): """ The implementation of a shared resource manager. """ ########################################################################### # 'ResourceFactory' interface. ########################################################################### def image_from_file(self, filename): """ Creates an image from the data in the specified filename. """ # Although QPixmap can load SVG directly, it does not respect the # default size, so we use a QSvgRenderer to get the default size. if filename.endswith(('.svg', '.SVG')): renderer = QtSvg.QSvgRenderer(filename) pixmap = QtGui.QPixmap(renderer.defaultSize()) pixmap.fill(QtCore.Qt.transparent) painter = QtGui.QPainter(pixmap) renderer.render(painter) else: pixmap = QtGui.QPixmap(filename) return pixmap def image_from_data(self, data, filename=None): """ Creates an image from the specified data. """ image = QtGui.QPixmap() image.loadFromData(data) return image #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/split_widget.py0000644000175100001440000000614211674463363021430 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ Mix-in class for split widgets. """ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Callable, Enum, Float, HasTraits, implements # Local imports. from pyface.i_split_widget import ISplitWidget, MSplitWidget class SplitWidget(MSplitWidget, HasTraits): """ The toolkit specific implementation of a SplitWidget. See the ISPlitWidget interface for the API documentation. """ implements(ISplitWidget) #### 'ISplitWidget' interface ############################################# direction = Enum('vertical', 'vertical', 'horizontal') ratio = Float(0.5) lhs = Callable rhs = Callable ########################################################################### # Protected 'ISplitWidget' interface. ########################################################################### def _create_splitter(self, parent): """ Create the toolkit-specific control that represents the widget. """ splitter = QtGui.QSplitter(parent) # Yes, this is correct. if self.direction == 'horizontal': splitter.setOrientation(QtCore.Qt.Vertical) # Only because the wx implementation does the same. splitter.setChildrenCollapsible(False) # Left hand side/top. splitter.addWidget(self._create_lhs(splitter)) # Right hand side/bottom. splitter.addWidget(self._create_rhs(splitter)) # Set the initial splitter position. if self.direction == 'horizontal': pos = splitter.sizeHint().height() else: pos = splitter.sizeHint().width() splitter.setSizes([int(pos * self.ratio), int(pos * (1.0 - self.ratio))]) return splitter def _create_lhs(self, parent): """ Creates the left hand/top panel depending on the direction. """ if self.lhs is not None: lhs = self.lhs(parent) if not isinstance(lhs, QtGui.QWidget): lhs = lhs.control else: # Dummy implementation - override! lhs = QtGui.QWidget(parent) return lhs def _create_rhs(self, parent): """ Creates the right hand/bottom panel depending on the direction. """ if self.rhs is not None: rhs = self.rhs(parent) if not isinstance(rhs, QtGui.QWidget): rhs = rhs.control else: # Dummy implementation - override! rhs = QtGui.QWidget(parent) return rhs #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/console/0000755000175100001440000000000011674463363020017 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/console/history_console_widget.py0000644000175100001440000001402011674463363025154 0ustar ischnellusers00000000000000# System library imports from pyface.qt import QtGui # Local imports from console_widget import ConsoleWidget class HistoryConsoleWidget(ConsoleWidget): """ A ConsoleWidget that keeps a history of the commands that have been executed and provides a readline-esque interface to this history. """ #--------------------------------------------------------------------------- # 'object' interface #--------------------------------------------------------------------------- def __init__(self, *args, **kw): super(HistoryConsoleWidget, self).__init__(*args, **kw) # HistoryConsoleWidget protected variables. self._history = [] self._history_index = 0 self._history_prefix = '' #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- def execute(self, source=None, hidden=False, interactive=False): """ Reimplemented to the store history. """ if not hidden: history = self.input_buffer if source is None else source executed = super(HistoryConsoleWidget, self).execute( source, hidden, interactive) if executed and not hidden: # Save the command unless it was an empty string or was identical # to the previous command. history = history.rstrip() if history and (not self._history or self._history[-1] != history): self._history.append(history) # Move the history index to the most recent item. self._history_index = len(self._history) return executed #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- def _up_pressed(self): """ Called when the up key is pressed. Returns whether to continue processing the event. """ prompt_cursor = self._get_prompt_cursor() if self._get_cursor().blockNumber() == prompt_cursor.blockNumber(): # Set a search prefix based on the cursor position. col = self._get_input_buffer_cursor_column() input_buffer = self.input_buffer if self._history_index == len(self._history) or \ (self._history_prefix and col != len(self._history_prefix)): self._history_index = len(self._history) self._history_prefix = input_buffer[:col] # Perform the search. self.history_previous(self._history_prefix) # Go to the first line of the prompt for seemless history scrolling. # Emulate readline: keep the cursor position fixed for a prefix # search. cursor = self._get_prompt_cursor() if self._history_prefix: cursor.movePosition(QtGui.QTextCursor.Right, n=len(self._history_prefix)) else: cursor.movePosition(QtGui.QTextCursor.EndOfLine) self._set_cursor(cursor) return False return True def _down_pressed(self): """ Called when the down key is pressed. Returns whether to continue processing the event. """ end_cursor = self._get_end_cursor() if self._get_cursor().blockNumber() == end_cursor.blockNumber(): # Perform the search. self.history_next(self._history_prefix) # Emulate readline: keep the cursor position fixed for a prefix # search. (We don't need to move the cursor to the end of the buffer # in the other case because this happens automatically when the # input buffer is set.) if self._history_prefix: cursor = self._get_prompt_cursor() cursor.movePosition(QtGui.QTextCursor.Right, n=len(self._history_prefix)) self._set_cursor(cursor) return False return True #--------------------------------------------------------------------------- # 'HistoryConsoleWidget' public interface #--------------------------------------------------------------------------- def history_previous(self, prefix=''): """ If possible, set the input buffer to a previous item in the history. Parameters: ----------- prefix : str, optional If specified, search for an item with this prefix. """ index = self._history_index while index > 0: index -= 1 history = self._history[index] if history.startswith(prefix): break else: history = None if history is not None: self._history_index = index self.input_buffer = history def history_next(self, prefix=''): """ Set the input buffer to a subsequent item in the history, or to the original search prefix if there is no such item. Parameters: ----------- prefix : str, optional If specified, search for an item with this prefix. """ while self._history_index < len(self._history) - 1: self._history_index += 1 history = self._history[self._history_index] if history.startswith(prefix): break else: self._history_index = len(self._history) history = prefix self.input_buffer = history #--------------------------------------------------------------------------- # 'HistoryConsoleWidget' protected interface #--------------------------------------------------------------------------- def _set_history(self, history): """ Replace the current history with a sequence of history items. """ self._history = list(history) self._history_index = len(self._history) pyface-4.1.0/pyface/ui/qt4/console/api.py0000644000175100001440000000034311674463363021142 0ustar ischnellusers00000000000000from bracket_matcher import BracketMatcher from call_tip_widget import CallTipWidget from completion_lexer import CompletionLexer from console_widget import ConsoleWidget from history_console_widget import HistoryConsoleWidget pyface-4.1.0/pyface/ui/qt4/console/completion_lexer.py0000644000175100001440000000471411674463363023747 0ustar ischnellusers00000000000000# System library imports from pygments.token import Token, is_token_subtype class CompletionLexer(object): """ Uses Pygments and some auxillary information to lex code snippets for symbol contexts. """ # Maps Lexer names to a list of possible name separators separator_map = { 'C' : [ '.', '->' ], 'C++' : [ '.', '->', '::' ], 'Python' : [ '.' ] } def __init__(self, lexer): """ Create a CompletionLexer using the specified Pygments lexer. """ self.lexer = lexer def get_context(self, string): """ Assuming the cursor is at the end of the specified string, get the context (a list of names) for the symbol at cursor position. """ context = [] reversed_tokens = list(self._lexer.get_tokens(string)) reversed_tokens.reverse() # Pygments often tacks on a newline when none is specified in the input. # Remove this newline. if reversed_tokens and reversed_tokens[0][1].endswith('\n') and \ not string.endswith('\n'): reversed_tokens.pop(0) current_op = '' for token, text in reversed_tokens: if is_token_subtype(token, Token.Name): # Handle a trailing separator, e.g 'foo.bar.' if current_op in self._name_separators: if not context: context.insert(0, '') # Handle non-separator operators and punction. elif current_op: break context.insert(0, text) current_op = '' # Pygments doesn't understand that, e.g., '->' is a single operator # in C++. This is why we have to build up an operator from # potentially several tokens. elif token is Token.Operator or token is Token.Punctuation: current_op = text + current_op # Break on anything that is not a Operator, Punctuation, or Name. else: break return context def get_lexer(self, lexer): return self._lexer def set_lexer(self, lexer, name_separators=None): self._lexer = lexer if name_separators is None: self._name_separators = self.separator_map.get(lexer.name, ['.']) else: self._name_separators = list(name_separators) lexer = property(get_lexer, set_lexer) pyface-4.1.0/pyface/ui/qt4/console/bracket_matcher.py0000644000175100001440000000723411674463363023515 0ustar ischnellusers00000000000000""" Provides bracket matching for Q[Plain]TextEdit widgets. """ # System library imports from pyface.qt import QtCore, QtGui class BracketMatcher(QtCore.QObject): """ Matches square brackets, braces, and parentheses based on cursor position. """ # Protected class variables. _opening_map = { '(':')', '{':'}', '[':']' } _closing_map = { ')':'(', '}':'{', ']':'[' } #-------------------------------------------------------------------------- # 'QObject' interface #-------------------------------------------------------------------------- def __init__(self, text_edit): """ Create a call tip manager that is attached to the specified Qt text edit widget. """ assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) super(BracketMatcher, self).__init__() # The format to apply to matching brackets. self.format = QtGui.QTextCharFormat() self.format.setBackground(QtGui.QColor('silver')) self._text_edit = text_edit text_edit.cursorPositionChanged.connect(self._cursor_position_changed) #-------------------------------------------------------------------------- # Protected interface #-------------------------------------------------------------------------- def _find_match(self, position): """ Given a valid position in the text document, try to find the position of the matching bracket. Returns -1 if unsuccessful. """ # Decide what character to search for and what direction to search in. document = self._text_edit.document() start_char = document.characterAt(position) search_char = self._opening_map.get(start_char) if search_char: increment = 1 else: search_char = self._closing_map.get(start_char) if search_char: increment = -1 else: return -1 # Search for the character. char = start_char depth = 0 while position >= 0 and position < document.characterCount(): if char == start_char: depth += 1 elif char == search_char: depth -= 1 if depth == 0: break position += increment char = document.characterAt(position) else: position = -1 return position def _selection_for_character(self, position): """ Convenience method for selecting a character. """ selection = QtGui.QTextEdit.ExtraSelection() cursor = self._text_edit.textCursor() cursor.setPosition(position) cursor.movePosition(QtGui.QTextCursor.NextCharacter, QtGui.QTextCursor.KeepAnchor) selection.cursor = cursor selection.format = self.format return selection #------ Signal handlers ---------------------------------------------------- def _cursor_position_changed(self): """ Updates the document formatting based on the new cursor position. """ # Clear out the old formatting. self._text_edit.setExtraSelections([]) # Attempt to match a bracket for the new cursor position. cursor = self._text_edit.textCursor() if not cursor.hasSelection(): position = cursor.position() - 1 match_position = self._find_match(position) if match_position != -1: extra_selections = [ self._selection_for_character(pos) for pos in (position, match_position) ] self._text_edit.setExtraSelections(extra_selections) pyface-4.1.0/pyface/ui/qt4/console/__init__.py0000644000175100001440000000000011674463363022116 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/console/console_widget.py0000644000175100001440000022200611674463363023400 0ustar ischnellusers00000000000000""" An abstract base class for console-type widgets. """ # FIXME: This file and the others in this directory have been ripped, more or # less intact, out of IPython. At some point we should figure out a more # maintainable solution. #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Standard library imports import os from os.path import commonprefix import re import sys from textwrap import dedent from unicodedata import category # System library imports from pyface.qt import QtCore, QtGui #----------------------------------------------------------------------------- # Functions #----------------------------------------------------------------------------- def is_letter_or_number(char): """ Returns whether the specified unicode character is a letter or a number. """ cat = category(char) return cat.startswith('L') or cat.startswith('N') #----------------------------------------------------------------------------- # Classes #----------------------------------------------------------------------------- class ConsoleWidget(QtGui.QWidget): """ An abstract base class for console-type widgets. This class has functionality for: * Maintaining a prompt and editing region * Providing the traditional Unix-style console keyboard shortcuts * Performing tab completion * Paging text ConsoleWidget also provides a number of utility methods that will be convenient to implementors of a console-style widget. """ #------ Configuration ------------------------------------------------------ # The maximum number of lines of text before truncation. Specifying a # non-positive number disables text truncation (not recommended). buffer_size = 500 # The type of underlying text widget to use. Valid values are 'plain', which # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit. # NOTE: this value can only be specified during initialization. kind = 'plain' # The type of paging to use. Valid values are: # 'inside' : The widget pages like a traditional terminal. # 'hsplit' : When paging is requested, the widget is split # horizontally. The top pane contains the console, and the # bottom pane contains the paged text. # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used. # 'custom' : No action is taken by the widget beyond emitting a # 'custom_page_requested(str)' signal. # 'none' : The text is written directly to the console. # NOTE: this value can only be specified during initialization. paging = 'inside' # Whether to override ShortcutEvents for the keybindings defined by this # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take # priority (when it has focus) over, e.g., window-level menu shortcuts. override_shortcuts = False #------ Signals ------------------------------------------------------------ # Signals that indicate ConsoleWidget state. copy_available = QtCore.Signal(bool) redo_available = QtCore.Signal(bool) undo_available = QtCore.Signal(bool) # Signal emitted when paging is needed and the paging style has been # specified as 'custom'. custom_page_requested = QtCore.Signal(object) # Signal emitted when the font is changed. font_changed = QtCore.Signal(QtGui.QFont) #------ Protected class variables ------------------------------------------ # When the control key is down, these keys are mapped. _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left, QtCore.Qt.Key_F : QtCore.Qt.Key_Right, QtCore.Qt.Key_A : QtCore.Qt.Key_Home, QtCore.Qt.Key_P : QtCore.Qt.Key_Up, QtCore.Qt.Key_N : QtCore.Qt.Key_Down, QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, } if not sys.platform == 'darwin': # On OS X, Ctrl-E already does the right thing, whereas End moves the # cursor to the bottom of the buffer. _ctrl_down_remap[QtCore.Qt.Key_E] = QtCore.Qt.Key_End # The shortcuts defined by this widget. We need to keep track of these to # support 'override_shortcuts' above. _shortcuts = set(_ctrl_down_remap.keys() + [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O, QtCore.Qt.Key_V ]) #--------------------------------------------------------------------------- # 'QObject' interface #--------------------------------------------------------------------------- def __init__(self, parent=None): """ Create a ConsoleWidget. Parameters: ----------- parent : QWidget, optional [default None] The parent for this widget. """ super(ConsoleWidget, self).__init__(parent) # Create the layout and underlying text widget. layout = QtGui.QStackedLayout(self) layout.setContentsMargins(0, 0, 0, 0) self._control = self._create_control() self._page_control = None self._splitter = None if self.paging in ('hsplit', 'vsplit'): self._splitter = QtGui.QSplitter() if self.paging == 'hsplit': self._splitter.setOrientation(QtCore.Qt.Horizontal) else: self._splitter.setOrientation(QtCore.Qt.Vertical) self._splitter.addWidget(self._control) layout.addWidget(self._splitter) else: layout.addWidget(self._control) # Create the paging widget, if necessary. if self.paging in ('inside', 'hsplit', 'vsplit'): self._page_control = self._create_page_control() if self._splitter: self._page_control.hide() self._splitter.addWidget(self._page_control) else: layout.addWidget(self._page_control) # Initialize protected variables. Some variables contain useful state # information for subclasses; they should be considered read-only. self._continuation_prompt = '> ' self._continuation_prompt_html = None self._executing = False self._filter_drag = False self._filter_resize = False self._prompt = '' self._prompt_html = None self._prompt_pos = 0 self._prompt_sep = '' self._reading = False self._reading_callback = None self._tab_width = 8 self._text_completing_pos = 0 self._filename = 'python.html' self._png_mode=None # Set a monospaced font. self.reset_font() # Configure actions. action = QtGui.QAction('Print', None) action.setEnabled(True) printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print) if printkey.matches("Ctrl+P") and sys.platform != 'darwin': # Only override the default if there is a collision. # Qt ctrl = cmd on OSX, so that the match gets a false positive. printkey = "Ctrl+Shift+P" action.setShortcut(printkey) action.triggered.connect(self.print_) self.addAction(action) self._print_action = action action = QtGui.QAction('Save as HTML/XML', None) action.setEnabled(self.can_export()) action.setShortcut(QtGui.QKeySequence.Save) action.triggered.connect(self.export) self.addAction(action) self._export_action = action action = QtGui.QAction('Select All', None) action.setEnabled(True) action.setShortcut(QtGui.QKeySequence.SelectAll) action.triggered.connect(self.select_all) self.addAction(action) self._select_all_action = action def eventFilter(self, obj, event): """ Reimplemented to ensure a console-like behavior in the underlying text widgets. """ etype = event.type() if etype == QtCore.QEvent.KeyPress: # Re-map keys for all filtered widgets. key = event.key() if self._control_key_down(event.modifiers()) and \ key in self._ctrl_down_remap: new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, self._ctrl_down_remap[key], QtCore.Qt.NoModifier) QtGui.QApplication.sendEvent(obj, new_event) return True elif obj == self._control: return self._event_filter_console_keypress(event) elif obj == self._page_control: return self._event_filter_page_keypress(event) # Make middle-click paste safe. elif etype == QtCore.QEvent.MouseButtonRelease and \ event.button() == QtCore.Qt.MidButton and \ obj == self._control.viewport(): cursor = self._control.cursorForPosition(event.pos()) self._control.setTextCursor(cursor) self.paste(QtGui.QClipboard.Selection) return True # Manually adjust the scrollbars *after* a resize event is dispatched. elif etype == QtCore.QEvent.Resize and not self._filter_resize: self._filter_resize = True QtGui.QApplication.sendEvent(obj, event) self._adjust_scrollbars() self._filter_resize = False return True # Override shortcuts for all filtered widgets. elif etype == QtCore.QEvent.ShortcutOverride and \ self.override_shortcuts and \ self._control_key_down(event.modifiers()) and \ event.key() in self._shortcuts: event.accept() # Ensure that drags are safe. The problem is that the drag starting # logic, which determines whether the drag is a Copy or Move, is locked # down in QTextControl. If the widget is editable, which it must be if # we're not executing, the drag will be a Move. The following hack # prevents QTextControl from deleting the text by clearing the selection # when a drag leave event originating from this widget is dispatched. # The fact that we have to clear the user's selection is unfortunate, # but the alternative--trying to prevent Qt from using its hardwired # drag logic and writing our own--is worse. elif etype == QtCore.QEvent.DragEnter and \ obj == self._control.viewport() and \ event.source() == self._control.viewport(): self._filter_drag = True elif etype == QtCore.QEvent.DragLeave and \ obj == self._control.viewport() and \ self._filter_drag: cursor = self._control.textCursor() cursor.clearSelection() self._control.setTextCursor(cursor) self._filter_drag = False # Ensure that drops are safe. elif etype == QtCore.QEvent.Drop and obj == self._control.viewport(): cursor = self._control.cursorForPosition(event.pos()) if self._in_buffer(cursor.position()): text = event.mimeData().text() self._insert_plain_text_into_buffer(cursor, text) # Qt is expecting to get something here--drag and drop occurs in its # own event loop. Send a DragLeave event to end it. QtGui.QApplication.sendEvent(obj, QtGui.QDragLeaveEvent()) return True return super(ConsoleWidget, self).eventFilter(obj, event) #--------------------------------------------------------------------------- # 'QWidget' interface #--------------------------------------------------------------------------- def sizeHint(self): """ Reimplemented to suggest a size that is 80 characters wide and 25 lines high. """ font_metrics = QtGui.QFontMetrics(self.font) margin = (self._control.frameWidth() + self._control.document().documentMargin()) * 2 style = self.style() splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth) # Note 1: Despite my best efforts to take the various margins into # account, the width is still coming out a bit too small, so we include # a fudge factor of one character here. # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due # to a Qt bug on certain Mac OS systems where it returns 0. width = font_metrics.width(' ') * 81 + margin width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent) if self.paging == 'hsplit': width = width * 2 + splitwidth height = font_metrics.height() * 25 + margin if self.paging == 'vsplit': height = height * 2 + splitwidth return QtCore.QSize(width, height) #--------------------------------------------------------------------------- # 'ConsoleWidget' public interface #--------------------------------------------------------------------------- def can_copy(self): """ Returns whether text can be copied to the clipboard. """ return self._control.textCursor().hasSelection() def can_cut(self): """ Returns whether text can be cut to the clipboard. """ cursor = self._control.textCursor() return (cursor.hasSelection() and self._in_buffer(cursor.anchor()) and self._in_buffer(cursor.position())) def can_paste(self): """ Returns whether text can be pasted from the clipboard. """ if self._control.textInteractionFlags() & QtCore.Qt.TextEditable: return bool(QtGui.QApplication.clipboard().text()) return False def can_export(self): """Returns whether we can export. Currently only rich widgets can export html. """ return self.kind == "rich" def clear(self, keep_input=True): """ Clear the console. Parameters: ----------- keep_input : bool, optional (default True) If set, restores the old input buffer if a new prompt is written. """ if self._executing: self._control.clear() else: if keep_input: input_buffer = self.input_buffer self._control.clear() self._show_prompt() if keep_input: self.input_buffer = input_buffer def copy(self): """ Copy the currently selected text to the clipboard. """ self._control.copy() def cut(self): """ Copy the currently selected text to the clipboard and delete it if it's inside the input buffer. """ self.copy() if self.can_cut(): self._control.textCursor().removeSelectedText() def execute(self, source=None, hidden=False, interactive=False): """ Executes source or the input buffer, possibly prompting for more input. Parameters: ----------- source : str, optional The source to execute. If not specified, the input buffer will be used. If specified and 'hidden' is False, the input buffer will be replaced with the source before execution. hidden : bool, optional (default False) If set, no output will be shown and the prompt will not be modified. In other words, it will be completely invisible to the user that an execution has occurred. interactive : bool, optional (default False) Whether the console is to treat the source as having been manually entered by the user. The effect of this parameter depends on the subclass implementation. Raises: ------- RuntimeError If incomplete input is given and 'hidden' is True. In this case, it is not possible to prompt for more input. Returns: -------- A boolean indicating whether the source was executed. """ # WARNING: The order in which things happen here is very particular, in # large part because our syntax highlighting is fragile. If you change # something, test carefully! # Decide what to execute. if source is None: source = self.input_buffer if not hidden: # A newline is appended later, but it should be considered part # of the input buffer. source += '\n' elif not hidden: self.input_buffer = source # Execute the source or show a continuation prompt if it is incomplete. complete = self._is_complete(source, interactive) if hidden: if complete: self._execute(source, hidden) else: error = 'Incomplete noninteractive input: "%s"' raise RuntimeError(error % source) else: if complete: self._append_plain_text('\n') self._executing_input_buffer = self.input_buffer self._executing = True self._prompt_finished() # The maximum block count is only in effect during execution. # This ensures that _prompt_pos does not become invalid due to # text truncation. self._control.document().setMaximumBlockCount(self.buffer_size) # Setting a positive maximum block count will automatically # disable the undo/redo history, but just to be safe: self._control.setUndoRedoEnabled(False) # Perform actual execution. self._execute(source, hidden) else: # Do this inside an edit block so continuation prompts are # removed seamlessly via undo/redo. cursor = self._get_end_cursor() cursor.beginEditBlock() cursor.insertText('\n') self._insert_continuation_prompt(cursor) cursor.endEditBlock() # Do not do this inside the edit block. It works as expected # when using a QPlainTextEdit control, but does not have an # effect when using a QTextEdit. I believe this is a Qt bug. self._control.moveCursor(QtGui.QTextCursor.End) return complete def _get_input_buffer(self): """ The text that the user has entered entered at the current prompt. """ # If we're executing, the input buffer may not even exist anymore due to # the limit imposed by 'buffer_size'. Therefore, we store it. if self._executing: return self._executing_input_buffer cursor = self._get_end_cursor() cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor) input_buffer = cursor.selection().toPlainText() # Strip out continuation prompts. return input_buffer.replace('\n' + self._continuation_prompt, '\n') def _set_input_buffer(self, string): """ Replaces the text in the input buffer with 'string'. """ # For now, it is an error to modify the input buffer during execution. if self._executing: raise RuntimeError("Cannot change input buffer during execution.") # Remove old text. cursor = self._get_end_cursor() cursor.beginEditBlock() cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() # Insert new text with continuation prompts. self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string) cursor.endEditBlock() self._control.moveCursor(QtGui.QTextCursor.End) input_buffer = property(_get_input_buffer, _set_input_buffer) def _get_font(self): """ The base font being used by the ConsoleWidget. """ return self._control.document().defaultFont() def _set_font(self, font): """ Sets the base font for the ConsoleWidget to the specified QFont. """ font_metrics = QtGui.QFontMetrics(font) self._control.setTabStopWidth(self.tab_width * font_metrics.width(' ')) self._control.document().setDefaultFont(font) if self._page_control: self._page_control.document().setDefaultFont(font) self.font_changed.emit(font) font = property(_get_font, _set_font) def paste(self, mode=QtGui.QClipboard.Clipboard): """ Paste the contents of the clipboard into the input region. Parameters: ----------- mode : QClipboard::Mode, optional [default QClipboard::Clipboard] Controls which part of the system clipboard is used. This can be used to access the selection clipboard in X11 and the Find buffer in Mac OS. By default, the regular clipboard is used. """ if self._control.textInteractionFlags() & QtCore.Qt.TextEditable: # Make sure the paste is safe. self._keep_cursor_in_buffer() cursor = self._control.textCursor() # Remove any trailing newline, which confuses the GUI and forces the # user to backspace. text = QtGui.QApplication.clipboard().text(mode).rstrip() self._insert_plain_text_into_buffer(cursor, dedent(text)) def print_(self, printer = None): """ Print the contents of the ConsoleWidget to the specified QPrinter. """ if (not printer): printer = QtGui.QPrinter() if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted): return self._control.print_(printer) def export(self, parent = None): """Export HTML/XML in various modes from one Dialog.""" parent = parent or None # sometimes parent is False dialog = QtGui.QFileDialog(parent, 'Save Console as...') dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) filters = [ 'HTML with PNG figures (*.html *.htm)', 'XHTML with inline SVG figures (*.xhtml *.xml)' ] dialog.setNameFilters(filters) if self._filename: dialog.selectFile(self._filename) root,ext = os.path.splitext(self._filename) if ext.lower() in ('.xml', '.xhtml'): dialog.selectNameFilter(filters[-1]) if dialog.exec_(): filename = str(dialog.selectedFiles()[0]) self._filename = filename choice = str(dialog.selectedNameFilter()) if choice.startswith('XHTML'): exporter = self.export_xhtml else: exporter = self.export_html try: return exporter(filename) except Exception, e: title = self.window().windowTitle() msg = "Error while saving to: %s\n"%filename+str(e) reply = QtGui.QMessageBox.warning(self, title, msg, QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok) return None def export_html(self, filename): """ Export the contents of the ConsoleWidget as HTML. Parameters: ----------- filename : str The file to be saved. inline : bool, optional [default True] If True, include images as inline PNGs. Otherwise, include them as links to external PNG files, mimicking web browsers' "Web Page, Complete" behavior. """ # N.B. this is overly restrictive, but Qt's output is # predictable... img_re = re.compile(r'') html = self.fix_html_encoding( str(self._control.toHtml().toUtf8())) if self._png_mode: # preference saved, don't ask again if img_re.search(html): inline = (self._png_mode == 'inline') else: inline = True elif img_re.search(html): # there are images widget = QtGui.QWidget() layout = QtGui.QVBoxLayout(widget) title = self.window().windowTitle() msg = "Exporting HTML with PNGs" info = "Would you like inline PNGs (single large html file) or "+\ "external image files?" checkbox = QtGui.QCheckBox("&Don't ask again") checkbox.setShortcut('D') ib = QtGui.QPushButton("&Inline", self) ib.setShortcut('I') eb = QtGui.QPushButton("&External", self) eb.setShortcut('E') box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg) box.setInformativeText(info) box.addButton(ib,QtGui.QMessageBox.NoRole) box.addButton(eb,QtGui.QMessageBox.YesRole) box.setDefaultButton(ib) layout.setSpacing(0) layout.addWidget(box) layout.addWidget(checkbox) widget.setLayout(layout) widget.show() reply = box.exec_() inline = (reply == 0) if checkbox.checkState(): # don't ask anymore, always use this choice if inline: self._png_mode='inline' else: self._png_mode='external' else: # no images inline = True if inline: path = None else: root,ext = os.path.splitext(filename) path = root+"_files" if os.path.isfile(path): raise OSError("%s exists, but is not a directory."%path) f = open(filename, 'w') try: f.write(img_re.sub( lambda x: self.image_tag(x, path = path, format = "png"), html)) except Exception, e: f.close() raise e else: f.close() return filename def export_xhtml(self, filename): """ Export the contents of the ConsoleWidget as XHTML with inline SVGs. """ f = open(filename, 'w') try: # N.B. this is overly restrictive, but Qt's output is # predictable... img_re = re.compile(r'') html = str(self._control.toHtml().toUtf8()) # Hack to make xhtml header -- note that we are not doing # any check for valid xml offset = html.find("") assert(offset > -1) html = ('\n'+ html[offset+6:]) # And now declare UTF-8 encoding html = self.fix_html_encoding(html) f.write(img_re.sub( lambda x: self.image_tag(x, path = None, format = "svg"), html)) except Exception, e: f.close() raise e else: f.close() return filename def fix_html_encoding(self, html): """ Return html string, with a UTF-8 declaration added to . Assumes that html is Qt generated and has already been UTF-8 encoded and coerced to a python string. If the expected head element is not found, the given object is returned unmodified. This patching is needed for proper rendering of some characters (e.g., indented commands) when viewing exported HTML on a local system (i.e., without seeing an encoding declaration in an HTTP header). C.f. http://www.w3.org/International/O-charset for details. """ offset = html.find("") if(offset > -1): html = (html[:offset+6]+ '\n\n'+ html[offset+6:]) return html def image_tag(self, match, path = None, format = "png"): """ Return (X)HTML mark-up for the image-tag given by match. Parameters ---------- match : re.SRE_Match A match to an HTML image tag as exported by Qt, with match.group("Name") containing the matched image ID. path : string|None, optional [default None] If not None, specifies a path to which supporting files may be written (e.g., for linked images). If None, all images are to be included inline. format : "png"|"svg", optional [default "png"] Format for returned or referenced images. Subclasses supporting image display should override this method. """ # Default case -- not enough information to generate tag return "" def prompt_to_top(self): """ Moves the prompt to the top of the viewport. """ if not self._executing: prompt_cursor = self._get_prompt_cursor() if self._get_cursor().blockNumber() < prompt_cursor.blockNumber(): self._set_cursor(prompt_cursor) self._set_top_cursor(prompt_cursor) def redo(self): """ Redo the last operation. If there is no operation to redo, nothing happens. """ self._control.redo() def reset_font(self): """ Sets the font to the default fixed-width font for this platform. """ if sys.platform == 'win32': # Consolas ships with Vista/Win7, fallback to Courier if needed family, fallback = 'Consolas', 'Courier' elif sys.platform == 'darwin': # OSX always has Monaco, no need for a fallback family, fallback = 'Monaco', None else: # FIXME: remove Consolas as a default on Linux once our font # selections are configurable by the user. family, fallback = 'Consolas', 'Monospace' # Check whether we got what we wanted using QFontInfo, since # exactMatch() is overly strict and returns false in too many cases. font = QtGui.QFont(family) font_info = QtGui.QFontInfo(font) if fallback is not None and font_info.family() != family: font = QtGui.QFont(fallback) font.setPointSize(QtGui.QApplication.font().pointSize()) font.setStyleHint(QtGui.QFont.TypeWriter) self._set_font(font) def change_font_size(self, delta): """Change the font size by the specified amount (in points). """ font = self.font font.setPointSize(font.pointSize() + delta) self._set_font(font) def select_all(self): """ Selects all the text in the buffer. """ self._control.selectAll() def _get_tab_width(self): """ The width (in terms of space characters) for tab characters. """ return self._tab_width def _set_tab_width(self, tab_width): """ Sets the width (in terms of space characters) for tab characters. """ font_metrics = QtGui.QFontMetrics(self.font) self._control.setTabStopWidth(tab_width * font_metrics.width(' ')) self._tab_width = tab_width tab_width = property(_get_tab_width, _set_tab_width) def undo(self): """ Undo the last operation. If there is no operation to undo, nothing happens. """ self._control.undo() #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- def _is_complete(self, source, interactive): """ Returns whether 'source' can be executed. When triggered by an Enter/Return key press, 'interactive' is True; otherwise, it is False. """ raise NotImplementedError def _execute(self, source, hidden): """ Execute 'source'. If 'hidden', do not show any output. """ raise NotImplementedError def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ pass def _prompt_finished_hook(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ pass def _up_pressed(self): """ Called when the up key is pressed. Returns whether to continue processing the event. """ return True def _down_pressed(self): """ Called when the down key is pressed. Returns whether to continue processing the event. """ return True def _tab_pressed(self): """ Called when the tab key is pressed. Returns whether to continue processing the event. """ return False #-------------------------------------------------------------------------- # 'ConsoleWidget' protected interface #-------------------------------------------------------------------------- def _append_html(self, html): """ Appends html at the end of the console buffer. """ cursor = self._get_end_cursor() self._insert_html(cursor, html) def _append_html_fetching_plain_text(self, html): """ Appends 'html', then returns the plain text version of it. """ cursor = self._get_end_cursor() return self._insert_html_fetching_plain_text(cursor, html) def _append_plain_text(self, text): """ Appends plain text at the end of the console buffer, processing ANSI codes if enabled. """ cursor = self._get_end_cursor() self._insert_plain_text(cursor, text) def _append_plain_text_keeping_prompt(self, text): """ Writes 'text' after the current prompt, then restores the old prompt with its old input buffer. """ input_buffer = self.input_buffer self._append_plain_text('\n') self._prompt_finished() self._append_plain_text(text) self._show_prompt() self.input_buffer = input_buffer def _cancel_text_completion(self): """ If text completion is progress, cancel it. """ if self._text_completing_pos: self._clear_temporary_buffer() self._text_completing_pos = 0 def _clear_temporary_buffer(self): """ Clears the "temporary text" buffer, i.e. all the text following the prompt region. """ # Select and remove all text below the input buffer. cursor = self._get_prompt_cursor() prompt = self._continuation_prompt.lstrip() while cursor.movePosition(QtGui.QTextCursor.NextBlock): temp_cursor = QtGui.QTextCursor(cursor) temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor) text = temp_cursor.selection().toPlainText().lstrip() if not text.startswith(prompt): break else: # We've reached the end of the input buffer and no text follows. return cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline. cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() # After doing this, we have no choice but to clear the undo/redo # history. Otherwise, the text is not "temporary" at all, because it # can be recalled with undo/redo. Unfortunately, Qt does not expose # fine-grained control to the undo/redo system. if self._control.isUndoRedoEnabled(): self._control.setUndoRedoEnabled(False) self._control.setUndoRedoEnabled(True) def _complete_with_items(self, cursor, items): """ Performs completion with 'items' at the specified cursor location. """ self._cancel_text_completion() if len(items) == 1: cursor.setPosition(self._control.textCursor().position(), QtGui.QTextCursor.KeepAnchor) cursor.insertText(items[0]) elif len(items) > 1: current_pos = self._control.textCursor().position() prefix = commonprefix(items) if prefix: cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor) cursor.insertText(prefix) current_pos = cursor.position() cursor.beginEditBlock() self._append_plain_text('\n') self._page(self._format_as_columns(items)) cursor.endEditBlock() cursor.setPosition(current_pos) self._control.moveCursor(QtGui.QTextCursor.End) self._control.setTextCursor(cursor) self._text_completing_pos = current_pos def _context_menu_make(self, pos): """ Creates a context menu for the given QPoint (in widget coordinates). """ menu = QtGui.QMenu(self) cut_action = menu.addAction('Cut', self.cut) cut_action.setEnabled(self.can_cut()) cut_action.setShortcut(QtGui.QKeySequence.Cut) copy_action = menu.addAction('Copy', self.copy) copy_action.setEnabled(self.can_copy()) copy_action.setShortcut(QtGui.QKeySequence.Copy) paste_action = menu.addAction('Paste', self.paste) paste_action.setEnabled(self.can_paste()) paste_action.setShortcut(QtGui.QKeySequence.Paste) menu.addSeparator() menu.addAction(self._select_all_action) menu.addSeparator() menu.addAction(self._export_action) menu.addAction(self._print_action) return menu def _control_key_down(self, modifiers, include_command=False): """ Given a KeyboardModifiers flags object, return whether the Control key is down. Parameters: ----------- include_command : bool, optional (default True) Whether to treat the Command key as a (mutually exclusive) synonym for Control when in Mac OS. """ # Note that on Mac OS, ControlModifier corresponds to the Command key # while MetaModifier corresponds to the Control key. if sys.platform == 'darwin': down = include_command and (modifiers & QtCore.Qt.ControlModifier) return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier) else: return bool(modifiers & QtCore.Qt.ControlModifier) def _create_control(self): """ Creates and connects the underlying text widget. """ # Create the underlying control. if self.kind == 'plain': control = QtGui.QPlainTextEdit() elif self.kind == 'rich': control = QtGui.QTextEdit() control.setAcceptRichText(False) # Install event filters. The filter on the viewport is needed for # mouse events and drag events. control.installEventFilter(self) control.viewport().installEventFilter(self) # Connect signals. control.cursorPositionChanged.connect(self._cursor_position_changed) control.customContextMenuRequested.connect( self._custom_context_menu_requested) control.copyAvailable.connect(self.copy_available) control.redoAvailable.connect(self.redo_available) control.undoAvailable.connect(self.undo_available) # Hijack the document size change signal to prevent Qt from adjusting # the viewport's scrollbar. We are relying on an implementation detail # of Q(Plain)TextEdit here, which is potentially dangerous, but without # this functionality we cannot create a nice terminal interface. layout = control.document().documentLayout() layout.documentSizeChanged.disconnect() layout.documentSizeChanged.connect(self._adjust_scrollbars) # Configure the control. control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) control.setReadOnly(True) control.setUndoRedoEnabled(False) control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) return control def _create_page_control(self): """ Creates and connects the underlying paging widget. """ if self.kind == 'plain': control = QtGui.QPlainTextEdit() elif self.kind == 'rich': control = QtGui.QTextEdit() control.installEventFilter(self) control.setReadOnly(True) control.setUndoRedoEnabled(False) control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) return control def _event_filter_console_keypress(self, event): """ Filter key events for the underlying text widget to create a console-like interface. """ intercepted = False cursor = self._control.textCursor() position = cursor.position() key = event.key() ctrl_down = self._control_key_down(event.modifiers()) alt_down = event.modifiers() & QtCore.Qt.AltModifier shift_down = event.modifiers() & QtCore.Qt.ShiftModifier #------ Special sequences ---------------------------------------------- if event.matches(QtGui.QKeySequence.Copy): self.copy() intercepted = True elif event.matches(QtGui.QKeySequence.Cut): self.cut() intercepted = True elif event.matches(QtGui.QKeySequence.Paste): self.paste() intercepted = True #------ Special modifier logic ----------------------------------------- elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): intercepted = True # Special handling when tab completing in text mode. self._cancel_text_completion() if self._in_buffer(position): if self._reading: self._append_plain_text('\n') self._reading = False if self._reading_callback: self._reading_callback() # If the input buffer is a single line or there is only # whitespace after the cursor, execute. Otherwise, split the # line with a continuation prompt. elif not self._executing: cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.KeepAnchor) at_end = len(cursor.selectedText().strip()) == 0 single_line = (self._get_end_cursor().blockNumber() == self._get_prompt_cursor().blockNumber()) if (at_end or shift_down or single_line) and not ctrl_down: self.execute(interactive = not shift_down) else: # Do this inside an edit block for clean undo/redo. cursor.beginEditBlock() cursor.setPosition(position) cursor.insertText('\n') self._insert_continuation_prompt(cursor) cursor.endEditBlock() # Ensure that the whole input buffer is visible. # FIXME: This will not be usable if the input buffer is # taller than the console widget. self._control.moveCursor(QtGui.QTextCursor.End) self._control.setTextCursor(cursor) #------ Control/Cmd modifier ------------------------------------------- elif ctrl_down: if key == QtCore.Qt.Key_G: self._keyboard_quit() intercepted = True elif key == QtCore.Qt.Key_K: if self._in_buffer(position): cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) if not cursor.hasSelection(): # Line deletion (remove continuation prompt) cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor, len(self._continuation_prompt)) cursor.removeSelectedText() intercepted = True elif key == QtCore.Qt.Key_L: self.prompt_to_top() intercepted = True elif key == QtCore.Qt.Key_O: if self._page_control and self._page_control.isVisible(): self._page_control.setFocus() intercepted = True elif key == QtCore.Qt.Key_Y: self.paste() intercepted = True elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete): intercepted = True elif key == QtCore.Qt.Key_Plus: self.change_font_size(1) intercepted = True elif key == QtCore.Qt.Key_Minus: self.change_font_size(-1) intercepted = True #------ Alt modifier --------------------------------------------------- elif alt_down: if key == QtCore.Qt.Key_B: self._set_cursor(self._get_word_start_cursor(position)) intercepted = True elif key == QtCore.Qt.Key_F: self._set_cursor(self._get_word_end_cursor(position)) intercepted = True elif key == QtCore.Qt.Key_Backspace: cursor = self._get_word_start_cursor(position) cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() intercepted = True elif key == QtCore.Qt.Key_D: cursor = self._get_word_end_cursor(position) cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() intercepted = True elif key == QtCore.Qt.Key_Delete: intercepted = True elif key == QtCore.Qt.Key_Greater: self._control.moveCursor(QtGui.QTextCursor.End) intercepted = True elif key == QtCore.Qt.Key_Less: self._control.setTextCursor(self._get_prompt_cursor()) intercepted = True #------ No modifiers --------------------------------------------------- else: if shift_down: anchormode=QtGui.QTextCursor.KeepAnchor else: anchormode=QtGui.QTextCursor.MoveAnchor if key == QtCore.Qt.Key_Escape: self._keyboard_quit() intercepted = True elif key == QtCore.Qt.Key_Up: if self._reading or not self._up_pressed(): intercepted = True else: prompt_line = self._get_prompt_cursor().blockNumber() intercepted = cursor.blockNumber() <= prompt_line elif key == QtCore.Qt.Key_Down: if self._reading or not self._down_pressed(): intercepted = True else: end_line = self._get_end_cursor().blockNumber() intercepted = cursor.blockNumber() == end_line elif key == QtCore.Qt.Key_Tab: if not self._reading: intercepted = not self._tab_pressed() elif key == QtCore.Qt.Key_Left: # Move to the previous line line, col = cursor.blockNumber(), cursor.columnNumber() if line > self._get_prompt_cursor().blockNumber() and \ col == len(self._continuation_prompt): self._control.moveCursor(QtGui.QTextCursor.PreviousBlock, mode=anchormode) self._control.moveCursor(QtGui.QTextCursor.EndOfBlock, mode=anchormode) intercepted = True # Regular left movement else: intercepted = not self._in_buffer(position - 1) elif key == QtCore.Qt.Key_Right: original_block_number = cursor.blockNumber() cursor.movePosition(QtGui.QTextCursor.Right, mode=anchormode) if cursor.blockNumber() != original_block_number: cursor.movePosition(QtGui.QTextCursor.Right, n=len(self._continuation_prompt), mode=anchormode) self._set_cursor(cursor) intercepted = True elif key == QtCore.Qt.Key_Home: start_line = cursor.blockNumber() if start_line == self._get_prompt_cursor().blockNumber(): start_pos = self._prompt_pos else: cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.KeepAnchor) start_pos = cursor.position() start_pos += len(self._continuation_prompt) cursor.setPosition(position) if shift_down and self._in_buffer(position): cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor) else: cursor.setPosition(start_pos) self._set_cursor(cursor) intercepted = True elif key == QtCore.Qt.Key_Backspace: # Line deletion (remove continuation prompt) line, col = cursor.blockNumber(), cursor.columnNumber() if not self._reading and \ col == len(self._continuation_prompt) and \ line > self._get_prompt_cursor().blockNumber(): cursor.beginEditBlock() cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.deletePreviousChar() cursor.endEditBlock() intercepted = True # Regular backwards deletion else: anchor = cursor.anchor() if anchor == position: intercepted = not self._in_buffer(position - 1) else: intercepted = not self._in_buffer(min(anchor, position)) elif key == QtCore.Qt.Key_Delete: # Line deletion (remove continuation prompt) if not self._reading and self._in_buffer(position) and \ cursor.atBlockEnd() and not cursor.hasSelection(): cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor, len(self._continuation_prompt)) cursor.removeSelectedText() intercepted = True # Regular forwards deletion: else: anchor = cursor.anchor() intercepted = (not self._in_buffer(anchor) or not self._in_buffer(position)) # Don't move the cursor if Control/Cmd is pressed to allow copy-paste # using the keyboard in any part of the buffer. if not self._control_key_down(event.modifiers(), include_command=True): self._keep_cursor_in_buffer() return intercepted def _event_filter_page_keypress(self, event): """ Filter key events for the paging widget to create console-like interface. """ key = event.key() ctrl_down = self._control_key_down(event.modifiers()) alt_down = event.modifiers() & QtCore.Qt.AltModifier if ctrl_down: if key == QtCore.Qt.Key_O: self._control.setFocus() intercept = True elif alt_down: if key == QtCore.Qt.Key_Greater: self._page_control.moveCursor(QtGui.QTextCursor.End) intercepted = True elif key == QtCore.Qt.Key_Less: self._page_control.moveCursor(QtGui.QTextCursor.Start) intercepted = True elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape): if self._splitter: self._page_control.hide() else: self.layout().setCurrentWidget(self._control) return True elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_PageDown, QtCore.Qt.NoModifier) QtGui.QApplication.sendEvent(self._page_control, new_event) return True elif key == QtCore.Qt.Key_Backspace: new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_PageUp, QtCore.Qt.NoModifier) QtGui.QApplication.sendEvent(self._page_control, new_event) return True return False def _format_as_columns(self, items, separator=' '): """ Transform a list of strings into a single string with columns. Parameters ---------- items : sequence of strings The strings to process. separator : str, optional [default is two spaces] The string that separates columns. Returns ------- The formatted string. """ # Note: this code is adapted from columnize 0.3.2. # See http://code.google.com/p/pycolumnize/ # Calculate the number of characters available. width = self._control.viewport().width() char_width = QtGui.QFontMetrics(self.font).width(' ') displaywidth = max(10, (width / char_width) - 1) # Some degenerate cases. size = len(items) if size == 0: return '\n' elif size == 1: return '%s\n' % items[0] # Try every row count from 1 upwards array_index = lambda nrows, row, col: nrows*col + row for nrows in range(1, size): ncols = (size + nrows - 1) // nrows colwidths = [] totwidth = -len(separator) for col in range(ncols): # Get max column width for this column colwidth = 0 for row in range(nrows): i = array_index(nrows, row, col) if i >= size: break x = items[i] colwidth = max(colwidth, len(x)) colwidths.append(colwidth) totwidth += colwidth + len(separator) if totwidth > displaywidth: break if totwidth <= displaywidth: break # The smallest number of rows computed and the max widths for each # column has been obtained. Now we just have to format each of the rows. string = '' for row in range(nrows): texts = [] for col in range(ncols): i = row + nrows*col if i >= size: texts.append('') else: texts.append(items[i]) while texts and not texts[-1]: del texts[-1] for col in range(len(texts)): texts[col] = texts[col].ljust(colwidths[col]) string += '%s\n' % separator.join(texts) return string def _get_block_plain_text(self, block): """ Given a QTextBlock, return its unformatted text. """ cursor = QtGui.QTextCursor(block) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) return cursor.selection().toPlainText() def _get_cursor(self): """ Convenience method that returns a cursor for the current position. """ return self._control.textCursor() def _get_end_cursor(self): """ Convenience method that returns a cursor for the last character. """ cursor = self._control.textCursor() cursor.movePosition(QtGui.QTextCursor.End) return cursor def _get_input_buffer_cursor_column(self): """ Returns the column of the cursor in the input buffer, excluding the contribution by the prompt, or -1 if there is no such column. """ prompt = self._get_input_buffer_cursor_prompt() if prompt is None: return -1 else: cursor = self._control.textCursor() return cursor.columnNumber() - len(prompt) def _get_input_buffer_cursor_line(self): """ Returns the text of the line of the input buffer that contains the cursor, or None if there is no such line. """ prompt = self._get_input_buffer_cursor_prompt() if prompt is None: return None else: cursor = self._control.textCursor() text = self._get_block_plain_text(cursor.block()) return text[len(prompt):] def _get_input_buffer_cursor_prompt(self): """ Returns the (plain text) prompt for line of the input buffer that contains the cursor, or None if there is no such line. """ if self._executing: return None cursor = self._control.textCursor() if cursor.position() >= self._prompt_pos: if cursor.blockNumber() == self._get_prompt_cursor().blockNumber(): return self._prompt else: return self._continuation_prompt else: return None def _get_prompt_cursor(self): """ Convenience method that returns a cursor for the prompt position. """ cursor = self._control.textCursor() cursor.setPosition(self._prompt_pos) return cursor def _get_selection_cursor(self, start, end): """ Convenience method that returns a cursor with text selected between the positions 'start' and 'end'. """ cursor = self._control.textCursor() cursor.setPosition(start) cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor) return cursor def _get_word_start_cursor(self, position): """ Find the start of the word to the left the given position. If a sequence of non-word characters precedes the first word, skip over them. (This emulates the behavior of bash, emacs, etc.) """ document = self._control.document() position -= 1 while position >= self._prompt_pos and \ not is_letter_or_number(document.characterAt(position)): position -= 1 while position >= self._prompt_pos and \ is_letter_or_number(document.characterAt(position)): position -= 1 cursor = self._control.textCursor() cursor.setPosition(position + 1) return cursor def _get_word_end_cursor(self, position): """ Find the end of the word to the right the given position. If a sequence of non-word characters precedes the first word, skip over them. (This emulates the behavior of bash, emacs, etc.) """ document = self._control.document() end = self._get_end_cursor().position() while position < end and \ not is_letter_or_number(document.characterAt(position)): position += 1 while position < end and \ is_letter_or_number(document.characterAt(position)): position += 1 cursor = self._control.textCursor() cursor.setPosition(position) return cursor def _insert_continuation_prompt(self, cursor): """ Inserts new continuation prompt using the specified cursor. """ if self._continuation_prompt_html is None: self._insert_plain_text(cursor, self._continuation_prompt) else: self._continuation_prompt = self._insert_html_fetching_plain_text( cursor, self._continuation_prompt_html) def _insert_html(self, cursor, html): """ Inserts HTML using the specified cursor in such a way that future formatting is unaffected. """ cursor.beginEditBlock() cursor.insertHtml(html) # After inserting HTML, the text document "remembers" it's in "html # mode", which means that subsequent calls adding plain text will result # in unwanted formatting, lost tab characters, etc. The following code # hacks around this behavior, which I consider to be a bug in Qt, by # (crudely) resetting the document's style state. cursor.movePosition(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor) if cursor.selection().toPlainText() == ' ': cursor.removeSelectedText() else: cursor.movePosition(QtGui.QTextCursor.Right) cursor.insertText(' ', QtGui.QTextCharFormat()) cursor.endEditBlock() def _insert_html_fetching_plain_text(self, cursor, html): """ Inserts HTML using the specified cursor, then returns its plain text version. """ cursor.beginEditBlock() cursor.removeSelectedText() start = cursor.position() self._insert_html(cursor, html) end = cursor.position() cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor) text = cursor.selection().toPlainText() cursor.setPosition(end) cursor.endEditBlock() return text def _insert_plain_text(self, cursor, text): """ Inserts plain text using the specified cursor, processing ANSI codes if enabled. """ cursor.insertText(text) def _insert_plain_text_into_buffer(self, cursor, text): """ Inserts text into the input buffer using the specified cursor (which must be in the input buffer), ensuring that continuation prompts are inserted as necessary. """ lines = text.splitlines(True) if lines: cursor.beginEditBlock() cursor.insertText(lines[0]) for line in lines[1:]: if self._continuation_prompt_html is None: cursor.insertText(self._continuation_prompt) else: self._continuation_prompt = \ self._insert_html_fetching_plain_text( cursor, self._continuation_prompt_html) cursor.insertText(line) cursor.endEditBlock() def _in_buffer(self, position=None): """ Returns whether the current cursor (or, if specified, a position) is inside the editing region. """ cursor = self._control.textCursor() if position is None: position = cursor.position() else: cursor.setPosition(position) line = cursor.blockNumber() prompt_line = self._get_prompt_cursor().blockNumber() if line == prompt_line: return position >= self._prompt_pos elif line > prompt_line: cursor.movePosition(QtGui.QTextCursor.StartOfBlock) prompt_pos = cursor.position() + len(self._continuation_prompt) return position >= prompt_pos return False def _keep_cursor_in_buffer(self): """ Ensures that the cursor is inside the editing region. Returns whether the cursor was moved. """ moved = not self._in_buffer() if moved: cursor = self._control.textCursor() cursor.movePosition(QtGui.QTextCursor.End) self._control.setTextCursor(cursor) return moved def _keyboard_quit(self): """ Cancels the current editing task ala Ctrl-G in Emacs. """ if self._text_completing_pos: self._cancel_text_completion() else: self.input_buffer = '' def _page(self, text, html=False): """ Displays text using the pager if it exceeds the height of the viewport. Parameters: ----------- html : bool, optional (default False) If set, the text will be interpreted as HTML instead of plain text. """ line_height = QtGui.QFontMetrics(self.font).height() minlines = self._control.viewport().height() / line_height if self.paging != 'none' and \ re.match("(?:[^\n]*\n){%i}" % minlines, text): if self.paging == 'custom': self.custom_page_requested.emit(text) else: self._page_control.clear() cursor = self._page_control.textCursor() if html: self._insert_html(cursor, text) else: self._insert_plain_text(cursor, text) self._page_control.moveCursor(QtGui.QTextCursor.Start) self._page_control.viewport().resize(self._control.size()) if self._splitter: self._page_control.show() self._page_control.setFocus() else: self.layout().setCurrentWidget(self._page_control) elif html: self._append_plain_html(text) else: self._append_plain_text(text) def _prompt_finished(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ self._control.setReadOnly(True) self._prompt_finished_hook() def _prompt_started(self): """ Called immediately after a new prompt is displayed. """ # Temporarily disable the maximum block count to permit undo/redo and # to ensure that the prompt position does not change due to truncation. self._control.document().setMaximumBlockCount(0) self._control.setUndoRedoEnabled(True) self._control.setReadOnly(False) self._control.moveCursor(QtGui.QTextCursor.End) self._executing = False self._prompt_started_hook() def _readline(self, prompt='', callback=None): """ Reads one line of input from the user. Parameters ---------- prompt : str, optional The prompt to print before reading the line. callback : callable, optional A callback to execute with the read line. If not specified, input is read *synchronously* and this method does not return until it has been read. Returns ------- If a callback is specified, returns nothing. Otherwise, returns the input string with the trailing newline stripped. """ if self._reading: raise RuntimeError('Cannot read a line. Widget is already reading.') if not callback and not self.isVisible(): # If the user cannot see the widget, this function cannot return. raise RuntimeError('Cannot synchronously read a line if the widget ' 'is not visible!') self._reading = True self._show_prompt(prompt, newline=False) if callback is None: self._reading_callback = None while self._reading: QtCore.QCoreApplication.processEvents() return self.input_buffer.rstrip('\n') else: self._reading_callback = lambda: \ callback(self.input_buffer.rstrip('\n')) def _set_continuation_prompt(self, prompt, html=False): """ Sets the continuation prompt. Parameters ---------- prompt : str The prompt to show when more input is needed. html : bool, optional (default False) If set, the prompt will be inserted as formatted HTML. Otherwise, the prompt will be treated as plain text, though ANSI color codes will be handled. """ if html: self._continuation_prompt_html = prompt else: self._continuation_prompt = prompt self._continuation_prompt_html = None def _set_cursor(self, cursor): """ Convenience method to set the current cursor. """ self._control.setTextCursor(cursor) def _set_top_cursor(self, cursor): """ Scrolls the viewport so that the specified cursor is at the top. """ scrollbar = self._control.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) original_cursor = self._control.textCursor() self._control.setTextCursor(cursor) self._control.ensureCursorVisible() self._control.setTextCursor(original_cursor) def _show_prompt(self, prompt=None, html=False, newline=True): """ Writes a new prompt at the end of the buffer. Parameters ---------- prompt : str, optional The prompt to show. If not specified, the previous prompt is used. html : bool, optional (default False) Only relevant when a prompt is specified. If set, the prompt will be inserted as formatted HTML. Otherwise, the prompt will be treated as plain text, though ANSI color codes will be handled. newline : bool, optional (default True) If set, a new line will be written before showing the prompt if there is not already a newline at the end of the buffer. """ # Insert a preliminary newline, if necessary. if newline: cursor = self._get_end_cursor() if cursor.position() > 0: cursor.movePosition(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor) if cursor.selection().toPlainText() != '\n': self._append_plain_text('\n') # Write the prompt. self._append_plain_text(self._prompt_sep) if prompt is None: if self._prompt_html is None: self._append_plain_text(self._prompt) else: self._append_html(self._prompt_html) else: if html: self._prompt = self._append_html_fetching_plain_text(prompt) self._prompt_html = prompt else: self._append_plain_text(prompt) self._prompt = prompt self._prompt_html = None self._prompt_pos = self._get_end_cursor().position() self._prompt_started() #------ Signal handlers ---------------------------------------------------- def _adjust_scrollbars(self): """ Expands the vertical scrollbar beyond the range set by Qt. """ # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp # and qtextedit.cpp. document = self._control.document() scrollbar = self._control.verticalScrollBar() viewport_height = self._control.viewport().height() if isinstance(self._control, QtGui.QPlainTextEdit): maximum = max(0, document.lineCount() - 1) step = viewport_height / self._control.fontMetrics().lineSpacing() else: # QTextEdit does not do line-based layout and blocks will not in # general have the same height. Therefore it does not make sense to # attempt to scroll in line height increments. maximum = document.size().height() step = viewport_height diff = maximum - scrollbar.maximum() scrollbar.setRange(0, maximum) scrollbar.setPageStep(step) # Compensate for undesirable scrolling that occurs automatically due to # maximumBlockCount() text truncation. if diff < 0 and document.blockCount() == document.maximumBlockCount(): scrollbar.setValue(scrollbar.value() + diff) def _cursor_position_changed(self): """ Clears the temporary buffer based on the cursor position. """ if self._text_completing_pos: document = self._control.document() if self._text_completing_pos < document.characterCount(): cursor = self._control.textCursor() pos = cursor.position() text_cursor = self._control.textCursor() text_cursor.setPosition(self._text_completing_pos) if pos < self._text_completing_pos or \ cursor.blockNumber() > text_cursor.blockNumber(): self._clear_temporary_buffer() self._text_completing_pos = 0 else: self._clear_temporary_buffer() self._text_completing_pos = 0 def _custom_context_menu_requested(self, pos): """ Shows a context menu at the given QPoint (in widget coordinates). """ menu = self._context_menu_make(pos) menu.exec_(self._control.mapToGlobal(pos)) pyface-4.1.0/pyface/ui/qt4/console/call_tip_widget.py0000644000175100001440000002130711674463363023526 0ustar ischnellusers00000000000000# Standard library imports import re from textwrap import dedent from unicodedata import category # System library imports from pyface.qt import QtCore, QtGui class CallTipWidget(QtGui.QLabel): """ Shows call tips by parsing the current text of Q[Plain]TextEdit. """ #-------------------------------------------------------------------------- # 'QObject' interface #-------------------------------------------------------------------------- def __init__(self, text_edit): """ Create a call tip manager that is attached to the specified Qt text edit widget. """ assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) super(CallTipWidget, self).__init__(None, QtCore.Qt.ToolTip) self._hide_timer = QtCore.QBasicTimer() self._text_edit = text_edit self.setFont(text_edit.document().defaultFont()) self.setForegroundRole(QtGui.QPalette.ToolTipText) self.setBackgroundRole(QtGui.QPalette.ToolTipBase) self.setPalette(QtGui.QToolTip.palette()) self.setAlignment(QtCore.Qt.AlignLeft) self.setIndent(1) self.setFrameStyle(QtGui.QFrame.NoFrame) self.setMargin(1 + self.style().pixelMetric( QtGui.QStyle.PM_ToolTipLabelFrameWidth, None, self)) self.setWindowOpacity(self.style().styleHint( QtGui.QStyle.SH_ToolTipLabel_Opacity, None, self, None) / 255.0) def eventFilter(self, obj, event): """ Reimplemented to hide on certain key presses and on text edit focus changes. """ if obj == self._text_edit: etype = event.type() if etype == QtCore.QEvent.KeyPress: key = event.key() if key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return): self.hide() elif key == QtCore.Qt.Key_Escape: self.hide() return True elif etype == QtCore.QEvent.FocusOut: self.hide() elif etype == QtCore.QEvent.Enter: self._hide_timer.stop() elif etype == QtCore.QEvent.Leave: self._leave_event_hide() return super(CallTipWidget, self).eventFilter(obj, event) def timerEvent(self, event): """ Reimplemented to hide the widget when the hide timer fires. """ if event.timerId() == self._hide_timer.timerId(): self._hide_timer.stop() self.hide() #-------------------------------------------------------------------------- # 'QWidget' interface #-------------------------------------------------------------------------- def enterEvent(self, event): """ Reimplemented to cancel the hide timer. """ super(CallTipWidget, self).enterEvent(event) self._hide_timer.stop() def hideEvent(self, event): """ Reimplemented to disconnect signal handlers and event filter. """ super(CallTipWidget, self).hideEvent(event) self._text_edit.cursorPositionChanged.disconnect( self._cursor_position_changed) self._text_edit.removeEventFilter(self) def leaveEvent(self, event): """ Reimplemented to start the hide timer. """ super(CallTipWidget, self).leaveEvent(event) self._leave_event_hide() def paintEvent(self, event): """ Reimplemented to paint the background panel. """ painter = QtGui.QStylePainter(self) option = QtGui.QStyleOptionFrame() option.initFrom(self) painter.drawPrimitive(QtGui.QStyle.PE_PanelTipLabel, option) painter.end() super(CallTipWidget, self).paintEvent(event) def setFont(self, font): """ Reimplemented to allow use of this method as a slot. """ super(CallTipWidget, self).setFont(font) def showEvent(self, event): """ Reimplemented to connect signal handlers and event filter. """ super(CallTipWidget, self).showEvent(event) self._text_edit.cursorPositionChanged.connect( self._cursor_position_changed) self._text_edit.installEventFilter(self) #-------------------------------------------------------------------------- # 'CallTipWidget' interface #-------------------------------------------------------------------------- def show_call_info(self, call_line=None, doc=None, maxlines=20): """ Attempts to show the specified call line and docstring at the current cursor location. The docstring is possibly truncated for length. """ if doc: match = re.match("(?:[^\n]*\n){%i}" % maxlines, doc) if match: doc = doc[:match.end()] + '\n[Documentation continues...]' else: doc = '' if call_line: doc = '\n\n'.join([call_line, doc]) return self.show_tip(doc) def show_tip(self, tip): """ Attempts to show the specified tip at the current cursor location. """ # Attempt to find the cursor position at which to show the call tip. text_edit = self._text_edit document = text_edit.document() cursor = text_edit.textCursor() search_pos = cursor.position() - 1 self._start_position, _ = self._find_parenthesis(search_pos, forward=False) if self._start_position == -1: return False # Set the text and resize the widget accordingly. self.setText(tip) self.resize(self.sizeHint()) # Locate and show the widget. Place the tip below the current line # unless it would be off the screen. In that case, place it above # the current line. padding = 3 # Distance in pixels between cursor bounds and tip box. cursor_rect = text_edit.cursorRect(cursor) screen_rect = QtGui.QApplication.desktop().screenGeometry(text_edit) point = text_edit.mapToGlobal(cursor_rect.bottomRight()) point.setY(point.y() + padding) tip_height = self.size().height() if point.y() + tip_height > screen_rect.height(): point = text_edit.mapToGlobal(cursor_rect.topRight()) point.setY(point.y() - tip_height - padding) self.move(point) self.show() return True #-------------------------------------------------------------------------- # Protected interface #-------------------------------------------------------------------------- def _find_parenthesis(self, position, forward=True): """ If 'forward' is True (resp. False), proceed forwards (resp. backwards) through the line that contains 'position' until an unmatched closing (resp. opening) parenthesis is found. Returns a tuple containing the position of this parenthesis (or -1 if it is not found) and the number commas (at depth 0) found along the way. """ commas = depth = 0 document = self._text_edit.document() char = document.characterAt(position) # Search until a match is found or a non-printable character is # encountered. while category(char) != 'Cc' and position > 0: if char == ',' and depth == 0: commas += 1 elif char == ')': if forward and depth == 0: break depth += 1 elif char == '(': if not forward and depth == 0: break depth -= 1 position += 1 if forward else -1 char = document.characterAt(position) else: position = -1 return position, commas def _leave_event_hide(self): """ Hides the tooltip after some time has passed (assuming the cursor is not over the tooltip). """ if (not self._hide_timer.isActive() and # If Enter events always came after Leave events, we wouldn't need # this check. But on Mac OS, it sometimes happens the other way # around when the tooltip is created. QtGui.QApplication.topLevelAt(QtGui.QCursor.pos()) != self): self._hide_timer.start(300, self) #------ Signal handlers ---------------------------------------------------- def _cursor_position_changed(self): """ Updates the tip based on user cursor movement. """ cursor = self._text_edit.textCursor() if cursor.position() <= self._start_position: self.hide() else: position, commas = self._find_parenthesis(self._start_position + 1) if position != -1: self.hide() pyface-4.1.0/pyface/ui/qt4/gui.py0000644000175100001440000001500111674463363017510 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, HasTraits, implements, Unicode from pyface.util.guisupport import start_event_loop_qt4 # Local imports. from pyface.i_gui import IGUI, MGUI # Logging. logger = logging.getLogger(__name__) class GUI(MGUI, HasTraits): """ The toolkit specific implementation of a GUI. See the IGUI interface for the API documentation. """ implements(IGUI) #### 'GUI' interface ###################################################### busy = Bool(False) started = Bool(False) state_location = Unicode ########################################################################### # 'object' interface. ########################################################################### def __init__(self, splash_screen=None): # Display the (optional) splash screen. self._splash_screen = splash_screen if self._splash_screen is not None: self._splash_screen.open() ########################################################################### # 'GUI' class interface. ########################################################################### def invoke_after(cls, millisecs, callable, *args, **kw): _FutureCall(millisecs, callable, *args, **kw) invoke_after = classmethod(invoke_after) def invoke_later(cls, callable, *args, **kw): _FutureCall(0, callable, *args, **kw) invoke_later = classmethod(invoke_later) def set_trait_after(cls, millisecs, obj, trait_name, new): _FutureCall(millisecs, setattr, obj, trait_name, new) set_trait_after = classmethod(set_trait_after) def set_trait_later(cls, obj, trait_name, new): _FutureCall(0, setattr, obj, trait_name, new) set_trait_later = classmethod(set_trait_later) def process_events(allow_user_events=True): if allow_user_events: events = QtCore.QEventLoop.AllEvents else: events = QtCore.QEventLoop.ExcludeUserInputEvents QtCore.QCoreApplication.processEvents(events) process_events = staticmethod(process_events) def set_busy(busy=True): if busy: QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) else: QtGui.QApplication.restoreOverrideCursor() set_busy = staticmethod(set_busy) ########################################################################### # 'GUI' interface. ########################################################################### def start_event_loop(self): if self._splash_screen is not None: self._splash_screen.close() # Make sure that we only set the 'started' trait after the main loop # has really started. self.set_trait_later(self, "started", True) logger.debug("---------- starting GUI event loop ----------") start_event_loop_qt4() self.started = False def stop_event_loop(self): logger.debug("---------- stopping GUI event loop ----------") QtGui.QApplication.quit() ########################################################################### # Trait handlers. ########################################################################### def _state_location_default(self): """ The default state location handler. """ return self._default_state_location() def _busy_changed(self, new): """ The busy trait change handler. """ if new: QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) else: QtGui.QApplication.restoreOverrideCursor() class _FutureCall(QtCore.QObject): """ This is a helper class that is similar to the wx FutureCall class. """ # Keep a list of references so that they don't get garbage collected. _calls = [] # Manage access to the list of instances. _calls_mutex = QtCore.QMutex() # A new Qt event type for _FutureCalls _pyface_event = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) def __init__(self, ms, callable, *args, **kw): super(_FutureCall, self).__init__() # Save the arguments. self._ms = ms self._callable = callable self._args = args self._kw = kw # Save the instance. self._calls_mutex.lock() self._calls.append(self) self._calls_mutex.unlock() # Move to the main GUI thread. self.moveToThread(QtGui.QApplication.instance().thread()) # Post an event to be dispatched on the main GUI thread. Note that # we do not call QTimer.singleShot here, which would be simpler, because # that only works on QThreads. We want regular Python threads to work. event = QtCore.QEvent(self._pyface_event) QtGui.QApplication.postEvent(self, event) QtGui.QApplication.sendPostedEvents() def event(self, event): """ QObject event handler. """ if event.type() == self._pyface_event: # Invoke the callable if self._ms: QtCore.QTimer.singleShot(self._ms, self._dispatch) else: self._callable(*self._args, **self._kw) # We cannot remove from self._calls here. QObjects don't like # being garbage collected during event handlers. QtCore.QTimer.singleShot(0, self._finished) return True return super(_FutureCall, self).event(event) def _dispatch(self): """ Invoke the callable. """ try: self._callable(*self._args, **self._kw) finally: self._finished() def _finished(self): """ Remove the call from the list, so it can be garbage collected. """ self._calls_mutex.lock() try: self._calls.remove(self) finally: self._calls_mutex.unlock() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/about_dialog.py0000644000175100001440000000632411674463363021365 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import sys # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import implements, Instance, List, Unicode # Local imports. from pyface.i_about_dialog import IAboutDialog, MAboutDialog from pyface.image_resource import ImageResource from dialog import Dialog # The HTML displayed in the QLabel. _DIALOG_TEXT = '''

%s

Python %s
Qt %s

Copyright © 2003-2010 Enthought, Inc.
Copyright © 2007 Riverbank Computing Limited

''' class AboutDialog(MAboutDialog, Dialog): """ The toolkit specific implementation of an AboutDialog. See the IAboutDialog interface for the API documentation. """ implements(IAboutDialog) #### 'IAboutDialog' interface ############################################# additions = List(Unicode) image = Instance(ImageResource, ImageResource('about')) ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): label = QtGui.QLabel() if parent.parent() is not None: title = parent.parent().windowTitle() else: title = "" # Set the title. self.title = "About %s" % title # Load the image to be displayed in the about box. image = self.image.create_image() path = self.image.absolute_path # The additional strings. additions = '
'.join(self.additions) # Get the version numbers. py_version = sys.version[0:sys.version.find("(")] qt_version = QtCore.__version__ # Set the page contents. label.setText(_DIALOG_TEXT % (path, additions, py_version, qt_version)) # Create the button. buttons = QtGui.QDialogButtonBox() if self.ok_label: buttons.addButton(self.ok_label, QtGui.QDialogButtonBox.AcceptRole) else: buttons.addButton(QtGui.QDialogButtonBox.Ok) buttons.connect(buttons, QtCore.SIGNAL('accepted()'), parent, QtCore.SLOT('accept()')) lay = QtGui.QVBoxLayout() lay.addWidget(label) lay.addWidget(buttons) parent.setLayout(lay) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/workbench/0000755000175100001440000000000011674463363020337 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/workbench/view.py0000644000175100001440000000352411674463363021667 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.workbench.i_view import MView class View(MView): """ The toolkit specific implementation of a View. See the IView interface for the API documentation. """ ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ from pyface.qt import QtGui control = QtGui.QWidget(parent) palette = control.palette() palette.setColor(QtGui.QPalette.Window, QtGui.QColor('red')) control.setPalette(palette) control.setAutoFillBackground(True) return control def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ if self.control is not None: self.control.hide() self.control.deleteLater() self.control = None return def set_focus(self): """ Set the focus to the appropriate control in the part. """ if self.control is not None: self.control.setFocus() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/workbench/images/0000755000175100001440000000000011674463363021604 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/workbench/images/spinner.gif0000644000175100001440000000331111674463363023747 0ustar ischnellusers00000000000000GIF89aԔ```@@@DDDnnnܤ$$$(((000666>>>jjjrrrŠPPP^^^:::\\\|||ↆhhhfffFFF xxx֮ƼzzzLLLRRRZZZbbbBBB<<B;>CD ;+F &03 8<݄!'14,< "PF`! ,;WX []<U Y-\$_&JOSHTJ^*\GP2 #7HKQ:6L=FF'MijNA:bIT@?R/D924I ::/E`ӃA&V,ZJCaTT9Ij! , i^,CK jl8^ghQCRk "dNJnj2H0T.-435GЄ`eC6:f:=σG5AT770/6@C(CBA;@/܆F r Z@! ,?(N!lG9'g[jNJA#HlF=B]ON*#F`8"p A/`9"07qE "T=36n F o)! ,aoe&? +qe4H2D< @33 %F8<;J @@ce[l>8TBjuaB7KAǐ0Cv^ ur6b?ՅY)3T3+'s 6F0:.ntn830֭! ,0@9(+ATA8cJq >`GT6:"VVA6@>/B4eb= oRTD3>hF^M'0F7BLd63Ӑ]jl7ۅ&NG+TTc1SOJvwn\-Yr;"0:TD AB8Q"6/37;(I@0=:GJ73QfA6D,<3e34.1&2*^Z\3#*N DK$+C9 \T6Jc -:gY(DZe u,AKXqoPt2Q=FAl!+ OjLM^aG1N\@ÕA;pyface-4.1.0/pyface/ui/qt4/workbench/editor.py0000644000175100001440000000410411674463363022176 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Local imports. from traits.api import Event, Bool from pyface.workbench.i_editor import MEditor class Editor(MEditor): """ The toolkit specific implementation of an Editor. See the IEditor interface for the API documentation. """ # Traits for showing spinner _loading = Event(Bool) _loading_on_open = Bool(False) ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ from pyface.qt import QtCore, QtGui # By default we create a yellow panel! control = QtGui.QWidget(parent) pal = control.palette() pal.setColour(QtGui.QPalette.Window, QtCore.Qt.yellow) control.setPalette(pal) control.setAutoFillBackground(True) control.resize(100, 200) return control def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ if self.control is not None: self.control.hide() self.control.close() self.control.deleteLater() self.control = None return def set_focus(self): """ Set the focus to the appropriate control in the part. """ if self.control is not None: self.control.setFocus() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/workbench/workbench_window_layout.py0000755000175100001440000005211511674463363025666 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. import logging # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Instance, on_trait_change # Local imports. from split_tab_widget import SplitTabWidget from pyface.message_dialog import error from pyface.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout # Logging. logger = logging.getLogger(__name__) # For mapping positions relative to the editor area. _EDIT_AREA_MAP = { 'left': QtCore.Qt.LeftDockWidgetArea, 'right': QtCore.Qt.RightDockWidgetArea, 'top': QtCore.Qt.TopDockWidgetArea, 'bottom': QtCore.Qt.BottomDockWidgetArea } # For mapping positions relative to another view. _VIEW_AREA_MAP = { 'left': (QtCore.Qt.Horizontal, True), 'right': (QtCore.Qt.Horizontal, False), 'top': (QtCore.Qt.Vertical, True), 'bottom': (QtCore.Qt.Vertical, False) } class WorkbenchWindowLayout(MWorkbenchWindowLayout): """ The Qt4 implementation of the workbench window layout interface. See the 'IWorkbenchWindowLayout' interface for the API documentation. """ #### Private interface #################################################### # The widget that implements the editor area. We keep (and use) this # separate reference because we can't always assume that it has been set to # be the main window's central widget. _qt4_editor_area = Instance(SplitTabWidget) ########################################################################### # 'IWorkbenchWindowLayout' interface. ########################################################################### def activate_editor(self, editor): if editor.control is not None: editor.control.show() self._qt4_editor_area.setCurrentWidget(editor.control) editor.set_focus() return editor def activate_view(self, view): # FIXME v3: This probably doesn't work as expected. view.control.raise_() view.set_focus() return view def add_editor(self, editor, title): if editor is None: return None try: self._qt4_editor_area.addTab(self._qt4_get_editor_control(editor), title) if editor._loading_on_open: self._qt4_editor_tab_spinner(editor, '', True) except Exception: logger.exception('error creating editor control [%s]', editor.id) return editor def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): if view is None: return None try: self._qt4_add_view(view, position, relative_to, size) view.visible = True except Exception: logger.exception('error creating view control [%s]', view.id) # Even though we caught the exception, it sometimes happens that # the view's control has been created as a child of the application # window (or maybe even the dock control). We should destroy the # control to avoid bad UI effects. view.destroy_control() # Additionally, display an error message to the user. error(self.window.control, 'Unable to add view [%s]' % view.id, 'Workbench Plugin Error') return view def close_editor(self, editor): if editor.control is not None: editor.control.close() return editor def close_view(self, view): self.hide_view(view) return view def close(self): # Don't fire signals for editors that have destroyed their controls. QtCore.QObject.disconnect(self._qt4_editor_area, QtCore.SIGNAL('hasFocus'), self._qt4_editor_focus) self._qt4_editor_area.clear() # Delete all dock widgets. for v in self.window.views: if self.contains_view(v): self._qt4_delete_view_dock_widget(v) def create_initial_layout(self, parent): self._qt4_editor_area = editor_area = SplitTabWidget(parent) QtCore.QObject.connect(editor_area, QtCore.SIGNAL('hasFocus'), self._qt4_editor_focus) # We are interested in focus changes but we get them from the editor # area rather than qApp to allow the editor area to restrict them when # needed. QtCore.QObject.connect( editor_area, QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), self._qt4_view_focus_changed) QtCore.QObject.connect(self._qt4_editor_area, QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self._qt4_editor_title_changed) editor_area.new_window_request.connect(self._qt4_new_window_request) editor_area.tab_close_request.connect(self._qt4_tab_close_request) editor_area.tab_window_changed.connect(self._qt4_tab_window_changed) return editor_area def contains_view(self, view): return hasattr(view, '_qt4_dock') def hide_editor_area(self): self._qt4_editor_area.hide() def hide_view(self, view): view._qt4_dock.hide() view.visible = False return view def refresh(self): # Nothing to do. pass def reset_editors(self): self._qt4_editor_area.setCurrentIndex(0) def reset_views(self): # Qt doesn't provide information about the order of dock widgets in a # dock area. pass def show_editor_area(self): self._qt4_editor_area.show() def show_view(self, view): view._qt4_dock.show() view.visible = True #### Methods for saving and restoring the layout ########################## def get_view_memento(self): # Get the IDs of the views in the main window. This information is # also in the QMainWindow state, but that is opaque. view_ids = [v.id for v in self.window.views if self.contains_view(v)] # Everything else is provided by QMainWindow. state = str(self.window.control.saveState()) return (0, (view_ids, state)) def set_view_memento(self, memento): version, mdata = memento # There has only ever been version 0 so far so check with an assert. assert version == 0 # Now we know the structure of the memento we can "parse" it. view_ids, state = mdata # Get a list of all views that have dock widgets and mark them. dock_views = [v for v in self.window.views if self.contains_view(v)] for v in dock_views: v._qt4_gone = True # Create a dock window for all views that had one last time. for v in self.window.views: # Make sure this is in a known state. v.visible = False for vid in view_ids: if vid == v.id: # Create the dock widget if needed and make sure that it is # invisible so that it matches the state of the visible # trait. Things will all come right when the main window # state is restored below. self._qt4_create_view_dock_widget(v).setVisible(False) if v in dock_views: delattr(v, '_qt4_gone') break # Remove any remain unused dock widgets. for v in dock_views: try: delattr(v, '_qt4_gone') except AttributeError: pass else: self._qt4_delete_view_dock_widget(v) # Restore the state. This will update the view's visible trait through # the dock window's toggle action. self.window.control.restoreState(state) def get_editor_memento(self): # Get the layout of the editors. editor_layout = self._qt4_editor_area.saveState() # Get a memento for each editor that describes its contents. editor_references = self._get_editor_references() return (0, (editor_layout, editor_references)) def set_editor_memento(self, memento): version, mdata = memento # There has only ever been version 0 so far so check with an assert. assert version == 0 # Now we know the structure of the memento we can "parse" it. editor_layout, editor_references = mdata def resolve_id(id): # Get the memento for the editor contents (if any). editor_memento = editor_references.get(id) if editor_memento is None: return None # Create the restored editor. editor = self.window.editor_manager.set_editor_memento( editor_memento) if editor is None: return None # Save the editor. self.window.editors.append(editor) # Create the control if needed and return it. return self._qt4_get_editor_control(editor) self._qt4_editor_area.restoreState(editor_layout, resolve_id) def get_toolkit_memento(self): return (0, dict(geometry=str(self.window.control.saveGeometry()))) def set_toolkit_memento(self, memento): if hasattr(memento, 'toolkit_data'): data = memento.toolkit_data if isinstance(data, tuple) and len(data) == 2: version, datadict = data if version == 0: geometry = datadict.pop('geometry', None) if geometry is not None: self.window.control.restoreGeometry(geometry) ########################################################################### # Private interface. ########################################################################### def _qt4_editor_focus(self, new): """ Handle an editor getting the focus. """ for editor in self.window.editors: control = editor.control editor.has_focus = control is new or \ (control is not None and new in control.children()) def _qt4_editor_title_changed(self, control, title): """ Handle the title being changed """ for editor in self.window.editors: if editor.control == control: editor.name = unicode(title) def _qt4_editor_tab_spinner(self, editor, name, new): # Do we need to do this verification? tw, tidx = self._qt4_editor_area._tab_widget(editor.control) if new: tw.show_button(tidx) else: tw.hide_button(tidx) if not new and not editor == self.window.active_editor: self._qt4_editor_area.setTabTextColor(editor.control, QtCore.Qt.red) def _qt4_view_focus_changed(self, old, new): """ Handle the change of focus for a view. """ focus_part = None if new is not None: # Handle focus changes to views. for view in self.window.views: if view.control is not None and view.control.isAncestorOf(new): view.has_focus = True focus_part = view break if old is not None: # Handle focus changes from views. for view in self.window.views: if view is not focus_part and view.control is not None and view.control.isAncestorOf(old): view.has_focus = False break def _qt4_new_window_request(self, pos, control): """ Handle a tab tear-out request from the splitter widget. """ editor = self._qt4_remove_editor_with_control(control) kind = self.window.editor_manager.get_editor_kind(editor) window = self.window.workbench.create_window() window.open() window.add_editor(editor) window.editor_manager.add_editor(editor, kind) window.position = (pos.x(), pos.y()) window.size = self.window.size window.activate_editor(editor) editor.window = window def _qt4_tab_close_request(self, control): """ Handle a tabCloseRequest from the splitter widget. """ for editor in self.window.editors: if editor.control == control: editor.close() break def _qt4_tab_window_changed(self, control): """ Handle a tab drag to a different WorkbenchWindow. """ editor = self._qt4_remove_editor_with_control(control) kind = self.window.editor_manager.get_editor_kind(editor) while not control.isWindow(): control = control.parent() for window in self.window.workbench.windows: if window.control == control: window.editors.append(editor) window.editor_manager.add_editor(editor, kind) window.layout._qt4_get_editor_control(editor) window.activate_editor(editor) editor.window = window break def _qt4_remove_editor_with_control(self, control): """ Finds the editor associated with 'control' and removes it. Returns the editor, or None if no editor was found. """ for editor in self.window.editors: if editor.control == control: self.editor_closing = editor control.removeEventFilter(self._qt4_mon) self.editor_closed = editor # Make sure that focus events get fired if this editor is # subsequently added to another window. editor.has_focus = False return editor def _qt4_get_editor_control(self, editor): """ Create the editor control if it hasn't already been done. """ if editor.control is None: self.editor_opening = editor # We must provide a parent (because TraitsUI checks for it when # deciding what sort of panel to create) but it can't be the editor # area (because it will be automatically added to the base # QSplitter). editor.control = editor.create_control(self.window.control) editor.control.setObjectName(editor.id) editor.on_trait_change(self._qt4_editor_tab_spinner, '_loading') self.editor_opened = editor def on_name_changed(editor, trait_name, old, new): self._qt4_editor_area.setWidgetTitle(editor.control, editor.name) editor.on_trait_change(on_name_changed, 'name') self._qt4_monitor(editor.control) return editor.control def _qt4_add_view(self, view, position, relative_to, size): """ Add a view. """ # If no specific position is specified then use the view's default # position. if position is None: position = view.position dw = self._qt4_create_view_dock_widget(view, size) mw = self.window.control try: rel_dw = relative_to._qt4_dock except AttributeError: rel_dw = None if rel_dw is None: # If we are trying to add a view with a non-existent item, then # just default to the left of the editor area. if position == 'with': position = 'left' # Position the view relative to the editor area. try: dwa = _EDIT_AREA_MAP[position] except KeyError: raise ValueError, "unknown view position: %s" % position mw.addDockWidget(dwa, dw) elif position == 'with': # FIXME v3: The Qt documentation says that the second should be # placed above the first, but it always seems to be underneath (ie. # hidden) which is not what the user is expecting. mw.tabifyDockWidget(rel_dw, dw) else: try: orient, swap = _VIEW_AREA_MAP[position] except KeyError: raise ValueError, "unknown view position: %s" % position mw.splitDockWidget(rel_dw, dw, orient) # The Qt documentation implies that the layout direction can be # used to position the new dock widget relative to the existing one # but I could only get the button positions to change. Instead we # move things around afterwards if required. if swap: mw.removeDockWidget(rel_dw) mw.splitDockWidget(dw, rel_dw, orient) rel_dw.show() def _qt4_create_view_dock_widget(self, view, size=(-1, -1)): """ Create a dock widget that wraps a view. """ # See if it has already been created. try: dw = view._qt4_dock except AttributeError: dw = QtGui.QDockWidget(view.name, self.window.control) dw.setWidget(_ViewContainer(size, self.window.control)) dw.setObjectName(view.id) dw.connect(dw.toggleViewAction(), QtCore.SIGNAL('toggled(bool)'), self._qt4_handle_dock_visibility) # Save the dock window. view._qt4_dock = dw def on_name_changed(): view._qt4_dock.setWindowTitle(view.name) view.on_trait_change(on_name_changed, 'name') # Make sure the view control exists. if view.control is None: # Make sure that the view knows which window it is in. view.window = self.window try: view.control = view.create_control(self.window.control) except: # Tidy up if the view couldn't be created. delattr(view, '_qt4_dock') self.window.control.removeDockWidget(dw) dw.deleteLater() del dw raise dw.widget().setCentralWidget(view.control) return dw def _qt4_delete_view_dock_widget(self, view): """ Delete a view's dock widget. """ dw = view._qt4_dock # Disassociate the view from the dock. if view.control is not None: view.control.setParent(None) delattr(view, '_qt4_dock') # Delete the dock (and the view container). self.window.control.removeDockWidget(dw) dw.deleteLater() def _qt4_handle_dock_visibility(self, checked): """ Handle the visibility of a dock window changing. """ # Find the dock window by its toggle action. for v in self.window.views: try: dw = v._qt4_dock except AttributeError: continue if dw.toggleViewAction() is dw.sender(): v.visible = checked def _qt4_monitor(self, control): """ Install an event filter for a view or editor control to keep an eye on certain events. """ # Create the monitoring object if needed. try: mon = self._qt4_mon except AttributeError: mon = self._qt4_mon = _Monitor(self) control.installEventFilter(mon) class _Monitor(QtCore.QObject): """ This class monitors a view or editor control. """ def __init__(self, layout): QtCore.QObject.__init__(self, layout.window.control) self._layout = layout def eventFilter(self, obj, e): if isinstance(e, QtGui.QCloseEvent): for editor in self._layout.window.editors: if editor.control is obj: self._layout.editor_closing = editor editor.destroy_control() self._layout.editor_closed = editor break return False class _ViewContainer(QtGui.QMainWindow): """ This class is a container for a view that allows an initial size (specified as a tuple) to be set. """ def __init__(self, size, main_window): """ Initialise the object. """ QtGui.QMainWindow.__init__(self) # Save the size and main window. self._width, self._height = size self._main_window = main_window def sizeHint(self): """ Reimplemented to return the initial size or the view's current size. """ sh = self.centralWidget().sizeHint() if self._width > 0: if self._width > 1: w = self._width else: w = self._main_window.width() * self._width sh.setWidth(int(w)) if self._height > 0: if self._height > 1: h = self._height else: h = self._main_window.height() * self._height sh.setHeight(int(h)) return sh def showEvent(self, e): """ Reimplemented to use the view's current size once shown. """ self._width = self._height = -1 QtGui.QMainWindow.showEvent(self, e) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/workbench/__init__.py0000644000175100001440000000010411674463363022443 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/ui/qt4/workbench/split_tab_widget.py0000644000175100001440000010544711674463363024250 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply #------------------------------------------------------------------------------ # Standard library imports. import sys # Major library imports. from pyface.qt import QtCore, QtGui, qt_api from pyface.image_resource import ImageResource class SplitTabWidget(QtGui.QSplitter): """ The SplitTabWidget class is a hierarchy of QSplitters the leaves of which are QTabWidgets. Any tab may be moved around with the hierarchy automatically extended and reduced as required. """ # Signals for WorkbenchWindowLayout to handle new_window_request = QtCore.Signal(QtCore.QPoint, QtGui.QWidget) tab_close_request = QtCore.Signal(QtGui.QWidget) tab_window_changed = QtCore.Signal(QtGui.QWidget) # The different hotspots of a QTabWidget. An non-negative value is a tab # index and the hotspot is to the left of it. tabTextChanged = QtCore.Signal(QtGui.QWidget, unicode) _HS_NONE = -1 _HS_AFTER_LAST_TAB = -2 _HS_NORTH = -3 _HS_SOUTH = -4 _HS_EAST = -5 _HS_WEST = -6 _HS_OUTSIDE = -7 def __init__(self, *args): """ Initialise the instance. """ QtGui.QSplitter.__init__(self, *args) self.clear() QtCore.QObject.connect(QtGui.QApplication.instance(), QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), self._focus_changed) def clear(self): """ Restore the widget to its pristine state. """ w = None for i in range(self.count()): w = self.widget(i) w.hide() w.deleteLater() del w self._repeat_focus_changes = True self._rband = None self._selected_tab_widget = None self._selected_hotspot = self._HS_NONE self._current_tab_w = None self._current_tab_idx = -1 def saveState(self): """ Returns a Python object containing the saved state of the widget. Widgets are saved only by their object name. """ return self._save_qsplitter(self) def _save_qsplitter(self, qsplitter): # A splitter state is a tuple of the QSplitter state (as a string) and # the list of child states. sp_ch_states = [] # Save the children. for i in range(qsplitter.count()): ch = qsplitter.widget(i) if isinstance(ch, _TabWidget): # A tab widget state is a tuple of the current tab index and # the list of individual tab states. tab_states = [] for t in range(ch.count()): # A tab state is a tuple of the widget's object name and # the title. name = unicode(ch.widget(t).objectName()) title = unicode(ch.tabText(t)) tab_states.append((name, title)) ch_state = (ch.currentIndex(), tab_states) else: # Recurse down the tree of splitters. ch_state = self._save_qsplitter(ch) sp_ch_states.append(ch_state) return (str(QtGui.QSplitter.saveState(qsplitter)), sp_ch_states) def restoreState(self, state, factory): """ Restore the contents from the given state (returned by a previous call to saveState()). factory is a callable that is passed the object name of the widget that is in the state and needs to be restored. The callable returns the restored widget. """ # Ensure we are not restoring to a non-empty widget. assert self.count() == 0 self._restore_qsplitter(state, factory, self) def _restore_qsplitter(self, state, factory, qsplitter): sp_qstate, sp_ch_states = state # Go through each child state which will consist of a tuple of two # objects. We use the type of the first to determine if the child is a # tab widget or another splitter. for ch_state in sp_ch_states: if isinstance(ch_state[0], int): current_idx, tabs = ch_state new_tab = _TabWidget(self) # Go through each tab and use the factory to restore the page. for name, title in tabs: page = factory(name) if page is not None: new_tab.addTab(page, title) # Only add the new tab widget if it is used. if new_tab.count() > 0: qsplitter.addWidget(new_tab) # Set the correct tab as the current one. new_tab.setCurrentIndex(current_idx) else: del new_tab else: new_qsp = QtGui.QSplitter() # Recurse down the tree of splitters. self._restore_qsplitter(ch_state, factory, new_qsp) # Only add the new splitter if it is used. if new_qsp.count() > 0: qsplitter.addWidget(new_qsp) else: del new_qsp # Restore the QSplitter state (being careful to get the right # implementation). QtGui.QSplitter.restoreState(qsplitter, sp_qstate) def addTab(self, w, text): """ Add a new tab to the main tab widget. """ # Find the first tab widget going down the left of the hierarchy. This # will be the one in the top left corner. if self.count() > 0: ch = self.widget(0) while not isinstance(ch, _TabWidget): assert isinstance(ch, QtGui.QSplitter) ch = ch.widget(0) else: # There is no tab widget so create one. ch = _TabWidget(self) self.addWidget(ch) idx = ch.insertTab(self._current_tab_idx+1, w, text) # If the tab has been added to the current tab widget then make it the # current tab. if ch is not self._current_tab_w: self._set_current_tab(ch, idx) ch.tabBar().setFocus() def _close_tab_request(self, w): """ A close button was clicked in one of out _TabWidgets """ self.tab_close_request.emit(w) def setCurrentWidget(self, w): """ Make the given widget current. """ tw, tidx = self._tab_widget(w) if tw is not None: self._set_current_tab(tw, tidx) def setActiveIcon(self, w, icon=None): """ Set the active icon on a widget. """ tw, tidx = self._tab_widget(w) if tw is not None: if icon is None: icon = tw.active_icon() tw.setTabIcon(tidx, icon) def setTabTextColor(self, w, color=None): """ Set the tab text color on a particular widget w """ tw, tidx = self._tab_widget(w) if tw is not None: if color is None: # null color reverts to foreground role color color = QtGui.QColor() tw.tabBar().setTabTextColor(tidx, color) def setWidgetTitle(self, w, title): """ Set the title for the given widget. """ tw, idx = self._tab_widget(w) if tw is not None: tw.setTabText(idx, title) def _tab_widget(self, w): """ Return the tab widget and index containing the given widget. """ for tw in self.findChildren(_TabWidget, None): idx = tw.indexOf(w) if idx >= 0: return (tw, idx) return (None, None) def _set_current_tab(self, tw, tidx): """ Set the new current tab. """ # Handle the trivial case. if self._current_tab_w is tw and self._current_tab_idx == tidx: return if tw is not None: tw.setCurrentIndex(tidx) # Save the new current widget. self._current_tab_w = tw self._current_tab_idx = tidx def _set_focus(self): """ Set the focus to an appropriate widget in the current tab. """ # Only try and change the focus if the current focus isn't already a # child of the widget. w = self._current_tab_w.widget(self._current_tab_idx) fw = self.window().focusWidget() if fw is not None and not w.isAncestorOf(fw): # Find a widget to focus using the same method as # QStackedLayout::setCurrentIndex(). First try the last widget # with the focus. nfw = w.focusWidget() if nfw is None: # Next, try the first child widget in the focus chain. nfw = fw.nextInFocusChain() while nfw is not fw: if nfw.focusPolicy() & QtCore.Qt.TabFocus and \ nfw.focusProxy() is None and \ nfw.isVisibleTo(w) and \ nfw.isEnabled() and \ w.isAncestorOf(nfw): break nfw = nfw.nextInFocusChain() else: # Fallback to the tab page widget. nfw = w nfw.setFocus() def _focus_changed(self, old, new): """ Handle a change in focus that affects the current tab. """ # It is possible for the C++ layer of this object to be deleted between # the time when the focus change signal is emitted and time when the # slots are dispatched by the Qt event loop. This may be a bug in PyQt4. if qt_api == 'pyqt': import sip if sip.isdeleted(self): return if self._repeat_focus_changes: self.emit(QtCore.SIGNAL('focusChanged(QWidget *,QWidget *)'), old, new) if new is None: return elif isinstance(new, _DragableTabBar): ntw = new.parent() ntidx = ntw.currentIndex() else: ntw, ntidx = self._tab_widget_of(new) if ntw is not None: self._set_current_tab(ntw, ntidx) # See if the widget that has lost the focus is ours. otw, _ = self._tab_widget_of(old) if otw is not None or ntw is not None: if ntw is None: nw = None else: nw = ntw.widget(ntidx) self.emit(QtCore.SIGNAL('hasFocus'), nw) def _tab_widget_of(self, target): """ Return the tab widget and index of the widget that contains the given widget. """ for tw in self.findChildren(_TabWidget, None): for tidx in range(tw.count()): w = tw.widget(tidx) if w is not None and w.isAncestorOf(target): return (tw, tidx) return (None, None) def _move_left(self, tw, tidx): """ Move the current tab to the one logically to the left. """ tidx -= 1 if tidx < 0: # Find the tab widget logically to the left. twlist = self.findChildren(_TabWidget, None) i = twlist.index(tw) i -= 1 if i < 0: i = len(twlist) - 1 tw = twlist[i] # Move the to right most tab. tidx = tw.count() - 1 self._set_current_tab(tw, tidx) tw.setFocus() def _move_right(self, tw, tidx): """ Move the current tab to the one logically to the right. """ tidx += 1 if tidx >= tw.count(): # Find the tab widget logically to the right. twlist = self.findChildren(_TabWidget, None) i = twlist.index(tw) i += 1 if i >= len(twlist): i = 0 tw = twlist[i] # Move the to left most tab. tidx = 0 self._set_current_tab(tw, tidx) tw.setFocus() def _select(self, pos): tw, hs, hs_geom = self._hotspot(pos) # See if the hotspot has changed. if self._selected_tab_widget is not tw or self._selected_hotspot != hs: if self._selected_tab_widget is not None: self._rband.hide() if tw is not None and hs != self._HS_NONE: if self._rband: self._rband.deleteLater() position = QtCore.QPoint(*hs_geom[0:2]) window = tw.window() self._rband = QtGui.QRubberBand(QtGui.QRubberBand.Rectangle, window) self._rband.move(window.mapFromGlobal(position)) self._rband.resize(*hs_geom[2:4]) self._rband.show() self._selected_tab_widget = tw self._selected_hotspot = hs def _drop(self, pos, stab_w, stab): self._rband.hide() # Get the destination locations. dtab_w = self._selected_tab_widget dhs = self._selected_hotspot if dhs == self._HS_NONE: return elif dhs != self._HS_OUTSIDE: dsplit_w = dtab_w.parent() while not isinstance(dsplit_w, SplitTabWidget): dsplit_w = dsplit_w.parent() self._selected_tab_widget = None self._selected_hotspot = self._HS_NONE # See if the tab is being moved to a new window. if dhs == self._HS_OUTSIDE: # Disable tab tear-out for now. It works, but this is something that # should be turned on manually. We need an interface for this. #ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) #self.new_window_request.emit(pos, twidg) return # See if the tab is being moved to an existing tab widget. if dhs >= 0 or dhs == self._HS_AFTER_LAST_TAB: # Make sure it really is being moved. if stab_w is dtab_w: if stab == dhs: return if dhs == self._HS_AFTER_LAST_TAB and stab == stab_w.count()-1: return QtGui.QApplication.instance().blockSignals(True) ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) if dhs == self._HS_AFTER_LAST_TAB: idx = dtab_w.addTab(twidg, ticon, ttext) dtab_w.tabBar().setTabTextColor(idx, ttextcolor) elif dtab_w is stab_w: # Adjust the index if necessary in case the removal of the tab # from its old position has skewed things. dst = dhs if dhs > stab: dst -= 1 idx = dtab_w.insertTab(dst, twidg, ticon, ttext) dtab_w.tabBar().setTabTextColor(idx, ttextcolor) else: idx = dtab_w.insertTab(dhs, twidg, ticon, ttext) dtab_w.tabBar().setTabTextColor(idx, ttextcolor) if (tbuttn): dtab_w.show_button(idx) dsplit_w._set_current_tab(dtab_w, idx) else: # Ignore drops to the same tab widget when it only has one tab. if stab_w is dtab_w and stab_w.count() == 1: return QtGui.QApplication.instance().blockSignals(True) # Remove the tab from its current tab widget and create a new one # for it. ticon, ttext, ttextcolor, tbuttn, twidg = self._remove_tab(stab_w, stab) new_tw = _TabWidget(dsplit_w) idx = new_tw.addTab(twidg, ticon, ttext) new_tw.tabBar().setTabTextColor(0, ttextcolor) if tbuttn: new_tw.show_button(idx) # Get the splitter containing the destination tab widget. dspl = dtab_w.parent() dspl_idx = dspl.indexOf(dtab_w) if dhs in (self._HS_NORTH, self._HS_SOUTH): dspl, dspl_idx = dsplit_w._horizontal_split(dspl, dspl_idx, dhs) else: dspl, dspl_idx = dsplit_w._vertical_split(dspl, dspl_idx, dhs) # Add the new tab widget in the right place. dspl.insertWidget(dspl_idx, new_tw) dsplit_w._set_current_tab(new_tw, 0) dsplit_w._set_focus() # Signal that the tab's SplitTabWidget has changed, if necessary. if dsplit_w != self: self.tab_window_changed.emit(twidg) QtGui.QApplication.instance().blockSignals(False) def _horizontal_split(self, spl, idx, hs): """ Returns a tuple of the splitter and index where the new tab widget should be put. """ if spl.orientation() == QtCore.Qt.Vertical: if hs == self._HS_SOUTH: idx += 1 elif spl is self and spl.count() == 1: # The splitter is the root and only has one child so we can just # change its orientation. spl.setOrientation(QtCore.Qt.Vertical) if hs == self._HS_SOUTH: idx = -1 else: new_spl = QtGui.QSplitter(QtCore.Qt.Vertical) new_spl.addWidget(spl.widget(idx)) spl.insertWidget(idx, new_spl) if hs == self._HS_SOUTH: idx = -1 else: idx = 0 spl = new_spl return (spl, idx) def _vertical_split(self, spl, idx, hs): """ Returns a tuple of the splitter and index where the new tab widget should be put. """ if spl.orientation() == QtCore.Qt.Horizontal: if hs == self._HS_EAST: idx += 1 elif spl is self and spl.count() == 1: # The splitter is the root and only has one child so we can just # change its orientation. spl.setOrientation(QtCore.Qt.Horizontal) if hs == self._HS_EAST: idx = -1 else: new_spl = QtGui.QSplitter(QtCore.Qt.Horizontal) new_spl.addWidget(spl.widget(idx)) spl.insertWidget(idx, new_spl) if hs == self._HS_EAST: idx = -1 else: idx = 0 spl = new_spl return (spl, idx) def _remove_tab(self, tab_w, tab): """ Remove a tab from a tab widget and return a tuple of the icon, label text and the widget so that it can be recreated. """ icon = tab_w.tabIcon(tab) text = tab_w.tabText(tab) text_color = tab_w.tabBar().tabTextColor(tab) button = tab_w.tabBar().tabButton(tab, QtGui.QTabBar.LeftSide) w = tab_w.widget(tab) tab_w.removeTab(tab) return (icon, text, text_color, button, w) def _hotspot(self, pos): """ Return a tuple of the tab widget, hotspot and hostspot geometry (as a tuple) at the given position. """ global_pos = self.mapToGlobal(pos) miss = (None, self._HS_NONE, None) # Get the bounding rect of the cloned QTbarBar. top_widget = QtGui.QApplication.instance().topLevelAt(global_pos) if isinstance(top_widget, QtGui.QTabBar): cloned_rect = top_widget.frameGeometry() else: cloned_rect = None # Determine which visible SplitTabWidget, if any, is under the cursor # (compensating for the cloned QTabBar that may be rendered over it). split_widget = None for top_widget in QtGui.QApplication.instance().topLevelWidgets(): for split_widget in top_widget.findChildren(SplitTabWidget, None): visible_region = split_widget.visibleRegion() widget_pos = split_widget.mapFromGlobal(global_pos) if cloned_rect and split_widget.geometry().contains(widget_pos): visible_rect = visible_region.boundingRect() widget_rect = QtCore.QRect( split_widget.mapFromGlobal(cloned_rect.topLeft()), split_widget.mapFromGlobal(cloned_rect.bottomRight())) if not visible_rect.intersected(widget_rect).isEmpty(): break elif visible_region.contains(widget_pos): break else: split_widget = None if split_widget: break # Handle a drag outside of any split tab widget. if not split_widget: if self.window().frameGeometry().contains(global_pos): return miss else: return (None, self._HS_OUTSIDE, None) # Go through each tab widget. pos = split_widget.mapFromGlobal(global_pos) for tw in split_widget.findChildren(_TabWidget, None): if tw.geometry().contains(tw.parent().mapFrom(split_widget, pos)): break else: return miss # See if the hotspot is in the widget area. widg = tw.currentWidget() if widg is not None: # Get the widget's position relative to its parent. wpos = widg.parent().mapFrom(split_widget, pos) if widg.geometry().contains(wpos): # Get the position of the widget relative to itself (ie. the # top left corner is (0, 0)). p = widg.mapFromParent(wpos) x = p.x() y = p.y() h = widg.height() w = widg.width() # Get the global position of the widget. gpos = widg.mapToGlobal(widg.pos()) gx = gpos.x() gy = gpos.y() # The corners of the widget belong to the north and south # sides. if y < h / 4: return (tw, self._HS_NORTH, (gx, gy, w, h / 4)) if y >= (3 * h) / 4: return (tw, self._HS_SOUTH, (gx, gy + (3*h) / 4, w, h / 4)) if x < w / 4: return (tw, self._HS_WEST, (gx, gy, w / 4, h)) if x >= (3 * w) / 4: return (tw, self._HS_EAST, (gx + (3*w) / 4, gy, w / 4, h)) return miss # See if the hotspot is in the tab area. tpos = tw.mapFrom(split_widget, pos) tab_bar = tw.tabBar() top_bottom = tw.tabPosition() in (QtGui.QTabWidget.North, QtGui.QTabWidget.South) for i in range(tw.count()): rect = tab_bar.tabRect(i) if rect.contains(tpos): w = rect.width() h = rect.height() # Get the global position. gpos = tab_bar.mapToGlobal(rect.topLeft()) gx = gpos.x() gy = gpos.y() if top_bottom: off = pos.x() - rect.x() ext = w gx -= w / 2 else: off = pos.y() - rect.y() ext = h gy -= h / 2 # See if it is in the left (or top) half or the right (or # bottom) half. if off < ext / 2: return (tw, i, (gx, gy, w, h)) if top_bottom: gx += w else: gy += h if i + 1 == tw.count(): return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h)) return (tw, i + 1, (gx, gy, w, h)) else: rect = tab_bar.rect() if rect.contains(tpos): gpos = tab_bar.mapToGlobal(rect.topLeft()) gx = gpos.x() gy = gpos.y() w = rect.width() h = rect.height() if top_bottom: tab_widths = sum(tab_bar.tabRect(i).width() for i in range(tab_bar.count())) w -= tab_widths gx += tab_widths else: tab_heights = sum(tab_bar.tabRect(i).height() for i in range(tab_bar.count())) h -= tab_heights gy -= tab_heights return (tw, self._HS_AFTER_LAST_TAB, (gx, gy, w, h)) return miss active_style = """QTabWidget::pane { /* The tab widget frame */ border: 2px solid #00FF00; } """ inactive_style = """QTabWidget::pane { /* The tab widget frame */ border: 2px solid #C2C7CB; margin: 0px; } """ class _TabWidget(QtGui.QTabWidget): """ The _TabWidget class is a QTabWidget with a dragable tab bar. """ # The active icon. It is created when it is first needed. _active_icon = None _spinner_data = None def __init__(self, root, *args): """ Initialise the instance. """ QtGui.QTabWidget.__init__(self, *args) # XXX this requires Qt > 4.5 if sys.platform == 'darwin': self.setDocumentMode(True) #self.setStyleSheet(inactive_style) self._root = root # We explicitly pass the parent to the tab bar ctor to work round a bug # in PyQt v4.2 and earlier. self.setTabBar(_DragableTabBar(self._root, self)) self.setTabsClosable(True) self.tabCloseRequested.connect(self._close_tab) if not (_TabWidget._spinner_data): _TabWidget._spinner_data = ImageResource('spinner.gif') def show_button(self, index): lbl = QtGui.QLabel(self) movie = QtGui.QMovie(_TabWidget._spinner_data.absolute_path, parent=lbl) movie.setCacheMode(QtGui.QMovie.CacheAll) movie.setScaledSize(QtCore.QSize(16, 16)) lbl.setMovie(movie) movie.start() self.tabBar().setTabButton(index, QtGui.QTabBar.LeftSide, lbl) def hide_button(self, index): curr = self.tabBar().tabButton(index, QtGui.QTabBar.LeftSide) if curr: curr.close() self.tabBar().setTabButton(index, QtGui.QTabBar.LeftSide, None) def active_icon(self): """ Return the QIcon to be used to indicate an active tab page. """ if _TabWidget._active_icon is None: # The gradient start and stop colours. start = QtGui.QColor(0, 255, 0) stop = QtGui.QColor(0, 63, 0) size = self.iconSize() width = size.width() height = size.height() pm = QtGui.QPixmap(size) p = QtGui.QPainter() p.begin(pm) # Fill the image background from the tab background. p.initFrom(self.tabBar()) p.fillRect(0, 0, width, height, p.background()) # Create the colour gradient. rg = QtGui.QRadialGradient(width / 2, height / 2, width) rg.setColorAt(0.0, start) rg.setColorAt(1.0, stop) # Draw the circle. p.setBrush(rg) p.setPen(QtCore.Qt.NoPen) p.setRenderHint(QtGui.QPainter.Antialiasing) p.drawEllipse(0, 0, width, height) p.end() _TabWidget._active_icon = QtGui.QIcon(pm) return _TabWidget._active_icon def _still_needed(self): """ Delete the tab widget (and any relevant parent splitters) if it is no longer needed. """ if self.count() == 0: prune = self parent = prune.parent() # Go up the QSplitter hierarchy until we find one with at least one # sibling. while parent is not self._root and parent.count() == 1: prune = parent parent = prune.parent() prune.hide() prune.deleteLater() def tabRemoved(self, idx): """ Reimplemented to update the record of the current tab if it is removed. """ self._still_needed() if self._root._current_tab_w is self and self._root._current_tab_idx == idx: self._root._current_tab_w = None def _close_tab(self, index): """ Close the current tab. """ self._root._close_tab_request(self.widget(index)) class _IndependentLineEdit(QtGui.QLineEdit): def keyPressEvent(self, e): QtGui.QLineEdit.keyPressEvent(self, e) if (e.key() == QtCore.Qt.Key_Escape): self.hide() class _DragableTabBar(QtGui.QTabBar): """ The _DragableTabBar class is a QTabBar that can be dragged around. """ def __init__(self, root, parent): """ Initialise the instance. """ QtGui.QTabBar.__init__(self, parent) # XXX this requires Qt > 4.5 if sys.platform == 'darwin': self.setDocumentMode(True) self._root = root self._drag_state = None # LineEdit to change tab bar title te = _IndependentLineEdit("", self) te.hide() te.connect(te, QtCore.SIGNAL('editingFinished()'), te, QtCore.SLOT('hide()')) self.connect(te, QtCore.SIGNAL('returnPressed()'), self._setCurrentTabText) self._title_edit = te def resizeEvent(self, e): # resize edit tab if self._title_edit.isVisible(): self._resize_title_edit_to_current_tab() QtGui.QTabBar.resizeEvent(self, e) def keyPressEvent(self, e): """ Reimplemented to handle traversal across different tab widgets. """ if e.key() == QtCore.Qt.Key_Left: self._root._move_left(self.parent(), self.currentIndex()) elif e.key() == QtCore.Qt.Key_Right: self._root._move_right(self.parent(), self.currentIndex()) else: e.ignore() def mouseDoubleClickEvent(self, e): self._resize_title_edit_to_current_tab() te = self._title_edit te.setText(self.tabText(self.currentIndex())[1:]) te.setFocus() te.selectAll() te.show() def mousePressEvent(self, e): """ Reimplemented to handle mouse press events. """ # There is something odd in the focus handling where focus temporarily # moves elsewhere (actually to a View) when switching to a different # tab page. We suppress the notification so that the workbench doesn't # temporarily make the View active. self._root._repeat_focus_changes = False QtGui.QTabBar.mousePressEvent(self, e) self._root._repeat_focus_changes = True # Update the current tab. self._root._set_current_tab(self.parent(), self.currentIndex()) self._root._set_focus() if e.button() != QtCore.Qt.LeftButton: return if self._drag_state is not None: return # Potentially start dragging if the tab under the mouse is the current # one (which will eliminate disabled tabs). tab = self._tab_at(e.pos()) if tab < 0 or tab != self.currentIndex(): return self._drag_state = _DragState(self._root, self, tab, e.pos()) def mouseMoveEvent(self, e): """ Reimplemented to handle mouse move events. """ QtGui.QTabBar.mouseMoveEvent(self, e) if self._drag_state is None: return if self._drag_state.dragging: self._drag_state.drag(e.pos()) else: self._drag_state.start_dragging(e.pos()) # If the mouse has moved far enough that dragging has started then # tell the user. if self._drag_state.dragging: QtGui.QApplication.setOverrideCursor(QtCore.Qt.OpenHandCursor) def mouseReleaseEvent(self, e): """ Reimplemented to handle mouse release events. """ QtGui.QTabBar.mouseReleaseEvent(self, e) if e.button() != QtCore.Qt.LeftButton: if e.button() == QtCore.Qt.MidButton: self.tabCloseRequested.emit(self.tabAt(e.pos())) return if self._drag_state is not None and self._drag_state.dragging: QtGui.QApplication.restoreOverrideCursor() self._drag_state.drop(e.pos()) self._drag_state = None def _tab_at(self, pos): """ Return the index of the tab at the given point. """ for i in range(self.count()): if self.tabRect(i).contains(pos): return i return -1 def _setCurrentTabText(self): idx = self.currentIndex() text = self._title_edit.text() self.setTabText(idx, u'\u25b6'+text) self._root.emit(QtCore.SIGNAL('tabTextChanged(QWidget *, QString)'), self.parent().widget(idx), text) def _resize_title_edit_to_current_tab(self): idx = self.currentIndex() tab = QtGui.QStyleOptionTabV3() self.initStyleOption(tab, idx) rect = self.style().subElementRect(QtGui.QStyle.SE_TabBarTabText, tab) self._title_edit.setGeometry(rect.adjusted(0,8,0,-8)) class _DragState(object): """ The _DragState class handles most of the work when dragging a tab. """ def __init__(self, root, tab_bar, tab, start_pos): """ Initialise the instance. """ self.dragging = False self._root = root self._tab_bar = tab_bar self._tab = tab self._start_pos = QtCore.QPoint(start_pos) self._clone = None def start_dragging(self, pos): """ Start dragging a tab. """ if (pos - self._start_pos).manhattanLength() <= QtGui.QApplication.startDragDistance(): return self.dragging = True # Create a clone of the tab being moved (except for its icon). otb = self._tab_bar tab = self._tab ctb = self._clone = QtGui.QTabBar() if sys.platform == 'darwin' and QtCore.QT_VERSION >= 0x40500: ctb.setDocumentMode(True) ctb.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) ctb.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool | QtCore.Qt.X11BypassWindowManagerHint) ctb.setWindowOpacity(0.5) ctb.setElideMode(otb.elideMode()) ctb.setShape(otb.shape()) ctb.addTab(otb.tabText(tab)) ctb.setTabTextColor(0, otb.tabTextColor(tab)) # The clone offset is the position of the clone relative to the mouse. trect = otb.tabRect(tab) self._clone_offset = trect.topLeft() - pos # The centre offset is the position of the center of the clone relative # to the mouse. The center of the clone determines the hotspot, not # the position of the mouse. self._centre_offset = trect.center() - pos self.drag(pos) ctb.show() def drag(self, pos): """ Handle the movement of the cloned tab during dragging. """ self._clone.move(self._tab_bar.mapToGlobal(pos) + self._clone_offset) self._root._select(self._tab_bar.mapTo(self._root, pos + self._centre_offset)) def drop(self, pos): """ Handle the drop of the cloned tab. """ self.drag(pos) self._clone = None global_pos = self._tab_bar.mapToGlobal(pos) self._root._drop(global_pos, self._tab_bar.parent(), self._tab) self.dragging = False pyface-4.1.0/pyface/ui/qt4/splash_screen.py0000644000175100001440000000543311674463363021565 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Standard library imports. from logging import DEBUG # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Any, Bool, Font, implements, Instance, Int from traits.api import Tuple, Unicode # Local imports. from pyface.i_splash_screen import ISplashScreen, MSplashScreen from pyface.image_resource import ImageResource from window import Window class SplashScreen(MSplashScreen, Window): """ The toolkit specific implementation of a SplashScreen. See the ISplashScreen interface for the API documentation. """ implements(ISplashScreen) #### 'ISplashScreen' interface ############################################ image = Instance(ImageResource, ImageResource('splash')) log_level = Int(DEBUG) show_log_messages = Bool(True) text = Unicode text_color = Any text_font = Any text_location = Tuple(5, 5) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): splash_screen = QtGui.QSplashScreen(self.image.create_image()) self._qt4_show_message(splash_screen) return splash_screen ########################################################################### # Private interface. ########################################################################### def _text_changed(self): """ Called when the splash screen text has been changed. """ if self.control is not None: self._qt4_show_message(self.control) def _qt4_show_message(self, control): """ Set the message text for a splash screen control. """ if self.text_font is not None: control.setFont(self.text_font) if self.text_color is None: text_color = QtCore.Qt.black else: # Until we get the type of this trait finalised (ie. when TraitsUI # supports PyQt) convert it explcitly to a colour. text_color = QtGui.QColor(self.text_color) control.showMessage(self.text, QtCore.Qt.AlignLeft, text_color) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/widget.py0000644000175100001440000000257611674463363020224 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, HasTraits, implements # Local imports. from pyface.i_widget import IWidget, MWidget class Widget(MWidget, HasTraits): """ The toolkit specific implementation of a Widget. See the IWidget interface for the API documentation. """ implements(IWidget) #### 'IWidget' interface ################################################## control = Any parent = Any ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): if self.control is not None: self.control.hide() self.control.deleteLater() self.control = None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/code_editor/0000755000175100001440000000000011674463363020635 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/code_editor/code_widget.py0000644000175100001440000006520011674463363023467 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ # Standard library imports import math import sys # System library imports from pyface.qt import QtCore, QtGui # Local imports from find_widget import FindWidget from gutters import LineNumberWidget, StatusGutterWidget from replace_widget import ReplaceWidget from pygments_highlighter import PygmentsHighlighter class CodeWidget(QtGui.QPlainTextEdit): """ A widget for viewing and editing code. """ ########################################################################### # CodeWidget interface ########################################################################### def __init__(self, parent, should_highlight_current_line=True, font=None, lexer=None): super(CodeWidget, self).__init__(parent) self.highlighter = PygmentsHighlighter(self.document(), lexer) self.line_number_widget = LineNumberWidget(self) self.status_widget = StatusGutterWidget(self) if font is None: # Set a decent fixed width font for this platform. font = QtGui.QFont() if sys.platform == 'win32': # Prefer Consolas, but fall back to Courier if necessary. font.setFamily('Consolas') if not font.exactMatch(): font.setFamily('Courier') elif sys.platform == 'darwin': font.setFamily('Monaco') else: font.setFamily('Monospace') font.setStyleHint(QtGui.QFont.TypeWriter) self.set_font(font) # Whether we should highlight the current line or not. self.should_highlight_current_line = should_highlight_current_line # What that highlight color should be. self.line_highlight_color = QtGui.QColor(QtCore.Qt.yellow).lighter(160) # Auto-indentation behavior self.auto_indent = True self.smart_backspace = True # Tab settings self.tabs_as_spaces = True self.tab_width = 4 self.indent_character = ':' self.comment_character = '#' # Set up gutter widget and current line highlighting self.blockCountChanged.connect(self.update_line_number_width) self.updateRequest.connect(self.update_line_numbers) self.cursorPositionChanged.connect(self.highlight_current_line) self.update_line_number_width() self.highlight_current_line() # Don't wrap text self.setLineWrapMode(QtGui.QPlainTextEdit.NoWrap) # Key bindings self.indent_key = QtGui.QKeySequence(QtCore.Qt.Key_Tab) self.unindent_key = QtGui.QKeySequence(QtCore.Qt.SHIFT + QtCore.Qt.Key_Backtab) self.comment_key = QtGui.QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Slash) self.backspace_key = QtGui.QKeySequence(QtCore.Qt.Key_Backspace) def lines(self): """ Return the number of lines. """ return self.blockCount() def set_line_column(self, line, column): """ Move the cursor to a particular line/column number. These line and column numbers are 1-indexed. """ # Allow the caller to ignore either line or column by passing None. line0, col0 = self.get_line_column() if line is None: line = line0 if column is None: column = col0 line -= 1 column -= 1 block = self.document().findBlockByLineNumber(line) line_start = block.position() position = line_start + column cursor = self.textCursor() cursor.setPosition(position) self.setTextCursor(cursor) def get_line_column(self): """ Get the current line and column numbers. These line and column numbers are 1-indexed. """ cursor = self.textCursor() pos = cursor.position() line = cursor.blockNumber() + 1 line_start = cursor.block().position() column = pos - line_start + 1 return line, column def get_selected_text(self): """ Return the currently selected text. """ return unicode(self.textCursor().selectedText()) def set_font(self, font): """ Set the new QFont. """ self.document().setDefaultFont(font) self.line_number_widget.set_font(font) self.update_line_number_width() def update_line_number_width(self, nblocks=0): """ Update the width of the line number widget. """ left = 0 if not self.line_number_widget.isHidden(): left = self.line_number_widget.digits_width() self.setViewportMargins(left, 0, 0, 0) def update_line_numbers(self, rect, dy): """ Update the line numbers. """ if dy: self.line_number_widget.scroll(0, dy) self.line_number_widget.update( 0, rect.y(), self.line_number_widget.width(), rect.height()) if rect.contains(self.viewport().rect()): self.update_line_number_width() def set_info_lines(self, info_lines): self.status_widget.info_lines = info_lines self.status_widget.update() def set_warn_lines(self, warn_lines): self.status_widget.warn_lines = warn_lines self.status_widget.update() def set_error_lines(self, error_lines): self.status_widget.error_lines = error_lines self.status_widget.update() def highlight_current_line(self): """ Highlight the line with the cursor. """ if self.should_highlight_current_line: selection = QtGui.QTextEdit.ExtraSelection() selection.format.setBackground(self.line_highlight_color) selection.format.setProperty( QtGui.QTextFormat.FullWidthSelection, True) selection.cursor = self.textCursor() selection.cursor.clearSelection() self.setExtraSelections([selection]) def autoindent_newline(self): tab = '\t' if self.tabs_as_spaces: tab = ' '*self.tab_width cursor = self.textCursor() text = cursor.block().text() trimmed = text.rstrip() current_indent_pos = self._get_indent_position(text) cursor.beginEditBlock() # Create the new line. There is no need to move to the new block, as # the insertBlock will do that automatically cursor.insertBlock() # Remove any leading whitespace from the current line after = cursor.block().text() trimmed_after = after.rstrip() pos = after.index(trimmed_after) for i in range(pos): cursor.deleteChar() if self.indent_character and trimmed.endswith(self.indent_character): # indent one level indent = text[:current_indent_pos] + tab else: # indent to the same level indent = text[:current_indent_pos] cursor.insertText(indent) cursor.endEditBlock() self.ensureCursorVisible() def block_indent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Insert a tabulator self.line_indent(cursor) else: # Indent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_indent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_unindent(self): cursor = self.textCursor() if not cursor.hasSelection(): # Unindent current line position = cursor.position() cursor.beginEditBlock() removed = self.line_unindent(cursor) position = max(position-removed, 0) cursor.endEditBlock() cursor.setPosition(position) self.setTextCursor(cursor) else: # Unindent every selected line sel_blocks = self._get_selected_blocks() cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) self.line_unindent(cursor) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def block_comment(self): """the comment char will be placed at the first non-whitespace char of the first line. For example: if foo: bar will be commented as: #if foo: # bar """ cursor = self.textCursor() if not cursor.hasSelection(): text = cursor.block().text() current_indent_pos = self._get_indent_position(text) if text[current_indent_pos] == self.comment_character: self.line_uncomment(cursor, current_indent_pos) else: self.line_comment(cursor, current_indent_pos) else: sel_blocks = self._get_selected_blocks() text = sel_blocks[0].text() indent_pos = self._get_indent_position(text) comment = True for block in sel_blocks: text = block.text() if len(text) > indent_pos and \ text[indent_pos] == self.comment_character: # Already commented. comment = False break cursor.clearSelection() cursor.beginEditBlock() for block in sel_blocks: cursor.setPosition(block.position()) if comment: if block.length() < indent_pos: cursor.insertText(' ' * indent_pos) self.line_comment(cursor, indent_pos) else: self.line_uncomment(cursor, indent_pos) cursor.endEditBlock() self._show_selected_blocks(sel_blocks) def line_comment(self, cursor, position): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.Right, QtGui.QTextCursor.MoveAnchor, position) cursor.insertText(self.comment_character) def line_uncomment(self, cursor, position=0): cursor.movePosition(QtGui.QTextCursor.StartOfBlock) text = cursor.block().text() new_text = text[:position] + text[position+1:] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) def line_indent(self, cursor): tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.insertText(tab) def line_unindent(self, cursor): """ Unindents the cursor's line. Returns the number of characters removed. """ tab = '\t' if self.tabs_as_spaces: tab = ' ' cursor.movePosition(QtGui.QTextCursor.StartOfBlock) if cursor.block().text().startswith(tab): new_text = cursor.block().text()[len(tab):] cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) cursor.removeSelectedText() cursor.insertText(new_text) return len(tab) else: return 0 def word_under_cursor(self): """ Return the word under the cursor. """ cursor = self.textCursor() cursor.select(QtGui.QTextCursor.WordUnderCursor) return unicode(cursor.selectedText()) ########################################################################### # QWidget interface ########################################################################### # FIXME: This is a quick hack to be able to access the keyPressEvent # from the rest editor. This should be changed to work within the traits # framework. def keyPressEvent_action(self, event): pass def keyPressEvent(self, event): key_sequence = QtGui.QKeySequence(event.key() + int(event.modifiers())) self.keyPressEvent_action(event) # FIXME: see above # If the cursor is in the middle of the first line, pressing the "up" # key causes the cursor to go to the start of the first line, i.e. the # beginning of the document. Likewise, if the cursor is somewhere in the # last line, the "down" key causes it to go to the end. cursor = self.textCursor() if key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Up)): cursor.movePosition(QtGui.QTextCursor.StartOfLine) if cursor.atStart(): self.setTextCursor(cursor) event.accept() elif key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Down)): cursor.movePosition(QtGui.QTextCursor.EndOfLine) if cursor.atEnd(): self.setTextCursor(cursor) event.accept() elif self.auto_indent and \ key_sequence.matches(QtGui.QKeySequence(QtCore.Qt.Key_Return)): event.accept() return self.autoindent_newline() elif key_sequence.matches(self.indent_key): event.accept() return self.block_indent() elif key_sequence.matches(self.unindent_key): event.accept() return self.block_unindent() elif key_sequence.matches(self.comment_key): event.accept() return self.block_comment() elif self.auto_indent and self.smart_backspace and \ key_sequence.matches(self.backspace_key) and \ self._backspace_should_unindent(): event.accept() return self.block_unindent() return super(CodeWidget, self).keyPressEvent(event) def resizeEvent(self, event): QtGui.QPlainTextEdit.resizeEvent(self, event) contents = self.contentsRect() self.line_number_widget.setGeometry(QtCore.QRect(contents.left(), contents.top(), self.line_number_widget.digits_width(), contents.height())) # use the viewport width to determine the right edge. This allows for # the propper placement w/ and w/o the scrollbar right_pos = self.viewport().width() + self.line_number_widget.width() + 1\ - self.status_widget.sizeHint().width() self.status_widget.setGeometry(QtCore.QRect(right_pos, contents.top(), self.status_widget.sizeHint().width(), contents.height())) def sizeHint(self): # Suggest a size that is 80 characters wide and 40 lines tall. style = self.style() opt = QtGui.QStyleOptionHeader() font_metrics = QtGui.QFontMetrics(self.document().defaultFont()) width = font_metrics.width(' ') * 80 width += self.line_number_widget.sizeHint().width() width += self.status_widget.sizeHint().width() width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent, opt, self) height = font_metrics.height() * 40 return QtCore.QSize(width, height) ########################################################################### # Private methods ########################################################################### def _get_indent_position(self, line): trimmed = line.rstrip() if len(trimmed) != 0: return line.index(trimmed) else: # if line is all spaces, treat it as the indent position return len(line) def _show_selected_blocks(self, selected_blocks): """ Assumes contiguous blocks """ cursor = self.textCursor() cursor.clearSelection() cursor.setPosition(selected_blocks[0].position()) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) cursor.movePosition(QtGui.QTextCursor.NextBlock, QtGui.QTextCursor.KeepAnchor, len(selected_blocks)) cursor.movePosition(QtGui.QTextCursor.EndOfBlock, QtGui.QTextCursor.KeepAnchor) self.setTextCursor(cursor) def _get_selected_blocks(self): cursor = self.textCursor() if cursor.position() > cursor.anchor(): move_op = QtGui.QTextCursor.PreviousBlock start_pos = cursor.anchor() end_pos = cursor.position() else: move_op = QtGui.QTextCursor.NextBlock start_pos = cursor.position() end_pos = cursor.anchor() cursor.setPosition(start_pos) cursor.movePosition(QtGui.QTextCursor.StartOfBlock) blocks = [cursor.block()] while cursor.movePosition(QtGui.QTextCursor.NextBlock): block = cursor.block() if block.position() < end_pos: blocks.append(block) return blocks def _backspace_should_unindent(self): cursor = self.textCursor() # Don't unindent if we have a selection. if cursor.hasSelection(): return False column = cursor.columnNumber() # Don't unindent if we are at the beggining of the line if column < self.tab_width: return False else: # Unindent if we are at the indent position return column == self._get_indent_position(cursor.block().text()) class AdvancedCodeWidget(QtGui.QWidget): """ Advanced widget for viewing and editing code, with support for search & replace """ ########################################################################### # AdvancedCodeWidget interface ########################################################################### def __init__(self, parent, font=None, lexer=None): super(AdvancedCodeWidget, self).__init__(parent) self.code = CodeWidget(self, font=font, lexer=lexer) self.find = FindWidget(self) self.find.hide() self.replace = ReplaceWidget(self) self.replace.hide() self.replace.replace_button.setEnabled(False) self.replace.replace_all_button.setEnabled(False) self.active_find_widget = None self.previous_find_widget = None self.code.selectionChanged.connect(self._update_replace_enabled) self.find.line_edit.returnPressed.connect(self.find_next) self.find.next_button.clicked.connect(self.find_next) self.find.prev_button.clicked.connect(self.find_prev) self.replace.line_edit.returnPressed.connect(self.find_next) self.replace.line_edit.textChanged.connect( self._update_replace_all_enabled) self.replace.next_button.clicked.connect(self.find_next) self.replace.prev_button.clicked.connect(self.find_prev) self.replace.replace_button.clicked.connect(self.replace_next) self.replace.replace_all_button.clicked.connect(self.replace_all) layout = QtGui.QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self.code) layout.addWidget(self.find) layout.addWidget(self.replace) self.setLayout(layout) def lines(self): """ Return the number of lines. """ return self.code.lines() def set_line_column(self, line, column): """ Move the cursor to a particular line/column position. """ self.code.set_line_column(line, column) def get_line_column(self): """ Get the current line and column numbers. """ return self.code.get_line_column() def get_selected_text(self): """ Return the currently selected text. """ return self.code.get_selected_text() def set_info_lines(self, info_lines): self.code.set_info_lines(info_lines) def set_warn_lines(self, warn_lines): self.code.set_warn_lines(warn_lines) def set_error_lines(self, error_lines): self.code.set_error_lines(error_lines) def enable_find(self): self.replace.hide() self.find.show() self.find.setFocus() if (self.active_find_widget == self.replace or (not self.active_find_widget and self.previous_find_widget == self.replace)): self.find.line_edit.setText(self.replace.line_edit.text()) self.find.line_edit.selectAll() self.active_find_widget = self.find def enable_replace(self): self.find.hide() self.replace.show() self.replace.setFocus() if (self.active_find_widget == self.find or (not self.active_find_widget and self.previous_find_widget == self.find)): self.replace.line_edit.setText(self.find.line_edit.text()) self.replace.line_edit.selectAll() self.active_find_widget = self.replace def find_in_document(self, search_text, direction='forward', replace=None): """ Finds the next occurance of the desired text and optionally replaces it. If 'replace' is None, a regular search will be executed, otherwise it will replace the occurance with the value of 'replace'. Returns the number of occurances found (0 or 1) """ if not search_text: return wrap = self.active_find_widget.wrap_action.isChecked() document = self.code.document() find_cursor = None flags = QtGui.QTextDocument.FindFlags(0) if self.active_find_widget.case_action.isChecked(): flags |= QtGui.QTextDocument.FindCaseSensitively if self.active_find_widget.word_action.isChecked(): flags |= QtGui.QTextDocument.FindWholeWords if direction == 'backward': flags |= QtGui.QTextDocument.FindBackward find_cursor = document.find(search_text, self.code.textCursor(), flags) if find_cursor.isNull() and wrap: if direction == 'backward': find_cursor = document.find(search_text, document.characterCount()-1, flags) else: find_cursor = document.find(search_text, 0, flags) if not find_cursor.isNull(): if replace is not None: find_cursor.beginEditBlock() find_cursor.removeSelectedText() find_cursor.insertText(replace) find_cursor.endEditBlock() find_cursor.movePosition( QtGui.QTextCursor.Left, QtGui.QTextCursor.MoveAnchor,len(replace)) find_cursor.movePosition( QtGui.QTextCursor.Right, QtGui.QTextCursor.KeepAnchor,len(replace)) self.code.setTextCursor(find_cursor) else: self.code.setTextCursor(find_cursor) return find_cursor else: #else not found: beep or indicate? return None def find_next(self): if not self.active_find_widget: self.enable_find() search_text = unicode(self.active_find_widget.line_edit.text()) cursor = self.find_in_document(search_text=search_text) if cursor: return 1 return 0 def find_prev(self): if not self.active_find_widget: self.enable_find() search_text = unicode(self.active_find_widget.line_edit.text()) cursor = self.find_in_document(search_text=search_text, direction='backward') if cursor: return 1 return 0 def replace_next(self): search_text = self.replace.line_edit.text() replace_text = self.replace.replace_edit.text() cursor = self.code.textCursor() if cursor.selectedText() == search_text: cursor.beginEditBlock() cursor.removeSelectedText() cursor.insertText(replace_text) cursor.endEditBlock() return self.find_next() return 0 def replace_all(self): search_text = unicode(self.replace.line_edit.text()) replace_text = unicode(self.replace.replace_edit.text()) count = 0 cursor = self.code.textCursor() cursor.beginEditBlock() while self.find_in_document(search_text=search_text, replace=replace_text) != None: count += 1 cursor.endEditBlock() return count def print_(self, printer): """ Convenience method to call 'print_' on the CodeWidget. """ self.code.print_(printer) def ensureCursorVisible(self): self.code.ensureCursorVisible() def centerCursor(self): self.code.centerCursor() ########################################################################### # QWidget interface ########################################################################### def keyPressEvent(self, event): key_sequence = QtGui.QKeySequence(event.key() + int(event.modifiers())) if key_sequence.matches(QtGui.QKeySequence.Find): self.enable_find() elif key_sequence.matches(QtGui.QKeySequence.Replace): self.enable_replace() elif key_sequence.matches(QtCore.Qt.Key_Escape): if self.active_find_widget: self.find.hide() self.replace.hide() self.code.setFocus() self.previous_find_widget = self.active_find_widget self.active_find_widget = None return super(AdvancedCodeWidget, self).keyPressEvent(event) ########################################################################### # Private methods ########################################################################### def _update_replace_enabled(self): selection = self.code.textCursor().selectedText() find_text = self.replace.line_edit.text() self.replace.replace_button.setEnabled(selection == find_text) def _update_replace_all_enabled(self, text): self.replace.replace_all_button.setEnabled(len(text)) if __name__ == '__main__': def set_trace(): from PyQt4.QtCore import pyqtRemoveInputHook pyqtRemoveInputHook() import pdb pdb.Pdb().set_trace(sys._getframe().f_back) import sys app = QtGui.QApplication(sys.argv) window = AdvancedCodeWidget(None) if len(sys.argv) > 1: f = open(sys.argv[1], 'r') window.code.setPlainText(f.read()) window.code.set_info_lines([3,4,8]) window.resize(640, 640) window.show() sys.exit(app.exec_()) pyface-4.1.0/pyface/ui/qt4/code_editor/find_widget.py0000644000175100001440000000475111674463363023501 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ import weakref from pyface.qt import QtGui, QtCore class FindWidget(QtGui.QWidget): def __init__(self, parent): super(FindWidget, self).__init__(parent) self.adv_code_widget = weakref.ref(parent) self.button_size = self.fontMetrics().width(u'Replace All') + 20 form_layout = QtGui.QFormLayout() form_layout.addRow('Fin&d', self._create_find_control()) layout = QtGui.QHBoxLayout() layout.addLayout(form_layout) close_button = QtGui.QPushButton('Close') layout.addWidget(close_button, 1, QtCore.Qt.AlignRight) close_button.clicked.connect(self.hide) self.setLayout(layout) def setFocus(self): self.line_edit.setFocus() def _create_find_control(self): control = QtGui.QWidget(self) self.line_edit = QtGui.QLineEdit() self.next_button = QtGui.QPushButton('&Next') self.next_button.setFixedWidth(self.button_size) self.prev_button = QtGui.QPushButton('&Prev') self.prev_button.setFixedWidth(self.button_size) self.options_button = QtGui.QPushButton('&Options') self.options_button.setFixedWidth(self.button_size) options_menu = QtGui.QMenu(self) self.case_action = QtGui.QAction('Match &case', options_menu) self.case_action.setCheckable(True) self.word_action = QtGui.QAction('Match words', options_menu) self.word_action.setCheckable(True) self.wrap_action = QtGui.QAction('Wrap search', options_menu) self.wrap_action.setCheckable(True) self.wrap_action.setChecked(True) options_menu.addAction(self.case_action) options_menu.addAction(self.word_action) options_menu.addAction(self.wrap_action) self.options_button.setMenu(options_menu) layout = QtGui.QHBoxLayout() layout.addWidget(self.line_edit) layout.addWidget(self.next_button) layout.addWidget(self.prev_button) layout.addWidget(self.options_button) layout.addStretch(2) layout.setContentsMargins(0, 0, 0, 0) control.setLayout(layout) return control pyface-4.1.0/pyface/ui/qt4/code_editor/gutters.py0000644000175100001440000000735411674463363022715 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ import math from pyface.qt import QtCore, QtGui class GutterWidget(QtGui.QWidget): min_width = 5 background_color = QtGui.QColor(220, 220, 220) def sizeHint(self): return QtCore.QSize(self.min_width, 0) def paintEvent(self, event): """ Paint the line numbers. """ painter = QtGui.QPainter(self) painter.fillRect(event.rect(), QtCore.Qt.lightGray) def wheelEvent(self, event): """ Delegate mouse wheel events to parent for seamless scrolling. """ self.parent().wheelEvent(event) class StatusGutterWidget(GutterWidget): """ Draws status markers """ def __init__(self, *args, **kw): super(StatusGutterWidget, self).__init__(*args, **kw) self.error_lines = [] self.warn_lines = [] self.info_lines = [] def sizeHint(self): return QtCore.QSize(10, 0) def paintEvent(self, event): """ Paint the line numbers. """ painter = QtGui.QPainter(self) painter.fillRect(event.rect(), self.background_color) cw = self.parent() pixels_per_block = self.height()/float(cw.blockCount()) for line in self.info_lines: painter.fillRect(QtCore.QRect(0, line*pixels_per_block, self.width(), 3), QtCore.Qt.green) for line in self.warn_lines: painter.fillRect(QtCore.QRect(0, line*pixels_per_block, self.width(), 3), QtCore.Qt.yellow) for line in self.error_lines: painter.fillRect(QtCore.QRect(0, line*pixels_per_block, self.width(), 3), QtCore.Qt.red) class LineNumberWidget(GutterWidget): """ Draw line numbers. """ min_char_width = 4 def fontMetrics(self): # QWidget's fontMetrics method does not provide an up to date # font metrics, just one corresponding to the initial font return QtGui.QFontMetrics(self.font) def set_font(self, font): self.font = font def digits_width(self): nlines = max(1, self.parent().blockCount()) ndigits = max(self.min_char_width, int(math.floor(math.log10(nlines) + 1))) width = max(self.fontMetrics().width(u'0' * ndigits) + 3, self.min_width) return width def sizeHint(self): return QtCore.QSize(self.digits_width(), 0) def paintEvent(self, event): """ Paint the line numbers. """ painter = QtGui.QPainter(self) painter.setFont(self.font) painter.fillRect(event.rect(), self.background_color) cw = self.parent() block = cw.firstVisibleBlock() blocknum = block.blockNumber() top = cw.blockBoundingGeometry(block).translated( cw.contentOffset()).top() bottom = top + int(cw.blockBoundingRect(block).height()) while block.isValid() and top <= event.rect().bottom(): if block.isVisible() and bottom >= event.rect().top(): painter.setPen(QtCore.Qt.black) painter.drawText(0, top, self.width() - 2, self.fontMetrics().height(), QtCore.Qt.AlignRight, str(blocknum + 1)) block = block.next() top = bottom bottom = top + int(cw.blockBoundingRect(block).height()) blocknum += 1 pyface-4.1.0/pyface/ui/qt4/code_editor/replace_widget.py0000644000175100001440000000346411674463363024174 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ import weakref from pyface.qt import QtGui, QtCore from find_widget import FindWidget class ReplaceWidget(FindWidget): def __init__(self, parent): super(FindWidget, self).__init__(parent) self.adv_code_widget = weakref.ref(parent) self.button_size = self.fontMetrics().width(u'Replace All') + 20 form_layout = QtGui.QFormLayout() form_layout.addRow('Fin&d', self._create_find_control()) form_layout.addRow('Rep&lace', self._create_replace_control()) layout = QtGui.QHBoxLayout() layout.addLayout(form_layout) close_button = QtGui.QPushButton('Close') layout.addWidget(close_button, 1, QtCore.Qt.AlignRight) close_button.clicked.connect(self.hide) self.setLayout(layout) def _create_replace_control(self): control = QtGui.QWidget(self) self.replace_edit = QtGui.QLineEdit() self.replace_button = QtGui.QPushButton('&Replace') self.replace_button.setFixedWidth(self.button_size) self.replace_all_button = QtGui.QPushButton('Replace &All') self.replace_all_button.setFixedWidth(self.button_size) layout = QtGui.QHBoxLayout() layout.addWidget(self.replace_edit) layout.addWidget(self.replace_button) layout.addWidget(self.replace_all_button) layout.addStretch(2) layout.setContentsMargins(0, 0, 0, 0) control.setLayout(layout) return control pyface-4.1.0/pyface/ui/qt4/code_editor/pygments_highlighter.py0000644000175100001440000002060511674463363025436 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ from pyface.qt import QtGui from pygments.lexer import RegexLexer, _TokenType, Text, Error from pygments.lexers import CLexer, CppLexer, PythonLexer, get_lexer_by_name from pygments.styles.default import DefaultStyle from pygments.token import Comment def get_tokens_unprocessed(self, text, stack=('root',)): """ Split ``text`` into (tokentype, text) pairs. Monkeypatched to store the final stack on the object itself. """ pos = 0 tokendefs = self._tokens if hasattr(self, '_epd_state_stack'): statestack = list(self._epd_state_stack) else: statestack = list(stack) statetokens = tokendefs[statestack[-1]] while 1: for rexmatch, action, new_state in statetokens: m = rexmatch(text, pos) if m: if type(action) is _TokenType: yield pos, action, m.group() else: for item in action(self, m): yield item pos = m.end() if new_state is not None: # state transition if isinstance(new_state, tuple): for state in new_state: if state == '#pop': statestack.pop() elif state == '#push': statestack.append(statestack[-1]) else: statestack.append(state) elif isinstance(new_state, int): # pop del statestack[new_state:] elif new_state == '#push': statestack.append(statestack[-1]) else: assert False, "wrong state def: %r" % new_state statetokens = tokendefs[statestack[-1]] break else: try: if text[pos] == '\n': # at EOL, reset state to "root" pos += 1 statestack = ['root'] statetokens = tokendefs['root'] yield pos, Text, u'\n' continue yield pos, Error, text[pos] pos += 1 except IndexError: break self._epd_state_stack = list(statestack) # Monkeypatch! RegexLexer.get_tokens_unprocessed = get_tokens_unprocessed # Even with the above monkey patch to store state, multiline comments do not # work since they are stateless (Pygments uses a single multiline regex for # these comments, but Qt lexes by line). So we need to add a state for comments # to the C and C++ lexers. This means that nested multiline comments will appear # to be valid C/C++, but this is better than the alternative for now. def replace_pattern(tokens, new_pattern): """ Given a RegexLexer token dictionary 'tokens', replace all patterns that match the token specified in 'new_pattern' with 'new_pattern'. """ for state in tokens.values(): for index, pattern in enumerate(state): if isinstance(pattern, tuple) and pattern[1] == new_pattern[1]: state[index] = new_pattern # More monkeypatching! comment_start = (r'/\*', Comment.Multiline, 'comment') comment_state = [ (r'[^*/]', Comment.Multiline), (r'/\*', Comment.Multiline, '#push'), (r'\*/', Comment.Multiline, '#pop'), (r'[*/]', Comment.Multiline) ] replace_pattern(CLexer.tokens, comment_start) replace_pattern(CppLexer.tokens, comment_start) CLexer.tokens['comment'] = comment_state CppLexer.tokens['comment'] = comment_state class BlockUserData(QtGui.QTextBlockUserData): """ Storage for the user data associated with each line. """ syntax_stack = ('root',) def __init__(self, **kwds): QtGui.QTextBlockUserData.__init__(self) for key, value in kwds.iteritems(): setattr(self, key, value) def __repr__(self): attrs = ['syntax_stack'] kwds = ', '.join([ '%s=%r' % (attr, getattr(self, attr)) for attr in attrs ]) return 'BlockUserData(%s)' % kwds class PygmentsHighlighter(QtGui.QSyntaxHighlighter): """ Syntax highlighter that uses Pygments for parsing. """ def __init__(self, parent, lexer=None): super(PygmentsHighlighter, self).__init__(parent) try: self._lexer = get_lexer_by_name(lexer) except: self._lexer = PythonLexer() self._style = DefaultStyle # Caches for formats and brushes. self._brushes = {} self._formats = {} def highlightBlock(self, qstring): """ Highlight a block of text. """ qstring = unicode(qstring) prev_data = self.previous_block_data() if prev_data is not None: self._lexer._epd_state_stack = prev_data.syntax_stack elif hasattr(self._lexer, '_epd_state_stack'): del self._lexer._epd_state_stack index = 0 # Lex the text using Pygments for token, text in self._lexer.get_tokens(qstring): l = len(text) format = self._get_format(token) if format is not None: self.setFormat(index, l, format) index += l if hasattr(self._lexer, '_epd_state_stack'): data = BlockUserData(syntax_stack=self._lexer._epd_state_stack) self.currentBlock().setUserData(data) # there is a bug in pyside and it will crash unless we # hold on to the reference a little longer data = self.currentBlock().userData() # Clean up for the next go-round. del self._lexer._epd_state_stack def previous_block_data(self): """ Convenience method for returning the previous block's user data. """ return self.currentBlock().previous().userData() def _get_format(self, token): """ Returns a QTextCharFormat for token or None. """ if token in self._formats: return self._formats[token] result = None for key, value in self._style.style_for_token(token) .items(): if value: if result is None: result = QtGui.QTextCharFormat() if key == 'color': result.setForeground(self._get_brush(value)) elif key == 'bgcolor': result.setBackground(self._get_brush(value)) elif key == 'bold': result.setFontWeight(QtGui.QFont.Bold) elif key == 'italic': result.setFontItalic(True) elif key == 'underline': result.setUnderlineStyle( QtGui.QTextCharFormat.SingleUnderline) elif key == 'sans': result.setFontStyleHint(QtGui.QFont.SansSerif) elif key == 'roman': result.setFontStyleHint(QtGui.QFont.Times) elif key == 'mono': result.setFontStyleHint(QtGui.QFont.TypeWriter) elif key == 'border': # Borders are normally used for errors. We can't do a border # so instead we do a wavy underline result.setUnderlineStyle( QtGui.QTextCharFormat.WaveUnderline) result.setUnderlineColor(self._get_color(value)) self._formats[token] = result return result def _get_brush(self, color): """ Returns a brush for the color. """ result = self._brushes.get(color) if result is None: qcolor = self._get_color(color) result = QtGui.QBrush(qcolor) self._brushes[color] = result return result def _get_color(self, color): qcolor = QtGui.QColor() qcolor.setRgb(int(color[:2],base=16), int(color[2:4], base=16), int(color[4:6], base=16)) return qcolor pyface-4.1.0/pyface/ui/qt4/code_editor/__init__.py0000644000175100001440000000056711674463363022756 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/qt4/message_dialog.py0000644000175100001440000000443511674463363021700 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import Enum, implements, Unicode # Local imports. from pyface.i_message_dialog import IMessageDialog, MMessageDialog from dialog import Dialog # Map the ETS severity to the corresponding PyQt standard icon. _SEVERITY_TO_ICON_MAP = { 'information': QtGui.QMessageBox.Information, 'warning': QtGui.QMessageBox.Warning, 'error': QtGui.QMessageBox.Critical } class MessageDialog(MMessageDialog, Dialog): """ The toolkit specific implementation of a MessageDialog. See the IMessageDialog interface for the API documentation. """ implements(IMessageDialog) #### 'IMessageDialog' interface ########################################### message = Unicode informative = Unicode detail = Unicode severity = Enum('information', 'warning', 'error') ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In PyQt this is a canned dialog. pass ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): message_box = QtGui.QMessageBox(_SEVERITY_TO_ICON_MAP[self.severity], self.title, self.message, QtGui.QMessageBox.Ok, parent) message_box.setInformativeText(self.informative) message_box.setDetailedText(self.detail) return message_box #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/action/0000755000175100001440000000000011674463363017632 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/action/action_item.py0000644000175100001440000004362511674463363022511 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ The PyQt specific implementations the action manager internal classes. """ # Standard libary imports. from inspect import getargspec # Major package imports. from pyface.qt import QtGui, QtCore # Enthought library imports. from traits.api import Any, Bool, HasTraits # Local imports. from pyface.action.action_event import ActionEvent class _MenuItem(HasTraits): """ A menu item representation of an action item. """ #### '_MenuItem' interface ################################################ # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the menu item is not part of such # a group). group = Any # The toolkit control. control = Any() ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, menu, item, controller): """ Creates a new menu item for an action item. """ self.item = item action = item.action # FIXME v3: This is a wx'ism and should be hidden in the toolkit code. self.control_id = None if action.image is None: self.control = menu.addAction(action.name, self._qt4_on_triggered, action.accelerator) else: self.control = menu.addAction(action.image.create_icon(), action.name, self._qt4_on_triggered, action.accelerator) menu.menu_items.append(self) self.control.setToolTip(action.tooltip) self.control.setWhatsThis(action.description) self.control.setEnabled(action.enabled) self.control.setVisible(action.visible) if getattr(action, 'menu_role', False): if action.menu_role == "About": self.control.setMenuRole(QtGui.QAction.AboutRole) elif action.menu_role == "Preferences": self.control.setMenuRole(QtGui.QAction.PreferencesRole) if action.style == 'toggle': self.control.setCheckable(True) self.control.setChecked(action.checked) elif action.style == 'radio': # Create an action group if it hasn't already been done. try: ag = item.parent._qt4_ag except AttributeError: ag = item.parent._qt4_ag = QtGui.QActionGroup(parent) self.control.setActionGroup(ag) self.control.setCheckable(True) self.control.setChecked(action.checked) # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') action.on_trait_change(self._on_action_name_changed, 'name') action.on_trait_change(self._on_action_accelerator_changed, 'accelerator') # Detect if the control is destroyed. self.control.destroyed.connect(self._qt4_on_destroyed) if controller is not None: self.controller = controller controller.add_to_menu(self) def dispose(self): action = self.item.action action.on_trait_change(self._on_action_enabled_changed, 'enabled', remove=True) action.on_trait_change(self._on_action_visible_changed, 'visible', remove=True) action.on_trait_change(self._on_action_checked_changed, 'checked', remove=True) action.on_trait_change(self._on_action_name_changed, 'name', remove=True) action.on_trait_change(self._on_action_accelerator_changed, 'accelerator', remove=True) ########################################################################### # Private interface. ########################################################################### def _qt4_on_destroyed(self, control=None): """ Delete the reference to the control to avoid attempting to talk to it again. """ self.control = None def _qt4_on_triggered(self): """ Called when the menu item has been clicked. """ action = self.item.action action_event = ActionEvent() is_checkable = action.style in ['radio', 'toggle'] # Perform the action! if self.controller is not None: if is_checkable: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. args, varargs, varkw, dflts = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: if is_checkable: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. args, varargs, varkw, dflts = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(args) == 1: action.perform() else: action.perform(action_event) #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ if self.control is not None: self.control.setEnabled(self.enabled) def _visible_changed(self): """ Called when our 'visible' trait is changed. """ if self.control is not None: self.control.setVisible(self.visible) def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.control is not None: self.control.setChecked(self.checked) def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ if self.control is not None: self.control.setEnabled(action.enabled) def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ if self.control is not None: self.control.setVisible(action.visible) def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if self.control is not None: self.control.setChecked(action.checked) def _on_action_name_changed(self, action, trait_name, old, new): """ Called when the name trait is changed on an action. """ if self.control is not None: self.control.setText(action.name) def _on_action_accelerator_changed(self, action, trait_name, old, new): """ Called when the accelerator trait is changed on an action. """ if self.control is not None: self.control.setShortcut(action.accelerator) class _Tool(HasTraits): """ A tool bar tool representation of an action item. """ #### '_Tool' interface #################################################### # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the tool is not part of such a # group). group = Any # The toolkit control. control = Any() ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, tool_bar, image_cache, item, controller, show_labels): """ Creates a new tool bar tool for an action item. """ self.item = item self.tool_bar = tool_bar action = item.action # FIXME v3: This is a wx'ism and should be hidden in the toolkit code. self.control_id = None if action.image is None: self.control = tool_bar.addAction(action.name) else: size = tool_bar.iconSize() image = action.image.create_icon((size.width(), size.height())) self.control = tool_bar.addAction(image, action.name) QtCore.QObject.connect(self.control, QtCore.SIGNAL('triggered()'), self._qt4_on_triggered) self.control.setToolTip(action.tooltip) self.control.setWhatsThis(action.description) self.control.setEnabled(action.enabled) self.control.setVisible(action.visible) if action.style == 'toggle': self.control.setCheckable(True) self.control.setChecked(action.checked) elif action.style == 'radio': # Create an action group if it hasn't already been done. try: ag = item.parent._qt4_ag except AttributeError: ag = item.parent._qt4_ag = QtGui.QActionGroup(parent) self.control.setActionGroup(ag) self.control.setCheckable(True) self.control.setChecked(action.checked) # Keep a reference in the action. This is done to make sure we live as # long as the action (and still respond to its signals) and don't die # if the manager that created us is garbage collected. self.control._tool_instance = self # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') # Detect if the control is destroyed. self.control.destroyed.connect(self._qt4_on_destroyed) if controller is not None: self.controller = controller controller.add_to_toolbar(self) ########################################################################### # Private interface. ########################################################################### def _qt4_on_destroyed(self, control=None): """ Delete the reference to the control to avoid attempting to talk to it again. """ self.control = None def _qt4_on_triggered(self): """ Called when the tool bar tool is clicked. """ action = self.item.action action_event = ActionEvent() # Perform the action! if self.controller is not None: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. args, varargs, varkw, dflts = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: self.checked = action.checked = self.control.isChecked() # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. args, varargs, varkw, dflts = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(args) == 1: action.perform() else: action.perform(action_event) #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ if self.control is not None: self.control.setEnabled(self.enabled) def _visible_changed(self): """ Called when our 'visible' trait is changed. """ if self.control is not None: self.control.setVisible(self.visible) def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.control is not None: self.control.setChecked(self.checked) def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ if self.control is not None: self.control.setEnabled(action.enabled) def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ if self.control is not None: self.control.setVisible(action.visible) def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if self.control is not None: self.control.setChecked(action.checked) class _PaletteTool(HasTraits): """ A tool palette representation of an action item. """ #### '_PaletteTool' interface ############################################# # The radio group we are part of (None if the tool is not part of such a # group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tool_palette, image_cache, item, show_labels): """ Creates a new tool palette tool for an action item. """ self.item = item self.tool_palette = tool_palette action = self.item.action label = action.name # Tool palette tools never have '...' at the end. if label.endswith('...'): label = label[:-3] # And they never contain shortcuts. label = label.replace('&', '') image = action.image.create_image() path = action.image.absolute_path bmp = image_cache.get_bitmap(path) kind = action.style tooltip = action.tooltip longtip = action.description if not show_labels: label = '' # Add the tool to the tool palette. self.tool_id = tool_palette.add_tool(label, bmp, kind, tooltip,longtip) tool_palette.toggle_tool(self.tool_id, action.checked) tool_palette.enable_tool(self.tool_id, action.enabled) tool_palette.on_tool_event(self.tool_id, self._on_tool) # Listen to the trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_checked_changed, 'checked') return ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ self.tool_palette.enable_tool(self.tool_id, action.enabled) return def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if action.style == 'radio': # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if new: for item in self.item.parent.items: if item is not self.item: item.action.checked = False # This will *not* emit a tool event. self.tool_palette.toggle_tool(self.tool_id, new) return #### Tool palette event handlers ########################################## def _on_tool(self, event): """ Called when the tool palette button is clicked. """ action = self.item.action action_event = ActionEvent() is_checkable = (action.style == 'radio' or action.style == 'check') # Perform the action! action.checked = self.tool_palette.get_tool_state(self.tool_id) == 1 action.perform(action_event) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/action/status_bar_manager.py0000644000175100001440000000771011674463363024052 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import Any, Bool, HasTraits, List, Property, Str, \ Unicode class StatusBarManager(HasTraits): """ A status bar manager realizes itself in a status bar control. """ # The message displayed in the first field of the status bar. message = Property # The messages to be displayed in the status bar fields. messages = List(Unicode) # The toolkit-specific control that represents the status bar. status_bar = Any # Whether to show a size grip on the status bar. size_grip = Bool(False) # Whether the status bar is visible. visible = Bool(True) ########################################################################### # 'StatusBarManager' interface. ########################################################################### def create_status_bar(self, parent): """ Creates a status bar. """ if self.status_bar is None: self.status_bar = QtGui.QStatusBar(parent) self.status_bar.setSizeGripEnabled(self.size_grip) self.status_bar.setVisible(self.visible) if len(self.messages) > 1: self._show_messages() else: self.status_bar.showMessage(self.message) return self.status_bar ########################################################################### # Property handlers. ########################################################################### def _get_message(self): if len(self.messages) > 0: message = self.messages[0] else: message = '' return message def _set_message(self, value): if len(self.messages) > 0: old = self.messages[0] self.messages[0] = value else: old = '' self.messages.append(old) self.trait_property_changed('message', old, value) ########################################################################### # Trait event handlers. ########################################################################### def _messages_changed(self): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: self._show_messages() def _messages_items_changed(self): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: self._show_messages() def _size_grip_changed(self): """ Turns the size grip on the status bar on and off. """ if self.status_bar is not None: self.status_bar.setSizeGripEnabled(self.size_grip) def _visible_changed(self): """ Turns the status bar visibility on and off. """ if self.status_bar is not None: self.status_bar.setVisible(self.visible) ########################################################################### # Private interface. ########################################################################### def _show_messages(self): """ Display the list of messages. """ # FIXME v3: At the moment we just string them together but we may # decide to put all but the first message into separate widgets. We # probably also need to extend the API to allow a "message" to be a # widget - depends on what wx is capable of. self.status_bar.showMessage(" ".join(self.messages)) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/action/menu_bar_manager.py0000644000175100001440000000435711674463363023477 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ The PyQt specific implementation of a menu bar manager. """ # Standard library imports. import sys # Major package imports. from pyface.qt import QtGui # Local imports. from pyface.action.action_manager import ActionManager class MenuBarManager(ActionManager): """ A menu bar manager realizes itself in errr, a menu bar control. """ ########################################################################### # 'MenuBarManager' interface. ########################################################################### def create_menu_bar(self, parent, controller=None): """ Creates a menu bar representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller # Create the menu bar. Work around disappearing menu bars on OS X # (particulary on PySide but also under certain circumstances on PyQt4). if isinstance(parent, QtGui.QMainWindow) and sys.platform == 'darwin': parent.menuBar().setParent(None) menu_bar = parent.menuBar() else: menu_bar = QtGui.QMenuBar(parent) # Every item in every group must be a menu manager. for group in self.groups: for item in group.items: menu = item.create_menu(parent, controller) menu.menuAction().setText(item.name) menu_bar.addMenu(menu) return menu_bar #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/action/__init__.py0000644000175100001440000000010411674463363021736 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/ui/qt4/action/menu_manager.py0000644000175100001440000001511511674463363022645 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ The PyQt specific implementation of a menu manager. """ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Unicode # Local imports. from pyface.action.action_manager import ActionManager from pyface.action.action_manager_item import ActionManagerItem from pyface.action.group import Group class MenuManager(ActionManager, ActionManagerItem): """ A menu manager realizes itself in a menu control. This could be a sub-menu or a context (popup) menu. """ #### 'MenuManager' interface ############################################## # The menu manager's name (if the manager is a sub-menu, this is what its # label will be). name = Unicode ########################################################################### # 'MenuManager' interface. ########################################################################### def create_menu(self, parent, controller=None): """ Creates a menu representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller return _Menu(self, parent, controller) ########################################################################### # 'ActionManagerItem' interface. ########################################################################### def add_to_menu(self, parent, menu, controller): """ Adds the item to a menu. """ submenu = self.create_menu(parent, controller) submenu.menuAction().setText(self.name) menu.addMenu(submenu) def add_to_toolbar(self, parent, tool_bar, image_cache, controller): """ Adds the item to a tool bar. """ raise ValueError("Cannot add a menu manager to a toolbar.") class _Menu(QtGui.QMenu): """ The toolkit-specific menu control. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, manager, parent, controller): """ Creates a new tree. """ # Base class constructor. QtGui.QMenu.__init__(self, parent) # The parent of the menu. self._parent = parent # The manager that the menu is a view of. self._manager = manager # The controller. self._controller = controller # List of menu items self.menu_items = [] # Create the menu structure. self.refresh() # Listen to the manager being updated. self._manager.on_trait_change(self.refresh, 'changed') self._manager.on_trait_change(self._on_enabled_changed, 'enabled') self._manager.on_trait_change(self._on_visible_changed, 'visible') self._manager.on_trait_change(self._on_name_changed, 'name') self.setEnabled(self._manager.enabled) self.menuAction().setVisible(self._manager.visible) return ########################################################################### # '_Menu' interface. ########################################################################### def clear(self): """ Clears the items from the menu. """ for item in self.menu_items: item.dispose() self.menu_items = [] super(_Menu, self).clear() def is_empty(self): """ Is the menu empty? """ return self.isEmpty() def refresh(self): """ Ensures that the menu reflects the state of the manager. """ self.clear() manager = self._manager parent = self._parent previous_non_empty_group = None for group in manager.groups: previous_non_empty_group = self._add_group(parent, group, previous_non_empty_group) self.setEnabled(manager.enabled) def show(self, x=None, y=None): """ Show the menu at the specified location. """ if x is None or y is None: point = QtGui.QCursor.pos() else: point = QtCore.QPoint(x, y) self.popup(point) ########################################################################### # Private interface. ########################################################################### def _on_enabled_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.setEnabled(new) def _on_visible_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.menuAction().setVisible(new) return def _on_name_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.menuAction().setText(new) return def _add_group(self, parent, group, previous_non_empty_group=None): """ Adds a group to a menu. """ if len(group.items) > 0: # Is a separator required? if previous_non_empty_group is not None and group.separator: self.addSeparator() # Create actions and sub-menus for each contribution item in # the group. for item in group.items: if isinstance(item, Group): if len(item.items) > 0: self._add_group(parent, item, previous_non_empty_group) if previous_non_empty_group is not None \ and previous_non_empty_group.separator \ and item.separator: self.addSeparator() previous_non_empty_group = item else: item.add_to_menu(parent, self, self._controller) previous_non_empty_group = group return previous_non_empty_group #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/action/tool_bar_manager.py0000644000175100001440000001363111674463363023503 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, Enum, Instance, Str, Tuple # Local imports. from pyface.image_cache import ImageCache from pyface.action.action_manager import ActionManager class ToolBarManager(ActionManager): """ A tool bar manager realizes itself in errr, a tool bar control. """ #### 'ToolBarManager' interface ########################################### # Is the tool bar enabled? enabled = Bool(True) # Is the tool bar visible? visible = Bool(True) # The size of tool images (width, height). image_size = Tuple((16, 16)) # The toolbar name (used to distinguish multiple toolbars). name = Str('ToolBar') # The orientation of the toolbar. orientation = Enum('horizontal', 'vertical') # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) # Should we display the horizontal divider? show_divider = Bool(True) #### Private interface #################################################### # Cache of tool images (scaled to the appropriate size). _image_cache = Instance(ImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new tool bar manager. """ # Base class contructor. super(ToolBarManager, self).__init__(*args, **traits) # An image cache to make sure that we only load each image used in the # tool bar exactly once. self._image_cache = ImageCache(self.image_size[0], self.image_size[1]) return ########################################################################### # 'ToolBarManager' interface. ########################################################################### def create_tool_bar(self, parent, controller=None): """ Creates a tool bar. """ # If a controller is required it can either be set as a trait on the # tool bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller # Create the control. tool_bar = _ToolBar(self, parent) tool_bar.setObjectName(self.id) tool_bar.setWindowTitle(self.name) if self.show_tool_names: tool_bar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) if self.orientation == 'horizontal': tool_bar.setOrientation(QtCore.Qt.Horizontal) else: tool_bar.setOrientation(QtCore.Qt.Vertical) # We would normally leave it to the current style to determine the icon # size. w, h = self.image_size tool_bar.setIconSize(QtCore.QSize(w, h)) # Add all of items in the manager's groups to the tool bar. self._qt4_add_tools(parent, tool_bar, controller) return tool_bar ########################################################################### # Private interface. ########################################################################### def _qt4_add_tools(self, parent, tool_bar, controller): """ Adds tools for all items in the list of groups. """ previous_non_empty_group = None for group in self.groups: if len(group.items) > 0: # Is a separator required? if previous_non_empty_group is not None and group.separator: tool_bar.addSeparator() previous_non_empty_group = group # Create a tool bar tool for each item in the group. for item in group.items: item.add_to_toolbar( parent, tool_bar, self._image_cache, controller, self.show_tool_names ) return class _ToolBar(QtGui.QToolBar): """ The toolkit-specific tool bar implementation. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tool_bar_manager, parent): """ Constructor. """ QtGui.QToolBar.__init__(self, parent) # Listen for changes to the tool bar manager's enablement and # visibility. self.tool_bar_manager = tool_bar_manager self.tool_bar_manager.on_trait_change( self._on_tool_bar_manager_enabled_changed, 'enabled' ) self.tool_bar_manager.on_trait_change( self._on_tool_bar_manager_visible_changed, 'visible' ) return ########################################################################### # Trait change handlers. ########################################################################### def _on_tool_bar_manager_enabled_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.setEnabled(new) return def _on_tool_bar_manager_visible_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ self.setVisible(new) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/tasks/0000755000175100001440000000000011674463363017502 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/tasks/util.py0000644000175100001440000000272411674463363021036 0ustar ischnellusers00000000000000# System library imports. from pyface.qt import QtCore ############################################################################### # Functions. ############################################################################### def set_focus(control): """ Assign keyboard focus to the given control. Ideally, we would just call ``setFocus()`` on the control and let Qt do the right thing. Unfortunately, this method is implemented in the most naive manner possible, and is essentially a no-op if the toplevel widget does not itself accept focus. We adopt the following procedure: 1. If the control itself accepts focus, use it. This is important since the control may have custom focus dispatching logic. 2. Otherwise, if there is a child widget of the control that previously had focus, use it. 3. Finally, have Qt determine the next item using its internal logic. Qt will only restrict itself to this widget's children if it is a Qt::Window or Qt::SubWindow, hence the hack below. """ if control.focusPolicy() != QtCore.Qt.NoFocus: control.setFocus() else: widget = control.focusWidget() if widget: widget.setFocus() else: flags = control.windowFlags() control.setWindowFlags(flags | QtCore.Qt.SubWindow) try: control.focusNextChild() finally: control.setWindowFlags(flags) pyface-4.1.0/pyface/ui/qt4/tasks/task_window_backend.py0000644000175100001440000001576111674463363024066 0ustar ischnellusers00000000000000# System library imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Instance, List # Local imports. from dock_pane import AREA_MAP, INVERSE_AREA_MAP from main_window_layout import MainWindowLayout from pyface.tasks.i_task_window_backend import MTaskWindowBackend from pyface.tasks.task_layout import PaneItem, TaskLayout # Constants. CORNER_MAP = { 'top_left' : QtCore.Qt.TopLeftCorner, 'top_right' : QtCore.Qt.TopRightCorner, 'bottom_left' : QtCore.Qt.BottomLeftCorner, 'bottom_right' : QtCore.Qt.BottomRightCorner } class TaskWindowBackend(MTaskWindowBackend): """ The toolkit-specific implementation of a TaskWindowBackend. See the ITaskWindowBackend interface for API documentation. """ #### Private interface #################################################### _main_window_layout = Instance(MainWindowLayout) ########################################################################### # 'ITaskWindowBackend' interface. ########################################################################### def create_contents(self, parent): """ Create and return the TaskWindow's contents. """ QtGui.QApplication.instance().focusChanged.connect( self._focus_changed_signal) return QtGui.QStackedWidget(parent) def destroy(self): """ Destroy the backend. """ QtGui.QApplication.instance().focusChanged.disconnect( self._focus_changed_signal) def hide_task(self, state): """ Assuming the specified TaskState is active, hide its controls. """ # Save the task's layout in case it is shown again later. self.window._active_state.layout = self.get_layout() # Now hide its controls. self.control.centralWidget().removeWidget(state.central_pane.control) for dock_pane in state.dock_panes: # Warning: The layout behavior is subtly different (and wrong!) if # the order of these two statement is switched. dock_pane.control.hide() self.control.removeDockWidget(dock_pane.control) def show_task(self, state): """ Assumming no task is currently active, show the controls of the specified TaskState. """ # Show the central pane. self.control.centralWidget().addWidget(state.central_pane.control) # Show the dock panes. self._layout_state(state) # OSX-specific: if there is only a single tool bar, it doesn't matter if # the user can drag it around or not. Therefore, we can combine it with # the title bar, which is idiomatic on the Mac. self.control.setUnifiedTitleAndToolBarOnMac( len(state.tool_bar_managers) <= 1) #### Methods for saving and restoring the layout ########################## def get_layout(self): """ Returns a TaskLayout for the current state of the window. """ # Extract the layout from the main window. layout = TaskLayout(id=self.window._active_state.task.id) self._main_window_layout.state = self.window._active_state self._main_window_layout.get_layout(layout) # Extract the window's corner configuration. for name, corner in CORNER_MAP.iteritems(): area = INVERSE_AREA_MAP[int(self.control.corner(corner))] setattr(layout, name + '_corner', area) return layout def set_layout(self, layout): """ Applies a TaskLayout (which should be suitable for the active task) to the window. """ self.window._active_state.layout = layout self._layout_state(self.window._active_state) ########################################################################### # Private interface. ########################################################################### def _layout_state(self, state): """ Layout the dock panes in the specified TaskState using its TaskLayout. """ # Assign the window's corners to the appropriate dock areas. for name, corner in CORNER_MAP.iteritems(): area = getattr(state.layout, name + '_corner') self.control.setCorner(corner, AREA_MAP[area]) # Add all panes in the TaskLayout. self._main_window_layout.state = state self._main_window_layout.set_layout(state.layout) # Add all panes not assigned an area by the TaskLayout. for dock_pane in state.dock_panes: if dock_pane.control not in self._main_window_layout.consumed: self.control.addDockWidget(AREA_MAP[dock_pane.dock_area], dock_pane.control) # By default, these panes are not visible. However, if a pane # has been explicitly set as visible, honor that setting. if dock_pane.visible: dock_pane.control.show() #### Trait initializers ################################################### def __main_window_layout_default(self): return TaskWindowLayout(control=self.control) #### Signal handlers ###################################################### def _focus_changed_signal(self, old, new): if self.window.active_task: panes = [ self.window.central_pane ] + self.window.dock_panes for pane in panes: if new and pane.control.isAncestorOf(new): pane.has_focus = True elif old and pane.control.isAncestorOf(old): pane.has_focus = False class TaskWindowLayout(MainWindowLayout): """ A MainWindowLayout for a TaskWindow. """ #### 'TaskWindowLayout' interface ######################################### consumed = List state = Instance('pyface.tasks.task_window.TaskState') ########################################################################### # 'MainWindowLayout' interface. ########################################################################### def set_layout(self, layout): """ Applies a DockLayout to the window. """ self.consumed = [] super(TaskWindowLayout, self).set_layout(layout) ########################################################################### # 'MainWindowLayout' abstract interface. ########################################################################### def _get_dock_widget(self, pane): """ Returns the QDockWidget associated with a PaneItem. """ for dock_pane in self.state.dock_panes: if dock_pane.id == pane.id: self.consumed.append(dock_pane.control) return dock_pane.control return None def _get_pane(self, dock_widget): """ Returns a PaneItem for a QDockWidget. """ for dock_pane in self.state.dock_panes: if dock_pane.control == dock_widget: return PaneItem(id=dock_pane.id) return None pyface-4.1.0/pyface/ui/qt4/tasks/editor.py0000644000175100001440000000257611674463363021354 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.tasks.i_editor import IEditor, MEditor from traits.api import Bool, Property, implements # System library imports. from pyface.qt import QtGui class Editor(MEditor): """ The toolkit-specific implementation of a Editor. See the IEditor interface for API documentation. """ implements(IEditor) #### 'IEditor' interface ################################################## has_focus = Property(Bool) ########################################################################### # 'IEditor' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ self.control = QtGui.QWidget(parent) def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ if self.control is not None: self.control.hide() self.control.deleteLater() self.control = None ########################################################################### # Private interface. ########################################################################### def _get_has_focus(self): if self.control is not None: return self.control.hasFocus() return False pyface-4.1.0/pyface/ui/qt4/tasks/editor_area_pane.py0000644000175100001440000002065411674463363023344 0ustar ischnellusers00000000000000# Standard library imports. import sys # Enthought library imports. from pyface.tasks.i_editor_area_pane import IEditorAreaPane, \ MEditorAreaPane from traits.api import implements, on_trait_change # System library imports. from pyface.qt import QtCore, QtGui # Local imports. from task_pane import TaskPane from util import set_focus ############################################################################### # 'EditorAreaPane' class. ############################################################################### class EditorAreaPane(TaskPane, MEditorAreaPane): """ The toolkit-specific implementation of a EditorAreaPane. See the IEditorAreaPane interface for API documentation. """ implements(IEditorAreaPane) ########################################################################### # 'TaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ # Create and configure the tab widget. self.control = control = EditorAreaWidget(self, parent) self._filter = EditorAreaDropFilter(self) control.installEventFilter(self._filter) control.tabBar().setVisible(not self.hide_tab_bar) # Connect to the widget's signals. control.currentChanged.connect(self._update_active_editor) control.tabCloseRequested.connect(self._close_requested) # Add shortcuts for scrolling through tabs. if sys.platform == 'darwin': next_seq = 'Ctrl+}' prev_seq = 'Ctrl+{' elif sys.platform.startswith('linux'): next_seq = 'Ctrl+PgDown' prev_seq = 'Ctrl+PgUp' else: next_seq = 'Alt+n' prev_seq = 'Alt+p' shortcut = QtGui.QShortcut(QtGui.QKeySequence(next_seq), self.control) shortcut.activated.connect(self._next_tab) shortcut = QtGui.QShortcut(QtGui.QKeySequence(prev_seq), self.control) shortcut.activated.connect(self._previous_tab) # Add shortcuts for switching to a specific tab. mod = 'Ctrl+' if sys.platform == 'darwin' else 'Alt+' mapper = QtCore.QSignalMapper(self.control) mapper.mapped.connect(self.control.setCurrentIndex) for i in xrange(1, 10): sequence = QtGui.QKeySequence(mod + str(i)) shortcut = QtGui.QShortcut(sequence, self.control) shortcut.activated.connect(mapper.map) mapper.setMapping(shortcut, i - 1) def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ self.control.removeEventFilter(self._filter) self._filter = None for editor in self.editors: self.remove_editor(editor) super(EditorAreaPane, self).destroy() ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def activate_editor(self, editor): """ Activates the specified editor in the pane. """ self.control.setCurrentWidget(editor.control) def add_editor(self, editor): """ Adds an editor to the pane. """ editor.editor_area = self editor.create(self.control) index = self.control.addTab(editor.control, self._get_label(editor)) self.control.setTabToolTip(index, editor.tooltip) self.editors.append(editor) self._update_tab_bar() # The 'currentChanged' signal, used below, is not emitted when the first # editor is added. if len(self.editors) == 1: self.active_editor = editor def remove_editor(self, editor): """ Removes an editor from the pane. """ self.editors.remove(editor) self.control.removeTab(self.control.indexOf(editor.control)) editor.destroy() editor.editor_area = None self._update_tab_bar() ########################################################################### # Protected interface. ########################################################################### def _get_label(self, editor): """ Return a tab label for an editor. """ label = editor.name if editor.dirty: label = '*' + label return label def _get_editor_with_control(self, control): """ Return the editor with the specified control. """ for editor in self.editors: if editor.control == control: return editor return None def _next_tab(self): """ Activate the tab after the currently active tab. """ self.control.setCurrentIndex(self.control.currentIndex() + 1) def _previous_tab(self): """ Activate the tab before the currently active tab. """ self.control.setCurrentIndex(self.control.currentIndex() - 1) #### Trait change handlers ################################################ @on_trait_change('editors:[dirty, name]') def _update_label(self, editor, name, new): index = self.control.indexOf(editor.control) self.control.setTabText(index, self._get_label(editor)) @on_trait_change('editors:tooltip') def _update_tooltip(self, editor, name, new): index = self.control.indexOf(editor.control) self.control.setTabToolTip(index, editor.tooltip) #### Signal handlers ###################################################### def _close_requested(self, index): control = self.control.widget(index) editor = self._get_editor_with_control(control) editor.close() def _update_active_editor(self): index = self.control.currentIndex() if index == -1: self.active_editor = None else: control = self.control.widget(index) self.active_editor = self._get_editor_with_control(control) @on_trait_change('hide_tab_bar') def _update_tab_bar(self): if self.control is not None: visible = self.control.count() > 1 if self.hide_tab_bar else True self.control.tabBar().setVisible(visible) ############################################################################### # Auxillary classes. ############################################################################### class EditorAreaWidget(QtGui.QTabWidget): """ An auxillary widget for implementing AdvancedEditorAreaPane. """ def __init__(self, editor_area, parent=None): super(EditorAreaWidget, self).__init__(parent) self.editor_area = editor_area # Configure the QTabWidget. self.setAcceptDrops(True) self.setDocumentMode(True) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setFocusProxy(None) self.setMovable(True) self.setTabsClosable(True) self.setUsesScrollButtons(True) def focusInEvent(self, event): """ Assign focus to the active editor, if possible. """ active_editor = self.editor_area.active_editor if active_editor: set_focus(active_editor.control) class EditorAreaDropFilter(QtCore.QObject): """ Implements drag and drop support. """ def __init__(self, editor_area): super(EditorAreaDropFilter, self).__init__() self.editor_area = editor_area def eventFilter(self, object, event): """ Handle drag and drop events with MIME type 'text/uri-list'. """ if event.type() in (QtCore.QEvent.DragEnter, QtCore.QEvent.Drop): # Build list of accepted files. extensions = tuple(self.editor_area.file_drop_extensions) file_paths = [] for url in event.mimeData().urls(): file_path = url.toLocalFile() if file_path.endswith(extensions): file_paths.append(file_path) # Accept the event if we have at least one accepted file. if event.type() == QtCore.QEvent.DragEnter: if file_paths: event.acceptProposedAction() # Dispatch the events. elif event.type() == QtCore.QEvent.Drop: for file_path in file_paths: self.editor_area.file_dropped = file_path return True return super(EditorAreaDropFilter, self).eventFilter(object, event) pyface-4.1.0/pyface/ui/qt4/tasks/advanced_editor_area_pane.py0000644000175100001440000005536111674463363025174 0ustar ischnellusers00000000000000# Standard library imports. import sys # System library imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import DelegatesTo, Instance, implements, on_trait_change # Local imports. from pyface.tasks.i_advanced_editor_area_pane import IAdvancedEditorAreaPane from pyface.tasks.i_editor_area_pane import MEditorAreaPane from editor_area_pane import EditorAreaDropFilter from main_window_layout import MainWindowLayout, PaneItem from task_pane import TaskPane from util import set_focus ############################################################################### # 'AdvancedEditorAreaPane' class. ############################################################################### class AdvancedEditorAreaPane(TaskPane, MEditorAreaPane): """ The toolkit-specific implementation of an AdvancedEditorAreaPane. See the IAdvancedEditorAreaPane interface for API documentation. """ implements(IAdvancedEditorAreaPane) #### Private interface #################################################### _main_window_layout = Instance(MainWindowLayout) ########################################################################### # 'TaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ self.control = control = EditorAreaWidget(self, parent) self._filter = EditorAreaDropFilter(self) self.control.installEventFilter(self._filter) # Add shortcuts for scrolling through tabs. if sys.platform == 'darwin': next_seq = 'Ctrl+}' prev_seq = 'Ctrl+{' elif sys.platform.startswith('linux'): next_seq = 'Ctrl+PgDown' prev_seq = 'Ctrl+PgUp' else: next_seq = 'Alt+n' prev_seq = 'Alt+p' shortcut = QtGui.QShortcut(QtGui.QKeySequence(next_seq), self.control) shortcut.activated.connect(self._next_tab) shortcut = QtGui.QShortcut(QtGui.QKeySequence(prev_seq), self.control) shortcut.activated.connect(self._previous_tab) # Add shortcuts for switching to a specific tab. mod = 'Ctrl+' if sys.platform == 'darwin' else 'Alt+' mapper = QtCore.QSignalMapper(self.control) mapper.mapped.connect(self._activate_tab) for i in xrange(1, 10): sequence = QtGui.QKeySequence(mod + str(i)) shortcut = QtGui.QShortcut(sequence, self.control) shortcut.activated.connect(mapper.map) mapper.setMapping(shortcut, i - 1) def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ self.control.removeEventFilter(self._filter) self._filter = None for editor in self.editors: editor_widget = editor.control.parent() self.control.destroy_editor_widget(editor_widget) editor.editor_area = None super(AdvancedEditorAreaPane, self).destroy() ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def activate_editor(self, editor): """ Activates the specified editor in the pane. """ editor_widget = editor.control.parent() editor_widget.setVisible(True) editor_widget.raise_() editor.control.setFocus() self.active_editor = editor def add_editor(self, editor): """ Adds an editor to the pane. """ editor.editor_area = self editor_widget = EditorWidget(editor, self.control) self.control.add_editor_widget(editor_widget) self.editors.append(editor) def remove_editor(self, editor): """ Removes an editor from the pane. """ editor_widget = editor.control.parent() self.editors.remove(editor) self.control.remove_editor_widget(editor_widget) editor.editor_area = None ########################################################################### # 'IAdvancedEditorAreaPane' interface. ########################################################################### def get_layout(self): """ Returns a LayoutItem that reflects the current state of the editors. """ return self._main_window_layout.get_layout_for_area( QtCore.Qt.LeftDockWidgetArea) def set_layout(self, layout): """ Applies a LayoutItem to the editors in the pane. """ if layout is not None: self._main_window_layout.set_layout_for_area( layout, QtCore.Qt.LeftDockWidgetArea) ########################################################################### # Private interface. ########################################################################### def _activate_tab(self, index): """ Activates the tab with the specified index, if there is one. """ widgets = self.control.get_dock_widgets_ordered() if index < len(widgets): self.activate_editor(widgets[index].editor) def _next_tab(self): """ Activate the tab after the currently active tab. """ if self.active_editor: widgets = self.control.get_dock_widgets_ordered() index = widgets.index(self.active_editor.control.parent()) + 1 if index < len(widgets): self.activate_editor(widgets[index].editor) def _previous_tab(self): """ Activate the tab before the currently active tab. """ if self.active_editor: widgets = self.control.get_dock_widgets_ordered() index = widgets.index(self.active_editor.control.parent()) - 1 if index >= 0: self.activate_editor(widgets[index].editor) def _get_label(self, editor): """ Return a tab label for an editor. """ label = editor.name if editor.dirty: label = '*' + label return label #### Trait initializers ################################################### def __main_window_layout_default(self): return EditorAreaMainWindowLayout(editor_area=self) #### Trait change handlers ################################################ @on_trait_change('editors:[dirty, name]') def _update_label(self, editor, name, new): editor.control.parent().update_title() @on_trait_change('editors:tooltip') def _update_tooltip(self, editor, name, new): editor.control.parent().update_tooltip() ############################################################################### # Auxillary classes. ############################################################################### class EditorAreaMainWindowLayout(MainWindowLayout): """ A MainWindowLayout for implementing AdvancedEditorAreaPane. Used for getting and setting layouts for the pane. """ #### 'MainWindowLayout' interface ######################################### control = DelegatesTo('editor_area') #### 'TaskWindowLayout' interface ######################################### editor_area = Instance(AdvancedEditorAreaPane) ########################################################################### # 'MainWindowLayout' abstract interface. ########################################################################### def _get_dock_widget(self, pane): """ Returns the QDockWidget associated with a PaneItem. """ try: editor = self.editor_area.editors[pane.id] return editor.control.parent() except IndexError: return None def _get_pane(self, dock_widget): """ Returns a PaneItem for a QDockWidget. """ for i, editor in enumerate(self.editor_area.editors): if editor.control == dock_widget.widget(): return PaneItem(id=i) return None class EditorAreaWidget(QtGui.QMainWindow): """ An auxillary widget for implementing AdvancedEditorAreaPane. """ ########################################################################### # 'EditorAreaWidget' interface. ########################################################################### def __init__(self, editor_area, parent=None): super(EditorAreaWidget, self).__init__(parent) self.editor_area = editor_area self.reset_drag() # Fish out the rubber band used by Qt to indicate a drop region. We use # it to determine which dock widget is the hover widget. for child in self.children(): if isinstance(child, QtGui.QRubberBand): child.installEventFilter(self) self._rubber_band = child break # Monitor focus changes so we can set the active editor. QtGui.QApplication.instance().focusChanged.connect(self._focus_changed) # Configure the QMainWindow. # FIXME: Currently animation is not supported. self.setAcceptDrops(True) self.setAnimated(False) self.setDockNestingEnabled(True) self.setDocumentMode(True) self.setFocusPolicy(QtCore.Qt.StrongFocus) self.setTabPosition(QtCore.Qt.AllDockWidgetAreas, QtGui.QTabWidget.North) def add_editor_widget(self, editor_widget): """ Adds a dock widget to the editor area. """ editor_widget.installEventFilter(self) self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, editor_widget) # Try to place the editor in a sensible spot. top_left = None for widget in self.get_dock_widgets(): if top_left is None or (widget.pos().manhattanLength() < top_left.pos().manhattanLength()): top_left = widget if top_left: self.tabifyDockWidget(top_left, editor_widget) top_left.set_title_bar(False) # Qt will not give the dock widget focus by default. self.editor_area.activate_editor(editor_widget.editor) def destroy_editor_widget(self, editor_widget): """ Destroys a dock widget in the editor area. """ editor_widget.hide() editor_widget.removeEventFilter(self) editor_widget.editor.destroy() self.removeDockWidget(editor_widget) def get_dock_widgets(self): """ Gets all visible dock widgets. """ return [ child for child in self.children() if isinstance(child, QtGui.QDockWidget) and child.isVisible() ] def get_dock_widgets_for_bar(self, tab_bar): """ Get the dock widgets, in order, attached to given tab bar. Because QMainWindow locks this info down, we have resorted to a hack. """ pos = tab_bar.pos() key = lambda w: QtGui.QVector2D(pos - w.pos()).lengthSquared() all_widgets = self.get_dock_widgets() if all_widgets: current = min(all_widgets, key=key) widgets = self.tabifiedDockWidgets(current) widgets.insert(tab_bar.currentIndex(), current) return widgets return [] def get_dock_widgets_ordered(self, visible_only=False): """ Gets all dock widgets in left-to-right, top-to-bottom order. """ def compare(one, two): y = cmp(one.pos().y(), two.pos().y()) return cmp(one.pos().x(), two.pos().x()) if y == 0 else y children = [] for child in self.children(): if (child.isWidgetType() and child.isVisible() and ((isinstance(child, QtGui.QTabBar) and not visible_only) or (isinstance(child, QtGui.QDockWidget) and (visible_only or not self.tabifiedDockWidgets(child))))): children.append(child) children.sort(cmp=compare) widgets = [] for child in children: if isinstance(child, QtGui.QTabBar): widgets.extend(self.get_dock_widgets_for_bar(child)) else: widgets.append(child) return widgets def remove_editor_widget(self, editor_widget): """ Removes a dock widget from the editor area. """ # Get the tabs in this editor's dock area before removing it. tabified = self.tabifiedDockWidgets(editor_widget) if tabified: widgets = self.get_dock_widgets_ordered() tabified = [widget for widget in widgets \ if widget in tabified or widget == editor_widget] visible = self.get_dock_widgets_ordered(visible_only=True) # Destroy and remove the editor. Get the active widget first, since it # may be destroyed! next_widget = self.editor_area.active_editor.control.parent() self.destroy_editor_widget(editor_widget) # Ensure that the appropriate editor is activated. editor_area = self.editor_area choices = tabified if len(tabified) >= 2 else visible if len(choices) >= 2 and editor_widget == next_widget: i = choices.index(editor_widget) next_widget = choices[i+1] if i+1 < len(choices) else choices[i-1] editor_area.activate_editor(next_widget.editor) # Update tab bar hide state. if len(tabified) == 2: next_widget.editor.control.parent().set_title_bar(True) if editor_area.hide_tab_bar and len(editor_area.editors) == 1: editor_area.editors[0].control.parent().set_title_bar(False) def reset_drag(self): """ Clear out all drag state. """ self._drag_widget = None self._hover_widget = None self._tear_handled = False self._tear_widgets = [] def set_hover_widget(self, widget): """ Set the dock widget being 'hovered over' during a drag. """ old_widget = self._hover_widget self._hover_widget = widget if old_widget: if old_widget in self._tear_widgets: if len(self._tear_widgets) == 1: old_widget.set_title_bar(True) elif not self.tabifiedDockWidgets(old_widget): old_widget.set_title_bar(True) if widget: if widget in self._tear_widgets: if len(self._tear_widgets) == 1: widget.set_title_bar(False) elif len(self.tabifiedDockWidgets(widget)) == 1: widget.set_title_bar(False) ########################################################################### # Event handlers. ########################################################################### def childEvent(self, event): """ Reimplemented to gain access to the tab bars as they are created. """ super(EditorAreaWidget, self).childEvent(event) if event.polished(): child = event.child() if isinstance(child, QtGui.QTabBar): # Use UniqueConnections since Qt recycles the tab bars. child.installEventFilter(self) child.currentChanged.connect(self._tab_index_changed, QtCore.Qt.UniqueConnection) child.setTabsClosable(True) child.setUsesScrollButtons(True) child.tabCloseRequested.connect(self._tab_close_requested, QtCore.Qt.UniqueConnection) # FIXME: We would like to have the tabs movable, but this # confuses the QMainWindowLayout. For now, we disable this. #child.setMovable(True) def eventFilter(self, obj, event): """ Reimplemented to dispatch to sub-handlers. """ if isinstance(obj, QtGui.QDockWidget): return self._filter_dock_widget(obj, event) elif isinstance(obj, QtGui.QRubberBand): return self._filter_rubber_band(obj, event) elif isinstance(obj, QtGui.QTabBar): return self._filter_tab_bar(obj, event) return False def _filter_dock_widget(self, widget, event): """ Support hover widget state tracking. """ if self._drag_widget and event.type() == QtCore.QEvent.Resize: if widget.geometry() == self._rubber_band.geometry(): self.set_hover_widget(widget) elif self._drag_widget == widget and event.type() == QtCore.QEvent.Move: if len(self._tear_widgets) == 1 and not self._tear_handled: widget = self._tear_widgets[0] widget.set_title_bar(True) self._tear_handled = True elif self._drag_widget == widget and \ event.type() == QtCore.QEvent.MouseButtonRelease: self.reset_drag() return False def _filter_rubber_band(self, rubber_band, event): """ Support hover widget state tracking. """ if self._drag_widget and event.type() in (QtCore.QEvent.Resize, QtCore.QEvent.Move): self.set_hover_widget(None) return False def _filter_tab_bar(self, tab_bar, event): """ Support 'tearing off' a tab. """ if event.type() == QtCore.QEvent.MouseMove: if tab_bar.rect().contains(event.pos()): self.reset_drag() else: if not self._drag_widget: index = tab_bar.currentIndex() self._tear_widgets = self.get_dock_widgets_for_bar(tab_bar) self._drag_widget = widget = self._tear_widgets.pop(index) pos = QtCore.QPoint(0, 0) press_event = QtGui.QMouseEvent( QtCore.QEvent.MouseButtonPress, pos, widget.mapToGlobal(pos), QtCore.Qt.LeftButton, QtCore.Qt.LeftButton, event.modifiers()) QtCore.QCoreApplication.sendEvent(widget, press_event) return True event = QtGui.QMouseEvent( QtCore.QEvent.MouseMove, event.pos(), event.globalPos(), event.button(), event.buttons(), event.modifiers()) QtCore.QCoreApplication.sendEvent(self._drag_widget, event) return True elif event.type() == QtCore.QEvent.ToolTip: # QDockAreaLayout forces the tooltips to be QDockWidget.windowTitle, # so we provide the tooltips manually. widgets = self.get_dock_widgets_for_bar(tab_bar) index = tab_bar.tabAt(event.pos()) tooltip = widgets[index].editor.tooltip if index >= 0 else '' if tooltip: QtGui.QToolTip.showText(event.globalPos(), tooltip, tab_bar) return True return False def focusInEvent(self, event): """ Assign focus to the active editor, if possible. """ active_editor = self.editor_area.active_editor if active_editor: set_focus(active_editor.control) ########################################################################### # Signal handlers. ########################################################################### def _focus_changed(self, old, new): """ Handle an application-level focus change. """ if new is not None: for editor in self.editor_area.editors: control = editor.control if control is not None and control.isAncestorOf(new): self.editor_area.active_editor = focused = editor break else: if not self.editor_area.editors: self.editor_area.active_editor = None def _tab_index_changed(self, index): """ Handle a tab selection. """ widgets = self.get_dock_widgets_for_bar(self.sender()) if index < len(widgets): editor_widget = widgets[index] editor_widget.editor.control.setFocus() def _tab_close_requested(self, index): """ Handle a tab close request. """ editor_widget = self.get_dock_widgets_for_bar(self.sender())[index] editor_widget.editor.close() class EditorWidget(QtGui.QDockWidget): """ An auxillary widget for implementing AdvancedEditorAreaPane. """ def __init__(self, editor, parent=None): super(EditorWidget, self).__init__(parent) self.editor = editor self.editor.create(self) self.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea) self.setFeatures(QtGui.QDockWidget.DockWidgetClosable | QtGui.QDockWidget.DockWidgetMovable) self.setWidget(editor.control) self.update_title() self.dockLocationChanged.connect(self.update_title_bar) self.visibilityChanged.connect(self.update_title_bar) def update_title(self): title = self.editor.editor_area._get_label(self.editor) self.setWindowTitle(title) title_bar = self.titleBarWidget() if isinstance(title_bar, EditorTitleBarWidget): title_bar.setTabText(0, title) def update_tooltip(self): title_bar = self.titleBarWidget() if isinstance(title_bar, EditorTitleBarWidget): title_bar.setTabToolTip(0, self.editor.tooltip) def update_title_bar(self): if self not in self.parent()._tear_widgets: tabbed = self.parent().tabifiedDockWidgets(self) self.set_title_bar(not tabbed) def set_title_bar(self, title_bar): current = self.titleBarWidget() editor_area = self.editor.editor_area if title_bar and editor_area and (not editor_area.hide_tab_bar or len(editor_area.editors) > 1): if not isinstance(current, EditorTitleBarWidget): self.setTitleBarWidget(EditorTitleBarWidget(self)) elif current is None or isinstance(current, EditorTitleBarWidget): self.setTitleBarWidget(QtGui.QWidget()) class EditorTitleBarWidget(QtGui.QTabBar): """ An auxillary widget for implementing AdvancedEditorAreaPane. """ def __init__(self, editor_widget): super(EditorTitleBarWidget, self).__init__(editor_widget) self.addTab(editor_widget.windowTitle()) self.setTabToolTip(0, editor_widget.editor.tooltip) self.setDocumentMode(True) self.setExpanding(False) self.setTabsClosable(True) self.tabCloseRequested.connect(editor_widget.editor.close) def mousePressEvent(self, event): self.parent().parent()._drag_widget = self.parent() event.ignore() def mouseMoveEvent(self, event): event.ignore() def mouseReleaseEvent(self, event): event.ignore() pyface-4.1.0/pyface/ui/qt4/tasks/dock_pane.py0000644000175100001440000001370311674463363022003 0ustar ischnellusers00000000000000# Standard library imports from contextlib import contextmanager # Enthought library imports. from pyface.tasks.i_dock_pane import IDockPane, MDockPane from traits.api import Bool, Property, Tuple, implements, \ on_trait_change # System library imports. from pyface.qt import QtCore, QtGui # Local imports. from task_pane import TaskPane from util import set_focus # Constants. AREA_MAP = { 'left' : QtCore.Qt.LeftDockWidgetArea, 'right' : QtCore.Qt.RightDockWidgetArea, 'top' : QtCore.Qt.TopDockWidgetArea, 'bottom' : QtCore.Qt.BottomDockWidgetArea } INVERSE_AREA_MAP = dict((int(v), k) for k, v in AREA_MAP.iteritems()) class DockPane(TaskPane, MDockPane): """ The toolkit-specific implementation of a DockPane. See the IDockPane interface for API documentation. """ implements(IDockPane) #### 'IDockPane' interface ################################################ size = Property(Tuple) #### Protected traits ##################################################### _receiving = Bool(False) ########################################################################### # 'ITaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the dock widget that contains the pane contents. """ self.control = control = QtGui.QDockWidget(parent) # Set the widget's object name. This important for QMainWindow state # saving. Use the task ID and the pane ID to avoid collisions when a # pane is present in multiple tasks attached to the same window. control.setObjectName(self.task.id + ':' + self.id) # Configure the dock widget according to the DockPane settings. self._set_dock_features() self._set_dock_title() self._set_floating() self._set_visible() # Connect signal handlers for updating DockPane traits. control.dockLocationChanged.connect(self._receive_dock_area) control.topLevelChanged.connect(self._receive_floating) control.visibilityChanged.connect(self._receive_visible) # Add the pane contents to the dock widget. contents = self.create_contents(control) control.setWidget(contents) # For some reason the QDockWidget doesn't respect the minimum size # of its widgets control.setMinimumSize(contents.minimumSize()) # Hide the control by default. Otherwise, the widget will visible in its # parent immediately! control.hide() def set_focus(self): """ Gives focus to the control that represents the pane. """ if self.control is not None: set_focus(self.control.widget()) ########################################################################### # 'IDockPane' interface. ########################################################################### def create_contents(self, parent): """ Create and return the toolkit-specific contents of the dock pane. """ return QtGui.QWidget(parent) ########################################################################### # Protected interface. ########################################################################### @contextmanager def _signal_context(self): """ Defines a context appropriate for Qt signal callbacks. Necessary to prevent feedback between Traits and Qt event handlers. """ original = self._receiving self._receiving = True yield self._receiving = original #### Trait property getters/setters ####################################### def _get_size(self): if self.control is not None: return (self.control.width(), self.control.height()) return (-1, -1) #### Trait change handlers ################################################ @on_trait_change('dock_area') def _set_dock_area(self): if self.control is not None and not self._receiving: # Only attempt to adjust the area if the task is active. main_window = self.task.window.control if main_window and self.task == self.task.window.active_task: # Qt will automatically remove the dock widget from its previous # area, if it had one. main_window.addDockWidget(AREA_MAP[self.dock_area], self.control) @on_trait_change('closable', 'floatable', 'movable') def _set_dock_features(self): if self.control is not None: features = QtGui.QDockWidget.NoDockWidgetFeatures if self.closable: features |= QtGui.QDockWidget.DockWidgetClosable if self.floatable: features |= QtGui.QDockWidget.DockWidgetFloatable if self.movable: features |= QtGui.QDockWidget.DockWidgetMovable self.control.setFeatures(features) @on_trait_change('name') def _set_dock_title(self): if self.control is not None: self.control.setWindowTitle(self.name) @on_trait_change('floating') def _set_floating(self): if self.control is not None and not self._receiving: self.control.setFloating(self.floating) @on_trait_change('visible') def _set_visible(self): if self.control is not None and not self._receiving: self.control.setVisible(self.visible) #### Signal handlers ###################################################### def _receive_dock_area(self, area): with self._signal_context(): self.dock_area = INVERSE_AREA_MAP[int(area)] def _receive_floating(self, floating): with self._signal_context(): self.floating = floating def _receive_visible(self): with self._signal_context(): if self.control is not None: self.visible = self.control.isVisible() pyface-4.1.0/pyface/ui/qt4/tasks/__init__.py0000644000175100001440000000000011674463363021601 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/tasks/main_window_layout.py0000644000175100001440000002745211674463363023776 0ustar ischnellusers00000000000000# Standard library imports. from itertools import combinations import logging # System library imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Any, HasTraits # Local imports. from dock_pane import AREA_MAP from pyface.tasks.task_layout import LayoutContainer, PaneItem, Tabbed, \ Splitter, HSplitter, VSplitter # Contants. ORIENTATION_MAP = { 'horizontal' : QtCore.Qt.Horizontal, 'vertical': QtCore.Qt.Vertical } # Logging. logger = logging.getLogger(__name__) class MainWindowLayout(HasTraits): """ A class for applying declarative layouts to a QMainWindow. """ #### 'MainWindowLayout' interface ######################################### # The QMainWindow control to lay out. control = Any ########################################################################### # 'MainWindowLayout' interface. ########################################################################### def get_layout(self, layout, include_sizes=True): """ Get the layout by adding sublayouts to the specified DockLayout. """ for name, q_dock_area in AREA_MAP.iteritems(): sublayout = self.get_layout_for_area(q_dock_area, include_sizes) setattr(layout, name, sublayout) def get_layout_for_area(self, q_dock_area, include_sizes=True): """ Gets a LayoutItem for the specified dock area. """ # Build the initial set of leaf-level items. items = set() rects = {} for child in self.control.children(): # Iterate through *visibile* dock widgets. (Inactive tabbed dock # widgets are "visible" but have invalid positions.) if isinstance(child, QtGui.QDockWidget) and child.isVisible() and \ self.control.dockWidgetArea(child) == q_dock_area and \ child.x() >= 0 and child.y() >= 0: # Get the list of dock widgets in this tab group in order. geometry = child.geometry() tabs = [ tab for tab in self.control.tabifiedDockWidgets(child) if tab.isVisible() ] if tabs: tab_bar = self._get_tab_bar(child) tab_index = tab_bar.currentIndex() tabs.insert(tab_index, child) geometry = tab_bar.geometry().united(geometry) # Create the leaf-level item for the child. if tabs: panes = [ self._prepare_pane(dock_widget, include_sizes) for dock_widget in tabs ] item = Tabbed(*panes, active_tab=panes[tab_index].id) else: item = self._prepare_pane(child, include_sizes) items.add(item) rects[item] = geometry # Build the layout tree bottom-up, in multiple passes. while len(items) > 1: add, remove = set(), set() for item1, item2 in combinations(items, 2): if item1 not in remove and item2 not in remove: rect1, rect2 = rects[item1], rects[item2] orient = self._get_division_orientation(rect1, rect2, True) if orient == QtCore.Qt.Horizontal: if rect1.y() < rect2.y(): item = VSplitter(item1, item2) else: item = VSplitter(item2, item1) elif orient == QtCore.Qt.Vertical: if rect1.x() < rect2.x(): item = HSplitter(item1, item2) else: item = HSplitter(item2, item1) else: continue rects[item] = rect1.united(rect2) add.add(item) remove.update((item1, item2)) if add or remove: items.update(add) items.difference_update(remove) else: # Raise an exception instead of falling into an infinite loop. raise RuntimeError('Unable to extract layout from QMainWindow.') if items: return items.pop() return None def set_layout(self, layout): """ Applies a DockLayout to the window. """ # Remove all existing dock widgets. for child in self.control.children(): if isinstance(child, QtGui.QDockWidget): child.hide() self.control.removeDockWidget(child) # Perform the layout. This will assign fixed sizes to the dock widgets # to enforce size constraints specified in the PaneItems. for name, q_dock_area in AREA_MAP.iteritems(): sublayout = getattr(layout, name) if sublayout: self.set_layout_for_area(sublayout, q_dock_area, _toplevel_call=False) # Remove the fixed sizes once Qt activates the layout. QtCore.QTimer.singleShot(0, self._reset_fixed_sizes) def set_layout_for_area(self, layout, q_dock_area, _toplevel_added=False, _toplevel_call=True): """ Applies a LayoutItem to the specified dock area. """ # If we try to do the layout bottom-up, Qt will become confused. In # order to do it top-down, we have know which dock widget is # "effectively" top level, requiring us to reach down to the leaves of # the layout. (This is really only an issue for Splitter layouts, since # Tabbed layouts are, for our purposes, leaves.) if isinstance(layout, PaneItem): if not _toplevel_added: widget = self._prepare_toplevel_for_item(layout) if widget: self.control.addDockWidget(q_dock_area, widget) widget.show() elif isinstance(layout, Tabbed): active_widget = first_widget = None for item in layout.items: widget = self._prepare_toplevel_for_item(item) if not widget: continue if item.id == layout.active_tab: active_widget = widget if first_widget: self.control.tabifyDockWidget(first_widget, widget) else: if not _toplevel_added: self.control.addDockWidget(q_dock_area, widget) first_widget = widget widget.show() # Activate the appropriate tab, if possible. if not active_widget: # By default, Qt will activate the last widget. active_widget = first_widget if active_widget: # It seems that the 'raise_' call only has an effect after the # QMainWindow has performed its internal layout. QtCore.QTimer.singleShot(0, active_widget.raise_) elif isinstance(layout, Splitter): # Perform top-level splitting as per above comment. orient = ORIENTATION_MAP[layout.orientation] prev_widget = None for item in layout.items: widget = self._prepare_toplevel_for_item(item) if not widget: continue if prev_widget: self.control.splitDockWidget(prev_widget, widget, orient) elif not _toplevel_added: self.control.addDockWidget(q_dock_area, widget) prev_widget = widget widget.show() # Now we can recurse. for i, item in enumerate(layout.items): self.set_layout_for_area(item, q_dock_area, _toplevel_added=True, _toplevel_call=False) else: raise MainWindowLayoutError("Unknown layout item %r" % layout) # Remove the fixed sizes once Qt activates the layout. if _toplevel_call: QtCore.QTimer.singleShot(0, self._reset_fixed_sizes) ########################################################################### # 'MainWindowLayout' abstract interface. ########################################################################### def _get_dock_widget(self, pane): """ Returns the QDockWidget associated with a PaneItem. """ raise NotImplementedError def _get_pane(self, dock_widget): """ Returns a PaneItem for a QDockWidget. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### def _get_division_orientation(self, one, two, splitter=False): """ Returns whether there is a division between two visible QWidgets. Divided in context means that the widgets are adjacent and aligned along the direction of the adjaceny. """ united = one.united(two) if splitter: sep = self.control.style().pixelMetric( QtGui.QStyle.PM_DockWidgetSeparatorExtent, None, self.control) united.adjust(0, 0, -sep, -sep) if one.x() == two.x() and one.width() == two.width() and \ united.height() == one.height() + two.height(): return QtCore.Qt.Horizontal elif one.y() == two.y() and one.height() == two.height() and \ united.width() == one.width() + two.width(): return QtCore.Qt.Vertical return 0 def _get_tab_bar(self, dock_widget): """ Returns the tab bar associated with the given QDockWidget, or None if there is no tab bar. """ dock_geometry = dock_widget.geometry() for child in self.control.children(): if isinstance(child, QtGui.QTabBar) and child.isVisible(): geometry = child.geometry() if self._get_division_orientation(dock_geometry, geometry): return child return None def _prepare_pane(self, dock_widget, include_sizes=True): """ Returns a sized PaneItem for a QDockWidget. """ pane = self._get_pane(dock_widget) if include_sizes: pane.width = dock_widget.widget().width() pane.height = dock_widget.widget().height() return pane def _prepare_toplevel_for_item(self, layout): """ Returns a sized toplevel QDockWidget for a LayoutItem. """ if isinstance(layout, PaneItem): dock_widget = self._get_dock_widget(layout) if dock_widget is None: logger.warning('Cannot retrieve dock widget for pane %r' % layout.id) else: if layout.width > 0: dock_widget.widget().setFixedWidth(layout.width) if layout.height > 0: dock_widget.widget().setFixedHeight(layout.height) return dock_widget elif isinstance(layout, LayoutContainer): return self._prepare_toplevel_for_item(layout.items[0]) else: raise MainWindowLayoutError("Leaves of layout must be PaneItems") def _reset_fixed_sizes(self): """ Clears any fixed sizes assined to QDockWidgets. """ QWIDGETSIZE_MAX = (1 << 24) - 1 # Not exposed by Qt bindings. for child in self.control.children(): if isinstance(child, QtGui.QDockWidget): child.widget().setFixedSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX) class MainWindowLayoutError(ValueError): """ Exception raised when a malformed LayoutItem is passed to the MainWindowLayout. """ pass pyface-4.1.0/pyface/ui/qt4/tasks/task_pane.py0000644000175100001440000000230111674463363022015 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.tasks.i_task_pane import ITaskPane, MTaskPane from traits.api import Bool, Property, implements # System library imports. from pyface.qt import QtGui # Local imports. from util import set_focus class TaskPane(MTaskPane): """ The toolkit-specific implementation of a TaskPane. See the ITaskPane interface for API documentation. """ implements(ITaskPane) ########################################################################### # 'ITaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ self.control = QtGui.QWidget(parent) def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ if self.control is not None: self.control.hide() self.control.setParent(None) self.control = None def set_focus(self): """ Gives focus to the control that represents the pane. """ if self.control is not None: set_focus(self.control) pyface-4.1.0/pyface/ui/qt4/wizard/0000755000175100001440000000000011674463363017655 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/qt4/wizard/wizard_page.py0000644000175100001440000001001311674463363022516 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ A page in a wizard. """ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, implements, HasTraits, Str, Tuple, \ Unicode from pyface.wizard.i_wizard_page import IWizardPage, MWizardPage class WizardPage(MWizardPage, HasTraits): """ The toolkit specific implementation of a WizardPage. See the IWizardPage interface for the API documentation. """ implements(IWizardPage) #### 'IWizardPage' interface ############################################## id = Str next_id = Str last_page = Bool(False) complete = Bool(False) heading = Unicode subheading = Unicode size = Tuple ########################################################################### # 'IWizardPage' interface. ########################################################################### def create_page(self, parent): """ Creates the wizard page. """ content = self._create_page_content(parent) # We allow some flexibility with the sort of control we are given. if not isinstance(content, QtGui.QWizardPage): wp = _WizardPage(self) if isinstance(content, QtGui.QLayout): wp.setLayout(content) else: assert isinstance(content, QtGui.QWidget) lay = QtGui.QVBoxLayout() lay.addWidget(content) wp.setLayout(lay) content = wp # Honour any requested page size. if self.size: width, height = self.size if width > 0: content.setMinimumWidth(width) if height > 0: content.setMinimumHeight(height) content.setTitle(self.heading) content.setSubTitle(self.subheading) return content ########################################################################### # Protected 'IWizardPage' interface. ########################################################################### def _create_page_content(self, parent): """ Creates the actual page content. """ # Dummy implementation - override! control = QtGui.QWidget(parent) palette = control.palette() palette.setColor(QtGui.QPalette.Window, QtGui.QColor('yellow')) control.setPalette(palette) control.setAutoFillBackground(True) return control class _WizardPage(QtGui.QWizardPage): """ A QWizardPage sub-class that hooks into the IWizardPage's 'complete' trait. """ def __init__(self, page): """ Initialise the object. """ QtGui.QWizardPage.__init__(self) self.pyface_wizard = None page.on_trait_change(self._on_complete_changed, 'complete') self._page = page def initializePage(self): """ Reimplemented to call the IWizard's 'next'. """ if self.pyface_wizard is not None: self.pyface_wizard.next() def cleanupPage(self): """ Reimplemented to call the IWizard's 'previous'. """ if self.pyface_wizard is not None: self.pyface_wizard.previous() def isComplete(self): """ Reimplemented to return the state of the 'complete' trait. """ return self._page.complete def _on_complete_changed(self): """ The trait handler for when the page's completion state changes. """ self.emit(QtCore.SIGNAL('completeChanged()')) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/wizard/__init__.py0000644000175100001440000000010411674463363021761 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/ui/qt4/wizard/wizard.py0000644000175100001440000001563711674463363021543 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ """ The base class for all pyface wizards. """ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, implements, Instance, List, Property, \ Unicode from pyface.api import Dialog from pyface.wizard.i_wizard import IWizard, MWizard from pyface.wizard.i_wizard_controller import IWizardController from pyface.wizard.i_wizard_page import IWizardPage class Wizard(MWizard, Dialog): """ The base class for all pyface wizards. See the IWizard interface for the API documentation. """ implements(IWizard) #### 'IWizard' interface ################################################## pages = Property(List(IWizardPage)) controller = Instance(IWizardController) show_cancel = Bool(True) #### 'IWindow' interface ################################################## title = Unicode('Wizard') ########################################################################### # 'IWizard' interface. ########################################################################### # Override MWizard implementation to do nothing. We still call these methods # because it expected by IWizard, and users may wish to hook in custom code # before changing a page. def next(self): pass def previous(self): pass ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): pass ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): control = _Wizard(parent, self) control.setOptions(QtGui.QWizard.NoDefaultButton | QtGui.QWizard.NoBackButtonOnStartPage) control.setModal(self.style == 'modal') control.setWindowTitle(self.title) # Necessary for 'nonmodal'. See Dialog for more info. if self.style == 'nonmodal': QtCore.QObject.connect(control, QtCore.SIGNAL('finished(int)'), self._finished_fired) if self.size != (-1, -1): size = QtCore.QSize(*self.size) control.setMinimumSize(size) control.resize(size) if not self.show_cancel: control.setOption(QtGui.QWizard.NoCancelButton) if self.help_id: control.setOption(QtGui.QWizard.HaveHelpButton) QtCore.QObject.connect(control, QtCore.SIGNAL('helpRequested()'), self._help_requested) # Add the initial pages. for page in self.pages: page.pyface_wizard = self control.addWizardPage(page) # Set the start page. control.setStartWizardPage() return control ########################################################################### # Private interface. ########################################################################### def _help_requested(self): """ Called when the 'Help' button is pressed. """ # FIXME: Hook into a help system. print "Show help for", self.help_id #### Trait handlers ####################################################### def _get_pages(self): """ The pages getter. """ return self.controller.pages def _set_pages(self, pages): """ The pages setter. """ # Remove pages from the old list that appear in the new list. The old # list will now contain pages that are no longer in the wizard. old_pages = self.pages new_pages = [] for page in pages: try: old_pages.remove(page) except ValueError: new_pages.append(page) # Dispose of the old pages. for page in old_pages: page.dispose_page() # If we have created the control then we need to add the new pages, # otherwise we leave it until the control is created. if self.control: for page in new_pages: self.control.addWizardPage(page) self.controller.pages = pages def _controller_default(self): """ Provide a default controller. """ from pyface.wizard.wizard_controller import WizardController return WizardController() class _Wizard(QtGui.QWizard): """ A QWizard sub-class that hooks into the controller to determine the next page to show. """ def __init__(self, parent, pyface_wizard): """ Initialise the object. """ QtGui.QWizard.__init__(self, parent) self._pyface_wizard = pyface_wizard self._controller = pyface_wizard.controller self._ids = {} QtCore.QObject.connect(self, QtCore.SIGNAL('currentIdChanged(int)'), self._update_controller) def addWizardPage(self, page): """ Add a page that implements IWizardPage. """ # We must pass a parent otherwise TraitsUI does the wrong thing. qpage = page.create_page(self) qpage.pyface_wizard = self._pyface_wizard id = self.addPage(qpage) self._ids[id] = page def setStartWizardPage(self): """ Set the first page. """ page = self._controller.get_first_page() id = self._page_to_id(page) if id >= 0: self.setStartId(id) def nextId(self): """ Reimplemented to return the id of the next page to display. """ if self.currentId() < 0: return self._page_to_id(self._controller.get_first_page()) current = self._ids[self.currentId()] next = self._controller.get_next_page(current) return self._page_to_id(next) def _update_controller(self, id): """ Called when the current page has changed. """ # Keep the controller in sync with the wizard. self._controller.current_page = self._ids.get(id) def _page_to_id(self, page): """ Return the id of the given page. """ if page is None: id = -1 else: for id, p in self._ids.items(): if p is page: break else: id = -1 return id #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/heading_text.py0000644000175100001440000000531311674463363021374 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import implements, Int, Unicode # Local imports. from pyface.i_heading_text import IHeadingText, MHeadingText from widget import Widget class HeadingText(MHeadingText, Widget): """ The toolkit specific implementation of a HeadingText. See the IHeadingText interface for the API documentation. """ implements(IHeadingText) #### 'IHeadingText' interface ############################################# level = Int(1) text = Unicode('Default') ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates the panel. """ # Base class constructor. super(HeadingText, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self._create_control(parent) ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ self.control = QtGui.QLabel(parent) self._set_text(self.text) self.control.setFrameShape(QtGui.QFrame.StyledPanel) self.control.setFrameShadow(QtGui.QFrame.Raised) self.control.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) def _set_text(self, text): """ Set the text on the toolkit specific widget. """ # Bold the text. Qt supports a limited subset of HTML for rich text. text = "" + text + "" self.control.setText(text) #### Trait event handlers ################################################# def _text_changed(self, new): """ Called when the text is changed. """ if self.control is not None: self._set_text(new) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/__init__.py0000644000175100001440000000010411674463363020461 0ustar ischnellusers00000000000000# Copyright (c) 2007-2011 by Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/ui/qt4/directory_dialog.py0000644000175100001440000000514311674463363022255 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtGui # Enthought library imports. from traits.api import Bool, implements, Unicode # Local imports. from pyface.i_directory_dialog import IDirectoryDialog, MDirectoryDialog from dialog import Dialog class DirectoryDialog(MDirectoryDialog, Dialog): """ The toolkit specific implementation of a DirectoryDialog. See the IDirectoryDialog interface for the API documentation. """ implements(IDirectoryDialog) #### 'IDirectoryDialog' interface ######################################### default_path = Unicode message = Unicode new_directory = Bool(True) path = Unicode ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In PyQt this is a canned dialog. pass ########################################################################### # 'IWindow' interface. ########################################################################### def close(self): # Get the path of the chosen directory. files = self.control.selectedFiles() if files: self.path = unicode(files[0]) else: self.path = '' # Let the window close as normal. super(DirectoryDialog, self).close() ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): dlg = QtGui.QFileDialog(parent, self.title, self.default_path) dlg.setViewMode(QtGui.QFileDialog.Detail) dlg.setFileMode(QtGui.QFileDialog.DirectoryOnly) if not self.new_directory: dlg.setReadOnly(True) if self.message: dlg.setLabelText(QtGui.QFileDialog.LookIn, self.message) return dlg #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/python_shell.py0000644000175100001440000004662611674463363021455 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Evan Patterson #------------------------------------------------------------------------------ # Standard library imports. import __builtin__ from code import compile_command, InteractiveInterpreter from cStringIO import StringIO import sys from time import time # System package imports. from pyface.qt import QtCore, QtGui from pygments.lexers import PythonLexer # Enthought library imports. from traits.api import Event, implements # Local imports. from code_editor.pygments_highlighter import PygmentsHighlighter from console.api import BracketMatcher, CallTipWidget, CompletionLexer, \ HistoryConsoleWidget from pyface.i_python_shell import IPythonShell, MPythonShell from pyface.key_pressed_event import KeyPressedEvent from widget import Widget #------------------------------------------------------------------------------- # 'PythonShell' class: #------------------------------------------------------------------------------- class PythonShell(MPythonShell, Widget): """ The toolkit specific implementation of a PythonShell. See the IPythonShell interface for the API documentation. """ implements(IPythonShell) #### 'IPythonShell' interface ############################################# command_executed = Event key_pressed = Event(KeyPressedEvent) #-------------------------------------------------------------------------- # 'object' interface #-------------------------------------------------------------------------- # FIXME v3: Either make this API consistent with other Widget sub-classes # or make it a sub-class of HasTraits. def __init__(self, parent, **traits): super(PythonShell, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) # Set up to be notified whenever a Python statement is executed: self.control.executed.connect(self._on_command_executed) #-------------------------------------------------------------------------- # 'IPythonShell' interface #-------------------------------------------------------------------------- def interpreter(self): return self.control.interpreter def execute_command(self, command, hidden=True): self.control.execute(command, hidden=hidden) def execute_file(self, path, hidden=True): self.control.execute_file(path, hidden=hidden) #-------------------------------------------------------------------------- # 'IWidget' interface. #-------------------------------------------------------------------------- def _create_control(self, parent): return PyfacePythonWidget(self, parent) #------------------------------------------------------------------------------- # 'PythonWidget' class: #------------------------------------------------------------------------------- class PythonWidget(HistoryConsoleWidget): """ A basic in-process Python interpreter. """ # Emitted when a command has been executed in the interpeter. executed = QtCore.Signal() #-------------------------------------------------------------------------- # 'object' interface #-------------------------------------------------------------------------- def __init__(self, parent=None): super(PythonWidget, self).__init__(parent) # PythonWidget attributes. self.locals = dict(__name__='__console__', __doc__=None) self.interpreter = InteractiveInterpreter(self.locals) # PythonWidget protected attributes. self._buffer = StringIO() self._bracket_matcher = BracketMatcher(self._control) self._call_tip_widget = CallTipWidget(self._control) self._completion_lexer = CompletionLexer(PythonLexer()) self._hidden = False self._highlighter = PythonWidgetHighlighter(self) self._last_refresh_time = 0 # file-like object attributes. self.encoding = sys.stdin.encoding # Configure the ConsoleWidget. self.tab_width = 4 self._set_continuation_prompt('... ') # Configure the CallTipWidget. self._call_tip_widget.setFont(self.font) self.font_changed.connect(self._call_tip_widget.setFont) # Connect signal handlers. document = self._control.document() document.contentsChange.connect(self._document_contents_change) # Display the banner and initial prompt. self.reset() #-------------------------------------------------------------------------- # file-like object interface #-------------------------------------------------------------------------- def flush(self): """ Flush the buffer by writing its contents to the screen. """ self._buffer.seek(0) text = self._buffer.getvalue() self._buffer.close() self._buffer = StringIO() self._append_plain_text(text) self._control.moveCursor(QtGui.QTextCursor.End) def readline(self, prompt=None): """ Read and return one line of input from the user. """ return self._readline(prompt) def write(self, text, refresh=True): """ Write text to the buffer, possibly flushing it if 'refresh' is set. """ if not self._hidden: self._buffer.write(text) if refresh: current_time = time() if current_time - self._last_refresh_time > 0.05: self.flush() self._last_refresh_time = current_time def writelines(self, lines, refresh=True): """ Write a list of lines to the buffer. """ for line in lines: self.write(line, refresh=refresh) #--------------------------------------------------------------------------- # 'ConsoleWidget' abstract interface #--------------------------------------------------------------------------- def _is_complete(self, source, interactive): """ Returns whether 'source' can be completely processed and a new prompt created. When triggered by an Enter/Return key press, 'interactive' is True; otherwise, it is False. """ if interactive: lines = source.splitlines() if len(lines) == 1: try: return compile_command(source) is not None except: # We'll let the interpeter handle the error. return True else: return lines[-1].strip() == '' else: return True def _execute(self, source, hidden): """ Execute 'source'. If 'hidden', do not show any output. See parent class :meth:`execute` docstring for full details. """ # Save the current std* and point them here old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr sys.stdin = sys.stdout = sys.stderr = self # Run the source code in the interpeter self._hidden = hidden try: more = self.interpreter.runsource(source) finally: self._hidden = False # Restore std* unless the executed changed them if sys.stdin is self: sys.stdin = old_stdin if sys.stdout is self: sys.stdout = old_stdout if sys.stderr is self: sys.stderr = old_stderr self.executed.emit() self._show_interpreter_prompt() def _prompt_started_hook(self): """ Called immediately after a new prompt is displayed. """ if not self._reading: self._highlighter.highlighting_on = True def _prompt_finished_hook(self): """ Called immediately after a prompt is finished, i.e. when some input will be processed and a new prompt displayed. """ if not self._reading: self._highlighter.highlighting_on = False def _tab_pressed(self): """ Called when the tab key is pressed. Returns whether to continue processing the event. """ # Perform tab completion if: # 1) The cursor is in the input buffer. # 2) There is a non-whitespace character before the cursor. text = self._get_input_buffer_cursor_line() if text is None: return False complete = bool(text[:self._get_input_buffer_cursor_column()].strip()) if complete: self._complete() return not complete #--------------------------------------------------------------------------- # 'ConsoleWidget' protected interface #--------------------------------------------------------------------------- def _event_filter_console_keypress(self, event): """ Reimplemented for smart backspace. """ if event.key() == QtCore.Qt.Key_Backspace and \ not event.modifiers() & QtCore.Qt.AltModifier: # Smart backspace: remove four characters in one backspace if: # 1) everything left of the cursor is whitespace # 2) the four characters immediately left of the cursor are spaces col = self._get_input_buffer_cursor_column() cursor = self._control.textCursor() if col > 3 and not cursor.hasSelection(): text = self._get_input_buffer_cursor_line()[:col] if text.endswith(' ') and not text.strip(): cursor.movePosition(QtGui.QTextCursor.Left, QtGui.QTextCursor.KeepAnchor, 4) cursor.removeSelectedText() return True return super(PythonWidget, self)._event_filter_console_keypress(event) def _insert_continuation_prompt(self, cursor): """ Reimplemented for auto-indentation. """ super(PythonWidget, self)._insert_continuation_prompt(cursor) source = self.input_buffer space = 0 for c in source.splitlines()[-1]: if c == '\t': space += 4 elif c == ' ': space += 1 else: break if source.rstrip().endswith(':'): space += 4 cursor.insertText(' ' * space) #--------------------------------------------------------------------------- # 'PythonWidget' public interface #--------------------------------------------------------------------------- def execute_file(self, path, hidden=False): """ Attempts to execute file with 'path'. If 'hidden', no output is shown. """ self.execute('execfile("%s")' % path, hidden=hidden) def reset(self): """ Resets the widget to its initial state. Similar to ``clear``, but also re-writes the banner. """ self._reading = False self._highlighter.highlighting_on = False self._control.clear() self._append_plain_text(self._get_banner()) self._show_interpreter_prompt() #--------------------------------------------------------------------------- # 'PythonWidget' protected interface #--------------------------------------------------------------------------- def _call_tip(self): """ Shows a call tip, if appropriate, at the current cursor location. """ # Decide if it makes sense to show a call tip cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.Left) if cursor.document().characterAt(cursor.position()) != '(': return False context = self._get_context(cursor) if not context: return False # Look up the context and show a tip for it symbol, leftover = self._get_symbol_from_context(context) doc = getattr(symbol, '__doc__', None) if doc is not None and not leftover: self._call_tip_widget.show_call_info(doc=doc) return True return False def _complete(self): """ Performs completion at the current cursor location. """ context = self._get_context() if context: symbol, leftover = self._get_symbol_from_context(context) if len(leftover) == 1: leftover = leftover[0] if symbol is None: names = self.interpreter.locals.keys() names += __builtin__.__dict__.keys() else: names = dir(symbol) completions = [ n for n in names if n.startswith(leftover) ] if completions: cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.Left, n=len(context[-1])) self._complete_with_items(cursor, completions) def _get_banner(self): """ Gets a banner to display at the beginning of a session. """ banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \ '"license" for more information.' return banner % (sys.version, sys.platform) def _get_context(self, cursor=None): """ Gets the context for the specified cursor (or the current cursor if none is specified). """ if cursor is None: cursor = self._get_cursor() cursor.movePosition(QtGui.QTextCursor.StartOfBlock, QtGui.QTextCursor.KeepAnchor) text = cursor.selection().toPlainText() return self._completion_lexer.get_context(text) def _get_symbol_from_context(self, context): """ Find a python object in the interpeter namespace from a context (a list of names). """ context = map(str, context) if len(context) == 0: return None, context base_symbol_string = context[0] symbol = self.interpreter.locals.get(base_symbol_string, None) if symbol is None: symbol = __builtin__.__dict__.get(base_symbol_string, None) if symbol is None: return None, context context = context[1:] for i, name in enumerate(context): new_symbol = getattr(symbol, name, None) if new_symbol is None: return symbol, context[i:] else: symbol = new_symbol return symbol, [] def _show_interpreter_prompt(self): """ Shows a prompt for the interpreter. """ self.flush() self._show_prompt('>>> ') #------ Signal handlers ---------------------------------------------------- def _document_contents_change(self, position, removed, added): """ Called whenever the document's content changes. Display a call tip if appropriate. """ # Calculate where the cursor should be *after* the change: position += added document = self._control.document() if position == self._get_cursor().position(): self._call_tip() #------------------------------------------------------------------------------- # 'PythonWidgetHighlighter' class: #------------------------------------------------------------------------------- class PythonWidgetHighlighter(PygmentsHighlighter): """ A PygmentsHighlighter that can be turned on and off and that ignores prompts. """ def __init__(self, python_widget): super(PythonWidgetHighlighter, self).__init__( python_widget._control.document()) self._current_offset = 0 self._python_widget = python_widget self.highlighting_on = False def highlightBlock(self, string): """ Highlight a block of text. Reimplemented to highlight selectively. """ if not self.highlighting_on: return # The input to this function is a unicode string that may contain # paragraph break characters, non-breaking spaces, etc. Here we acquire # the string as plain text so we can compare it. current_block = self.currentBlock() string = self._python_widget._get_block_plain_text(current_block) # Decide whether to check for the regular or continuation prompt. if current_block.contains(self._python_widget._prompt_pos): prompt = self._python_widget._prompt else: prompt = self._python_widget._continuation_prompt # Don't highlight the part of the string that contains the prompt. if string.startswith(prompt): self._current_offset = len(prompt) string = string[len(prompt):] else: self._current_offset = 0 super(PythonWidgetHighlighter, self).highlightBlock(string) def rehighlightBlock(self, block): """ Reimplemented to temporarily enable highlighting if disabled. """ old = self.highlighting_on self.highlighting_on = True super(PythonWidgetHighlighter, self).rehighlightBlock(block) self.highlighting_on = old def setFormat(self, start, count, format): """ Reimplemented to highlight selectively. """ start += self._current_offset super(PythonWidgetHighlighter, self).setFormat(start, count, format) #------------------------------------------------------------------------------- # 'PyfacePythonWidget' class: #------------------------------------------------------------------------------- class PyfacePythonWidget(PythonWidget): """ A PythonWidget customized to support the IPythonShell interface. """ #-------------------------------------------------------------------------- # 'object' interface #-------------------------------------------------------------------------- def __init__(self, pyface_widget, *args, **kw): """ Reimplemented to store a reference to the Pyface widget which contains this control. """ self._pyface_widget = pyface_widget super(PyfacePythonWidget, self).__init__(*args, **kw) #--------------------------------------------------------------------------- # 'QWidget' interface #--------------------------------------------------------------------------- def keyPressEvent(self, event): """ Reimplemented to generate Pyface key press events. """ # Pyface doesn't seem to be Unicode aware. Only keep the key code if it # corresponds to a single Latin1 character. kstr = event.text() try: kcode = ord(str(kstr)) except: kcode = 0 mods = event.modifiers() self._pyface_widget.key_pressed = KeyPressedEvent( alt_down = ((mods & QtCore.Qt.AltModifier) == QtCore.Qt.AltModifier), control_down = ((mods & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier), shift_down = ((mods & QtCore.Qt.ShiftModifier) == QtCore.Qt.ShiftModifier), key_code = kcode, event = event) super(PyfacePythonWidget, self).keyPressEvent(event) pyface-4.1.0/pyface/ui/qt4/window.py0000644000175100001440000001474111674463363020245 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Any, Event, implements, Property, Unicode from traits.api import Tuple # Local imports. from pyface.i_window import IWindow, MWindow from pyface.key_pressed_event import KeyPressedEvent from gui import GUI from widget import Widget class Window(MWindow, Widget): """ The toolkit specific implementation of a Window. See the IWindow interface for the API documentation. """ implements(IWindow) #### 'IWindow' interface ################################################## position = Property(Tuple) size = Property(Tuple) title = Unicode #### Events ##### activated = Event closed = Event closing = Event deactivated = Event key_pressed = Event(KeyPressedEvent) opened = Event opening = Event #### Private interface #################################################### # Shadow trait for position. _position = Tuple((-1, -1)) # Shadow trait for size. _size = Tuple((-1, -1)) ########################################################################### # 'IWindow' interface. ########################################################################### def activate(self): self.control.activateWindow() self.control.raise_() def show(self, visible): self.control.setVisible(visible) ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): self._event_filter = _EventFilter(self) ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): self._event_filter = None if self.control is not None: # Avoid problems with recursive calls. control = self.control self.control = None # Hide the widget before closing it. This is not strictly necessary # (closing the window in fact hides it), but the close may # trigger an application shutdown, which can take a long time. # The window should not be visible during this process. control.hide() control.close() ########################################################################### # Private interface. ########################################################################### def _get_position(self): """ Property getter for position. """ return self._position def _set_position(self, position): """ Property setter for position. """ if self.control is not None: self.control.move(*position) old = self._position self._position = position self.trait_property_changed('position', old, position) def _get_size(self): """ Property getter for size. """ return self._size def _set_size(self, size): """ Property setter for size. """ if self.control is not None: self.control.resize(*size) old = self._size self._size = size self.trait_property_changed('size', old, size) def _title_changed(self, title): """ Static trait change handler. """ if self.control is not None: self.control.setWindowTitle(title) class _EventFilter(QtCore.QObject): """ An internal class that watches for certain events on behalf of the Window instance. """ def __init__(self, window): """ Initialise the event filter. """ QtCore.QObject.__init__(self) window.control.installEventFilter(self) self._window = window def eventFilter(self, obj, e): """ Adds any event listeners required by the window. """ window = self._window # Sanity check. if obj is not window.control: return False if e.type() == QtCore.QEvent.Close: # Do not destroy the window during its event handler. GUI.invoke_later(window.close) if window.control is not None: e.ignore() return True if e.type() == QtCore.QEvent.WindowActivate: window.activated = window elif e.type() == QtCore.QEvent.WindowDeactivate: window.deactivated = window elif e.type() == QtCore.QEvent.Resize: # Get the new size and set the shadow trait without performing # notification. size = e.size() window._size = (size.width(), size.height()) elif e.type() == QtCore.QEvent.Move: # Get the real position and set the trait without performing # notification. Don't use event.pos(), as this excludes the window # frame geometry. pos = window.control.pos() window._position = (pos.x(), pos.y()) elif e.type() == QtCore.QEvent.KeyPress: # Pyface doesn't seem to be Unicode aware. Only keep the key code # if it corresponds to a single Latin1 character. kstr = e.text() try: kcode = ord(str(kstr)) except: kcode = 0 mods = e.modifiers() window.key_pressed = KeyPressedEvent( alt_down = ((mods & QtCore.Qt.AltModifier) == QtCore.Qt.AltModifier), control_down = ((mods & QtCore.Qt.ControlModifier) == QtCore.Qt.ControlModifier), shift_down = ((mods & QtCore.Qt.ShiftModifier) == QtCore.Qt.ShiftModifier), key_code = kcode, event = e) return False #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/dialog.py0000644000175100001440000001236311674463363020173 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # However, when used with the GPL version of PyQt the additional terms described in the PyQt GPL exception also apply # # Author: Riverbank Computing Limited # Description: #------------------------------------------------------------------------------ # Major package imports. from pyface.qt import QtCore, QtGui # Enthought library imports. from traits.api import Bool, Enum, implements, Int, Str, Unicode # Local imports. from pyface.i_dialog import IDialog, MDialog from pyface.constant import OK, CANCEL, YES, NO from window import Window # Map PyQt dialog related constants to the pyface equivalents. _RESULT_MAP = { int(QtGui.QDialog.Accepted): OK, int(QtGui.QDialog.Rejected): CANCEL, int(QtGui.QMessageBox.Ok): OK, int(QtGui.QMessageBox.Cancel): CANCEL, int(QtGui.QMessageBox.Yes): YES, int(QtGui.QMessageBox.No): NO } class Dialog(MDialog, Window): """ The toolkit specific implementation of a Dialog. See the IDialog interface for the API documentation. """ implements(IDialog) #### 'IDialog' interface ################################################## cancel_label = Unicode help_id = Str help_label = Unicode ok_label = Unicode resizeable = Bool(True) return_code = Int(OK) style = Enum('modal', 'nonmodal') #### 'IWindow' interface ################################################## title = Unicode("Dialog") ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_buttons(self, parent): buttons = QtGui.QDialogButtonBox() # 'OK' button. if self.ok_label: btn = buttons.addButton(self.ok_label, QtGui.QDialogButtonBox.AcceptRole) else: btn = buttons.addButton(QtGui.QDialogButtonBox.Ok) btn.setDefault(True) QtCore.QObject.connect(btn, QtCore.SIGNAL('clicked()'), self.control, QtCore.SLOT('accept()')) # 'Cancel' button. if self.cancel_label: btn = buttons.addButton(self.cancel_label, QtGui.QDialogButtonBox.RejectRole) else: btn = buttons.addButton(QtGui.QDialogButtonBox.Cancel) QtCore.QObject.connect(btn, QtCore.SIGNAL('clicked()'), self.control, QtCore.SLOT('reject()')) # 'Help' button. # FIXME v3: In the original code the only possible hook into the help # was to reimplement self._on_help(). However this was a private # method. Obviously nobody uses the Help button. For the moment we # display it but can't actually use it. if len(self.help_id) > 0: if self.help_label: buttons.addButton(self.help_label, QtGui.QDialogButtonBox.HelpRole) else: buttons.addButton(QtGui.QDialogButtonBox.Help) return buttons def _create_contents(self, parent): layout = QtGui.QVBoxLayout() if not self.resizeable: layout.setSizeConstraint(QtGui.QLayout.SetFixedSize) layout.addWidget(self._create_dialog_area(parent)) layout.addWidget(self._create_buttons(parent)) parent.setLayout(layout) def _create_dialog_area(self, parent): panel = QtGui.QWidget(parent) panel.setMinimumSize(QtCore.QSize(100, 200)) palette = panel.palette() palette.setColor(QtGui.QPalette.Window, QtGui.QColor('red')) panel.setPalette(palette) panel.setAutoFillBackground(True) return panel def _show_modal(self): self.control.setWindowModality(QtCore.Qt.ApplicationModal) retval = self.control.exec_() return _RESULT_MAP[retval] ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): dlg = QtGui.QDialog(parent) # Setting return code and firing close events is handled for 'modal' in # MDialog's open method. For 'nonmodal', we do it here. if self.style == 'nonmodal': QtCore.QObject.connect(dlg, QtCore.SIGNAL('finished(int)'), self._finished_fired) if self.size != (-1, -1): dlg.resize(*self.size) dlg.setWindowTitle(self.title) return dlg ########################################################################### # Private interface. ########################################################################### def _finished_fired(self, result): """ Called when the dialog is closed (and nonmodal). """ self.return_code = _RESULT_MAP[result] self.close() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/qt4/progress_dialog.py0000644000175100001440000001622511674463363022120 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A simple progress bar intended to run in the UI thread """ import time from pyface.qt import QtGui, QtCore # Enthought library imports from traits.api import Instance, Enum, Int, Bool, Unicode, implements # Local imports from widget import Widget from pyface.i_progress_dialog import IProgressDialog, MProgressDialog from window import Window class ProgressDialog(MProgressDialog, Window): """ A simple progress dialog window which allows itself to be updated FIXME: buttons are not set up correctly yet """ progress_bar = Instance(QtGui.QProgressBar) title = Unicode message = Unicode min = Int max = Int margin = Int(5) can_cancel = Bool(False) # The IProgressDialog interface doesn't declare this, but since this is a # feature of the QT backend ProgressDialog that doesn't appear in WX, we # offer an option to disable it. can_ok = Bool(False) show_time = Bool(False) show_percent = Bool(False) _user_cancelled = Bool(False) dialog_size = Instance(QtCore.QRect) # Label for the 'cancel' button cancel_button_label = Unicode('Cancel') def open(self): super(ProgressDialog, self).open() self._start_time = time.time() def close(self): self.progress_bar.destroy() self.progress_bar = None super(ProgressDialog, self).close() def change_message(self, message): self.message = message def update(self, value): """ updates the progress bar to the desired value. If the value is >= the maximum and the progress bar is not contained in another panel the parent window will be closed """ if self.progress_bar is None: return None, None if self.max > 0: self.progress_bar.setValue(value) if (self.max != self.min): percent = (float(value) - self.min)/(self.max - self.min) else: percent = 1.0 if self.show_time and (percent != 0): current_time = time.time() elapsed = current_time - self._start_time estimated = elapsed/percent remaining = estimated - elapsed self._set_time_label(elapsed, self._elapsed_control) self._set_time_label(estimated, self._estimated_control) self._set_time_label(remaining, self._remaining_control) if self.show_percent: self._percent_control = "%3f" % ((percent * 100) % 1) if value >= self.max or self._user_cancelled: self.close() else: self.progress_bar.setValue(self.progress_bar.value() + value) if self._user_cancelled: self.close() QtGui.QApplication.processEvents() return (not self._user_cancelled, False) def reject(self, event): self._user_cancelled = True self.close() def _set_time_label(self, value, control): hours = value / 3600 minutes = (value % 3600) / 60 seconds = value % 60 label = "%1u:%02u:%02u" % (hours, minutes, seconds) control.setText(control.text()[:-7] + label) def _create_buttons(self, dialog, layout): """ Creates the buttons. """ if not (self.can_cancel or self.can_ok): return # Create the button. buttons = QtGui.QDialogButtonBox() if self.can_cancel: buttons.addButton(self.cancel_button_label, QtGui.QDialogButtonBox.RejectRole) if self.can_ok: buttons.addButton(QtGui.QDialogButtonBox.Ok) # TODO: hookup the buttons to our methods, this may involve subclassing from QDialog if self.can_cancel: buttons.connect(buttons, QtCore.SIGNAL('rejected()'), dialog, QtCore.SLOT('reject()')) if self.can_ok: buttons.connect(buttons, QtCore.SIGNAL('accepted()'), dialog, QtCore.SLOT('accept()')) layout.addWidget(buttons) def _create_label(self, dialog, layout, text): dummy = QtGui.QLabel(text, dialog) dummy.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) label = QtGui.QLabel("unknown", dialog) label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft | QtCore.Qt.AlignRight) sub_layout = QtGui.QHBoxLayout() sub_layout.addWidget(dummy) sub_layout.addWidget(label) layout.addLayout(sub_layout) return label def _create_gauge(self, dialog, layout): self.progress_bar = QtGui.QProgressBar(dialog) # self.progress_bar.setFrameStyle(QtGui.QFrame.Sunken | QtGui.QFrame.Panel) self.progress_bar.setRange(self.min, self.max) layout.addWidget(self.progress_bar) if self.show_percent: self.progress_bar.setFormat("%p%") else: self.progress_bar.setFormat("%v") return def _create_message(self, dialog, layout): label = QtGui.QLabel(self.message, dialog) label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft) layout.addWidget(label) return def _create_percent(self, dialog, layout): #not an option with the QT progress bar return def _create_timer(self, dialog, layout): if not self.show_time: return self._elapsed_control = self._create_label(dialog, layout, "Elapsed time : ") self._estimated_control = self._create_label(dialog, layout, "Estimated time : ") self._remaining_control = self._create_label(dialog, layout, "Remaining time : ") return def _create_control(self, parent): return QtGui.QDialog(parent) def _create(self): super(ProgressDialog, self)._create() contents = self._create_contents(self.control) def _create_contents(self, parent): dialog = parent layout = QtGui.QVBoxLayout(dialog) # The 'guts' of the dialog. self._create_message(dialog, layout) self._create_gauge(dialog, layout) self._create_percent(dialog, layout) self._create_timer(dialog, layout) self._create_buttons(dialog, layout) dialog.setWindowTitle(self.title) parent.setLayout(layout) def _max_changed(self, new): if self.progress_bar is not None: self.progress_bar.setMaximum(new) def _min_changed(self, new): if self.progress_bar is not None: self.progress_bar.setMinimum(new) pyface-4.1.0/pyface/ui/qt4/clipboard.py0000644000175100001440000000713011674463363020667 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Evan Patterson # Date: 06/26/09 #------------------------------------------------------------------------------ # Standard library imports from cStringIO import StringIO from cPickle import dumps, load, loads # System library imports from pyface.qt import QtCore, QtGui # ETS imports from traits.api import implements from pyface.i_clipboard import IClipboard, BaseClipboard # Shortcuts cb = QtGui.QApplication.clipboard() # Custom MIME type representing python objects PYTHON_TYPE = "python/object" class Clipboard(BaseClipboard): implements(IClipboard) #--------------------------------------------------------------------------- # 'data' property methods: #--------------------------------------------------------------------------- def _get_has_data(self): return self.has_object_data or self.has_text_data or self.has_file_data #--------------------------------------------------------------------------- # 'object_data' property methods: #--------------------------------------------------------------------------- def _get_object_data(self): obj = None mime_data = cb.mimeData() if mime_data.hasFormat(PYTHON_TYPE): serialized_data = StringIO(str(mime_data.data(PYTHON_TYPE))) klass = load(serialized_data) obj = load(serialized_data) return obj def _set_object_data(self, data): mime_data = QtCore.QMimeData() serialized_data = dumps(data.__class__) + dumps(data) mime_data.setData(PYTHON_TYPE, QtCore.QByteArray(serialized_data)) cb.setMimeData(mime_data) def _get_has_object_data(self): return cb.mimeData().hasFormat(PYTHON_TYPE) def _get_object_type(self): result = '' mime_data = cb.mimeData() if mime_data.hasFormat(PYTHON_TYPE): try: # We may not be able to load the required class: result = loads(str(mime_data.data(PYTHON_TYPE))) except: pass return result #--------------------------------------------------------------------------- # 'text_data' property methods: #--------------------------------------------------------------------------- def _get_text_data(self): return str(cb.text()) def _set_text_data(self, data): cb.setText(data) def _get_has_text_data(self): return cb.mimeData().hasText() #--------------------------------------------------------------------------- # 'file_data' property methods: #--------------------------------------------------------------------------- def _get_file_data(self): mime_data = cb.mimeData() if mime_data.hasUrls(): return [ str(url.toString()) for url in mime_data.urls() ] else: return [] def _set_file_data(self, data): if isinstance(data, basestring): data = [ data ] mime_data = QtCore.QMimeData() mime_data.setUrls([ QtCore.QUrl(path) for path in data ]) cb.setMimeData(mime_data) def _get_has_file_data (self): return cb.mimeData().hasUrls() pyface-4.1.0/pyface/ui/wx/0000755000175100001440000000000011674463363016303 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/timer/0000755000175100001440000000000011674463363017423 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/timer/timer.py0000644000175100001440000000402711674463363021120 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Prabhu Ramachandran # #------------------------------------------------------------------------------- """A `wx.Timer` subclass that invokes a specified callback periodically. """ # Standard library imports. import wx ###################################################################### # `Timer` class. class Timer(wx.Timer): """Simple subclass of wx.Timer that allows the user to have a function called periodically. Any exceptions raised in the callable are caught. If `StopIteration` is raised the timer stops. If other exceptions are encountered the timer is stopped and the exception re-raised. """ def __init__(self, millisecs, callable, *args, **kw_args): """Initialize instance to invoke the given `callable` with given arguments and keyword args after every `millisecs` (milliseconds). """ wx.Timer.__init__(self, id=wx.NewId()) self.callable = callable self.args = args self.kw_args = kw_args self.Start(millisecs) def Notify(self): """Overridden to call the given callable. Exceptions raised in the callable are caught. If `StopIteration` is raised the timer stops. If other exceptions are encountered the timer is stopped and the exception re-raised. """ try: self.callable(*self.args, **self.kw_args) wx.GetApp().Yield(True) except StopIteration: self.Stop() except: self.Stop() raise pyface-4.1.0/pyface/ui/wx/timer/do_later.py0000644000175100001440000000443611674463363021575 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 05/18/2005 # #------------------------------------------------------------------------------- """ Provides a simple function for scheduling some code to run at some time in the future (assumes application is wxPython based). """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx #------------------------------------------------------------------------------- # 'DoLaterTimer' class: #------------------------------------------------------------------------------- class DoLaterTimer ( wx.Timer ): # List of currently active timers: active_timers = [] #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, interval, callable, args, kw_args ): global active_timers wx.Timer.__init__( self ) for timer in self.active_timers: if ((timer.callable == callable) and (timer.args == args) and (timer.kw_args == kw_args)): timer.Start( interval, True ) return self.active_timers.append( self ) self.callable = callable self.args = args self.kw_args = kw_args self.Start( interval, True ) #--------------------------------------------------------------------------- # Handles the timer pop event: #--------------------------------------------------------------------------- def Notify ( self ): global active_timers self.active_timers.remove( self ) self.callable( *self.args, **self.kw_args ) pyface-4.1.0/pyface/ui/wx/timer/__init__.py0000644000175100001440000000034511674463363021536 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/image_resource.py0000644000175100001440000000566611674463363021663 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import os # Major package imports. import wx # Enthought library imports. from traits.api import Any, HasTraits, implements, List, Property from traits.api import Unicode # Local imports. from pyface.i_image_resource import IImageResource, MImageResource class ImageResource(MImageResource, HasTraits): """ The toolkit specific implementation of an ImageResource. See the IImageResource interface for the API documentation. """ implements(IImageResource) #### Private interface #################################################### # The resource manager reference for the image. _ref = Any #### 'ImageResource' interface ############################################ absolute_path = Property(Unicode) name = Unicode search_path = List ########################################################################### # 'ImageResource' interface. ########################################################################### def create_bitmap(self, size=None): return self.create_image(size).ConvertToBitmap() def create_icon(self, size=None): ref = self._get_ref(size) if ref is not None: icon = wx.Icon(self.absolute_path, wx.BITMAP_TYPE_ICO) else: image = self._get_image_not_found_image() # We have to convert the image to a bitmap first and then create an # icon from that. bmp = image.ConvertToBitmap() icon = wx.EmptyIcon() icon.CopyFromBitmap(bmp) return icon ########################################################################### # Private interface. ########################################################################### def _get_absolute_path(self): # FIXME: This doesn't quite wotk the new notion of image size. We # should find out who is actually using this trait, and for what! # (AboutDialog uses it to include the path name in some HTML.) ref = self._get_ref() if ref is not None: absolute_path = os.path.abspath(self._ref.filename) else: absolute_path = self._get_image_not_found().absolute_path return absolute_path #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/system_metrics.py0000644000175100001440000000403711674463363021733 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import sys # Major package imports. import wx # Enthought library imports. from traits.api import HasTraits, implements, Int, Property, Tuple # Local imports. from pyface.i_system_metrics import ISystemMetrics, MSystemMetrics class SystemMetrics(MSystemMetrics, HasTraits): """ The toolkit specific implementation of a SystemMetrics. See the ISystemMetrics interface for the API documentation. """ implements(ISystemMetrics) #### 'ISystemMetrics' interface ########################################### screen_width = Property(Int) screen_height = Property(Int) dialog_background_color = Property(Tuple) ########################################################################### # Private interface. ########################################################################### def _get_screen_width(self): return wx.SystemSettings.GetMetric(wx.SYS_SCREEN_X) def _get_screen_height(self): return wx.SystemSettings.GetMetric(wx.SYS_SCREEN_Y) def _get_dialog_background_color(self): if sys.platform == 'darwin': # wx lies. color = wx.Colour(232, 232, 232) else: color = wx.SystemSettings_GetColour(wx.SYS_COLOUR_MENUBAR).Get() return (color[0]/255., color[1]/255., color[2]/255.) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/init.py0000644000175100001440000000203611674463363017621 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # #------------------------------------------------------------------------------ # Major package imports. import wx # Check the version number is late enough. if wx.VERSION < (2, 6): raise RuntimeError, "Need wx version 2.6 or higher, but got %s" % str(wx.VERSION) # It's possible that it has already been initialised. _app = wx.GetApp() if _app is None: _app = wx.PySimpleApp() # Before we can load any images we have to initialize wxPython's image # handlers. wx.InitAllImageHandlers() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/confirmation_dialog.py0000644000175100001440000001043211674463363022664 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import Bool, Enum, implements, Instance, Unicode # Local imports. from pyface.i_confirmation_dialog import IConfirmationDialog, MConfirmationDialog from pyface.constant import CANCEL, YES, NO from pyface.image_resource import ImageResource from dialog import Dialog class ConfirmationDialog(MConfirmationDialog, Dialog): """ The toolkit specific implementation of a ConfirmationDialog. See the IConfirmationDialog interface for the API documentation. """ implements(IConfirmationDialog) #### 'IConfirmationDialog' interface ###################################### cancel = Bool(False) default = Enum(NO, YES, CANCEL) image = Instance(ImageResource) message = Unicode informative = Unicode detail = Unicode no_label = Unicode yes_label = Unicode ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_buttons(self, parent): sizer = wx.BoxSizer(wx.HORIZONTAL) # 'YES' button. if self.yes_label: label = self.yes_label else: label = "Yes" self._yes = yes = wx.Button(parent, wx.ID_YES, label) if self.default == YES: yes.SetDefault() wx.EVT_BUTTON(parent, wx.ID_YES, self._on_yes) sizer.Add(yes) # 'NO' button. if self.no_label: label = self.no_label else: label = "No" self._no = no = wx.Button(parent, wx.ID_NO, label) if self.default == NO: no.SetDefault() wx.EVT_BUTTON(parent, wx.ID_NO, self._on_no) sizer.Add(no, 0, wx.LEFT, 10) if self.cancel: # 'Cancel' button. if self.no_label: label = self.cancel_label else: label = "Cancel" self._cancel = cancel = wx.Button(parent, wx.ID_CANCEL, label) if self.default == CANCEL: cancel.SetDefault() wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) return sizer def _create_dialog_area(self, parent): panel = wx.Panel(parent, -1) sizer = wx.BoxSizer(wx.HORIZONTAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) # The image. if self.image is None: image_rc = ImageResource('warning') else: image_rc = self.image image = wx.StaticBitmap(panel, -1, image_rc.create_bitmap()) sizer.Add(image, 0, wx.EXPAND | wx.ALL, 10) # The message. if self.informative: message = self.message + '\n\n' + self.informative else: message = self.message message = wx.StaticText(panel, -1, message) sizer.Add(message, 1, wx.EXPAND | wx.TOP, 15) # Resize the panel to match the sizer. sizer.Fit(panel) return panel ########################################################################### # Private interface. ########################################################################### #### wx event handlers #################################################### def _on_yes(self, event): """ Called when the 'Yes' button is pressed. """ self.control.EndModal(wx.ID_YES) def _on_no(self, event): """ Called when the 'No' button is pressed. """ self.control.EndModal(wx.ID_NO) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/image_cache.py0000644000175100001440000000567611674463363021100 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import HasTraits, implements # Local imports. from pyface.i_image_cache import IImageCache, MImageCache class ImageCache(MImageCache, HasTraits): """ The toolkit specific implementation of an ImageCache. See the IImageCache interface for the API documentation. """ implements(IImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, width, height): self._width = width self._height = height # The images in the cache! self._images = {} # {filename : wx.Image} # The images in the cache converted to bitmaps. self._bitmaps = {} # {filename : wx.Bitmap} return ########################################################################### # 'ImageCache' interface. ########################################################################### def get_image(self, filename): # Try the cache first. image = self._images.get(filename) if image is None: # Load the image from the file and add it to the list. # # N.B 'wx.BITMAP_TYPE_ANY' tells wxPython to attempt to autodetect # --- the image format. image = wx.Image(filename, wx.BITMAP_TYPE_ANY) # We force all images in the cache to be the same size. if image.GetWidth() != self._width or image.GetHeight() != self._height: image.Rescale(self._width, self._height) # Add the bitmap to the cache! self._images[filename] = image return image def get_bitmap(self, filename): # Try the cache first. bmp = self._bitmaps.get(filename) if bmp is None: # Get the image. image = self.get_image(filename) # Convert the alpha channel to a mask. image.ConvertAlphaToMask() # Convert it to a bitmaps! bmp = image.ConvertToBitmap() # Add the bitmap to the cache! self._bitmaps[filename] = bmp return bmp #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/images/0000755000175100001440000000000011674463363017550 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/images/heading_level_1.png0000644000175100001440000000424011674463363023264 0ustar ischnellusers00000000000000PNG  IHDRv;lgAMA7tEXtSoftwareAdobe ImageReadyqe<2IDATxb?(%@u0/# b\ t0VyY:@'ssHa50nV)n$ٽx***\0,28 a fП:|ۜUtBxd|,d` 2GOV;CMv:cPL'@Pz#; FӚWķ3u*;)DRsnBK-!JyLRnb*EWj4I_} @E0ś&+a: 8"'7nxywV81xK0 [RtNmu4nz27ŋsh³X<xT3yGE_2`?썶vmKAeUi +?3\. -XGiSY#),.[~S Òc9K_<ڶȕ-e@PYE)'Ժ (i;*pм{ ==*0 |~YwOCM6mhkc0Zdqr8#_<'_ \T0@]k|s 21G/R E-9X#XuϖG+hRgjI躧Q`;3\q%`)#\0`r;_LLV}' /T6>26p lːbwγ]kxK)R/DA+3rx:Kޏ:9za'0lB>jh;UbgV)'"0$g JYR(B~.^6.MkM1[0Bvgpك 6Tכ`.Kne"+]i`5!`c=›]1]iK5 IwA#7 ˞Bm W߻0K#f>?0B3@)fL4X:$R|NnD;Ptehe?rBgGWݽHĺ&A&yqD/ҭPLN; Gb P K@#:\[E nPZFH DBP[VKP;0!+$G#<  0~ep}GG%F*X'RDeꀘzQd{ L@@ \%0U?j:EEfp q8wG‘WS2 t`(Yr"6e.7 ͡buDQ|?u/*Uz,q#s͜[?F )}..pJr@be?Ф g!IhY+ iFHox^2BZ?5m\AAQt:#z[`YYIcaRv| (a`XaM lkDn7'57L0.ТIDrT2ɡ`q)IENDB`pyface-4.1.0/pyface/ui/wx/images/application.ico0000644000175100001440000006410611674463363022556 0ustar ischnellusers00000000000000h hV     N#00300 %B( @uF{L^I|y{RByV~~Kzzz}}}[քRIzyyhanU^X/['c=O4`2rd!C .9 8Tt,(D+Fg6_jWP];lQ IV7p\S"<&-NH5K3Tqm 1M0k$GEfuAL@?J :%e)BZ#o> pRisjb*Y(  @~~~SvvvyxxtstjjjrrryyytstlllmmmN|y{uuuOU~HyzpwDvUڂqqqGW}IiLJJ{I|\{>u[քlll𐌏RKIByk{F{J/PhɷgaHMOQ-ܺ{qx&mt} L`FS\of *3<@y!Wb. I %#vDd+19u^ Ӡ'T?996pʀRs~~4XYKZ[K??(0 ` |||_yyyvvvvvvuuummm49zzzxxxuuussstttZggg lyyy{{{wwwjjjlll}}}{{{qqqkkkuuuduuuhhhkkkvvv^ǀNyhhhP{`ʃlllxxxs"iBx=up[zdƅuos]n=u6nukkknnnp|HI|J{|@v8ondkHxBu6nuuulllͨXЁOKcÃTyI|DxYтwouVwFzAuV~mmmﵵ~szUSOnPNJ}Fy~x}^xH|FyAvppp\xmtYVRqSSNK~{a{MJ}Fzsssξ~[ԃZUlˌUzXR^؇w}`ÀPLXρuuuQw^]Z[ny\T{pxZTNtxxx#Sm]WXwkЍtZWb|}}}!Qo[W|]݈}|}`Zak~XXXx}u񙙙qlBFi??( @6mZPWp}_jʊW}dxmtOvOYtnh_emtVԀjݏwFvbAxTbu_kb]ވPZTklkRHOblw~yyyb{mwZ\ZfrM}[yW\U}@sxG}ypemNzMWzlxI|uuuJt\idٌr[؄Z~t{h`\^Dwooo|uql}}}]qdYZvx;qaqzmbiR_}il»Q>uiiiJX~[ӃXsor`ىWNTR{\yY݆d}m\Dy\uX؁~UNH|}y|K~VrjbglikNxWFymmmwww]AvM|V~\RX8nY=rOCwUMHw{{{Gz|ty_܈hۏVL?tS[Q|w{l}sYu^mdj\>vD{IAwI~WQ`lz[Մdy\YO-M--M]Md-M--M]]ctdMz77cM(t77n4}Gzonno(ctcܸ/=2ڣt 6e%ګcfSb wqN~G(SA\䖸":\?v4gMd{L=K֏D?tC\?u] ŜpA\l tؓ\\?4tgc}(r耔L 5L\\lcgd+r>nFˁLLy$t*MиsXrE>FEŜL_tdߊ3iʗrHxŜޡ&v~=r5m}y|d}mDyCv@s7nl}}}jjj֌Up}JK~I|GzU}NzCw@tHwpemNxExCv@s6mpppkkkIxmtX؁OMK~G|nqzG}GzDw;q|w{\uH|ExCv?sR{|||nnnլjlkSROMH~~YuNK~H{AvjʊfrI~H{ExCv;qpppblVTRPIVzQNK~G{[Ӄl}sLJ}H{Fy>uppp_}iYVTRMWzUQNJ~[ՄmtNMK~H{AxrrrռZ\Z[YWTOVrXURLiɉlxQOMK~D{uuuZPW]ވ[YWTx`l\YUOhSRPMVԀuuuUW}d_[YVdٌY݆\YX~t{`ىWURMu~~~zzzRWNT[y_\YT]qd^Zw~\ZWTbz}}}jjj_RHOYt_\Yhۏ\ixyjݏ_]Zb{xmtZjbgPZTZv[؄W|ra_܈\y_kb|ty {mdjjagsor`\^\RXh_e|zzzqqq1񓓓- Dhqd= AfqfA (0` 4j[QWr|bjʊUexnuPuOYtng^dmtTjݏwGu`ĂAxT`r_kb^ߊN_TjnlMDIblzyyyb}pzw][\itM|Ww[]V}?svG}xpinO{M[}jvI|vtuJtXjdٌk}\؅Yu|f}bY_Cvpno|tqm}}}ZrbY]wy;r_t}laiR_}in»R>thihJ]]͂Ysor`ىXNTR{_{Y߆e}m]Dz`vX؁ULG|}y|LVrjbgkjkLxVFyolnxvw_AvM}Ỳ^V[9nX=rPDxTOHw{{{Fy{tx_܈k׎UM?tS[Q|w{l}sYu_mek\?wD{KAxJXQ`lz[Մdy\YO7gMM]Mg*gMM]M-cɬM--(]-M--M(]t-td*c-(z-7dc-(g77tttt7}-zMtt޽b-zg77on4P}ڽdz]b~~Obg]c77fP7}yl?P]Mcbܞf4 \??egڡ*D????8SK4q\???}bzc7PPd6\\????ctPPZlo(\\???}7]򔔧B\\?.^p??uc8\\???=c*ULLL\\y"BD\???*A\\??? d-M}GŜLLD^GHAL\\ t6L\\???tc(HŜLLKSFLL\?nߡC8L\\??:ɡdtCŜL@LL\ydMLL\\?/d-ɞrŜzxLLy:5LL\\g-ɭ[rGhrLL ɮ5ҜLL\\oɮ-߭GrʭhrL tɮHŜLL÷t-ڣrbI3XrA$ttHŜLLttg [jirיko [;irtZrŜLf 7tcthiirOJBFirrWV77rrŜA77-9)|iir Phi0 |rr^7M7J[ w>t;r?sS}wnnnjjjvvv Іj=r;pq=p;oXw~]zfˇ~laiPuDwCvAt@s>q8nLxjjjsssg M|EyDwCvBu@s?r=qe}:q7mlNJwuwhrlEyDwCvAt@s>q;oEtwwwjjj{{{v~V~H|GzFyExCvBu>sb}s}?wAt?r7lmekUvFyDwCvAt@s>q:oV}mmmrrr| 뚚y}k}JJ}I|H{FyExCv?txO{FyCvAt>rBtxvxf~nG|FyExCvAt@s>q7myyyooo>>>MumsRMLK~I|H{FyExO{huH}H{FyDwBu:onckM{H|FyExCvBu@s=qHvlllppp8jvOOMLK~I|H{EydX|LJ}H{FyDwAuS~pinXxI|H{FyExCvBu@s8oqqqnnn}pin`{RPOMLK~I|F{ttiqRNLJ}H{FyDw=sxsw`vKI|H{FyExCvBu=rpxxxoooܡg]c[́TRQOMLK~F|fhgRPNLJ}H{Fy?u}z|gtKK~J}H{FyExDwAu]ȁ}}}ooo榦bX_Z݅UTRQONLH~_sfURPNLJ}H{Ax~jtMLK~J}H{GzExBvT}qqq榦]TZ[݆WUTRQONI[obWUSPNLJ}DzlvONMK~J}H{GzDxU~rrrץZRW\˂YWVTRQOK]]\YWUSPNLF}~kxPPNMK~J}H{Fz_ƂsssaZ_]wZYWVTSQLyeZa[YWUSQNJ|w{g}RRPNMK~J}H|nuuu|y{Zuc]ZYWVTSPlNJzZ|]ZWUSQ]؆yrwbÂUSRPNMK~F}yyyvvv}BQHN^\ZYWVTSYRr]_\ZWUPzynv\WUSRPOMUրwwwvvv8㧧a\_Xf_\ZYWVTOf\b]ׄ^\ZX[t}YXWUSRPLtyyy~LAH\ȁ_\[YXVSoʍN_Ta^\X||syhʉ\ZYWUTQ`āw~||||}~NGK^ߊ_\[YXVS~Rcb]oz_]\ZYWT[لxsv}}}ꈈptqrMIL\̂_\[YXUjԍwntSinu|kُa_^\ZY^΂qln~~~dK@GXj^][YXTqmp|wdba_^]bui`fܛ]X\NHLWh[}[ZXW}|||||zeedb^Ā]m]WZpjnӏ#ڙxtw]V[TJPXNT[PW_V[kijQNPC9?E;BI?EODK_W\{vyؕÕy񗗗𔔔sZZZᔔ|铓薖 NÔ㒒쑑ޏ>FᒒᐐE??pyface-4.1.0/pyface/ui/wx/images/warning.png0000644000175100001440000000257711674463363021736 0ustar ischnellusers00000000000000PNG  IHDR DsRGBPLTE      )$1) ;->4B3C7@7K:E<TA8WI[H9 SK: ; < ;==<= AeP@ D BCC D F JHFH GHF r[KJK I NJONTOM|cQO P SzgRfTSPRQRSST ^TU YZo*q[s\u r blyx3vpu::tu{}};| Ȃ} Ņ}ƆɈ ; ʉ ˊ͋ȍބ"߅# 3 ГŚ՗ߕӛ͡ߟ̥֤)ש$ܩ/ӱ2߬2ҮG3ݳ546չ ж` Ѹi..0&   "b$&22'34**? @7ABJ90MEUNVOHQY`ASbiT\refmn<{AtRNS@fbKGDH pHYs{tIME "(ZMIDAT8c` xzj#<{3 B߻=o^^gfKA=KLZKM6FFƈyf.{T%O@1gb5`@ 7d`Ssa*v}N֥cjǾ\@w^wժu */^ܷ*]O**ټsJh׮*_nݪUJ.Jo$Tмiڵkȣ0$i(PA'vm<`m iYrL[ ޶`m %kd oۻwoM(ڻ [ ޽ S+ 7&CUÒ:ЀM`C|<"}Uld)w؀C1| hD0܀m?9 0: f = open(self.path, 'r') text = f.read() f.close() else: text = '' self.control.SetText(text) self.dirty = False return def save(self, path=None): """ Saves the contents of the editor. """ if path is None: path = self.path f = file(path, 'w') f.write(self.control.GetText()) f.close() self.dirty = False return def set_style(self, n, fore, back): self.control.StyleSetForeground(n, fore) #self.StyleSetBackground(n, '#c0c0c0') #self.StyleSetBackground(n, '#ffffff') self.control.StyleSetBackground(n, back) self.control.StyleSetFaceName(n, "courier new") self.control.StyleSetSize(n, faces['size']) #self.StyleSetForeground(n, "#f0f0f0") ##self.StyleSetBackground(n, "#000000") #self.StyleSetFaceName(n, "courier new") #self.StyleSetSize(n, 20) #self.StyleSetUnderline(n, 1) #self.StyleSetItalic(n, 1) #self.StyleSetBold(n, 1) #StyleClearAll #StyleResetDefault #StyleSetCase #StyleSetChangeable #StyleSetCharacterSet #StyleSetEOLFilled #StyleSetFont #StyleSetFontAttr #StyleSetHotSpot #StyleSetSpec --- batch #StyleSetVisible return def select_line(self, lineno): """ Selects the specified line. """ start = self.control.PositionFromLine(lineno) end = self.control.GetLineEndPosition(lineno) self.control.SetSelection(start, end) return ########################################################################### # Trait handlers. ########################################################################### def _path_changed(self): """ Handle a change to path. """ self._changed_path() return ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Creates the toolkit-specific control for the widget. """ # Base-class constructor. self.control = stc = PythonSTC(parent, -1) # No folding! stc.SetProperty("fold", "0") # Mark the maximum line size. stc.SetEdgeMode(wx.stc.STC_EDGE_LINE) stc.SetEdgeColumn(79) # Display line numbers in the margin. if self.show_line_numbers: stc.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER) stc.SetMarginWidth(1, 45) self.set_style(wx.stc.STC_STYLE_LINENUMBER, "#000000", "#c0c0c0") else: stc.SetMarginWidth(1, 4) self.set_style(wx.stc.STC_STYLE_LINENUMBER, "#ffffff", "#ffffff") # Create 'tabs' out of spaces! stc.SetUseTabs(False) # One 'tab' is 4 spaces. stc.SetIndent(4) # Line ending mode. stc.SetEOLMode(wx.stc.STC_EOL_LF) # Unix #self.SetEOLMode(wx.stc.STC_EOL_CR) # Apple Mac #self.SetEOLMode(wx.stc.STC_EOL_CRLF) # Windows ########################################## # Global styles for all languages. ########################################## self.set_style(wx.stc.STC_STYLE_DEFAULT, "#000000", "#ffffff") self.set_style(wx.stc.STC_STYLE_CONTROLCHAR, "#000000", "#ffffff") self.set_style(wx.stc.STC_STYLE_BRACELIGHT, "#000000", "#ffffff") self.set_style(wx.stc.STC_STYLE_BRACEBAD, "#000000", "#ffffff") ########################################## # Python styles. ########################################## # White space self.set_style(wx.stc.STC_P_DEFAULT, "#000000", "#ffffff") # Comment self.set_style(wx.stc.STC_P_COMMENTLINE, "#007f00", "#ffffff") # Number self.set_style(wx.stc.STC_P_NUMBER, "#007f7f", "#ffffff") # String self.set_style(wx.stc.STC_P_STRING, "#7f007f", "#ffffff") # Single quoted string self.set_style(wx.stc.STC_P_CHARACTER, "#7f007f", "#ffffff") # Keyword self.set_style(wx.stc.STC_P_WORD, "#00007f", "#ffffff") # Triple quotes self.set_style(wx.stc.STC_P_TRIPLE, "#7f0000", "#ffffff") # Triple double quotes self.set_style(wx.stc.STC_P_TRIPLEDOUBLE, "#ff0000", "#ffffff") # Class name definition self.set_style(wx.stc.STC_P_CLASSNAME, "#0000ff", "#ffffff") # Function or method name definition self.set_style(wx.stc.STC_P_DEFNAME, "#007f7f", "#ffffff") # Operators self.set_style(wx.stc.STC_P_OPERATOR, "#000000", "#ffffff") # Identifiers self.set_style(wx.stc.STC_P_IDENTIFIER, "#000000", "#ffffff") # Comment-blocks self.set_style(wx.stc.STC_P_COMMENTBLOCK, "#007f00", "#ffffff") # End of line where string is not closed self.set_style(wx.stc.STC_P_STRINGEOL, "#000000", "#ffffff") ########################################## # Events. ########################################## # By default, the will fire EVT_STC_CHANGE evented for all mask values # (STC_MODEVENTMASKALL). This generates too many events. stc.SetModEventMask(wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT | wx.stc.STC_PERFORMED_UNDO | wx.stc.STC_PERFORMED_REDO) # Listen for changes to the file. wx.stc.EVT_STC_CHANGE(stc, stc.GetId(), self._on_stc_changed) # Listen for key press events. wx.EVT_CHAR(stc, self._on_char) # Load the editor's contents. self.load() return stc #### wx event handlers #################################################### def _on_stc_changed(self, event): """ Called whenever a change is made to the text of the document. """ self.dirty = True self.changed = True # Give other event handlers a chance. event.Skip() return def _on_char(self, event): """ Called whenever a change is made to the text of the document. """ self.key_pressed = KeyPressedEvent( alt_down = event.m_altDown == 1, control_down = event.m_controlDown == 1, shift_down = event.m_shiftDown == 1, key_code = event.m_keyCode, event = event ) # Give other event handlers a chance. event.Skip() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/application_window.py0000644000175100001440000002012411674463363022546 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import sys # Major package imports. import wx try: import wx.aui AUI = True except ImportError: AUI = False # Enthought library imports. from pyface.action.api import MenuBarManager, StatusBarManager from pyface.action.api import ToolBarManager from traits.api import implements, Instance, List, Unicode from pyface.i_application_window import IApplicationWindow from pyface.i_application_window import MApplicationWindow from pyface.image_resource import ImageResource # Local imports. from window import Window from system_metrics import SystemMetrics class ApplicationWindow(MApplicationWindow, Window): """ The toolkit specific implementation of an ApplicationWindow. See the IApplicationWindow interface for the API documentation. """ implements(IApplicationWindow) #### 'IApplicationWindow' interface ####################################### icon = Instance(ImageResource) menu_bar_manager = Instance(MenuBarManager) status_bar_manager = Instance(StatusBarManager) tool_bar_manager = Instance(ToolBarManager) # If the underlying toolkit supports multiple toolbars then you can use # this list instead. tool_bar_managers = List(ToolBarManager) #### 'IWindow' interface ################################################## # fixme: We can't set the default value of the actual 'size' trait here as # in the toolkit-specific event handlers for window size and position # changes, we set the value of the shadow '_size' trait. The problem is # that by doing that traits never knows that the trait has been set and # hence always returns the default value! Using a trait initializer method # seems to work however (e.g. 'def _size_default'). Hmmmm.... ## size = (800, 600) title = Unicode("Pyface") ########################################################################### # Protected 'IApplicationWindow' interface. ########################################################################### def _create_contents(self, parent): panel = wx.Panel(parent, -1) panel.SetSize((500, 400)) panel.SetBackgroundColour('blue') return panel def _create_menu_bar(self, parent): if self.menu_bar_manager is not None: menu_bar = self.menu_bar_manager.create_menu_bar(parent) self.control.SetMenuBar(menu_bar) def _create_status_bar(self, parent): if self.status_bar_manager is not None: status_bar = self.status_bar_manager.create_status_bar(parent) self.control.SetStatusBar(status_bar) return def _create_tool_bar(self, parent): tool_bar_managers = self._get_tool_bar_managers() if len(tool_bar_managers) > 0: if AUI: for tool_bar_manager in reversed(tool_bar_managers): tool_bar = tool_bar_manager.create_tool_bar(parent) self._add_toolbar_to_aui_manager( tool_bar, tool_bar_manager.name ) else: tool_bar = tool_bar_managers[0].create_tool_bar(parent) self.control.SetToolBar(tool_bar) def _set_window_icon(self): if self.icon is None: icon = ImageResource('application.ico') else: icon = self.icon if self.control is not None: self.control.SetIcon(icon.create_icon()) return ########################################################################### # 'Window' interface. ########################################################################### def _size_default(self): """ Trait initialiser. """ return (800, 600) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): if AUI: # fixme: We have to capture the AUI manager as an attribute, # otherwise it gets garbage collected and we get a core dump... # Ahh, the sweet smell of open-source ;^() self._aui_manager = wx.aui.AuiManager() super(ApplicationWindow, self)._create() if AUI: body = self._create_body(self.control) contents = self._create_contents(body) body.GetSizer().Add(contents, 1, wx.EXPAND) body.Fit() else: contents = self._create_contents(self.control) self._create_trim_widgets(self.control) if AUI: # Updating the AUI manager actually commits all of the pane's added # to it (this allows batch updates). self._aui_manager.Update() return def _create_control(self, parent): style = wx.DEFAULT_FRAME_STYLE \ | wx.FRAME_NO_WINDOW_MENU \ | wx.CLIP_CHILDREN control = wx.Frame( parent, -1, self.title, style=style, size=self.size, pos=self.position ) control.SetBackgroundColour(SystemMetrics().dialog_background_color) if AUI: # Let the AUI manager look after the frame. self._aui_manager.SetManagedWindow(control) return control ########################################################################### # Private interface. ########################################################################### def _add_toolbar_to_aui_manager(self, tool_bar, name='Tool Bar'): """ Add a toolbar to the AUI manager. """ info = wx.aui.AuiPaneInfo() info.Caption(name) info.LeftDockable(False) info.Name(name) info.RightDockable(False) info.ToolbarPane() info.Top() self._aui_manager.AddPane(tool_bar, info) return def _create_body(self, parent): """ Create the body of the frame. """ panel = wx.Panel(parent, -1) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) info = wx.aui.AuiPaneInfo() info.Caption('Body') info.Dockable(False) info.Floatable(False) info.Name('Body') info.CentrePane() self._aui_manager.AddPane(panel, info) return panel def _get_tool_bar_managers(self): """ Return all tool bar managers specified for the window. """ # fixme: V3 remove the old-style single toolbar option! if self.tool_bar_manager is not None: tool_bar_managers = [self.tool_bar_manager] else: tool_bar_managers = self.tool_bar_managers return tool_bar_managers def _wx_enable_tool_bar(self, tool_bar, enabled): """ Enable/Disablea tool bar. """ if AUI: # AUI toolbars cannot be enabled/disabled. pass else: tool_bar.Enable(enabled) return def _wx_show_tool_bar(self, tool_bar, visible): """ Hide/Show a tool bar. """ if AUI: pane = self._aui_manager.GetPane(tool_bar.tool_bar_manager.name) if visible: pane.Show() else: pane.Hide() self._aui_manager.Update() else: tool_bar.Show(visible) return #### Trait change handlers ################################################ def _icon_changed(self): self._set_window_icon() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/file_dialog.py0000644000175100001440000001020611674463363021112 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import os # Major package imports. import wx # Enthought library imports. from traits.api import Enum, implements, Unicode, Int, List # Local imports. from pyface.i_file_dialog import IFileDialog, MFileDialog from dialog import Dialog class FileDialog(MFileDialog, Dialog): """ The toolkit specific implementation of a FileDialog. See the IFileDialog interface for the API documentation. """ implements(IFileDialog) #### 'IFileDialog' interface ############################################## action = Enum('open', 'open files', 'save as') default_directory = Unicode default_filename = Unicode default_path = Unicode directory = Unicode filename = Unicode path = Unicode paths = List(Unicode) wildcard = Unicode wildcard_index = Int(0) ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In wx this is a canned dialog. pass ########################################################################### # 'IWindow' interface. ########################################################################### def close(self): # Get the path of the chosen directory. self.path = unicode(self.control.GetPath()) # Work around wx bug throwing exception on cancel of file dialog if len(self.path)>0: self.paths = self.control.GetPaths() else: self.paths = [] # Extract the directory and filename. self.directory, self.filename = os.path.split(self.path) # Get the index of the selected filter. self.wildcard_index = self.control.GetFilterIndex() # Let the window close as normal. super(FileDialog, self).close() ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # If the caller provided a default path instead of a default directory # and filename, split the path into it directory and filename # components. if len(self.default_path) != 0 and len(self.default_directory) == 0 \ and len(self.default_filename) == 0: default_directory, default_filename = os.path.split(self.default_path) else: default_directory = self.default_directory default_filename = self.default_filename if self.action == 'open': style = wx.OPEN elif self.action == 'open files': style = wx.OPEN | wx.MULTIPLE else: style = wx.SAVE | wx.OVERWRITE_PROMPT # Create the actual dialog. dialog = wx.FileDialog(parent, self.title, defaultDir=default_directory, defaultFile=default_filename, style=style, wildcard=self.wildcard.rstrip('|')) dialog.SetFilterIndex(self.wildcard_index) return dialog ########################################################################### # Trait handlers. ########################################################################### def _wildcard_default(self): """ Return the default wildcard. """ return self.WILDCARD_ALL #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/resource_manager.py0000644000175100001440000000470111674463363022200 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import os import tempfile from cStringIO import StringIO # Major package imports. import wx # Enthought library imports. from pyface.resource.api import ResourceFactory from traits.api import Undefined class PyfaceResourceFactory(ResourceFactory): """ The implementation of a shared resource manager. """ ########################################################################### # 'ResourceFactory' toolkit interface. ########################################################################### def image_from_file(self, filename): """ Creates an image from the data in the specified filename. """ # N.B 'wx.BITMAP_TYPE_ANY' tells wxPython to attempt to autodetect the # --- image format. return wx.Image(filename, wx.BITMAP_TYPE_ANY) def image_from_data(self, data, filename=None): """ Creates an image from the specified data. """ try: return wx.ImageFromStream(StringIO(data)) except: # wx.ImageFromStream is only in wx 2.8 or later(?) if filename is Undefined: return None handle = None if filename is None: # If there is currently no way in wx to create an image from data, # we have write it out to a temporary file and then read it back in: handle, filename = tempfile.mkstemp() # Write it out... tf = open(filename, 'wb') tf.write(data) tf.close() # ... and read it back in! Lovely 8^() image = wx.Image(filename, wx.BITMAP_TYPE_ANY) # Remove the temporary file. if handle is not None: os.close(handle) os.unlink(filename) return image #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/split_widget.py0000644000175100001440000001201211674463363021347 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Mix-in class for split widgets. """ # Major package imports. import wx # Enthought library imports. from traits.api import Callable, Enum, Float, HasTraits, implements # Local imports. from pyface.i_split_widget import ISplitWidget, MSplitWidget class SplitWidget(MSplitWidget, HasTraits): """ The toolkit specific implementation of a SplitWidget. See the ISPlitWidget interface for the API documentation. """ implements(ISplitWidget) #### 'ISplitWidget' interface ############################################# direction = Enum('vertical', 'vertical', 'horizontal') ratio = Float(0.5) lhs = Callable rhs = Callable ########################################################################### # Protected 'ISplitWidget' interface. ########################################################################### def _create_splitter(self, parent): """ Create the toolkit-specific control that represents the widget. """ sizer = wx.BoxSizer(wx.VERTICAL) splitter = wx.SplitterWindow(parent, -1, style=wx.CLIP_CHILDREN) splitter.SetSizer(sizer) splitter.SetAutoLayout(True) # If we don't set the minimum pane size, the user can drag the sash and # make one pane disappear! splitter.SetMinimumPaneSize(50) # Left hand side/top. lhs = self._create_lhs(splitter) sizer.Add(lhs, 1, wx.EXPAND) # Right hand side/bottom. rhs = self._create_rhs(splitter) sizer.Add(rhs, 1, wx.EXPAND) # Resize the splitter to fit the sizer's minimum size. sizer.Fit(splitter) # Split the window in the appropriate direction. # # fixme: Notice that on the initial split, we DON'T specify the split # ratio. If we do then sadly, wx won't let us move the sash 8^() if self.direction == 'vertical': splitter.SplitVertically(lhs, rhs) else: splitter.SplitHorizontally(lhs, rhs) # We respond to the FIRST size event to make sure that the split ratio # is correct when the splitter is laid out in its parent. wx.EVT_SIZE(splitter, self._on_size) return splitter def _create_lhs(self, parent): """ Creates the left hand/top panel depending on the direction. """ if self.lhs is not None: lhs = self.lhs(parent) if hasattr(wx, 'WindowPtr'): if not isinstance(lhs, (wx.Window, wx.WindowPtr)): lhs = lhs.control else: # wx 2.8 did away with WindowPtr if not isinstance(lhs, wx.Window): lhs = lhs.control else: # Dummy implementation - override! lhs = wx.Panel(parent, -1) lhs.SetBackgroundColour("yellow") lhs.SetSize((300, 200)) return lhs def _create_rhs(self, parent): """ Creates the right hand/bottom panel depending on the direction. """ if self.rhs is not None: rhs = self.rhs(parent) if hasattr(wx, 'WindowPtr'): if not isinstance(rhs, (wx.Window, wx.WindowPtr)): rhs = rhs.control else: # wx 2.8 did away with WindowPtr if not isinstance(rhs, wx.Window): rhs = rhs.control else: # Dummy implementation - override! rhs = wx.Panel(parent, -1) rhs.SetBackgroundColour("green") rhs.SetSize((100, 200)) return rhs ########################################################################### # Private interface. ########################################################################### #### wx event handlers #################################################### def _on_size(self, event): """ Called when the frame is resized. """ splitter = event.GetEventObject() width, height = splitter.GetSize() # Make sure that the split ratio is correct. if self.direction == 'vertical': position = int(width * self.ratio) else: position = int(height * self.ratio) splitter.SetSashPosition(position) # Since we only care about the FIRST size event, remove ourselves as # a listener. #wx.EVT_SIZE(splitter, None) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/gui.py0000644000175100001440000001067011674463363017445 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import logging import sys # Major package imports. import wx # Enthought library imports. from traits.api import Bool, HasTraits, implements, Unicode from pyface.util.guisupport import start_event_loop_wx # Local imports. from pyface.i_gui import IGUI, MGUI # Logging. logger = logging.getLogger(__name__) class GUI(MGUI, HasTraits): implements(IGUI) #### 'GUI' interface ###################################################### busy = Bool(False) started = Bool(False) state_location = Unicode ########################################################################### # 'object' interface. ########################################################################### def __init__(self, splash_screen=None): # Display the (optional) splash screen. self._splash_screen = splash_screen if self._splash_screen is not None: self._splash_screen.open() ########################################################################### # 'GUI' class interface. ########################################################################### def invoke_after(cls, millisecs, callable, *args, **kw): wx.FutureCall(millisecs, callable, *args, **kw) invoke_after = classmethod(invoke_after) def invoke_later(cls, callable, *args, **kw): wx.CallAfter(callable, *args, **kw) invoke_later = classmethod(invoke_later) def set_trait_after(cls, millisecs, obj, trait_name, new): wx.FutureCall(millisecs, setattr, obj, trait_name, new) set_trait_after = classmethod(set_trait_after) def set_trait_later(cls, obj, trait_name, new): wx.CallAfter(setattr, obj, trait_name, new) set_trait_later = classmethod(set_trait_later) def process_events(allow_user_events=True): if allow_user_events: wx.GetApp().Yield(True) else: wx.SafeYield() process_events = staticmethod(process_events) def set_busy(busy=True): if busy: GUI._cursor = wx.BusyCursor() else: GUI._cursor = None set_busy = staticmethod(set_busy) ########################################################################### # 'GUI' interface. ########################################################################### def start_event_loop(self): """ Start the GUI event loop. """ if self._splash_screen is not None: self._splash_screen.close() # Make sure that we only set the 'started' trait after the main loop # has really started. self.set_trait_after(10, self, "started", True) # A hack to force menus to appear for applications run on Mac OS X. if sys.platform == 'darwin': def _mac_os_x_hack(): f = wx.Frame(None, -1) f.Show(True) f.Close() self.invoke_later(_mac_os_x_hack) logger.debug("---------- starting GUI event loop ----------") start_event_loop_wx() self.started = False def stop_event_loop(self): """ Stop the GUI event loop. """ logger.debug("---------- stopping GUI event loop ----------") wx.GetApp().ExitMainLoop() ########################################################################### # Trait handlers. ########################################################################### def _state_location_default(self): """ The default state location handler. """ return self._default_state_location() def _busy_changed(self, new): """ The busy trait change handler. """ if new: self._wx_cursor = wx.BusyCursor() else: del self._wx_cursor return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/ipython_widget.py0000644000175100001440000004226011674463363021716 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx-backend Pyface widget for an embedded IPython shell. """ # Standard library imports. import __builtin__ import codeop import re import sys # System library imports import IPython from IPython.frontend.wx.wx_frontend import WxController from IPython.kernel.core.interpreter import Interpreter import wx # Enthought library imports. from apptools.io.file import File as EnthoughtFile from pyface.i_python_shell import IPythonShell from pyface.key_pressed_event import KeyPressedEvent from traits.api import Event, implements, Instance, Str from traits.util.clean_strings import python_name from pyface.wx.drag_and_drop import PythonDropTarget # Local imports. from widget import Widget # Constants. IPYTHON_VERSION = tuple(map(int, IPython.Release.version_base.split('.'))) class IPythonController(WxController): """ An WxController for IPython version >= 0.9. Adds a few extras. """ def __init__(self, *args, **kwargs): WxController.__init__(self, *args, **kwargs) # Add a magic to clear the screen def cls(args): self.ClearAll() self.ipython0.magic_cls = cls class IPython010Controller(IPythonController): """ A WxController hacked/patched specifically for the 0.10 branch. """ def execute_command(self, command, hidden=False): # XXX: Overriden to fix bug where executing a hidden command still # causes the prompt number to increase. super(IPython010Controller, self).execute_command(command, hidden) if hidden: self.shell.current_cell_number -= 1 class IPython09Controller(IPythonController): """ A WxController hacked/patched specifically for the 0.9 branch. """ # In the parent class, this is a property that expects the # container to be a frame, thus it fails when modified. # The title of the IPython windows (not displayed in Envisage) title = Str # Cached value of the banner for the IPython shell. # NOTE: The WxController object (declared in wx_frontend module) contains # a 'banner' attribute. If this is None, WxController sets the # banner = IPython banner + an additional string ("This is the wx # frontend, by Gael Varoquaux. This is EXPERIMENTAL code."). We want to # set banner = the IPython banner. This means 'banner' needs to be # a property, since in the __init__ method of WxController, the IPython # shell object is created AND started (meaning the banner is written to # stdout). _banner = None def _get_banner(self): """ Returns the IPython banner. """ if self._banner is None: # 'ipython0' gets set in the __init__ method of the base class. if hasattr(self, 'ipython0'): self._banner = self.ipython0.BANNER return self._banner def _set_banner(self, value): self._banner = value banner = property(_get_banner, _set_banner) def __init__(self, *args, **kwargs): # This is a hack to avoid the IPython exception hook to trigger # on exceptions (https://bugs.launchpad.net/bugs/337105) # XXX: This is horrible: module-level monkey patching -> side # effects. from IPython import iplib iplib.InteractiveShell.isthreaded = True # Suppress all key input, to avoid waiting def my_rawinput(x=None): return '\n' old_rawinput = __builtin__.raw_input __builtin__.raw_input = my_rawinput IPythonController.__init__(self, *args, **kwargs) __builtin__.raw_input = old_rawinput # XXX: This is bugware for IPython bug: # https://bugs.launchpad.net/ipython/+bug/270998 # Fix all the magics with no docstrings: for funcname in dir(self.ipython0): if not funcname.startswith('magic'): continue func = getattr(self.ipython0, funcname) try: if func.__doc__ is None: func.__doc__ = '' except AttributeError: """ Avoid "attribute '__doc__' of 'instancemethod' objects is not writable". """ def complete(self, line): """ Returns a list of possible completions for line. Overridden from the base class implementation to fix bugs in retrieving the completion text from line. """ completion_text = self._get_completion_text(line) suggestion, completions = super(IPython09Controller, self).complete( \ completion_text) new_line = line[:-len(completion_text)] + suggestion return new_line, completions def is_complete(self, string): """ Check if a string forms a complete, executable set of commands. For the line-oriented frontend, multi-line code is not executed as soon as it is complete: the users has to enter two line returns. Overridden from the base class (linefrontendbase.py in IPython\frontend to handle a bug with using the '\' symbol in multi-line inputs. """ # FIXME: There has to be a nicer way to do this. Th code is # identical to the base class implementation, except for the if .. # statement on line 146. if string in ('', '\n'): # Prefiltering, eg through ipython0, may return an empty # string although some operations have been accomplished. We # thus want to consider an empty string as a complete # statement. return True elif ( len(self.input_buffer.split('\n'))>2 and not re.findall(r"\n[\t ]*\n[\t ]*$", string)): return False else: self.capture_output() try: # Add line returns here, to make sure that the statement is # complete (except if '\' was used). # This should probably be done in a different place (like # maybe 'prefilter_input' method? For now, this works. clean_string = string.rstrip('\n') if not clean_string.endswith('\\'): clean_string +='\n\n' is_complete = codeop.compile_command(clean_string, "", "exec") self.release_output() except Exception, e: # XXX: Hack: return True so that the # code gets executed and the error captured. is_complete = True return is_complete def execute_command(self, command, hidden=False): """ Execute a command, not only in the model, but also in the view. """ # XXX: This needs to be moved to the IPython codebase. if hidden: result = self.shell.execute(command) # XXX: Fix bug where executing a hidden command still causes the # prompt number to increase. self.shell.current_cell_number -= 1 return result else: # XXX: we are not storing the input buffer previous to the # execution, as this forces us to run the execution # input_buffer a yield, which is not good. ##current_buffer = self.shell.control.input_buffer command = command.rstrip() if len(command.split('\n')) > 1: # The input command is several lines long, we need to # force the execution to happen command += '\n' cleaned_command = self.prefilter_input(command) self.input_buffer = command # Do not use wx.Yield() (aka GUI.process_events()) to avoid # recursive yields. self.ProcessEvent(wx.PaintEvent()) self.write('\n') if not self.is_complete(cleaned_command + '\n'): self._colorize_input_buffer() self.render_error('Incomplete or invalid input') self.new_prompt(self.input_prompt_template.substitute( number=(self.last_result['number'] + 1))) return False self._on_enter() return True def clear_screen(self): """ Empty completely the widget. """ self.ClearAll() self.new_prompt(self.input_prompt_template.substitute( number=(self.last_result['number'] + 1))) def continuation_prompt(self): """Returns the current continuation prompt. Overridden to generate a continuation prompt matching the length of the current prompt.""" # This assumes that the prompt is always of the form 'In [#]'. n = self.last_result['number'] promptstr = "In [%d]" % n return ("."*len(promptstr) + ':') def _popup_completion(self, create=False): """ Updates the popup completion menu if it exists. If create is true, open the menu. Overridden from the base class implementation to filter out delimiters from the input buffer. """ # FIXME: The implementation in the base class (wx_frontend.py in # IPython/wx/frontend) is faulty in that it doesn't filter out # special characters (such as parentheses, '=') in the input buffer # correctly. # For example, (a): typing 's=re.' does not pop up the menu. # (b): typing 'x[0].' brings up a menu for x[0] but the offset is # incorrect and so, upon selection from the menu, the text is pasted # incorrectly. # I am patching this here instead of in the IPython module, but at some # point, this needs to be merged in. if self.debug: print >>sys.__stdout__, "_popup_completion" , self.input_buffer line = self.input_buffer if (self.AutoCompActive() and line and not line[-1] == '.') \ or create==True: suggestion, completions = self.complete(line) if completions: offset = len(self._get_completion_text(line)) self.pop_completion(completions, offset=offset) if self.debug: print >>sys.__stdout__, completions def _get_completion_text(self, line): """ Returns the text to be completed by breaking the line at specified delimiters. """ # Break at: spaces, '=', all parentheses (except if balanced). # FIXME2: In the future, we need to make the implementation similar to # that in the 'pyreadline' module (modes/basemode.py) where we break at # each delimiter and try to complete the residual line, until we get a # successful list of completions. expression = '\s|=|,|:|\((?!.*\))|\[(?!.*\])|\{(?!.*\})' complete_sep = re.compile(expression) text = complete_sep.split(line)[-1] return text def _on_enter(self): """ Called when the return key is pressed in a line editing buffer. Overridden from the base class implementation (in IPython/frontend/linefrontendbase.py) to include a continuation prompt. """ current_buffer = self.input_buffer cleaned_buffer = self.prefilter_input(current_buffer.replace( self.continuation_prompt(), '')) if self.is_complete(cleaned_buffer): self.execute(cleaned_buffer, raw_string=current_buffer) else: self.input_buffer = current_buffer + \ self.continuation_prompt() + \ self._get_indent_string(current_buffer.replace( self.continuation_prompt(), '')[:-1]) if len(current_buffer.split('\n')) == 2: self.input_buffer += '\t\t' if current_buffer[:-1].split('\n')[-1].rstrip().endswith(':'): self.input_buffer += '\t' class IPythonWidget(Widget): """ The toolkit specific implementation of a PythonShell. See the IPythonShell interface for the API documentation. """ implements(IPythonShell) #### 'IPythonShell' interface ############################################# command_executed = Event key_pressed = Event(KeyPressedEvent) #### 'IPythonWidget' interface ############################################ interp = Instance(Interpreter, ()) ########################################################################### # 'object' interface. ########################################################################### # FIXME v3: Either make this API consistent with other Widget sub-classes # or make it a sub-class of HasTraits. def __init__(self, parent, **traits): """ Creates a new pager. """ # Base class constructor. super(IPythonWidget, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) ########################################################################### # 'IPythonShell' interface. ########################################################################### def interpreter(self): return self.interp def execute_command(self, command, hidden=True): self.control.execute_command(command, hidden=hidden) self.command_executed = True def execute_file(self, path, hidden=True): self.control.execute_command('%run ' + '"%s"' % path, hidden=hidden) self.command_executed = True ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # Create the controller based on the version of the installed IPython klass = IPythonController if IPYTHON_VERSION[0] == 0: if IPYTHON_VERSION[1] == 9: klass = IPython09Controller elif IPYTHON_VERSION[1] == 10: klass = IPython010Controller shell = klass(parent, -1, shell=self.interp) # Listen for key press events. wx.EVT_CHAR(shell, self._wx_on_char) # Enable the shell as a drag and drop target. shell.SetDropTarget(PythonDropTarget(self)) return shell ########################################################################### # 'PythonDropTarget' handler interface. ########################################################################### def on_drop(self, x, y, obj, default_drag_result): """ Called when a drop occurs on the shell. """ # If this is a file, we'll just print the file name if isinstance(obj, EnthoughtFile): self.control.write(obj.absolute_path) elif ( isinstance(obj, list) and len(obj) ==1 and isinstance(obj[0], EnthoughtFile)): self.control.write(obj[0].absolute_path) else: # Not a file, we'll inject the object in the namespace # If we can't create a valid Python identifier for the name of an # object we use this instead. name = 'dragged' if hasattr(obj, 'name') \ and isinstance(obj.name, basestring) and len(obj.name) > 0: py_name = python_name(obj.name) # Make sure that the name is actually a valid Python identifier. try: if eval(py_name, {py_name : True}): name = py_name except: pass self.interp.user_ns[name] = obj self.execute_command(name, hidden=False) self.control.SetFocus() # We always copy into the shell since we don't want the data # removed from the source return wx.DragCopy def on_drag_over(self, x, y, obj, default_drag_result): """ Always returns wx.DragCopy to indicate we will be doing a copy.""" return wx.DragCopy ########################################################################### # Private handler interface. ########################################################################### def _wx_on_char(self, event): """ Called whenever a change is made to the text of the document. """ self.key_pressed = KeyPressedEvent( alt_down = event.m_altDown == 1, control_down = event.m_controlDown == 1, shift_down = event.m_shiftDown == 1, key_code = event.m_keyCode, event = event ) # Give other event handlers a chance. event.Skip() pyface-4.1.0/pyface/ui/wx/about_dialog.py0000644000175100001440000001003411674463363021304 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import sys # Major package imports. import wx import wx.html import wx.lib.wxpTag # Enthought library imports. from traits.api import implements, Instance, List, Unicode # Local imports. from pyface.i_about_dialog import IAboutDialog, MAboutDialog from pyface.image_resource import ImageResource from dialog import Dialog _DIALOG_TEXT = '''

%s

Python %s
wxPython %s

Copyright © 2003-2010 Enthought, Inc.

''' class AboutDialog(MAboutDialog, Dialog): """ The toolkit specific implementation of an AboutDialog. See the IAboutDialog interface for the API documentation. """ implements(IAboutDialog) #### 'IAboutDialog' interface ############################################# additions = List(Unicode) image = Instance(ImageResource, ImageResource('about')) ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): if parent.GetParent() is not None: title = parent.GetParent().GetTitle() else: title = "" # Set the title. self.title = "About %s" % title # Load the image to be displayed in the about box. image = self.image.create_image() path = self.image.absolute_path # The additional strings. additions = '
'.join(self.additions) # The width of a wx HTML window is fixed (and is given in the # constructor). We set it to the width of the image plus a fudge # factor! The height of the window depends on the content. width = image.GetWidth() + 80 html = wx.html.HtmlWindow(parent, -1, size=(width, -1)) # Get the version numbers. py_version = sys.version[0:sys.version.find("(")] wx_version = wx.VERSION_STRING # Get the text of the OK button. if self.ok_label is None: ok = "OK" else: ok = self.ok_label # Set the page contents. html.SetPage( _DIALOG_TEXT % (path, additions, py_version, wx_version, ok) ) # Make the 'OK' button the default button. ok_button = html.FindWindowById(wx.ID_OK) ok_button.SetDefault() # Set the height of the HTML window to match the height of the content. internal = html.GetInternalRepresentation() html.SetSize((-1, internal.GetHeight())) # Make the dialog client area big enough to display the HTML window. # We add a fudge factor to the height here, although I'm not sure why # it should be necessary, the HTML window should report its required # size!?! width, height = html.GetSize() parent.SetClientSize((width, height + 10)) ### EOF ####################################################################### pyface-4.1.0/pyface/ui/wx/workbench/0000755000175100001440000000000011674463363020265 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/workbench/view.py0000644000175100001440000000411111674463363021606 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Enthought library imports. from traits.api import Bool from pyface.workbench.i_view import MView class View(MView): """ The toolkit specific implementation of a View. See the IView interface for the API documentation. """ # Trait to indicate if the dock window containing the view should be # closeable. See FIXME comment in the _wx_create_view_dock_control method # in workbench_window_layout.py. closeable = Bool(False) ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ import wx # By default we create a red panel! control = wx.Panel(parent, -1) control.SetBackgroundColour("red") control.SetSize((100, 200)) return control def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ if self.control is not None: self.control.Destroy() self.control = None return def set_focus(self): """ Set the focus to the appropriate control in the part. """ if self.control is not None: self.control.SetFocus() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/editor.py0000644000175100001440000000353311674463363022131 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Local imports. from pyface.workbench.i_editor import MEditor class Editor(MEditor): """ The toolkit specific implementation of an Editor. See the IEditor interface for the API documentation. """ ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ import wx # By default we create a yellow panel! control = wx.Panel(parent, -1) control.SetBackgroundColour("yellow") control.SetSize((100, 200)) return control def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ if self.control is not None: self.control.Destroy() self.control = None return def set_focus(self): """ Set the focus to the appropriate control in the part. """ if self.control is not None: self.control.SetFocus() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/workbench_window_layout.py0000644000175100001440000006721511674463363025620 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx implementation of the workbench window layout interface. """ # Standard library imports. import cPickle import logging # Major package imports. import wx # Enthought library imports. from pyface.dock.api import DOCK_BOTTOM, DOCK_LEFT, DOCK_RIGHT from pyface.dock.api import DOCK_TOP from pyface.dock.api import DockControl, DockRegion, DockSection from pyface.dock.api import DockSizer from traits.api import Delegate from traitsui.dockable_view_element import DockableViewElement # Mixin class imports. from pyface.workbench.i_workbench_window_layout import \ MWorkbenchWindowLayout # Local imports. from editor_set_structure_handler import EditorSetStructureHandler from view_set_structure_handler import ViewSetStructureHandler from workbench_dock_window import WorkbenchDockWindow # Logging. logger = logging.getLogger(__name__) # Mapping from view position to the appropriate dock window constant. _POSITION_MAP = { 'top' : DOCK_TOP, 'bottom' : DOCK_BOTTOM, 'left' : DOCK_LEFT, 'right' : DOCK_RIGHT } class WorkbenchWindowLayout(MWorkbenchWindowLayout): """ The wx implementation of the workbench window layout interface. See the 'IWorkbenchWindowLayout' interface for the API documentation. """ #### 'IWorkbenchWindowLayout' interface ################################### editor_area_id = Delegate('window') ########################################################################### # 'IWorkbenchWindowLayout' interface. ########################################################################### def activate_editor(self, editor): """ Activate an editor. """ # This brings the dock control tab to the front. self._wx_editor_dock_window.activate_control(editor.id) editor.set_focus() return editor def activate_view(self, view): """ Activate a view. """ # This brings the dock control tab to the front. self._wx_view_dock_window.activate_control(view.id) view.set_focus() return view def add_editor(self, editor, title): """ Add an editor. """ try: self._wx_add_editor(editor, title) except Exception: logger.exception('error creating editor control <%s>', editor.id) return editor def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): """ Add a view. """ try: self._wx_add_view(view, position, relative_to, size) view.visible = True except Exception: logger.exception('error creating view control <%s>', view.id) # Even though we caught the exception, it sometimes happens that # the view's control has been created as a child of the application # window (or maybe even the dock control). We should destroy the # control to avoid bad UI effects. view.destroy_control() # Additionally, display an error message to the user. self.window.error('Unable to add view %s' % view.id) return view def close_editor(self, editor): """ Close and editor. """ self._wx_editor_dock_window.close_control(editor.id) return editor def close_view(self, view): """ Close a view. """ self.hide_view(view) return view def close(self): """ Close the entire window layout. """ self._wx_editor_dock_window.close() self._wx_view_dock_window.close() return def create_initial_layout(self, parent): """ Create the initial window layout. """ # The view dock window is where all of the views live. It also contains # a nested dock window where all of the editors live. self._wx_view_dock_window = WorkbenchDockWindow(parent) # The editor dock window (which is nested inside the view dock window) # is where all of the editors live. self._wx_editor_dock_window = WorkbenchDockWindow( self._wx_view_dock_window.control ) editor_dock_window_sizer = DockSizer(contents=DockSection()) self._wx_editor_dock_window.control.SetSizer(editor_dock_window_sizer) # Nest the editor dock window in the view dock window. editor_dock_window_control = DockControl( id = self.editor_area_id, name = 'Editors', control = self._wx_editor_dock_window.control, style = 'fixed', width = self.window.editor_area_size[0], height = self.window.editor_area_size[1], ) view_dock_window_sizer = DockSizer( contents=[editor_dock_window_control] ) self._wx_view_dock_window.control.SetSizer(view_dock_window_sizer) return self._wx_view_dock_window.control def contains_view(self, view): """ Return True if the view exists in the window layout. """ view_control = self._wx_view_dock_window.get_control(view.id, False) return view_control is not None def hide_editor_area(self): """ Hide the editor area. """ dock_control = self._wx_view_dock_window.get_control( self.editor_area_id, visible_only=False ) dock_control.show(False, layout=True) return def hide_view(self, view): """ Hide a view. """ dock_control = self._wx_view_dock_window.get_control( view.id, visible_only=False ) dock_control.show(False, layout=True) view.visible = False return view def refresh(self): """ Refresh the window layout to reflect any changes. """ self._wx_view_dock_window.update_layout() return def reset_editors(self): """ Activate the first editor in every group. """ self._wx_editor_dock_window.reset_regions() return def reset_views(self): """ Activate the first view in every group. """ self._wx_view_dock_window.reset_regions() return def show_editor_area(self): """ Show the editor area. """ dock_control = self._wx_view_dock_window.get_control( self.editor_area_id, visible_only=False ) dock_control.show(True, layout=True) return def show_view(self, view): """ Show a view. """ dock_control = self._wx_view_dock_window.get_control( view.id, visible_only=False ) dock_control.show(True, layout=True) view.visible = True return #### Methods for saving and restoring the layout ########################## def get_view_memento(self): structure = self._wx_view_dock_window.get_structure() # We always return a clone. return cPickle.loads(cPickle.dumps(structure)) def set_view_memento(self, memento): # We always use a clone. memento = cPickle.loads(cPickle.dumps(memento)) # The handler knows how to resolve view Ids when setting the dock # window structure. handler = ViewSetStructureHandler(self) # Set the layout of the views. self._wx_view_dock_window.set_structure(memento, handler) # fixme: We should be able to do this in the handler but we don't get a # reference to the actual dock control in 'resolve_id'. for view in self.window.views: control = self._wx_view_dock_window.get_control(view.id) if control is not None: self._wx_initialize_view_dock_control(view, control) view.visible = control.visible else: view.visible = False return def get_editor_memento(self): # Get the layout of the editors. structure = self._wx_editor_dock_window.get_structure() # Get a memento to every editor. editor_references = self._get_editor_references() return (structure, editor_references) def set_editor_memento(self, memento): # fixme: Mementos might want to be a bit more formal than tuples! structure, editor_references = memento if len(structure.contents) > 0: # The handler knows how to resolve editor Ids when setting the dock # window structure. handler = EditorSetStructureHandler(self, editor_references) # Set the layout of the editors. self._wx_editor_dock_window.set_structure(structure, handler) # fixme: We should be able to do this in the handler but we don't # get a reference to the actual dock control in 'resolve_id'. for editor in self.window.editors: control = self._wx_editor_dock_window.get_control(editor.id) if control is not None: self._wx_initialize_editor_dock_control(editor, control) return ########################################################################### # Private interface. ########################################################################### def _wx_add_editor(self, editor, title): """ Adds an editor. """ # Create a dock control that contains the editor. editor_dock_control = self._wx_create_editor_dock_control(editor) # If there are no other editors open (i.e., this is the first one!), # then create a new region to put the editor in. controls = self._wx_editor_dock_window.get_controls() if len(controls) == 0: # Get a reference to the empty editor section. sizer = self._wx_editor_dock_window.control.GetSizer() section = sizer.GetContents() # Add a region containing the editor dock control. region = DockRegion(contents=[editor_dock_control]) section.contents = [region] # Otherwise, add the editor to the same region as the first editor # control. # # fixme: We might want a more flexible placement strategy at some # point! else: region = controls[0].parent region.add(editor_dock_control) # fixme: Without this the window does not draw properly (manually # resizing the window makes it better!). self._wx_editor_dock_window.update_layout() return def _wx_add_view(self, view, position, relative_to, size): """ Adds a view. """ # If no specific position is specified then use the view's default # position. if position is None: position = view.position # Create a dock control that contains the view. dock_control = self._wx_create_view_dock_control(view) if position == 'with': # Does the item we are supposed to be positioned 'with' actual # exist? with_item = self._wx_view_dock_window.get_control(relative_to.id) # If so then we put the items in the same tab group. if with_item is not None: self._wx_add_view_with(dock_control, relative_to) # Otherwise, just fall back to the 'left' of the editor area. else: self._wx_add_view_relative(dock_control, None, 'left', size) else: self._wx_add_view_relative(dock_control,relative_to,position,size) return # fixme: Make the view dock window a sub class of dock window, and add # 'add_with' and 'add_relative_to' as methods on that. # # fixme: This is a good idea in theory, but the sizing is a bit iffy, as # it requires the window to be passed in to calculate the relative size # of the control. We could just calculate that here and pass in absolute # pixel sizes to the dock window subclass? def _wx_add_view_relative(self, dock_control, relative_to, position, size): """ Adds a view relative to another item. """ # If no 'relative to' Id is specified then we assume that the position # is relative to the editor area. if relative_to is None: relative_to_item = self._wx_view_dock_window.get_control( self.editor_area_id, visible_only=False ) # Find the item that we are adding the view relative to. else: relative_to_item = self._wx_view_dock_window.get_control( relative_to.id ) # Set the size of the dock control. self._wx_set_item_size(dock_control, size) # The parent of a dock control is a dock region. region = relative_to_item.parent section = region.parent section.add(dock_control, region, _POSITION_MAP[position]) return def _wx_add_view_with(self, dock_control, with_obj): """ Adds a view in the same region as another item. """ # Find the item that we are adding the view 'with'. with_item = self._wx_view_dock_window.get_control(with_obj.id) if with_item is None: raise ValueError('Cannot find item %s' % with_obj) # The parent of a dock control is a dock region. with_item.parent.add(dock_control) return def _wx_set_item_size(self, dock_control, size): """ Sets the size of a dock control. """ window_width, window_height = self.window.control.GetSize() width, height = size if width != -1: dock_control.width = int(window_width * width) if height != -1: dock_control.height = int(window_height * height) return def _wx_create_editor_dock_control(self, editor): """ Creates a dock control that contains the specified editor. """ self._wx_get_editor_control(editor) # Wrap a dock control around it. editor_dock_control = DockControl( id = editor.id, name = editor.name, closeable = True, control = editor.control, style = 'tab', # fixme: Create a subclass of dock control and give it a proper # editor trait! _editor = editor ) # Hook up the 'on_close' and trait change handlers etc. self._wx_initialize_editor_dock_control(editor, editor_dock_control) return editor_dock_control def _wx_create_view_dock_control(self, view): """ Creates a dock control that contains the specified view. """ # Get the view's toolkit-specific control. control = self._wx_get_view_control(view) # Check if the dock control should be 'closeable'. # FIXME: The 'fixme' comment below suggests some issue with closing a # view by clicking 'X' rather than just hiding the view. The two actions # appear to do the same thing however, so I'm not sure if the comment # below is an out-of-date comment. This needs more investigation. # For the time being, I am making a view closeable if it has a # 'closeable' trait set to True. closeable = view.closeable # Wrap a dock control around it. view_dock_control = DockControl( id = view.id, name = view.name, # fixme: We would like to make views closeable, but closing via the # tab is different than calling show(False, layout=True) on the # control! If we use a close handler can we change that?!? closeable = closeable, control = control, style = view.style_hint, # fixme: Create a subclass of dock control and give it a proper # view trait! _view = view ) # Hook up the 'on_close' and trait change handlers etc. self._wx_initialize_view_dock_control(view, view_dock_control) return view_dock_control def _wx_get_editor_control(self, editor): """ Returns the editor's toolkit-specific control. If the editor has not yet created its control, we will ask it to create it here. """ if editor.control is None: parent = self._wx_editor_dock_window.control # This is the toolkit-specific control that represents the 'guts' # of the editor. self.editor_opening = editor editor.control = editor.create_control(parent) self.editor_opened = editor # Hook up toolkit-specific events that are managed by the framework # etc. self._wx_initialize_editor_control(editor) return editor.control def _wx_initialize_editor_control(self, editor): """ Initializes the toolkit-specific control for an editor. This is used to hook events managed by the framework etc. """ def on_set_focus(event): """ Called when the control gets the focus. """ editor.has_focus = True # Let the default wx event handling do its thang. event.Skip() return def on_kill_focus(event): """ Called when the control gets the focus. """ editor.has_focus = False # Let the default wx event handling do its thang. event.Skip() return self._wx_add_focus_listeners(editor.control,on_set_focus,on_kill_focus) return def _wx_get_view_control(self, view): """ Returns a view's toolkit-specific control. If the view has not yet created its control, we will ask it to create it here. """ if view.control is None: parent = self._wx_view_dock_window.control # Make sure that the view knows which window it is in. view.window = self.window # This is the toolkit-specific control that represents the 'guts' # of the view. self.view_opening = view view.control = view.create_control(parent) self.view_opened = view # Hook up toolkit-specific events that are managed by the # framework etc. self._wx_initialize_view_control(view) return view.control def _wx_initialize_view_control(self, view): """ Initializes the toolkit-specific control for a view. This is used to hook events managed by the framework. """ def on_set_focus(event): """ Called when the control gets the focus. """ view.has_focus = True # Let the default wx event handling do its thang. event.Skip() return def on_kill_focus(event): """ Called when the control gets the focus. """ view.has_focus = False # Let the default wx event handling do its thang. event.Skip() return self._wx_add_focus_listeners(view.control, on_set_focus, on_kill_focus) return def _wx_add_focus_listeners(self, control, on_set_focus, on_kill_focus): """ Recursively adds focus listeners to a control. """ # NOTE: If we are passed a wx control that isn't correctly initialized # (like when the TraitsUIView isn't properly creating it) but it is # actually a wx control, then we get weird exceptions from trying to # register event handlers. The exception messages complain that # the passed control is a str object instead of a wx object. if on_set_focus is not None: #control.Bind(wx.EVT_SET_FOCUS, on_set_focus) wx.EVT_SET_FOCUS(control, on_set_focus) if on_kill_focus is not None: #control.Bind(wx.EVT_KILL_FOCUS, on_kill_focus) wx.EVT_KILL_FOCUS(control, on_kill_focus) for child in control.GetChildren(): self._wx_add_focus_listeners(child, on_set_focus, on_kill_focus) return def _wx_initialize_editor_dock_control(self, editor, editor_dock_control): """ Initializes an editor dock control. fixme: We only need this method because of a problem with the dock window API in the 'SetStructureHandler' class. Currently we do not get a reference to the dock control in 'resolve_id' and hence we cannot set up the 'on_close' and trait change handlers etc. """ # Some editors append information to their name to indicate status (in # our case this is often a 'dirty' indicator that shows when the # contents of an editor have been modified but not saved). When the # dock window structure is persisted it contains the name of each dock # control, which obviously includes any appended state information. # Here we make sure that when the dock control is recreated its name is # set to the editor name and nothing more! editor_dock_control.set_name(editor.name) # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(editor, 'ui') and editor.ui is not None: # This makes the control draggable outside of the main window. #editor_dock_control.export = 'pyface.workbench.editor' editor_dock_control.dockable = DockableViewElement( should_close=True, ui=editor.ui ) editor_dock_control.on_close = self._wx_on_editor_closed def on_id_changed(editor, trait_name, old, new): editor_dock_control.id = editor.id return editor.on_trait_change(on_id_changed, 'id') def on_name_changed(editor, trait_name, old, new): editor_dock_control.set_name(editor.name) return editor.on_trait_change(on_name_changed, 'name') def on_activated_changed(editor_dock_control, trait_name, old, new): if editor_dock_control._editor is not None: editor_dock_control._editor.set_focus() return editor_dock_control.on_trait_change(on_activated_changed, 'activated') return def _wx_initialize_view_dock_control(self, view, view_dock_control): """ Initializes a view dock control. fixme: We only need this method because of a problem with the dock window API in the 'SetStructureHandler' class. Currently we do not get a reference to the dock control in 'resolve_id' and hence we cannot set up the 'on_close' and trait change handlers etc. """ # Some views append information to their name to indicate status (in # our case this is often a 'dirty' indicator that shows when the # contents of a view have been modified but not saved). When the # dock window structure is persisted it contains the name of each dock # control, which obviously includes any appended state information. # Here we make sure that when the dock control is recreated its name is # set to the view name and nothing more! view_dock_control.set_name(view.name) # fixme: Should we roll the traits UI stuff into the default editor. if hasattr(view, 'ui') and view.ui is not None: # This makes the control draggable outside of the main window. #view_dock_control.export = 'pyface.workbench.view' # If the ui's 'view' trait has an 'export' field set, pass that on # to the dock control. This makes the control detachable from the # main window (if 'export' is not an empty string). if view.ui.view is not None: view_dock_control.export = view.ui.view.export view_dock_control.dockable = DockableViewElement( should_close=True, ui=view.ui ) view_dock_control.on_close = self._wx_on_view_closed def on_id_changed(view, trait_name, old, new): view_dock_control.id = view.id return view.on_trait_change(on_id_changed, 'id') def on_name_changed(view, trait_name, old, new): view_dock_control.set_name(view.name) return view.on_trait_change(on_name_changed, 'name') def on_activated_changed(view_dock_control, trait_name, old, new): if view_dock_control._view is not None: view_dock_control._view.set_focus() return view_dock_control.on_trait_change(on_activated_changed, 'activated') return #### Trait change handlers ################################################ #### Static #### def _window_changed(self, old, new): """ Static trait change handler. """ if old is not None: old.on_trait_change( self._wx_on_editor_area_size_changed, 'editor_area_size', remove=True ) if new is not None: new.on_trait_change( self._wx_on_editor_area_size_changed, 'editor_area_size', ) #### Dynamic #### def _wx_on_editor_area_size_changed(self, new): """ Dynamic trait change handler. """ window_width, window_height = self.window.control.GetSize() # Get the dock control that contains the editor dock window. control = self._wx_view_dock_window.get_control(self.editor_area_id) # We actually resize the region that the editor area is in. region = control.parent region.width = int(new[0] * window_width) region.height = int(new[1] * window_height) return #### Dock window handlers ################################################# # fixme: Should these just fire events that the window listens to? def _wx_on_view_closed(self, dock_control, force): """ Called when a view is closed via the dock window control. """ view = self.window.get_view_by_id(dock_control.id) if view is not None: logger.debug('workbench destroying view control <%s>', view) try: view.visible = False self.view_closing = view view.destroy_control() self.view_closed = view except: logger.exception('error destroying view control <%s>', view) return True def _wx_on_editor_closed(self, dock_control, force): """ Called when an editor is closed via the dock window control. """ dock_control._editor = None editor = self.window.get_editor_by_id(dock_control.id) ## import weakref ## editor_ref = weakref.ref(editor) if editor is not None: logger.debug('workbench destroying editor control <%s>', editor) try: # fixme: We would like this event to be vetoable, but it isn't # just yet (we will need to modify the dock window package). self.editor_closing = editor editor.destroy_control() self.editor_closed = editor except: logger.exception('error destroying editor control <%s>',editor) ## import gc ## gc.collect() ## print 'Editor references', len(gc.get_referrers(editor)) ## for r in gc.get_referrers(editor): ## print '********************************************' ## print type(r), id(r), r ## del editor ## gc.collect() ## print 'Is editor gone?', editor_ref() is None, 'ref', editor_ref() return True #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/view_set_structure_handler.py0000755000175100001440000000440011674463363026302 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The handler used to restore views. """ # Standard library imports. import logging # Enthought library imports. from pyface.dock.api import SetStructureHandler logger = logging.getLogger(__name__) class ViewSetStructureHandler(SetStructureHandler): """ The handler used to restore views. This is part of the 'dock window' API. It is used to resolve dock control IDs when setting the structure of a dock window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, window_layout): """ Creates a new handler. """ self.window_layout = window_layout return ########################################################################### # 'SetStructureHandler' interface. ########################################################################### def resolve_id(self, id): """ Resolves an unresolved dock control *id*. """ window_layout = self.window_layout window = window_layout.window view = window.get_view_by_id(id) if view is not None: # Get the view's toolkit-specific control. # # fixme: This is using a 'private' method on the window layout. # This may be ok since this is really part of the layout! control = window_layout._wx_get_view_control(view) else: logger.warn('could not restore view [%s]', id) control = None return control #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/workbench_dock_window.py0000755000175100001440000001052511674463363025216 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Base class for workbench dock windows. """ # Standard library imports. import logging # Enthought library imports. from pyface.dock.api import DockGroup, DockRegion, DockWindow logger = logging.getLogger(__name__) class WorkbenchDockWindow(DockWindow): """ Base class for workbench dock windows. This class just adds a few useful methods to the standard 'DockWindow' interface. Hopefully at some stage these can be part of that API too! """ ########################################################################### # Protected 'DockWindow' interface. ########################################################################### def _right_up(self, event): """ Handles the right mouse button being released. We override this to stop the default dock window context menus from appearing. """ pass ########################################################################### # 'WorkbenchDockWindow' interface. ########################################################################### def activate_control(self, id): """ Activates the dock control with the specified Id. Does nothing if no such dock control exists (well, it *does* write a debug message to the logger). """ control = self.get_control(id) if control is not None: logger.debug('activating control <%s>', id) control.activate() else: logger.debug('no control <%s> to activate', id) return def close_control(self, id): """ Closes the dock control with the specified Id. Does nothing if no such dock control exists (well, it *does* write a debug message to the logger). """ control = self.get_control(id) if control is not None: logger.debug('closing control <%s>', id) control.close() else: logger.debug('no control <%s> to close', id) return def get_control(self, id, visible_only=True): """ Returns the dock control with the specified Id. Returns None if no such dock control exists. """ for control in self.get_controls(visible_only): if control.id == id: break else: control = None return control def get_controls(self, visible_only=True): """ Returns all of the dock controls in the window. """ sizer = self.control.GetSizer() section = sizer.GetContents() return section.get_controls(visible_only=visible_only) def get_regions(self, group): """ Returns all dock regions in a dock group (recursively). """ regions = [] for item in group.contents: if isinstance(item, DockRegion): regions.append(item) if isinstance(item, DockGroup): regions.extend(self.get_regions(item)) return regions def get_structure(self): """ Returns the window structure (minus the content). """ sizer = self.control.GetSizer() return sizer.GetStructure() def reset_regions(self): """ Activates the first dock control in every region. """ sizer = self.control.GetSizer() section = sizer.GetContents() for region in self.get_regions(section): if len(region.contents) > 0: region.contents[0].activate(layout=False) return def set_structure(self, structure, handler=None): """ Sets the window structure. """ sizer = self.control.GetSizer() sizer.SetStructure(self.control.GetParent(), structure, handler) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/editor_set_structure_handler.py0000755000175100001440000000634011674463363026623 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The handler used to restore editors. """ # Standard library imports. import logging # Enthought library imports. from pyface.dock.api import SetStructureHandler logger = logging.getLogger(__name__) class EditorSetStructureHandler(SetStructureHandler): """ The handler used to restore editors. This is part of the 'dock window' API. It is used to resolve dock control Ids when setting the structure of a dock window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, window_layout, editor_mementos): """ Creates a new handler. """ self.window_layout = window_layout self.editor_mementos = editor_mementos return ########################################################################### # 'SetStructureHandler' interface. ########################################################################### def resolve_id(self, id): """ Resolves an unresolved dock control id. """ window_layout = self.window_layout window = window_layout.window try: # Get the memento for the editor with this Id. memento = self._get_editor_memento(id) # Ask the editor manager to create an editor from the memento. editor = window.editor_manager.set_editor_memento(memento) # Get the editor's toolkit-specific control. # # fixme: This is using a 'private' method on the window layout. # This may be ok since this structure handler is really part of the # layout! control = window_layout._wx_get_editor_control(editor) # fixme: This is ugly manipulating the editors list from in here! window.editors.append(editor) except: logger.warn('could not restore editor [%s]', id) control = None return control ########################################################################### # Private interface. ########################################################################### def _get_editor_memento(self, id): """ Return the editor memento for the editor with the specified Id. Raises a 'ValueError' if no such memento exists. """ editor_memento = self.editor_mementos.get(id) if editor_memento is None: raise ValueError('no editor memento with Id %s' % id) return editor_memento #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/workbench/__init__.py0000644000175100001440000000034511674463363022400 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/splash_screen.py0000644000175100001440000001054411674463363021512 0ustar ischnellusers00000000000000 #------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. from logging import DEBUG # Major package imports. import wx # Enthought library imports. from traits.api import Any, Bool, Font, implements, Instance, Int from traits.api import Tuple, Unicode # Private Enthought library imports. # FIXME v3: This should be moved out of pyface. from pyface.util.font_helper import new_font_like # Local imports. from pyface.i_splash_screen import ISplashScreen, MSplashScreen from pyface.image_resource import ImageResource from window import Window class SplashScreen(MSplashScreen, Window): """ The toolkit specific implementation of a SplashScreen. See the ISplashScreen interface for the API documentation. """ implements(ISplashScreen) #### 'ISplashScreen' interface ############################################ image = Instance(ImageResource, ImageResource('splash')) log_level = Int(DEBUG) show_log_messages = Bool(True) text = Unicode text_color = Any text_font = Any text_location = Tuple(5, 5) ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # Get the splash screen image. image = self.image.create_image() splash_screen = wx.SplashScreen( # The bitmap to display on the splash screen. image.ConvertToBitmap(), # Splash Style. wx.SPLASH_NO_TIMEOUT | wx.SPLASH_CENTRE_ON_SCREEN, # Timeout in milliseconds (we don't currently timeout!). 0, # The parent of the splash screen. parent, # wx Id. -1, # Window style. style = wx.SIMPLE_BORDER | wx.FRAME_NO_TASKBAR ) # By default we create a font slightly bigger and slightly more italic # than the normal system font ;^) The font is used inside the event # handler for 'EVT_PAINT'. self._wx_default_text_font = new_font_like( wx.NORMAL_FONT, point_size = wx.NORMAL_FONT.GetPointSize() + 1, style = wx.ITALIC ) # This allows us to write status text on the splash screen. wx.EVT_PAINT(splash_screen, self._on_paint) return splash_screen ########################################################################### # Private interface. ########################################################################### def _text_changed(self): """ Called when the splash screen text has been changed. """ # Passing 'False' to 'Refresh' means "do not erase the background". if self.control is not None: self.control.Refresh(False) self.control.Update() wx.GetApp().Yield(True) def _on_paint(self, event): """ Called when the splash window is being repainted. """ if self.control is not None: # Get the window that the splash image is drawn in. window = self.control.GetSplashWindow() dc = wx.PaintDC(window) if self.text_font is None: text_font = self._wx_default_text_font else: text_font = self.text_font dc.SetFont(text_font) if self.text_color is None: text_color = 'black' else: text_color = self.text_color dc.SetTextForeground(text_color) x, y = self.text_location dc.DrawText(self.text, x, y) # Let the normal wx paint handling do its stuff. event.Skip() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/widget.py0000644000175100001440000000272011674463363020141 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Enthought library imports. from traits.api import Any, HasTraits, implements # Local imports. from pyface.i_widget import IWidget, MWidget class Widget(MWidget, HasTraits): """ The toolkit specific implementation of a Widget. See the IWidget interface for the API documentation. """ implements(IWidget) #### 'IWidget' interface ################################################## control = Any parent = Any ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): if self.control is not None: self.control.Destroy() self.control = None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/message_dialog.py0000644000175100001440000000456211674463363021627 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import Enum, implements, Unicode # Local imports. from pyface.i_message_dialog import IMessageDialog, MMessageDialog from dialog import Dialog # Map the ETS severity to the corresponding wx standard icon. _SEVERITY_TO_ICON_MAP = { 'information': wx.ICON_INFORMATION, 'warning': wx.ICON_WARNING, 'error': wx.ICON_ERROR } class MessageDialog(MMessageDialog, Dialog): """ The toolkit specific implementation of a MessageDialog. See the IMessageDialog interface for the API documentation. """ implements(IMessageDialog) #### 'IMessageDialog' interface ########################################### message = Unicode informative = Unicode detail = Unicode severity = Enum('information', 'warning', 'error') ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In wx this is a canned dialog. pass ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # The message. if self.informative: message = self.message + '\n\n' + self.informative else: message = self.message return wx.MessageDialog(parent, message, self.title, _SEVERITY_TO_ICON_MAP[self.severity] | wx.OK | wx.STAY_ON_TOP) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/0000755000175100001440000000000011674463363017560 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/action/action_item.py0000644000175100001440000005102611674463363022431 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx specific implementations the action manager internal classes. """ # Standard libary imports. from inspect import getargspec # Major package imports. import wx # Enthought library imports. from traits.api import Any, Bool, HasTraits # Local imports. from pyface.action.action_event import ActionEvent _STYLE_TO_KIND_MAP = { 'push' : wx.ITEM_NORMAL, 'radio' : wx.ITEM_RADIO, 'toggle' : wx.ITEM_CHECK } class _MenuItem(HasTraits): """ A menu item representation of an action item. """ #### '_MenuItem' interface ################################################ # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the menu item is not part of such # a group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, menu, item, controller): """ Creates a new menu item for an action item. """ self.item = item # Create an appropriate menu item depending on the style of the action. # # N.B. Don't try to use -1 as the Id for the menu item... wx does not # ---- like it! action = item.action label = action.name kind = _STYLE_TO_KIND_MAP[action.style] longtip = action.description if len(action.accelerator) > 0: label = label + '\t' + action.accelerator # This just helps with debugging when people forget to specify a name # for their action (without this wx just barfs which is not very # helpful!). if len(label) == 0: label = item.action.__class__.__name__ self.control_id = wx.NewId() self.control = wx.MenuItem(menu, self.control_id, label, longtip, kind) # If the action has an image then display it. if action.image is not None: self.control.SetBitmap(action.image.create_bitmap()) menu.AppendItem(self.control) menu.menu_items.append(self) # Set the initial enabled/disabled state of the action. self.control.Enable(action.enabled and action.visible) # Set the initial checked state. if action.style in ['radio', 'toggle']: self.control.Check(action.checked) # Wire it up...create an ugly flag since some platforms dont skip the # event when we thought they would self._skip_menu_event = False wx.EVT_MENU(parent, self.control_id, self._on_menu) # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') action.on_trait_change(self._on_action_name_changed, 'name') if controller is not None: self.controller = controller controller.add_to_menu(self) return def dispose(self): action = self.item.action action.on_trait_change(self._on_action_enabled_changed, 'enabled', remove=True) action.on_trait_change(self._on_action_visible_changed, 'visible', remove=True) action.on_trait_change(self._on_action_checked_changed, 'checked', remove=True) action.on_trait_change(self._on_action_name_changed, 'name', remove=True) ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ self.control.Enable(self.enabled and self.visible) return def _visible_changed(self): """ Called when our 'visible' trait is changed. """ self.control.Enable(self.visible and self.enabled) return def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.item.action.style == 'radio': # fixme: Not sure why this is even here, we had to guard it to # make it work? Must take a look at svn blame! # FIXME v3: Note that menu_checked() doesn't seem to exist, so we # comment it out and do the following instead. #if self.group is not None: # self.group.menu_checked(self) # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if self.checked: for item in self.item.parent.items: if item is not self.item: item.action.checked = False self.control.Check(self.checked) return def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ self.control.Enable(action.enabled and action.visible) return def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ self.control.Enable(action.visible and action.enabled) return def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if self.item.action.style == 'radio': # fixme: Not sure why this is even here, we had to guard it to # make it work? Must take a look at svn blame! # FIXME v3: Note that menu_checked() doesn't seem to exist, so we # comment it out and do the following instead. #if self.group is not None: # self.group.menu_checked(self) # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if action.checked: for item in self.item.parent.items: if item is not self.item: item.action.checked = False # This will *not* emit a menu event because of this ugly flag self._skip_menu_event = True self.control.Check(action.checked) self._skip_menu_event = False return def _on_action_name_changed(self, action, trait_name, old, new): """ Called when the name trait is changed on an action. """ self.control.SetText(action.name) return #### wx event handlers #################################################### def _on_menu(self, event): """ Called when the menu item is clicked. """ # if the ugly flag is set, do not perform the menu event if self._skip_menu_event: return action = self.item.action action_event = ActionEvent() is_checkable = action.style in ['radio', 'toggle'] # Perform the action! if self.controller is not None: if is_checkable: # fixme: There is a difference here between having a controller # and not in that in this case we do not set the checked state # of the action! This is confusing if you start off without a # controller and then set one as the action now behaves # differently! self.checked = self.control.IsChecked() == 1 # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. args, varargs, varkw, dflts = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: if is_checkable: action.checked = self.control.IsChecked() == 1 # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. args, varargs, varkw, dflts = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(args) == 1: action.perform() else: action.perform(action_event) return class _Tool(HasTraits): """ A tool bar tool representation of an action item. """ #### '_Tool' interface #################################################### # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the tool is not part of such a # group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, tool_bar, image_cache, item, controller, show_labels): """ Creates a new tool bar tool for an action item. """ self.item = item self.tool_bar = tool_bar # Create an appropriate tool depending on the style of the action. action = self.item.action label = action.name # Tool bar tools never have '...' at the end! if label.endswith('...'): label = label[:-3] # And they never contain shortcuts. label = label.replace('&', '') # If the action has an image then convert it to a bitmap (as required # by the toolbar). if action.image is not None: image = action.image.create_image( self.tool_bar.GetToolBitmapSize() ) path = action.image.absolute_path bmp = image_cache.get_bitmap(path) else: from pyface.api import ImageResource image = ImageResource('foo') bmp = image.create_bitmap() kind = _STYLE_TO_KIND_MAP[action.style] tooltip = action.tooltip longtip = action.description if not show_labels: label = '' else: self.tool_bar.SetSize((-1, 50)) self.control_id = wx.NewId() self.control = tool_bar.AddLabelTool( self.control_id, label, bmp, wx.NullBitmap, kind, tooltip, longtip ) # Set the initial checked state. tool_bar.ToggleTool(self.control_id, action.checked) # Set the initial enabled/disabled state of the action. tool_bar.EnableTool(self.control_id, action.enabled and action.visible) # Wire it up. wx.EVT_TOOL(parent, self.control_id, self._on_tool) # Listen for trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_visible_changed, 'visible') action.on_trait_change(self._on_action_checked_changed, 'checked') if controller is not None: self.controller = controller controller.add_to_toolbar(self) return ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _enabled_changed(self): """ Called when our 'enabled' trait is changed. """ self.tool_bar.EnableTool(self.control_id, self.enabled and self.visible) return def _visible_changed(self): """ Called when our 'visible' trait is changed. """ self.tool_bar.EnableTool(self.control_id, self.visible and self.enabled) return def _checked_changed(self): """ Called when our 'checked' trait is changed. """ if self.item.action.style == 'radio': # FIXME v3: Note that toolbar_checked() doesn't seem to exist, so # we comment it out and do the following instead. #self.group.toolbar_checked(self) # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if self.checked: for item in self.item.parent.items: if item is not self.item: item.action.checked = False self.tool_bar.ToggleTool(self.control_id, self.checked) return def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ self.tool_bar.EnableTool(self.control_id, action.enabled and action.visible) return def _on_action_visible_changed(self, action, trait_name, old, new): """ Called when the visible trait is changed on an action. """ self.tool_bar.EnableTool(self.control_id, action.visible and action.enabled) return def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if action.style == 'radio': # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if new: for item in self.item.parent.items: if item is not self.item: item.action.checked = False # This will *not* emit a tool event. self.tool_bar.ToggleTool(self.control_id, new) return #### wx event handlers #################################################### def _on_tool(self, event): """ Called when the tool bar tool is clicked. """ action = self.item.action action_event = ActionEvent() is_checkable = (action.style == 'radio' or action.style == 'check') # Perform the action! if self.controller is not None: # fixme: There is a difference here between having a controller # and not in that in this case we do not set the checked state # of the action! This is confusing if you start off without a # controller and then set one as the action now behaves # differently! self.checked = self.tool_bar.GetToolState(self.control_id) == 1 # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. This is also # useful as Traits UI controllers *never* require the event. args, varargs, varkw, dflts = getargspec(self.controller.perform) # If the only arguments are 'self' and 'action' then don't pass # the event! if len(args) == 2: self.controller.perform(action) else: self.controller.perform(action, action_event) else: action.checked = self.tool_bar.GetToolState(self.control_id) == 1 # Most of the time, action's do no care about the event (it # contains information about the time the event occurred etc), so # we only pass it if the perform method requires it. args, varargs, varkw, dflts = getargspec(action.perform) # If the only argument is 'self' then don't pass the event! if len(args) == 1: action.perform() else: action.perform(action_event) return class _PaletteTool(HasTraits): """ A tool palette representation of an action item. """ #### '_PaletteTool' interface ############################################# # The radio group we are part of (None if the tool is not part of such a # group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tool_palette, image_cache, item, show_labels): """ Creates a new tool palette tool for an action item. """ self.item = item self.tool_palette = tool_palette action = self.item.action label = action.name # Tool palette tools never have '...' at the end. if label.endswith('...'): label = label[:-3] # And they never contain shortcuts. label = label.replace('&', '') image = action.image.create_image() path = action.image.absolute_path bmp = image_cache.get_bitmap(path) kind = action.style tooltip = action.tooltip longtip = action.description if not show_labels: label = '' # Add the tool to the tool palette. self.tool_id = tool_palette.add_tool(label, bmp, kind, tooltip,longtip) tool_palette.toggle_tool(self.tool_id, action.checked) tool_palette.enable_tool(self.tool_id, action.enabled) tool_palette.on_tool_event(self.tool_id, self._on_tool) # Listen to the trait changes on the action (so that we can update its # enabled/disabled/checked state etc). action.on_trait_change(self._on_action_enabled_changed, 'enabled') action.on_trait_change(self._on_action_checked_changed, 'checked') return ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_action_enabled_changed(self, action, trait_name, old, new): """ Called when the enabled trait is changed on an action. """ self.tool_palette.enable_tool(self.tool_id, action.enabled) return def _on_action_checked_changed(self, action, trait_name, old, new): """ Called when the checked trait is changed on an action. """ if action.style == 'radio': # If we're turning this one on, then we need to turn all the others # off. But if we're turning this one off, don't worry about the # others. if new: for item in self.item.parent.items: if item is not self.item: item.action.checked = False # This will *not* emit a tool event. self.tool_palette.toggle_tool(self.tool_id, new) return #### Tool palette event handlers ########################################## def _on_tool(self, event): """ Called when the tool palette button is clicked. """ action = self.item.action action_event = ActionEvent() is_checkable = (action.style == 'radio' or action.style == 'check') # Perform the action! action.checked = self.tool_palette.get_tool_state(self.tool_id) == 1 action.perform(action_event) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/status_bar_manager.py0000644000175100001440000000661111674463363023777 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ A status bar manager realizes itself in a status bar control. """ # Major package imports. import wx # Enthought library imports. from traits.api import Any, HasTraits, List, Property, Str, Unicode class StatusBarManager(HasTraits): """ A status bar manager realizes itself in a status bar control. """ # The message displayed in the first field of the status bar. message = Property # The messages to be displayed in the status bar fields. messages = List(Unicode) # The toolkit-specific control that represents the status bar. status_bar = Any ########################################################################### # 'StatusBarManager' interface. ########################################################################### def create_status_bar(self, parent): """ Creates a status bar. """ if self.status_bar is None: self.status_bar = wx.StatusBar(parent) self.status_bar._pyface_control = self if len(self.messages) > 1: self.status_bar.SetFieldsCount(len(self.messages)) for i in range(len(self.messages)): self.status_bar.SetStatusText(self.messages[i], i) else: self.status_bar.SetStatusText(self.message) return self.status_bar ########################################################################### # Property handlers. ########################################################################### def _get_message(self): """ Property getter. """ if len(self.messages) > 0: message = self.messages[0] else: message = '' return message def _set_message(self, value): """ Property setter. """ if len(self.messages) > 0: old = self.messages[0] self.messages[0] = value else: old = '' self.messages.append(value) self.trait_property_changed('message', old, value) return ########################################################################### # Trait event handlers. ########################################################################### def _messages_changed(self): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: for i in range(len(self.messages)): self.status_bar.SetStatusText(self.messages[i], i) return def _messages_items_changed(self): """ Sets the text displayed on the status bar. """ if self.status_bar is not None: for i in range(len(self.messages)): self.status_bar.SetStatusText(self.messages[i], i) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/tool_palette_manager.py0000644000175100001440000001166411674463363024327 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ A tool bar manager realizes itself in a tool palette control. """ # Major package imports. import wx # Enthought library imports. from traits.api import Any, Bool, Enum, Instance, Tuple # Local imports. from pyface.image_cache import ImageCache from pyface.action.action_manager import ActionManager from tool_palette import ToolPalette class ToolPaletteManager(ActionManager): """ A tool bar manager realizes itself in a tool palette bar control. """ #### 'ToolPaletteManager' interface ####################################### # The size of tool images (width, height). image_size = Tuple((16, 16)) # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) #### Private interface #################################################### # Cache of tool images (scaled to the appropriate size). _image_cache = Instance(ImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new tool bar manager. """ # Base class contructor. super(ToolPaletteManager, self).__init__(*args, **traits) # An image cache to make sure that we only load each image used in the # tool bar exactly once. self._image_cache = ImageCache(self.image_size[0], self.image_size[1]) return ########################################################################### # 'ToolPaletteManager' interface. ########################################################################### def create_tool_palette(self, parent, controller=None): """ Creates a tool bar. """ # Create the control. tool_palette = ToolPalette(parent) # Add all of items in the manager's groups to the tool bar. self._add_tools(tool_palette, self.groups) self._set_initial_tool_state(tool_palette, self.groups) return tool_palette ########################################################################### # Private interface. ########################################################################### def _add_tools(self, tool_palette, groups): """ Adds tools for all items in a list of groups. """ previous_non_empty_group = None for group in self.groups: if len(group.items) > 0: # Is a separator required? ## FIXME : Does the palette need the notion of a separator? ## if previous_non_empty_group is not None and group.separator: ## tool_bar.AddSeparator() ## ## previous_non_empty_group = group # Create a tool bar tool for each item in the group. for item in group.items: control_id = item.add_to_palette( tool_palette, self._image_cache, self.show_tool_names ) item.control_id = control_id tool_palette.realize() return def _set_initial_tool_state(self, tool_palette, groups): """ Workaround for the wxPython tool bar bug. Without this, only the first item in a radio group can be selected when the tool bar is first realised 8^() """ for group in groups: checked = False for item in group.items: # If the group is a radio group, set the initial checked state # of every tool in it. if item.action.style == 'radio': tool_palette.toggle_tool(item.control_id, item.action.checked) checked = checked or item.action.checked # Every item in a radio group MUST be 'radio' style, so we # can just skip to the next group. else: break # We get here if the group is a radio group. else: # If none of the actions in the group is specified as 'checked' # we will check the first one. if not checked and len(group.items) > 0: group.items[0].action.checked = True return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/tool_palette.py0000644000175100001440000001370611674463363022634 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ View of an ActionManager drawn as a rectangle of buttons. """ import wx from pyface.widget import Widget from traits.api import Bool, Dict, Int, List, Tuple # HTML templates. # FIXME : Not quite the right color. HTML = """ %s """ PART = """""" class ToolPalette(Widget): tools = List id_tool_map = Dict tool_id_to_button_map = Dict button_size = Tuple((25, 25), Int, Int) is_realized = Bool(False) tool_listeners = Dict # Maps a button id to its tool id. button_tool_map = Dict ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new tool palette. """ # Base class constructor. super(ToolPalette, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) return ########################################################################### # ToolPalette interface. ########################################################################### def add_tool(self, label, bmp, kind, tooltip, longtip): """ Add a tool with the specified properties to the palette. Return an id that can be used to reference this tool in the future. """ wxid = wx.NewId() params = (wxid, label, bmp, kind, tooltip, longtip) self.tools.append(params) self.id_tool_map[wxid] = params if self.is_realized: self._reflow() return wxid def toggle_tool(self, id, checked): """ Toggle the tool identified by 'id' to the 'checked' state. If the button is a toggle or radio button, the button will be checked if the 'checked' parameter is True; unchecked otherwise. If the button is a standard button, this method is a NOP. """ button = self.tool_id_to_button_map.get(id, None) if button is not None and hasattr(button, 'SetToggle'): button.SetToggle(checked) return def enable_tool(self, id, enabled): """ Enable or disable the tool identified by 'id'. """ button = self.tool_id_to_button_map.get(id, None) if button is not None: button.SetEnabled(enabled) return def on_tool_event(self, id, callback): """ Register a callback for events on the tool identified by 'id'. """ callbacks = self.tool_listeners.setdefault(id, []) callbacks.append(callback) return def realize(self): """ Realize the control so that it can be displayed. """ self.is_realized = True self._reflow() return def get_tool_state(self, id): """ Get the toggle state of the tool identified by 'id'. """ button = self.tool_id_to_button_map.get(id, None) if hasattr(button, 'GetToggle'): if button.GetToggle(): state = 1 else: state = 0 else: state = 0 return state ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): html_window = wx.html.HtmlWindow(parent, -1, style=wx.CLIP_CHILDREN) return html_window def _reflow(self): """ Reflow the layout. """ # Create a bit of html for each tool. parts = [] for param in self.tools: parts.append(PART % (str(param[0]), self.button_size)) # Create the entire html page. html = HTML % ''.join(parts) # Set the HTML on the widget. This will create all of the buttons. self.control.SetPage(html) for param in self.tools: self._initialize_tool(param) return def _initialize_tool(self, param): """ Initialize the tool palette button. """ wxid, label, bmp, kind, tooltip, longtip = param panel = self.control.FindWindowById(wxid) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) panel.SetWindowStyleFlag(wx.CLIP_CHILDREN) from wx.lib.buttons import GenBitmapToggleButton, GenBitmapButton if kind == 'radio': button = GenBitmapToggleButton(panel, -1, None, size=self.button_size) else: button = GenBitmapButton(panel, -1, None, size=self.button_size) self.button_tool_map[button.GetId()] = wxid self.tool_id_to_button_map[wxid] = button wx.EVT_BUTTON(panel, button.GetId(), self._on_button) button.SetBitmapLabel(bmp) button.SetToolTipString(label) sizer.Add(button, 0, wx.EXPAND) return def _on_button(self, event): button_id = event.GetId() tool_id = self.button_tool_map.get(button_id, None) if tool_id is not None: for listener in self.tool_listeners.get(tool_id, []): listener(event) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/menu_bar_manager.py0000644000175100001440000000357111674463363023422 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx specific implementation of a menu bar manager. """ # Major package imports. import wx # Local imports. from pyface.action.action_manager import ActionManager class MenuBarManager(ActionManager): """ A menu bar manager realizes itself in errr, a menu bar control. """ ########################################################################### # 'MenuBarManager' interface. ########################################################################### def create_menu_bar(self, parent, controller=None): """ Creates a menu bar representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller menu_bar = wx.MenuBar() # Every item in every group must be a menu manager. for group in self.groups: for item in group.items: menu = item.create_menu(parent, controller) menu_bar.Append(menu, item.name) return menu_bar #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/__init__.py0000644000175100001440000000034511674463363021673 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/action/menu_manager.py0000644000175100001440000001505211674463363022573 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx specific implementation of a menu manager. """ # Major package imports. import wx # Enthought library imports. from traits.api import Unicode # Local imports. from pyface.action.action_manager import ActionManager from pyface.action.action_manager_item import ActionManagerItem from pyface.action.group import Group class MenuManager(ActionManager, ActionManagerItem): """ A menu manager realizes itself in a menu control. This could be a sub-menu or a context (popup) menu. """ #### 'MenuManager' interface ############################################## # The menu manager's name (if the manager is a sub-menu, this is what its # label will be). name = Unicode ########################################################################### # 'MenuManager' interface. ########################################################################### def create_menu(self, parent, controller=None): """ Creates a menu representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller return _Menu(self, parent, controller) ########################################################################### # 'ActionManagerItem' interface. ########################################################################### def add_to_menu(self, parent, menu, controller): """ Adds the item to a menu. """ id = wx.NewId() sub = self.create_menu(parent, controller) # fixme: Nasty hack to allow enabling/disabling of menus. sub._id = id sub._menu = menu menu.AppendMenu(id, self.name, sub) return def add_to_toolbar(self, parent, tool_bar, image_cache, controller): """ Adds the item to a tool bar. """ raise ValueError("Cannot add a menu manager to a toolbar.") class _Menu(wx.Menu): """ The toolkit-specific menu control. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, manager, parent, controller): """ Creates a new tree. """ # Base class constructor. wx.Menu.__init__(self) # The parent of the menu. self._parent = parent # The manager that the menu is a view of. self._manager = manager # The controller. self._controller = controller # List of menu items self.menu_items = [] # Create the menu structure. self.refresh() # Listen to the manager being updated. self._manager.on_trait_change(self.refresh, 'changed') self._manager.on_trait_change(self._on_enabled_changed, 'enabled') return ########################################################################### # '_Menu' interface. ########################################################################### def clear(self): """ Clears the items from the menu. """ for item in self.GetMenuItems(): if item.GetSubMenu() is not None: item.GetSubMenu().clear() self.Delete(item.GetId()) for item in self.menu_items: item.dispose() self.menu_items = [] return def is_empty(self): """ Is the menu empty? """ return self.GetMenuItemCount() == 0 def refresh(self): """ Ensures that the menu reflects the state of the manager. """ self.clear() manager = self._manager parent = self._parent previous_non_empty_group = None for group in manager.groups: previous_non_empty_group = self._add_group( parent, group, previous_non_empty_group ) return def show(self, x=None, y=None): """ Show the menu at the specified location. """ if x is None or y is None: self._parent.PopupMenu(self) else: self._parent.PopupMenuXY(self, x, y) return ########################################################################### # Private interface. ########################################################################### def _on_enabled_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ # fixme: Nasty hack to allow enabling/disabling of menus. # # We cannot currently (AFAIK) disable menus on the menu bar. Hence # we don't give them an '_id'... if hasattr(self, '_id'): self._menu.Enable(self._id, new) return def _add_group(self, parent, group, previous_non_empty_group=None): """ Adds a group to a menu. """ if len(group.items) > 0: # Is a separator required? if previous_non_empty_group is not None and group.separator: self.AppendSeparator() # Create actions and sub-menus for each contribution item in # the group. for item in group.items: if isinstance(item, Group): if len(item.items) > 0: self._add_group(parent, item, previous_non_empty_group) if previous_non_empty_group is not None \ and previous_non_empty_group.separator \ and item.separator: self.AppendSeparator() previous_non_empty_group = item else: item.add_to_menu(parent, self, self._controller) previous_non_empty_group = group return previous_non_empty_group #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/action/tool_bar_manager.py0000644000175100001440000001754011674463363023434 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ The wx specific implementation of the tool bar manager. """ # Major package imports. import wx # Enthought library imports. from traits.api import Bool, Enum, Instance, Str, Tuple # Local imports. from pyface.image_cache import ImageCache from pyface.action.action_manager import ActionManager class ToolBarManager(ActionManager): """ A tool bar manager realizes itself in errr, a tool bar control. """ #### 'ToolBarManager' interface ########################################### # Is the tool bar enabled? enabled = Bool(True) # Is the tool bar visible? visible = Bool(True) # The size of tool images (width, height). image_size = Tuple((16, 16)) # The toolbar name (used to distinguish multiple toolbars). name = Str('ToolBar') # The orientation of the toolbar. orientation = Enum('horizontal', 'vertical') # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) # Should we display the horizontal divider? show_divider = Bool(False) #### Private interface #################################################### # Cache of tool images (scaled to the appropriate size). _image_cache = Instance(ImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new tool bar manager. """ # Base class contructor. super(ToolBarManager, self).__init__(*args, **traits) # An image cache to make sure that we only load each image used in the # tool bar exactly once. self._image_cache = ImageCache(self.image_size[0], self.image_size[1]) return ########################################################################### # 'ToolBarManager' interface. ########################################################################### #### Trait change handlers ################################################ #### Methods ############################################################## def create_tool_bar(self, parent, controller=None): """ Creates a tool bar. """ # If a controller is required it can either be set as a trait on the # tool bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller # Determine the wx style for the tool bar based on any optional # settings. style = wx.NO_BORDER | wx.TB_FLAT | wx.CLIP_CHILDREN if self.show_tool_names: style |= wx.TB_TEXT if self.orientation == 'horizontal': style |= wx.TB_HORIZONTAL else: style |= wx.TB_VERTICAL if not self.show_divider: style |= wx.TB_NODIVIDER # Create the control. tool_bar = _ToolBar(self, parent, -1, style=style) # fixme: Setting the tool bitmap size seems to be the only way to # change the height of the toolbar in wx. tool_bar.SetToolBitmapSize(self.image_size) # Add all of items in the manager's groups to the tool bar. self._wx_add_tools(parent, tool_bar, controller) # Make the tools appear in the tool bar (without this you will see # nothing!). tool_bar.Realize() # fixme: Without the following hack, only the first item in a radio # group can be selected when the tool bar is first realised 8^() self._wx_set_initial_tool_state(tool_bar) return tool_bar ########################################################################### # Private interface. ########################################################################### def _wx_add_tools(self, parent, tool_bar, controller): """ Adds tools for all items in the list of groups. """ previous_non_empty_group = None for group in self.groups: if len(group.items) > 0: # Is a separator required? if previous_non_empty_group is not None and group.separator: tool_bar.AddSeparator() previous_non_empty_group = group # Create a tool bar tool for each item in the group. for item in group.items: item.add_to_toolbar( parent, tool_bar, self._image_cache, controller, self.show_tool_names ) return def _wx_set_initial_tool_state(self, tool_bar): """ Workaround for the wxPython tool bar bug. Without this, only the first item in a radio group can be selected when the tool bar is first realised 8^() """ for group in self.groups: checked = False for item in group.items: # If the group is a radio group, set the initial checked state # of every tool in it. if item.action.style == 'radio': tool_bar.ToggleTool(item.control_id, item.action.checked) checked = checked or item.action.checked # Every item in a radio group MUST be 'radio' style, so we # can just skip to the next group. else: break # We get here if the group is a radio group. else: # If none of the actions in the group is specified as 'checked' # we will check the first one. if not checked and len(group.items) > 0: group.items[0].action.checked = True return class _ToolBar(wx.ToolBar): """ The toolkit-specific tool bar implementation. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tool_bar_manager, parent, id, style): """ Constructor. """ wx.ToolBar.__init__(self, parent, -1, style=style) # Listen for changes to the tool bar manager's enablement and # visibility. self.tool_bar_manager = tool_bar_manager self.tool_bar_manager.on_trait_change( self._on_tool_bar_manager_enabled_changed, 'enabled' ) self.tool_bar_manager.on_trait_change( self._on_tool_bar_manager_visible_changed, 'visible' ) return ########################################################################### # Trait change handlers. ########################################################################### def _on_tool_bar_manager_enabled_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ obj.window._wx_enable_tool_bar(self, new) return def _on_tool_bar_manager_visible_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ obj.window._wx_show_tool_bar(self, new) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/wizard/0000755000175100001440000000000011674463363017603 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/wizard/wizard_page.py0000644000175100001440000000552511674463363022460 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A page in a wizard. """ # Major package imports. import wx # Enthought library imports. from traits.api import Bool, implements, HasTraits, Str, Tuple, \ Unicode from pyface.api import HeadingText from pyface.wizard.i_wizard_page import IWizardPage, MWizardPage class WizardPage(MWizardPage, HasTraits): """ The toolkit specific implementation of a WizardPage. See the IWizardPage interface for the API documentation. """ implements(IWizardPage) #### 'IWizardPage' interface ############################################## id = Str next_id = Str last_page = Bool(False) complete = Bool(False) heading = Unicode subheading = Unicode size = Tuple ########################################################################### # 'IWizardPage' interface. ########################################################################### def create_page(self, parent): """ Creates the wizard page. """ # FIXME: implement support for the size trait. panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) # The 'pretty' heading ;^) if len(self.heading) > 0: title = HeadingText(panel, text=self.heading) sizer.Add(title.control, 0, wx.EXPAND | wx.BOTTOM, 5) if len(self.subheading) > 0: subtitle = wx.StaticText(panel, -1, self.subheading) sizer.Add(subtitle, 0, wx.EXPAND | wx.BOTTOM, 5) # The page content. content = self._create_page_content(panel) sizer.Add(content, 1, wx.EXPAND) return panel ########################################################################### # Protected 'IWizardPage' interface. ########################################################################### def _create_page_content(self, parent): """ Creates the actual page content. """ # Dummy implementation - override! panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) panel.SetBackgroundColour('yellow') return panel #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/wizard/__init__.py0000644000175100001440000000034511674463363021716 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/wizard/wizard.py0000644000175100001440000001364711674463363021470 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all pyface wizards. """ # Major package imports. import wx # Enthought library imports. from traits.api import Bool, implements, Instance, List, Property, \ Unicode from pyface.api import Dialog, LayeredPanel from pyface.wizard.i_wizard import IWizard, MWizard from pyface.wizard.i_wizard_controller import IWizardController from pyface.wizard.i_wizard_page import IWizardPage class Wizard(MWizard, Dialog): """ The base class for all pyface wizards. See the IWizard interface for the API documentation. """ implements(IWizard) #### 'IWizard' interface ################################################## pages = Property(List(IWizardPage)) controller = Instance(IWizardController) show_cancel = Bool(True) #### 'IWindow' interface ################################################## title = Unicode('Wizard') ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_dialog_area(self, parent): """ Creates the main content of the dialog. """ self._layered_panel = panel = LayeredPanel(parent) # fixme: Specific size? panel.control.SetSize((100, 200)) return panel.control def _create_buttons(self, parent): """ Creates the buttons. """ sizer = wx.BoxSizer(wx.HORIZONTAL) # 'Back' button. self._back = back = wx.Button(parent, -1, "Back") wx.EVT_BUTTON(parent, back.GetId(), self._on_back) sizer.Add(back, 0) # 'Next' button. self._next = next = wx.Button(parent, -1, "Next") wx.EVT_BUTTON(parent, next.GetId(), self._on_next) sizer.Add(next, 0, wx.LEFT, 5) next.SetDefault() # 'Finish' button. self._finish = finish = wx.Button(parent, wx.ID_OK, "Finish") finish.Enable(self.controller.complete) wx.EVT_BUTTON(parent, wx.ID_OK, self._wx_on_ok) sizer.Add(finish, 0, wx.LEFT, 5) # 'Cancel' button. if self.show_cancel: self._cancel = cancel = wx.Button(parent, wx.ID_CANCEL, "Cancel") wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) # 'Help' button. if len(self.help_id) > 0: help = wx.Button(parent, wx.ID_HELP, "Help") wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help) sizer.Add(help, 0, wx.LEFT, 10) return sizer ########################################################################### # Protected 'MWizard' interface. ########################################################################### def _show_page(self, page): """ Show the specified page. """ panel = self._layered_panel # If the page has not yet been shown then create it. if not panel.has_layer(page.id): panel.add_layer(page.id, page.create_page(panel.control)) # Show the page. layer = panel.show_layer(page.id) layer.SetFocus() # Set the current page in the controller. # # fixme: Shouldn't this interface be reversed? Maybe calling # 'next_page' on the controller should cause it to set its own current # page? self.controller.current_page = page return def _update(self): """ Enables/disables buttons depending on the state of the wizard. """ controller = self.controller current_page = controller.current_page is_first_page = controller.is_first_page(current_page) is_last_page = controller.is_last_page(current_page) # 'Next button'. if self._next is not None: self._next.Enable(current_page.complete and not is_last_page) # 'Back' button. if self._back is not None: self._back.Enable(not is_first_page) # 'Finish' button. if self._finish is not None: self._finish.Enable(controller.complete) # If this is the last page then the 'Finish' button is the default # button, otherwise the 'Next' button is the default button. if is_last_page: if self._finish is not None: self._finish.SetDefault() else: if self._next is not None: self._next.SetDefault() return #### Trait handlers ####################################################### def _controller_default(self): """ Provide a default controller. """ from pyface.wizard.wizard_controller import WizardController return WizardController() def _get_pages(self): """ Returns the pages in the wizard. """ return self.controller.pages def _set_pages(self, pages): """ Sets the pages in the wizard. """ self.controller.pages = pages #### wx event handlers #################################################### def _on_next(self, event): """ Called when the 'Next' button is pressed. """ self.next() return def _on_back(self, event): """ Called when the 'Back' button is pressed. """ self.previous() return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/0000755000175100001440000000000011674463363017230 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/grid/checkbox_image_renderer.py0000644000175100001440000000276511674463363024432 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A renderer which displays a checked-box for a True value and an unchecked box for a false value. """ # Enthought-library imports from pyface.image_resource import ImageResource # local imports from mapped_grid_cell_image_renderer import MappedGridCellImageRenderer checked_image_map = { True: ImageResource('checked'), False: ImageResource('unchecked'), } class CheckboxImageRenderer(MappedGridCellImageRenderer): def __init__(self, display_text = False): text_map = None if display_text: text_map = { True: 'True', False: 'False' } # Base-class constructor super(CheckboxImageRenderer, self).__init__(checked_image_map, text_map) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/tests/0000755000175100001440000000000011674463363020372 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/grid/tests/simple_grid_model_test_case.py0000644000175100001440000000375111674463363026462 0ustar ischnellusers00000000000000import unittest from pyface.ui.wx.grid.api \ import GridRow, GridColumn, SimpleGridModel class CompositeGridModelTestCase( unittest.TestCase ): def setUp(self): self.model = SimpleGridModel(data=[[None,2],[3,4]], rows=[GridRow(label='foo'), GridRow(label='bar')], columns=[GridColumn(label='cfoo'), GridColumn(label='cbar')] ) return def test_get_column_count(self): self.assertEqual(self.model.get_column_count(), 2) return def test_get_row_count(self): self.assertEqual(self.model.get_row_count(), 2) return def test_get_row_name(self): # Regardless of the rows specified in the composed models, the # composite model returns its own rows. self.assertEqual(self.model.get_row_name(0), 'foo') self.assertEqual(self.model.get_row_name(1), 'bar') return def test_get_column_name(self): self.assertEqual(self.model.get_column_name(0), 'cfoo') self.assertEqual(self.model.get_column_name(1), 'cbar') return def test_get_value(self): self.assertEqual(self.model.get_value(0,0), None) self.assertEqual(self.model.get_value(0,1), 2) self.assertEqual(self.model.get_value(1,0), 3) self.assertEqual(self.model.get_value(1,1), 4) return def test_is_cell_empty(self): rows = self.model.get_row_count() columns = self.model.get_column_count() self.assertEqual(self.model.is_cell_empty(0,0), True, "Cell containing None should be empty.") self.assertEqual(self.model.is_cell_empty(10,10), True, "Cell outside the range of values should be empty.") return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/tests/composite_grid_model_test_case.py0000644000175100001440000000674311674463363027177 0ustar ischnellusers00000000000000import unittest from pyface.ui.wx.grid.api \ import CompositeGridModel, GridRow, GridColumn, SimpleGridModel class CompositeGridModelTestCase( unittest.TestCase ): def setUp(self): self.model_1 = SimpleGridModel(data=[[1,2],[3,4]], rows=[GridRow(label='foo'), GridRow(label='bar')], columns=[GridColumn(label='cfoo'), GridColumn(label='cbar')] ) self.model_2 = SimpleGridModel(data=[[3,4,5],[6,7,8]], rows=[GridRow(label='baz'), GridRow(label='bat')], columns=[GridColumn(label='cfoo_2'), GridColumn(label='cbar_2'), GridColumn(label='cbaz_2')] ) self.model = CompositeGridModel(data=[self.model_1, self.model_2]) return def test_get_column_count(self): column_count_1 = self.model_1.get_column_count() column_count_2 = self.model_2.get_column_count() self.assertEqual(self.model.get_column_count(), column_count_1 + column_count_2) return def test_get_row_count(self): self.assertEqual(self.model.get_row_count(), 2) return def test_get_row_name(self): # Regardless of the rows specified in the composed models, the # composite model returns its own rows. self.assertEqual(self.model.get_row_name(0), '1') self.assertEqual(self.model.get_row_name(1), '2') return def test_get_column_name(self): self.assertEqual(self.model.get_column_name(0), 'cfoo') self.assertEqual(self.model.get_column_name(1), 'cbar') self.assertEqual(self.model.get_column_name(2), 'cfoo_2') self.assertEqual(self.model.get_column_name(3), 'cbar_2') self.assertEqual(self.model.get_column_name(4), 'cbaz_2') return def test_get_value(self): self.assertEqual(self.model.get_value(0,0), 1) self.assertEqual(self.model.get_value(0,1), 2) self.assertEqual(self.model.get_value(0,2), 3) self.assertEqual(self.model.get_value(0,3), 4) self.assertEqual(self.model.get_value(0,4), 5) self.assertEqual(self.model.get_value(1,0), 3) self.assertEqual(self.model.get_value(1,1), 4) self.assertEqual(self.model.get_value(1,2), 6) self.assertEqual(self.model.get_value(1,3), 7) self.assertEqual(self.model.get_value(1,4), 8) return def test_is_cell_empty(self): rows = self.model.get_row_count() columns = self.model.get_column_count() self.assertEqual(self.model.is_cell_empty(0,0), False, "Cell (0,0) should not be empty.") self.assertEqual(self.model.is_cell_empty(rows,0), True, "Cell below the table should be empty.") self.assertEqual(self.model.is_cell_empty(0,columns), True, "Cell right of the table should be empty.") self.assertEqual(self.model.is_cell_empty(rows,columns), True, "Cell below and right of table should be empty.") return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/edit_image_renderer.py0000644000175100001440000000262411674463363023563 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # ETS imports from pyface.image_resource import ImageResource # Local import from grid_cell_image_renderer import GridCellImageRenderer class EditImageRenderer(GridCellImageRenderer): image = ImageResource('table_edit') def __init__(self, **kw): super(EditImageRenderer, self).__init__(self, **kw) def get_image_for_cell(self, grid, row, col): """ returns the image resource for the table_edit bitmap """ # show the icon if the obj does not have an editable trait # or if the editable trait is True obj = grid.GetTable().model.get_rows_drag_value([row])[0] if (not hasattr(obj, 'editable')) or obj.editable: return self.image return None def _get_text(self, grid, row, col): return None pyface-4.1.0/pyface/ui/wx/grid/images/0000755000175100001440000000000011674463363020475 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/wx/grid/images/table_edit.png0000644000175100001440000000612111674463363023277 0ustar ischnellusers00000000000000PNG  IHDRa pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3-gAMA|Q cHRMz%u0`:o_FlIDATxڤ/CaG.]HT%b; k?C QhJT]{{㺆g:9-9-hd2;6ɣ'o&o}RRg2Y޻UtАRb?]12ODB^ѩBRH)D"^ B.AcvoXZ^$_uB?i nz[X:Z ( vbm8%4͏mYre<:u+Wۮ-Ϸ9H5'WjH>}c[M -dsyݿC JaOu׽wύYIENDB`pyface-4.1.0/pyface/ui/wx/grid/images/image_not_found.png0000644000175100001440000000136711674463363024347 0ustar ischnellusers00000000000000PNG  IHDRabKGD pHYs  tIME 6eDtEXtCommentMenu-sized icon ========== (c) 2003 Jakub 'jimmac' Steiner, http://jimmac.musichall.cz created with the GIMP, http://www.gimp.orggGIDATxڥkSQwf$ZbېBk+ŕ]\W @t!n]Bp#"FED(U5ƒ4sq__Ru>sΙ3הgOYZ,j5yo =DVmx$K&zky*01 R>{U8xx/W.B ށ *J\¹i&bf64TQu;E:΅&Jƞ}lQ,h[D @^ Fdžqf[ { >mʡ#{!=a0;B,;sJd D= "6*pZV%]^ώa&! 6j?t''xrfwNKpu#`R"c =HIGA?r /Ȥwa?o(VmP3;?٦_?Vd9AIENDB`pyface-4.1.0/pyface/ui/wx/grid/images/image_LICENSE.txt0000644000175100001440000000153611674463363023467 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- GV (Gael Varoquaux) Public Domain N/A Nuvola LGPL image_LICENSE_Nuvola.txt OOo LGPL image_LICENSE_OOo.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/grid/images: checked.png | GV image_not_found.png | Nuvola table_edit.png | OOo unchecked.png | GV pyface-4.1.0/pyface/ui/wx/grid/images/unchecked.png0000644000175100001440000000034511674463363023136 0ustar ischnellusers00000000000000PNG  IHDRabKGDC pHYs  tIME (7rIDAT8 0 E-n!%A{z'AQQ j.b-.x,{ʙsF8f$7Rvg6afvi%愝P7NCxwEtIENDB`pyface-4.1.0/pyface/ui/wx/grid/images/checked.png0000644000175100001440000000063411674463363022574 0ustar ischnellusers00000000000000PNG  IHDRabKGD pHYs  tIME - (Ig)IDAT8œjPZ'BI5Щ?K2)84KBHJP ⢁3.+ZB!,cPŶ B{뺙^ 8&RAey0$QɷҤd"tRqѽnО5f3C_l Q`>g<S=coSerX)h5bݭ9]PT@ga }kj-b~/O븅>z2@HPCM,',Iwd^QcfIENDB`pyface-4.1.0/pyface/ui/wx/grid/combobox_focus_handler.py0000644000175100001440000001027511674463363024313 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Workaround for combobox focus problem in wx 2.6. """ # Major package imports import wx #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- # Mapping from key code to key event handler names: Handlers = { wx.WXK_LEFT: '_left_key', wx.WXK_RIGHT: '_right_key', wx.WXK_UP: '_up_key', wx.WXK_DOWN: '_down_key', wx.WXK_ESCAPE: '_escape_key' } #------------------------------------------------------------------------------- # 'ComboboxFocusHandler' class: #------------------------------------------------------------------------------- class ComboboxFocusHandler(wx.EvtHandler): def __init__(self, grid): wx.EvtHandler.__init__(self) self._grid = grid wx.EVT_KEY_DOWN(self, self._on_key) def _on_key(self, evt): """ Called when a key is pressed. """ getattr( self, Handlers.get( evt.GetKeyCode(), '_ignore_key' ))( evt ) #-- Key Event Handlers -------------------------------------------------------- def _ignore_key ( self, evt ): evt.Skip() def _escape_key ( self, evt ): self._grid.DisableCellEditControl() def _left_key ( self, evt ): if not (evt.ControlDown() or evt.AltDown()): evt.Skip() return grid, row, col, rows, cols = self._grid_info() grid._no_reset_row = True first = True while first or (not self._edit_cell( row, col )): col -= 1 if col < 0: col = cols - 1 row -= 1 if row < 0: if not first: break row = rows - 1 first = False def _right_key ( self, evt ): if not (evt.ControlDown() or evt.AltDown()): evt.Skip() return grid, row, col, rows, cols = self._grid_info() grid._no_reset_row = True first = True while first or (not self._edit_cell( row, col )): col += 1 if col >= cols: col = 0 row += 1 if row >= rows: if not first: break row = 0 first = False def _up_key ( self, evt ): if not (evt.ControlDown() or evt.AltDown()): evt.Skip() return grid, row, col, rows, cols = self._grid_info() grid._no_reset_col = True row -= 1 if row < 0: row = rows - 1 self._edit_cell( row, col ) def _down_key ( self, evt ): if not (evt.ControlDown() or evt.AltDown()): evt.Skip() return grid, row, col, rows, cols = self._grid_info() grid._no_reset_col = True row += 1 if row >= rows: row = 0 self._edit_cell( row, col ) #-- Private Methods ----------------------------------------------------------- def _grid_info ( self ): g = self._grid return ( g, g.GetGridCursorRow(), g.GetGridCursorCol(), g.GetNumberRows(), g.GetNumberCols() ) def _edit_cell ( self, row, col ): grid = self._grid grid.DisableCellEditControl() grid.SetGridCursor( row, col ) if not grid.CanEnableCellControl(): return False grid.EnableCellEditControl() grid.MakeCellVisible( row, col ) return True #### EOF #################################################################### pyface-4.1.0/pyface/ui/wx/grid/mapped_grid_cell_image_renderer.py0000644000175100001440000000441011674463363026103 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A renderer which will display a cell-specific image in addition to some text displayed in the same way the standard string renderer normally would, all data retrieved from specified value maps. """ from grid_cell_image_renderer import GridCellImageRenderer class MappedGridCellImageRenderer(GridCellImageRenderer): """ Maps data values to image and text. """ def __init__(self, image_map = None, text_map = None): # Base-class constructor. We pass ourself as the provider super(MappedGridCellImageRenderer, self).__init__(self) self.image_map = image_map self.text_map = text_map return def get_image_for_cell(self, grid, row, col): if self.image_map is None: return value = self._get_value(grid, row, col) if self.image_map.has_key(value): result = self.image_map[value] else: result = None return result def get_text_for_cell(self, grid, row, col): if self.text_map is None: return value = self._get_value(grid, row, col) if self.text_map.has_key(value): result = self.text_map[value] else: result = None return result def _get_value(self, grid, row, col): # first grab the PyGridTableBase object base = grid.GetTable() # from that we can get the pyface-level model object model = base.model # retrieve the unformatted value from the model and return it return model.get_cell_drag_value(row, col) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/api.py0000644000175100001440000000211111674463363020346 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from grid import Grid from grid_model import GridModel, GridSortEvent from composite_grid_model import CompositeGridModel from inverted_grid_model import InvertedGridModel from simple_grid_model import SimpleGridModel, GridRow, GridColumn from trait_grid_model import TraitGridModel, TraitGridColumn, \ TraitGridSelection from grid_cell_renderer import GridCellRenderer #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/grid.py0000644000175100001440000020312311674463363020530 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A grid control with a model/ui architecture. """ # Major package imports import sys import wx import wx.lib.gridmovers as grid_movers from os.path import abspath, exists from wx.grid import Grid as wxGrid from wx.grid import GridCellAttr, GridCellBoolRenderer, PyGridTableBase from wx.grid import GridTableMessage, \ GRIDTABLE_NOTIFY_ROWS_APPENDED, GRIDTABLE_NOTIFY_ROWS_DELETED, \ GRIDTABLE_NOTIFY_ROWS_INSERTED, GRIDTABLE_NOTIFY_COLS_APPENDED, \ GRIDTABLE_NOTIFY_COLS_DELETED, GRIDTABLE_NOTIFY_COLS_INSERTED, \ GRIDTABLE_REQUEST_VIEW_GET_VALUES, GRID_VALUE_STRING from wx import TheClipboard # Enthought library imports from pyface.api import Sorter, Widget from pyface.timer.api import do_later from traits.api import Bool, Color, Enum, Event, Font, Instance, Int, \ List, Trait, Undefined from pyface.wx.drag_and_drop import PythonDropSource, \ PythonDropTarget, PythonObject from pyface.wx.drag_and_drop import clipboard as enClipboard from traitsui.wx.dnd_editor import FileDropSource # local imports from grid_model import GridModel from combobox_focus_handler import ComboboxFocusHandler # Is this code running on MS Windows? is_win32 = (sys.platform == 'win32') ASCII_C = 67 class Grid(Widget): """ A grid control with a model/ui architecture. """ #### 'Grid' interface ##################################################### # The model that provides the data for the grid. model = Instance(GridModel, ()) # Should grid lines be shown on the table? enable_lines = Bool(True) # The color to show gridlines in grid_line_color = Color("blue") # Show row headers? show_row_headers = Bool(True) # Show column headers? show_column_headers = Bool(True) # The default font to use for text in labels default_label_font = Font(None) # The default background color for labels default_label_bg_color = Color(wx.Colour(236, 233, 216)) # The default text color for labels default_label_text_color = Color("black") # The color to use for a selection background selection_bg_color = Trait(wx.Colour(49, 106, 197), None, Color) # The color to use for a selection foreground/text selection_text_color = Trait(wx.Colour(255, 255, 255), None, Color) # The default font to use for text in cells default_cell_font = Font(None) # The default text color to use for text in cells default_cell_text_color = Color("black") # The default background color to use for editable cells default_cell_bg_color = Color("white") # The default background color to use for read-only cells #default_cell_read_only_color = Trait(Color("linen"), None, Color) default_cell_read_only_color = Color(wx.Colour(248, 247, 241)) # Should the grid be read-only? If this is set to false, individual # cells can still declare themselves read-only. read_only = Bool(False) # Selection mode. selection_mode = Enum('cell', 'rows', 'cols', '') # Sort data when a column header is clicked? allow_column_sort = Bool(True) # Sort data when a row header is clicked? allow_row_sort = Bool(False) # pixel height of column labels column_label_height = Int(32) # pixel width of row labels row_label_width = Int(82) # auto-size columns and rows? autosize = Bool(False) # Allow single-click access to cell-editors? edit_on_first_click = Bool(True) #### Events #### # A cell has been activated (ie. double-clicked). cell_activated = Event # The current selection has changed. selection_changed = Event # A drag operation was started on a cell. cell_begin_drag = Event # A left-click occurred on a cell. cell_left_clicked = Event # A left-click occurred on a cell at specific location # Useful if the cell contains multiple controls though the hit test # is left to the consumer of the event cell_left_clicked_location = Event # A right-click occurred on a cell. cell_right_clicked = Event # protected variables to store the location of the clicked event _x_clicked = Int _y_clicked = Int ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new grid. 'parent' is the toolkit-specific control that is the grid's parent. """ # Base class constructors. super(Grid, self).__init__(**traits) # Flag set when columns are resizing: self._user_col_size = False # Create the toolkit-specific control. self.control = self._grid = grid = wxGrid(parent, -1) grid.grid = self # Set when moving edit cursor: grid._no_reset_col = False grid._no_reset_row = False # initialize the current selection self.__current_selection = () self.__initialize_counts(self.model) self.__initialize_sort_state() # Don't display any extra space around the rows and columns. grid.SetMargins(0, 0) # Provides more accurate scrolling behavior without creating large # margins on the bottom and right. The down side is that it makes # scrolling using the scroll bar buttons painfully slow: grid.SetScrollLineX(1) grid.SetScrollLineY(1) # Tell the grid to get its data from the model. # # N.B The terminology used in the wxPython API is a little confusing! # --- The 'SetTable' method is actually setting the model used by # the grid (which is the view)! # # The second parameter to 'SetTable' tells the grid to take ownership # of the model and to destroy it when it is done. Otherwise you would # need to keep a reference to the model and manually destroy it later # (by calling it's Destroy method). # # fixme: We should create a default model if one is not supplied. # The wx virtual table hook. self._grid_table_base = _GridTableBase(self.model, self) # keep the onership of the table in this class grid.SetTable(self._grid_table_base, takeOwnership=False) # Enable column and row moving: grid_movers.GridColMover(grid) grid_movers.GridRowMover(grid) grid.Bind(grid_movers.EVT_GRID_COL_MOVE, self._on_col_move, grid) grid.Bind(grid_movers.EVT_GRID_ROW_MOVE, self._on_row_move, grid) smotc = self.model.on_trait_change otc = self.on_trait_change smotc(self._on_model_content_changed, 'content_changed') smotc(self._on_model_structure_changed, 'structure_changed') smotc(self._on_row_sort, 'row_sorted') smotc(self._on_column_sort, 'column_sorted') otc(self._on_new_model, 'model') # hook up style trait handlers - note that we have to use # dynamic notification hook-ups because these handlers should # not be called until after the control object is initialized. # static trait notifiers get called when the object inits. otc(self._on_enable_lines_changed, 'enable_lines') otc(self._on_grid_line_color_changed, 'grid_line_color') otc(self._on_default_label_font_changed, 'default_label_font') otc(self._on_default_label_bg_color_changed, 'default_label_bg_color') otc(self._on_default_label_text_color_changed, 'default_label_text_color') otc(self._on_selection_bg_color_changed, 'selection_bg_color') otc(self._on_selection_text_color_changed, 'selection_text_color') otc(self._on_default_cell_font_changed, 'default_cell_font') otc(self._on_default_cell_text_color_changed, 'default_cell_text_color') otc(self._on_default_cell_bg_color_changed, 'default_cell_bg_color') otc(self._on_read_only_changed, 'read_only_changed') otc(self._on_selection_mode_changed, 'selection_mode') otc(self._on_column_label_height_changed, 'column_label_height') otc(self._on_row_label_width_changed, 'row_label_width') otc(self._on_show_column_headers_changed, 'show_column_headers') otc(self._on_show_row_headers_changed, 'show_row_headers') # Initialize wx handlers: self._notify_select = True wx.grid.EVT_GRID_SELECT_CELL(grid, self._on_select_cell) wx.grid.EVT_GRID_RANGE_SELECT(grid, self._on_range_select) wx.grid.EVT_GRID_COL_SIZE(grid, self._on_col_size) wx.grid.EVT_GRID_ROW_SIZE(grid, self._on_row_size) # This starts the cell editor on a double-click as well as on a second # click: wx.grid.EVT_GRID_CELL_LEFT_DCLICK(grid, self._on_cell_left_dclick) # Notify when cells are clicked on: wx.grid.EVT_GRID_CELL_RIGHT_CLICK(grid, self._on_cell_right_click) wx.grid.EVT_GRID_CELL_RIGHT_DCLICK(grid, self._on_cell_right_dclick) wx.grid.EVT_GRID_LABEL_RIGHT_CLICK(grid, self._on_label_right_click) wx.grid.EVT_GRID_LABEL_LEFT_CLICK(grid, self._on_label_left_click) #wx.grid.EVT_GRID_EDITOR_CREATED(grid, self._on_editor_created) if is_win32: wx.grid.EVT_GRID_EDITOR_HIDDEN(grid, self._on_editor_hidden) # We handle key presses to change the behavior of the and # keys to make manual data entry smoother. wx.EVT_KEY_DOWN(grid, self._on_key_down) # We handle control resize events to adjust column widths wx.EVT_SIZE(grid, self._on_size) # Handle drags: self._corner_window = grid.GetGridCornerLabelWindow() self._grid_window = gw = grid.GetGridWindow() self._row_window = rw = grid.GetGridRowLabelWindow() self._col_window = cw = grid.GetGridColLabelWindow() # Handle mouse button state changes: self._ignore = False for window in ( gw, rw, cw ): wx.EVT_MOTION( window, self._on_grid_motion ) wx.EVT_LEFT_DOWN( window, self._on_left_down ) wx.EVT_LEFT_UP( window, self._on_left_up ) wx.EVT_PAINT(self._grid_window, self._on_grid_window_paint) # Initialize the row and column models: self.__initialize_rows(self.model) self.__initialize_columns(self.model) self.__initialize_fonts() # Handle trait style settings: self.__initialize_style_settings() # Enable the grid as a drag and drop target: self._grid.SetDropTarget(PythonDropTarget(self)) self.__autosize() self._edit = False wx.EVT_IDLE(grid, self._on_idle) def dispose(self): # Remove all wx handlers: grid = self._grid wx.grid.EVT_GRID_SELECT_CELL( grid, None ) wx.grid.EVT_GRID_RANGE_SELECT( grid, None ) wx.grid.EVT_GRID_COL_SIZE( grid, None ) wx.grid.EVT_GRID_ROW_SIZE( grid, None ) wx.grid.EVT_GRID_CELL_LEFT_DCLICK( grid, None ) wx.grid.EVT_GRID_CELL_RIGHT_CLICK( grid, None ) wx.grid.EVT_GRID_CELL_RIGHT_DCLICK( grid, None ) wx.grid.EVT_GRID_LABEL_RIGHT_CLICK( grid, None ) wx.grid.EVT_GRID_LABEL_LEFT_CLICK( grid, None ) wx.grid.EVT_GRID_EDITOR_CREATED( grid, None ) if is_win32: wx.grid.EVT_GRID_EDITOR_HIDDEN( grid, None ) wx.EVT_KEY_DOWN( grid, None ) wx.EVT_SIZE( grid, None ) wx.EVT_PAINT( self._grid_window, None ) for window in ( self._grid_window , self._row_window , self._col_window ): wx.EVT_MOTION( window, None ) wx.EVT_LEFT_DOWN( window, None ) wx.EVT_LEFT_UP( window, None ) otc = self.on_trait_change otc(self._on_enable_lines_changed, 'enable_lines', remove = True) otc(self._on_grid_line_color_changed, 'grid_line_color', remove = True) otc(self._on_default_label_font_changed, 'default_label_font', remove = True) otc(self._on_default_label_bg_color_changed, 'default_label_bg_color', remove = True) otc(self._on_default_label_text_color_changed, 'default_label_text_color', remove = True) otc(self._on_selection_bg_color_changed, 'selection_bg_color', remove = True) otc(self._on_selection_text_color_changed, 'selection_text_color', remove = True) otc(self._on_default_cell_font_changed, 'default_cell_font', remove = True) otc(self._on_default_cell_text_color_changed, 'default_cell_text_color', remove = True) otc(self._on_default_cell_bg_color_changed, 'default_cell_bg_color', remove = True) otc(self._on_read_only_changed, 'read_only_changed', remove = True) otc(self._on_selection_mode_changed, 'selection_mode', remove = True) otc(self._on_column_label_height_changed, 'column_label_height', remove = True) otc(self._on_row_label_width_changed, 'row_label_width', remove = True) otc(self._on_show_column_headers_changed, 'show_column_headers', remove = True) otc(self._on_show_row_headers_changed, 'show_row_headers', remove = True) # It seems that the grid must be destroyed before disposing of # _grid_table_base: otherwise, the grid can apparently generate an # extra _GridTableBase.GetAttr call after _GridTableBase.dispose() # has been called, leading to headaches and segfaults. grid.Destroy() self._grid_table_base.dispose() ########################################################################### # Trait event handlers. ########################################################################### def _on_new_model(self): """ When we get a new model reinitialize grid match to that model. """ self._grid_table_base.model = self.model self.__initialize_counts(self.model) self._on_model_changed() if self.autosize: # Note that we don't call AutoSize() here, because autosizing # the rows looks like crap. self._grid.AutoSizeColumns(False) return def _on_model_content_changed(self): """ A notification method called when the data in the underlying model changes. """ self._grid.ForceRefresh() def _on_model_structure_changed(self, *arg, **kw): """ A notification method called when the underlying model has changed. Responsible for making sure the view object updates correctly. """ # Disable any active editors in order to prevent a wx crash bug: self._edit = False grid = self._grid # Make sure any current active editor has been disabled: grid.DisableCellEditControl() # More wacky fun with wx. We have to manufacture the appropriate # grid messages and send them off to make sure the grid updates # correctly: should_autosize = False # First check to see if rows have been added or deleted: row_count = self.model.get_row_count() delta = row_count - self._row_count self._row_count = row_count if delta > 0: # Rows were added: msg = GridTableMessage(self._grid_table_base, GRIDTABLE_NOTIFY_ROWS_APPENDED, delta) grid.ProcessTableMessage(msg) should_autosize = True elif delta < 0: # Rows were deleted: msg = GridTableMessage(self._grid_table_base, GRIDTABLE_NOTIFY_ROWS_DELETED, row_count, -delta) grid.ProcessTableMessage(msg) should_autosize = True # Now check for column changes: col_count = self.model.get_column_count() delta = col_count - self._col_count self._col_count = col_count if delta > 0: # Columns were added: msg = GridTableMessage(self._grid_table_base, GRIDTABLE_NOTIFY_COLS_APPENDED, delta) grid.ProcessTableMessage(msg) should_autosize = True elif delta < 0: # Columns were deleted: msg = GridTableMessage(self._grid_table_base, GRIDTABLE_NOTIFY_COLS_DELETED, col_count, -delta) grid.ProcessTableMessage(msg) should_autosize = True # Finally make sure we update for any new values in the table: msg = GridTableMessage(self._grid_table_base, GRIDTABLE_REQUEST_VIEW_GET_VALUES) grid.ProcessTableMessage(msg) if should_autosize: self.__autosize() # We have to make sure the editor/renderer cache in the GridTableBase # object is cleaned out: self._grid_table_base._clear_cache() grid.AdjustScrollbars() self._refresh() def _on_row_sort(self, evt): """ Handles a row_sorted event from the underlying model. """ # First grab the new data out of the event: if evt.index < 0: self._current_sorted_row = None else: self._current_sorted_row = evt.index self._row_sort_reversed = evt.reversed # Since the label may have changed we may need to autosize again: # fixme: when we change how we represent the sorted column # this should go away. self.__autosize() # Make sure everything updates to reflect the changes: self._on_model_structure_changed() def _on_column_sort(self, evt): """ Handles a column_sorted event from the underlying model. """ # first grab the new data out of the event if evt.index < 0: self._current_sorted_col = None else: self._current_sorted_col = evt.index self._col_sort_reversed = evt.reversed # since the label may have changed we may need to autosize again # fixme: when we change how we represent the sorted column # this should go away. self.__autosize() # make sure everything updates to reflect the changes self._on_model_structure_changed() def _on_enable_lines_changed(self): """ Handle a change to the enable_lines trait. """ self._grid.EnableGridLines(self.enable_lines) def _on_grid_line_color_changed(self): """ Handle a change to the enable_lines trait. """ self._grid.SetGridLineColour(self.grid_line_color) def _on_default_label_font_changed(self): """ Handle a change to the default_label_font trait. """ font = self.default_label_font if font is None: font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) font.SetWeight(wx.BOLD) self._grid.SetLabelFont(font) def _on_default_label_text_color_changed(self): """ Handle a change to the default_cell_text_color trait. """ if self.default_label_text_color is not None: color = self.default_label_text_color self._grid.SetLabelTextColour(color) self._grid.ForceRefresh() def _on_default_label_bg_color_changed(self): """ Handle a change to the default_cell_text_color trait. """ if self.default_label_bg_color is not None: color = self.default_label_bg_color self._grid.SetLabelBackgroundColour(color) self._grid.ForceRefresh() def _on_selection_bg_color_changed(self): """ Handle a change to the selection_bg_color trait. """ if self.selection_bg_color is not None: self._grid.SetSelectionBackground(self.selection_bg_color) def _on_selection_text_color_changed(self): """ Handle a change to the selection_text_color trait. """ if self.selection_text_color is not None: self._grid.SetSelectionForeground(self.selection_text_color) def _on_default_cell_font_changed(self): """ Handle a change to the default_cell_font trait. """ if self.default_cell_font is not None: self._grid.SetDefaultCellFont(self.default_cell_font) self._grid.ForceRefresh() def _on_default_cell_text_color_changed(self): """ Handle a change to the default_cell_text_color trait. """ if self.default_cell_text_color is not None: color = self.default_cell_text_color self._grid.SetDefaultCellTextColour(color) self._grid.ForceRefresh() def _on_default_cell_bg_color_changed(self): """ Handle a change to the default_cell_bg_color trait. """ if self.default_cell_bg_color is not None: color = self.default_cell_bg_color self._grid.SetDefaultCellBackgroundColour(color) self._grid.ForceRefresh() def _on_read_only_changed(self): """ Handle a change to the read_only trait. """ # should the whole grid be read-only? if self.read_only: self._grid.EnableEditing(False) else: self._grid.EnableEditing(True) def _on_selection_mode_changed(self): """ Handle a change to the selection_mode trait. """ # should we allow individual cells to be selected or only rows # or only columns if self.selection_mode == 'cell': self._grid.SetSelectionMode(wxGrid.wxGridSelectCells) elif self.selection_mode == 'rows': self._grid.SetSelectionMode(wxGrid.wxGridSelectRows) elif self.selection_mode == 'cols': self._grid.SetSelectionMode(wxGrid.wxGridSelectColumns) def _on_column_label_height_changed(self): """ Handle a change to the column_label_height trait. """ # handle setting for height of column labels if self.column_label_height is not None: self._grid.SetColLabelSize(self.column_label_height) def _on_row_label_width_changed(self): """ Handle a change to the row_label_width trait. """ # handle setting for width of row labels if self.row_label_width is not None: self._grid.SetRowLabelSize(self.row_label_width) def _on_show_column_headers_changed(self): """ Handle a change to the show_column_headers trait. """ if not self.show_column_headers: self._grid.SetColLabelSize(0) else: self._grid.SetColLabelSize(self.column_label_height) def _on_show_row_headers_changed(self): """ Handle a change to the show_row_headers trait. """ if not self.show_row_headers: self._grid.SetRowLabelSize(0) else: self._grid.SetRowLabelSize(self.row_label_width) ########################################################################### # 'Grid' interface. ########################################################################### def get_selection(self): """ Return a list of the currently selected objects. """ return self.__get_selection() def set_selection(self, selection_list, notify=True): """ Set the current selection to the objects listed in selection_list. Note that these objects are model-specific, as the grid depends on the underlying model to translate these objects into grid coordinates. A ValueError will be raised if the objects are not in the proper format for the underlying model. """ # Set the 'should we notify the model of the selection change' flag: self._notify_select = notify # First use the model to resolve the object list into a set of # grid coordinates cells = self.model.resolve_selection(selection_list) # Now make sure all those grid coordinates get set properly: grid = self._grid grid.BeginBatch() grid.ClearSelection() mode = self.selection_mode if mode == 'rows': self._select_rows(cells) elif mode != '': for selection in cells: row, col = max( 0, selection[0] ), max( 0, selection[1] ) grid.SelectBlock(row, col, row, col, True) grid.EndBatch() # Indicate that the selection has been changed: if notify: self.__fire_selection_changed() self._notify_select = True def stop_editing_indices(self, indices): """ If editing is occuring in a row in 'indices', stop editing. """ if self._grid.GetGridCursorRow() in indices: self._grid.DisableCellEditControl() ########################################################################### # wx event handlers. ########################################################################### def _on_size(self, evt): """ Called when the grid is resized. """ self.__autosize() # needed to handle problem in wx 2.6 with combobox cell editors def _on_editor_created(self, evt): editor = evt.GetControl() if editor is not None: editor.PushEventHandler(ComboboxFocusHandler(self._grid)) evt.Skip() def _on_editor_hidden(self, evt): # This is a hack to make clicking on a window button while a grid # editor is active work correctly under Windows. Normally, when the # user clicks on the button to exit grid cell editing and perform the # button function, only the grid cell editing is terminated under # Windows. A second click on the button is required to actually # trigger the button functionality. We circumvent this problem by # generating a 'fake' button click event. Technically this solution # is not correct since the button click should be generated on mouse # up, but I'm not sure if we will get the mouse up event, so we do it # here instead. Note that this handler is only set-up when the OS is # Windows. control = wx.FindWindowAtPointer() if isinstance(control, wx.Button): do_later(wx.PostEvent, control, wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, control.GetId())) def _on_grid_window_paint(self, evt): # fixme: this is a total h*ck to get rid of the scrollbars that appear # on a grid under wx2.6 when it starts up. these appear whether or # not needed, and disappear as soon as the grid is resized. hopefully # we will be able to remove this egregious code on some future version # of wx. self._grid.SetColSize(0, self._grid.GetColSize(0) + 1) self._grid.SetColSize(0, self._grid.GetColSize(0) - 1) evt.Skip() def _on_left_down ( self, evt ): """ Called when the left mouse button is pressed. """ grid = self._grid self._x_clicked = evt.GetX() self._y_clicked = evt.GetY() self._ignore = ((grid.XToEdgeOfCol(evt.GetX()) != wx.NOT_FOUND) or (grid.YToEdgeOfRow(evt.GetY()) != wx.NOT_FOUND)) evt.Skip() def _on_left_up ( self, evt ): """ Called when the left mouse button is released. """ self._ignore = False evt.Skip() def _on_motion(self, evt): """ Called when the mouse moves. """ evt.Skip() if evt.Dragging() and not evt.ControlDown(): data = self.__get_drag_value() if isinstance(data, basestring): file = abspath(data) if exists(file): FileDropSource(self._grid, file) return PythonDropSource(self._grid, data) def _on_grid_motion(self, evt): if evt.GetEventObject() is self._grid_window: x, y = self._grid.CalcUnscrolledPosition(evt.GetPosition()) row = self._grid.YToRow(y) col = self._grid.XToCol(x) # Notify the model that the mouse has moved into the cell at row,col, # only if the row and col are valid. if (row >= 0) and (col >= 0): self.model.mouse_cell = (row, col) # If we are not ignoring mouse events, call _on_motion. if not self._ignore: self._on_motion(evt) evt.Skip() def _on_select_cell(self, evt): """ Called when the user has moved to another cell. """ row, col = evt.GetRow(), evt.GetCol() self.cell_left_clicked = self.model.click = (row, col) # Try to find a renderer for this cell: renderer = self.model.get_cell_renderer(row, col) # If the renderer has defined a handler then call it: result = False if renderer is not None: result = renderer.on_left_click(self, row, col) # if the handler didn't tell us to stop further handling then skip if not result: if (self.selection_mode != '') or (not self.edit_on_first_click): self._grid.SelectBlock(row, col, row, col, evt.ControlDown()) self._edit = True evt.Skip() def _on_range_select(self, evt): if evt.Selecting(): if (self.selection_mode == 'cell') and evt.ControlDown(): self._grid.SelectBlock(evt.GetTopRow(), evt.GetLeftCol(), evt.GetBottomRow(), evt.GetRightCol(), True) if self._notify_select: self.__fire_selection_changed() def _on_col_size(self, evt): """ Called when the user changes a column's width. """ self.__autosize() evt.Skip() def _on_row_size(self, evt): """ Called when the user changes a row's height. """ self._grid.AdjustScrollbars() evt.Skip() def _on_idle(self, evt): """ Immediately jumps into editing mode, bypassing the usual select mode of a spreadsheet. See also self.OnSelectCell(). """ if self._edit == True and self.edit_on_first_click: if self._grid.CanEnableCellControl(): self._grid.EnableCellEditControl() self._edit = False evt.Skip() def _on_cell_left_dclick(self, evt): """ Called when the left mouse button was double-clicked. From the wxPython demo code:- 'I do this because I don't like the default behaviour of not starting the cell editor on double clicks, but only a second click.' Fair enuff! """ row, col = evt.GetRow(), evt.GetCol() data = self.model.get_value(row, col) self.cell_activated = data # Tell the model that a cell was double-clicked on: self.model.dclick = (row, col) # Try to find a renderer for this cell renderer = self.model.get_cell_renderer(row, col) # If the renderer has defined a handler then call it if renderer is not None: renderer.on_left_dclick(self, row, col) def _on_cell_right_dclick(self, evt): """ Called when the right mouse button was double-clicked. From the wxPython demo code:- 'I do this because I don't like the default behaviour of not starting the cell editor on double clicks, but only a second click.' Fair enuff! """ row, col = evt.GetRow(), evt.GetCol() # try to find a renderer for this cell renderer = self.model.get_cell_renderer(row, col) # if the renderer has defined a handler then call it if renderer is not None: renderer.on_right_dclick(self, row, col) def _on_cell_right_click(self, evt): """ Called when a right click occurred in a cell. """ row, col = evt.GetRow(), evt.GetCol() # try to find a renderer for this cell renderer = self.model.get_cell_renderer(row, col) # if the renderer has defined a handler then call it result = False if renderer is not None: result = renderer.on_right_click(self, row, col) # if the handler didn't tell us to stop further handling then skip if not result: # ask model for the appropriate context menu menu_manager = self.model.get_cell_context_menu(row, col) # get the underlying menu object if menu_manager is not None: controller = None if type( menu_manager ) is tuple: menu_manager, controller = menu_manager menu = menu_manager.create_menu(self._grid, controller) # if it has anything in it pop it up if menu.GetMenuItemCount() > 0: # Popup the menu (if an action is selected it will be # performed before before 'PopupMenu' returns). x, y = evt.GetPosition() self._grid.PopupMenuXY(menu, x - 10, y - 10 ) self.cell_right_clicked = (row, col) evt.Skip() return def _on_label_right_click(self, evt): """ Called when a right click occurred on a label. """ row, col = evt.GetRow(), evt.GetCol() # a row value of -1 means this click happened on a column. # vice versa, a col value of -1 means a row click. menu_manager = None if row == -1: menu_manager = self.model.get_column_context_menu(col) else: menu_manager = self.model.get_row_context_menu(row) if menu_manager is not None: # get the underlying menu object menu = menu_manager.create_menu(self._grid) # if it has anything in it pop it up if menu.GetMenuItemCount() > 0: # Popup the menu (if an action is selected it will be performed # before before 'PopupMenu' returns). self._grid.PopupMenu(menu, evt.GetPosition()) elif col >= 0: cws = getattr( self, '_cached_widths', None ) if (cws is not None) and (0 <= col < len( cws )): cws[ col ] = None self.__autosize() evt.Skip() def _on_label_left_click(self, evt): """ Called when a left click occurred on a label. """ row, col = evt.GetRow(), evt.GetCol() # A row value of -1 means this click happened on a column. # vice versa, a col value of -1 means a row click. if (row == -1) and self.allow_column_sort and evt.ControlDown(): self._column_sort( col ) elif (col == -1) and self.allow_row_sort and evt.ControlDown(): self._row_sort( row ) evt.Skip() def _column_sort(self, col): """ Sorts the data on the specified column **col**. """ self._grid.Freeze() if (col == self._current_sorted_col) and (not self._col_sort_reversed): # If this column is currently sorted on then reverse it: self.model.sort_by_column(col, True) elif (col == self._current_sorted_col) and self._col_sort_reversed: # If this column is currently reverse-sorted then unsort it: try: self.model.no_column_sort() except NotImplementedError: # If an unsort function is not implemented then just # reverse the sort: self.model.sort_by_column(col, False) else: # Sort the data: self.model.sort_by_column(col, False) self._grid.Thaw() def _row_sort(self, row): self._grid.Freeze() if (row == self._current_sorted_row) and (not self._row_sort_reversed): # If this row is currently sorted on then reverse it: self.model.sort_by_row(row, True) elif row == self._current_sorted_row and self._row_sort_reversed: # If this row is currently reverse-sorted then unsort it: try: self.model.no_row_sort() except NotImplementedError: # If an unsort function is not implemented then just # reverse the sort: self.model.sort_by_row(row, False) else: # Sort the data: self.model.sort_by_row(row, False) self._grid.Thaw() def _on_key_down(self, evt): """ Called when a key is pressed. """ # This changes the behaviour of the and keys to make # manual data entry smoother! # # Don't change the behavior if the key is pressed as this # has meaning to the edit control. key_code = evt.GetKeyCode() if (key_code == wx.WXK_RETURN) and not evt.ControlDown(): self._move_to_next_cell(evt.ShiftDown()) elif (key_code == wx.WXK_TAB) and not evt.ControlDown(): if evt.ShiftDown(): # fixme: in a split window the shift tab is being eaten # by tabbing between the splits self._move_to_previous_cell() else: self._move_to_next_cell() elif key_code == ASCII_C: data = self.__get_drag_value() # deposit the data in our singleton clipboard enClipboard.data = data # build a wxCustomDataObject to notify the system clipboard # that some in-process data is available data_object = wx.CustomDataObject(PythonObject) data_object.SetData('dummy') if TheClipboard.Open(): TheClipboard.SetData(data_object) TheClipboard.Close() evt.Skip() def _move_to_next_cell(self, expandSelection=False): """ Move to the 'next' cell. """ # Complete the edit on the current cell. self._grid.DisableCellEditControl() # Try to move to the next column. success = self._grid.MoveCursorRight(expandSelection) # If the move failed then we must be at the end of a row. if not success: # Move to the first column in the next row. newRow = self._grid.GetGridCursorRow() + 1 if newRow < self._grid.GetNumberRows(): self._grid.SetGridCursor(newRow, 0) self._grid.MakeCellVisible(newRow, 0) else: # This would be a good place to add a new row if your app # needs to do that. pass return success def _move_to_previous_cell(self, expandSelection=False): """ Move to the 'previous' cell. """ # Complete the edit on the current cell. self._grid.DisableCellEditControl() # Try to move to the previous column (without expanding the current # selection). success = self._grid.MoveCursorLeft(expandSelection) # If the move failed then we must be at the start of a row. if not success: # Move to the last column in the previous row. newRow = self._grid.GetGridCursorRow() - 1 if newRow >= 0: self._grid.SetGridCursor(newRow, self._grid.GetNumberCols() - 1) self._grid.MakeCellVisible(newRow, self._grid.GetNumberCols() - 1) def _refresh(self): self._grid.GetParent().Layout() def _on_col_move(self, evt): """ Called when a column move is taking place. """ self._ignore = True # Get the column being moved: frm = evt.GetMoveColumn() # Get the column to insert it before: to = evt.GetBeforeColumn() # Tell the model to update its columns: if self.model._move_column(frm, to): # Modify the grid: grid = self._grid cols = grid.GetNumberCols() widths = [ grid.GetColSize(i) for i in range( cols ) ] width = widths[frm] del widths[frm] to -= (frm < to) widths.insert( to, width ) grid.BeginBatch() grid.ProcessTableMessage( GridTableMessage( self._grid_table_base, GRIDTABLE_NOTIFY_COLS_DELETED, frm, 1 ) ) grid.ProcessTableMessage( GridTableMessage( self._grid_table_base, GRIDTABLE_NOTIFY_COLS_INSERTED, to, 1 ) ) [ grid.SetColSize(i, widths[i]) for i in range(min(frm, to), cols) ] grid.EndBatch() def _on_row_move(self, evt): """ Called when a row move is taking place. """ self._ignore = True # Get the column being moved: frm = evt.GetMoveRow() # Get the column to insert it before: to = evt.GetBeforeRow() # Tell the model to update its rows: if self.model._move_row(frm, to): # Notify the grid: grid = self._grid rows = grid.GetNumberRows() heights = [ grid.GetRowSize(i) for i in range( rows ) ] height = heights[frm] del heights[frm] to -= (frm < to) heights.insert( to, height ) grid.BeginBatch() grid.ProcessTableMessage( GridTableMessage( self._grid_table_base, GRIDTABLE_NOTIFY_ROWS_DELETED, frm, 1 ) ) grid.ProcessTableMessage( GridTableMessage( self._grid_table_base, GRIDTABLE_NOTIFY_ROWS_INSERTED, to, 1 ) ) [ grid.SetRowSize(i, heights[i]) for i in range(min(frm, to), rows)] grid.EndBatch() ########################################################################### # PythonDropTarget interface. ########################################################################### def wx_dropped_on ( self, x, y, drag_object, drag_result ): # first resolve the x/y coords into a grid row/col row, col = self.__resolve_grid_coords(x, y) result = wx.DragNone if row != -1 and col != -1: # now ask the model if the target cell can accept this object valid_target = self.model.is_valid_cell_value(row, col, drag_object) # if this is a valid target then attempt to set the value if valid_target: # find the data data = drag_object # sometimes a 'node' attribute on the clipboard gets set # to a binding. if this happens we want to use it, otherwise # we want to just use the drag_object passed to us if hasattr(enClipboard, 'node') and \ enClipboard.node is not None: data = enClipboard.node # now make sure the value gets set in the model self.model.set_value(row, col, data) result = drag_result return result def wx_drag_over ( self, x, y, drag_object, drag_result ): # first resolve the x/y coords into a grid row/col row, col = self.__resolve_grid_coords(x, y) result = wx.DragNone if row != -1 and col != -1: # now ask the model if the target cell can accept this object valid_target = self.model.is_valid_cell_value(row, col, drag_object) if valid_target: result = drag_result return result ########################################################################### # private interface. ########################################################################### def __initialize_fonts(self): """ Initialize the label fonts. """ self._on_default_label_font_changed() self._on_default_cell_font_changed() self._on_default_cell_text_color_changed() self._on_grid_line_color_changed() self._grid.SetColLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self._grid.SetRowLabelAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE) def __initialize_rows(self, model): """ Initialize the row headers. """ # should we really be doing this? for row in range(model.get_row_count()): if model.is_row_read_only(row): attr = wx.grid.GridCellAttr() attr.SetReadOnly() #attr.SetRenderer(None) #attr.SetBackgroundColour('linen') self._grid.SetRowAttr(row, attr) def __initialize_columns(self, model): """ Initialize the column headers. """ # should we really be doing this? for column in range(model.get_column_count()): if model.is_column_read_only(column): attr = wx.grid.GridCellAttr() attr.SetReadOnly() #attr.SetRenderer(None) #attr.SetBackgroundColour('linen') self._grid.SetColAttr(column, attr) def __initialize_counts(self, model): """ Initializes the row and column counts. """ if model is not None: self._row_count = model.get_row_count() else: self._row_count = 0 if model is not None: self._col_count = model.get_column_count() else: self._col_count = 0 def __initialize_sort_state(self): """ Initializes the row and column counts. """ self._current_sorted_col = None self._current_sorted_row = None self._col_sort_reversed = False self._row_sort_reversed = False def __initialize_style_settings(self, event=None): # make sure all the handlers for traits defining styles get called self._on_enable_lines_changed() self._on_read_only_changed() self._on_selection_mode_changed() self._on_column_label_height_changed() self._on_row_label_width_changed() self._on_show_column_headers_changed() self._on_show_row_headers_changed() self._on_default_cell_bg_color_changed() self._on_default_label_bg_color_changed() self._on_default_label_text_color_changed() self._on_selection_bg_color_changed() self._on_selection_text_color_changed() def __get_drag_value(self): """ Calculates the drag value based on the current selection. """ # fixme: The following line seems like a more useful implementation than # the previous commented out version below, but I am leaving the old # version in the code for now just in case. If anyone sees this comment # after 1/1/2009, it should be safe to delete this comment and the # commented out code below... return self.model.get_cell_drag_value(self._grid.GetGridCursorRow(), self._grid.GetGridCursorCol()) ###rows, cols = self.__get_selected_rows_and_cols() ### ###if len(rows) > 0: ### rows.sort() ### value = self.model.get_rows_drag_value(rows) ### if len(rows) == 1 and len(value) == 1: ### value = value[0] ###elif len(cols) > 0: ### cols.sort() ### value = self.model.get_cols_drag_value(cols) ### if len(cols) == 1 and len(value) == 1: ### value = value[0] ###else: ### # our final option -- grab the cell that the cursor is currently in ### row = self._grid.GetGridCursorRow() ### col = self._grid.GetGridCursorCol() ### value = self.model.get_cell_drag_value(row, col) ### ###return value def __get_selection(self): """ Returns a list of values for the current selection. """ rows, cols = self.__get_selected_rows_and_cols() if len(rows) > 0: rows.sort() value = self.model.get_rows_selection_value(rows) elif len(cols) > 0: cols.sort() value = self.model.get_cols_selection_value(cols) else: # our final option -- grab the cell that the cursor is currently in row = self._grid.GetGridCursorRow() col = self._grid.GetGridCursorCol() value = self.model.get_cell_selection_value(row, col) if value is not None: value = [value] if value is None: value = [] return value def __get_selected_rows_and_cols(self): """ Return lists of the selected rows and the selected columns. """ # note: because the wx grid control is so screwy, we have limited # the selection behavior. we only allow single cells to be selected, # or whole row, or whole columns. rows = self._grid.GetSelectedRows() cols = self._grid.GetSelectedCols() # because wx is retarded we have to check this as well -- why # the blazes can't they come up with one Q#$%@$#% API to manage # selections??? makes me want to put the smack on somebody. # note that all this malarkey is working on the assumption that # only entire rows or entire columns or single cells are selected. top_left = self._grid.GetSelectionBlockTopLeft() bottom_right = self._grid.GetSelectionBlockBottomRight() selection_mode = self._grid.GetSelectionMode() if selection_mode == wxGrid.wxGridSelectRows: # handle rows differently. figure out which rows were # selected. turns out that in this case, wx adds a "block" # per row, so we have to cycle over the list returned by # the GetSelectionBlock routines for i in range(len(top_left)): top_point = top_left[i] bottom_point = bottom_right[i] for row_index in range(top_point[0], bottom_point[0] + 1): rows.append(row_index) elif selection_mode == wxGrid.wxGridSelectColumns: # again, in this case we know that only whole columns can be # selected for i in range(len(top_left)): top_point = top_left[i] bottom_point = bottom_right[i] for col_index in range(top_point[1], bottom_point[1] + 1): cols.append(col_index) elif selection_mode == wxGrid.wxGridSelectCells: # this is the case where the selection_mode is cell, which also # allows complete columns or complete rows to be selected. # first find the size of the grid row_size = self.model.get_row_count() col_size = self.model.get_column_count() for i in range(len(top_left)): top_point = top_left[i] bottom_point = bottom_right[i] # precalculate whether this is a row or column select row_select = top_point[1] == 0 and \ bottom_point[1] == col_size - 1 col_select = top_point[0] == 0 and \ bottom_point[0] == row_size - 1 if row_select: for row_index in range(top_point[0], bottom_point[0] + 1): rows.append(row_index) if col_select: for col_index in range(top_point[1], bottom_point[1] + 1): cols.append(top_point[0]) return ( rows, cols ) def __fire_selection_changed(self): self.selection_changed = True def __autosize(self): """ Autosize the grid with appropriate flags. """ model = self.model grid = self._grid if grid is not None and self.autosize: grid.AutoSizeColumns(False) grid.AutoSizeRows(False) # Whenever we size the grid we need to take in to account any # explicitly set column sizes: grid.BeginBatch() dx, dy = grid.GetClientSize() n = model.get_column_count() pdx = 0 wdx = 0.0 widths = [] cached = getattr( self, '_cached_widths', None ) current = [ grid.GetColSize( i ) for i in xrange( n ) ] if (cached is None) or (len( cached ) != n): self._cached_widths = cached = [ None ] * n for i in xrange( n ): cw = cached[i] if ((cw is None) or (-cw == current[i]) or # hack: For some reason wx always seems to adjust column 0 by # 1 pixel from what we set it to (at least on Windows), so we # need to add a little fudge factor just for this column: ((i == 0) and (abs( current[i] + cw ) <= 1))): width = model.get_column_size( i ) if width <= 0.0: width = 0.1 if width <= 1.0: wdx += width cached[i] = -1 else: width = int( width ) pdx += width if cw is None: cached[i] = width else: cached[i] = width = current[i] pdx += width widths.append( width ) # The '-1' below adjusts for an off by 1 error in the way the wx.Grid # control determines whether or not it needs a horizontal scroll bar: adx = max( 0, dx - pdx - 1 ) for i in range( n ): width = cached[i] if width < 0: width = widths[i] if width <= 1.0: w = max( 30, int( round( (adx * width) / wdx ) ) ) wdx -= width width = w adx -= width cached[i] = -w grid.SetColSize( i, width ) grid.AdjustScrollbars() grid.EndBatch() grid.ForceRefresh() def __resolve_grid_coords(self, x, y): """ Resolve the specified x and y coordinates into row/col coordinates. Returns row, col. """ # the x,y coordinates here are Unscrolled coordinates. # They must be changed to scrolled coordinates. x, y = self._grid.CalcUnscrolledPosition(x, y) # now we need to get the row and column from the grid # but we need to first remove the RowLabel and ColumnLabel # bounding boxes if self.show_row_headers: x = x - self._grid.GetGridRowLabelWindow().GetRect().width if self.show_column_headers: y = y - self._grid.GetGridColLabelWindow().GetRect().height return ( self._grid.YToRow(y), self._grid.XToCol(x) ) def _select_rows(self, cells): """ Selects all of the rows specified by a list of (row,column) pairs. """ # For a large set of rows, simply calling 'SelectBlock' on the Grid # object for each row is very inefficient, so we first make a pass over # all of the cells to merge them into contiguous ranges as much as # possible: sb = self._grid.SelectBlock # Extract the rows and sort them: rows = [ row for row, column in cells ] rows.sort() # Now find contiguous ranges of rows, and select the current range # whenever a break in the sequence is found: first = last = -999 for row in rows: if row == (last + 1): last = row else: if first >= 0: sb(first, 0, last, 0, True) first = last = row # Handle the last pending range of lines to be selected: if first >= 0: sb(first, 0, last, 0, True) class _GridTableBase(PyGridTableBase): """ A private adapter for the underlying wx grid implementation. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, model, grid): """ Creates a new table base. """ # Base class constructor. PyGridTableBase.__init__(self) # The Pyface model that provides the data. self.model = model self._grid = grid # hacky state variables so we can identify when rows have been # added or deleted. self._row_count = -1 self._col_count = -1 # caches for editors and renderers self._editor_cache = {} self._renderer_cache = {} def dispose(self): # Make sure dispose gets called on all traits editors: for editor in self._editor_cache.values(): editor.dispose() self._editor_cache = {} for renderer in self._renderer_cache.values(): renderer.dispose() self._renderer_cache = {} ########################################################################### # 'wxPyGridTableBase' interface. ########################################################################### def GetNumberRows(self): """ Return the number of rows in the model. """ # because wx is such a wack job we have to store away the row # and column counts so we can figure out when rows or cols have # been appended or deleted. lacking a better place to do this # we just set the local variable every time GetNumberRows is called. self._row_count = self.model.get_row_count() return self._row_count def GetNumberCols(self): """ Return the number of columns in the model. """ # see comment in GetNumberRows for logic here self._col_count = self.model.get_column_count() return self._col_count def IsEmptyCell(self, row, col): """ Is the specified cell empty? """ return self.model.is_cell_empty(row, col) def GetValue(self, row, col): """ Get the value at the specified row and column. """ return self.model.get_value(row, col) def SetValue(self, row, col, value): """ Set the value at the specified row and column. """ return self.model.set_value(row, col, value) def GetRowLabelValue(self, row): """ Called when the grid needs to display a row label. """ label = self.model.get_row_name(row) if row == self._grid._current_sorted_row: if self._grid._row_sort_reversed: if is_win32: ulabel = unicode(label, 'ascii') + u' \u00ab' label = ulabel.encode('latin-1') else: label += ' <<' elif is_win32: ulabel = unicode(label, 'ascii') + u' \u00bb' label = ulabel.encode('latin-1') else: label += ' >>' return label def GetColLabelValue(self, col): """ Called when the grid needs to display a column label. """ label = self.model.get_column_name(col) if col == self._grid._current_sorted_col: if self._grid._col_sort_reversed: if is_win32: ulabel = unicode(label, 'ascii') + u' \u00ab' label = ulabel.encode('latin-1') else: label += ' <<' elif is_win32: ulabel = unicode(label, 'ascii') + u' \u00bb' label = ulabel.encode('latin-1') else: label += ' >>' return label def GetTypeName(self, row, col): """ Called to determine the kind of editor/renderer to use. This doesn't necessarily have to be the same type used natively by the editor/renderer if they know how to convert. """ typename = None try: typename = self.model.get_type(row, col) except NotImplementedError: pass if typename == None: typename = GRID_VALUE_STRING return typename def DeleteRows(self, pos, num_rows): """ Called when the view is deleting rows. """ # clear the cache self._clear_cache() return self.model.delete_rows(pos, num_rows) def InsertRows(self, pos, num_rows): """ Called when the view is inserting rows. """ # clear the cache self._clear_cache() return self.model.insert_rows(pos, num_rows) def AppendRows(self, num_rows): """ Called when the view is inserting rows. """ # clear the cache self._clear_cache() pos = self.model.get_row_count() return self.model.insert_rows(pos, num_rows) def DeleteCols(self, pos, num_cols): """ Called when the view is deleting columns. """ # clear the cache self._clear_cache() return self.model.delete_columns(pos, num_cols) def InsertCols(self, pos, num_cols): """ Called when the view is inserting columns. """ # clear the cache self._clear_cache() return self.model.insert_columns(pos, num_cols) def AppendCols(self, num_cols): """ Called when the view is inserting columns. """ # clear the cache self._clear_cache() pos = self.model.get_column_count() return self.model.insert_columns(pos, num_cols) def GetAttr(self, row, col, kind): """ Retrieve the cell attribute object for the specified cell. """ result = GridCellAttr() # we only handle cell requests, for other delegate to the supa if kind != GridCellAttr.Cell and kind != GridCellAttr.Any: return result rows = self.model.get_row_count() cols = self.model.get_column_count() # First look in the cache for the editor: editor = self._editor_cache.get((row, col)) if editor is None: if (row >= rows) or (col >= cols): editor = DummyGridCellEditor() else: # Ask the underlying model for an editor for this cell: editor = self.model.get_cell_editor(row, col) if editor is not None: self._editor_cache[(row, col)] = editor editor._grid_info = (self._grid._grid, row, col) if editor is not None: # Note: We have to increment the reference to keep the # underlying code from destroying our object. editor.IncRef() result.SetEditor(editor) # try to find a renderer for this cell renderer = None if row < rows and col < cols: renderer = self.model.get_cell_renderer(row, col) if renderer is not None and renderer.renderer is not None: renderer.renderer.IncRef() result.SetRenderer(renderer.renderer) # look to see if this cell is editable read_only = False if row < rows and col < cols: read_only = self.model.is_cell_read_only(row, col) or \ self.model.is_row_read_only(row) or \ self.model.is_column_read_only(col) result.SetReadOnly(read_only) if read_only : read_only_color = self._grid.default_cell_read_only_color if read_only_color is not None and read_only_color is not Undefined: result.SetBackgroundColour(read_only_color) # check to see if colors or fonts are specified for this cell bgcolor = None if row < rows and col < cols: bgcolor = self.model.get_cell_bg_color(row, col) else: bgcolor = self._grid.default_cell_bg_color if bgcolor is not None: result.SetBackgroundColour(bgcolor) text_color = None if row < rows and col < cols: text_color = self.model.get_cell_text_color(row, col) else: text_color = self._grid.default_cell_text_color if text_color is not None: result.SetTextColour(text_color) cell_font = None if row < rows and col < cols: cell_font = self.model.get_cell_font(row, col) else: cell_font = self._grid.default_cell_font if cell_font is not None: result.SetFont(cell_font) # check for alignment definition for this cell halignment = valignment = None if row < rows and col < cols: halignment = self.model.get_cell_halignment(row, col) valignment = self.model.get_cell_valignment(row, col) if halignment is not None and valignment is not None: if halignment == 'center': h = wx.ALIGN_CENTRE elif halignment == 'right': h = wx.ALIGN_RIGHT else: h = wx.ALIGN_LEFT if valignment == 'top': v = wx.ALIGN_TOP elif valignment == 'bottom': v = wx.ALIGN_BOTTOM else: v = wx.ALIGN_CENTRE result.SetAlignment(h, v) return result ########################################################################### # private interface. ########################################################################### def _clear_cache(self): """ Clean out the editor/renderer cache. """ # Dispose of the editors in the cache after a brief delay, so as # to allow completion of the current event: do_later( self._editor_dispose, self._editor_cache.values() ) self._editor_cache = {} self._renderer_cache = {} return def _editor_dispose(self, editors): for editor in editors: editor.dispose() from wx.grid import PyGridCellEditor class DummyGridCellEditor(PyGridCellEditor): def Show(self, show, attr): return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/simple_grid_model.py0000644000175100001440000002303411674463363023262 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A SimpleGridModel simply builds a table from a 2-dimensional list/array containing the data. Optionally users can pass in specifications for rows and columns. By default these are built off the data itself, with row/column labels as the index + 1.""" # Enthought library imports from pyface.action.api import Action, Group, MenuManager, Separator from traits.api import HasTraits, Any, List, Str, Bool, Trait from pyface.wx.drag_and_drop import clipboard as enClipboard # local imports from grid_model import GridColumn, GridModel, GridRow class SimpleGridModel(GridModel): """ A SimpleGridModel simply builds a table from a 2-dimensional list/array containing the data. Optionally users can pass in specifications for rows and columns. By default these are built off the data itself, with row/column labels as the index + 1.""" # A 2-dimensional list/array containing the grid data. data = Any # The rows in the model. rows = Trait(None, None, List(GridRow)) # The columns in the model. columns = Trait(None, None, List(GridColumn)) ######################################################################### # 'object' interface. ######################################################################### def __init__(self, **traits): """ Create a SimpleGridModel object. """ # Base class constructor super(SimpleGridModel, self).__init__(**traits) return ######################################################################### # 'GridModel' interface. ######################################################################### def get_column_count(self): """ Return the number of columns for this table. """ if self.columns is not None: # if we have an explicit declaration then use it count = len(self.columns) else: # otherwise look at the length of the first row # note: the data had better be 2D count = len(self.data[0]) return count def get_column_name(self, index): """ Return the name of the column specified by the (zero-based) index. """ if self.columns is not None: # if we have an explicit declaration then use it try: name = self.columns[index].label except IndexError: name = '' else: # otherwise return the index plus 1 name = str(index + 1) return name def get_cols_drag_value(self, cols): """ Return the value to use when the specified columns are dragged or copied and pasted. cols is a list of column indexes. """ # if there is only one column in cols, then we return a 1-dimensional # list if len(cols) == 1: value = self.__get_data_column(cols[0]) else: # iterate over every column, building a list of the values in that # column value = [] for col in cols: value.append(self.__get_data_column(col)) return value def is_column_read_only(self, index): """ Return True if the column specified by the zero-based index is read-only. """ # if there is no declaration then assume the column is not # read only read_only = False if self.columns is not None: # if we have an explicit declaration then use it try: read_only = self.columns[index].read_only except IndexError: pass return read_only def get_row_count(self): """ Return the number of rows for this table. """ if self.rows is not None: # if we have an explicit declaration then use it count = len(self.rows) else: # otherwise look at the data count = len(self.data) return count def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) index. """ if self.rows is not None: # if we have an explicit declaration then use it try: name = self.rows[index].label except IndexError: name = str(index + 1) else: # otherwise return the index plus 1 name = str(index + 1) return name def get_rows_drag_value(self, rows): """ Return the value to use when the specified rows are dragged or copied and pasted. rows is a list of row indexes. """ # if there is only one row in rows, then we return a 1-dimensional # list if len(rows) == 1: value = self.__get_data_row(rows[0]) else: # iterate over every row, building a list of the values in that # row value = [] for row in rows: value.append(self.__get_data_row(row)) return value def is_row_read_only(self, index): """ Return True if the row specified by the zero-based index is read-only. """ # if there is no declaration then assume the row is not # read only read_only = False if self.rows is not None: # if we have an explicit declaration then use it try: read_only = self.rows[index].read_only except IndexError: pass return read_only def get_value(self, row, col): """ Return the value stored in the table at (row, col). """ try: return self.data[row][col] except IndexError: pass return '' def is_cell_empty(self, row, col): """ Returns True if the cell at (row, col) has a None value, False otherwise.""" if row >= self.get_row_count() or col >= self.get_column_count(): empty = True else: try: value = self.get_value(row, col) empty = value is None except IndexError: empty = True return empty def get_cell_context_menu(self, row, col): """ Return a MenuManager object that will generate the appropriate context menu for this cell.""" context_menu = MenuManager( Group( _CopyAction(self, row, col, name='Copy'), id = 'Group' ) ) return context_menu def is_cell_editable(self, row, col): """ Returns True if the cell at (row, col) is editable, False otherwise. """ return True ######################################################################### # protected 'GridModel' interface. ######################################################################### def _set_value(self, row, col, value): """ Sets the value of the cell at (row, col) to value. Raises a ValueError if the value is vetoed or the cell at (row, col) does not exist. """ new_rows = 0 try: self.data[row][col] = value except IndexError: # Add a new row. self.data.append([0] * self.GetNumberCols()) self.data[row][col] = value new_rows = 1 return new_rows def _delete_rows(self, pos, num_rows): """ Removes rows pos through pos + num_rows from the model. """ if pos + num_rows >= self.get_row_count(): num_rows = self.get_rows_count() - pos del self.data[pos, pos + num_rows] return num_rows ########################################################################### # private interface. ########################################################################### def __get_data_column(self, col): """ Return a 1-d list of data from the column indexed by col. """ row_count = self.get_row_count() coldata = [] for row in range(row_count): try: coldata.append(self.get_value(row, col)) except IndexError: coldata.append(None) return coldata def __get_data_row(self, row): """ Return a 1-d list of data from the row indexed by row. """ col_count = self.get_column_count() rowdata = [] for col in range(col_count): try: rowdata.append(self.get_value(row, col)) except IndexError: rowdata.append(None) return rowdata # Private class class _CopyAction(Action): def __init__(self, model, row, col, **kw): super(_CopyAction, self).__init__(**kw) self._model = model self._row = row self._col = col def perform(self): # grab the specified value from the model and add it to the # clipboard value = self._model.get_cell_drag_value(self._row, self._col) enClipboard.data = value #### EOF #################################################################### pyface-4.1.0/pyface/ui/wx/grid/trait_grid_cell_adapter.py0000644000175100001440000002313311674463363024433 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Adapter class to make trait editor controls work inside of a grid. """ # Major package imports import wx from wx.grid import PyGridCellEditor from wx import SIZE_ALLOW_MINUS_ONE # Enthought library imports from traitsui.api import UI, default_handler # Local imports: from combobox_focus_handler import ComboboxFocusHandler wx_28 = (float( wx.__version__[:3] ) >= 2.8) def get_control(control): if isinstance(control, wx.Control): return control for control in control.GetChildren(): result = get_control(control) if result is not None: return result return None def push_control(control, grid): control.PushEventHandler(ComboboxFocusHandler(grid)) for child_control in control.GetChildren(): push_control(child_control, grid) class TraitGridCellAdapter(PyGridCellEditor): """ Wrap a trait editor as a GridCellEditor object. """ def __init__(self, trait_editor_factory, obj, name, description, handler = None, context = None, style = 'simple', width = -1.0, height = -1.0): """ Build a new TraitGridCellAdapter object. """ PyGridCellEditor.__init__(self) self._factory = trait_editor_factory self._style = style self._width = width self._height = height self._editor = None self._obj = obj self._name = name self._description = description self._handler = handler self._context = context def Create(self, parent, id, evtHandler): """ Called to create the control, which must derive from wxControl. """ # If the editor has already been created, ignore the request: if hasattr( self, '_control' ): return handler = self._handler if handler is None: handler = default_handler() if self._context is None: ui = UI(handler = handler) else: context = self._context.copy() context['table_editor_object'] = context['object'] context['object'] = self._obj ui = UI(handler = handler, context = context) # Link the editor's undo history in to the main ui undo history if the # UI object is available: factory = self._factory if factory._ui is not None: ui.history = factory._ui.history # make sure the factory knows this is a grid_cell editor factory.is_grid_cell = True factory_method = getattr(factory, self._style + '_editor') self._editor = factory_method(ui, self._obj, self._name, self._description, parent) # Tell the editor to actually build the editing widget: self._editor.prepare(parent) # Find the control to use as the editor: self._control = control = self._editor.control # Calculate and save the required editor height: grid, row, col = getattr(self, '_grid_info', (None, None, None)) width, height = control.GetBestSize() self_height = self._height if self_height > 1.0: height = int( self_height ) elif (self_height >= 0.0) and (grid is not None): height = int( self_height * grid.GetSize()[1] ) self_width = self._width if self_width > 1.0: width = int( self_width ) elif (self_width >= 0.0) and (grid is not None): width = int( self_width * grid.GetSize()[0] ) self._edit_width, self._edit_height = width, height # Set up the event handler for each window in the cell editor: push_control(control, grid) # Set up the first control found within the cell editor as the cell # editor control: control = get_control(control) if control is not None: self.SetControl(control) def SetSize(self, rect): """ Called to position/size the edit control within the cell rectangle. If you don't fill the cell (the rect) then be sure to override PaintBackground and do something meaningful there. """ changed = False edit_width, edit_height = rect.width, rect.height grid, row, col = getattr(self, '_grid_info', (None, None, None)) if (grid is not None) and self._editor.scrollable: edit_width, cur_width = self._edit_width, grid.GetColSize(col) restore_width = getattr( grid, '_restore_width', None ) if restore_width is not None: cur_width = restore_width if (edit_width > cur_width) or (restore_width is not None): edit_width = max( edit_width, cur_width ) grid._restore_width = cur_width grid.SetColSize(col, edit_width + 1 + (col == 0)) changed = True else: edit_width = cur_width edit_height, cur_height = self._edit_height, grid.GetRowSize(row) restore_height = getattr( grid, '_restore_height', None ) if restore_height is not None: cur_height = restore_height if (edit_height > cur_height) or (restore_height is not None): edit_height = max( edit_height, cur_height ) grid._restore_height = cur_height grid.SetRowSize(row, edit_height + 1 + (row == 0)) changed = True else: edit_height = cur_height if changed: grid.ForceRefresh() self._control.SetDimensions(rect.x + 1, rect.y + 1, edit_width, edit_height, SIZE_ALLOW_MINUS_ONE) if changed: grid.MakeCellVisible(grid.GetGridCursorRow(), grid.GetGridCursorCol()) def Show(self, show, attr): """ Show or hide the edit control. You can use the attr (if not None) to set colours or fonts for the control. """ if self.IsCreated(): if wx_28: super(TraitGridCellAdapter, self).Show(show, attr) else: self.base_Show(show, attr) return def PaintBackground(self, rect, attr): """ Draws the part of the cell not occupied by the edit control. The base class version just fills it with background colour from the attribute. In this class the edit control fills the whole cell so don't do anything at all in order to reduce flicker. """ return def BeginEdit(self, row, col, grid): """ Make sure the control is ready to edit. """ # We have to manually set the focus to the control self._editor.update_editor() control = self._control control.Show(True) control.SetFocus() if isinstance(control, wx.TextCtrl): control.SetSelection(-1, -1) def EndEdit(self, row, col, grid): """ Do anything necessary to complete the editing. """ self._control.Show(False) changed = False grid, row, col = self._grid_info if grid._no_reset_col: grid._no_reset_col = False else: width = getattr(grid, '_restore_width', None) if width is not None: del grid._restore_width grid.SetColSize(col, width) changed = True if grid._no_reset_row: grid._no_reset_row = False else: height = getattr(grid, '_restore_height', None) if height is not None: del grid._restore_height grid.SetRowSize(row, height) changed = True if changed: grid.ForceRefresh() def Reset(self): """ Reset the value in the control back to its starting value. """ # fixme: should we be using the undo history here? return def StartingKey(self, evt): """ If the editor is enabled by pressing keys on the grid, this will be called to let the editor do something about that first key if desired. """ return def StartingClick(self): """ If the editor is enabled by clicking on the cell, this method will be called to allow the editor to simulate the click on the control if needed. """ return def Destroy(self): """ Final cleanup. """ self._editor.dispose() return def Clone(self): """ Create a new object which is the copy of this one. """ return TraitGridCellAdapter(self._factory, self._obj, self._name, self._description, style=self._style) def dispose(self): if self._editor is not None: self._editor.dispose() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/edit_renderer.py0000644000175100001440000000266311674463363022424 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from edit_image_renderer import EditImageRenderer from grid_cell_renderer import GridCellRenderer class EditRenderer(GridCellRenderer): def __init__(self, **traits): # base-class constructor super(EditRenderer, self).__init__(**traits) # initialize the renderer, if it hasn't already been initialized if self.renderer is None: self.renderer = EditImageRenderer() return def on_left_click(self, grid, row, col): """ Calls edit_traits on the object represented by the row. """ obj = grid.model.get_rows_drag_value([row])[0] # allow editting if the obj does not have an editable trait # or if the editable trait is True if (not hasattr(obj, 'editable')) or obj.editable: obj.edit_traits(kind='live') pyface-4.1.0/pyface/ui/wx/grid/grid_cell_image_renderer.py0000644000175100001440000001332111674463363024556 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A renderer which will display a cell-specific image in addition to some text displayed in the same way the standard string renderer normally would. """ # Major package imports import wx from wx.grid import PyGridCellRenderer from wx.grid import GridCellStringRenderer from wx import SOLID, Brush, Rect, TRANSPARENT_PEN class GridCellImageRenderer(PyGridCellRenderer): """ A renderer which will display a cell-specific image in addition to some text displayed in the same way the standard string renderer normally would. """ def __init__(self, provider = None): """ Build a new PyGridCellImageRenderer. 'provider', if provided, should implement get_image_for_cell(row, col) to specify an image to appear in the cell at row, col. """ PyGridCellRenderer.__init__(self) # save the string renderer to use for text. self._string_renderer = GridCellStringRenderer() self._provider = provider return ######################################################################### # GridCellRenderer interface. ######################################################################### def Draw(self, grid, attr, dc, rect, row, col, isSelected): """ Draw the appropriate icon into the specified grid cell. """ # clear the cell first if isSelected: bgcolor = grid.GetSelectionBackground() else: bgcolor = grid.GetCellBackgroundColour(row, col) dc.SetBackgroundMode(SOLID) dc.SetBrush(Brush(bgcolor, SOLID)) dc.SetPen(TRANSPARENT_PEN) dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height) # find the correct image for this cell bmp = self._get_image(grid, row, col) # find the correct text for this cell text = self._get_text(grid, row, col) # figure out placement -- we try to center things! # fixme: we should be responding to the horizontal/vertical # alignment info in the attr object! size = self.GetBestSize(grid, attr, dc, row, col) halign, valign = attr.GetAlignment() # width first wdelta = rect.width - size.GetWidth() x = rect.x if halign == wx.ALIGN_CENTRE and wdelta > 0: x += wdelta / 2 # now height hdelta = rect.height - size.GetHeight() y = rect.y if valign == wx.ALIGN_CENTRE and hdelta > 0: y += hdelta / 2 dc.SetClippingRegion(*rect) if bmp is not None: # now draw our image into it dc.DrawBitmap(bmp, x, y, 1) x += bmp.GetWidth() if text is not None and text != '': width = rect.x + rect.width - x height = rect.y + rect.height - y # draw any text that should be included new_rect = Rect(x, y, width, height) self._string_renderer.Draw(grid, attr, dc, new_rect, row, col, isSelected) dc.DestroyClippingRegion() return def GetBestSize(self, grid, attr, dc, row, col): """ Determine best size for the cell. """ # find the correct image bmp = self._get_image(grid, row, col) if bmp is not None: bmp_size = wx.Size(bmp.GetWidth(), bmp.GetHeight()) else: bmp_size = wx.Size(0, 0) # find the correct text for this cell text = self._get_text(grid, row, col) if text is not None: text_size = self._string_renderer.GetBestSize(grid, attr, dc, row, col) else: text_size = wx.Size(0, 0) result = wx.Size(bmp_size.width + text_size.width, max(bmp_size.height, text_size.height)) return result def Clone(self): return GridCellImageRenderer(self._provider) ######################################################################### # protected 'GridCellIconRenderer' interface. ######################################################################### def _get_image(self, grid, row, col): """ Returns the correct bmp for the data at row, col. """ bmp = None if self._provider is not None and \ hasattr(self._provider, 'get_image_for_cell'): # get the image from the specified provider img = self._provider.get_image_for_cell(grid, row, col) if img is not None: bmp = img.create_bitmap() else: bmp = None return bmp def _get_text(self, grid, row, col): """ Returns the correct text for the data at row, col. """ text = None if self._provider is not None and \ hasattr(self._provider, 'get_text_for_cell'): # get the image from the specified provider text = self._provider.get_text_for_cell(grid, row, col) else: text = grid.GetCellValue(row, col) return text #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/grid_cell_renderer.py0000644000175100001440000000301311674463363023411 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Enthought library imports from traits.api import Any, HasTraits class GridCellRenderer(HasTraits): # The toolkit-specific renderer for this cell. renderer = Any # A handler to be invoked on right-button mouse clicks. def on_right_click(self, grid, row, col): pass # A handler to be invoked on right-button mouse double clicks. def on_right_dclick(self, grid, row, col): pass # A handler to be invoked on left-button mouse clicks. def on_left_click(self, grid, row, col): pass # A handler to be invoked on left-button mouse double clicks. def on_left_dclick(self, grid, row, col): pass # A handler to be invoked on key press. def on_key(self, grid, row, col, key_event): pass # Clean-up! def dispose(self): pass #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/__init__.py0000644000175100001440000000120211674463363021334 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/grid/inverted_grid_model.py0000644000175100001440000000635011674463363023613 0ustar ischnellusers00000000000000""" An adapter model that inverts all of its row/column targets. Use this class with the CompositeGridModel to make models with different orientations match, or use it to visually flip the data without modifying the underlying model's sense of row and column. """ # Enthought library imports from traits.api import Instance # local imports from grid_model import GridModel class InvertedGridModel(GridModel): """ An adapter model that inverts all of its row/column targets. Use this class with the CompositeGridModel to make models with different orientations match, or use it to visually flip the data without modifying the underlying model's sense of row and column. """ model = Instance(GridModel, ()) ######################################################################### # 'GridModel' interface. ######################################################################### def get_column_count(self): return self.model.get_row_count() def get_column_name(self, index): return self.model.get_row_name(index) def get_cols_drag_value(self, cols): return self.model.get_rows_drag_value(cols) def get_cols_selection_value(self, cols): return self.model.get_rows_selection_value(cols) def get_column_context_menu(self, col): return self.model.get_row_context_menu(col) def sort_by_column(self, col, reverse=False): return self.model.sort_by_row(col, reverse) def is_column_read_only(self, index): return self.model.is_row_read_only(index) def get_row_count(self): return self.model.get_column_count() def get_row_name(self, index): return self.model.get_column_name(index) def get_rows_drag_value(self, rows): return self.model.get_cols_drag_value(rows) def get_rows_selection_value(self, rows): return self.model.get_cols_selection_value(rows) def get_row_context_menu(self, row): return self.model.get_col_context_menu(row) def sort_by_row(self, row, reverse=False): return self.model.sort_by_col(row, reverse) def is_row_read_only(self, index): return self.model.is_column_read_only(index) def delete_rows(self, pos, num_rows): return self.model.delete_cols(pos, num_rows) def insert_cols(self, pos, num_rows): return self.model.insert_rows(pos, num_rows) def get_value(self, row, col): return self.model.get_value(col, row) def get_cell_drag_value(self, row, col): return self.model.get_cell_drag_value(col, row) def get_cell_selection_value(self, row, col): return self.model.get_cell_selection_value(col, row) def resolve_selection(self, selection_list): return self.model.resolve_selection(selection_list) def get_cell_context_menu(self, row, col): return self.model.get_cell_context_menu(col, row) def set_value(self, row, col, value): return self.model.set_value(col, row, value) def is_cell_empty(self, row, col): return self.model.is_cell_empty(col, row) def is_cell_editable(self, row, col): return self.model.is_cell_editable(col, row) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/checkbox_renderer.py0000644000175100001440000000265211674463363023263 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # local imports from checkbox_image_renderer import CheckboxImageRenderer from grid_cell_renderer import GridCellRenderer class CheckboxRenderer(GridCellRenderer): def __init__(self, **traits): # base-class constructor super(CheckboxRenderer, self).__init__(**traits) # initialize the renderer, if it hasn't already been initialized if self.renderer is None: self.renderer = CheckboxImageRenderer() return def on_left_click(self, grid, row, col): """ Toggles the value. """ value = grid.model.get_cell_drag_value(row, col) try: grid.model.set_value(row, col, not value) except: pass return True #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/grid/grid_model.py0000644000175100001440000003512311674463363021713 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Model for grid views. """ # Major package imports # Enthought library imports. from traits.api import Any, Bool, Event, HasPrivateTraits, HasTraits, \ Instance, Int, Str, Tuple # The classes below are part of the table specification. class GridRow(HasTraits): """ Structure for holding row/column specifications. """ # Is the data in this column read-only? read_only = Bool(False) # The label for this column label = Str # We specify the same info for rows and for columns, but add a GridColumn # name for clarity. GridColumn = GridRow class GridSortData(HasTraits): """ An event that signals a sorting has taken place. The index attribute should be set to the row or column on which the data was sorted. An index of -1 indicates that no sort has been applied (or a previous sort has been unapplied). The reversed flag indicates that the sort has been done in reverse order. """ index = Int(-1) reversed = Bool(False) # for backwards compatibility GridSortEvent = GridSortData class GridModel(HasPrivateTraits): """ Model for grid views. """ #### 'GridModel' interface ################################################ # Fire when the structure of the underlying grid model has changed. structure_changed = Event # Fire when the content of the underlying grid model has changed. content_changed = Event # Column model is currently sorted on. column_sorted = Instance(GridSortData) # The cell (row, col) the mouse is currently in. mouse_cell = Tuple(Int, Int) #### Events #### # A row was inserted or appended to this model rows_added = Event # A column was inserted or appended to this model columns_added = Event # A row sort took place row_sorted = Event # Event fired when a cell is clicked on: click = Event # = (row, column) that was clicked on # Event fired when a cell is double-clicked on: dclick = Event # = (row, column) that was double-clicked on ######################################################################### # 'object' interface. ######################################################################### def __init__(self, **traits): """ Creates a new grid model. """ # Base class constructors. super(GridModel, self).__init__(**traits) return ######################################################################### # 'GridModel' interface -- Subclasses MUST override the following ######################################################################### def get_column_count(self): """ Return the number of columns for this table. """ raise NotImplementedError def get_column_name(self, index): """ Return the name of the column specified by the (zero-based) index. """ raise NotImplementedError def get_row_count(self): """ Return the number of rows for this table. """ raise NotImplementedError def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) index. """ raise NotImplementedError def get_value(self, row, col): """ Return the value stored in the table at (row, col). """ raise NotImplementedError def is_cell_empty(self, row, col): """ Returns True if the cell at (row, col) has a None value, False otherwise.""" raise NotImplementedError ######################################################################### # 'GridModel' interface -- Subclasses MAY override the following ######################################################################### def get_cols_drag_value(self, cols): """ Return the value to use when the specified columns are dragged or copied and pasted. cols is a list of column indexes. """ pass def get_cols_selection_value(self, cols): """ Return the value to use when the specified cols are selected. This value should be enough to specify to other listeners what is going on in the grid. rows is a list of row indexes. """ cols_data = [] row_count = self.get_row_count() for col in cols: col_data = [] for row in range(row_count): col_data.append(self.get_value(row, col)) cols_data.append(col_data) return cols_data def get_column_context_menu(self, col): """ Return a MenuManager object that will generate the appropriate context menu for this column.""" pass def get_column_size(self, col): """ Return the size in pixels of the column indexed by col. A value of -1 or None means use the default. """ return None def sort_by_column(self, col, reverse=False): """ Sort model data by the column indexed by col. The reverse flag indicates that the sort should be done in reverse. """ pass def no_column_sort(self): """ Turn off any column sorting of the model data. """ raise NotImplementedError def is_column_read_only(self, index): """ Return True if the column specified by the zero-based index is read-only. """ return False def get_rows_drag_value(self, rows): """ Return the value to use when the specified rows are dragged or copied and pasted. rows is a list of row indexes. """ pass def get_rows_selection_value(self, rows): """ Return the value to use when the specified rows are selected. This value should be enough to specify to other listeners what is going on in the grid. rows is a list of row indexes. """ rows_data = [] column_count = self.get_column_count() for row in rows: row_data = [] for col in range(column_count): row_data.append(self.get_value(row, col)) rows_data.append(row_data) return rows_data def get_row_context_menu(self, row): """ Return a MenuManager object that will generate the appropriate context menu for this row.""" pass def get_row_size(self, row): """ Return the size in pixels of the row indexed by 'row'. A value of -1 or None means use the default. """ return None def sort_by_row(self, row, reverse=False): """ Sort model data by the data row indexed by row. The reverse flag indicates that the sort should be done in reverse. """ pass def no_row_sort(self): """ Turn off any row sorting of the model data. """ raise NotImplementedError def is_row_read_only(self, index): """ Return True if the row specified by the zero-based index is read-only. """ return False def get_type(self, row, col): """ Return the value stored in the table at (row, col). """ raise NotImplementedError def get_cell_drag_value(self, row, col): """ Return the value to use when the specified cell is dragged or copied and pasted. """ # by default we just use the cell value return self.get_value(row, col) def get_cell_selection_value(self, row, col): """ Return the value stored in the table at (row, col). """ pass def get_cell_editor(self, row, col): """ Return the editor for the specified cell. """ return None def get_cell_renderer(self, row, col): """ Return the renderer for the specified cell. """ return None def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in selection_list. For each coordinate, if the row is -1 it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. Note that the objects in selection_list are model-specific. """ return selection_list # fixme: this context menu stuff is going in here for now, but it # seems like this is really more of a view piece than a model piece. # this is how the tree control does it, however, so we're duplicating # that here. def get_cell_context_menu(self, row, col): """ Return a MenuManager object that will generate the appropriate context menu for this cell.""" pass def is_valid_cell_value(self, row, col, value): """ Tests whether value is valid for the cell at row, col. Returns True if value is acceptable, False otherwise. """ return False def set_value(self, row, col, value): """ Sets the value of the cell at (row, col) to value. Raises a ValueError if the value is vetoed. Note that subclasses should not override this method, but should override the _set_value method instead. """ #print 'GridModel.set_value row: ', row, ' col: ', col, ' value: ', value rows_appended = self._set_value(row, col, value) self.fire_content_changed() return def is_cell_read_only(self, row, col): """ Returns True if the cell at (row, col) is not editable, False otherwise. """ return False def get_cell_bg_color(self, row, col): """ Return a wxColour object specifying what the background color of the specified cell should be. """ return None def get_cell_text_color(self, row, col): """ Return a wxColour object specifying what the text color of the specified cell should be. """ return None def get_cell_font(self, row, col): """ Return a wxFont object specifying what the font of the specified cell should be. """ return None def get_cell_halignment(self, row, col): """ Return a string specifying what the horizontal alignment of the specified cell should be. Return 'left' for left alignment, 'right' for right alignment, or 'center' for center alignment. """ return None def get_cell_valignment(self, row, col): """ Return a string specifying what the vertical alignment of the specified cell should be. Return 'top' for top alignment, 'bottom' for bottom alignment, or 'center' for center alignment. """ return None ######################################################################### # 'GridModel' interface -- Subclasses MAY NOT override the following ######################################################################### def fire_content_changed(self): """ Fires the appearance changed event. """ self.content_changed = 'changed' return def fire_structure_changed(self): """ Fires the appearance changed event. """ self.structure_changed = 'changed' return def delete_rows(self, pos, num_rows): """ Removes rows pos through pos + num_rows from the model. Subclasses should not override this method, but should override _delete_rows instead. """ deleted = self._delete_rows(pos, num_rows) if deleted > 0: self.fire_structure_changed() return True def insert_rows(self, pos, num_rows): """ Inserts rows at pos through pos + num_rows into the model. Subclasses should not override this method, but should override _insert_rows instead. """ inserted = self._insert_rows(pos, num_rows) if inserted > 0: self.fire_structure_changed() return True def delete_columns(self, pos, num_cols): """ Removes columns pos through pos + num_cols from the model. Subclasses should not override this method, but should override _delete_columns instead. """ deleted = self._delete_columns(pos, num_cols) if deleted > 0: self.fire_structure_changed() return True def insert_columns(self, pos, num_cols): """ Inserts columns at pos through pos + num_cols into the model. Subclasses should not override this method, but should override _insert_columns instead. """ inserted = self._insert_columns(pos, num_cols) if inserted > 0: self.fire_structure_changed() return True ######################################################################### # protected 'GridModel' interface -- Subclasses should override these # if they wish to support the # specific actions. ######################################################################### def _delete_rows(self, pos, num_rows): """ Implementation method for delete_rows. Should return the number of rows that were deleted. """ pass def _insert_rows(self, pos, num_rows): """ Implementation method for insert_rows. Should return the number of rows that were inserted. """ pass def _delete_columns(self, pos, num_cols): """ Implementation method for delete_cols. Should return the number of columns that were deleted. """ pass def _insert_columns(self, pos, num_cols): """ Implementation method for insert_columns. Should return the number of columns that were inserted. """ pass def _set_value(self, row, col, value): """ Implementation method for set_value. Should return the number of rows or columns, if any, that were appended. """ pass def _move_column(self, frm, to): """ Moves a specified **frm** column to before the specified **to** column. Returns **True** if successful; **False** otherwise. """ return False def _move_row(self, frm, to): """ Moves a specified **frm** row to before the specified **to** row. Returns **True** if successful; **False** otherwise. """ return False #### EOF #################################################################### pyface-4.1.0/pyface/ui/wx/grid/trait_grid_model.py0000644000175100001440000006005311674463363023116 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A TraitGridModel builds a grid from a list of traits objects. Each row represents on object, each column one trait from those objects. All the objects must be of the same type. Optionally a user may pass in a list of trait names defining which traits will be shown in the columns and in which order. If this list is not passed in, then the first object is inspected and every trait from that object gets a column.""" # Enthought library imports from traits.api import Any, Bool, Callable, Dict, Function, HasTraits, \ Int, List, Str, Trait, TraitError, Type # local imports from grid_model import GridColumn, GridModel, GridSortEvent from trait_grid_cell_adapter import TraitGridCellAdapter # The classes below are part of the table specification. class TraitGridColumn(GridColumn): """ Structure for holding column specifications in a TraitGridModel. """ # The trait name for this column. This takes precedence over method name = Trait(None, None, Str) # A method name to call to get the value for this column method = Trait(None, None, Str) # A method to be used to sort on this column sorter = Trait(None, None, Callable) # A dictionary of formats for the display of different types. If it is # defined as a callable, then that callable must accept a single argument. formats = Dict(key_trait = Type, value_trait=Trait('', Str, Callable)) # A name to designate the type of this column typename = Trait(None, None, Str) # note: context menus should go in here as well? but we need # more info than we have available at this point size = Int(-1) class TraitGridSelection(HasTraits): """ Structure for holding specification information. """ # The selected object obj = Trait(HasTraits) # The specific trait selected on the object trait_name = Trait(None, None, Str) # The meat. class TraitGridModel(GridModel): """ A TraitGridModel builds a grid from a list of traits objects. Each row represents on object, each column one trait from those objects. All the objects must be of the same type. Optionally a user may pass in a list of trait names defining which traits will be shown in the columns and in which order. If this list is not passed in, then the first object is inspected and every trait from that object gets a column.""" # A 2-dimensional list/array containing the grid data. data = List()#HasTraits) # The column definitions columns = Trait(None, None, List(Trait(None, Str, TraitGridColumn))) # The trait to look at to get the row name row_name_trait = Trait(None, None, Str) # Allow column sorting? allow_column_sort = Bool(True) # A factory to generate new rows. If this is not None then it must # be a no-argument function. row_factory = Trait(None, None, Function) ######################################################################### # 'object' interface. ######################################################################### def __init__(self, **traits): """ Create a TraitGridModel object. """ # Base class constructor super(TraitGridModel, self).__init__(**traits) # if no columns are pass in then create the list of names # from the first trait in the list. if the list is empty, # the columns should be an empty list as well. self._auto_columns = self.columns if self.columns is None or len(self.columns) == 0: if self.data is not None and len(self.data) > 0: self._auto_columns = [] # we only add traits that aren't events, since events # are write-only for name, trait in self.data[0].traits().items(): if trait.type != 'event': self._auto_columns.append(TraitGridColumn(name = name)) else: self._auto_columns = [] # attach trait handlers to the list object self.on_trait_event(self._on_data_changed, 'data') self.on_trait_event(self._on_data_items_changed, 'data_items') # attach appropriate trait handlers to objects in the list self.__manage_data_listeners(self.data) # attach a listener to the column definitions so we refresh when # they change self.on_trait_change(self._on_columns_changed, 'columns') self.on_trait_event(self._on_columns_items_changed, 'columns_items') # attach listeners to the column definitions themselves self.__manage_column_listeners(self.columns) # attach a listener to the row_name_trait self.on_trait_change(self._on_row_name_trait_changed, 'row_name_trait') return ######################################################################### # 'GridModel' interface. ######################################################################### def get_column_count(self): """ Return the number of columns for this table. """ return len(self._auto_columns) def get_column_name(self, index): """ Return the label of the column specified by the (zero-based) index. """ try: name = col = self._auto_columns[index] if isinstance(col, TraitGridColumn): if col.label is not None: name = col.label else: name = col.name except IndexError: name = '' return name def get_column_size(self, index): """ Return the size in pixels of the column indexed by col. A value of -1 or None means use the default. """ size = -1 try: col = self._auto_columns[index] if isinstance(col, TraitGridColumn): size = col.size except IndexError: pass return size def get_cols_drag_value(self, cols): """ Return the value to use when the specified columns are dragged or copied and pasted. cols is a list of column indexes. """ # iterate over every column, building a list of the values in that # column value = [] for col in cols: value.append(self.__get_data_column(col)) return value def get_cols_selection_value(self, cols): """ Returns a list of TraitGridSelection objects containing the object corresponding to the grid rows and the traits corresponding to the specified columns. """ values = [] for obj in self.data: for col in cols: values.append(TraitGridSelection(obj = obj, trait_name = self.__get_column_name(col))) return values def sort_by_column(self, col, reverse=False): """ Sort model data by the column indexed by col. """ # first check to see if we allow sorts by column if not self.allow_column_sort: return # see if a sorter is specified for this column try: column = self._auto_columns[col] name = self.__get_column_name(col) # by default we use cmp to sort on the traits sorter = cmp if isinstance(column, TraitGridColumn) and \ column.sorter is not None: sorter = column.sorter except IndexError: return # now sort the data appropriately def sort_function(a, b): if hasattr(a, name): a_trait = getattr(a, name) else: a_trait = None if hasattr(b, name): b_trait = getattr(b, name) else: b_trait = None return sorter(a_trait, b_trait) self.data.sort(sort_function) if reverse: self.data.reverse() # now fire an event to tell the grid we're sorted print 'firing sort event' self.column_sorted = GridSortEvent(index = col, reversed = reverse) return def is_column_read_only(self, index): """ Return True if the column specified by the zero-based index is read-only. """ return self.__get_column_readonly(index) def get_row_count(self): """ Return the number of rows for this table. """ if self.data is not None: count = len(self.data) else: count = 0 return count def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) index. """ if self.row_name_trait is not None: try: row = self._get_row(index) if hasattr(row, self.row_name_trait): name = getattr(row, self.row_name_trait) except IndexError: name = str(index + 1) else: name = str(index + 1) return name def get_rows_drag_value(self, rows): """ Return the value to use when the specified rows are dragged or copied and pasted. rows is a list of row indexes. If there is only one row listed, return the corresponding trait object. If more than one row is listed then return a list of objects. """ # return a list of objects value = [] for index in rows: try: # note that we can't use get_value for this because it # sometimes returns strings instead of the actual value, # e.g. in cases where a float_format is specified value.append(self._get_row(index)) except IndexError: value.append(None) return value def get_rows_selection_value(self, rows): """ Returns a list of TraitGridSelection objects containing the object corresponding to the selected rows. """ values = [] for row_index in rows: values.append(TraitGridSelection(obj = self.data[row_index])) return values def is_row_read_only(self, index): """ Return True if the row specified by the zero-based index is read-only. """ return False def get_cell_editor(self, row, col): """ Return the editor for the specified cell. """ # print 'TraitGridModel.get_cell_editor row: ', row, ' col: ', col obj = self.data[row] trait_name = self.__get_column_name(col) trait = obj.base_trait(trait_name) if trait is None: return None factory = trait.get_editor() return TraitGridCellAdapter(factory, obj, trait_name, '') def get_cell_drag_value(self, row, col): """ Return the value to use when the specified cell is dragged or copied and pasted. """ # find the name of the column indexed by col # note that this code is the same as the get_value code but without # the potential string formatting column = self.__get_column(col) obj = self._get_row(row) value = self._get_data_from_row(obj, column) return value def get_cell_selection_value(self, row, col): """ Returns a TraitGridSelection object specifying the data stored in the table at (row, col). """ obj = self.data[row] trait_name = self.__get_column_name(col) return TraitGridSelection(obj = obj, trait_name = trait_name) def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in objlist. For each coordinate, if the row is -1 it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. For the TraitGridModel, the objects in objlist must be TraitGridSelection objects. """ cells = [] for selection in selection_list: try: row = self.data.index(selection.obj) except ValueError: continue column = -1 if selection.trait_name is not None: column = self._get_column_index_by_trait(selection.trait_name) if column is None: continue cells.append((row, column)) return cells def get_type(self, row, col): """ Return the value stored in the table at (row, col). """ typename = self.__get_column_typename(col) return typename def get_value(self, row, col): """ Return the value stored in the table at (row, col). """ value = self.get_cell_drag_value(row, col) formats = self.__get_column_formats(col) if value is not None and formats is not None and \ formats.has_key(type(value)) and \ formats[type(value)] is not None: try: format = formats[type(value)] if callable(format): value = format(value) else: value = format % value except TypeError: # not enough arguments? wrong kind of arguments? pass return value def is_cell_empty(self, row, col): """ Returns True if the cell at (row, col) has a None value, False otherwise.""" value = self.get_value(row, col) return value is None def is_cell_editable(self, row, col): """ Returns True if the cell at (row, col) is editable, False otherwise. """ return not self.is_column_read_only(col) ######################################################################### # protected 'GridModel' interface. ######################################################################### def _insert_rows(self, pos, num_rows): """ Inserts num_rows at pos and fires an event iff a factory method for new rows is defined. Otherwise returns 0. """ count = 0 if self.row_factory is not None: new_data = [] for i in range(num_rows): new_data.append(self.row_factory()) count = self._insert_rows_into_model(pos, new_data) self.rows_added = ('added', pos, new_data) return count def _delete_rows(self, pos, num_rows): """ Removes rows pos through pos + num_rows from the model. """ if pos + num_rows >= self.get_row_count(): num_rows = self.get_rows_count() - pos return self._delete_rows_from_model(pos, num_rows) def _set_value(self, row, col, value): """ Sets the value of the cell at (row, col) to value. Raises a ValueError if the value is vetoed or the cell at (row, col) does not exist. """ #print 'TraitGridModel._set_value: new: ', value new_rows = 0 # find the column indexed by col column = self.__get_column(col) obj = self._get_row(row) success = False if obj is not None: success = self._set_data_on_row(obj, column, value) else: # Add a new row. new_rows = self._insert_rows(self.get_row_count(), 1) if new_rows > 0: # now set the value on the new object obj = self._get_row(self.get_row_count() - 1) success = self._set_data_on_row(obj, column, value) if not success: # fixme: what do we do in this case? veto the set somehow? raise # an exception? pass return new_rows ######################################################################### # protected interface. ######################################################################### def _get_row(self, index): """ Return the object that corresponds to the row at index. Override this to handle very large data sets. """ return self.data[index] def _get_data_from_row(self, row, column): """ Retrieve the data specified by column for this row. Attribute can be either a member of the row object, or a no-argument method on that object. Override this method to provide alternative ways of accessing the data in the object. """ value = None if row is not None and column is not None: if not isinstance(column, TraitGridColumn): # first handle the case where the column # definition might be just a string if hasattr(row, column): value = getattr(row, column) elif column.name is not None and hasattr(row, column.name): # this is the case when the trait name is specified value = getattr(row, column.name) elif column.method is not None and hasattr(row, column.method): # this is the case when an object method is specified value = getattr(row, column.method)() return value def _set_data_on_row(self, row, column, value): """ Retrieve the data specified by column for this row. Attribute can be either a member of the row object, or a no-argument method on that object. Override this method to provide alternative ways of accessing the data in the object. """ success = False if row is not None and column is not None: if not isinstance(column, TraitGridColumn): if hasattr(row, column): # sometimes the underlying grid gives us 0/1 instead # of True/False. do some conversion here to make that # case worl. #if type(getattr(row, column)) == bool and \ # type(value) != bool: # convert the value to a boolean # value = bool(value) setattr(row, column, value) success = True elif column.name is not None and hasattr(row, column.name): # sometimes the underlying grid gives us 0/1 instead # of True/False. do some conversion here to make that # case worl. #if type(getattr(row, column.name)) == bool and \ # type(value) != bool: # convert the value to a boolean # value = bool(value) setattr(row, column.name, value) success = True # do nothing in the method case as we don't allow rows # defined to return a method value to set the value return success def _insert_rows_into_model(self, pos, new_data): """ Insert the given new rows into the model. Override this method to handle very large data sets. """ for data in new_data: self.data.insert(pos, data) pos += 1 return def _delete_rows_from_model(self, pos, num_rows): """ Delete the specified rows from the model. Override this method to handle very large data sets. """ del self.data[pos, pos + num_rows] return num_rows ########################################################################### # trait handlers ########################################################################### def _on_row_name_trait_changed(self, new): """ Force the grid to refresh when any underlying trait changes. """ self.fire_content_changed() return def _on_columns_changed(self, object, name, old, new): """ Force the grid to refresh when any underlying trait changes. """ self.__manage_column_listeners(old, remove=True) self.__manage_column_listeners(self.columns) self._auto_columns = self.columns self.fire_structure_changed() return def _on_columns_items_changed(self, event): """ Force the grid to refresh when any underlying trait changes. """ self.__manage_column_listeners(event.removed, remove=True) self.__manage_column_listeners(event.added) self.fire_structure_changed() return def _on_contained_trait_changed(self, new): """ Force the grid to refresh when any underlying trait changes. """ self.fire_content_changed() return def _on_data_changed(self, object, name, old, new): """ Force the grid to refresh when the underlying list changes. """ self.__manage_data_listeners(old, remove=True) self.__manage_data_listeners(self.data) self.fire_structure_changed() return def _on_data_items_changed(self, event): """ Force the grid to refresh when the underlying list changes. """ # if an item was removed then remove that item's listener self.__manage_data_listeners(event.removed, remove=True) # if items were added then add trait change listeners on those items self.__manage_data_listeners(event.added) self.fire_content_changed() return ########################################################################### # private interface. ########################################################################### def __get_data_column(self, col): """ Return a 1-d list of data from the column indexed by col. """ row_count = self.get_row_count() coldata = [] for row in range(row_count): try: coldata.append(self.get_value(row, col)) except IndexError: coldata.append(None) return coldata def __get_column(self, col): try: column = self._auto_columns[col] except IndexError: column = None return column def __get_column_name(self, col): name = column = self.__get_column(col) if isinstance(column, TraitGridColumn): name = column.name return name def __get_column_typename(self, col): name = column = self.__get_column(col) typename = None if isinstance(column, TraitGridColumn): typename = column.typename return typename def __get_column_readonly(self, col): read_only = False column = self.__get_column(col) if isinstance(column, TraitGridColumn): read_only = column.read_only return read_only def __get_column_formats(self, col): formats = None column = self.__get_column(col) if isinstance(column, TraitGridColumn): formats = column.formats return formats def _get_column_index_by_trait(self, trait_name): cols = self._auto_columns for i in range(len(cols)): col = cols[i] if isinstance(col, TraitGridColumn): col_name = col.name else: col_name = col if col_name == trait_name: return i return None def __manage_data_listeners(self, list, remove=False): # attach appropriate trait handlers to objects in the list if list is not None: for item in list: item.on_trait_change(self._on_contained_trait_changed, remove = remove) return def __manage_column_listeners(self, collist, remove=False): if collist is not None: for col in collist: if isinstance(col, TraitGridColumn): col.on_trait_change(self._on_columns_changed, remove = remove) return #### EOF #################################################################### pyface-4.1.0/pyface/ui/wx/grid/composite_grid_model.py0000644000175100001440000003154611674463363024002 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Dict, List, Trait # local imports from grid_model import GridModel, GridRow class CompositeGridModel(GridModel): """ A CompositeGridModel is a model whose underlying data is a collection of other grid models. """ # The models this model is comprised of. data = List(GridModel) # The rows in the model. rows = Trait(None, None, List(GridRow)) # The cached data indexes. _data_index = Dict ######################################################################### # 'object' interface. ######################################################################### def __init__(self, **traits): """ Create a CompositeGridModel object. """ # Base class constructor super(CompositeGridModel, self).__init__(**traits) self._row_count = None return ######################################################################### # 'GridModel' interface. ######################################################################### def get_column_count(self): """ Return the number of columns for this table. """ # for the composite grid model, this is simply the sum of the # column counts for the underlying models count = 0 for model in self.data: count += model.get_column_count() return count def get_column_name(self, index): """ Return the name of the column specified by the (zero-based) index. """ model, new_index = self._resolve_column_index(index) return model.get_column_name(new_index) def get_column_size(self, index): """ Return the size in pixels of the column indexed by col. A value of -1 or None means use the default. """ model, new_index = self._resolve_column_index(index) return model.get_column_size(new_index) def get_cols_drag_value(self, cols): """ Return the value to use when the specified columns are dragged or copied and pasted. cols is a list of column indexes. """ values = [] for col in cols: model, real_col = self._resolve_column_index(col) values.append(model.get_cols_drag_value([real_col])) return values def get_cols_selection_value(self, cols): """ Return the value to use when the specified cols are selected. This value should be enough to specify to other listeners what is going on in the grid. rows is a list of row indexes. """ return self.get_cols_drag_value(self, cols) def get_column_context_menu(self, col): """ Return a MenuManager object that will generate the appropriate context menu for this column.""" model, new_index = self._resolve_column_index(col) return model.get_column_context_menu(new_index) def sort_by_column(self, col, reverse=False): """ Sort model data by the column indexed by col. The reverse flag indicates that the sort should be done in reverse. """ pass def is_column_read_only(self, index): """ Return True if the column specified by the zero-based index is read-only. """ model, new_index = self._resolve_column_index(index) return model.is_column_read_only(new_index) def get_row_count(self): """ Return the number of rows for this table. """ # see if we've already calculated the row_count if self._row_count is None: row_count = 0 # return the maximum rows of any of the contained models for model in self.data: rows = model.get_row_count() if rows > row_count: row_count = rows # save the result for next time self._row_count = row_count return self._row_count def get_row_name(self, index): """ Return the name of the row specified by the (zero-based) index. """ label = None # if the rows list exists then grab the label from there... if self.rows is not None: if len(self.rows) > index: label = self.rows[index].label # ... otherwise generate it from the zero-based index. else: label = str(index + 1) return label def get_rows_drag_value(self, rows): """ Return the value to use when the specified rows are dragged or copied and pasted. rows is a list of row indexes. """ row_values = [] for rindex in rows: row = [] for model in self.data: new_data = model.get_rows_drag_value([rindex]) # if it's a list then we assume that it represents more than # one column's worth of values if isinstance(new_data, list): row.extend(new_data) else: row.append(new_data) # now save our new row value row_values.append(row) return row_values def is_row_read_only(self, index): """ Return True if the row specified by the zero-based index is read-only. """ read_only = False if self.rows is not None and len(self.rows) > index: read_only = self.rows[index].read_only return read_only def get_type(self, row, col): """ Return the type of the value stored in the table at (row, col). """ model, new_col = self._resolve_column_index(col) return model.get_type(row, new_col) def get_value(self, row, col): """ Return the value stored in the table at (row, col). """ model, new_col = self._resolve_column_index(col) return model.get_value(row, new_col) def get_cell_selection_value(self, row, col): """ Return the value stored in the table at (row, col). """ model, new_col = self._resolve_column_index(col) return model.get_cell_selection_value(row, new_col) def resolve_selection(self, selection_list): """ Returns a list of (row, col) grid-cell coordinates that correspond to the objects in selection_list. For each coordinate, if the row is -1 it indicates that the entire column is selected. Likewise coordinates with a column of -1 indicate an entire row that is selected. Note that the objects in selection_list are model-specific. """ coords = [] for selection in selection_list: # we have to look through each of the models in order # for the selected object for model in self.data: cells = model.resolve_selection([selection]) # we know this model found the object if cells comes back # non-empty if cells is not None and len(cells) > 0: coords.extend(cells) break return coords # fixme: this context menu stuff is going in here for now, but it # seems like this is really more of a view piece than a model piece. # this is how the tree control does it, however, so we're duplicating # that here. def get_cell_context_menu(self, row, col): """ Return a MenuManager object that will generate the appropriate context menu for this cell.""" model, new_col = self._resolve_column_index(col) return model.get_cell_context_menu(row, new_col) def is_cell_empty(self, row, col): """ Returns True if the cell at (row, col) has a None value, False otherwise.""" model, new_col = self._resolve_column_index(col) if model is None: return True else: return model.is_cell_empty(row, new_col) def is_cell_editable(self, row, col): """ Returns True if the cell at (row, col) is editable, False otherwise. """ model, new_col = self._resolve_column_index(col) return model.is_cell_editable(row, new_col) def is_cell_read_only(self, row, col): """ Returns True if the cell at (row, col) is not editable, False otherwise. """ model, new_col = self._resolve_column_index(col) return model.is_cell_read_only(row, new_col) def get_cell_bg_color(self, row, col): """ Return a wxColour object specifying what the background color of the specified cell should be. """ model, new_col = self._resolve_column_index(col) return model.get_cell_bg_color(row, new_col) def get_cell_text_color(self, row, col): """ Return a wxColour object specifying what the text color of the specified cell should be. """ model, new_col = self._resolve_column_index(col) return model.get_cell_text_color(row, new_col) def get_cell_font(self, row, col): """ Return a wxFont object specifying what the font of the specified cell should be. """ model, new_col = self._resolve_column_index(col) return model.get_cell_font(row, new_col) def get_cell_halignment(self, row, col): """ Return a string specifying what the horizontal alignment of the specified cell should be. Return 'left' for left alignment, 'right' for right alignment, or 'center' for center alignment. """ model, new_col = self._resolve_column_index(col) return model.get_cell_halignment(row, new_col) def get_cell_valignment(self, row, col): """ Return a string specifying what the vertical alignment of the specified cell should be. Return 'top' for top alignment, 'bottom' for bottom alignment, or 'center' for center alignment. """ model, new_col = self._resolve_column_index(col) return model.get_cell_valignment(row, new_col) ######################################################################### # protected 'GridModel' interface. ######################################################################### def _delete_rows(self, pos, num_rows): """ Implementation method for delete_rows. Should return the number of rows that were deleted. """ for model in self.data: model._delete_rows(pos, num_rows) return num_rows def _insert_rows(self, pos, num_rows): """ Implementation method for insert_rows. Should return the number of rows that were inserted. """ for model in self.data: model._insert_rows(pos, num_rows) return num_rows def _set_value(self, row, col, value): """ Implementation method for set_value. Should return the number of rows, if any, that were appended. """ model, new_col = self._resolve_column_index(col) model._set_value(row, new_col, value) return 0 ######################################################################### # private interface ######################################################################### def _resolve_column_index(self, index): """ Resolves a column index into the correct model and adjusted index. Returns the target model and the corrected index. """ real_index = index cached = None #self._data_index.get(index) if cached is not None: model, col_index = cached else: model = None for m in self.data: cols = m.get_column_count() if real_index < cols: model = m break else: real_index -= cols self._data_index[index] = (model, real_index) return model, real_index def _data_changed(self): """ Called when the data trait is changed. Since this is called when our underlying models change, the cached results of the column lookups is wrong and needs to be invalidated. """ self._data_index.clear() return def _data_items_changed(self): """ Called when the members of the data trait have changed. Since this is called when our underlying model change, the cached results of the column lookups is wrong and needs to be invalidated. """ self._data_index.clear() return #### EOF #################################################################### pyface-4.1.0/pyface/ui/wx/heading_text.py0000644000175100001440000001114611674463363021323 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import implements, Instance, Int, Unicode # Local imports. from pyface.i_heading_text import IHeadingText, MHeadingText from pyface.image_resource import ImageResource from pyface.util.font_helper import new_font_like from widget import Widget class HeadingText(MHeadingText, Widget): """ The toolkit specific implementation of a HeadingText. See the IHeadingText interface for the API documentation. """ implements(IHeadingText) #### 'IHeadingText' interface ############################################# level = Int(1) text = Unicode('Default') image = Instance(ImageResource, ImageResource('heading_level_1')) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates the panel. """ # Base class constructor. super(HeadingText, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) return ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ # The background image (it is tiled). image = self.image.create_image() self._bmp = image.ConvertToBitmap() sizer = wx.BoxSizer(wx.VERTICAL) panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN | wx.SIMPLE_BORDER) panel.SetSizer(sizer) panel.SetAutoLayout(True) # Create a suitable font. self._font = new_font_like(wx.NORMAL_FONT, family=wx.SWISS) width, height = self._get_preferred_size(self.text, self._font) panel.SetMinSize((width, height)) wx.EVT_PAINT(panel, self._on_paint_background) wx.EVT_ERASE_BACKGROUND(panel, self._on_erase_background) return panel def _get_preferred_size(self, text, font): """ Calculates the preferred size of the widget. """ dc = wx.ScreenDC() dc.SetFont(font) width, height = dc.GetTextExtent(text) return (width + 10, height + 10) def _tile_background_image(self, dc, width, height): """ Tiles the background image. """ w = self._bmp.GetWidth() h = self._bmp.GetHeight() x = 0 while x < width: y = 0 while y < height: dc.DrawBitmap(self._bmp, x, y) y = y + h x = x + w return #### Trait event handlers ################################################# def _text_changed(self, new): """ Called when the text is changed. """ if self.control is not None: self.control.Refresh() return #### wx event handlers #################################################### def _on_paint_background(self, event): """ Called when the background of the panel is painted. """ dc = wx.PaintDC(self.control) size = self.control.GetClientSize() # Tile the background image. self._tile_background_image(dc, size.width, size.height) # Render the text. dc.SetFont(self._font) dc.DrawText(self.text, 5, 4) return def _on_erase_background(self, event): """ Called when the background of the panel is erased. """ dc = event.GetDC() size = self.control.GetClientSize() # Tile the background image. self._tile_background_image(dc, size.width, size.height) # Render the text. dc.SetFont(self._font) dc.DrawText(self.text, 5, 4) return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/__init__.py0000644000175100001440000000034511674463363020416 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. # #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/wx/directory_dialog.py0000644000175100001440000000532711674463363022207 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import Bool, implements, Unicode # Local imports. from pyface.i_directory_dialog import IDirectoryDialog, MDirectoryDialog from dialog import Dialog class DirectoryDialog(MDirectoryDialog, Dialog): """ The toolkit specific implementation of a DirectoryDialog. See the IDirectoryDialog interface for the API documentation. """ implements(IDirectoryDialog) #### 'IDirectoryDialog' interface ######################################### default_path = Unicode message = Unicode new_directory = Bool(True) path = Unicode ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_contents(self, parent): # In wx this is a canned dialog. pass ########################################################################### # 'IWindow' interface. ########################################################################### def close(self): # Get the path of the chosen directory. self.path = unicode(self.control.GetPath()) # Let the window close as normal. super(DirectoryDialog, self).close() ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): # The default style. style = wx.OPEN # Create the wx style depending on which buttons are required etc. if self.new_directory: style = style | wx.DD_NEW_DIR_BUTTON if self.message: message = self.message else: message = "Choose a directory" # Create the actual dialog. return wx.DirDialog(parent, message=message, defaultPath=self.default_path, style=style) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/python_shell.py0000644000175100001440000002346511674463363021377 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Standard library imports. import __builtin__ import os import sys import types # Major package imports. from wx.py.shell import Shell as PyShellBase import wx # Enthought library imports. from traits.api import Event, implements # Private Enthought library imports. from traits.util.clean_strings import python_name from pyface.wx.drag_and_drop import PythonDropTarget # Local imports. from pyface.i_python_shell import IPythonShell, MPythonShell from pyface.key_pressed_event import KeyPressedEvent from widget import Widget class PythonShell(MPythonShell, Widget): """ The toolkit specific implementation of a PythonShell. See the IPythonShell interface for the API documentation. """ implements(IPythonShell) #### 'IPythonShell' interface ############################################# command_executed = Event key_pressed = Event(KeyPressedEvent) ########################################################################### # 'object' interface. ########################################################################### # FIXME v3: Either make this API consistent with other Widget sub-classes # or make it a sub-class of HasTraits. def __init__(self, parent, **traits): """ Creates a new pager. """ # Base class constructor. super(PythonShell, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) # Set up to be notified whenever a Python statement is executed: self.control.handlers.append(self._on_command_executed) ########################################################################### # 'IPythonShell' interface. ########################################################################### def interpreter(self): return self.control.interp def execute_command(self, command, hidden=True): if hidden: self.control.hidden_push(command) else: # Replace the edit area text with command, then run it: self.control.Execute(command) def execute_file(self, path, hidden=True): # Note: The code in this function is largely ripped from IPython's # Magic.py, FakeModule.py, and iplib.py. filename = os.path.basename(path) # Run in a fresh, empty namespace main_mod = types.ModuleType('__main__') prog_ns = main_mod.__dict__ prog_ns['__file__'] = filename prog_ns['__nonzero__'] = lambda: True # Make sure that the running script gets a proper sys.argv as if it # were run from a system shell. save_argv = sys.argv sys.argv = [ filename ] # Make sure that the running script thinks it is the main module save_main = sys.modules['__main__'] sys.modules['__main__'] = main_mod # Redirect sys.std* to control or null old_stdin = sys.stdin old_stdout = sys.stdout old_stderr = sys.stderr if hidden: sys.stdin = sys.stdout = sys.stderr = _NullIO() else: sys.stdin = sys.stdout = sys.stderr = self.control # Execute the file try: if not hidden: self.control.clearCommand() self.control.write('# Executing "%s"\n' % path) if sys.platform == 'win32' and sys.version_info < (2,5,1): # Work around a bug in Python for Windows. For details, see: # http://projects.scipy.org/ipython/ipython/ticket/123 exec file(path) in prog_ns, prog_ns else: execfile(path, prog_ns, prog_ns) if not hidden: self.control.prompt() finally: # Ensure key global stuctures are restored sys.argv = save_argv sys.modules['__main__'] = save_main sys.stdin = old_stdin sys.stdout = old_stdout sys.stderr = old_stderr # Update the interpreter with the new namespace del prog_ns['__name__'] del prog_ns['__file__'] del prog_ns['__nonzero__'] self.interpreter().locals.update(prog_ns) ########################################################################### # 'IWidget' interface. ########################################################################### def _create_control(self, parent): shell = PyShell(parent, -1) # Listen for key press events. wx.EVT_CHAR(shell, self._wx_on_char) # Enable the shell as a drag and drop target. shell.SetDropTarget(PythonDropTarget(self)) return shell ########################################################################### # 'PythonDropTarget' handler interface. ########################################################################### def on_drop(self, x, y, obj, default_drag_result): """ Called when a drop occurs on the shell. """ # If we can't create a valid Python identifier for the name of an # object we use this instead. name = 'dragged' if hasattr(obj, 'name') \ and isinstance(obj.name, basestring) and len(obj.name) > 0: py_name = python_name(obj.name) # Make sure that the name is actually a valid Python identifier. try: if eval(py_name, {py_name : True}): name = py_name except: pass self.control.interp.locals[name] = obj self.control.run(name) self.control.SetFocus() # We always copy into the shell since we don't want the data # removed from the source return wx.DragCopy def on_drag_over(self, x, y, obj, default_drag_result): """ Always returns wx.DragCopy to indicate we will be doing a copy.""" return wx.DragCopy ########################################################################### # Private handler interface. ########################################################################### def _wx_on_char(self, event): """ Called whenever a change is made to the text of the document. """ # This was originally in the python_shell plugin, but is toolkit # specific. if event.m_altDown and event.m_keyCode == 317: zoom = self.shell.control.GetZoom() if zoom != 20: self.control.SetZoom(zoom+1) elif event.m_altDown and event.m_keyCode == 319: zoom = self.shell.control.GetZoom() if zoom != -10: self.control.SetZoom(zoom-1) self.key_pressed = KeyPressedEvent( alt_down = event.m_altDown == 1, control_down = event.m_controlDown == 1, shift_down = event.m_shiftDown == 1, key_code = event.m_keyCode, event = event ) # Give other event handlers a chance. event.Skip() class PyShell(PyShellBase): def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN, introText='', locals=None, InterpClass=None, *args, **kwds): self.handlers=[] # save a reference to the original raw_input() function since # wx.py.shell dosent reassign it back to the original on destruction self.raw_input = __builtin__.raw_input super(PyShell,self).__init__(parent, id, pos, size, style, introText, locals, InterpClass, *args, **kwds) def hidden_push(self, command): """ Send a command to the interpreter for execution without adding output to the display. """ wx.BeginBusyCursor() try: self.waiting = True self.more = self.interp.push(command) self.waiting = False if not self.more: self.addHistory(command.rstrip()) for handler in self.handlers: handler() finally: # This needs to be out here to make this works with # traits.util.refresh.refresh() wx.EndBusyCursor() def push(self, command): """Send command to the interpreter for execution.""" self.write(os.linesep) self.hidden_push(command) self.prompt() def Destroy(self): """Cleanup before destroying the control...namely, return std I/O and the raw_input() function back to their rightful owners! """ self.redirectStdout(False) self.redirectStderr(False) self.redirectStdin(False) __builtin__.raw_input = self.raw_input self.destroy() super(PyShellBase, self).Destroy() class _NullIO: """ A portable /dev/null for use with PythonShell.execute_file. """ def tell(self): return 0 def read(self, n = -1): return "" def readline(self, length = None): return "" def readlines(self): return [] def write(self, s): pass def writelines(self, list): pass def isatty(self): return 0 def flush(self): pass def close(self): pass def seek(self, pos, mode = 0): pass #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/window.py0000644000175100001440000001250511674463363020167 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import wx # Enthought library imports. from traits.api import Any, Event, implements, Property, Unicode from traits.api import Tuple # Local imports. from pyface.i_window import IWindow, MWindow from pyface.key_pressed_event import KeyPressedEvent from widget import Widget class Window(MWindow, Widget): """ The toolkit specific implementation of a Window. See the IWindow interface for the API documentation. """ implements(IWindow) #### 'IWindow' interface ################################################## position = Property(Tuple) size = Property(Tuple) title = Unicode #### Events ##### activated = Event closed = Event closing = Event deactivated = Event key_pressed = Event(KeyPressedEvent) opened = Event opening = Event #### Private interface #################################################### # Shadow trait for position. _position = Tuple((-1, -1)) # Shadow trait for size. _size = Tuple((-1, -1)) ########################################################################### # 'IWindow' interface. ########################################################################### def activate(self): self.control.Iconize(False) self.control.Raise() def show(self, visible): self.control.Show(visible) ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): wx.EVT_ACTIVATE(self.control, self._wx_on_activate) wx.EVT_CLOSE(self.control, self._wx_on_close) wx.EVT_SIZE(self.control, self._wx_on_control_size) wx.EVT_MOVE(self.control, self._wx_on_control_move) wx.EVT_CHAR(self.control, self._wx_on_char) ########################################################################### # Private interface. ########################################################################### def _get_position(self): """ Property getter for position. """ return self._position def _set_position(self, position): """ Property setter for position. """ if self.control is not None: self.control.SetPosition(position) old = self._position self._position = position self.trait_property_changed('position', old, position) def _get_size(self): """ Property getter for size. """ return self._size def _set_size(self, size): """ Property setter for size. """ if self.control is not None: self.control.SetSize(size) old = self._size self._size = size self.trait_property_changed('size', old, size) def _title_changed(self, title): """ Static trait change handler. """ if self.control is not None: self.control.SetTitle(title) #### wx event handlers #################################################### def _wx_on_activate(self, event): """ Called when the frame is being activated or deactivated. """ if event.GetActive(): self.activated = self else: self.deactivated = self def _wx_on_close(self, event): """ Called when the frame is being closed. """ self.close() def _wx_on_control_move(self, event): """ Called when the window is resized. """ # Get the real position and set the trait without performing # notification. # WXBUG - From the API documentation you would think that you could # call event.GetPosition directly, but that would be wrong. The pixel # reported by that call is the pixel just below the window menu and # just right of the Windows-drawn border. self._position = event.GetEventObject().GetPositionTuple() event.Skip() def _wx_on_control_size(self, event): """ Called when the window is resized. """ # Get the new size and set the shadow trait without performing # notification. wxsize = event.GetSize() self._size = (wxsize.GetWidth(), wxsize.GetHeight()) event.Skip() def _wx_on_char(self, event): """ Called when a key is pressed when the tree has focus. """ self.key_pressed = KeyPressedEvent( alt_down = event.m_altDown == 1, control_down = event.m_controlDown == 1, shift_down = event.m_shiftDown == 1, key_code = event.m_keyCode, event = event ) event.Skip() #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/dialog.py0000644000175100001440000001244411674463363020121 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ Enthought pyface package component """ # Major package imports. import sys import wx # Enthought library imports. from traits.api import Bool, Enum, implements, Int, Str, Unicode # Local imports. from pyface.i_dialog import IDialog, MDialog from pyface.constant import OK, CANCEL, YES, NO from window import Window # Map wx dialog related constants to the pyface equivalents. _RESULT_MAP = { wx.ID_OK : OK, wx.ID_CANCEL : CANCEL, wx.ID_YES : YES, wx.ID_NO : NO, wx.ID_CLOSE : CANCEL, # There seems to be a bug in wx.SingleChoiceDialog that allows it to return # 0 when it is closed via the window (closing it via the buttons works just # fine). 0 : CANCEL } class Dialog(MDialog, Window): """ The toolkit specific implementation of a Dialog. See the IDialog interface for the API documentation. """ implements(IDialog) #### 'IDialog' interface ################################################## cancel_label = Unicode help_id = Str help_label = Unicode ok_label = Unicode resizeable = Bool(True) return_code = Int(OK) style = Enum('modal', 'nonmodal') #### 'IWindow' interface ################################################## title = Unicode("Dialog") ########################################################################### # Protected 'IDialog' interface. ########################################################################### def _create_buttons(self, parent): sizer = wx.BoxSizer(wx.HORIZONTAL) # The 'OK' button. if self.ok_label: label = self.ok_label else: label = "OK" self._wx_ok = ok = wx.Button(parent, wx.ID_OK, label) ok.SetDefault() wx.EVT_BUTTON(parent, wx.ID_OK, self._wx_on_ok) sizer.Add(ok) # The 'Cancel' button. if self.cancel_label: label = self.cancel_label else: label = "Cancel" self._wx_cancel = cancel = wx.Button(parent, wx.ID_CANCEL, label) wx.EVT_BUTTON(parent, wx.ID_CANCEL, self._wx_on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) # The 'Help' button. if len(self.help_id) > 0: if self.help_label: label = self.help_label else: label = "Help" help = wx.Button(parent, wx.ID_HELP, label) wx.EVT_BUTTON(parent, wx.ID_HELP, self._wx_on_help) sizer.Add(help, 0, wx.LEFT, 10) return sizer def _create_contents(self, parent): sizer = wx.BoxSizer(wx.VERTICAL) parent.SetSizer(sizer) parent.SetAutoLayout(True) # The 'guts' of the dialog. dialog_area = self._create_dialog_area(parent) sizer.Add(dialog_area, 1, wx.EXPAND | wx.ALL, 5) # The buttons. buttons = self._create_buttons(parent) sizer.Add(buttons, 0, wx.ALIGN_RIGHT | wx.ALL, 5) # Resize the dialog to match the sizer's minimal size. if self.size != (-1, -1): parent.SetSize(self.size) else: sizer.Fit(parent) parent.CentreOnParent() def _create_dialog_area(self, parent): panel = wx.Panel(parent, -1) panel.SetBackgroundColour("red") panel.SetSize((100, 200)) return panel def _show_modal(self): if sys.platform == 'darwin': # Calling Show(False) is needed on the Mac for the modal dialog # to show up at all. self.control.Show(False) return _RESULT_MAP[self.control.ShowModal()] ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create_control(self, parent): style = wx.DEFAULT_DIALOG_STYLE | wx.CLIP_CHILDREN if self.resizeable: style |= wx.RESIZE_BORDER return wx.Dialog(parent, -1, self.title, style=style) #### wx event handlers #################################################### def _wx_on_ok(self, event): """ Called when the 'OK' button is pressed. """ self.return_code = OK # Let the default handler close the dialog appropriately. event.Skip() def _wx_on_cancel(self, event): """ Called when the 'Cancel' button is pressed. """ self.return_code = CANCEL # Let the default handler close the dialog appropriately. event.Skip() def _wx_on_help(self, event): """ Called when the 'Help' button is pressed. """ print 'Heeeeelllllllllllllpppppppppppppppppppp' #### EOF ###################################################################### pyface-4.1.0/pyface/ui/wx/progress_dialog.py0000644000175100001440000002436711674463363022054 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: Enthought, Inc. # #------------------------------------------------------------------------------ """ A simple progress bar intended to run in the UI thread """ import wx import time # Enthought library imports from traits.api import Instance, Enum, Int, Bool, Str, implements, Property # Local imports from widget import Widget from pyface.i_progress_dialog import IProgressDialog, MProgressDialog from window import Window class ProgressBar(Widget): """ A simple progress bar dialog intended to run in the UI thread """ parent = Instance(wx.Window) control = Instance(wx.Gauge) direction = Enum('horizontal', 'horizontal', 'vertical') def __init__(self, parent, minimum=0, maximum=100, direction='horizontal', size=(200, -1)): """ Constructs a progress bar which can be put into a panel, or optionaly, its own window """ self._max = maximum self.parent = parent style = wx.GA_HORIZONTAL if direction == "vertical": style = wx.GA_VERTICAL self.control = wx.Gauge(parent, -1, maximum, style=style, size=size) def update(self, value): """ Updates the progress bar to the desired value. """ if self._max == 0: self.control.Pulse() else: self.control.SetValue(value) self.control.Update() def _show(self): # Show the parent self.parent.Show() # Show the toolkit-specific control in the parent self.control.Show() class ProgressDialog(MProgressDialog, Window): """ A simple progress dialog window which allows itself to be updated """ progress_bar = Instance(ProgressBar) title = Str message = Property() min = Int max = Int margin = Int(5) can_cancel = Bool(False) show_time = Bool(False) show_percent = Bool(False) _user_cancelled = Bool(False) _message_text = Str() dialog_size = Instance(wx.Size) # Label for the 'cancel' button cancel_button_label = Str('Cancel') def __init__(self, *args, **kw): if 'message' in kw: self._message_text = kw.pop('message') # initialize the start time in case some tries updating # before open() is called self._start_time = 0 super(ProgressDialog, self).__init__( *args, **kw) def open(self): super(ProgressDialog, self).open() self._start_time = time.time() wx.GetApp().Yield(True) def close(self): if self.progress_bar is not None: self.progress_bar.destroy() self.progress_bar = None if self._message_control is not None: self._message_control = None super(ProgressDialog, self).close() def change_message(self, value): self._message_text = value if self._message_control is not None: self._message_control.SetLabel(value) self._message_control.Update() msg_control_size = self._message_control.GetSize() self.dialog_size.x = max(self.dialog_size.x, msg_control_size.x + 2*self.margin) self.control.SetClientSize(self.dialog_size) self.control.GetSizer().Layout() def update(self, value): """ updates the progress bar to the desired value. If the value is >= the maximum and the progress bar is not contained in another panel the parent window will be closed """ if self.progress_bar is None: # the developer is trying to update a progress bar which is already # done. Allow it, but do nothing return (False, False) self.progress_bar.update(value) # A bit hackish, but on Windows if another window sets focus, the # other window will come to the top, obscuring the progress dialog. # Only do this if the control is a top level window, so windows which # embed a progress dialog won't keep popping to the top # When we do embed the dialog, self.control may be None since the # embedding might just be grabbing the guts of the control. This happens # in the Traits UI ProgressEditor. if self.control is not None and self.control.IsTopLevel(): self.control.Raise() if self.max > 0: percent = (float(value) - self.min)/(self.max - self.min) if self.show_time and (percent != 0): current_time = time.time() elapsed = current_time - self._start_time estimated = elapsed/percent remaining = estimated - elapsed self._set_time_label(elapsed, self._elapsed_control) self._set_time_label(estimated, self._estimated_control) self._set_time_label(remaining, self._remaining_control) if self.show_percent: self._percent_control = "%3f" % ((percent * 100) % 1) if value >= self.max or self._user_cancelled: self.close() else: if self._user_cancelled: self.close() wx.GetApp().Yield(True) return (not self._user_cancelled, False) def _on_cancel(self, event): self._user_cancelled = True self.close() def _on_close(self, event): self._user_cancelled = True return self.close() def _set_time_label(self, value, control): hours = value / 3600 minutes = (value % 3600) / 60 seconds = value % 60 label = "%u:%02u:%02u" % (hours, minutes, seconds) control.SetLabel(label) def _get_message(self): return self._message_text def _create_buttons(self, dialog, parent_sizer): """ Creates the buttons. """ sizer = wx.BoxSizer(wx.HORIZONTAL) self._cancel = None if self.can_cancel == True: # 'Cancel' button. self._cancel = cancel = wx.Button(dialog, wx.ID_CANCEL, self.cancel_button_label) wx.EVT_BUTTON(dialog, wx.ID_CANCEL, self._on_cancel) sizer.Add(cancel, 0, wx.LEFT, 10) button_size = cancel.GetSize() self.dialog_size.x = max(self.dialog_size.x, button_size.x + 2*self.margin) self.dialog_size.y += button_size.y + 2*self.margin parent_sizer.Add(sizer, 0, wx.ALIGN_RIGHT | wx.ALL, self.margin) return def _create_label(self, dialog, parent_sizer, text): local_sizer = wx.BoxSizer() dummy = wx.StaticText(dialog, -1, text) label = wx.StaticText(dialog, -1, "unknown") local_sizer.Add(dummy, 1, wx.ALIGN_LEFT) local_sizer.Add(label, 1, wx.ALIGN_LEFT | wx.ALIGN_RIGHT, self.margin) parent_sizer.Add(local_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, self.margin) return label def _create_gauge(self, dialog, parent_sizer): self.progress_bar = ProgressBar(dialog, self.min, self.max) parent_sizer.Add(self.progress_bar.control, 0, wx.CENTER | wx.ALL, self.margin) horiz_spacer = 50 progress_bar_size = self.progress_bar.control.GetSize() self.dialog_size.x = max(self.dialog_size.x, progress_bar_size.x + 2*self.margin + horiz_spacer) self.dialog_size.y += progress_bar_size.y + 2*self.margin return def _create_message(self, dialog, parent_sizer): self._message_control = wx.StaticText(dialog, -1, self.message) parent_sizer.Add(self._message_control, 0, wx.LEFT | wx.TOP, self.margin) msg_control_size = self._message_control.GetSize() self.dialog_size.x = max(self.dialog_size.x, msg_control_size.x + 2*self.margin) self.dialog_size.y += msg_control_size.y + 2*self.margin return def _create_percent(self, dialog, parent_sizer): if not self.show_percent: return raise NotImplementedError def _create_timer(self, dialog, parent_sizer): if not self.show_time: return self._elapsed_control = self._create_label(dialog, parent_sizer, "Elapsed time : ") self._estimated_control = self._create_label(dialog, parent_sizer, "Estimated time : ") self._remaining_control = self._create_label(dialog, parent_sizer, "Remaining time : ") elapsed_size = self._elapsed_control.GetSize() estimated_size = self._estimated_control.GetSize() remaining_size = self._remaining_control.GetSize() timer_size = wx.Size() timer_size.x = max(elapsed_size.x, estimated_size.x, remaining_size.x) timer_size.y = elapsed_size.y + estimated_size.y + remaining_size.y self.dialog_size.x = max(self.dialog_size.x, timer_size.x + 2*self.margin) self.dialog_size.y += timer_size.y + 2*self.margin return def _create_control(self, parent): """ Creates the window contents. This method is intended to be overridden if necessary. By default we just create an empty panel. """ style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_NO_WINDOW_MENU | wx.CLIP_CHILDREN dialog = wx.Frame(parent, -1, self.title, style=style, size=self.size, pos=self.position) sizer = wx.BoxSizer(wx.VERTICAL) dialog.SetSizer(sizer) dialog.SetAutoLayout(True) dialog.SetBackgroundColour(wx.NullColor) self.dialog_size = wx.Size() # The 'guts' of the dialog. self._create_message(dialog, sizer) self._create_gauge(dialog, sizer) self._create_percent(dialog, sizer) self._create_timer(dialog, sizer) self._create_buttons(dialog, sizer) dialog.SetClientSize(self.dialog_size) dialog.CentreOnParent() return dialog pyface-4.1.0/pyface/ui/wx/clipboard.py0000644000175100001440000001241711674463363020621 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! #------------------------------------------------------------------------------ # Standard library imports from cStringIO import StringIO from cPickle import dumps, load, loads # System library imports import wx # ETS imports from traits.api import implements from pyface.i_clipboard import IClipboard, BaseClipboard # Data formats PythonObjectFormat = wx.CustomDataFormat('PythonObject') TextFormat = wx.DataFormat(wx.DF_TEXT) FileFormat = wx.DataFormat(wx.DF_FILENAME) # Shortcuts cb = wx.TheClipboard class Clipboard(BaseClipboard): implements(IClipboard) #--------------------------------------------------------------------------- # 'data' property methods: #--------------------------------------------------------------------------- def _get_has_data(self): result = False if cb.Open(): result = (cb.IsSupported(TextFormat) or cb.IsSupported(FileFormat) or cb.IsSupported(PythonObjectFormat)) cb.Close() return result #--------------------------------------------------------------------------- # 'object_data' property methods: #--------------------------------------------------------------------------- def _get_object_data(self): result = None if cb.Open(): try: if cb.IsSupported(PythonObjectFormat): cdo = wx.CustomDataObject(PythonObjectFormat) if cb.GetData(cdo): file = StringIO(cdo.GetData()) klass = load(file) result = load(file) finally: cb.Close() return result def _set_object_data(self, data): if cb.Open(): try: cdo = wx.CustomDataObject(PythonObjectFormat) cdo.SetData(dumps(data.__class__) + dumps(data)) # fixme: There seem to be cases where the '-1' value creates # pickles that can't be unpickled (e.g. some TraitDictObject's) #cdo.SetData(dumps(data, -1)) cb.SetData(cdo) finally: cb.Close() cb.Flush() def _get_has_object_data(self): return self._has_this_data(PythonObjectFormat) def _get_object_type(self): result = '' if cb.Open(): try: if cb.IsSupported(PythonObjectFormat): cdo = wx.CustomDataObject(PythonObjectFormat) if cb.GetData(cdo): try: # We may not be able to load the required class: result = loads(cdo.GetData()) except: pass finally: cb.Close() return result #--------------------------------------------------------------------------- # 'text_data' property methods: #--------------------------------------------------------------------------- def _get_text_data(self): result = '' if cb.Open(): if cb.IsSupported(TextFormat): tdo = wx.TextDataObject() if cb.GetData(tdo): result = tdo.GetText() cb.Close() return result def _set_text_data(self, data): if cb.Open(): cb.SetData(wx.TextDataObject(str(data))) cb.Close() cb.Flush() def _get_has_text_data(self): return self._has_this_data(TextFormat) #--------------------------------------------------------------------------- # 'file_data' property methods: #--------------------------------------------------------------------------- def _get_file_data(self): result = [] if cb.Open(): if cb.IsSupported(FileFormat): tfo = wx.FileDataObject() if cb.GetData(tfo): result = tfo.GetFilenames() cb.Close() return result def _set_file_data(self, data): if cb.Open(): tfo = wx.FileDataObject() if isinstance(data, basestring): tfo.AddFile(data) else: for filename in data: tfo.AddFile(filename) cb.SetData(tfo) cb.Close() cb.Flush() def _get_has_file_data(self): return self._has_this_data(FileFormat) #--------------------------------------------------------------------------- # Private helper methods: #--------------------------------------------------------------------------- def _has_this_data(self, format): result = False if cb.Open(): result = cb.IsSupported(format) cb.Close() return result pyface-4.1.0/pyface/ui/null/0000755000175100001440000000000011674463363016617 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/null/image_resource.py0000644000175100001440000000476311674463363022174 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. import os # Enthought library imports. from traits.api import Any, HasTraits, implements, List, Property from traits.api import Unicode # Local imports. from pyface.i_image_resource import IImageResource, MImageResource class ImageResource(MImageResource, HasTraits): """ The 'null' toolkit specific implementation of an ImageResource. See the IImageResource interface for the API documentation. """ implements(IImageResource) #### Private interface #################################################### # The resource manager reference for the image. _ref = Any #### 'ImageResource' interface ############################################ absolute_path = Property(Unicode) name = Unicode search_path = List ########################################################################### # 'ImageResource' interface. ########################################################################### def create_bitmap(self, size=None): return self.create_image(size) def create_icon(self, size=None): return self.create_image(size) ########################################################################### # Private interface. ########################################################################### def _get_absolute_path(self): # FIXME: This doesn't quite work with the new notion of image size. We # should find out who is actually using this trait, and for what! # (AboutDialog uses it to include the path name in some HTML.) ref = self._get_ref() if ref is not None: absolute_path = os.path.abspath(self._ref.filename) else: absolute_path = self._get_image_not_found().absolute_path return absolute_path #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/init.py0000644000175100001440000000137011674463363020135 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt #------------------------------------------------------------------------------ """ Initialize this backend. """ # There is nothing for us to initialize, but the toolkit switching code depends # on the existence of this module. #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/resource_manager.py0000644000175100001440000000272511674463363022520 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Enthought library imports. from pyface.resource.api import ResourceFactory class PyfaceResourceFactory(ResourceFactory): """ The implementation of a shared resource manager. """ ########################################################################### # 'ResourceFactory' toolkit interface. ########################################################################### def image_from_file(self, filename): """ Creates an image from the data in the specified filename. """ # Just return the data as a string for now. f = open(filename, 'rb') data = f.read() f.close() return data def image_from_data(self, data): """ Creates an image from the specified data. """ return data #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/widget.py0000644000175100001440000000257711674463363020467 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, HasTraits, implements # Local imports. from pyface.i_widget import IWidget, MWidget class Widget(MWidget, HasTraits): """ The toolkit specific implementation of a Widget. See the IWidget interface for the API documentation. """ implements(IWidget) #### 'IWidget' interface ################################################## control = Any parent = Any ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): self.control = None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/0000755000175100001440000000000011674463363020074 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/null/action/action_item.py0000644000175100001440000001030711674463363022742 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enth373ought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The 'null' specific implementations of the action manager internal classes. """ # Enthought library imports. from traits.api import Any, Bool, HasTraits class _MenuItem(HasTraits): """ A menu item representation of an action item. """ #### '_MenuItem' interface ################################################ # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the menu item is not part of such # a group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, menu, item, controller): """ Creates a new menu item for an action item. """ self.item = item self.control_id = 1 self.control = None if controller is not None: self.controller = controller controller.add_to_menu(self) return class _Tool(HasTraits): """ A tool bar tool representation of an action item. """ #### '_Tool' interface #################################################### # Is the item checked? checked = Bool(False) # A controller object we delegate taking actions through (if any). controller = Any # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) # The radio group we are part of (None if the tool is not part of such a # group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, tool_bar, image_cache, item, controller, show_labels): """ Creates a new tool bar tool for an action item. """ self.item = item self.tool_bar = tool_bar # Create an appropriate tool depending on the style of the action. action = self.item.action # If the action has an image then convert it to a bitmap (as required # by the toolbar). if action.image is not None: image = action.image.create_image() path = action.image.absolute_path bmp = image_cache.get_bitmap(path) else: from pyface.api import ImageResource image = ImageResource('foo') bmp = image.create_bitmap() self.control_id = 1 self.control = None if controller is not None: self.controller = controller controller.add_to_toolbar(self) return class _PaletteTool(HasTraits): """ A tool palette representation of an action item. """ #### '_PaletteTool' interface ############################################# # The radio group we are part of (None if the tool is not part of such a # group). group = Any ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tool_palette, image_cache, item, show_labels): """ Creates a new tool palette tool for an action item. """ self.item = item self.tool_palette = tool_palette #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/status_bar_manager.py0000644000175100001440000000522211674463363024310 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A status bar manager realizes itself in a status bar control. """ # Enthought library imports. from traits.api import Any, HasTraits, List, Property, Str, Unicode class StatusBarManager(HasTraits): """ A status bar manager realizes itself in a status bar control. """ # The manager's unique identifier (if it has one). id = Str # The message displayed in the first field of the status bar. message = Property # The messages to be displayed in the status bar fields. messages = List(Unicode) # The toolkit-specific control that represents the status bar. status_bar = Any ########################################################################### # 'StatusBarManager' interface. ########################################################################### def create_status_bar(self, parent): """ Creates a status bar. """ return self.status_bar ########################################################################### # Property handlers. ########################################################################### def _get_message(self): if len(self.messages) > 0: message = self.messages[0] else: message = '' return message def _set_message(self, value): if len(self.messages) > 0: old = self.messages[0] self.messages[0] = value else: old = '' self.messages.append(old) self.trait_property_changed('message', old, value) return ########################################################################### # Trait event handlers. ########################################################################### def _messages_changed(self): """ Sets the text displayed on the status bar. """ return def _messages_items_changed(self): """ Sets the text displayed on the status bar. """ return #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/tool_palette_manager.py0000644000175100001440000000471011674463363024635 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tool bar manager realizes itself in a tool palette control. """ # Enthought library imports. from traits.api import Any, Bool, Enum, Instance, Tuple # Local imports. from pyface.image_cache import ImageCache from pyface.action.action_manager import ActionManager from tool_palette import ToolPalette class ToolPaletteManager(ActionManager): """ A tool bar manager realizes itself in a tool palette bar control. """ #### 'ToolPaletteManager' interface ####################################### # The size of tool images (width, height). image_size = Tuple((16, 16)) # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) #### Private interface #################################################### # Cache of tool images (scaled to the appropriate size). _image_cache = Instance(ImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new tool bar manager. """ # Base class contructor. super(ToolPaletteManager, self).__init__(*args, **traits) # An image cache to make sure that we only load each image used in the # tool bar exactly once. self._image_cache = ImageCache(self.image_size[0], self.image_size[1]) return ########################################################################### # 'ToolPaletteManager' interface. ########################################################################### def create_tool_palette(self, parent, controller=None): """ Creates a tool bar. """ return None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/tool_palette.py0000644000175100001440000000615711674463363023152 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ View of an ActionManager drawn as a rectangle of buttons. """ from pyface.widget import Widget from traits.api import Bool, Dict, Int, List, Tuple class ToolPalette(Widget): tools = List id_tool_map = Dict tool_id_to_button_map = Dict button_size = Tuple((25, 25), Int, Int) is_realized = Bool(False) tool_listeners = Dict # Maps a button id to its tool id. button_tool_map = Dict ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new tool palette. """ # Base class constructor. super(ToolPalette, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) return ########################################################################### # ToolPalette interface. ########################################################################### def add_tool(self, label, bmp, kind, tooltip, longtip): """ Add a tool with the specified properties to the palette. Return an id that can be used to reference this tool in the future. """ return 1 def toggle_tool(self, id, checked): """ Toggle the tool identified by 'id' to the 'checked' state. If the button is a toggle or radio button, the button will be checked if the 'checked' parameter is True; unchecked otherwise. If the button is a standard button, this method is a NOP. """ return def enable_tool(self, id, enabled): """ Enable or disable the tool identified by 'id'. """ return def on_tool_event(self, id, callback): """ Register a callback for events on the tool identified by 'id'. """ return def realize(self): """ Realize the control so that it can be displayed. """ return def get_tool_state(self, id): """ Get the toggle state of the tool identified by 'id'. """ state = 0 return state ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): return None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/menu_bar_manager.py0000644000175100001440000000316111674463363023731 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The 'null' backend specific implementation of a menu bar manager. """ # Local imports. from pyface.action.action_manager import ActionManager class MenuBarManager(ActionManager): """ A menu bar manager realizes itself in errr, a menu bar control. """ ########################################################################### # 'MenuBarManager' interface. ########################################################################### def create_menu_bar(self, parent, controller=None): """ Creates a menu bar representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller return None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/__init__.py0000644000175100001440000000033711674463363022210 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007 by Enthought, Inc. # All rights reserved. #------------------------------------------------------------------------------ pyface-4.1.0/pyface/ui/null/action/menu_manager.py0000644000175100001440000000477311674463363023117 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The 'null' backend specific implementation of a menu manager. """ # Enthought library imports. from traits.api import Unicode # Local imports. from pyface.action.action_manager import ActionManager from pyface.action.action_manager_item import ActionManagerItem from pyface.action.group import Group class MenuManager(ActionManager, ActionManagerItem): """ A menu manager realizes itself in a menu control. This could be a sub-menu or a context (popup) menu. """ #### 'MenuManager' interface ############################################## # The menu manager's name (if the manager is a sub-menu, this is what its # label will be). name = Unicode ########################################################################### # 'MenuManager' interface. ########################################################################### def create_menu(self, parent, controller=None): """ Creates a menu representation of the manager. """ # If a controller is required it can either be set as a trait on the # menu manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller return None ########################################################################### # 'ActionManagerItem' interface. ########################################################################### def add_to_menu(self, parent, menu, controller): """ Adds the item to a menu. """ return def add_to_toolbar(self, parent, tool_bar, image_cache, controller): """ Adds the item to a tool bar. """ raise ValueError("Cannot add a menu manager to a toolbar.") #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/action/tool_bar_manager.py0000644000175100001440000000557711674463363023757 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The 'null' backend specific implementation of the tool bar manager. """ # Enthought library imports. from traits.api import Bool, Enum, Instance, Tuple # Local imports. from pyface.image_cache import ImageCache from pyface.action.action_manager import ActionManager class ToolBarManager(ActionManager): """ A tool bar manager realizes itself in errr, a tool bar control. """ #### 'ToolBarManager' interface ########################################### # The size of tool images (width, height). image_size = Tuple((16, 16)) # The orientation of the toolbar. orientation = Enum('horizontal', 'vertical') # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) # Should we display the horizontal divider? show_divider = Bool(True) #### Private interface #################################################### # Cache of tool images (scaled to the appropriate size). _image_cache = Instance(ImageCache) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new tool bar manager. """ # Base class contructor. super(ToolBarManager, self).__init__(*args, **traits) # An image cache to make sure that we only load each image used in the # tool bar exactly once. self._image_cache = ImageCache(self.image_size[0], self.image_size[1]) return ########################################################################### # 'ToolBarManager' interface. ########################################################################### def create_tool_bar(self, parent, controller=None): """ Creates a tool bar. """ # If a controller is required it can either be set as a trait on the # tool bar manager (the trait is part of the 'ActionManager' API), or # passed in here (if one is passed in here it takes precedence over the # trait). if controller is None: controller = self.controller return None #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/__init__.py0000644000175100001440000000000011674463363020716 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/ui/null/window.py0000644000175100001440000000566411674463363020513 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Enthought library imports. from traits.api import Any, Event, implements, Property, Unicode from traits.api import Tuple # Local imports. from pyface.i_window import IWindow, MWindow from pyface.key_pressed_event import KeyPressedEvent from widget import Widget class Window(MWindow, Widget): """ The toolkit specific implementation of a Window. See the IWindow interface for the API documentation. """ implements(IWindow) #### 'IWindow' interface ################################################## position = Property(Tuple) size = Property(Tuple) title = Unicode #### Events ##### activated = Event closed = Event closing = Event deactivated = Event key_pressed = Event(KeyPressedEvent) opened = Event opening = Event #### Private interface #################################################### # Shadow trait for position. _position = Tuple((-1, -1)) # Shadow trait for size. _size = Tuple((-1, -1)) ########################################################################### # 'IWindow' interface. ########################################################################### def show(self, visible): pass ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _add_event_listeners(self): pass ########################################################################### # Private interface. ########################################################################### def _get_position(self): """ Property getter for position. """ return self._position def _set_position(self, position): """ Property setter for position. """ old = self._position self._position = position self.trait_property_changed('position', old, position) def _get_size(self): """ Property getter for size. """ return self._size def _set_size(self, size): """ Property setter for size. """ old = self._size self._size = size self.trait_property_changed('size', old, size) #### EOF ###################################################################### pyface-4.1.0/pyface/ui/null/clipboard.py0000644000175100001440000000423011674463363021127 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Evan Patterson # Date: 06/29/09 #------------------------------------------------------------------------------ # ETS imports from traits.api import implements from pyface.i_clipboard import IClipboard, BaseClipboard class Clipboard(BaseClipboard): """ A dummy clipboard implementationf for the null backend. """ implements(IClipboard) #--------------------------------------------------------------------------- # 'data' property methods: #--------------------------------------------------------------------------- def _get_has_data(self): return False #--------------------------------------------------------------------------- # 'object_data' property methods: #--------------------------------------------------------------------------- def _get_object_data(self): pass def _set_object_data(self, data): pass def _get_has_object_data(self): return False def _get_object_type(self): return '' #--------------------------------------------------------------------------- # 'text_data' property methods: #--------------------------------------------------------------------------- def _get_text_data(self): return False def _set_text_data(self, data): pass def _get_has_text_data(self): pass #--------------------------------------------------------------------------- # 'file_data' property methods: #--------------------------------------------------------------------------- def _get_file_data(self): pass def _set_file_data(self, data): pass def _get_has_file_data (self): return False pyface-4.1.0/pyface/ui/__init__.py0000644000175100001440000000000011674463363017744 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/images/0000755000175100001440000000000011674463363016475 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/images/panel_gradient_over.png0000644000175100001440000000405411674463363023215 0ustar ischnellusers00000000000000PNG  IHDRbgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxl 0 ϰF` jӦJiΎ`H`V=i!q|f5*cc;us企m-_N.-2ah5H M=)&YB41т׮V} ȸa4 o:XB(X2Ѫ{K Ök'Wj;o$aJx2EgS`ʊ\|7mHS]3sx3DYH!Q&JyytѣcEr8vgO zЭjb6$eHZ*Dy`!HH#G} ( )gLHAn-\7%h vUHH$8r$)*g#X.\"j2~-{W:l؁Z34qDVTm!uDna"R:(>q=[,+e,+mU:TySPQFK،ݥYٵMFvH(S1ρD .(\82~Yt+3ew@ѧl/ǡ$onգG, `J i݂1<ԹB ) úk24ghl7;~ C7TP&M }$J<'aHf!*師8`~ K 19MkW*h1aVcj,&F2֚i Af]u6`Som _On 5Ltjl<}cCߧڜtwENfҶӰ  b*NkKa%V̥AO2_Ex.X,|:,5ڮ9 c 8Wx$zTmS!S\]В-V0h[$Dۋi43^c.v [#^afݮ=P lnǶKMZq8q\ B>[@*&#C ] $ud @!Ce5ؙE>=XF7et{<6=./>aDx֕ZGKaF=Z/#qT$FϮ&" rbPGn^!ՂALCa_4&BΆbbDo-IENDB`pyface-4.1.0/pyface/images/splash.jpg0000644000175100001440000007022411674463363020476 0ustar ischnellusers00000000000000JFIFC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;("=!1A"Qaq2#B3RbCr$4s$!1AQ"a2qB ?Ŧ8MOR:UjxZڼwrH1Z*@d J1 sV+w'XLwPKj$Y ySZi>.zHV~ y9m GQ_$AF;c88+'~M`F:jL#"@#TϽVF`ԗqXd1gBī?Q֌ЬRPrDd7 sz[eu%,lA_xi⹉%YG'Ϛ)$#C]pHGfu),7EmNu>RpBQ@s&ܮDGC[nxm Gce.M@=L4)؟^Ѧh˂=9𕞋@J\[%!ZM<lvM5I8tuӥnۏd $vY>ęk6"W@{_hϤYseUMv4-h%T#!Rn9Hp)bj/2ˁQEiH9553ikq\f9$W8ZK7`.4y/}Ǘ(= Z3XsQha1F701jq.k 򘬋Cuq!{`C3ځ+&mb M=j;gzGdZx2 %{-nr߉,jZrԚFlnqT rګJR1Eᰒܱ ֑ZJmSRUmj2pw8K${ugz ',1FɦVɈd2c:Q2E$06}Y)AJ玼œYʧ #8sߥ-3WE3iW>Z#ʀI3FzT=+c# 3 ?ƾuzjڔ'XO8*!~ة<}k*Ec qm#2Dlּ qdQ6;#炭8棴 L!Y_lcYx9+ cr/CZ'6:12H;dގ~Iӥ^y.v~u.0G^ע,l2yǰX/" D*X1w8.Ǡ$ӰEψc!LFSGR8B*%l@TBƇ}6ESd!'cjxK RnLD8X$B%Uc3 ls晴/Se%G=;ĜuYe4+/Kg ޡu,=cQжp>$ ,7lnѰ5X.+2GҫʃqȪe1.rݫ>KK =( '2z#?TWȌ9n+T MHt"|g'ְk(vJ3)m=h#WٚGaU_WJ&K}5fMCyH{fp{Ajsu]E$`a*uRI߸I9_t'5t4jrJjW[.k9"D>|nI:VAdF+uu?Jms\ŗ<5Dቅ7ku#OszIC\h:x3jz͝7P_Qcovm-\7n<˙o5䥼a@J`ӥ$1,ЌZt`y3Ki')4jV=#sɵ=5՝Q2S`}\_IVUX?rLqNwI[ܩvqqc3qg@? @5OZ iY Ao6g|ێ;BE”'QJ7M*<*oC4 :9S+fOߌGZy߄Pp7O.ּw|"hƴ0^ZG 3&+z})F#0:H b7Җi!rcHwS%a@2/|dԿQg lYQVXZ]^YEiͮ"BѰP O)E}UFoMT`V?UZzv7Ḩ[ȿy$<փLE(c|tKYo)e`=RTWi7 ђTҽ#DP!{T1?1(85/-3bpqȭ߇f70FfAF+Z'\sl8!#YKܱ ?j5)`fKdc y=鉷ʂ3;$斃b+=GZqFO9=9QP_jWnKfq6%P{*LhRubd\ Xn!ODgMj<19aѝ6{Zʼg c;Mz76ְ .ZFa*g抍zM<>PcmIg.c+Ck٠Eô0+=F)UIaӜR)GV4ELo?8?SMb wLct,( !`#>#PmBU@WːqiR\tak+]][ks?T>>)hNN1'jS,3B$]ht+m ;- o@c4hQyn34KQiw+z8j*COHI76r mqJѨBİ!q֬5;tŎA("1d2dZMi 9Y ab[-%X'Pk4++]akRr7,ۉ?)u17 j[7T-k@ј\tKțZf3!T9` jӥK0L 1h]OoRvF&Z- N1g#h<߾{W<2VeĬ3"8ݒ(Xb(!|{sylfx?WF AT$wsDIG"u<*;Wӵ(cw2O:$(+7K&s@]'SkqqW\hwY2jٙ7]s~բկ]`) i]/$ sGּP#_*F_, 'fY7P}2D?#}q)|CsڪƇ*D0?L)5P@ Ur4*Y7Y@ +[Jx =JH9Z9k)*6p]ބɔ 4sF5!Hta}: HLáN[Rܵgw(.)YgxlK+x"HI%T\z2_xl2B-.|9F|!f-zVVtk5+E&xj ̓ сLv4kSQ֪KQ i%r2o Wwqޙph(҆=+)F= uم mh犻UԎZEIe `6n$ֺTnkb#̍GcSHn;歳9 gx*4LHm=# px=*9lϑ~ Ѓ⾍(ne2qې|I  9$@X= `{A@OsҲ}0;zGJ}08cYfrN#%<#5X]HpUIڱZ˨jClO$Wy`z>Z FA1nKT[cl삘6=4Jrqjcl*\j[Ghy;j•=R\HU@PYwr UM s,*݀E/52 UAd%p=I=(%@\CB0 *qy,!,}DҺll< brsT\$@z:3tsOJœ%Rx ]!MVe;ՃB]RO̤)bP>R~$V m.qZ˅fb*3(3׌"MA9!v\8QgDYI`vgc{d{gwirгHXĀ?{PeԮV1! xSY m9%m,%Zv<~jYj0@0&y{F;,[^EGN+Xڵ++ HxPW|wyu؏~PijnޫЂMQGm-[r+ ';Z KetXTiKb#pIQ;r:לco"rKHRyNJ I SI- )ǡ=0$Ki⻶e t"ȕḂr9J @}Cr@WvmI}RcW{rq44dB24go?ڂ~K5-R[(#?ǏXx@jZbkIlG$vϽ& #][jQғZ% ZdSD4rƭGUy4K,NdqX>As-02;0ZE&и 7S/"'.ș Pe=)] <)wtamzx#Hw4Xk6SOpJM5e]9)q%DbzkcFr1ip`r?Z< CЂ3?Jcf}`-~~(33\}6Ϟ9@Id`=*_&@=;SY/7ZM`LtjA N]YϰslnA+Aqz 6I"G¢3 z =i 6OQU  2Tƥy&}Hr9A\+ȡ@$'8"{F' u'ہ&$=|YRpHZ!cyK5 Ӡ{)NqGHUν/YXc^jA0Nyo{fa,glb"laBek J5Z [vGnI* XLLa'<8vn[-[os)AD-G~k\96'MuaRn$/?4[Y+& sK ѰYFGei^p4dKY-P4)`6 m4%bNI^MDDaLZZ|$K{ina#y恸ůo\OaZ$1a VW&KfN]oQv˙7[Fvaq$R^fK ʂ AL=g/!ѧ}ͼvp]h˛?P=#[dt|OIIdr0G_ݿZ3hH%4_m*MG] ӻY !>j?sȫ<БJ (=)&ZTAT=ARsӭQ!pr3*4Bїg*=ѓާ6X2\t}No v8# &x>Ҹ dIsx@]6-F8p{۰?J8:1{,I,NsTu1'jlڃF[+vROjk_-c'Vٿ7ڮ07r; _4 /mld): .*=՜sP˹U03aY%$'''ll,6;+×vojT`lN\d]]B*b8#?N s"lu'X6XU6Jd).؟a*Zg]lHhad+cW=s%|̫֒@:BUrLTIbe1w5}}+YFcj(I4]gV͵m,8gVH72[Kٰl㡢+uvNzɡoM40@88)RqکTC!VXփȗ"%mQMv6<s܎zXQ9SZYcADVn,#Tszڱ'Z/ sHݏamn4p biHqK`ƯR f*8=a:tѫze~*pUFXUHDFI=*+}з@B:B01`o01ym+v1a=Z"pv3)qlW$ A4:` y'#SKWS^~PmmMm>ReB$7^b~zz>fB QҳM<`a8=V{jm';ժT=$'<+CWČ>T6Q9@=c$_f 5fFQCќ[Yn|ߜR BO_I^NqQފяAU,ޢ ZYm*}kag_*81FDl$( .NFM/ 4ꊪN(K.IS+m VLua"OvmPryM8@?Kկ5+iR6qz;I fեӬ#W%真`84mb[qL/oFQMEEG0sfE[`u=Buh텱|2-oqzOfEDRrZ_{spܸ!7Eta9|E)@OJ6*99c5-Ih[FdˍޮwcYȭ&]D {gS70h`ux.yU1 3'edkȕzaRnT`~Ny旴XؿԎ24sGXQ!aG>)tTѴIWv槧4w9 ~@:/ۊYC`$Q13FnRm $  G;ISiJsw1=if@ac*9q4vZC@;բ.AĠ~e\=Y;ۅTbKGZIkW6>%e|sqj+%Pv-}=٤W60!9}.Jv-[SQ udFФz)բ2^hU}={A$֓,*LֲUKh4 Ksr6@㟵7v?4teK v$ 飶JvŤrPTZ6 ̥ҲCSD??JZ#=]L#AbFw*OaA}O]q69jمbIT6e:_[iwmC~W-.Z6$w_՗F92eQb/wNΈz:[=;Nn›%򷡈ݞVSODW{Xm`:{R}~mWMH O|o)f̒3*$\jM*v@+ ا⭹Lok @,QZ ombNA]G֓l$rNFR@Ü:3\-zzRV y"TH*xbZiL.!X@*mZ}6x{Ty`%&\ k=Ҩ>[lW52"`N p{A*񥦻$6 ۨIJ=g-e݋xe;]_=5d9\^RnDWC~J,/K;.)14gaY* @0 4G+"NN-ڋS3I[⬧TZt"P28?|x- iB?eRKyR?sRHucIeS<`, 8-f ,NKU1e4̎p =+P֍u;k]L#҉xv 2##`=!?hՃ;r; -Ȣy;s3T]X4g xgZ&G^jb4c!-MMA'XW'~Pe"H)nS2Y%!W'hhT8HSl,l'-3 RZ:I#>Qa,eK|VJgR""rlCwZs3vn)ͅp@D=9 WJ6B{;]NJdȥwZe핱̣רSdęsb.@~5zfE5I#^=Unn2љ Fisnvrwgr9R^DVjcrRrFz5/m";ROsQ`83M h0\eIf?+<ɥXΙ'Dd#{*0_k$^qlDmϽFMd2I{}h-0 fwV$`mV^Ĭ 7oL&( S$ӕ,;5v?:ҲW$dqNdeC v%&MwycCzԥ&MՔ-d:nUTJ[MHpOC% )+9A #bYO?)$ڹQ ]waI]ivע9n'5GtAtInnJbx۽갂FrQ8r16zQSNiӤ7LFH#ھcNڡǔ۷#GnMc-O].i^'c 0={{Jꣿҩ$g䐿Ὲ,Ҷ1U6)߷g98Vx&V(hY\NH#OZ7d }3e gPRec'ڪ.I#S+ݍʐ1!j[py>63֪%۞Bd;IfsE&F$.r%Hsf:7 /[d%'c1yX1wU0A`J۔&o4Xc2&=ȭޜC3ck@;E*vj<gOVjnI k|#{UQ<7v65F8a X3Ҧ뀲땗S[gUfbI9lg)]52N`@ۣsJ0 zcBY 8?]ցs-–i  q{R9)\. 69js&6Ka7{,2V"¶]t +5b7/ >)ƻRM$$HJJиC9R)gvf|`~^ P3z7. q^pq A{WxI-cOK-:chlq Q݄W&x J(-QFڤ]YŪ4ߪ9 h1wmo+BI# ݕV2$ji4\,l/ |R- .[5r=Mh]Y@fY܉'POpi.3ިD$[vVo[[+YA-pqe73H#pWۼҿ˯k-fͼdUZ13^NJcl[^OxvDӉ,o+'YUb:es-d5}MWdI-{C zWQqn:0uUR;vT8~ᚱ/ojҧ4Eo,dg:WVI>Trp9:W֭#E*sЊj0 [,(5gm0zXS1zRł{~ERlt :s]\HQ.IpxqAϵZiW=Gp۷%~GJ0FppGڅEI#q:UeT''k5iV1[4{-.: L Iezmnm 绍cN R|Ƨ,O4ʦиR } TRH៊1Ïcֆ, rE\']$& ۖ]JxVv%>;[hOJY^MsR4ʼn>ֵӕQ@<89X @Kw"DoRz+o]nV9[Z{F}"?3'MquyBnTcGCiSJ4Q(Lo4F^UjxC=~Z~ݤ[N帏E2RaTA@ֆ($6SH|; y`qǖZȢv.'U#ypa#ƂI ?h=Pt]qGVqjd3IM {B#'?qdprjfh)Y0qj]vU4LR8E#KCJq@s׃ԺdA+:f%hQbGtA1BHEN QS?)ܫ%w U#~԰uu-y,Hy5zEő"up  ua`AjZu‰$GfO}x<7iw7əWQ:gިZ0g4et |:WWw3Ϟ&K2IvG jQF/o_Jy`I,}z%ReaҖZm%QHw08j7Mm4PmbBwa[_ Y鷯qg Qi#l1SW)Mlp(aI2ER>)mJ㴵&$ɘwlzIq"HPс VCOA7R}ڦl L=0^·y8 Z 62z|9.# MXlmnXH$M>e|CucW4K!HAۦOȯF庉D p_Ȩ Z ;RFUaPm*,LJcnL+`*:ſYF铟'?Vas7RU2_ަC37mɑ&ƱGvNܪ_M|_^0?f@MQnRD}ʡ8X-#KcAE$G1MO—-DIg}jN7ڃϧ=Zq Fz <⺍# c_Bry?'|QF~w(m01lLWБvRbI2En7] O`ы)*2xR)'8aA=TL2u|)wm Vz~y$ V\#ب:dd'( J)hmV?z!Igg~6#U< h5 6I㊽GK,?v2#F'q"<@$gҰ8˰bGk%5L)9 j$hQE Mh.Kk(#)|Q4(nJA^4oeLZ?7ʦYYhIOeYlXYĞ )7-`~D1ȍ|fhjk-)A])K +r:_*gkb˟j-F{-lo!љ RK%v|LWyfW6sIgw08Ps?U&n-84[_4hnj噋vg^H=f*'TVƳโ9H泯qyvXs^<mk-z$?i:{^S07ۥR9߸C-ty%A9S!{EɝQ4B_ֺkb#:UR"vM-m!&E$(O,f""<$[Z?GYd+zQ3ѷ6#ɷ vއY.5 cQmn{_t : v~.J xFfס,D7c5؃FWxf R8tƪ+n O/10FIJlLB5w#xATb{HABa@Ǟ(OKK4gX^n+ XB.'(,s֭UTc9"4nU]qU"s#2OOAz;*NzUYl-l^3)$QH̡excҖίy;cv+ ǵ4>Yߓҧ%+gVt%gpL}i)P9SG<∔~r Tr=%܃#gbcWh%>dHIi%.I?qI$ۢ0M#[HpJ&J @SWN-ּC;N8^?4QkV'BhYVf9wrsڽš ԣ_&Qfv RY0Gv`*0fFnKiGLV5 [WK2M, p$耜gڈ񎡧Wi2Oo}~&XI=I[E}6c446𕍺0iN6 ԍha66%HL*P^ ҆D Fv2Fx GMsITӥFB\_z[ Sy;eQ6,7<Kpc#/C,p894z9d_[Csy0sUݼ6D[) fF{si[(#9#ǯUSrn:A(.# r:M! 6I$9~A<'qt! y5HI$ZE=nQ#{ԄswyB 9<5}[Lr:d* i1\he76qI$D.cXmߚR-$?'+7s8cqjvO2)&p ;^1M K!XƐK2Z(<ќGnv)d+]\7YI}MkTbt:^Ku$g#=3Ix_2I rtUN(^*SSyefi9.O'jS0i,H895Eg9$r~}#F]ok,Qf|:ڼ~r3қB倹ܪ9j$ sWM&Oz& .<e8.=psR1MEbkKi ) 2u11"FOnEj4udjSC[K c'|Z`GRkkvH}<&X O,S4>ݱҗ%$#AV^EE'HT[H0%}ʒِ73]FW^I>ތFV, 4'R*FsL 1E&IX33.}~xs8g WCŸ#) -ޙk܄u1'\ExrK[y\IJ Ίkl#s̊< 3Kkxʀ+(ݑAvsiReF4MTc t%Yof+k3FqqLM_\w5k-Sz?™"~i`P}ۯY}CڨFs1P=K4 x兪Xڅg ~QH<5=L*X;?ku+Db*H(9Zࣺ+@']Qh{{;-|2y֩reźR2sG&D=DZn.;F ތƈ ^H:~! {RÔ OڷpZ݀g0#J> FN1F|go+zy-."\cS[?[& )p ˏX`2d%DZ?tY+ p0zQS0 W @LduWQ#\wp1ހChr?Ʃ*9T *ghjjg޹R MȢ*B@-y/ +HklI6rk>K&fr CDy4U bogt3#d!< ZdJuVZ^@pnނV 9 vh'9';Qʆ &]yt1pꈥҨAp3zs4QN #c:A[Eh3ލwc}*K g &Pּ{=3cGYL 锾D p0)M`VlRF)W+#ڳr9P ֪bA}1H3>ǂha*blcZi)#t3+ypޛkQ\Iy,#SK`aX?]֝3M3\FGֵ[ %QԀ9@M1渹.U'Ѐt%ܗ7M,ZFx< cOu=F?KI8F.mZXΦA6:m=PoZ[Z+޲,Q7Zi}jxf~:"[`$@8ݩƛf-j>2m-YJ6SY_0SY4PXwZ+F%<¨I7mau zLr;(ھ˥K p{Xjؼg-RdYbn)A.E.xfcF(KI4,aۍA-t7ȨA%W!AQ׽AtcM7hr cZ!1F,> )f;20~(͜,9m^#2K,QUNZCBWۖ7Xc?s҄[a#xE=}Ѵ%X*۽<Ӵ^IjuƧv#qi3tVLmFQ=Ğ"O\'A4g]A73CP3~yEUsUK>IK< cF\Wޗxl*@ hg!X@*#^큤JTxX(A.K``~5]+)n҃xAkJ% -ze,_YX'LdD>P(Ev֑킺ΧlCE%]HQ^MK  5RX ;jof q}aLXgr@]]Ze?ڎg]]HaQ)N&,0471{U[@%]]Cb>`3GjW+xq&B;{ބDwv+Z$ xz xɮdXdwzsU3Ńמ՘Kme0v9'ѫ|$00ic]]Af6VsCi yGϘ{ ꞑD(L;&: )XjFKKIIJ~VvPӦEv6%G6l Һ~K9VFxBz֫IT28'uu<6sE+ [j&:6&1[tLd*e`\Vi'$6W^ 2dȈ0xf<Ϋ:'uu\lG䢷 \-`c)?uuw6 5o. fojm4y.?n+)j:ď+6;ڭӭg7MG$1O񮮧H ~+~=I?8 ۉH=uu0gxh""!Bx0̏ 8 /g" (1JҺEơAy_5۲4Vb?>LkʗՃ m7:.l7KOS>մ!#iveK3Tq#+E{*7-~WWVpyface-4.1.0/pyface/images/carat_open.png0000644000175100001440000000035211674463363021316 0ustar ischnellusers00000000000000PNG  IHDR gAMA7tEXtSoftwareAdobe ImageReadyqe<|IDATxb?!@L Dbpva-T*@ b9yP @V(@arW.`iyEyG Fb 1tĞIENDB`pyface-4.1.0/pyface/images/carat_closed.png0000644000175100001440000000034311674463363021626 0ustar ischnellusers00000000000000PNG  IHDR gAMA7tEXtSoftwareAdobe ImageReadyqe<uIDATxb? ``-X\ss8yBBQiy( "vs+ \\=QL 7V` P| @ p}0EȾIENDB`pyface-4.1.0/pyface/images/background.jpg0000644000175100001440000007052411674463363021326 0ustar ischnellusers00000000000000JFIFHHExifMM*bj(1r2iHHAdobe Photoshop 7.02004:11:12 11:44:20d(&dHHJFIFHH Adobe_CMAdobed            "?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?e%I9c7$$e%I9cI$I$I$I$I$LPhotoshop 3.08BIM%8BIMHH8BIM&?8BIM 8BIM8BIM 8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM@@8BIM8BIMId backgrounddnullboundsObjcRct1Top longLeftlongBtomlongRghtlongdslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongRghtlongdurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM  dJFIFHH Adobe_CMAdobed            "?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?e%I9c7$$e%I9cI$I$I$I$I$8BIM!UAdobe PhotoshopAdobe Photoshop 7.08BIMHhttp://ns.adobe.com/xap/1.0/ adobe:docid:photoshop:ad8d0f28-34ca-11d9-8347-878451da9c84 Adobed@d      u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?7A{{^׺u~z׺u{{^׺u{3oAu}u?￧u}{w]{{w'޺^׿O~{=u_~uu_(AV}y~{'=u:]_7o>z˯GSu?_~뫛mzv }Ͽcu>׺?:˯_}{(ןgWOu޺S>ׇ_﷯yuc{{^׉}ϯq׿?_~:+׿ߺ=zw}޼׹yy[~^z qןgנz@{^$u _ǯ|_ǿy}o.ׯ^}ǽuxcj{E'?>ozZz|?˭ӯ93׳_׿uuw}uz׺u}{}zwu~?{^A{{^ׯ{_^u~z׺u}{{^׺߳=zֺ}^zb=^]s׾}uqׇ^oߺϯ_߿yu=c}{# g93ׯz׺Ǯ׏]_?>C֩ߟg?>'ׯשO}{_ߏ˯u~{ߺ]]u~:^z{o߾}{_ϿuA~{߾}{_ߺ^ׯz{z׸~{ߺ^ׯ~)sfsuﳯ_5^ֺ#o~~}uO׾λ޺^~?ǯ_u,u~o~׷~g]~}=|}Ooz.޽gMzOw}߼׼?ǟ~]{ϿW?/~^?>׮/N?[{^ï}={?={ϯ__~<:^~پ}t^u{o^u^}{_߼^c]{~={qOw}o{^ٶz {}z׺׺u߿uq{ׯ{#޺]_7#u׽uZ٫]ּ߳h}~{[u~?^:^6[ߺzyW}~{{^9GW޺:{νOO׸cm_O٢og]~μ?ߺ~׾κ7+9zxׯ{w~׸u?޾]{u~uUuKqy^ٚΩg_ǽu{z:^ׯǯu^ﳯu~^z׺sߺ_ِ>κ {^_׺~u~~֫w=z{׾7}{ϯ_#׿~׺z_ي?ucᄒOu_}o~ws#{-n=zmo~g]}?ꎽ<ׯo{xO߽u^׸u?>_>N~αg_ߺ^uzz}k_u}{}{#ׯu~>{_xurO:=zu_~z}u^|=uׯ'ߺ^'߾}{_?v^{z׺:_jGg]r8_N uluAׯ^}}yyzz~`~ߺ~?N=u?׺{{z'k}mz^>gsz]z3ϿuWׯ_EϿu{_W޺]_:O־޽￧u׽޺^xazWϠ^{^}~z׺u}uwWZ^}{_{_޺^q־]JQ.=z[O~>{^}~{:7oN}_:^_~u]߽Wߗ^}˭u~}uu[&ר:B>Og}:O[:o{_ߺ^ߺ^z^[ߏ^{_#u﷯_^:k/6Ogc^u_~z=^:^~}wz]u^刺u?~}z{Z.wgaן~u=Vwq{[#Ͻu^{ߗZ~}??ׯokߺ:}?@c{Wzczן: Wz>~]z?׿_ֱǯ_o{{)?^:?}{_ߺ׺o~~]z׷\:]\}u uz^~{_ߺ^yu]+kވw{}zqu{z{ZO: yOo_#}u~~u=zaO^y{{:^m~=zO~]{~ߺ٧^=Xׯ<}~[?<}=o~N}yuϯso׿uׯ>׺>}8Wzw]_~}wu.q9}ou??uu: u^ߺ]￯uu.u~:ϯ_ߺ]ߺ]_ߺˮ]{z<]_﷯u~د^#=S޿ϿuOuW~]vOGí}zïycO~gOߺ+ׯOz]ygׯ׫׿^z>ǯuu{_Mu.ׯZ:uu?Žغ: uu^ׯׯE{_ߺ^Ou~z׺׺u׺خ__g:'^]?~{o͸{׾=ʽoo~ֺo^wxǯ^׿c` o{ߟ~mo_sWuآA}{ׯßǽu:uׯ#]{_7ͽyu^ӟߏz]zk~u^ؚz u׿u׺u^w^׺u[ׯ#^Su؎?=z Pu?}׺H׺W|?ߺu{z^}˭c{ߺ]Y]_{ϯu}9=]z}>׫؂z uߺ˯_'߾]k׿[ߺ^'^:˭1׿-׺Z+׿ׯ{4_vz g_߸Wׯߺ^uuׯߺ^}u['ߺ]ߺ_nןg1^_ߺ]}{z{z:~Ɲu~o_|9^Qw}ߺ_Ã:)Nr~ߺז:ߺzk=b﾿_g: yu˯u׿~?ï_uׯ{5^z^Oߟqzׯ^}{V゙z uu^}u^^^>׺Ƚu^wo{_+{J"}o޺CZ?ƭo{uZmoQcu?z)xu{~Xǎ?uu}Sˮ/yuu@-^޽W_ݽ{_'ߺ^:^GuWcxׯ{u}ׯ{_޺^~[u6z uz]޺]_ߺ^G}{_߾}{ׯ}{_pz=uȽu^.O_AGHϿ׿oz{?~׺?>׺{[ױZ{^?W_ߺ^~OGǭu=z_${=1WuߺDOzxuwy}:ׯ?ߺ]ߟ.ׯ~=c]_G tׯ{{^ׯ޺^~.~׺uz^WP}=t^]~:zߺ^^u>ױWo>`}ǿOߺ^=o+k_}|o?O~^]| Zz?c߾ν׾_~u׾'~zן:}u޿?:x{Z׼?}?kߺsտggAuSu~u~zNׯׯïuSu߿u{u_>ֿcϯz:^~_ߺ^{zz>׿}~>::>ֱ׿o>ﳯ_}Ͽu}oϽcg׿Ϻ yu{uu{ߺ^'m#׼Eu{~|x}hu޺~]xߏ=:׽ǽ.п{>c{X{^xׯ]{{^{z^׽uϯuuѿ_g1ׯǟ߼^o޺^'}{}uS޺^׺~׺uq^}GׯϽu׿uҿ;K: uaqW{~w~t:k]ߞ/z{c{^]_uwߺǯ>׼v}Pyu_{^7]kΝӿ ?1wo{^߽|^ׯo{z{^^￧m{~}u^׺߾]{ׇ~?-׿Ծ?1wׯc^z^u~:^}ߺ]ߺ]_޸u~wد^վ?z}߱׫=y_}?^]oq׺GSqqzugf{Z]{[~o^+{Ϯͯ?]z}־R=z~]u_=}{=us{}?~]{pwmx}y?=[Wǿ}{_~x'߸u_׾z u^>׾޽{Oׯ:߿u[{^?~z{_ߺ^}~ot>оk?1׽u{{޺^cu^ׯ?:^u{z׺u^ѾKOAϿSg˯_/N׮O)o~[׮qxRz~z_{^?K{^׿?bk^K}}Î}p뻟ߩ׳Ҿ/qcϯ_u.ߏ˯SӯNm{z>=^so:Oqu~z~]w ׇ]_mc{?_>Ny_Ӿ : u^~o]ߟ>޺]_o^cϽuׯ#ߺ^{Z]_׸uuԽg:y^ïu~zָ{ϟ[zw}{_ߺ^uu׺ս 1׾?㞼z^g#ߺ^'޺^׺ﯿun{Z{޾}{^׺uOzֽ?{y/ׯ{3ׇˮq{޺ϯA>1׺/>}ǿu={&^uo{˯uug_[_׽> |?_߸aϽu>{vZﹿu>׺{}{ߺ]~z{}yuнAc~z׺u^׸u~{'ߺzu^׺uuVѽobz׺ߺ]_߾޽~׺u~׺ߺ^u~w{ͽSҽoA~uׯ{{^u{׿oߺ^ogu~^^ߍz^}׾}{m{ӽ1??bc׿ycǿuꎼO_n=W~>׍zs~-.y>׼5֏˯}?ӟ~:?~[_~y[ߪxu_Խ.\{cA~K^ߓ~wߺ}=x|xsc~8﷯W={ߏ^k|n?އ^u?}ۭpSս> |zxu>u={{˯uuߺ^}/ߺ^sk~K[_ߟ}{׸uֽ t׺ǿuﳯ{^:u=uߺ^ׯk|}u~}z=b׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ѽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ҽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ӽb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Լb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ռb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ּb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~׼b{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~мb{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~pyface-4.1.0/pyface/images/panel_gradient.png0000644000175100001440000000314411674463363022161 0ustar ischnellusers00000000000000PNG  IHDRbgAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxl 'sw[ *Zc@R&Fd咁'gNϘ)ls&A8rA$h)c@Ɔ[|8БtjMhZi2h|$5?ƷuX[ndIh],YSI9L>p+d0PGMd?MM£ O/{eHcT;u(p5}>v 55Uujl*CB*S_&- ae+NvGLH0pe6`PzxaTZ;{)ߝ:) @.%dTԉY`! =aR)e P'{;,&%VW}~7 ,ſ8 L5Ndґ g5+l U h0:v6xN:dj%ΰg5_M{1jæ"CzZýQ"yF\AiHr `(_Ĕx-9!ױH} T @OLϚ@'`uD6F&4EVŰ*=!}[㴂2|ZHj9~,`x,̻!Mwj̋:׾6^ qp؁S~ղ)!-A9MU%qbdo4ak0b+Q#zgn>#YF <*1G-).&2aMnp@ЎriKJQZ!Bɐ!p_Gт>. 99ՂYhG.aϸR+ ؼ4Wdkpe:~LaisD?ԝ]v0eϭNW^ Ci@ :~tʰs7ז[.Akr~ÐF|zHOY}| u Lg1:amMLv&/Xjg~^ rBogS~^ɧ-·ᚣ /[N֌W2 avF].J/ß}Z!d Ŋ6nRխSړ^֋5X\"TNbYUgjL.Do0@ex\CyB5u ၓ'n7ARC~"ݘM ӁoDGb*Ck QL!(Z\4/S?l\l@R@)PLЋ`: &Q@0zڱCOZIENDB`pyface-4.1.0/pyface/images/question.png0000644000175100001440000000347111674463363021057 0ustar ischnellusers00000000000000PNG  IHDR szzsBIT|d pHYsu85tEXtSoftwarewww.inkscape.org<IDATXŗkW37v]ZdA6]JiXUhR?E mMڦ!-ibh4l$r6tP{qvwvg\3;evRbINs|%u;{Qʇ!:|wJs|gRNoh&07S`]ajrR\.7䦊;E_OOo+ϭvN% *!ǕNL^;K?[^L޷6ս$5(ApSb,RgׂMz~O>k1DƦiNϢN^J{}TEtS{(oI)xTw->jG7 챶.~֘T:[ǺEX5gƘ9ʉѓr')8GW>׷if{[;Ye1UH;+~|Wgv081X鿴S|oP˻M*S‚fW ~D 6El#j7wqB1pq^t%ƊwQViLw2Vt?]mDDD}M~e7oz&Cs-  `ooc]Heo}OֵV_ e0~30\,VˈSt'bT.^wPEƇ>rbf1nd5{[6$)%cM)7s(_QJK7!""GN-U3t}Rj¶k`0dZ+w5cwxthoI4 X=xٽ~;'/^w弹8 Ѕz5: '/y+iͶАg}xU")}$&7r)+#>k&ŀ3$ __x-kۖI=0 X^{8=Z Mc ܗ#5 ߟz:y/X}E>f[iܰsceKKtFofŦΆgIVK]ju֐I#6 T&vGIᛉ{UIBtt|!;rp򨪆듾_7 _K.IENDB`pyface-4.1.0/pyface/images/image_not_found.png0000644000175100001440000000136711674463363022347 0ustar ischnellusers00000000000000PNG  IHDRabKGD pHYs  tIME 6eDtEXtCommentMenu-sized icon ========== (c) 2003 Jakub 'jimmac' Steiner, http://jimmac.musichall.cz created with the GIMP, http://www.gimp.orggGIDATxڥkSQwf$ZbېBk+ŕ]\W @t!n]Bp#"FED(U5ƒ4sq__Ru>sΙ3הgOYZ,j5yo =DVmx$K&zky*01 R>{U8xx/W.B ށ *J\¹i&bf64TQu;E:΅&Jƞ}lQ,h[D @^ Fdžqf[ { >mʡ#{!=a0;B,;sJd D= "6*pZV%]^ώa&! 6j?t''xrfwNKpu#`R"c =HIGA?r /Ȥwa?o(VmP3;?٦_?Vd9AIENDB`pyface-4.1.0/pyface/images/close.png0000644000175100001440000000055411674463363020314 0ustar ischnellusers00000000000000PNG  IHDRbKGDhv|ay pHYsOtIME  IDATm=KBQ`eC m  t@K[ Ff- CP(B jt)ǹvNKcx1z~בJFGsӥ}}Fl(Ff}9Z[dj}"}L"sJp`Vչ`Ef7 /5:G$u 2R^I\-P}V5jMI_-Q_z _ͬFIENDB`pyface-4.1.0/pyface/images/image_LICENSE.txt0000644000175100001440000000216511674463363021466 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Enthought BSD 3-Clause LICENSE.txt GV (Gael Varoquaux) Public Domain N/A Nuvola LGPL image_LICENSE_Nuvola.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/images: about.jpg | Enthought background.jpg | Enthought caret_close.png | Enthought caret_open.png | Enthought close.png | GV image_not_found.png | Nuvola panel_gradient.png | Enthought panel_gradient_over.png | Enthought question.png | GV splash.png | Enthought pyface-4.1.0/pyface/images/about.jpg0000644000175100001440000007022411674463363020316 0ustar ischnellusers00000000000000JFIFC    #%$""!&+7/&)4)!"0A149;>>>%.DIC;C  ;("(;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;("=!1A"Qaq2#B3RbCr$4s$!1AQ"a2qB ?Ŧ8MOR:UjxZڼwrH1Z*@d J1 sV+w'XLwPKj$Y ySZi>.zHV~ y9m GQ_$AF;c88+'~M`F:jL#"@#TϽVF`ԗqXd1gBī?Q֌ЬRPrDd7 sz[eu%,lA_xi⹉%YG'Ϛ)$#C]pHGfu),7EmNu>RpBQ@s&ܮDGC[nxm Gce.M@=L4)؟^Ѧh˂=9𕞋@J\[%!ZM<lvM5I8tuӥnۏd $vY>ęk6"W@{_hϤYseUMv4-h%T#!Rn9Hp)bj/2ˁQEiH9553ikq\f9$W8ZK7`.4y/}Ǘ(= Z3XsQha1F701jq.k 򘬋Cuq!{`C3ځ+&mb M=j;gzGdZx2 %{-nr߉,jZrԚFlnqT rګJR1Eᰒܱ ֑ZJmSRUmj2pw8K${ugz ',1FɦVɈd2c:Q2E$06}Y)AJ玼œYʧ #8sߥ-3WE3iW>Z#ʀI3FzT=+c# 3 ?ƾuzjڔ'XO8*!~ة<}k*Ec qm#2Dlּ qdQ6;#炭8棴 L!Y_lcYx9+ cr/CZ'6:12H;dގ~Iӥ^y.v~u.0G^ע,l2yǰX/" D*X1w8.Ǡ$ӰEψc!LFSGR8B*%l@TBƇ}6ESd!'cjxK RnLD8X$B%Uc3 ls晴/Se%G=;ĜuYe4+/Kg ޡu,=cQжp>$ ,7lnѰ5X.+2GҫʃqȪe1.rݫ>KK =( '2z#?TWȌ9n+T MHt"|g'ְk(vJ3)m=h#WٚGaU_WJ&K}5fMCyH{fp{Ajsu]E$`a*uRI߸I9_t'5t4jrJjW[.k9"D>|nI:VAdF+uu?Jms\ŗ<5Dቅ7ku#OszIC\h:x3jz͝7P_Qcovm-\7n<˙o5䥼a@J`ӥ$1,ЌZt`y3Ki')4jV=#sɵ=5՝Q2S`}\_IVUX?rLqNwI[ܩvqqc3qg@? @5OZ iY Ao6g|ێ;BE”'QJ7M*<*oC4 :9S+fOߌGZy߄Pp7O.ּw|"hƴ0^ZG 3&+z})F#0:H b7Җi!rcHwS%a@2/|dԿQg lYQVXZ]^YEiͮ"BѰP O)E}UFoMT`V?UZzv7Ḩ[ȿy$<փLE(c|tKYo)e`=RTWi7 ђTҽ#DP!{T1?1(85/-3bpqȭ߇f70FfAF+Z'\sl8!#YKܱ ?j5)`fKdc y=鉷ʂ3;$斃b+=GZqFO9=9QP_jWnKfq6%P{*LhRubd\ Xn!ODgMj<19aѝ6{Zʼg c;Mz76ְ .ZFa*g抍zM<>PcmIg.c+Ck٠Eô0+=F)UIaӜR)GV4ELo?8?SMb wLct,( !`#>#PmBU@WːqiR\tak+]][ks?T>>)hNN1'jS,3B$]ht+m ;- o@c4hQyn34KQiw+z8j*COHI76r mqJѨBİ!q֬5;tŎA("1d2dZMi 9Y ab[-%X'Pk4++]akRr7,ۉ?)u17 j[7T-k@ј\tKțZf3!T9` jӥK0L 1h]OoRvF&Z- N1g#h<߾{W<2VeĬ3"8ݒ(Xb(!|{sylfx?WF AT$wsDIG"u<*;Wӵ(cw2O:$(+7K&s@]'SkqqW\hwY2jٙ7]s~բկ]`) i]/$ sGּP#_*F_, 'fY7P}2D?#}q)|CsڪƇ*D0?L)5P@ Ur4*Y7Y@ +[Jx =JH9Z9k)*6p]ބɔ 4sF5!Hta}: HLáN[Rܵgw(.)YgxlK+x"HI%T\z2_xl2B-.|9F|!f-zVVtk5+E&xj ̓ сLv4kSQ֪KQ i%r2o Wwqޙph(҆=+)F= uم mh犻UԎZEIe `6n$ֺTnkb#̍GcSHn;歳9 gx*4LHm=# px=*9lϑ~ Ѓ⾍(ne2qې|I  9$@X= `{A@OsҲ}0;zGJ}08cYfrN#%<#5X]HpUIڱZ˨jClO$Wy`z>Z FA1nKT[cl삘6=4Jrqjcl*\j[Ghy;j•=R\HU@PYwr UM s,*݀E/52 UAd%p=I=(%@\CB0 *qy,!,}DҺll< brsT\$@z:3tsOJœ%Rx ]!MVe;ՃB]RO̤)bP>R~$V m.qZ˅fb*3(3׌"MA9!v\8QgDYI`vgc{d{gwirгHXĀ?{PeԮV1! xSY m9%m,%Zv<~jYj0@0&y{F;,[^EGN+Xڵ++ HxPW|wyu؏~PijnޫЂMQGm-[r+ ';Z KetXTiKb#pIQ;r:לco"rKHRyNJ I SI- )ǡ=0$Ki⻶e t"ȕḂr9J @}Cr@WvmI}RcW{rq44dB24go?ڂ~K5-R[(#?ǏXx@jZbkIlG$vϽ& #][jQғZ% ZdSD4rƭGUy4K,NdqX>As-02;0ZE&и 7S/"'.ș Pe=)] <)wtamzx#Hw4Xk6SOpJM5e]9)q%DbzkcFr1ip`r?Z< CЂ3?Jcf}`-~~(33\}6Ϟ9@Id`=*_&@=;SY/7ZM`LtjA N]YϰslnA+Aqz 6I"G¢3 z =i 6OQU  2Tƥy&}Hr9A\+ȡ@$'8"{F' u'ہ&$=|YRpHZ!cyK5 Ӡ{)NqGHUν/YXc^jA0Nyo{fa,glb"laBek J5Z [vGnI* XLLa'<8vn[-[os)AD-G~k\96'MuaRn$/?4[Y+& sK ѰYFGei^p4dKY-P4)`6 m4%bNI^MDDaLZZ|$K{ina#y恸ůo\OaZ$1a VW&KfN]oQv˙7[Fvaq$R^fK ʂ AL=g/!ѧ}ͼvp]h˛?P=#[dt|OIIdr0G_ݿZ3hH%4_m*MG] ӻY !>j?sȫ<БJ (=)&ZTAT=ARsӭQ!pr3*4Bїg*=ѓާ6X2\t}No v8# &x>Ҹ dIsx@]6-F8p{۰?J8:1{,I,NsTu1'jlڃF[+vROjk_-c'Vٿ7ڮ07r; _4 /mld): .*=՜sP˹U03aY%$'''ll,6;+×vojT`lN\d]]B*b8#?N s"lu'X6XU6Jd).؟a*Zg]lHhad+cW=s%|̫֒@:BUrLTIbe1w5}}+YFcj(I4]gV͵m,8gVH72[Kٰl㡢+uvNzɡoM40@88)RqکTC!VXփȗ"%mQMv6<s܎zXQ9SZYcADVn,#Tszڱ'Z/ sHݏamn4p biHqK`ƯR f*8=a:tѫze~*pUFXUHDFI=*+}з@B:B01`o01ym+v1a=Z"pv3)qlW$ A4:` y'#SKWS^~PmmMm>ReB$7^b~zz>fB QҳM<`a8=V{jm';ժT=$'<+CWČ>T6Q9@=c$_f 5fFQCќ[Yn|ߜR BO_I^NqQފяAU,ޢ ZYm*}kag_*81FDl$( .NFM/ 4ꊪN(K.IS+m VLua"OvmPryM8@?Kկ5+iR6qz;I fեӬ#W%真`84mb[qL/oFQMEEG0sfE[`u=Buh텱|2-oqzOfEDRrZ_{spܸ!7Eta9|E)@OJ6*99c5-Ih[FdˍޮwcYȭ&]D {gS70h`ux.yU1 3'edkȕzaRnT`~Ny旴XؿԎ24sGXQ!aG>)tTѴIWv槧4w9 ~@:/ۊYC`$Q13FnRm $  G;ISiJsw1=if@ac*9q4vZC@;բ.AĠ~e\=Y;ۅTbKGZIkW6>%e|sqj+%Pv-}=٤W60!9}.Jv-[SQ udFФz)բ2^hU}={A$֓,*LֲUKh4 Ksr6@㟵7v?4teK v$ 飶JvŤrPTZ6 ̥ҲCSD??JZ#=]L#AbFw*OaA}O]q69jمbIT6e:_[iwmC~W-.Z6$w_՗F92eQb/wNΈz:[=;Nn›%򷡈ݞVSODW{Xm`:{R}~mWMH O|o)f̒3*$\jM*v@+ ا⭹Lok @,QZ ombNA]G֓l$rNFR@Ü:3\-zzRV y"TH*xbZiL.!X@*mZ}6x{Ty`%&\ k=Ҩ>[lW52"`N p{A*񥦻$6 ۨIJ=g-e݋xe;]_=5d9\^RnDWC~J,/K;.)14gaY* @0 4G+"NN-ڋS3I[⬧TZt"P28?|x- iB?eRKyR?sRHucIeS<`, 8-f ,NKU1e4̎p =+P֍u;k]L#҉xv 2##`=!?hՃ;r; -Ȣy;s3T]X4g xgZ&G^jb4c!-MMA'XW'~Pe"H)nS2Y%!W'hhT8HSl,l'-3 RZ:I#>Qa,eK|VJgR""rlCwZs3vn)ͅp@D=9 WJ6B{;]NJdȥwZe핱̣רSdęsb.@~5zfE5I#^=Unn2љ Fisnvrwgr9R^DVjcrRrFz5/m";ROsQ`83M h0\eIf?+<ɥXΙ'Dd#{*0_k$^qlDmϽFMd2I{}h-0 fwV$`mV^Ĭ 7oL&( S$ӕ,;5v?:ҲW$dqNdeC v%&MwycCzԥ&MՔ-d:nUTJ[MHpOC% )+9A #bYO?)$ڹQ ]waI]ivע9n'5GtAtInnJbx۽갂FrQ8r16zQSNiӤ7LFH#ھcNڡǔ۷#GnMc-O].i^'c 0={{Jꣿҩ$g䐿Ὲ,Ҷ1U6)߷g98Vx&V(hY\NH#OZ7d }3e gPRec'ڪ.I#S+ݍʐ1!j[py>63֪%۞Bd;IfsE&F$.r%Hsf:7 /[d%'c1yX1wU0A`J۔&o4Xc2&=ȭޜC3ck@;E*vj<gOVjnI k|#{UQ<7v65F8a X3Ҧ뀲땗S[gUfbI9lg)]52N`@ۣsJ0 zcBY 8?]ցs-–i  q{R9)\. 69js&6Ka7{,2V"¶]t +5b7/ >)ƻRM$$HJJиC9R)gvf|`~^ P3z7. q^pq A{WxI-cOK-:chlq Q݄W&x J(-QFڤ]YŪ4ߪ9 h1wmo+BI# ݕV2$ji4\,l/ |R- .[5r=Mh]Y@fY܉'POpi.3ިD$[vVo[[+YA-pqe73H#pWۼҿ˯k-fͼdUZ13^NJcl[^OxvDӉ,o+'YUb:es-d5}MWdI-{C zWQqn:0uUR;vT8~ᚱ/ojҧ4Eo,dg:WVI>Trp9:W֭#E*sЊj0 [,(5gm0zXS1zRł{~ERlt :s]\HQ.IpxqAϵZiW=Gp۷%~GJ0FppGڅEI#q:UeT''k5iV1[4{-.: L Iezmnm 绍cN R|Ƨ,O4ʦиR } TRH៊1Ïcֆ, rE\']$& ۖ]JxVv%>;[hOJY^MsR4ʼn>ֵӕQ@<89X @Kw"DoRz+o]nV9[Z{F}"?3'MquyBnTcGCiSJ4Q(Lo4F^UjxC=~Z~ݤ[N帏E2RaTA@ֆ($6SH|; y`qǖZȢv.'U#ypa#ƂI ?h=Pt]qGVqjd3IM {B#'?qdprjfh)Y0qj]vU4LR8E#KCJq@s׃ԺdA+:f%hQbGtA1BHEN QS?)ܫ%w U#~԰uu-y,Hy5zEő"up  ua`AjZu‰$GfO}x<7iw7əWQ:gިZ0g4et |:WWw3Ϟ&K2IvG jQF/o_Jy`I,}z%ReaҖZm%QHw08j7Mm4PmbBwa[_ Y鷯qg Qi#l1SW)Mlp(aI2ER>)mJ㴵&$ɘwlzIq"HPс VCOA7R}ڦl L=0^·y8 Z 62z|9.# MXlmnXH$M>e|CucW4K!HAۦOȯF庉D p_Ȩ Z ;RFUaPm*,LJcnL+`*:ſYF铟'?Vas7RU2_ަC37mɑ&ƱGvNܪ_M|_^0?f@MQnRD}ʡ8X-#KcAE$G1MO—-DIg}jN7ڃϧ=Zq Fz <⺍# c_Bry?'|QF~w(m01lLWБvRbI2En7] O`ы)*2xR)'8aA=TL2u|)wm Vz~y$ V\#ب:dd'( J)hmV?z!Igg~6#U< h5 6I㊽GK,?v2#F'q"<@$gҰ8˰bGk%5L)9 j$hQE Mh.Kk(#)|Q4(nJA^4oeLZ?7ʦYYhIOeYlXYĞ )7-`~D1ȍ|fhjk-)A])K +r:_*gkb˟j-F{-lo!љ RK%v|LWyfW6sIgw08Ps?U&n-84[_4hnj噋vg^H=f*'TVƳโ9H泯qyvXs^<mk-z$?i:{^S07ۥR9߸C-ty%A9S!{EɝQ4B_ֺkb#:UR"vM-m!&E$(O,f""<$[Z?GYd+zQ3ѷ6#ɷ vއY.5 cQmn{_t : v~.J xFfס,D7c5؃FWxf R8tƪ+n O/10FIJlLB5w#xATb{HABa@Ǟ(OKK4gX^n+ XB.'(,s֭UTc9"4nU]qU"s#2OOAz;*NzUYl-l^3)$QH̡excҖίy;cv+ ǵ4>Yߓҧ%+gVt%gpL}i)P9SG<∔~r Tr=%܃#gbcWh%>dHIi%.I?qI$ۢ0M#[HpJ&J @SWN-ּC;N8^?4QkV'BhYVf9wrsڽš ԣ_&Qfv RY0Gv`*0fFnKiGLV5 [WK2M, p$耜gڈ񎡧Wi2Oo}~&XI=I[E}6c446𕍺0iN6 ԍha66%HL*P^ ҆D Fv2Fx GMsITӥFB\_z[ Sy;eQ6,7<Kpc#/C,p894z9d_[Csy0sUݼ6D[) fF{si[(#9#ǯUSrn:A(.# r:M! 6I$9~A<'qt! y5HI$ZE=nQ#{ԄswyB 9<5}[Lr:d* i1\he76qI$D.cXmߚR-$?'+7s8cqjvO2)&p ;^1M K!XƐK2Z(<ќGnv)d+]\7YI}MkTbt:^Ku$g#=3Ix_2I rtUN(^*SSyefi9.O'jS0i,H895Eg9$r~}#F]ok,Qf|:ڼ~r3қB倹ܪ9j$ sWM&Oz& .<e8.=psR1MEbkKi ) 2u11"FOnEj4udjSC[K c'|Z`GRkkvH}<&X O,S4>ݱҗ%$#AV^EE'HT[H0%}ʒِ73]FW^I>ތFV, 4'R*FsL 1E&IX33.}~xs8g WCŸ#) -ޙk܄u1'\ExrK[y\IJ Ίkl#s̊< 3Kkxʀ+(ݑAvsiReF4MTc t%Yof+k3FqqLM_\w5k-Sz?™"~i`P}ۯY}CڨFs1P=K4 x兪Xڅg ~QH<5=L*X;?ku+Db*H(9Zࣺ+@']Qh{{;-|2y֩reźR2sG&D=DZn.;F ތƈ ^H:~! {RÔ OڷpZ݀g0#J> FN1F|go+zy-."\cS[?[& )p ˏX`2d%DZ?tY+ p0zQS0 W @LduWQ#\wp1ހChr?Ʃ*9T *ghjjg޹R MȢ*B@-y/ +HklI6rk>K&fr CDy4U bogt3#d!< ZdJuVZ^@pnނV 9 vh'9';Qʆ &]yt1pꈥҨAp3zs4QN #c:A[Eh3ލwc}*K g &Pּ{=3cGYL 锾D p0)M`VlRF)W+#ڳr9P ֪bA}1H3>ǂha*blcZi)#t3+ypޛkQ\Iy,#SK`aX?]֝3M3\FGֵ[ %QԀ9@M1渹.U'Ѐt%ܗ7M,ZFx< cOu=F?KI8F.mZXΦA6:m=PoZ[Z+޲,Q7Zi}jxf~:"[`$@8ݩƛf-j>2m-YJ6SY_0SY4PXwZ+F%<¨I7mau zLr;(ھ˥K p{Xjؼg-RdYbn)A.E.xfcF(KI4,aۍA-t7ȨA%W!AQ׽AtcM7hr cZ!1F,> )f;20~(͜,9m^#2K,QUNZCBWۖ7Xc?s҄[a#xE=}Ѵ%X*۽<Ӵ^IjuƧv#qi3tVLmFQ=Ğ"O\'A4g]A73CP3~yEUsUK>IK< cF\Wޗxl*@ hg!X@*#^큤JTxX(A.K``~5]+)n҃xAkJ% -ze,_YX'LdD>P(Ev֑킺ΧlCE%]HQ^MK  5RX ;jof q}aLXgr@]]Ze?ڎg]]HaQ)N&,0471{U[@%]]Cb>`3GjW+xq&B;{ބDwv+Z$ xz xɮdXdwzsU3Ńמ՘Kme0v9'ѫ|$00ic]]Af6VsCi yGϘ{ ꞑD(L;&: )XjFKKIIJ~VvPӦEv6%G6l Һ~K9VFxBz֫IT28'uu<6sE+ [j&:6&1[tLd*e`\Vi'$6W^ 2dȈ0xf<Ϋ:'uu\lG䢷 \-`c)?uuw6 5o. fojm4y.?n+)j:ď+6;ڭӭg7MG$1O񮮧H ~+~=I?8 ۉH=uu0gxh""!Bx0̏ 8 /g" (1JҺEơAy_5۲4Vb?>LkʗՃ m7:.l7KOS>մ!#iveK3Tq#+E{*7-~WWVpyface-4.1.0/pyface/dock/0000755000175100001440000000000011674463363016150 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/dock/dock_window.py0000644000175100001440000014473711674463363021051 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 10/18/2005 # #------------------------------------------------------------------------------- """ Pyface 'DockWindow' support. This package implements a Pyface 'dockable' window component that allows child windows to be reorganized within the DockWindow using drag and drop. The component also allows multiple sub-windows to occupy the same sub-region of the DockWindow, in which case each sub-window appears as a separate notebook-like tab within the region. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import shelve import os import wx import sys from pyface.api import SystemMetrics from traits.api \ import HasPrivateTraits, Instance, Tuple, Property, Any, Str, List, false from traits.trait_base \ import traits_home from traitsui.api \ import View, HGroup, VGroup, Item, Handler, error from traitsui.helper \ import user_name_for from traitsui.menu \ import Menu, Action, Separator from traitsui.dockable_view_element \ import DockableViewElement from traitsui.dock_window_theme \ import dock_window_theme, DockWindowTheme from pyface.wx.drag_and_drop \ import PythonDropTarget, clipboard from pyface.message_dialog \ import error as warning from dock_sizer \ import DockSizer, DockControl, DockRegion, DockStyle, DockSplitter, \ no_dock_info, clear_window, features from idockable \ import IDockable from idock_ui_provider \ import IDockUIProvider is_mac = (sys.platform == 'darwin') #------------------------------------------------------------------------------- # Global data: #------------------------------------------------------------------------------- # Dictionary of cursors in use: cursor_map = {} #------------------------------------------------------------------------------- # DockWindow context menu: #------------------------------------------------------------------------------- min_max_action = Action( name = 'Maximize', action = 'on_min_max' ) undock_action = Action( name = 'Undock', action = 'on_undock' ) lock_action = Action( name = 'Lock Layout', action = 'on_lock_layout', style = 'toggle' ) layout_action = Action( name = 'Switch Layout', action = 'on_switch_layout' ) save_action = Action( name = 'Save Layout...', action = 'on_save_layout' ) #hide_action = Action( name = 'Hide', # action = 'on_hide' ) # #show_action = Action( name = 'Show', # action = 'on_show' ) edit_action = Action( name = 'Edit Properties...', action = 'on_edit' ) enable_features_action = Action( name = 'Show All', action = 'on_enable_all_features' ) disable_features_action = Action( name = 'Hide All', action = 'on_disable_all_features' ) #------------------------------------------------------------------------------- # 'DockWindowHandler' class/interface: #------------------------------------------------------------------------------- class DockWindowHandler ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Returns whether or not a specified object can be inserted into the view: #--------------------------------------------------------------------------- def can_drop ( self, object ): """ Returns whether or not a specified object can be inserted into the view. """ return True #--------------------------------------------------------------------------- # Returns the DockControl object for a specified object: #--------------------------------------------------------------------------- def dock_control_for ( self, parent, object ): """ Returns the DockControl object for a specified object. """ try: name = object.name except: try: name = object.label except: name = '' if name == '': name = user_name_for( object.__class__.__name__ ) image = None export = '' if isinstance( object, DockControl ): dock_control = object image = dock_control.image export = dock_control.export dockable = dock_control.dockable close = dockable.dockable_should_close() if close: dock_control.close( force = True ) control = dockable.dockable_get_control( parent ) # If DockControl was closed, then reset it to point to the new # control: if close: dock_control.set( control = control, style = parent.owner.style ) dockable.dockable_init_dockcontrol( dock_control ) return dock_control elif isinstance( object, IDockable ): dockable = object control = dockable.dockable_get_control( parent ) else: ui = object.get_dockable_ui( parent ) dockable = DockableViewElement( ui = ui ) export = ui.view.export control = ui.control dc = DockControl( control = control, name = name, export = export, style = parent.owner.style, image = image, closeable = True ) dockable.dockable_init_dockcontrol( dc ) return dc #--------------------------------------------------------------------------- # Creates a new view of a specified control: #--------------------------------------------------------------------------- def open_view_for ( self, control, use_mouse = True ): """ Creates a new view of a specified control. """ from dock_window_shell import DockWindowShell DockWindowShell( control, use_mouse = use_mouse ) #--------------------------------------------------------------------------- # Handles the DockWindow becoming empty: #--------------------------------------------------------------------------- def dock_window_empty ( self, dock_window ): """ Handles the DockWindow becoming empty. """ if dock_window.auto_close: dock_window.control.GetParent().Destroy() # Create a singleton handler: dock_window_handler = DockWindowHandler() #------------------------------------------------------------------------------- # 'LayoutName' class: #------------------------------------------------------------------------------- class LayoutName ( Handler ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Name the user wants to assign to the layout: name = Str # List of currently assigned names: names = List( Str ) #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( Item( 'name', label = 'Layout name' ), title = 'Save Layout', kind = 'modal', buttons = [ 'OK', 'Cancel' ] ) #--------------------------------------------------------------------------- # Handles a request to close a dialog-based user interface by the user: #--------------------------------------------------------------------------- def close ( self, info, is_ok ): if is_ok: name = info.object.name.strip() if name == '': warning( info.ui.control, 'No name specified', title = 'Save Layout Error' ) return False if name in self.names: return error( message = '%s is already defined. Replace?' % name, title = 'Save Layout Warning', parent = info.ui.control ) return True #------------------------------------------------------------------------------- # 'DockWindow' class: #------------------------------------------------------------------------------- class DockWindow ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The wx.Window being used as the DockWindow: control = Instance( wx.Window ) # The handler used to determine how certain events should be processed: handler = Any( dock_window_handler ) # The 'extra' arguments to be passed to each handler call: handler_args = Tuple # Close the parent window if the DockWindow becomes empty: auto_close = false # The DockWindow graphical theme style information: theme = Instance( DockWindowTheme, allow_none = False ) # Default style for external objects dragged into the window: style = DockStyle # Return the sizer associated with the control (i.e. window) sizer = Property # The id used to identify this DockWindow: id = Str #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, parent, wid = -1, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.FULL_REPAINT_ON_RESIZE, **traits ): super( DockWindow, self ).__init__( **traits ) # Create the actual window: self.control = control = wx.Window( parent, wid, pos, size, style ) control.owner = self # Set up the 'paint' event handler: wx.EVT_PAINT( control, self._paint ) wx.EVT_SIZE( control, self._size ) # Set up mouse event handlers: wx.EVT_LEFT_DOWN( control, self._left_down ) wx.EVT_LEFT_UP( control, self._left_up ) wx.EVT_LEFT_DCLICK( control, self._left_dclick ) wx.EVT_RIGHT_DOWN( control, self._right_down ) wx.EVT_RIGHT_UP( control, self.right_up ) wx.EVT_MOTION( control, self._mouse_move ) wx.EVT_LEAVE_WINDOW( control, self._mouse_leave ) control.SetDropTarget( PythonDropTarget( self ) ) # Initialize the window background color: if self.theme.use_theme_color: color = self.theme.tab.image_slice.bg_color else: color = SystemMetrics().dialog_background_color color = wx.Colour(color[0]*255, color[1]*255, color[2]*255) self.control.SetBackgroundColour( color ) #-- Default Trait Values --------------------------------------------------- def _theme_default ( self ): return dock_window_theme() #-- Trait Event Handlers --------------------------------------------------- def _theme_changed ( self, theme ): if self.control is not None: if theme.use_theme_color: color = theme.tab.image_slice.bg_color else: color = wx.NullColour self.control.SetBackgroundColour( color ) self.update_layout() #--------------------------------------------------------------------------- # Notifies the DockWindow that its contents are empty: #--------------------------------------------------------------------------- def dock_window_empty ( self ): """ Notifies the DockWindow that its contents are empty. """ self.handler.dock_window_empty( self ) #--------------------------------------------------------------------------- # Sets the cursor to a specified cursor shape: #--------------------------------------------------------------------------- def set_cursor ( self, cursor = None ): """ Sets the cursor to a specified cursor shape. """ if cursor is None: self.control.SetCursor( wx.NullCursor ) return global cursor_map if cursor not in cursor_map: cursor_map[ cursor ] = wx.StockCursor( cursor ) self.control.SetCursor( cursor_map[ cursor ] ) #--------------------------------------------------------------------------- # Releases ownership of the mouse capture: #--------------------------------------------------------------------------- def release_mouse ( self ): """ Releases ownership of the mouse capture. """ if self._owner is not None: self._owner = None self.control.ReleaseMouse() #--------------------------------------------------------------------------- # Updates the layout of the window: #--------------------------------------------------------------------------- def update_layout ( self ): """ Updates the layout of the window. """ # There are cases where a layout has been scheduled for a DockWindow, # but then the DockWindow is destroyed, which will cause the calls # below to fail. So we catch the 'PyDeadObjectError' exception and # ignore it: try: self.control.Layout() self.control.Refresh() except wx.PyDeadObjectError: pass #--------------------------------------------------------------------------- # Minimizes/Maximizes a specified DockControl: #--------------------------------------------------------------------------- def min_max ( self, dock_control ): """ Minimizes/maximizes a specified DockControl. """ sizer = self.sizer if sizer is not None: sizer.MinMax( self.control, dock_control ) self.update_layout() #--------------------------------------------------------------------------- # Pops up the feature bar for a specified DockControl: #--------------------------------------------------------------------------- def feature_bar_popup ( self, dock_control ): """ Pops up the feature bar for a specified DockControl. """ fb = self._feature_bar if fb is None: from feature_bar import FeatureBar self._feature_bar = fb = FeatureBar( parent = self.control ) fb.on_trait_change( self._feature_bar_closed, 'completed' ) fb.dock_control = dock_control fb.show() #--------------------------------------------------------------------------- # Handles closing the feature bar: #--------------------------------------------------------------------------- def _feature_bar_closed ( self ): fb = self._feature_bar fb.dock_control.feature_bar_closed() fb.hide() #--------------------------------------------------------------------------- # Perform all operations needed to close the window: #--------------------------------------------------------------------------- def close ( self ): """ Closes the dock window. In this case, all event handlers are unregistered. Other cleanup operations go here, but at the moment Linux (and other non-Windows platforms?) are less forgiving when things like event handlers arent unregistered. """ self._unregister_event_handlers() #--------------------------------------------------------------------------- # Unregister all event handlers: #--------------------------------------------------------------------------- def _unregister_event_handlers ( self ): """ Unregister all event handlers setup in the constructor. This is typically done prior to an app shutting down and is needed since Linux (and other non-Windows platforms?) trigger mouse, repaint, etc. events for controls which have already been deleted. """ control = self.control if control is not None: wx.EVT_PAINT( control, None ) wx.EVT_SIZE( control, None ) wx.EVT_LEFT_DOWN( control, None ) wx.EVT_LEFT_UP( control, None ) wx.EVT_LEFT_DCLICK( control, None ) wx.EVT_RIGHT_DOWN( control, None ) wx.EVT_RIGHT_UP( control, None ) wx.EVT_MOTION( control, None ) wx.EVT_LEAVE_WINDOW( control, None ) #--------------------------------------------------------------------------- # Handles repainting the window: #--------------------------------------------------------------------------- def _paint ( self, event ): """ Handles repainting the window. """ # There is a problem on macs where we get paints when the update # is entirely within children. if is_mac and self._is_child_paint(): return sizer = self.sizer if isinstance( sizer, DockSizer ): sizer.Draw( self.control ) else: clear_window( self.control ) #--------------------------------------------------------------------------- # Uses wx calls to determine if we really need to paint or the children will # do it. #--------------------------------------------------------------------------- def _is_child_paint ( self ): """ Returns whether or not the current update region is entirely within a child. """ if self.control.Children: update_rect = self.control.UpdateRegion.Box for child in self.control.Children: if not child.HasTransparentBackground() and \ child.Rect.ContainsRect(update_rect): return True return False #--------------------------------------------------------------------------- # Handles the window being resized: #--------------------------------------------------------------------------- def _size ( self, event ): """ Handles the window being resized. """ sizer = self.sizer if sizer is not None: try: dx, dy = self.control.GetSizeTuple() sizer.SetDimension( 0, 0, dx, dy ) except: # fixme: This is temporary code to work around a problem in # ProAVA2 that we are still trying to track down... pass #--------------------------------------------------------------------------- # Handles the left mouse button being pressed: #--------------------------------------------------------------------------- def _left_down ( self, event ): """ Handles the left mouse button being pressed. """ sizer = self.sizer if sizer is not None: object = sizer.ObjectAt( event.GetX(), event.GetY() ) if object is not None: self._owner = object self.control.CaptureMouse() object.mouse_down( event ) #--------------------------------------------------------------------------- # Handles the left mouse button being released: #--------------------------------------------------------------------------- def _left_up ( self, event ): """ Handles the left mouse button being released. """ window = self.control if self._owner is not None: window.ReleaseMouse() self._owner.mouse_up( event ) self._owner = None # Check for the user requesting that the layout structure be reset: if event.ShiftDown(): if event.ControlDown(): # Check for the developer requesting a structure dump (DEBUG): if event.AltDown(): contents = self.sizer.GetContents() if contents is not None: contents.dump() sys.stdout.flush() else: self.sizer.ResetStructure( window ) self.update_layout() elif event.AltDown(): self.sizer.ToggleLock() self.update_layout() #--------------------------------------------------------------------------- # Handles the left mouse button being double clicked: #--------------------------------------------------------------------------- def _left_dclick ( self, event ): """ Handles the left mouse button being double clicked. """ sizer = self.sizer if sizer is not None: object = sizer.ObjectAt( event.GetX(), event.GetY(), True ) if isinstance( object, DockControl ): dockable = object.dockable if (((dockable is None) or (dockable.dockable_dclick( object, event ) is False)) and (object.style != 'fixed')): self.min_max( object ) elif isinstance( object, DockRegion ): self._owner = object self.control.CaptureMouse() object.mouse_down( event ) #--------------------------------------------------------------------------- # Handles the right mouse button being pressed: #--------------------------------------------------------------------------- def _right_down ( self, event ): """ Handles the right mouse button being pressed. """ pass #--------------------------------------------------------------------------- # Handles the right mouse button being released: #--------------------------------------------------------------------------- def right_up ( self, event ): """ Handles the right mouse button being released. """ sizer = self.sizer if sizer is not None: object = sizer.ObjectAt( event.GetX(), event.GetY(), True ) if object is not None: popup_menu = None window = self.control is_dock_control = isinstance( object, DockControl ) if (is_dock_control and (object.dockable is not None) and (event.ShiftDown() or event.ControlDown() or event.AltDown())): self._menu_self = object.dockable popup_menu = object.dockable.dockable_menu( object, event ) if popup_menu is None: self._menu_self = self section = self.sizer.GetContents() is_splitter = isinstance( object, DockSplitter ) self._object = object if is_splitter: self._object = object = object.parent group = object if is_dock_control: group = group.parent if sizer.IsMaximizable(): min_max_action.name = 'Maximize' else: min_max_action.name = 'Restore' min_max_action.enabled = is_dock_control undock_action.enabled = is_dock_control edit_action.enabled = (not is_splitter) controls = section.get_controls( False ) lock_action.checked = ((len( controls ) > 0) and controls[0].locked) save_action.enabled = (self.id != '') feature_menu = self._get_feature_menu() restore_menu, delete_menu = self._get_layout_menus() popup_menu = Menu( min_max_action, undock_action, Separator(), feature_menu, #Separator(), #hide_action, #show_action, Separator(), lock_action, layout_action, Separator(), save_action, restore_menu, delete_menu, Separator(), edit_action, name = 'popup' ) window.PopupMenuXY( popup_menu.create_menu( window, self ), event.GetX() - 10, event.GetY() - 10 ) self._object = None #--------------------------------------------------------------------------- # Handles the mouse moving over the window: #--------------------------------------------------------------------------- def _mouse_move ( self, event ): """ Handles the mouse moving over the window. """ if self._last_dock_control is not None: self._last_dock_control.reset_feature_popup() self._last_dock_control = None if self._owner is not None: self._owner.mouse_move( event ) else: sizer = self.sizer if sizer is not None: object = (self._object or sizer.ObjectAt( event.GetX(), event.GetY() )) self._set_cursor( event, object ) if object is not self._hover: if self._hover is not None: self._hover.hover_exit( event ) if object is not None: object.hover_enter( event ) self._hover = object # Handle feature related processing: if (isinstance( object, DockControl ) and object.feature_activate( event )): self._last_dock_control = object #--------------------------------------------------------------------------- # Handles the mouse leaving the window: #--------------------------------------------------------------------------- def _mouse_leave ( self, event ): """ Handles the mouse leaving the window. """ if self._hover is not None: self._hover.hover_exit( event ) self._hover = None self._set_cursor( event ) #--------------------------------------------------------------------------- # Sets the cursor for a specified object: #--------------------------------------------------------------------------- def _set_cursor ( self, event, object = None ): """ Sets the cursor for a specified object. """ if object is None: self.set_cursor() else: self.set_cursor( object.get_cursor( event ) ) #-- Context menu action handlers ----------------------------------------------- #--------------------------------------------------------------------------- # Handles the user asking for a DockControl to be maximized/restored: #--------------------------------------------------------------------------- def on_min_max ( self ): """ Handles the user asking for a DockControl to be maximized/restored. """ self.min_max( self._object ) #--------------------------------------------------------------------------- # Handles the user requesting an element to be undocked: #--------------------------------------------------------------------------- def on_undock ( self ): """ Handles the user requesting an element to be undocked. """ self.handler.open_view_for( self._object, use_mouse = False ) #--------------------------------------------------------------------------- # Handles the user requesting an element to be hidden: #--------------------------------------------------------------------------- def on_hide ( self ): """ Handles the user requesting an element to be hidden. """ self._object.show( False ) #--------------------------------------------------------------------------- # Handles the user requesting an element to be shown: #--------------------------------------------------------------------------- def on_show ( self ): """ Handles the user requesting an element to be shown. """ object = self._object if isinstance( object, DockControl ): object = object.parent self._hidden_group_for( object ).show( True ) #--------------------------------------------------------------------------- # Handles the user requesting that the current layout be switched: #--------------------------------------------------------------------------- def on_switch_layout ( self ): """ Handles the user requesting that the current layout be switched. """ self.sizer.ResetStructure( self.control ) self.update_layout() #--------------------------------------------------------------------------- # Handles the user requesting that the layout be locked/unlocked: #--------------------------------------------------------------------------- def on_lock_layout ( self ): """ Handles the user requesting that the layout be locked/unlocked. """ self.sizer.ToggleLock() self.update_layout() #--------------------------------------------------------------------------- # Handles the user requesting that the layout be saved: #--------------------------------------------------------------------------- def on_save_layout ( self ): """ Handles the user requesting that the layout be saved. """ layout_name = LayoutName( names = self._get_layout_names() ) if layout_name.edit_traits( parent = self.control ).result: self._set_layout( layout_name.name, self.sizer.GetStructure() ) #--------------------------------------------------------------------------- # Handles the user requesting a specified layout to be restored: #--------------------------------------------------------------------------- def on_restore_layout ( self, name ): """ Handles the user requesting a specified layout to be restored. """ self.sizer.SetStructure( self.control, self._get_layout( name ) ) self.update_layout() #--------------------------------------------------------------------------- # Handles the user reqesting a specified layout to be deleted: #--------------------------------------------------------------------------- def on_delete_layout ( self, name ): """ Handles the user reqesting a specified layout to be deleted. """ if error( message = "Delete the '%s' layout?" % name, title = 'Delete Layout Warning', parent = self.control ): self._delete_layout( name ) #--------------------------------------------------------------------------- # Handles the user requesting to edit an item: #--------------------------------------------------------------------------- def on_edit ( self, object = None ): """ Handles the user requesting to edit an item. """ if object is None: object = self._object control_info = ControlInfo( **object.get( 'name', 'user_name', 'style', 'user_style' ) ) if control_info.edit_traits( parent = self.control ).result: name = control_info.name.strip() if name != '': object.name = name object.set( **control_info.get( 'user_name', 'style', 'user_style' ) ) self.update_layout() #--------------------------------------------------------------------------- # Enables all features: #--------------------------------------------------------------------------- def on_enable_all_features ( self, action ): """ Enables all features. """ for feature in features: if (feature.feature_name != '') and (feature.state != 1): feature.toggle_feature( action ) #--------------------------------------------------------------------------- # Disables all features: #--------------------------------------------------------------------------- def on_disable_all_features ( self, action ): """ Disables all features. """ for feature in features: if (feature.feature_name != '') and (feature.state == 1): feature.toggle_feature( action ) #--------------------------------------------------------------------------- # Toggles the enabled/disabled state of the action's associated feature: #--------------------------------------------------------------------------- def on_toggle_feature ( self, action ): """ Toggles the enabled/disabled state of the action's associated feature. """ action._feature.toggle_feature( action ) #-- DockWindow user preference database methods -------------------------------- #--------------------------------------------------------------------------- # Gets the layout dictionary for the DockWindow: #--------------------------------------------------------------------------- def _get_layouts ( self ): """ Gets the layout dictionary for the DockWindow. """ id = self.id if id != '': db = self._get_dw_db() if db is not None: layouts = db.get( id ) db.close() return layouts return None #--------------------------------------------------------------------------- # Gets the names of all current layouts defined for the DockWindow: #--------------------------------------------------------------------------- def _get_layout_names ( self ): """ Gets the names of all current layouts defined for the DockWindow. """ layouts = self._get_layouts() if layouts is not None: return layouts.keys() return [] #--------------------------------------------------------------------------- # Gets the layout data for a specified layout name: #--------------------------------------------------------------------------- def _get_layout ( self, name ): """ Gets the layout data for a specified layout name. """ layouts = self._get_layouts() if layouts is not None: return layouts.get( name ) return None #--------------------------------------------------------------------------- # Deletes the layout data for a specified layout name: #--------------------------------------------------------------------------- def _delete_layout ( self, name ): """ Deletes the layout data for a specified layout name. """ id = self.id if id != '': db = self._get_dw_db( mode = 'c' ) if db is not None: layouts = db.get( id ) if layouts is not None: del layouts[ name ] db[ id ] = layouts db.close() #--------------------------------------------------------------------------- # Sets the layout data for a specified layout name: #--------------------------------------------------------------------------- def _set_layout ( self, name, layout ): """ Sets the layout data for a specified layout name. """ id = self.id if id != '': db = self._get_dw_db( mode = 'c' ) if db is not None: layouts = db.get( id ) if layouts is None: layouts = {} layouts[ name ] = layout db[ id ] = layouts db.close() #--------------------------------------------------------------------------- # Gets a reference to the DockWindow UI preference database: #--------------------------------------------------------------------------- def _get_dw_db ( self, mode = 'r' ): try: return shelve.open( os.path.join( traits_home(), 'dock_window' ), flag = mode, protocol = -1 ) except: return None #--------------------------------------------------------------------------- # Returns the 'Features' sub_menu: #--------------------------------------------------------------------------- def _get_feature_menu ( self ): """ Returns the 'Features' sub_menu. """ enable_features_action.enabled = disable_features_action.enabled = False for feature in features: if feature.feature_name != '': if feature.state == 1: disable_features_action.enabled = True if enable_features_action.enabled: break else: enable_features_action.enabled = True if disable_features_action.enabled: break actions = [] for feature in features: if feature.feature_name != '': actions.append( Action( name = feature.feature_name, action = 'on_toggle_feature', _feature = feature, style = 'toggle', checked = (feature.state == 1) ) ) if len( actions ) > 0: actions.sort( lambda l, r: cmp( l.name, r.name ) ) actions[0:0] = [ Separator() ] return Menu( name = 'Features', *([ enable_features_action, disable_features_action ] + actions) ) #--------------------------------------------------------------------------- # Gets the sub-menus for the 'Restore layout' and 'Delete layout' menu # options: #--------------------------------------------------------------------------- def _get_layout_menus ( self ): """ Gets the sub-menus for the 'Restore layout' and 'Delete layout' menu options. """ names = self._get_layout_names() if len( names ) == 0: restore_actions = [ Action( name = '', enabled = False ) ] delete_actions = [ Action( name = '', enabled = False ) ] else: names.sort() restore_actions = [ Action( name = name, action = "self.on_restore_layout(%s)" % repr( name ) ) for name in names ] delete_actions = [ Action( name = name, action = "self.on_delete_layout(%s)" % repr( name ) ) for name in names ] return [ Menu( name = 'Restore Layout', *restore_actions ), Menu( name = 'Delete Layout', *delete_actions ) ] #-- Drag and drop event handlers: ---------------------------------------------- #--------------------------------------------------------------------------- # Handles a Python object being dropped on the control: #--------------------------------------------------------------------------- def wx_dropped_on ( self, x, y, data, drag_result ): """ Handles a Python object being dropped on the window. """ if isinstance( data, ( IDockUIProvider, DockControl ) ): window = self.control dock_info = self._dock_info # See the 'wx_drag_leave' method for an explanation of this code: if dock_info is None: dock_info = self._leave_info dock_info.draw( window ) self._dock_info = None try: control = self.handler.dock_control_for( *(self.handler_args + ( window, data )) ) # Safely check to see if the object quacks like a Binding binding = getattr( clipboard, 'node', None ) if (hasattr(binding, "obj") and (binding.obj is data) and hasattr(binding, "namespace_name")): control.id = '@@%s' % binding.namespace_name dock_info.dock( control, window ) return drag_result except: warning( window, "An error occurred while attempting to add an item of " "type '%s' to the window." % data.__class__.__name__, title = 'Cannot add item to window' ) return wx.DragNone #--------------------------------------------------------------------------- # Handles a Python object being dragged over the control: #--------------------------------------------------------------------------- def wx_drag_any ( self, x, y, data, drag_result ): """ Handles a Python object being dragged over the control. """ ui_provider = isinstance( data, IDockUIProvider ) if ui_provider or isinstance( data, DockControl ): if (ui_provider and (not self.handler.can_drop( *(self.handler_args + ( data, )) ))): return wx.DragNone # Check to see if we are in 'drag mode' yet: cur_dock_info = self._dock_info if cur_dock_info is None: cur_dock_info = no_dock_info if isinstance( data, DockControl ): self._dock_size = data.tab_width else: self._dock_size = 80 # Get the window and DockInfo object associated with the event: window = self.control self._dock_info = dock_info = \ self.sizer.DockInfoAt( x, y, self._dock_size, False ) # If the DockInfo has changed, then update the screen: if ((cur_dock_info.kind != dock_info.kind) or (cur_dock_info.region is not dock_info.region) or (cur_dock_info.bounds != dock_info.bounds) or (cur_dock_info.tab_bounds != dock_info.tab_bounds)): # Erase the old region: cur_dock_info.draw( window ) # Draw the new region: dock_info.draw( window ) return drag_result # Handle the case of dragging a normal object over a 'feature': if self._can_drop_on_feature( x, y, data ) is not None: return drag_result return wx.DragNone #--------------------------------------------------------------------------- # Handles a dragged Python object leaving the window: #--------------------------------------------------------------------------- def wx_drag_leave ( self, data ): """ Handles a dragged Python object leaving the window. """ if self._dock_info is not None: self._dock_info.draw( self.control ) # Save the current '_dock_info' in '_leave_info' because under # Linux the drag and drop code sends a spurious 'drag_leave' event # immediately before a 'dropped_on' event, so we need to remember # the '_dock_info' value just in case the next event is # 'dropped_on': self._leave_info, self._dock_info = self._dock_info, None #-- Pyface menu interface implementation --------------------------------------- #--------------------------------------------------------------------------- # Adds a menu item to the menu bar being constructed: #--------------------------------------------------------------------------- def add_to_menu ( self, menu_item ): """ Adds a menu item to the menu bar being constructed. """ pass #--------------------------------------------------------------------------- # Adds a tool bar item to the tool bar being constructed: #--------------------------------------------------------------------------- def add_to_toolbar ( self, toolbar_item ): """ Adds a tool bar item to the tool bar being constructed. """ pass #--------------------------------------------------------------------------- # Returns whether the menu action should be defined in the user interface: #--------------------------------------------------------------------------- def can_add_to_menu ( self, action ): """ Returns whether the action should be defined in the user interface. """ return True #--------------------------------------------------------------------------- # Returns whether the toolbar action should be defined in the user # interface: #--------------------------------------------------------------------------- def can_add_to_toolbar ( self, action ): """ Returns whether the toolbar action should be defined in the user interface. """ return True #--------------------------------------------------------------------------- # Performs the action described by a specified Action object: #--------------------------------------------------------------------------- def perform ( self, action_object ): """ Performs the action described by a specified Action object. """ action = action_object.action if action[ : 5 ] == 'self.': eval( action, globals(), { 'self': self._menu_self } ) else: method = getattr( self, action ) try: method() except: method( action_object ) #-- Property implementations --------------------------------------------------- #--------------------------------------------------------------------------- # Implementation of the 'sizer' property: #--------------------------------------------------------------------------- def _get_sizer ( self ): if self.control is not None: return self.control.GetSizer() return None def _set_sizer ( self, sizer ): self.control.SetSizer( sizer ) #-- Private methods ------------------------------------------------------------ #--------------------------------------------------------------------------- # Finds the first group with any hidden items (if any): #--------------------------------------------------------------------------- def _hidden_group_for ( self, group ): """ Finds the first group with any hidden items (if any). """ while True: if group is None: return None if len( group.contents ) > len( group.visible_contents ): return group group = group.parent #--------------------------------------------------------------------------- # Returns a feature that the pointer is over and which can accept the # specified data: #--------------------------------------------------------------------------- def _can_drop_on_feature ( self, x, y, data ): """ Returns a feature that the pointer is over and which can accept the specified data. """ if self.sizer is not None: object = self.sizer.ObjectAt( x, y ) if isinstance( object, DockControl ): event = FakeEvent( x, y, self.control ) if object.feature_activate( event, data ): ldc = self._last_dock_control if (ldc is not None) and (ldc is not object): ldc.reset_feature_popup() self._last_dock_control = object return None if self._last_dock_control is not None: self._last_dock_control.reset_feature_popup() self._last_dock_control = None return None #------------------------------------------------------------------------------- # 'FakeEvent' class: #------------------------------------------------------------------------------- class FakeEvent ( object ): def __init__ ( self, x, y, object ): self.x, self.y, self.object = x, y, object def GetX ( self ): return self.x def GetY ( self ): return self.y def GetEventObject ( self ): return self.object #------------------------------------------------------------------------------- # 'ControlInfo' class: #------------------------------------------------------------------------------- class ControlInfo ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Name to be edited: name = Str # Has the user set the name of the control? user_name = false id = Str # Style of drag bar/tab: style = DockStyle # Has the user set the style for this control: user_style = false #--------------------------------------------------------------------------- # Traits view definition: #--------------------------------------------------------------------------- traits_view = View( VGroup( HGroup( HGroup( 'name<100>{Label}', '3' ), HGroup( 'user_name{Remember label}', show_left = False ) ), HGroup( HGroup( 'style<101>', '3' ), HGroup( 'user_style{Remember style}', show_left = False ) ) ), title = 'Edit properties', kind = 'modal', buttons = [ 'OK', 'Cancel' ] ) pyface-4.1.0/pyface/dock/idock_ui_provider.py0000644000175100001440000000272711674463363022232 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 06/17/2006 # #------------------------------------------------------------------------------- """ Defines the IDockUIProvider interface which objects which support being dragged and dropped into a DockWindow must implement. """ #------------------------------------------------------------------------------- # 'IDockUIProvider' class: #------------------------------------------------------------------------------- class IDockUIProvider ( object ): #--------------------------------------------------------------------------- # Returns a Traits UI which a DockWindow can imbed: #--------------------------------------------------------------------------- def get_dockable_ui ( self, parent ): """ Returns a Traits UI which a DockWindow can imbed. """ return self.edit_traits( parent = parent, kind = 'subpanel', scrollable = True ) pyface-4.1.0/pyface/dock/images/0000755000175100001440000000000011674463363017415 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/dock/images/bar_feature_disabled.png0000644000175100001440000000037711674463363024240 0ustar ischnellusers00000000000000PNG  IHDRRW pHYs  ~IDATx5̱jP@1!K7'C !s:dRSd d\N.B@ .Ió@.,UY_afwy u]$٪DQt.۶Q@8^Q' `x7u1˲sj4MAyA8X~IENDB`pyface-4.1.0/pyface/dock/images/close_tab.png0000644000175100001440000000124211674463363022055 0ustar ischnellusers00000000000000PNG  IHDR Vu\ pHYs+tIMEAtEXtAuthorH tEXtDescription !# tEXtCopyright:tEXtCreation time5 tEXtSoftware]p: tEXtDisclaimertEXtWarningtEXtSourcetEXtComment̖tEXtTitle'mIDAT(]=hS'i 5EC)8HZ\+ HV\\{7 IR8%u` BMƘ`gDi#8YdWq$t KT A$U,c#tzz!VXcsizj>}}yJX9xAoy˥)\@z\)MV>?qYw{]gk r0#j&+de^^}$G#OνqsIbspL,֌GwPIKO/+UOn{.S,@DZa)9agٹ~l<R íBZ432xz!h0CIENDB`pyface-4.1.0/pyface/dock/images/sh_bottom.png0000644000175100001440000000021211674463363022114 0ustar ischnellusers00000000000000PNG  IHDR× pHYs  ~wIܶs 0uKIZ$-06}~xȲ}-OuO%pIZ{$=p Lȅ1ƀN#EHNsIENDB`pyface-4.1.0/pyface/dock/images/sv_middle.png0000644000175100001440000000030211674463363022064 0ustar ischnellusers00000000000000PNG  IHDR"|h pHYs  ~tIDATxӻ PƢl@u)N"QB[4pX >0 7rX'+a[<9H!MNB%b5t8f z,i} n3 "IENDB`pyface-4.1.0/pyface/dock/images/window.png0000644000175100001440000000072711674463363021440 0ustar ischnellusers00000000000000PNG  IHDR(-SPLTEoznz+[)o)m)n*l*k)j*j)g*g*d*c*a*^*\*Y+Y+X+X+VwzzrlhhvS!tRNS@fbKGDH pHYs  tIME 4$ IDATe0@QTBAb) ąpxlƲjC.rnכnb:hӖ* \iːK qYuC7@hx1* )!DC4l:FIMp1k|*ՏEJؤDIENDB`pyface-4.1.0/pyface/dock/images/bar_feature_drop.png0000644000175100001440000000044411674463363023430 0ustar ischnellusers00000000000000PNG  IHDRRW pHYsodIDATx4t 7#} @] 8_#V\[Va- h  P*,  -0~h,4̚\?9"IENDB`pyface-4.1.0/pyface/dock/images/sv_left.png0000644000175100001440000000023311674463363021563 0ustar ischnellusers00000000000000PNG  IHDRL pHYs  ~MIDATxѫ C+FAV8`j2^' , W!-.0{`?29}x]yIENDB`pyface-4.1.0/pyface/dock/images/close_drag.png0000644000175100001440000000107111674463363022224 0ustar ischnellusers00000000000000PNG  IHDR  pHYs+tIME'DptEXtAuthorH tEXtDescription !# tEXtCopyright:tEXtCreation time5 tEXtSoftware]p: tEXtDisclaimertEXtWarningtEXtSourcetEXtComment̖tEXtTitle'IDATM=K`MHZPPl6 )d*3eďA K~ElEA("j6)(T@J$Š7ܝPJk C"jך90˟>u Пvr:&\[F}ll}E!3Ohcm!^ $]&1;|>ķ~J`[S+.&zH-4ZACC@XԺzWR~<5D~;/l\IENDB`pyface-4.1.0/pyface/dock/images/tab_scroll_l.png0000644000175100001440000000035211674463363022562 0ustar ischnellusers00000000000000PNG  IHDRkz pHYsodIDATx Pn!cBW(t cbA?! "MZbuF;?NpDDr-.[=N\ u[6'DqQjkCį8H-)ZW!D+5(\48n9c\',eQZrTkN717v$EIENDB`pyface-4.1.0/pyface/dock/images/tab_feature_no_drop.png0000644000175100001440000000067211674463363024131 0ustar ischnellusers00000000000000PNG  IHDRa pHYs  ~lIDATxӻSQ/q&ͨ3"Ⅸ )@ Sh#LHAR ] ZiiAtlMagśH2irȧ!_@W8×Fcǐ[ $In?IlqV}좰H* pt"YdCu@8 d}d2zQl.Q_4wBj&I2]V _Ct1nGLWP*c7><\T*kZ\.?MÉ)@>9;+븆38͕/1E ]q^6,Tv& =L*" IENDB`pyface-4.1.0/pyface/dock/images/tab_feature_changed.png0000644000175100001440000000063711674463363024063 0ustar ischnellusers00000000000000PNG  IHDRa pHYs  ~QIDATx1OSQ_+նH{$DIqЁ(lG1O@Bsu9 Or>{#20~6U(Tx7 u(OLYKRI}aMQɃ q_'I}Yܗ%S 4ayԟ''X'ܯ</9(%TPW~Fm(([Mz~0^8[s 6+E/&ڨNس_3] ,26aΫM3vy#x4سބ_bUA.I=L|hh4UD&X G :,ZUBM@s΅o*R/IENDB`pyface-4.1.0/pyface/dock/images/bar_feature_changed.png0000644000175100001440000000044411674463363024055 0ustar ischnellusers00000000000000PNG  IHDRRW pHYsodIDATx4zQ) 7# 0   ƭ] E_V#\VTa-  , 2)z\ ')$22+1d3QIENDB`pyface-4.1.0/pyface/dock/images/tab_scroll_lr.png0000644000175100001440000000035111674463363022743 0ustar ischnellusers00000000000000PNG  IHDR&(ۙ pHYsodIDATx QsQ7=\+CzH1PJœ|<䄋Øm:oF!"p%ő54qU:Qpp9&jHg8l dmZEq˹Uq*[>GEϩu'S(wIENDB`pyface-4.1.0/pyface/dock/images/shell.ico0000644000175100001440000000257611674463363021232 0ustar ischnellusers00000000000000h( Ni6Iݻg*m)p;Uj*0Y*u>|C8\*:£zV+AY+liwzKÏPS$n)Wx@Xx@ӭxvhk*aҤznׯHǽBCDzo}Dc*Kt=&zo)h-./m9˙l*JbƏԫyX+yAX+^*Klp:g)rga*"[+Oj)~DSd*66666666666666=RAu:TDOhOI#Ree+HSy!b#FR31`%nYvUR{r cG[%%C?vtRm0 q;/EZ,J$oRa"pM>X&?$oR*BjPwN)dtoRg(K0] |9oWR` 8WWRRR4~2'RRRWizlxx}7.VV5f^<@@@\Q-VklxLxLs _ Vpyface-4.1.0/pyface/dock/images/image_LICENSE.txt0000644000175100001440000000352111674463363022403 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Eclipse Eclipse Public License image_LICENSE_Eclipse.txt Enthought BSD 3-Clause LICENSE.txt GV (Gael Varoquaux) Public Domain N/A OOo LGPL image_LICENSE_OOo.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/dock/images: bar_feature_changed.png | Enthought bar_feature_disabled.png | Enthought bar_feature_drop.png | Enthought bar_feature_no_drop.png | Enthought bar_feature_normal.png | Enthought close_drag.png | GV close_tab.png | GV feature_tool.png | GV sh_bottom.png | Enthought sh_middle.png | Enthought sh_top.png | Enthought shell.ico | Enthought sv_left.png | Enthought sv_middle.png | Enthought sv_right.png | Enthought tab_feature_changed.png | Enthought tab_feature_disabled.png | Enthought tab_feature_drop.png | Enthought tab_feature_no_drop.png | OOo tab_feature_normal.png | OOo tab_scroll_l.png | Enthought tab_scroll_lr.png | Enthought tab_scroll_r.png | Enthought window.png | Eclipse pyface-4.1.0/pyface/dock/images/feature_tool.png0000644000175100001440000000277511674463363022626 0ustar ischnellusers00000000000000PNG  IHDR?T/sBIT|d pHYs : :dJtEXtSoftwarewww.inkscape.org<zIDATH[LUg^`łFiZH[k&ꃱO>h/Mև&64i&5b$[ lRB/bҲla/{e+ ɞw0kؘn2y)8/,ۍ\E2p CQʳ9/c 1d``HcY+Ͽ~PU}a)+ o dYlppn&pn(ehDHr>r)>9Q,I*LŘƲ71p۝d>NDqt*rݯ^ tBאN$uAg8$[as%<-yg"D{ztWC}"K~d('2 @@BHn\cIB5p]}A} t!)ҔԈ腎om-vH- [ Df,ҋvXg(WeFKwG94_d$Y /4Vb=Ķ|Ft4tY8S捥0t"<6raQVZMvK#˘-Ҽ[!g-emЅ?u(1v{4^>ax'?=>u֊ei',v ^c}7;N(j]ɖDBtr,?Ad[ڀ`0:veL"_j_2[ IgDX8vل߮-Z£|p"JR*W#8hk)3b$Dv+ {o؝qF];òx_rIC]mQ ZdBێ8/5G@<60P QRU š&b!sɝm5\c ^lˑ&Wsp,ZZj,ƺuՑLjtV4S4 XϤZoo{zBMX,Zp $Is$q'tGW/&'opp,7QAx\QtIN?Ztnl쐧D(P9!S ݊ĆN`l(X9TW)JEć˼+"949_E1 IENDB`pyface-4.1.0/pyface/dock/images/tab_feature_normal.png0000644000175100001440000000063411674463363023757 0ustar ischnellusers00000000000000PNG  IHDRa pHYsodNIDATx=HQгh\J)m, ZJ!!XFDEEB*F#`?i¸A 3yIs"I'+ONYDBjWܬxټ P(?vï-I:{,j?nY\Ţ* dV52=f8d@tn,ޠQ^3Q jЂ8 ħ*L vTߛ5Kv5&tj?_[|4"Pؼ,Z bz>˽x֒SHp-K9t;\x^W) H6ٌP*nb V܅#IENDB`pyface-4.1.0/pyface/dock/images/tab_feature_disabled.png0000644000175100001440000000075611674463363024243 0ustar ischnellusers00000000000000PNG  IHDRa pHYs  ~IDATx?kPGϓ7j~⚎vt ޻9dH1:t`: :c8PJFP:4yuƕ,y"V^ۻ5SW Q|tj`Q$O4=勚l+օo?Ol+Jav!Lg5Ѕ{R:&RVvx`6 7/fٮeY/d۶W? :e۶.P21$Ied2xe@YEpHLYӉFѻ4Mn(z9@sʷXoZϚNTz:N ~?3] Qvʹ0. IM6TA$b'pGi W|HV8DzIENDB`pyface-4.1.0/pyface/dock/images/tab_scroll_r.png0000644000175100001440000000035111674463363022567 0ustar ischnellusers00000000000000PNG  IHDRkz pHYsodIDATx  BW(t{ljbbAϻ"B4a+yF;?wNp9g"RԾib QpOQ瘨p$Ro`]E#\EqY*YUNZ>WS!W&X 9IENDB`pyface-4.1.0/pyface/dock/api.py0000644000175100001440000000325611674463363017301 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 10/18/2005 # #------------------------------------------------------------------------------- """ Pyface 'DockWindow' support. This package implements a Pyface 'dockable' window component that allows child windows to be reorganized within the DockWindow using drag and drop. The component also allows multiple sub-windows to occupy the same sub-region of the DockWindow, in which case each sub-window appears as a separate notebook-like tab within the region. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from dock_window \ import DockWindow, DockWindowHandler from dock_sizer \ import DockSizer, DockSection, DockRegion, DockControl, DockStyle, \ DOCK_LEFT, DOCK_RIGHT, DOCK_TOP, DOCK_BOTTOM, SetStructureHandler, \ add_feature, DockGroup from idockable \ import IDockable from idock_ui_provider \ import IDockUIProvider from ifeature_tool \ import IFeatureTool from dock_window_shell \ import DockWindowShell from dock_window_feature \ import DockWindowFeature pyface-4.1.0/pyface/dock/dock_window_feature.py0000644000175100001440000010624411674463363022553 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 07/03/2006 # #------------------------------------------------------------------------------- """ Implements the DockWindowFeature base class. A DockWindowFeature is an optional feature of a DockControl that can be dynamically contributed to the package. Whenever a DockControl is added to a DockWindow, each feature will be given the opportunity to add itself to the DockControl. Each feature is manifested as an image that appears on the DockControl tab (or drag bar). The user interacts wth the feature using mouse clicks and drag and drop operations (depending upon how the feature is implemented). """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from weakref \ import ref from traits.api \ import HasPrivateTraits, Instance, Int, Str, Bool, Property from traitsui.menu \ import Menu, Action from pyface.timer.api \ import do_later from pyface.image_resource \ import ImageResource from dock_window \ import DockWindow from dock_sizer \ import DockControl, add_feature from ifeature_tool \ import IFeatureTool #------------------------------------------------------------------------------- # 'DockWindowFeature' class: #------------------------------------------------------------------------------- class DockWindowFeature ( HasPrivateTraits ): """ Implements "features" on DockWindows. See "The DockWindowFeature Feature of DockWindows" document (.doc or .pdf) in pyface.doc for details on using this class. Traits are defined on each feature instance. One or more feature instances are created for each application component that a feature class applies to. A given feature class might or might not apply to a specific application component. The feature class determines whether it applies to an application component when the feature is activated, or when a new application component is added to the DockWindow (and the feature is already active). """ #--------------------------------------------------------------------------- # Class variables: #--------------------------------------------------------------------------- # A string value that is the user interface name of the feature as it # should appear in the DockWindow Features sub-menu (e.g., 'Connect'). An # empty string (the default) means that the feature does not appear in the # Features sub-menu and cannot be enabled or disabled by the user. Avoid # feature names that conflict with other, known features. feature_name = '' # An integer that specifies th current state of the feature # (0 = uninstalled, 1 = active, 2 = disabled). Usually you do not need to # change this value explicitly; DockWindows normally manages the value # automatically, setting it when the user enables or disables the feature. state = 0 # List of weak references to all current instances. instances = [] #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- #-- Public Traits -------------------------------------------------------------- # The DockControl instance associated with this feature. Note that features # not directly associated with application components, and instead are # associated with the DockControl object that manages an application # component. The DockControl object provides the feature with access to # information about the parent DockWindow object, other DockControl objects # contained within the same DockWindow, as well as the application # component. This trait is automatically set by the DockWindow when the # feature instance is created and associated with an application component. dock_control = Instance( DockControl ) #-- Public Traits (new defaults can be defined by subclasses) ------------------ # The image (icon) to display on the feature bar. If **None**, no image # is displayed. For images that never change, the value can be declared # statically in the class definition. However, the feature is free to # change the value at any time. Changing the value to a new # **ImageResource** object causes the associated image to be updated on the # feature bar. Setting the value to **None** removes the image from the # feature bar. image = Instance( ImageResource, allow_none = True ) # The tooltip to display when the pointer hovers over the image. The value # can be changed dynamically to reflect changes in the feature's state. tooltip = Str # The x-coordinate of a pointer event that occurred over the feature's # image. This can be used in cases where the event-handling for a feature is # sensitive to the position of the pointer relative to the feature image. # This is not normally the case, but the information is available if it is # needed. x = Int # The y-coordinate of a pointer event that occurred over the feature's # image. y = Int # A boolean value that specifies whether the shift key was being held down # when a mouse event occurred. shift_down = Bool( False ) # A boolean value that specifies whether the control key was being held down # when a mouse event occurred. control_down = Bool( False ) # A boolean value that specifies whether the alt key was being held down # when a mouse event occurred. alt_down = Bool( False ) #-- Private Traits ------------------------------------------------------------- # The current bitmap to display on the feature bar. bitmap = Property #-- Overridable Public Methods ------------------------------------------------- #--------------------------------------------------------------------------- # Handles the user left clicking on the feature image: #--------------------------------------------------------------------------- def click ( self ): """ Handles the user left-clicking on a feature image. This method is designed to be overridden by subclasses. The default implementation attempts to perform a 'quick drag' operation (see the 'quick_drag' method). Returns nothing. """ self.quick_drag() #--------------------------------------------------------------------------- # Handles the user right clicking on the feature image: #--------------------------------------------------------------------------- def right_click ( self ): """ Handles the user right-clicking on a feature image. This method is designed to be overridden by subclasses. The default implementation attempts to perform a 'quick drag' operation (see the 'quick_right_drag' method). Returns nothing. Typically, you override this method to display the feature's shortcut menu. """ self.quick_right_drag() #--------------------------------------------------------------------------- # Returns the object to be dragged when the user drags the feature image: #--------------------------------------------------------------------------- def drag ( self ): """ Returns the object to be dragged when the user drags a feature image. This method can be overridden by subclasses. If dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user drags the feature image # while holding down the 'Ctrl' key: #--------------------------------------------------------------------------- def control_drag ( self ): """ Returns the object to be dragged when the user drags a feature image while pressing the 'Ctrl' key. This method is designed to be overridden by subclasses. If control-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user drags the feature image # while holding down the 'Shift' key: #--------------------------------------------------------------------------- def shift_drag ( self ): """ Returns the object to be dragged when the user drags a feature image while pressing the 'Shift' key. This method is designed to be overridden by subclasses. If shift-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user drags the feature image # while holding down the 'Alt' key: #--------------------------------------------------------------------------- def alt_drag ( self ): """ Returns the object to be dragged when the user drags a feature image while pressing the 'Alt' key. This method is designed to be overridden by subclasses. If Alt-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user right mouse button drags # the feature image: #--------------------------------------------------------------------------- def right_drag ( self ): """ Returns the object to be dragged when the user right mouse button drags a feature image. This method can be overridden by subclasses. If right dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user right mouse button drags # the feature image while holding down the 'Ctrl' key: #--------------------------------------------------------------------------- def control_right_drag ( self ): """ Returns the object to be dragged when the user right mouse button drags a feature image while pressing the 'Ctrl' key. This method is designed to be overridden by subclasses. If right control-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user right mouse button drags # the feature image while holding down the 'Shift' key: #--------------------------------------------------------------------------- def shift_control_drag ( self ): """ Returns the object to be dragged when the user right mouse button drags a feature image while pressing the 'Shift' key. This method is designed to be overridden by subclasses. If right shift-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Returns the object to be dragged when the user right mouse button drags # the feature image while holding down the 'Alt' key: #--------------------------------------------------------------------------- def alt_right_drag ( self ): """ Returns the object to be dragged when the user right mouse button drags a feature image while pressing the 'Alt' key. This method is designed to be overridden by subclasses. If right Alt-dragging is supported by the feature, then the method returns the object to be dragged; otherwise it returns **None**. The default implementation returns **None**. """ return None #--------------------------------------------------------------------------- # Handles the user dropping a specified object on the feature image: #--------------------------------------------------------------------------- def drop ( self, object ): """ Handles the user dropping a specified object on a feature image. Parameters ---------- object : any object The object being dropped onto the feature image Returns ------- Nothing. Description ----------- This method is designed to be overridden by subclasses. It is called whenever the user drops an object on the feature's tab or drag bar image. This method can be called only if a previous call to **can_drop()** for the same object returned **True**. The default implementation does nothing. """ return #--------------------------------------------------------------------------- # Returns whether a specified object can be dropped on the feature image: #--------------------------------------------------------------------------- def can_drop ( self, object ): """ Returns whether a specified object can be dropped on a feature image. Parameters ---------- object : any object The object being dragged onto the feature image Returns ------- **True** if *object* is a valid object for the feature to process; **False** otherwise. Description ----------- This method is designed to be overridden by subclasses. It is called whenever the user drags an icon over the feature's tab or drag bar image. The method does not perform any processing on *object*; it only examines it. Processing of the object occurs in the **drop()** method, which is called when the user release the object over the feature's image, which typically occurs after the **can_drop()** method has indicated that the feature can process the object, by returning **True**. The default implementation returns **False**, indicating that the feature does not accept any objects for dropping. """ return False #--------------------------------------------------------------------------- # Performs any clean-up needed when the feature is being removed: #--------------------------------------------------------------------------- def dispose ( self ): """ Performs any clean-up needed when the feature is removed from its associated application component (for example, when the user disables the feature). This method is designed to be overridden by subclasses. The method performs any clean-up actions needed by the feature, such as closing files, removing trait listeners, and so on. The method does not return a result. The default implementation does nothing. """ pass #-- Public Methods ------------------------------------------------------------- #--------------------------------------------------------------------------- # Displays a pop-up menu: #--------------------------------------------------------------------------- def popup_menu ( self, menu ): """ Displays a shortcut menu. Parameters ---------- menu : traitsui.menu.Menu object The menu to be displayed Returns ------- Nothing. Description ----------- This helper method displays the shortcut menu specified by *menu* at a point near the feature object's current (x,y) value, as specified by the **x** and **y** traits. Normally, the (x,y) value contains the screen location where the user clicked on the feature's tab or drag bar image. The effect is that the menu is displayed near the feature's icon, with the pointer directly over the top menu option. """ window = self.dock_control.control.GetParent() wx, wy = window.GetScreenPosition() window.PopupMenuXY( menu.create_menu( window, self ), self.x - wx - 10, self.y - wy - 10 ) #--------------------------------------------------------------------------- # Refreshes the display of the feature image: #--------------------------------------------------------------------------- def refresh ( self ): """ Refreshes the display of the feature image. Returns ------- Nothing. Description ----------- This helper method requests the containing DockWindow to refresh the feature bar. """ self.dock_control.feature_changed = True #--------------------------------------------------------------------------- # Disables the feature: #--------------------------------------------------------------------------- def disable ( self ): """ Disables the feature. Returns ------- Nothing. Description ----------- This helper method temporarily disables the feature for the associated application component. The feature can be re-enabled by calling the **enable()** method. Disabling the feature removes the feature's icon from the feature bar, without actually deleting the feature (i.e., the **dispose()** method is not called). """ self._image = self.image self.image = None if self._image is not None: self.dock_control.feature_changed = True #--------------------------------------------------------------------------- # Enables the feature: #--------------------------------------------------------------------------- def enable ( self ): """ Enables the feature. Returns ------- Nothing. Description ----------- This helper method re-enables a previously disabled feature for its associated application component. Enabling a feature restores the feature bar icon that the feature displayed at the time it was disabled. """ self.image = self._image self._image = None if self.image is not None: self.dock_control.feature_changed = True #--------------------------------------------------------------------------- # Performs a quick drag and drop operation by displaying a pop-up menu # containing all targets that the feature's xxx_drag() method can be # dropped on. Selecting an item drops the item on the selected target. #--------------------------------------------------------------------------- def quick_drag ( self ): """ Performs a quick drag and drop operation by displaying a pop-up menu containing all targets that the feature's xxx_drag() method can be dropped on. Selecting an item drops the item on the selected target. """ # Get the object that would have been dragged: if self.control_down: object = self.control_drag() elif self.alt_down: object = self.alt_drag() elif self.shift_down: object = self.shift_drag() else: object = self.drag() # If there is an object, pop up the menu: if object is not None: self._quick_drag_menu( object ) #--------------------------------------------------------------------------- # Performs a quick drag and drop operation with the right mouse button by # displaying a pop-up menu containing all targets that the feature's # xxx_right_drag() method can be dropped on. Selecting an item drops the # item on the selected target. #--------------------------------------------------------------------------- def quick_right_drag ( self ): """ Performs a quick drag and drop operation with the right mouse button by displaying a pop-up menu containing all targets that the feature's xxx_right_drag() method can be dropped on. Selecting an item drops the item on the selected target. """ # Get the object that would have been dragged: if self.control_down: object = self.control_right_drag() elif self.alt_down: object = self.alt_right_drag() elif self.shift_down: object = self.shift_right_drag() else: object = self.right_drag() # If there is an object, pop up the menu: if object is not None: self._quick_drag_menu( object ) #-- Overridable Class Methods --------------------------------------------------- #--------------------------------------------------------------------------- # Returns a single new feature object or list of new feature objects for a # specified DockControl (or None if the feature does not apply to it): #--------------------------------------------------------------------------- def feature_for ( cls, dock_control ): """ Returns a single new feature object or list of new feature objects for a specified DockControl. Parameters ---------- dock_control : pyface.dock.api.DockControl The DockControl object that corresponds to the application component being added, or for which the feature is being enabled. Returns ------- An instance or list of instances of this class that will be associated with the application component; **None** if the feature does not apply to the application component. Description ----------- This class method is designed to be overridden by subclasses. Normally, a feature class determines whether it applies to an application component by examining the component to see if it is an instance of a certain class, supports a specified interface, or has trait attributes with certain types of metadata. The application component is available through the *dock_control.object* trait attribute. Note that it is possible for *dock_control.object* to be **None**. The default implementation for this method calls the **is_feature_for()** class method to determine whether the feature applies to the specified DockControl. If it does, it calls the **new_feature()** class method to create the feature instances to be returned. If it does not, it simply returns **None**. """ if cls.is_feature_for( dock_control): return cls.new_feature( dock_control ) return None feature_for = classmethod( feature_for ) #--------------------------------------------------------------------------- # Returns a new feature instance for a specified DockControl: #--------------------------------------------------------------------------- def new_feature ( cls, dock_control ): """ Returns a new feature instance for a specified DockControl. Parameters ---------- dock_control : pyface.dock.api.DockControl The DockControl object that corresponds to the application component being added, or for which the feature is being enabled. Returns ------- An instance or list of instances of this class to be associated with the application component; it can also return **None**. Description ----------- This method is designed to be overridden by subclasses. This method is called by the default implementation of the **feature_for()** class method to create the feature instances to be associated with the application component specified by *dock_control*. The default implementation returns the result of calling the class constructor as follows:: cls( dock_control=dock_control ) """ return cls( dock_control = dock_control ) new_feature = classmethod( new_feature ) #--------------------------------------------------------------------------- # Returns whether or not the DockWindowFeature is a valid feature for a # specified DockControl: #--------------------------------------------------------------------------- def is_feature_for ( self, dock_control ): """ Returns whether this class is a valid feature for the application object corresponding to a specified DockControl. Parameters ---------- dock_control : pyface.dock.api.DockControl The DockControl object that corresponds to the application component being added, or for which the feature is being enabled. Returns ------- **True** if the feature applies to the application object associated with the *dock_control*; **False** otherwise. Description ----------- This class method is designed to be overridden by subclasses. It is called by the default implementation of the **feature_for()** class method to determine whether the feature applies to the application object specified by *dock_control*. The default implementation always returns **True**. """ return True is_feature_for = classmethod( is_feature_for ) #-- Private Methods ------------------------------------------------------------ #--------------------------------------------------------------------------- # Sets the feature's 'event' traits for a specified mouse 'event': #--------------------------------------------------------------------------- def _set_event ( self, event ): """ Sets the feature's 'event' traits for a specified mouse 'event'. """ x, y = event.GetEventObject().GetScreenPosition() self.set( x = event.GetX() + x, y = event.GetY() + y, shift_down = event.ShiftDown(), control_down = event.ControlDown(), alt_down = event.AltDown() ) #--------------------------------------------------------------------------- # Displays the quick drag menu for a specified drag object: #--------------------------------------------------------------------------- def _quick_drag_menu ( self, object ): """ Displays the quick drag menu for a specified drag object. """ # Get all the features it could be dropped on: feature_lists = [] if isinstance( object, IFeatureTool ): msg = 'Apply to' for dc in self.dock_control.dock_controls: if (dc.visible and (object.feature_can_drop_on( dc.object ) or object.feature_can_drop_on_dock_control( dc ))): from feature_tool import FeatureTool feature_lists.append( [ FeatureTool( dock_control = dc ) ] ) else: msg = 'Send to' for dc in self.dock_control.dock_controls: if dc.visible: allowed = [ f for f in dc.features if (f.feature_name != '') and f.can_drop( object ) ] if len( allowed ) > 0: feature_lists.append( allowed ) # If there are any compatible features: if len( feature_lists ) > 0: # Create the pop-up menu: features = [] actions = [] for list in feature_lists: if len( list ) > 1: sub_actions = [] for feature in list: sub_actions.append( Action( name = '%s Feature' % feature.feature_name, action = "self._drop_on(%d)" % len( features ) ) ) features.append( feature ) actions.append( Menu( name = '%s the %s' % ( msg, feature.dock_control.name ), *sub_actions ) ) else: actions.append( Action( name = '%s %s' % ( msg, list[0].dock_control.name ), action = "self._drop_on(%d)" % len( features ) ) ) features.append( list[0] ) # Display the pop-up menu: self._object = object self._features = features self.popup_menu( Menu( name = 'popup', *actions ) ) self._object = self._features = None #--------------------------------------------------------------------------- # Drops the current object on the feature selected by the user (used by # the 'quick_drag' method: #--------------------------------------------------------------------------- def _drop_on ( self, index ): """ Drops the current object on the feature selected by the user. """ object = self._object if isinstance( object, IFeatureTool ): dc = self._features[ index ].dock_control object.feature_dropped_on( dc.object ) object.feature_dropped_on_dock_control( dc ) else: self._features[ index ].drop( object ) #-- Public Class Methods ------------------------------------------------------- #--------------------------------------------------------------------------- # Returns a feature object for use with the specified DockControl (or None # if the feature does not apply to the DockControl object): #--------------------------------------------------------------------------- def new_feature_for ( cls, dock_control ): """ Returns a feature object for use with the specified DockControl (or **None** if the feature does not apply to the DockControl object). """ result = cls.feature_for( dock_control ) if result is not None: cls.instances = [ aref for aref in cls.instances if aref() is not None ] if isinstance( result, DockWindowFeature ): result = [ result ] cls.instances.extend( [ ref( feature ) for feature in result ] ) return result new_feature_for = classmethod( new_feature_for ) #--------------------------------------------------------------------------- # Toggles the feature on/off: #--------------------------------------------------------------------------- def toggle_feature ( cls, event ): """ Toggles the feature on or off. """ if cls.state == 0: cls.state = 1 add_feature( cls ) for control in event.window.control.GetChildren(): window = getattr( control, 'owner', None ) if isinstance( window, DockWindow ): do_later( window.update_layout ) else: method = 'disable' cls.state = 3 - cls.state if cls.state == 1: method = 'enable' cls.instances = [ aref for aref in cls.instances if aref() is not None ] for aref in cls.instances: feature = aref() if feature is not None: getattr( feature, method )() toggle_feature = classmethod( toggle_feature ) #-- Event Handlers ------------------------------------------------------------- #--------------------------------------------------------------------------- # Handles the 'image' trait being changed: #--------------------------------------------------------------------------- def _image_changed ( self ): self._bitmap = None #-- Property Implementations --------------------------------------------------- def _get_bitmap ( self ): if (self._bitmap is None) and (self.image is not None): self._bitmap = self.image.create_image().ConvertToBitmap() return self._bitmap #-- Pyface menu interface implementation --------------------------------------- #--------------------------------------------------------------------------- # Adds a menu item to the menu bar being constructed: #--------------------------------------------------------------------------- def add_to_menu ( self, menu_item ): """ Adds a menu item to the menu bar being constructed. """ pass #--------------------------------------------------------------------------- # Adds a tool bar item to the tool bar being constructed: #--------------------------------------------------------------------------- def add_to_toolbar ( self, toolbar_item ): """ Adds a tool bar item to the tool bar being constructed. """ pass #--------------------------------------------------------------------------- # Returns whether the menu action should be defined in the user interface: #--------------------------------------------------------------------------- def can_add_to_menu ( self, action ): """ Returns whether the action should be defined in the user interface. """ return True #--------------------------------------------------------------------------- # Returns whether the toolbar action should be defined in the user # interface: #--------------------------------------------------------------------------- def can_add_to_toolbar ( self, action ): """ Returns whether the toolbar action should be defined in the user interface. """ return True #--------------------------------------------------------------------------- # Performs the action described by a specified Action object: #--------------------------------------------------------------------------- def perform ( self, action ): """ Performs the action described by a specified Action object. """ action = action.action if action[ : 5 ] == 'self.': eval( action, globals(), { 'self': self } ) else: getattr( self, action )() pyface-4.1.0/pyface/dock/dock_window_shell.py0000644000175100001440000001700111674463363022217 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 11/04/2005 # #------------------------------------------------------------------------------- """ Defines the DockWindowShell class used to house drag and drag DockWindow items that are dropped on the desktop or on the DockWindowShell window. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx # Fixme: Hack to force 'image_slice' to be added via Category to Theme class: import traitsui.wx from traits.api \ import HasPrivateTraits, Instance from traitsui.api \ import View, Group from pyface.api import SystemMetrics from pyface.image_resource \ import ImageResource from dock_window \ import DockWindow from dock_sizer \ import DockSizer, DockSection, DockRegion, DockControl, DOCK_RIGHT #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- # DockWindowShell frame icon: FrameIcon = ImageResource( 'shell.ico' ) #------------------------------------------------------------------------------- # 'DockWindowShell' class: #------------------------------------------------------------------------------- class DockWindowShell ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The wx.Frame window which is the actual shell: control = Instance( wx.Frame ) #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, dock_control, use_mouse = False, **traits ): super( DockWindowShell, self ).__init__( **traits ) old_control = dock_control.control parent = wx.GetTopLevelParent( old_control ) while True: next_parent = parent.GetParent() if next_parent is None: break parent = next_parent self.control = shell = wx.Frame( parent, -1, dock_control.name, style = wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT | wx.FRAME_NO_TASKBAR ) shell.SetIcon( FrameIcon.create_icon() ) shell.SetBackgroundColour( SystemMetrics().dialog_background_color ) wx.EVT_CLOSE( shell, self._on_close ) theme = dock_control.theme self._dock_window = dw = DockWindow( shell, auto_close = True, theme = theme ).set( style = 'tab' ) sizer = wx.BoxSizer( wx.VERTICAL ) sizer.Add( dw.control, 1, wx.EXPAND ) shell.SetSizer( sizer ) if use_mouse: x, y = wx.GetMousePosition() else: x, y = old_control.GetPositionTuple() x, y = old_control.GetParent().ClientToScreenXY( x, y ) dx, dy = old_control.GetSize() tis = theme.tab.image_slice tc = theme.tab.content tdy = theme.tab_active.image_slice.dy dx += (tis.xleft + tc.left + tis.xright + tc.right) dy += (tis.xtop + tc.top + tis.xbottom + tc.bottom + tdy) self.add_control( dock_control ) # Set the correct window size and position, accounting for the tab size # and window borders: shell.SetDimensions( x, y, dx, dy ) cdx, cdy = shell.GetClientSizeTuple() ex_dx = dx - cdx ex_dy = dy - cdy shell.SetDimensions( x - (ex_dx / 2) - tis.xleft - tc.left, y - ex_dy + (ex_dx / 2) - tdy - tis.xtop - tc.top, dx + ex_dx, dy + ex_dy ) shell.Show() #--------------------------------------------------------------------------- # Adds a new DockControl to the shell window: #--------------------------------------------------------------------------- def add_control ( self, dock_control ): """ Adds a new DockControl to the shell window. """ dw = self._dock_window.control dockable = dock_control.dockable # If the current DockControl should be closed, then do it: close = dockable.dockable_should_close() if close: dock_control.close( force = True ) # Create the new control: control = dockable.dockable_get_control( dw ) # If the DockControl was closed, then reset it to point to the new # control: if close: dock_control.set( control = control, style = 'tab' ) else: # Create a DockControl to describe the new control: dock_control = DockControl( control = control, name = dock_control.name, export = dock_control.export, style = 'tab', image = dock_control.image, closeable = True ) # Finish initializing the DockControl: dockable.dockable_init_dockcontrol( dock_control ) # Get the current DockSizer: sizer = dw.GetSizer() if sizer is None: # Create the initial sizer: dw.SetSizer( DockSizer( DockSection( contents = [ DockRegion( contents = [ dock_control ] ) ] ) ) ) else: # Sizer exists already, try to add the DockControl as a new # notebook tab. If the user has reorganized the layout, then just # dock it on the right side somewhere: section = sizer.GetContents() region = section.contents[0] if isinstance( region, DockRegion ): region.add( dock_control ) else: section.add( dock_control, region, DOCK_RIGHT ) # Force the control to update: dw.Layout() dw.Refresh() #--------------------------------------------------------------------------- # Handles the user attempting to close the window: #--------------------------------------------------------------------------- def _on_close ( self, event ): """ Handles the user attempting to close the window. """ window = self._dock_window.control section = window.GetSizer().GetContents() n = len( section.contents ) # Try to close each individual control: for control in section.get_controls(): control.close( layout = False ) # If some, but not all, were closed, make sure the window gets updated: if 0 < len( section.contents ) < n: window.Layout() window.Refresh() pyface-4.1.0/pyface/dock/feature_bar.py0000644000175100001440000004205611674463363021010 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2007, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 02/05/2007 # #------------------------------------------------------------------------------- """ Pyface 'FeatureBar' support. Defines the 'FeatureBar' class which displays and allows the user to interact with a set of DockWindowFeatures for a specified DockControl. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx from traits.api \ import HasPrivateTraits, Instance, Bool, Event, Color from pyface.wx.drag_and_drop \ import PythonDropTarget, PythonDropSource from dock_sizer \ import DockControl, FEATURE_EXTERNAL_DRAG from ifeature_tool \ import IFeatureTool #------------------------------------------------------------------------------- # 'FeatureBar' class: #------------------------------------------------------------------------------- class FeatureBar ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The wx.Window which is the parent for the FeatureBar: parent = Instance( wx.Window ) # The DockControl whose features are being displayed: dock_control = Instance( DockControl ) # The wx.Window being used for the FeatureBar: control = Instance( wx.Window ) # Event posted when the user has completed using the FeatureBar: completed = Event # The background color for the FeatureBar: bg_color = Color( 0xDBEEF7, allow_none = True ) # The border color for the FeatureBar: border_color = Color( 0X2583AF, allow_none = True ) # Should the feature bar display horizontally (or vertically)? horizontal = Bool( True ) #--------------------------------------------------------------------------- # Hides the feature bar: #--------------------------------------------------------------------------- def hide ( self ): """ Hides the feature bar. """ if self.control is not None: self.control.Hide() #--------------------------------------------------------------------------- # Shows the feature bar: #--------------------------------------------------------------------------- def show ( self ): """ Shows the feature bar. """ # Make sure all prerequisites are met: dock_control, parent = self.dock_control, self.parent if (dock_control is None) or (parent is None): return # Create the actual control (if needed): control = self.control if control is None: self.control = control = wx.Frame( None, -1, '', style = wx.BORDER_NONE ) # Set up the 'erase background' event handler: wx.EVT_ERASE_BACKGROUND( control, self._erase_background ) # Set up the 'paint' event handler: wx.EVT_PAINT( control, self._paint ) # Set up mouse event handlers: wx.EVT_LEFT_DOWN( control, self._left_down ) wx.EVT_LEFT_UP( control, self._left_up ) wx.EVT_RIGHT_DOWN( control, self._right_down ) wx.EVT_RIGHT_UP( control, self._right_up ) wx.EVT_MOTION( control, self._mouse_move ) wx.EVT_ENTER_WINDOW( control, self._mouse_enter ) control.SetDropTarget( PythonDropTarget( self ) ) # Calculate the best size and position for the feature bar: size = wx.Size( 32, 32 ) width = height = 0 horizontal = self.horizontal for feature in dock_control.active_features: bitmap = feature.bitmap if bitmap is not None: if horizontal: width += (bitmap.GetWidth() + 3) height = max( height, bitmap.GetHeight() ) else: width = max( width, bitmap.GetWidth() ) height += (bitmap.GetHeight() + 3) if width > 0: if horizontal: size = wx.Size( width + 5, height + 8 ) else: size = wx.Size( width + 8, height + 5 ) control.SetSize( size ) px, py = parent.GetScreenPosition() fx, fy = dock_control.feature_popup_position control.SetPosition( wx.Point( px + fx, py + fy ) ) control.Show() #-- Window Event Handlers -------------------------------------------------- #--------------------------------------------------------------------------- # Handles repainting the window: #--------------------------------------------------------------------------- def _paint ( self, event ): """ Handles repainting the window. """ window = self.control dx, dy = window.GetSizeTuple() dc = wx.PaintDC( window ) # Draw the feature container: bg_color = self.bg_color border_color = self.border_color if (bg_color is not None) or (border_color is not None): if border_color is None: dc.SetPen( wx.TRANSPARENT_PEN ) else: dc.SetPen( wx.Pen( border_color, 1, wx.SOLID ) ) if bg_color is None: dc.SetBrush( wx.TRANSPARENT_PEN ) else: dc.SetBrush( wx.Brush( bg_color, wx.SOLID ) ) dc.DrawRectangle( 0, 0, dx, dy ) # Draw the feature icons: if self.horizontal: x = 4 for feature in self.dock_control.active_features: bitmap = feature.bitmap if bitmap is not None: dc.DrawBitmap( bitmap, x, 4, True ) x += (bitmap.GetWidth() + 3) else: y = 4 for feature in self.dock_control.active_features: bitmap = feature.bitmap if bitmap is not None: dc.DrawBitmap( bitmap, 4, y, True ) y += (bitmap.GetHeight() + 3) #--------------------------------------------------------------------------- # Handles erasing the window background: #--------------------------------------------------------------------------- def _erase_background ( self, event ): """ Handles erasing the window background. """ pass #--------------------------------------------------------------------------- # Handles the left mouse button being pressed: #--------------------------------------------------------------------------- def _left_down ( self, event ): """ Handles the left mouse button being pressed. """ self._feature = self._feature_at( event ) self._dragging = False self._xy = ( event.GetX(), event.GetY() ) #self.control.CaptureMouse() #--------------------------------------------------------------------------- # Handles the left mouse button being released: #--------------------------------------------------------------------------- def _left_up ( self, event ): """ Handles the left mouse button being released. """ #self.control.ReleaseMouse() self._dragging = None feature, self._feature = self._feature, None if feature is not None: if feature is self._feature_at( event ): self.control.ReleaseMouse() self.completed = True feature._set_event( event ) feature.click() #--------------------------------------------------------------------------- # Handles the right mouse button being pressed: #--------------------------------------------------------------------------- def _right_down ( self, event ): """ Handles the right mouse button being pressed. """ self._feature = self._feature_at( event ) self._dragging = False self._xy = ( event.GetX(), event.GetY() ) #self.control.CaptureMouse() #--------------------------------------------------------------------------- # Handles the right mouse button being released: #--------------------------------------------------------------------------- def _right_up ( self, event ): """ Handles the right mouse button being released. """ #self.control.ReleaseMouse() self._dragging = None feature, self._feature = self._feature, None if feature is not None: if feature is self._feature_at( event ): self.control.ReleaseMouse() self.completed = True feature._set_event( event ) feature.right_click() #--------------------------------------------------------------------------- # Handles the mouse moving over the window: #--------------------------------------------------------------------------- def _mouse_move ( self, event ): """ Handles the mouse moving over the window. """ # Update tooltips if no mouse button is currently pressed: if self._dragging is None: feature = self._feature_at( event ) if feature is not self._tooltip_feature: self._tooltip_feature = feature tooltip = '' if feature is not None: tooltip = feature.tooltip wx.ToolTip.Enable( False ) wx.ToolTip.Enable( True ) self.control.SetToolTip( wx.ToolTip( tooltip ) ) # Check to see if the mouse has left the window, and mark it # completed if it has: x, y = event.GetX(), event.GetY() dx, dy = self.control.GetSizeTuple() if (x < 0) or (y < 0) or (x >= dx) or (y >= dy): self.control.ReleaseMouse() self._tooltip_feature = None self.completed = True return # Check to see if we are in 'drag mode' yet: if not self._dragging: x, y = self._xy if (abs( x - event.GetX() ) + abs( y - event.GetY() )) < 3: return self._dragging = True # Check to see if user is trying to drag a 'feature': feature = self._feature if feature is not None: feature._set_event( event ) prefix = button = '' if event.RightIsDown(): button = 'right_' if event.ControlDown(): prefix = 'control_' elif event.AltDown(): prefix = 'alt_' elif event.ShiftDown(): prefix = 'shift_' object = getattr( feature, '%s%sdrag' % ( prefix, button ) )() if object is not None: self.control.ReleaseMouse() self._feature = None self.completed = True self.dock_control.pre_drag_all( object ) PythonDropSource( self.control, object ) self.dock_control.post_drag_all() self._dragging = None #--------------------------------------------------------------------------- # Handles the mouse entering the window: #--------------------------------------------------------------------------- def _mouse_enter ( self, event ): """ Handles the mouse entering the window. """ self.control.CaptureMouse() #-- Drag and drop event handlers: ---------------------------------------------- #--------------------------------------------------------------------------- # Handles a Python object being dropped on the control: #--------------------------------------------------------------------------- def wx_dropped_on ( self, x, y, data, drag_result ): """ Handles a Python object being dropped on the window. """ # Determine what, if any, feature the object was dropped on: feature = self._can_drop_on_feature( x, y, data ) # Indicate use of the feature bar is complete: self.completed = True # Reset any drag state information: self.dock_control.post_drag( FEATURE_EXTERNAL_DRAG ) # Check to see if the data was dropped on a feature or not: if feature is not None: if isinstance( data, IFeatureTool ): # Handle an object implementing IFeatureTool being dropped: dock_control = feature.dock_control data.feature_dropped_on_dock_control( dock_control ) data.feature_dropped_on( dock_control.object ) else: # Handle a normal object being dropped: wx, wy = self.control.GetScreenPosition() feature.set( x = wx + x, y = wy + y ) feature.drop( data ) return drag_result return wx.DragNone #--------------------------------------------------------------------------- # Handles a Python object being dragged over the control: #--------------------------------------------------------------------------- def wx_drag_over ( self, x, y, data, drag_result ): """ Handles a Python object being dragged over the control. """ # Handle the case of dragging a normal object over a 'feature': if self._can_drop_on_feature( x, y, data ) is not None: return drag_result return wx.DragNone #--------------------------------------------------------------------------- # Handles a dragged Python object leaving the window: #--------------------------------------------------------------------------- def wx_drag_leave ( self, data ): """ Handles a dragged Python object leaving the window. """ # Indicate use of the feature bar is complete: self.completed = True # Reset any drag state information: self.dock_control.post_drag( FEATURE_EXTERNAL_DRAG ) #-- Private Methods -------------------------------------------------------- #--------------------------------------------------------------------------- # Returns a feature that the pointer is over and which can accept the # specified data: #--------------------------------------------------------------------------- def _can_drop_on_feature ( self, x, y, data ): """ Returns a feature that the pointer is over and which can accept the specified data. """ feature = self._feature_at( FakeEvent( x, y ) ) if (feature is not None) and feature.can_drop( data ): return feature return None #--------------------------------------------------------------------------- # Returns the DockWindowFeature (if any) at a specified window position: #--------------------------------------------------------------------------- def _feature_at ( self, event ): """ Returns the DockWindowFeature (if any) at a specified window position. """ if self.horizontal: x = 4 for feature in self.dock_control.active_features: bitmap = feature.bitmap if bitmap is not None: bdx = bitmap.GetWidth() if self._is_in( event, x, 4, bdx, bitmap.GetHeight() ): return feature x += (bdx + 3) else: y = 4 for feature in self.dock_control.active_features: bitmap = feature.bitmap if bitmap is not None: bdy = bitmap.GetHeight() if self._is_in( event, 4, y, bitmap.GetWidth(), bdy ): return feature y += (bdy + 3) return None #--------------------------------------------------------------------------- # Returns whether or not an event is within a specified bounds: #--------------------------------------------------------------------------- def _is_in ( self, event, x, y, dx, dy ): """ Returns whether or not an event is within a specified bounds. """ return ((x <= event.GetX() < (x + dx)) and (y <= event.GetY() < (y + dy))) #------------------------------------------------------------------------------- # 'FakeEvent' class: #------------------------------------------------------------------------------- class FakeEvent ( object ): def __init__ ( self, x, y ): self.x, self.y = x, y def GetX ( self ): return self.x def GetY ( self ): return self.y pyface-4.1.0/pyface/dock/idockable.py0000644000175100001440000001161411674463363020442 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 12/14/2005 # #------------------------------------------------------------------------------- """ Defines the IDockable interface which objects contained in a DockWindow DockControl can implement in order to allow themselves to be dragged into a different DockWindow. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx #------------------------------------------------------------------------------- # 'IDockable' class: #------------------------------------------------------------------------------- class IDockable ( object ): #--------------------------------------------------------------------------- # Should the current DockControl be closed before creating the new one: #--------------------------------------------------------------------------- def dockable_should_close ( self ): """ Should the current DockControl be closed before creating the new one. """ return True #--------------------------------------------------------------------------- # Returns whether or not it is OK to close the control, and if it is OK, # then it closes the DockControl itself: #--------------------------------------------------------------------------- def dockable_close ( self, dock_control, force ): """ Returns whether or not it is OK to close the control. """ return False #--------------------------------------------------------------------------- # Gets a control that can be docked into a DockWindow: #--------------------------------------------------------------------------- def dockable_get_control ( self, parent ): """ Gets a control that can be docked into a DockWindow. """ print "The 'IDockable.dockable_get_control' method must be overridden" panel = wx.Panel( parent, -1 ) panel.SetBackgroundColour( wx.RED ) return panel #--------------------------------------------------------------------------- # Allows the object to override the default DockControl settings: #--------------------------------------------------------------------------- def dockable_init_dockcontrol ( self, dock_control ): """ Allows the object to override the default DockControl settings. """ pass #--------------------------------------------------------------------------- # Returns the right-click popup menu for a DockControl (if any): #--------------------------------------------------------------------------- def dockable_menu ( self, dock_control, event ): """ Returns the right-click popup menu for a DockControl (if any). """ return None #--------------------------------------------------------------------------- # Handles the user double-clicking on the DockControl: # A result of False indicates the event was not handled; all other results # indicate that the event was handled successfully. #--------------------------------------------------------------------------- def dockable_dclick ( self, dock_control, event ): """ Handles the user double-clicking on the DockControl. A result of False indicates the event was not handled; all other results indicate that the event was handled successfully. """ return False #--------------------------------------------------------------------------- # Handles a notebook tab being activated or deactivated: #--------------------------------------------------------------------------- def dockable_tab_activated ( self, dock_control, activated ): """ Handles a notebook tab being activated or deactivated. 'dock_control' is the control being activated or deactivated. If 'activated' is True, the control is being activated; otherwise the control is being deactivated. """ pass #--------------------------------------------------------------------------- # Handles the IDockable being bound to a specified DockControl: #--------------------------------------------------------------------------- def dockable_bind ( self, dock_control ): """ Handles the dockable being bound to a specified DockControl. """ pass pyface-4.1.0/pyface/dock/feature_tool.py0000644000175100001440000000363311674463363021217 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2006, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 07/04/2006 # #------------------------------------------------------------------------------- """ Implements the FeatureTool feature that allows a dragged object implementing the IFeatureTool interface to be dropped onto any compatible object. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- from dock_window_feature \ import DockWindowFeature from pyface.image_resource \ import ImageResource #------------------------------------------------------------------------------- # 'FeatureTool' class: #------------------------------------------------------------------------------- class FeatureTool ( DockWindowFeature ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- image = ImageResource( 'feature_tool' ) #--------------------------------------------------------------------------- # Returns whether a specified object can be dropped on the feature image: #--------------------------------------------------------------------------- def can_drop ( self, object ): """ Returns whether a specified object can be dropped on the feature image. """ return True pyface-4.1.0/pyface/dock/__init__.py0000644000175100001440000000177411674463363020272 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 10/18/2005 # #------------------------------------------------------------------------------- """ Pyface 'DockWindow' support. This package implements a Pyface 'dockable' window component that allows child windows to be reorganized within the DockWindow using drag and drop. The component also allows multiple sub-windows to occupy the same sub-region of the DockWindow, in which case each sub-window appears as a separate notebook-like tab within the region. """ pyface-4.1.0/pyface/dock/ifeature_tool.py0000644000175100001440000000602611674463363021367 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2007, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 02/07/2007 # #------------------------------------------------------------------------------- """ Defines the IFeatureTool interface which allows objects dragged using the DockWindowFeature API to control the drag target and drop events. Useful for implementing tools which can be dropped onto compatible view objects. """ #------------------------------------------------------------------------------- # 'IFeatureTool' class: #------------------------------------------------------------------------------- class IFeatureTool ( object ): #--------------------------------------------------------------------------- # Returns whether or not the object being dragged (i.e. self) can be # dropped on the specified target object: #--------------------------------------------------------------------------- def feature_can_drop_on ( self, object ): """ Returns whether or not the object being dragged (i.e. self) can be dropped on the specified target object. """ return False #--------------------------------------------------------------------------- # Returns whether or not the object being dragged (i.e. self) can be # dropped on the specified target object's DockControl: #--------------------------------------------------------------------------- def feature_can_drop_on_dock_control ( self, dock_control ): """ Returns whether or not the object being dragged (i.e. self) can be dropped on the specified target object's DockControl. """ return False #--------------------------------------------------------------------------- # Allows the dragged object (i.e. self) to handle being dropped on the # specified target object: #--------------------------------------------------------------------------- def feature_dropped_on ( self, object ): """ Allows the dragged object (i.e. self) to handle being dropped on the specified target object. """ return #--------------------------------------------------------------------------- # Allows the dragged object (i.e. self) to handle being dropped on the # specified target object's DockControl: #--------------------------------------------------------------------------- def feature_dropped_on_dock_control ( self, dock_control ): """ Allows the dragged object (i.e. self) to handle being dropped on the specified target object's DockControl. """ return pyface-4.1.0/pyface/dock/dock_sizer.py0000644000175100001440000045703511674463363020674 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # # Author: David C. Morrill # Date: 10/18/2005 # #------------------------------------------------------------------------------- """ Pyface 'DockSizer' support. This package implements the sizer associated with a Pyface DockWindow component. The sizer manages the layout of the DockWindow child controls and the notebook tabs and dragbars associated with the DockWindow. """ #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx, sys from traits.api \ import HasPrivateTraits, Instance, Str, Int, List, Enum, Tuple, Any, \ Range, Property, Callable, Constant, Event, Undefined, Bool, \ cached_property from traitsui.dock_window_theme \ import dock_window_theme from traitsui.wx.helper \ import BufferDC from pyface.api import SystemMetrics from pyface.image_resource \ import ImageResource from pyface.wx.drag_and_drop \ import PythonDropSource from pyface.timer.api \ import do_later, do_after from idockable \ import IDockable from ifeature_tool \ import IFeatureTool # Define version dependent values: wx_26 = (wx.__version__[:3] == '2.6') is_mac = (sys.platform == 'darwin') #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- # Standard font text height: text_dy = 13 # Maximum allowed length of a tab label: MaxTabLength = 30 # Size of a drag bar (in pixels): DragBarSize = 14 # Images sizes (in pixels): CloseTabSize = 10 CloseDragSize = 7 # Tab drawing states: TabInactive = 0 TabActive = 1 TabHover = 2 NormalStates = ( TabInactive, TabActive ) NotActiveStates = ( TabInactive, TabHover ) # Feature overlay colors: FeatureBrushColor = ( 255, 255, 255 ) FeaturePenColor = ( 92, 92, 92 ) # Color used to update the screen while dragging a splitter bar: DragColor = ( 96, 96, 96 ) # Color used to update the screen while showing a docking operation in progress: DockColorBrush = ( 255, 0, 0, 96 ) # Drop Info kinds: DOCK_TOP = 0 DOCK_BOTTOM = 1 DOCK_LEFT = 2 DOCK_RIGHT = 3 DOCK_TAB = 4 DOCK_TABADD = 5 DOCK_BAR = 6 DOCK_NONE = 7 DOCK_SPLITTER = 8 DOCK_EXPORT = 9 # Splitter states: SPLIT_VLEFT = 0 SPLIT_VMIDDLE = 1 SPLIT_VRIGHT = 2 SPLIT_HTOP = 3 SPLIT_HMIDDLE = 4 SPLIT_HBOTTOM = 5 # Empty clipping area: no_clip = ( 0, 0, 0, 0 ) # Valid sequence types: SequenceType = ( list, tuple ) # Tab scrolling directions: SCROLL_LEFT = 1 SCROLL_RIGHT = 2 SCROLL_TO = 3 # Feature modes: FEATURE_NONE = -1 # Has no features FEATURE_NORMAL = 0 # Has normal features FEATURE_CHANGED = 1 # Has changed or new features FEATURE_DROP = 2 # Has drag data compatible drop features FEATURE_DISABLED = 3 # Has feature icon, but is currently disabled FEATURE_VISIBLE = 4 # Has visible features (mouseover mode) FEATURE_DROP_VISIBLE = 5 # Has visible drop features (mouseover mode) FEATURE_PRE_NORMAL = 6 # Has normal features (but has not been drawn yet) FEATURE_EXTERNAL_DRAG = 256 # A drag started in another DockWindow is active # Feature sets: NO_FEATURE_ICON = ( FEATURE_NONE, FEATURE_DISABLED, FEATURE_VISIBLE, FEATURE_DROP_VISIBLE ) FEATURES_VISIBLE = ( FEATURE_VISIBLE, FEATURE_DROP_VISIBLE ) FEATURE_END_DROP = ( FEATURE_DROP, FEATURE_VISIBLE, FEATURE_DROP_VISIBLE ) NORMAL_FEATURES = ( FEATURE_NORMAL, FEATURE_DISABLED ) #------------------------------------------------------------------------------- # Global data: #------------------------------------------------------------------------------- # Standard font used by the DockWindow: standard_font = None # The list of available DockWindowFeatures: features = [] #------------------------------------------------------------------------------- # Trait definitions: #------------------------------------------------------------------------------- # Bounds (i.e. x, y, dx, dy): Bounds = Tuple( Int, Int, Int, Int ) # Docking drag bar style: DockStyle = Enum( 'horizontal', 'vertical', 'tab', 'fixed' ) #------------------------------------------------------------------------------- # Adds a new DockWindowFeature class to the list of available features: #------------------------------------------------------------------------------- def add_feature ( feature_class ): """ Adds a new DockWindowFeature class to the list of available features. """ global features result = (feature_class not in features) if result: features.append( feature_class ) # Mark the feature class as having been installed: if feature_class.state == 0: feature_class.state = 1 return result #------------------------------------------------------------------------------- # Sets the standard font to use for a specified device context: #------------------------------------------------------------------------------- def set_standard_font ( dc ): """ Sets the standard font to use for a specified device context. """ global standard_font if standard_font is None: standard_font = wx.SystemSettings_GetFont( wx.SYS_DEFAULT_GUI_FONT ) dc.SetFont( standard_font ) return dc #------------------------------------------------------------------------------- # Clears a window to the standard background color: #------------------------------------------------------------------------------- def clear_window ( window ): """ Clears a window to the standard background color. """ bg_color = SystemMetrics().dialog_background_color bg_color = wx.Colour(bg_color[0]*255, bg_color[1]*255, bg_color[2]*255) dx, dy = window.GetSizeTuple() dc = wx.PaintDC( window ) dc.SetBrush( wx.Brush( bg_color, wx.SOLID ) ) dc.SetPen( wx.TRANSPARENT_PEN ) dc.DrawRectangle( 0, 0, dx, dy ) #------------------------------------------------------------------------------- # Gets a temporary device context for a specified window to draw in: #------------------------------------------------------------------------------- def get_dc ( window ): """ Gets a temporary device context for a specified window to draw in. """ if is_mac: dc = wx.ClientDC( window ) x, y = window.GetPositionTuple() dx, dy = window.GetSizeTuple() while True: window = window.GetParent() if window is None: break xw, yw = window.GetPositionTuple() dxw, dyw = window.GetSizeTuple() dx, dy = min( dx, dxw - x ), min( dy, dyw - y ) x += xw y += yw dc.SetClippingRegion( 0, 0, dx, dy ) return ( dc, 0, 0 ) x, y = window.ClientToScreenXY( 0, 0 ) return ( wx.ScreenDC(), x, y ) #------------------------------------------------------------------------------- # 'DockImages' class: #------------------------------------------------------------------------------- class DockImages ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Image for closing a tab: close_tab = Instance( ImageResource, ImageResource( 'close_tab' ) ) # Image for closing a drag bar: close_drag = Instance( ImageResource, ImageResource( 'close_drag' ) ) #--------------------------------------------------------------------------- # Initalizes the object: #--------------------------------------------------------------------------- def __init__ ( self, **traits ): """ Initializes the object. """ super( DockImages, self ).__init__( **traits ) self._lazy_init_done = False def init ( self ): """ Initializes the parts of the object that depend on the toolkit selection. """ # See if it has already been done. if self._lazy_init_done: return self._lazy_init_done = True self._close_tab = self.close_tab.create_image().ConvertToBitmap() self._close_drag = self.close_drag.create_image().ConvertToBitmap() self._splitter_images = [ ImageResource( name ).create_image().ConvertToBitmap() for name in [ 'sv_left', 'sv_middle', 'sv_right', 'sh_top', 'sh_middle', 'sh_bottom' ] ] self._tab_scroller_images = [ ImageResource( name ).create_image().ConvertToBitmap() for name in [ 'tab_scroll_l', 'tab_scroll_r', 'tab_scroll_lr' ] ] self._tab_scroller_dx = self._tab_scroller_images[0].GetWidth() self._tab_scroller_dy = self._tab_scroller_images[0].GetHeight() self._feature_images = [ ImageResource( name ).create_image().ConvertToBitmap() for name in [ 'tab_feature_normal', 'tab_feature_changed', 'tab_feature_drop', 'tab_feature_disabled', 'bar_feature_normal', 'bar_feature_changed', 'bar_feature_drop', 'bar_feature_disabled' ] ] self._tab_feature_width = self._feature_images[0].GetWidth() self._tab_feature_height = self._feature_images[0].GetHeight() self._bar_feature_width = self._feature_images[3].GetWidth() self._bar_feature_height = self._feature_images[3].GetHeight() #--------------------------------------------------------------------------- # Returns the splitter image to use for a specified splitter state: #--------------------------------------------------------------------------- def get_splitter_image ( self, state ): """ Returns the splitter image to use for a specified splitter state. """ return self._splitter_images[ state ] #--------------------------------------------------------------------------- # Returns the feature image to use for a specified feature state: #--------------------------------------------------------------------------- def get_feature_image ( self, state, is_tab = True ): """ Returns the feature image to use for a specified feature state. """ if is_tab: return self._feature_images[ state ] return self._feature_images[ state + 3 ] # Creates a singleton instance of the class: DockImages = DockImages() #------------------------------------------------------------------------------- # 'DockItem' class: #------------------------------------------------------------------------------- class DockItem ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The parent of this item: parent = Any # The DockWindow that owns this item: owner = Property( depends_on = 'parent' ) # Bounds of the item: bounds = Bounds # Current width of the item: width = Int( -1 ) # Current height of the item: height = Int( -1 ) # Bounds of the item's drag bar or tab: drag_bounds = Bounds # The current tab state: tab_state = Any # The tab displayable version of the control's UI name: tab_name = Property( depends_on = 'name' ) # Width of the item's tab: tab_width = Property( depends_on = 'control, tab_state, tab_name' ) # The DockWindowTheme for this item's DockWindow: theme = Property # The theme for the current tab state: tab_theme = Property # The current feature mode: feature_mode = Enum( FEATURE_NONE, FEATURE_NORMAL, FEATURE_CHANGED, FEATURE_DROP, FEATURE_VISIBLE, FEATURE_DROP_VISIBLE, FEATURE_DISABLED, FEATURE_PRE_NORMAL ) # The position where the feature popup should appear: feature_popup_position = Property # The list of features for this item: features = List # The list of drag data compatible drop features for this item: drop_features = List # Current active set of features: active_features = Property # The name of this item (implemented in subclasses): # name = Str # The control associated with this item (implemented in subclasses): # control = Instance( wx.Control ) #--------------------------------------------------------------------------- # Implementation of the 'owner' property: #--------------------------------------------------------------------------- def __init__(self, **kw): super(DockItem, self).__init__(**kw) @cached_property def _get_owner ( self ): if self.parent is None: return None return self.parent.owner #--------------------------------------------------------------------------- # Implementation of the 'tab_name' property: #--------------------------------------------------------------------------- @cached_property def _get_tab_name ( self ): name = self.name if len( name ) > MaxTabLength: name = '%s...%s' % ( name[ : MaxTabLength - 23 ], name[ -20: ] ) return name #--------------------------------------------------------------------------- # Implementation of the 'tab_width' property: #--------------------------------------------------------------------------- @cached_property def _get_tab_width ( self ): if self.control is None: return 0 self._is_tab = True # Calculate the size needed by the theme and margins: theme = self.tab_theme tw = (theme.image_slice.xleft + theme.image_slice.xright + theme.content.left + theme.content.right) # Add feature marker width: if self.feature_mode != FEATURE_NONE: tw += DockImages._tab_feature_width + 3 # Add text width: dc = set_standard_font( wx.ClientDC( self.control ) ) tw += dc.GetTextExtent( self.tab_name )[0] # Add custom image width: image = self.get_image() if image is not None: tw += (image.GetWidth() + 3) # Add close button width: if self.closeable: tw += (CloseTabSize + 6) # Return the computed width: return tw #--------------------------------------------------------------------------- # Implementation of the 'theme' property: #--------------------------------------------------------------------------- def _get_theme ( self ): if self.control is None: return dock_window_theme() return self.control.GetParent().owner.theme #--------------------------------------------------------------------------- # Implementation of the 'tab_theme' property: #--------------------------------------------------------------------------- def _get_tab_theme ( self ): if self.tab_state == TabInactive: return self.theme.tab_inactive if self.tab_state == TabActive: return self.theme.tab_active return self.theme.tab_hover #--------------------------------------------------------------------------- # Implementation of the 'active_features' property: #--------------------------------------------------------------------------- def _get_active_features ( self ): if len( self.drop_features ) > 0: return self.drop_features return self.features #--------------------------------------------------------------------------- # Implementation of the 'feature_popup_position' property: #--------------------------------------------------------------------------- def _get_feature_popup_position ( self ): x, y, dx, dy = self.drag_bounds return wx.Point( x + 5, y + 3 ) #--------------------------------------------------------------------------- # Returns whether or not the item is at a specified window position: #--------------------------------------------------------------------------- def is_at ( self, x, y, bounds = None ): """ Returns whether or not the item is at a specified window position. """ if bounds is None: bounds = self.bounds bx, by, bdx, bdy = bounds return ((bx <= x < (bx + bdx)) and (by <= y < (by + bdy))) #--------------------------------------------------------------------------- # Returns whether or not an event is within a specified bounds: #--------------------------------------------------------------------------- def is_in ( self, event, x, y, dx, dy ): """ Returns whether or not an event is within a specified bounds. """ return ((x <= event.GetX() < (x + dx)) and (y <= event.GetY() < (y + dy))) #--------------------------------------------------------------------------- # Sets the control's drag bounds: #--------------------------------------------------------------------------- def set_drag_bounds ( self, x, y, dx, dy ): """ Sets the control's drag bounds. """ bx, by, bdx, bdy = self.bounds self.drag_bounds = ( x, y, min( x + dx, bx + bdx ) - x, dy ) #--------------------------------------------------------------------------- # Gets the cursor to use when the mouse is over the item: #--------------------------------------------------------------------------- def get_cursor ( self, event ): """ Gets the cursor to use when the mouse is over the item. """ if self._is_tab and (not self._is_in_close( event )): return wx.CURSOR_ARROW return wx.CURSOR_HAND #--------------------------------------------------------------------------- # Gets the DockInfo object for a specified window position: #--------------------------------------------------------------------------- def dock_info_at ( self, x, y, tdx, is_control ): """ Gets the DockInfo object for a specified window position. """ if self.is_at( x, y, self.drag_bounds ): x, y, dx, dy = self.drag_bounds control = self if self._is_tab: if is_control: kind = DOCK_TABADD tab_bounds = ( x, y, dx, dy ) else: kind = DOCK_TAB tab_bounds = ( x - (tdx / 2), y, tdx, dy ) else: if is_control: kind = DOCK_TABADD tab_bounds = ( x, y, self.tab_width, dy ) else: kind = DOCK_TAB control = None tab_bounds = ( x + self.tab_width, y, tdx, dy ) return DockInfo( kind = kind, tab_bounds = tab_bounds, region = self.parent, control = control ) return None #--------------------------------------------------------------------------- # Prepares for drawing into a device context: #--------------------------------------------------------------------------- def begin_draw ( self, dc, ox = 0, oy = 0 ): """ Prepares for drawing into a device context. """ self._save_clip = dc.GetClippingBox() x, y, dx, dy = self.bounds dc.SetClippingRegion( x + ox, y + oy, dx, dy ) #--------------------------------------------------------------------------- # Terminates drawing into a device context: #--------------------------------------------------------------------------- def end_draw ( self, dc ): """ Terminates drawing into a device context. """ dc.DestroyClippingRegion() if self._save_clip != no_clip: dc.SetClippingRegion( *self._save_clip ) self._save_clip = None #--------------------------------------------------------------------------- # Handles the left mouse button being pressed: #--------------------------------------------------------------------------- def mouse_down ( self, event ): """ Handles the left mouse button being pressed. """ self._xy = ( event.GetX(), event.GetY() ) self._closing = self._is_in_close( event ) self._dragging = False #--------------------------------------------------------------------------- # Handles the left mouse button being released: #--------------------------------------------------------------------------- def mouse_up ( self, event ): """ Handles the left mouse button being released. """ # Handle the user closing a control: if self._closing: if self._is_in_close( event ): self.close() # Handle the completion of a dragging operation: elif self._dragging: window = event.GetEventObject() dock_info, self._dock_info = self._dock_info, None self.mark_bounds( False ) control = self # Check to see if the user is attempting to drag an entire notebook # region: if event.AltDown(): control = self.parent # If the parent is not a notebook, then use the parent's parent: if (isinstance( control, DockRegion ) and (not control.is_notebook)): control = control.parent # Make sure the target is not contained within the notebook # group we are trying to move: region = dock_info.region while region is not None: if region is control: # If it is, the operation is invalid, abort: return region = region.parent # Check to see if the user is attempting to copy the control: elif event.ControlDown(): owner = window.owner control = owner.handler.dock_control_for( *(owner.handler_args + ( window, control )) ) # Complete the docking maneuver: dock_info.dock( control, window ) # Handle the user clicking on a notebook tab to select it: elif (self._is_tab and self.is_at( event.GetX(), event.GetY(), self.drag_bounds )): self.parent.tab_clicked( self ) #--------------------------------------------------------------------------- # Handles the mouse moving while the left mouse button is pressed: #--------------------------------------------------------------------------- def mouse_move ( self, event ): """ Handles the mouse moving while the left mouse button is pressed. """ # Exit if control is 'fixed' or a 'close' is pending: if self._closing or self.locked or (self.style == 'fixed'): return window = event.GetEventObject() # Check to see if we are in 'drag mode' yet: if not self._dragging: x, y = self._xy if (abs( x - event.GetX() ) + abs( y - event.GetY() )) < 3: return self._dragging = True self._dock_info = no_dock_info self._dock_size = self.tab_width self.mark_bounds( True ) # Get the window and DockInfo object associated with the event: cur_dock_info = self._dock_info self._dock_info = dock_info = \ window.GetSizer().DockInfoAt( event.GetX(), event.GetY(), self._dock_size, event.ShiftDown() ) # If the DockInfo has not changed, then no update is needed: if ((cur_dock_info.kind == dock_info.kind) and (cur_dock_info.region is dock_info.region) and (cur_dock_info.bounds == dock_info.bounds) and (cur_dock_info.tab_bounds == dock_info.tab_bounds)): return # Make sure the new DockInfo is legal: region = self.parent if ((not event.ControlDown()) and (dock_info.region is region) and ((len( region.contents ) <= 1) or (DOCK_TAB <= dock_info.kind <= DOCK_BAR) and (dock_info.control is self))): self._dock_info = no_dock_info window.owner.set_cursor( wx.CURSOR_SIZING ) return # Draw the new region: dock_info.draw( window, self._drag_bitmap ) # If this is the start of an export (i.e. drag and drop) request: if ((dock_info.kind == DOCK_EXPORT) and (self.export != '') and (self.dockable is not None)): # Begin the drag and drop operation: self.mark_bounds( False ) window.owner.set_cursor( wx.CURSOR_ARROW ) window.owner.release_mouse() try: window._dragging = True if (PythonDropSource( window, self ).result in ( wx.DragNone, wx.DragCancel )): window.owner.handler.open_view_for( self ) finally: window._dragging = False else: # Update the mouse pointer as required: cursor = wx.CURSOR_SIZING if dock_info.kind == DOCK_BAR: cursor = wx.CURSOR_HAND window.owner.set_cursor( cursor ) #--------------------------------------------------------------------------- # Handles the mouse hovering over the item: #--------------------------------------------------------------------------- def hover_enter ( self, event ): """ Handles the mouse hovering over the item. """ if self._is_tab and (self.tab_state != TabActive): self._redraw_tab( TabHover ) #--------------------------------------------------------------------------- # Handles the mouse exiting from hovering over the item: #--------------------------------------------------------------------------- def hover_exit ( self, event ): """ Handles the mouse exiting from hovering over the item. """ if self._is_tab and (self.tab_state != TabActive): self._redraw_tab( TabInactive ) #--------------------------------------------------------------------------- # Marks/Unmarks the bounds of the bounding DockWindow: #--------------------------------------------------------------------------- def mark_bounds ( self, begin ): """ Marks/Unmarks the bounds of the bounding DockWindow. """ window = self.control.GetParent() if begin: dc, x, y = get_dc( window ) dx, dy = window.GetSize() dc2 = wx.MemoryDC() self._drag_bitmap = wx.EmptyBitmap( dx, dy ) dc2.SelectObject( self._drag_bitmap ) dc2.Blit( 0, 0, dx, dy, dc, x, y ) try: dc3 = wx.GCDC( dc2 ) dc3.SetBrush( wx.Brush( wx.Colour( 158, 166, 255, 64 ) ) ) dc3.SetPen( wx.TRANSPARENT_PEN ) dc3.DrawRectangle( 0, 0, dx, dy ) except AttributeError: pass dc.Blit( x, y, dx, dy, dc2, 0, 0 ) else: self._drag_bitmap = None if is_mac: top_level_window_for( window ).Refresh() else: window.Refresh() def get_bg_color(self): """ Gets the background color """ color = SystemMetrics().dialog_background_color return wx.Colour( color[0]*255, color[1]*255, color[2]*255 ) #--------------------------------------------------------------------------- # Fills a specified region with the control's background color: #--------------------------------------------------------------------------- def fill_bg_color ( self, dc, x, y, dx, dy ): """ Fills a specified region with the control's background color. """ dc.SetPen( wx.TRANSPARENT_PEN ) dc.SetBrush( wx.Brush( self.get_bg_color() ) ) dc.DrawRectangle( x, y, dx, dy ) #--------------------------------------------------------------------------- # Draws a notebook tab: #--------------------------------------------------------------------------- def draw_tab ( self, dc, state ): global text_dy """ Draws a notebook tab. """ x0, y0, dx, dy = self.drag_bounds tab_color = self.get_bg_color() if state == TabActive: pass elif state == TabInactive: r,g,b = tab_color.Get() tab_color.Set(max(0, r-20), max(0, g-20), max(0, b-20)) else: r,g,b = tab_color.Get() tab_color.Set(min(255, r+20), min(255, g+20), min(255, b+20)) self._is_tab = True self.tab_state = state theme = self.tab_theme slice = theme.image_slice bdc = BufferDC( dc, dx, dy ) self.fill_bg_color(bdc, 0, 0, dx, dy) if state == TabActive: # fill the tab bg with the desired color brush = wx.Brush(tab_color) bdc.SetBrush(brush) bdc.SetPen(wx.TRANSPARENT_PEN) bdc.DrawRectangle(0, 0, dx, dy) # Draw the left, top, and right side of a rectange around the tab pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) bdc.SetPen(pen) bdc.DrawLine(0,dy,0,0) #up bdc.DrawLine(0,0,dx,0) #right bdc.DrawLine(dx-1,0,dx-1,dy) #down pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNHIGHLIGHT)) bdc.SetPen(pen) bdc.DrawLine(1,dy,1,1) bdc.DrawLine(1,1,dx-2,1) bdc.DrawLine(dx-2,1,dx-2,dy) else: # fill the tab bg with the desired color brush = wx.Brush(tab_color) bdc.SetBrush(brush) bdc.SetPen(wx.TRANSPARENT_PEN) bdc.DrawRectangle(0, 3, dx, dy) # Draw the left, top, and right side of a rectange around the tab pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) bdc.SetPen(pen) bdc.DrawLine(0,dy,0,3) bdc.DrawLine(0,3,dx-1,3) bdc.DrawLine(dx-1,3,dx-1,dy) # Compute the initial drawing position: name = self.tab_name tdx, text_dy = dc.GetTextExtent( name ) tc = theme.content ox, oy = theme.label.left, theme.label.top y = (oy + ((dy + slice.xtop + tc.top - slice.xbottom - tc.bottom - text_dy) / 2)) x = ox + slice.xleft + tc.left mode = self.feature_mode if mode == FEATURE_PRE_NORMAL: mode = self.set_feature_mode( False ) # Draw the feature 'trigger' icon (if necessary): if mode != FEATURE_NONE: if mode not in FEATURES_VISIBLE: bdc.DrawBitmap( DockImages.get_feature_image( mode ), x, y, True ) x += (DockImages._tab_feature_width + 3) # Draw the image (if necessary): image = self.get_image() if image is not None: bdc.DrawBitmap( image, x, y, True ) x += (image.GetWidth() + 3) # Draw the text label: bdc.DrawText( name, x, y + 1 ) # Draw the close button (if necessary): if self.closeable: bdc.DrawBitmap( DockImages._close_tab, x + tdx + 5, y + 2, True ) # Copy the buffer to the display: bdc.copy( x0, y0 ) #--------------------------------------------------------------------------- # Draws a fixed drag bar: #--------------------------------------------------------------------------- def draw_fixed ( self, dc ): """ Draws a fixed drag bar. """ pass #--------------------------------------------------------------------------- # Draws a horizontal drag bar: #--------------------------------------------------------------------------- def draw_horizontal ( self, dc ): """ Draws a horizontal drag bar. """ self._is_tab = False x, y, dx, dy = self.drag_bounds self.fill_bg_color( dc, x, y, dx, dy ) pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNHILIGHT)) dc.SetPen(pen) dc.DrawLine(x, y, x+dx, y) dc.DrawLine(x, y+2, x+dx, y+2) #--------------------------------------------------------------------------- # Draws a vertical drag bar: #--------------------------------------------------------------------------- def draw_vertical ( self, dc ): """ Draws a vertical drag bar. """ self._is_tab = False x, y, dx, dy = self.drag_bounds self.fill_bg_color( dc, x, y, dx, dy ) pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNHILIGHT)) dc.SetPen(pen) dc.DrawLine(x, y, x, y+dy) dc.DrawLine(x+2, y, x+2, y+dy) #--------------------------------------------------------------------------- # Redraws the control's tab: #--------------------------------------------------------------------------- def _redraw_tab ( self, state = None ): if state is None: state = self.tab_state region = self.parent if region is not None: dc = set_standard_font( wx.ClientDC( self.control.GetParent() ) ) if region.is_notebook: dc.SetClippingRegion( *region._tab_clip_bounds ) self.draw_tab( dc, state ) dc.DestroyClippingRegion() else: self.draw_tab( dc, state ) #--------------------------------------------------------------------------- # Redraws the control's drag bar: #--------------------------------------------------------------------------- def _redraw_bar ( self ): dc = wx.ClientDC( self.control ) getattr( self, 'draw_' + self.style )( dc ) #--------------------------------------------------------------------------- # Redraws the control's tab or bar: #--------------------------------------------------------------------------- def _redraw_control ( self ): if self._is_tab: self._redraw_tab() else: self._redraw_bar() #--------------------------------------------------------------------------- # Returns the bounds of the close button (if any): #--------------------------------------------------------------------------- def _close_bounds ( self ): global text_dy if self.closeable and self._is_tab: x, y, dx, dy = self.drag_bounds theme = self.tab_theme slice = theme.image_slice tc = theme.content ox, oy = theme.label.left, theme.label.top # fixme: x calculation seems to be off by -1... return ( x + dx + ox - slice.xright - tc.right - CloseTabSize, y + oy + ((dy + slice.xtop + tc.top - slice.xbottom - tc.bottom - text_dy) / 2) + 3, CloseTabSize, CloseTabSize ) return ( 0, 0, 0, 0 ) #--------------------------------------------------------------------------- # Returns whether a specified window position is over the close button: #--------------------------------------------------------------------------- def _is_in_close ( self, event ): return self.is_in( event, *self._close_bounds() ) #--------------------------------------------------------------------------- # Sets/Returns the 'normal' feature mode for the control based on the # number of currently active features: #--------------------------------------------------------------------------- def set_feature_mode ( self, changed = True ): if (not changed) or (self.feature_mode != FEATURE_PRE_NORMAL): mode = FEATURE_DROP features = self.drop_features if len( features ) == 0: mode = FEATURE_NORMAL features = self.features for feature in features: if feature.bitmap is not None: if changed: self.feature_mode = FEATURE_CHANGED else: self.feature_mode = mode break else: self.feature_mode = FEATURE_DISABLED return self.feature_mode #--------------------------------------------------------------------------- # Returns whether or not a specified window position is over the feature # 'trigger' icon, and if so, triggers display of the feature icons: #--------------------------------------------------------------------------- def feature_activate ( self, event, drag_object = Undefined ): global text_dy if (self.feature_mode in NO_FEATURE_ICON) or (not self._is_tab): return False # In 'drag' mode, we may get the same coordinate over and over again. # We don't want to restart the timer, so exit now: exy = ( event.GetX(), event.GetY() ) if self._feature_popup_xy == exy: return True x, y, dx, dy = self.drag_bounds idx = DockImages._tab_feature_width idy = DockImages._tab_feature_height theme = self.tab_theme slice = theme.image_slice tc = theme.content ox, oy = theme.label.left, theme.label.top y += (oy + ((dy + slice.xtop + tc.top - slice.xbottom - tc.bottom - text_dy) / 2)) x += ox + slice.xleft + tc.left result = self.is_in( event, x, y, idx, idy ) # If the pointer is over the feature 'trigger' icon, save the event for # the popup processing: if result: # If this is part of a drag operation, prepare for drag mode: if drag_object is not Undefined: self.pre_drag( drag_object, FEATURE_EXTERNAL_DRAG ) # Schedule the popup for later: self._feature_popup_xy = exy do_after( 100, self._feature_popup ) return result #--------------------------------------------------------------------------- # Resets any pending feature popup: #--------------------------------------------------------------------------- def reset_feature_popup ( self ): self._feature_popup_xy = None #--------------------------------------------------------------------------- # Pops up the current features if a feature popup is still pending: #--------------------------------------------------------------------------- def _feature_popup ( self ): if self._feature_popup_xy is not None: # Set the new feature mode: if self.feature_mode == FEATURE_DROP: self.feature_mode = FEATURE_DROP_VISIBLE else: self.feature_mode = FEATURE_VISIBLE self.owner.feature_bar_popup( self ) self._feature_popup_xy = None else: self.post_drag( FEATURE_EXTERNAL_DRAG ) #--------------------------------------------------------------------------- # Finishes the processing of a feature popup: #--------------------------------------------------------------------------- def feature_bar_closed ( self ): if self.feature_mode == FEATURE_DROP_VISIBLE: self.feature_mode = FEATURE_DROP else: self.feature_mode = FEATURE_NORMAL do_later( self._redraw_control ) #--------------------------------------------------------------------------- # Handles all pre-processing before a feature is dragged: #--------------------------------------------------------------------------- def pre_drag_all ( self, object ): """ Prepare all DockControls in the associated DockWindow for being dragged over. """ for control in self.dock_controls: control.pre_drag( object ) self.pre_drag( object ) def pre_drag ( self, object, tag = 0 ): """ Prepare this DockControl for being dragged over. """ if (self.visible and (self.feature_mode != FEATURE_NONE) and (self._feature_mode is None)): if isinstance( object, IFeatureTool ): if (object.feature_can_drop_on( self.object ) or object.feature_can_drop_on_dock_control( self )): from feature_tool import FeatureTool self.drop_features = [ FeatureTool( dock_control = self ) ] else: self.drop_features = [ f for f in self.features if f.can_drop( object ) and (f.bitmap is not None) ] self._feature_mode = self.feature_mode + tag if len( self.drop_features ) > 0: self.feature_mode = FEATURE_DROP else: self.feature_mode = FEATURE_DISABLED self._redraw_control() #--------------------------------------------------------------------------- # Handles all post-processing after a feature has been dragged: #--------------------------------------------------------------------------- def post_drag_all ( self ): """ Restore all DockControls in the associated DockWindow after a drag operation is completed. """ for control in self.dock_controls: control.post_drag() self.post_drag() def post_drag ( self, tag = 0 ): """ Restore this DockControl after a drag operation is completed. """ if ((self._feature_mode is None) or (tag == 0) or ((self._feature_mode & tag) != 0)): self.drop_features = [] if self.feature_mode != FEATURE_NONE: if self._feature_mode is not None: self.feature_mode = self._feature_mode & (~tag) self._feature_mode = None else: self.set_feature_mode( False ) self._redraw_control() #------------------------------------------------------------------------------- # 'DockSplitter' class: #------------------------------------------------------------------------------- class DockSplitter ( DockItem ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Style of the splitter bar: style = Enum( 'horizontal', 'vertical' ) # Index of the splitter within its parent: index = Int # Current state of the splitter (i.e. its position relative to the things # it splits): state = Property #--------------------------------------------------------------------------- # Override the definition of the inherited 'theme' property: #--------------------------------------------------------------------------- def _get_theme ( self ): return self.parent.control.GetParent().owner.theme #--------------------------------------------------------------------------- # Draws the contents of the splitter: #--------------------------------------------------------------------------- def draw ( self, dc ): """ Draws the contents of the splitter. """ if (self._live_drag is False) and (self._first_bounds is not None): x, y, dx, dy = self._first_bounds else: x, y, dx, dy = self.bounds image = DockImages.get_splitter_image( self.state ) idx, idy = image.GetWidth(), image.GetHeight() self.fill_bg_color( dc, x, y, dx, dy ) if self.style == 'horizontal': # Draw a line the same color as the system button shadow, which # should be a darkish color in the users color scheme pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) dc.SetPen(pen) dc.DrawLine(x+idx+1,y+dy/2,x+dx-2,y+dy/2) iy = y+2 ix = x # sets the hittable area for changing the cursor to be the height of # the image dx = idx else: # Draw a line the same color as the system button shadow, which # should be a darkish color in the users color scheme pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) dc.SetPen(pen) dc.DrawLine(x+dx/2,y+idy+1,x+dx/2,y+dy-2) iy = y ix = x + 2 # sets the hittable area for changing the cursor to be the width of # the image dy = idy dc.DrawBitmap( image, ix, iy, True ) self._hot_spot = ( x, y, dx, dy ) #--------------------------------------------------------------------------- # Gets the cursor to use when the mouse is over the splitter bar: #--------------------------------------------------------------------------- def get_cursor ( self, event ): """ Gets the cursor to use when the mouse is over the splitter bar. """ if (self._hot_spot is None) or self.is_in( event, *self._hot_spot ): return wx.CURSOR_ARROW if self.style == 'horizontal': return wx.CURSOR_SIZENS return wx.CURSOR_SIZEWE #--------------------------------------------------------------------------- # Returns a copy of the splitter 'structure', minus the actual content: #--------------------------------------------------------------------------- def get_structure ( self ): """ Returns a copy of the splitter 'structure', minus the actual content. """ return self.clone_traits( [ '_last_bounds' ] ) #--------------------------------------------------------------------------- # Handles the left mouse button being pressed: #--------------------------------------------------------------------------- def mouse_down ( self, event ): """ Handles the left mouse button being pressed. """ self._live_drag = event.ControlDown() self._click_pending = ((self._hot_spot is not None) and self.is_in( event, *self._hot_spot )) if not self._click_pending: self._xy = ( event.GetX(), event.GetY() ) self._max_bounds = self.parent.get_splitter_bounds( self ) self._first_bounds = self.bounds if not self._live_drag: self._draw_bounds( event, self.bounds ) #--------------------------------------------------------------------------- # Handles the left mouse button being released: #--------------------------------------------------------------------------- def mouse_up ( self, event ): """ Handles the left mouse button being released. """ if self._click_pending: hx, hy, hdx, hdy = self._hot_spot if not self.is_in( event, hx, hy, hdx, hdy ): return if self.style == 'horizontal': if event.GetX() < (hx + (hdx / 2)): self.collapse(True) else: self.collapse(False) else: if event.GetY() < (hy + (hdy / 2)): self.collapse(True) else: self.collapse(False) else: self._last_bounds, self._first_bounds = self._first_bounds, None if not self._live_drag: self._draw_bounds( event ) self.parent.update_splitter( self, event.GetEventObject() ) #--------------------------------------------------------------------------- # Handles the mouse moving while the left mouse button is pressed: #--------------------------------------------------------------------------- def mouse_move ( self, event ): """ Handles the mouse moving while the left mouse button is pressed. """ if not self._click_pending: x, y, dx, dy = self._first_bounds mx, my, mdx, mdy = self._max_bounds if self.style == 'horizontal': y = y + event.GetY() - self._xy[1] y = min( max( y, my ), my + mdy - dy ) else: x = x + event.GetX() - self._xy[0] x = min( max( x, mx ), mx + mdx - dx ) bounds = ( x, y, dx, dy ) if bounds != self.bounds: self.bounds = bounds if self._live_drag: self.parent.update_splitter( self, event.GetEventObject() ) else: self._draw_bounds( event, bounds ) #--------------------------------------------------------------------------- # Collapse/expands a splitter #--------------------------------------------------------------------------- def collapse ( self, forward ): """ Move the splitter has far as possible in one direction. 'forward' is a boolean: True=right/down, False=left/up. If the splitter is already collapsed, restores it to its previous position. """ is_horizontal = (self.style == 'horizontal') x, y, dx, dy = self.bounds if self._last_bounds is not None: if is_horizontal: y = self._last_bounds[1] else: x = self._last_bounds[0] state = self.state contents = self.parent.visible_contents ix1, iy1, idx1, idy1 = contents[ self.index ].bounds ix2, iy2, idx2, idy2 = contents[ self.index + 1 ].bounds if is_horizontal: if state != SPLIT_HMIDDLE: if ((y == self.bounds[1]) or (y < iy1) or ((y + dy) > (iy2 + idy2))): y = (iy1 + iy2 + idy2 - dy) / 2 else: self._last_bounds = self.bounds if forward: y = iy1 else: y = iy2 + idy2 - dy elif state != SPLIT_VMIDDLE: if ((x == self.bounds[0]) or (x < ix1) or ((x + dx) > (ix2 + idx2))): x = (ix1 + ix2 + idx2 - dx) / 2 else: self._last_bounds = self.bounds if forward: x = ix2 + idx2 - dx else: x = ix1 self.bounds = ( x, y, dx, dy ) #--------------------------------------------------------------------------- # Handles the mouse hovering over the item: #--------------------------------------------------------------------------- def hover_enter ( self, event ): """ Handles the mouse hovering over the item. """ pass #--------------------------------------------------------------------------- # Handles the mouse exiting from hovering over the item: #--------------------------------------------------------------------------- def hover_exit ( self, event ): """ Handles the mouse exiting from hovering over the item. """ pass #--------------------------------------------------------------------------- # Draws the splitter bar in a new position while it is being dragged: #--------------------------------------------------------------------------- def _draw_bounds ( self, event, bounds = None ): """ Draws the splitter bar in a new position while it is being dragged. """ # Set up the drawing environment: window = event.GetEventObject() dc, x0, y0 = get_dc( window ) dc.SetLogicalFunction( wx.XOR ) dc.SetPen( wx.TRANSPARENT_PEN ) dc.SetBrush( wx.Brush( wx.Colour( *DragColor ), wx.SOLID ) ) is_horizontal = (self.style == 'horizontal') nx = ox = None # Draw the new bounds (if any): if bounds is not None: ax = ay = adx = ady = 0 nx, ny, ndx, ndy = bounds if is_horizontal: ady = (ndy - 6) ay = ady / 2 else: adx = (ndx - 6) ax = adx / 2 nx += ax ny += ay ndx -= adx ndy -= ady if self._bounds is not None: ax = ay = adx = ady = 0 ox, oy, odx, ody = self._bounds if is_horizontal: ady = (ody - 6) ay = ady / 2 else: adx = (odx - 6) ax = adx / 2 ox += ax oy += ay odx -= adx ody -= ady if nx is not None: tx, ty, tdx, tdy = nx, ny, ndx, ndy if ox is not None: if is_horizontal: yoy = oy - ty if 0 <= yoy < tdy: tdy = yoy elif -ody < yoy <= 0: ty = oy + ody tdy = tdy - ody - yoy else: xox = ox - tx if 0 <= xox < tdx: tdx = xox elif -odx < xox <= 0: tx = ox + odx tdx = tdx - odx - xox dc.DrawRectangle( tx + x0, ty + y0, tdx, tdy ) # Erase the old bounds (if any): if ox is not None: if nx is not None: if is_horizontal: yoy = ny - oy if 0 <= yoy < ody: ody = yoy elif -ndy < yoy <= 0: oy = ny + ndy ody = ody - ndy - yoy else: xox = nx - ox if 0 <= xox < odx: odx = xox elif -ndx < xox <= 0: ox = nx + ndx odx = odx - ndx - xox dc.DrawRectangle( ox + x0, oy + y0, odx, ody ) if is_mac: window.Refresh(rect=wx.Rect(ox + x0, oy + y0, odx, ody)) # Save the new bounds for the next call: self._bounds = bounds #--------------------------------------------------------------------------- # Implementation of the 'state' property: #--------------------------------------------------------------------------- def _get_state ( self ): contents = self.parent.contents x, y, dx, dy = self.bounds ix1, iy1, idx1, idy1 = contents[ self.index ].bounds ix2, iy2, idx2, idy2 = contents[ self.index + 1 ].bounds if self.style == 'horizontal': if y == iy1: return SPLIT_HTOP if (y + dy) == (iy2 + idy2): return SPLIT_HBOTTOM return SPLIT_HMIDDLE else: if x == ix1: return SPLIT_VLEFT if (x + dx) == (ix2 + idx2): return SPLIT_VRIGHT return SPLIT_VMIDDLE #------------------------------------------------------------------------------- # 'DockControl' class: #------------------------------------------------------------------------------- class DockControl ( DockItem ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The control this object describes: control = Instance( wx.Window, allow_none = True ) # The number of global DockWindowFeature's that were available the last # the time the feature set was checked: num_features = Int # A feature associated with the DockControl has been changed: feature_changed = Event # The image to display for this control: image = Instance( ImageResource, allow_none = True ) # The UI name of this control: name = Str # Has the user set the name of the control? user_name = Bool( False ) # The object (if any) associated with this control: object = Property # The id of this control: id = Str # Style of drag bar/tab: style = DockStyle # Has the user set the style for this control: user_style = Bool( False ) # Category of control when it is dragged out of the DockWindow: export = Str # Is the control visible? visible = Bool( True ) # Is the control's drag bar locked? locked = Bool( False ) # Can the control be resized? resizable = Bool( True ) # Can the control be closed? closeable = Bool( False ) # Function to call when a DockControl is requesting to be closed: on_close = Callable # (Optional) object that allows the control to be docked with a different # DockWindow: dockable = Instance( IDockable, allow_none = True ) # List of all other DockControl's in the same DockWindow: dock_controls = Property # Event fired when the control's notebook tab is activated by the user: activated = Event #--------------------------------------------------------------------------- # Calculates the minimum size of the control: #--------------------------------------------------------------------------- def calc_min ( self, use_size = False ): """ Calculates the minimum size of the control. """ self.check_features() dx, dy = self.width, self.height if self.control is not None: if wx_26: size = self.control.GetBestFittingSize() else: size = self.control.GetEffectiveMinSize() dx = size.GetWidth() dy = size.GetHeight() if self.width < 0: self.width, self.height = dx, dy if use_size and (self.width >= 0): return ( self.width, self.height ) return ( dx, dy ) #--------------------------------------------------------------------------- # Layout the contents of the control based on the specified bounds: #--------------------------------------------------------------------------- def recalc_sizes ( self, x, y, dx, dy ): """ Layout the contents of the region based on the specified bounds. """ self.width = dx = max( 0, dx ) self.height = dy = max( 0, dy ) self.bounds = ( x, y, dx, dy ) # Note: All we really want to do is the 'SetDimensions' call, but the # other code is needed for Linux/GTK which will not correctly process # the SetDimensions call if the min size is larger than the specified # size. So we temporarily set its min size to (0,0), do the # SetDimensions, then restore the original min size. The restore is # necessary so that DockWindow itself will correctly draw the 'drag' # box when performing a docking maneuver... control = self.control min_size = control.GetMinSize() control.SetMinSize( wx.Size( 0, 0 ) ) control.SetDimensions( x, y, dx, dy ) control.SetMinSize( min_size ) #--------------------------------------------------------------------------- # Checks to make sure that all applicable DockWindowFeatures have been # applied: #--------------------------------------------------------------------------- def check_features ( self ): """ Checks to make sure that all applicable DockWindowFeatures have been applied. """ global features mode = self.feature_mode n = len( features ) if ((self.num_features < n) and (self.control is not None) and isinstance( self.control.GetParent().GetSizer(), DockSizer )): for i in range( self.num_features, n ): feature_class = features[i] feature = feature_class.new_feature_for( self ) if feature is not None: if not isinstance( feature, SequenceType ): feature = [ feature ] self.features.extend( list( feature ) ) if mode == FEATURE_NONE: self.feature_mode = FEATURE_PRE_NORMAL if feature_class.state != 1: for item in feature: item.disable() else: self._tab_width = None if mode in NORMAL_FEATURES: self.set_feature_mode() self.num_features = n #--------------------------------------------------------------------------- # Sets the visibility of the control: #--------------------------------------------------------------------------- def set_visibility ( self, visible ): """ Sets the visibility of the control. """ if self.control is not None: self.control.Show( visible ) #--------------------------------------------------------------------------- # Returns all DockControl objects contained in the control: #--------------------------------------------------------------------------- def get_controls ( self, visible_only = True ): """ Returns all DockControl objects contained in the control. """ if visible_only and (not self.visible): return [] return [ self ] #--------------------------------------------------------------------------- # Gets the image (if any) associated with the control: #--------------------------------------------------------------------------- def get_image ( self ): """ Gets the image (if any) associated with the control. """ if self._image is None: if self.image is not None: self._image = self.image.create_image().ConvertToBitmap() return self._image #--------------------------------------------------------------------------- # Hides or shows the control: #--------------------------------------------------------------------------- def show ( self, visible = True, layout = True ): """ Hides or shows the control. """ if visible != self.visible: self.visible = visible self._layout( layout ) #--------------------------------------------------------------------------- # Activates a control (i.e. makes it the active page within its containing # notebook): #--------------------------------------------------------------------------- def activate ( self, layout = True ): """ Activates a control (i.e. makes it the active page within its containing notebook). """ if self.parent is not None: self.parent.activate( self, layout ) #--------------------------------------------------------------------------- # Closes the control: #--------------------------------------------------------------------------- def close ( self, layout = True, force = False ): """ Closes the control. """ control = self.control if control is not None: window = control.GetParent() if self.on_close is not None: # Ask the handler if it is OK to close the control: if self.on_close( self, force ) is False: # If not OK to close it, we're done: return elif self.dockable is not None: # Ask the IDockable handler if it is OK to close the control: if self.dockable.dockable_close( self, force ) is False: # If not OK to close it, we're done: return else: # No close handler, just destroy the widget ourselves: control.Destroy() # Reset all features: self.reset_features() # Remove the DockControl from the sizer: self.parent.remove( self ) # Mark the DockControl as closed (i.e. has no associated widget or # parent): self.control = self.parent = None # If a screen update is requested, lay everything out again now: if layout: window.Layout() window.Refresh() #--------------------------------------------------------------------------- # Returns the object at a specified window position: #--------------------------------------------------------------------------- def object_at ( self, x, y ): """ Returns the object at a specified window position. """ return None #--------------------------------------------------------------------------- # Returns a copy of the control 'structure', minus the actual content: #--------------------------------------------------------------------------- def get_structure ( self ): """ Returns a copy of the control 'structure', minus the actual content. """ return self.clone_traits( [ 'id', 'name', 'user_name', 'style', 'user_style', 'visible', 'locked', 'closeable', 'resizable', 'width', 'height' ] ) #--------------------------------------------------------------------------- # Toggles the 'lock' status of the control: #--------------------------------------------------------------------------- def toggle_lock ( self ): """ Toggles the 'lock' status of the control. """ self.locked = not self.locked #--------------------------------------------------------------------------- # Prints the contents of the control: #--------------------------------------------------------------------------- def dump ( self, indent ): """ Prints the contents of the control. """ print ('%sControl( %08X, name = %s, id = %s,\n%s' 'style = %s, locked = %s,\n%s' 'closeable = %s, resizable = %s, visible = %s\n%s' 'width = %d, height = %d )' % ( ' ' * indent, id( self ), self.name, self.id, ' ' * (indent + 9), self.style, self.locked, ' ' * (indent + 9), self.closeable, self.resizable, self.visible, ' ' * (indent + 9), self.width, self.height )) #--------------------------------------------------------------------------- # Draws the contents of the control: #--------------------------------------------------------------------------- def draw ( self, dc ): """ Draws the contents of the control. """ pass #--------------------------------------------------------------------------- # Sets a new name for the control: #--------------------------------------------------------------------------- def set_name ( self, name, layout = True ): """ Sets a new name for the control. """ if name != self.name: self.name = name self._layout( layout ) #--------------------------------------------------------------------------- # Resets the state of the tab: #--------------------------------------------------------------------------- def reset_tab ( self ): """ Resets the state of the tab. """ self.reset_features() self._layout() #--------------------------------------------------------------------------- # Resets all currently defined features: #--------------------------------------------------------------------------- def reset_features ( self ): """ Resets all currently defined features. """ for feature in self.features: feature.dispose() self.features = [] self.num_features = 0 #--------------------------------------------------------------------------- # Forces the containing DockWindow to be laid out: #--------------------------------------------------------------------------- def _layout ( self, layout = True ): """ Forces the containing DockWindow to be laid out. """ if layout and (self.control is not None): do_later( self.control.GetParent().owner.update_layout ) #--------------------------------------------------------------------------- # Handles the 'activated' event being fired: #--------------------------------------------------------------------------- def _activated_fired(self): """ Notifies the active dockable that the control's tab is being activated. """ if self.dockable is not None: self.dockable.dockable_tab_activated(self, True) #--------------------------------------------------------------------------- # Handles the 'feature_changed' trait being changed: #--------------------------------------------------------------------------- def _feature_changed ( self ): """ Handles the 'feature_changed' trait being changed """ self.set_feature_mode() #--------------------------------------------------------------------------- # Handles the 'control' trait being changed: #--------------------------------------------------------------------------- def _control_changed ( self, old, new ): """ Handles the 'control' trait being changed. """ self._tab_width = None if old is not None: old._dock_control = None if new is not None: new._dock_control = self self.reset_tab() #--------------------------------------------------------------------------- # Handles the 'name' trait being changed: #--------------------------------------------------------------------------- def _name_changed ( self ): """ Handles the 'name' trait being changed. """ self._tab_width = self._tab_name = None #--------------------------------------------------------------------------- # Handles the 'style' trait being changed: #--------------------------------------------------------------------------- def _style_changed ( self ): """ Handles the 'style' trait being changed. """ if self.parent is not None: self.parent._is_notebook = None #--------------------------------------------------------------------------- # Handles the 'image' trait being changed: #--------------------------------------------------------------------------- def _image_changed ( self ): """ Handles the 'image' trait being changed. """ self._image = None #--------------------------------------------------------------------------- # Handles the 'visible' trait being changed: #--------------------------------------------------------------------------- def _visible_changed ( self ): """ Handles the 'visible' trait being changed. """ if self.parent is not None: self.parent.show_hide( self ) #--------------------------------------------------------------------------- # Handles the 'dockable' trait being changed: #--------------------------------------------------------------------------- def _dockable_changed ( self, dockable ): """ Handles the 'dockable' trait being changed. """ if dockable is not None: dockable.dockable_bind( self ) #--------------------------------------------------------------------------- # Implementation of the 'object' property: #--------------------------------------------------------------------------- def _get_object ( self ): return getattr( self.control, '_object', None ) #--------------------------------------------------------------------------- # Implementation of the DockControl's property: #--------------------------------------------------------------------------- def _get_dock_controls ( self ): # Get all of the DockControls in the parent DockSizer: controls = self.control.GetParent().GetSizer().GetContents( ).get_controls( False ) # Remove ourself from the list: try: controls.remove( self ) except: pass return controls #------------------------------------------------------------------------------- # 'DockGroup' class: #------------------------------------------------------------------------------- class DockGroup ( DockItem ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The contents of the group: contents = List # The UI name of this group: name = Property # Style of drag bar/tab: style = Property # Are the contents of the group resizable? resizable = Property # Category of control when it is dragged out of the DockWindow: export = Constant( '' ) # Is the group visible? visible = Property # Content items which are visible: visible_contents = Property # Can the control be closed? closeable = Property # The control associated with this group: control = Property # Is the group locked? locked = Property # Has the initial layout been performed? initialized = Bool( False ) #--------------------------------------------------------------------------- # Implementation of the 'name' property: #--------------------------------------------------------------------------- def _get_name ( self ): controls = self.get_controls() n = len( controls ) if n == 0: return '' if n == 1: return controls[0].name return '%s [%d]' % ( controls[0].name, n ) #--------------------------------------------------------------------------- # Implementation of the 'visible' property: #--------------------------------------------------------------------------- def _get_visible ( self ): for item in self.contents: if item.visible: return True return False #--------------------------------------------------------------------------- # Implementation of the 'visible_contents' property: #--------------------------------------------------------------------------- def _get_visible_contents ( self ): return [ item for item in self.contents if item.visible ] #--------------------------------------------------------------------------- # Implementation of the 'closeable' property: #--------------------------------------------------------------------------- def _get_closeable ( self ): for item in self.contents: if not item.closeable: return False return True #--------------------------------------------------------------------------- # Implementation of the 'style' property: #--------------------------------------------------------------------------- def _get_style ( self ): # Make sure there is at least one item in the group: if len( self.contents ) > 0: # Return the first item's style: return self.contents[0].style # Otherwise, return a default style for an empty group: return 'horizontal' #--------------------------------------------------------------------------- # Implementation of the 'resizable' property: #--------------------------------------------------------------------------- def _get_resizable ( self ): if self._resizable is None: self._resizable = False for control in self.get_controls(): if control.resizable: self._resizable = True break return self._resizable #--------------------------------------------------------------------------- # Implementation of the 'control' property: #--------------------------------------------------------------------------- def _get_control ( self ): if len( self.contents ) == 0: return None return self.contents[0].control #--------------------------------------------------------------------------- # Implementation of the 'locked' property: #--------------------------------------------------------------------------- def _get_locked ( self ): return self.contents[0].locked #--------------------------------------------------------------------------- # Handles 'initialized' being changed: #--------------------------------------------------------------------------- def _initialized_changed( self ): """ Handles 'initialized' being changed. """ for item in self.contents: if isinstance( item, DockGroup ): item.initialized = self.initialized #--------------------------------------------------------------------------- # Hides or shows the contents of the group: #--------------------------------------------------------------------------- def show ( self, visible = True, layout = True ): """ Hides or shows the contents of the group. """ for item in self.contents: item.show( visible, False ) if layout: window = self.control.GetParent() window.Layout() window.Refresh() #--------------------------------------------------------------------------- # Replaces a specified DockControl by another: #--------------------------------------------------------------------------- def replace_control ( self, old, new ): """ Replaces a specified DockControl by another. """ for i, item in enumerate( self.contents ): if isinstance( item, DockControl ): if item is old: self.contents[i] = new new.parent = self return True elif item.replace_control( old, new ): return True return False #--------------------------------------------------------------------------- # Returns all DockControl objects contained in the group: #--------------------------------------------------------------------------- def get_controls ( self, visible_only = True ): """ Returns all DockControl objects contained in the group. """ if visible_only: contents = self.visible_contents else: contents = self.contents result = [] for item in contents: result.extend( item.get_controls( visible_only ) ) return result #--------------------------------------------------------------------------- # Gets the image (if any) associated with the group: #--------------------------------------------------------------------------- def get_image ( self ): """ Gets the image (if any) associated with the group. """ if len( self.contents ) == 0: return None return self.contents[0].get_image() #--------------------------------------------------------------------------- # Gets the cursor to use when the mouse is over the item: #--------------------------------------------------------------------------- def get_cursor ( self, event ): """ Gets the cursor to use when the mouse is over the item. """ return wx.CURSOR_ARROW #--------------------------------------------------------------------------- # Toggles the 'lock' status of every control in the group: #--------------------------------------------------------------------------- def toggle_lock ( self ): """ Toggles the 'lock' status of every control in the group. """ for item in self.contents: item.toggle_lock() #--------------------------------------------------------------------------- # Closes the group: #--------------------------------------------------------------------------- def close ( self, layout = True, force = False ): """ Closes the control. """ window = self.control.control.GetParent() for item in self.contents[:]: item.close( False, force = force ) if layout: window.Layout() window.Refresh() #------------------------------------------------------------------------------- # 'DockRegion' class: #------------------------------------------------------------------------------- class DockRegion ( DockGroup ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Index of the currently active 'contents' DockControl: active = Int # Is the region drawn as a notebook or not: is_notebook = Property # Index of the tab scroll image to use (-1 = No tab scroll): tab_scroll_index = Int( -1 ) # The index of the current leftmost visible tab: left_tab = Int # The current maximum value for 'left_tab': max_tab = Int # Contents have been modified property: modified = Property #--------------------------------------------------------------------------- # Calculates the minimum size of the region: #--------------------------------------------------------------------------- def calc_min ( self, use_size = False ): """ Calculates the minimum size of the region. """ tab_dx = tdx = tdy = 0 contents = self.visible_contents theme = self.theme if self.is_notebook: for item in contents: dx, dy = item.calc_min( use_size ) tdx = max( tdx, dx ) tdy = max( tdy, dy ) tab_dx += item.tab_width tis = theme.tab.image_slice tc = theme.tab.content tdx = max( tdx, tab_dx ) + (tis.xleft + tis.xright + tc.left + tc.right) tdy += (theme.tab_active.image_slice.dy + tis.xtop + tis.xbottom + tc.top + tc.bottom) elif len( contents ) > 0: item = contents[0] tdx, tdy = item.calc_min( use_size ) if not item.locked: if item.style == 'horizontal': tdy += theme.horizontal_drag.image_slice.dy elif item.style == 'vertical': tdx += theme.vertical_drag.image_slice.dx if self.width < 0: self.width = tdx self.height = tdy return ( tdx, tdy ) #--------------------------------------------------------------------------- # Layout the contents of the region based on the specified bounds: #--------------------------------------------------------------------------- def recalc_sizes ( self, x, y, dx, dy ): """ Layout the contents of the region based on the specified bounds. """ self.width = dx = max( 0, dx ) self.height = dy = max( 0, dy ) self.bounds = ( x, y, dx, dy ) theme = self.theme contents = self.visible_contents if self.is_notebook: tis = theme.tab.image_slice tc = theme.tab.content th = theme.tab_active.image_slice.dy # Layout the region out as a notebook: x += tis.xleft + tc.left tx0 = tx = x + theme.tab.label.left dx -= (tis.xleft + tis.xright + tc.left + tc.right) ady = dy - th dy = ady - tis.xtop - tis.xbottom - tc.top - tc.bottom iy = y + tis.xtop + tc.top if theme.tabs_at_top: iy += th else: y += ady for item in contents: item.recalc_sizes( x, iy, dx, dy ) tdx = item.tab_width item.set_drag_bounds( tx, y, tdx, th ) tx += tdx # Calculate the default tab clipping bounds: cdx = dx + tc.left + tc.right self._tab_clip_bounds = ( tx0, y, cdx, th ) # Do we need to enable tab scrolling? xr = tx0 + cdx if tx > xr: # Scrolling needed, calculate maximum tab index for scrolling: self.max_tab = 1 n = len( contents ) - 1 xr -= DockImages._tab_scroller_dx for i in range( n, -1, -1 ): xr -= contents[i].tab_width if xr < tx0: self.max_tab = min( i + 1, n ) break # Set the new leftmost tab index: self.left_tab = min( self.left_tab, self.max_tab ) # Determine which tab scroll image to use: self.tab_scroll_index = ((self.left_tab < self.max_tab) + (2 * (self.left_tab > 0))) - 1 # Now adjust each tab's bounds accordingly: if self.left_tab > 0: adx = contents[ self.left_tab ].drag_bounds[0] - tx0 for item in contents: dbx, dby, dbdx, dbdy = item.drag_bounds item.set_drag_bounds( dbx - adx, dby, item.tab_width, dbdy ) # Exclude the scroll buttons from the tab clipping region: self._tab_clip_bounds = ( tx0, y, cdx - DockImages._tab_scroller_dx, th ) else: self.tab_scroll_index = -1 self.left_tab = 0 else: # Lay the region out as a drag bar: item = contents[0] drag_bounds = ( 0, 0, 0, 0 ) if not item.locked: if item.style == 'horizontal': db_dy = theme.horizontal_drag.image_slice.dy drag_bounds = ( x, y, dx, db_dy ) y += db_dy dy -= db_dy elif item.style == 'vertical': db_dx = theme.vertical_drag.image_slice.dx drag_bounds = ( x, y, db_dx, dy ) x += db_dx dx -= db_dx item.recalc_sizes( x, y, dx, dy ) item.set_drag_bounds( *drag_bounds ) # Make sure all of the contained controls have the right visiblity: self._set_visibility() #--------------------------------------------------------------------------- # Adds a new control before or after a specified control: #--------------------------------------------------------------------------- def add ( self, control, before = None, after = None, activate = True ): """ Adds a new control before a specified control. """ contents = self.contents if control.parent is self: contents.remove( control ) if before is None: if after is None: i = len( contents ) else: i = contents.index( after ) + 1 else: i = contents.index( before ) contents.insert( i, control ) if activate: self.active = i #--------------------------------------------------------------------------- # Removes a specified item: #--------------------------------------------------------------------------- def remove ( self, item ): """ Removes a specified item. """ contents = self.contents i = contents.index( item ) if isinstance( item, DockGroup ) and (len( item.contents ) == 1): item = item.contents[0] if isinstance( item, DockRegion ): contents[ i: i + 1 ] = item.contents[:] else: contents[ i ] = item else: del contents[ i ] # Change the active selection only if 'item' is in closing mode, # or was dragged to a new location. # If this entire dock region is being closed, then all contained # dock items will be removed and we do not want to change 'active' # selection. if item._closing or item._dragging: if (self.active > i) or (self.active >= len( contents )): self.active -= 1 # If the active item was removed, then 'active' stays # unchanged, but it reflects the index of the next page in # the dock region. Since _active_changed won't be fired now, # we fire the 'activated' event on the next page. elif (i == self.active): control = self.contents[ i ] if isinstance( control, DockControl ): control.activated = True if self.parent is not None: if len( contents ) == 0: self.parent.remove( self ) elif ((len( contents ) == 1) and isinstance( self.parent, DockRegion )): self.parent.remove( self ) #--------------------------------------------------------------------------- # Returns a copy of the region 'structure', minus the actual content: #--------------------------------------------------------------------------- def get_structure ( self ): """ Returns a copy of the region 'structure', minus the actual content. """ return self.clone_traits( [ 'active', 'width', 'height' ] ).set( contents = [ item.get_structure() for item in self.contents ] ) #--------------------------------------------------------------------------- # Toggles the 'lock' status of every control in the group: #--------------------------------------------------------------------------- def toggle_lock ( self ): """ Toggles the 'lock' status of every control in the group. """ super( DockRegion, self ).toggle_lock() self._is_notebook = None #--------------------------------------------------------------------------- # Draws the contents of the region: #--------------------------------------------------------------------------- def draw ( self, dc ): """ Draws the contents of the region. """ if self._visible is not False: self.begin_draw( dc ) if self.is_notebook: # fixme: There seems to be a case where 'draw' is called before # 'recalc_sizes' (which defines '_tab_clip_bounds'), so we need # to check to make sure it is defined. If not, it seems safe to # exit immediately, since in all known cases, the bounds are # ( 0, 0, 0, 0 ), so there is nothing to draw anyways. The # question is why 'recalc_sizes' is not being called first. if self._tab_clip_bounds is None: self.end_draw( dc ) return self.fill_bg_color( dc, *self.bounds ) if self.active >= len(self.contents): # on some platforms, if the active tab was destroyed # the new active tab may not have been set yet self.active = len(self.contents) - 1 self._draw_notebook( dc ) active = self.active # Draw the scroll buttons (if necessary): x, y, dx, dy = self._tab_clip_bounds index = self.tab_scroll_index if index >= 0: dc.DrawBitmap( DockImages._tab_scroller_images[ index ], x + dx, y + 2, True ) # Draw all the inactive tabs first: dc.SetClippingRegion( x, y, dx, dy ) last_inactive = -1 for i, item in enumerate( self.contents ): if (i != active) and item.visible: last_inactive = i state = item.tab_state if state not in NotActiveStates: state = TabInactive item.draw_tab( dc, state ) # Draw the active tab last: self.contents[ active ].draw_tab( dc, TabActive ) # If the last inactive tab drawn is also the rightmost tab and # the theme has a 'tab right edge' image, draw the image just # to the right of the last tab: if last_inactive > active: if item.tab_state == TabInactive: bitmap = self.theme.tab_inactive_edge_bitmap else: bitmap = self.theme.tab_hover_edge_bitmap if bitmap is not None: x, y, dx, dy = item.drag_bounds dc.DrawBitmap( bitmap, x + dx, y, True ) else: item = self.visible_contents[0] if not item.locked: getattr( item, 'draw_' + item.style )( dc ) self.end_draw( dc ) # Draw each of the items contained in the region: for item in self.contents: if item.visible: item.draw( dc ) #--------------------------------------------------------------------------- # Returns the object at a specified window position: #--------------------------------------------------------------------------- def object_at ( self, x, y ): """ Returns the object at a specified window position. """ if (self._visible is not False) and self.is_at( x, y ): if self.is_notebook and (self.tab_scroll_index >= 0): cx, cy, cdx, cdy = self._tab_clip_bounds if self.is_at( x, y, ( cx + cdx, cy + 2, DockImages._tab_scroller_dx, DockImages._tab_scroller_dy ) ): return self for item in self.visible_contents: if item.is_at( x, y, item.drag_bounds ): return item object = item.object_at( x, y ) if object is not None: return object return None #--------------------------------------------------------------------------- # Gets the DockInfo object for a specified window position: #--------------------------------------------------------------------------- def dock_info_at ( self, x, y, tdx, is_control ): """ Gets the DockInfo object for a specified window position. """ # Check to see if the point is in our drag bar: info = super( DockRegion, self ).dock_info_at( x, y, tdx, is_control ) if info is not None: return info # If we are not visible, or the point is not contained in us, give up: if (self._visible is False) or (not self.is_at( x, y )): return None # Check to see if the point is in the drag bars of any controls: contents = self.visible_contents for item in contents: object = item.dock_info_at( x, y, tdx, is_control ) if object is not None: return object # If we are in 'notebook mode' check to see if the point is in the # empty region outside of any tabs: lx, ty, dx, dy = self.bounds if self.is_notebook: item = contents[-1] ix, iy, idx, idy = item.drag_bounds if (x > (ix + idx)) and (iy <= y < (iy + idy)): return DockInfo( kind = DOCK_TAB, tab_bounds = ( ix + idx, iy, tdx, idy ), region = self ) # Otherwise, figure out which edge the point is closest to, and # return a DockInfo object describing that edge: left = x - lx right = lx + dx - 1 - x top = y - ty bottom = ty + dy - 1 - y choice = min( left, right, top, bottom ) mdx = dx / 3 mdy = dy / 3 if choice == left: return DockInfo( kind = DOCK_LEFT, bounds = ( lx, ty, mdx, dy ), region = self ) if choice == right: return DockInfo( kind = DOCK_RIGHT, bounds = ( lx + dx - mdx, ty, mdx, dy ), region = self ) if choice == top: return DockInfo( kind = DOCK_TOP, bounds = ( lx, ty, dx, mdy ), region = self ) return DockInfo( kind = DOCK_BOTTOM, bounds = ( lx, ty + dy - mdy, dx, mdy ), region = self ) #--------------------------------------------------------------------------- # Handles a contained notebook tab being clicked: #--------------------------------------------------------------------------- def tab_clicked ( self, control ): """ Handles a contained notebook tab being clicked. """ # Find the page that was clicked and mark it as active: i = self.contents.index( control ) if i != self.active: self.active = i # Recalculate the tab layout: self.recalc_sizes( *self.bounds ) # Force the notebook to be redrawn: control.control.GetParent().RefreshRect( wx.Rect( *self.bounds ) ) # Fire the 'activated' event on the control: if isinstance( control, DockControl ): control.activated = True #--------------------------------------------------------------------------- # Handles the user clicking an active scroll button: #--------------------------------------------------------------------------- def scroll ( self, type, left_tab = 0 ): """ Handles the user clicking an active scroll button. """ if type == SCROLL_LEFT: left_tab = min( self.left_tab + 1, self.max_tab ) elif type == SCROLL_RIGHT: left_tab = max( self.left_tab - 1, 0 ) if left_tab != self.left_tab: # Calculate the amount we need to adjust each tab by: contents = self.visible_contents adx = (contents[ left_tab ].drag_bounds[0] - contents[ self.left_tab ].drag_bounds[0]) # Set the new leftmost tab index: self.left_tab = left_tab # Determine which tab scroll image to use: self.tab_scroll_index = ((left_tab < self.max_tab) + (2 * (left_tab > 0))) - 1 # Now adjust each tab's bounds accordingly: for item in contents: dbx, dby, dbdx, dbdy = item.drag_bounds item.set_drag_bounds( dbx - adx, dby, item.tab_width, dbdy ) # Finally, force a redraw of the affected part of the window: x, y, dx, dy = self._tab_clip_bounds item.control.GetParent().RefreshRect( wx.Rect( x, y, dx + DockImages._tab_scroller_dx, dy ) ) #--------------------------------------------------------------------------- # Handles the left mouse button being pressed: #--------------------------------------------------------------------------- def mouse_down ( self, event ): """ Handles the left mouse button being pressed. """ self._scroll = self._get_scroll_button( event ) #--------------------------------------------------------------------------- # Handles the left mouse button being released: #--------------------------------------------------------------------------- def mouse_up ( self, event ): """ Handles the left mouse button being released. """ if ((self._scroll is not None) and (self._scroll == self._get_scroll_button( event ))): self.scroll( self._scroll ) else: super( DockRegion, self ).mouse_up( event ) #--------------------------------------------------------------------------- # Handles the mouse moving while the left mouse button is pressed: #--------------------------------------------------------------------------- def mouse_move ( self, event ): """ Handles the mouse moving while the left mouse button is pressed. """ pass #--------------------------------------------------------------------------- # Sets the visibility of the region: #--------------------------------------------------------------------------- def set_visibility ( self, visible ): """ Sets the visibility of the region. """ self._visible = visible active = self.active for i, item in enumerate( self.contents ): item.set_visibility( visible and (i == active) ) #--------------------------------------------------------------------------- # Activates a specified control (i.e. makes it the current notebook tab): #--------------------------------------------------------------------------- def activate ( self, control, layout = True ): """ Activates a specified control (i.e. makes it the current notebook tab). """ if control.visible and self.is_notebook: active = self.contents.index( control ) if active != self.active: self.active = active self.make_active_tab_visible() window = control.control.GetParent() if layout: do_later( window.owner.update_layout ) else: window.RefreshRect( wx.Rect( *self.bounds ) ) else: # Fire the activated event for the control. if isinstance( control, DockControl ): control.activated = True #--------------------------------------------------------------------------- # Makes sure the active control's tab is completely visible (if possible): #--------------------------------------------------------------------------- def make_active_tab_visible ( self ): """ Makes sure the active control's tab is completely visible (if possible). """ active = self.active if active < self.left_tab: self.scroll( SCROLL_TO, active ) else: x, y, dx, dy = self.contents[ active ].drag_bounds if not self.is_at( x + dx - 1, y + dy - 1, self._tab_clip_bounds ): self.scroll( SCROLL_TO, min( active, self.max_tab ) ) #--------------------------------------------------------------------------- # Handles a contained DockControl item being hidden or shown: #--------------------------------------------------------------------------- def show_hide ( self, control ): """ Handles a contained DockControl item being hidden or shown. """ i = self.contents.index( control ) if i == self.active: self._update_active() elif (self.active < 0) and control.visible: self.active = i self._is_notebook = None #--------------------------------------------------------------------------- # Prints the contents of the region: #--------------------------------------------------------------------------- def dump ( self, indent ): """ Prints the contents of the region. """ print '%sRegion( %08X, active = %s, width = %d, height = %d )' % ( ' ' * indent, id( self ), self.active, self.width, self.height ) for item in self.contents: item.dump( indent + 3 ) #--------------------------------------------------------------------------- # Returns which scroll button (if any) the pointer is currently over: #--------------------------------------------------------------------------- def _get_scroll_button ( self, event ): """ Returns which scroll button (if any) the pointer is currently over. """ x, y, dx, dy = self._tab_clip_bounds if self.is_in( event, x + dx, y + 2, DockImages._tab_scroller_dx, DockImages._tab_scroller_dy ): if (event.GetX() - (x + dx)) < (DockImages._tab_scroller_dx / 2): return SCROLL_LEFT return SCROLL_RIGHT return None #--------------------------------------------------------------------------- # Updates the currently active page after a change: #--------------------------------------------------------------------------- def _update_active ( self, active = None ): """ Updates the currently active page after a change. """ if active is None: active = self.active contents = self.contents for i in (range( active, len( contents ) ) + range( active - 1, -1, -1 )): if contents[ i ].visible: self.active = i return self.active = -1 #--------------------------------------------------------------------------- # Handles the 'active' trait being changed: #--------------------------------------------------------------------------- def _active_changed ( self, old, new ): self._set_visibility() # Set the correct tab state for each tab: for i, item in enumerate( self.contents ): item.tab_state = NormalStates[ i == new ] n = len( self.contents ) if 0 <= old < n: # Notify the previously active dockable that the control's tab is # being deactivated: control = self.contents[ old ] if (isinstance( control, DockControl ) and (control.dockable is not None)): control.dockable.dockable_tab_activated( control, False ) if 0 <= new < n: # Notify the new dockable that the control's tab is being # activated: control = self.contents[ new ] if isinstance( control, DockControl ): control.activated = True #--------------------------------------------------------------------------- # Handles the 'contents' trait being changed: #--------------------------------------------------------------------------- def _contents_changed ( self ): """ Handles the 'contents' trait being changed. """ self._is_notebook = None for item in self.contents: item.parent = self self.calc_min( True ) self.modified = True def _contents_items_changed ( self, event ): """ Handles the 'contents' trait being changed. """ self._is_notebook = None for item in event.added: item.parent = self self.calc_min( True ) self.modified = True #--------------------------------------------------------------------------- # Set the proper visiblity for all contained controls: #--------------------------------------------------------------------------- def _set_visibility ( self ): """ Set the proper visiblity for all contained controls. """ active = self.active for i, item in enumerate( self.contents ): item.set_visibility( i == active ) #--------------------------------------------------------------------------- # Implementation of the 'modified' property: #--------------------------------------------------------------------------- def _set_modified ( self, value ): if self.parent is not None: self.parent.modified = True #--------------------------------------------------------------------------- # Implementation of the 'is_notebook' property: #--------------------------------------------------------------------------- def _get_is_notebook ( self ): if self._is_notebook is None: contents = self.visible_contents n = len( contents ) self._is_notebook = (n > 1) if n == 1: self._is_notebook = (contents[0].style == 'tab') return self._is_notebook #--------------------------------------------------------------------------- # Draws the notebook body: #--------------------------------------------------------------------------- def _draw_notebook ( self, dc ): """ Draws the notebook body. """ theme = self.theme tab_height = theme.tab_active.image_slice.dy x, y, dx, dy = self.bounds self.fill_bg_color( dc, x, y, dx, dy ) # Draws a box around the frame containing the tab contents, starting # below the tab pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)) dc.SetPen(pen) dc.DrawRectangle(x, y+tab_height, dx, dy-tab_height) # draw highlight pen = wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNHIGHLIGHT)) dc.SetPen(pen) dc.DrawLine(x+1, y+tab_height+1, x+dx-1, y+tab_height+1) # Erases the line under the active tab x0 = x + self.tab_theme.label.left x1 = x0 for i in range(self.active+1): x0 = x1 + 1 x1 += self.contents[i].tab_width dc.SetPen(wx.Pen(self.get_bg_color())) dc.DrawLine(x0, y+tab_height, x1, y+tab_height) dc.DrawLine(x0, y+tab_height+1, x1, y+tab_height+1) #------------------------------------------------------------------------------- # 'DockSection' class: #------------------------------------------------------------------------------- class DockSection ( DockGroup ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Is this a row (or a column)? is_row = Bool( True ) # Bounds of any splitter bars associated with the region: splitters = List( DockSplitter ) # The DockWindow that owns this section (set on top level section only): dock_window = Instance( 'pyface.dock.dock_window.DockWindow' ) # Contents of the section have been modified property: modified = Property #--------------------------------------------------------------------------- # Re-implementation of the 'owner' property: #--------------------------------------------------------------------------- @cached_property def _get_owner ( self ): if self.dock_window is not None: return self.dock_window if self.parent is None: return None return self.parent.owner #--------------------------------------------------------------------------- # Calculates the minimum size of the section: #--------------------------------------------------------------------------- def calc_min ( self, use_size = False ): """ Calculates the minimum size of the section. """ tdx = tdy = 0 contents = self.visible_contents n = len( contents ) if self.is_row: # allow 10 pixels for the splitter sdx = 10 for item in contents: dx, dy = item.calc_min( use_size ) tdx += dx tdy = max( tdy, dy ) if self.resizable: tdx += ((n - 1) * sdx) else: tdx += ((n + 1) * 3) tdy += 6 else: # allow 10 pixels for the splitter sdy = 10 for item in contents: dx, dy = item.calc_min( use_size ) tdx = max( tdx, dx ) tdy += dy if self.resizable: tdy += ((n - 1) * sdy) else: tdx += 6 tdy += ((n + 1) * 3) if self.width < 0: self.width = tdx self.height = tdy return ( tdx, tdy ) #--------------------------------------------------------------------------- # Perform initial layout of the section based on the specified bounds: #--------------------------------------------------------------------------- def initial_recalc_sizes ( self, x, y, dx, dy ): """ Layout the contents of the section based on the specified bounds. """ self.width = dx = max( 0, dx ) self.height = dy = max( 0, dy ) self.bounds = ( x, y, dx, dy ) # If none of the contents are resizable, use the fixed layout method if not self.resizable: self.recalc_sizes_fixed( x, y, dx, dy ) return contents = self.visible_contents n = len( contents ) - 1 splitters = [] # Find out how much space is available. splitter_size = 10 sizes = [] if self.is_row: total = dx - (n * splitter_size) else: total = dy - (n * splitter_size) # Get requested sizes from the items. for item in contents: size = -1.0 for dock_control in item.get_controls(): dockable = dock_control.dockable if dockable is not None and dockable.element is not None: if self.is_row: size = max( size, dockable.element.width ) else: size = max( size, dockable.element.height ) sizes.append( size ) # Allocate requested space. avail = total remain = 0 for i, sz in enumerate( sizes ): if avail <= 0: break if sz >= 0: if sz >= 1: sz = min( sz, avail ) else: sz *= total sz = int( sz ) sizes[i] = sz avail -= sz else: remain += 1 # Allocate the remainder to those parts that didn't request a width. if remain > 0: remain = int( avail / remain ) for i, sz in enumerate( sizes ): if sz < 0: sizes[i] = remain # If all requested a width, allocate the remainder to the last item. else: sizes[-1] += avail # Resize contents and add splitters if self.is_row: for i, item in enumerate( contents ): idx = int( sizes[i] ) item.recalc_sizes( x, y, idx, dy ) x += idx if i < n: splitters.append( DockSplitter( bounds = ( x, y, splitter_size, dy ), style = 'vertical', parent = self, index = i ) ) x += splitter_size else: for i, item in enumerate( contents ): idy = int( sizes[i] ) item.recalc_sizes( x, y, dx, idy ) y += idy if i < n: splitters.append( DockSplitter( bounds = ( x, y, dx, splitter_size ), style = 'horizontal', parent = self, index = i ) ) y += splitter_size # Preserve the current internal '_last_bounds' for all splitters if # possible: cur_splitters = self.splitters for i in range( min( len( splitters ), len( cur_splitters ) ) ): splitters[i]._last_bounds = cur_splitters[i]._last_bounds # Save the new set of splitter bars: self.splitters = splitters # Set the visibility for all contained items: self._set_visibility() #--------------------------------------------------------------------------- # Layout the contents of the section based on the specified bounds: #--------------------------------------------------------------------------- def recalc_sizes ( self, x, y, dx, dy ): """ Layout the contents of the section based on the specified bounds. """ # Check if we need to perform initial layout if not self.initialized: self.initial_recalc_sizes( x, y, dx, dy ) self.initialized = True return self.width = dx = max( 0, dx ) self.height = dy = max( 0, dy ) self.bounds = ( x, y, dx, dy ) # If none of the contents are resizable, use the fixed layout method: if not self.resizable: self.recalc_sizes_fixed( x, y, dx, dy ) return contents = self.visible_contents n = len( contents ) - 1 splitters = [] # Perform a horizontal layout: if self.is_row: # allow 10 pixels for the splitter sdx = 10 dx -= (n * sdx) cdx = 0 # Calculate the current and minimum width: for item in contents: cdx += item.width cdx = max( 1, cdx ) # Calculate the delta between the current and new width: delta = remaining = dx - cdx # Allocate the change (plus or minus) proportionally based on each # item's current size: for i, item in enumerate( contents ): if i < n: idx = int( round( float( item.width * delta ) / cdx ) ) else: idx = remaining remaining -= idx idx += item.width item.recalc_sizes( x, y, idx, dy ) x += idx # Define the splitter bar between adjacent items: if i < n: splitters.append( DockSplitter( bounds = ( x, y, sdx, dy ), style = 'vertical', parent = self, index = i ) ) x += sdx # Perform a vertical layout: else: # allow 10 pixels for the splitter sdy = 10 dy -= (n * sdy) cdy = 0 # Calculate the current and minimum height: for item in contents: cdy += item.height cdy = max( 1, cdy ) # Calculate the delta between the current and new height: delta = remaining = dy - cdy # Allocate the change (plus or minus) proportionally based on each # item's current size: for i, item in enumerate( contents ): if i < n: idy = int( round( float( item.height * delta ) / cdy ) ) else: idy = remaining remaining -= idy idy += item.height item.recalc_sizes( x, y, dx, idy ) y += idy # Define the splitter bar between adjacent items: if i < n: splitters.append( DockSplitter( bounds = ( x, y, dx, sdy ), style = 'horizontal', parent = self, index = i ) ) y += sdy # Preserve the current internal '_last_bounds' for all splitters if # possible: cur_splitters = self.splitters for i in range( min( len( splitters ), len( cur_splitters ) ) ): splitters[i]._last_bounds = cur_splitters[i]._last_bounds # Save the new set of splitter bars: self.splitters = splitters # Set the visibility for all contained items: self._set_visibility() #--------------------------------------------------------------------------- # Layout the contents of the section based on the specified bounds using # the minimum requested size for each item: #--------------------------------------------------------------------------- def recalc_sizes_fixed ( self, x, y, dx, dy ): """ Layout the contents of the section based on the specified bounds using the minimum requested size for each item. """ self.splitters = [] x += 3 y += 3 dx = max( 0, dx - 3 ) dy = max( 0, dy - 3 ) # Perform a horizontal layout: if self.is_row: # Allocate the space for each item based on its minimum size until # the space runs out: for item in self.visible_contents: idx, idy = item.calc_min() idx = min( dx, idx ) idy = min( dy, idy ) dx = max( 0, dx - idx - 3 ) item.recalc_sizes( x, y, idx, idy ) x += idx + 3 # Perform a vertical layout: else: # Allocate the space for each item based on its minimum size until # the space runs out: for item in self.visible_contents: idx, idy = item.calc_min() idx = min( dx, idx ) idy = min( dy, idy ) dy = max( 0, dy - idy - 3 ) item.recalc_sizes( x, y, idx, idy ) y += idy + 3 # Set the visibility for all contained items: self._set_visibility() #--------------------------------------------------------------------------- # Draws the contents of the section: #--------------------------------------------------------------------------- def draw ( self, dc ): """ Draws the contents of the section. """ if self._visible is not False: contents = self.visible_contents x, y, dx, dy = self.bounds self.fill_bg_color( dc, x, y, dx, dy ) for item in contents: item.draw( dc ) self.begin_draw( dc ) for item in self.splitters: item.draw( dc ) self.end_draw( dc ) #--------------------------------------------------------------------------- # Returns the object at a specified window position: #--------------------------------------------------------------------------- def object_at ( self, x, y, force = False ): """ Returns the object at a specified window position. """ if self._visible is not False: for item in self.splitters: if item.is_at( x, y ): return item for item in self.visible_contents: object = item.object_at( x, y ) if object is not None: return object if force and self.is_at( x, y ): return self return None #--------------------------------------------------------------------------- # Gets the DockInfo object for a specified window position: #--------------------------------------------------------------------------- def dock_info_at ( self, x, y, tdx, is_control, force = False ): """ Gets the DockInfo object for a specified window position. """ # Check to see if the point is in our drag bar: info = super( DockSection, self ).dock_info_at( x, y, tdx, is_control ) if info is not None: return info if self._visible is False: return None for item in self.splitters: if item.is_at( x, y ): return DockInfo( kind = DOCK_SPLITTER ) for item in self.visible_contents: object = item.dock_info_at( x, y, tdx, is_control ) if object is not None: return object # Check to see if we must return a DockInfo object: if not force: return None # Otherwise, figure out which edge the point is closest to, and # return a DockInfo object describing that edge: lx, ty, dx, dy = self.bounds left = lx - x right = x - lx - dx + 1 top = ty - y bottom = y - ty - dy + 1 # If the point is way outside of the section, mark it is a drag and # drop candidate: if max( left, right, top, bottom ) > 20: return DockInfo( kind = DOCK_EXPORT ) left = abs( left ) right = abs( right ) top = abs( top ) bottom = abs( bottom ) choice = min( left, right, top, bottom ) mdx = dx / 3 mdy = dy / 3 if choice == left: return DockInfo( kind = DOCK_LEFT, bounds = ( lx, ty, mdx, dy ) ) if choice == right: return DockInfo( kind = DOCK_RIGHT, bounds = ( lx + dx - mdx, ty, mdx, dy ) ) if choice == top: return DockInfo( kind = DOCK_TOP, bounds = ( lx, ty, dx, mdy ) ) return DockInfo( kind = DOCK_BOTTOM, bounds = ( lx, ty + dy - mdy, dx, mdy ) ) #--------------------------------------------------------------------------- # Adds a control to the section at the edge of the region specified: #--------------------------------------------------------------------------- def add ( self, control, region, kind ): """ Adds a control to the section at the edge of the region specified. """ contents = self.contents new_region = control if not isinstance( control, DockRegion ): new_region = DockRegion( contents = [ control ] ) i = contents.index( region ) if self.is_row: if (kind == DOCK_TOP) or (kind == DOCK_BOTTOM): if kind == DOCK_TOP: new_contents = [ new_region, region ] else: new_contents = [ region, new_region ] contents[ i ] = DockSection( is_row = False ).set( contents = new_contents ) else: if new_region.parent is self: contents.remove( new_region ) i = contents.index( region ) if kind == DOCK_RIGHT: i += 1 contents.insert( i, new_region ) else: if (kind == DOCK_LEFT) or (kind == DOCK_RIGHT): if kind == DOCK_LEFT: new_contents = [ new_region, region ] else: new_contents = [ region, new_region ] contents[ i ] = DockSection( is_row = True ).set( contents = new_contents ) else: if new_region.parent is self: contents.remove( new_region ) i = contents.index( region ) if kind == DOCK_BOTTOM: i += 1 contents.insert( i, new_region ) #--------------------------------------------------------------------------- # Removes a specified region or section from the section: #--------------------------------------------------------------------------- def remove ( self, item ): """ Removes a specified region or section from the section. """ contents = self.contents if isinstance( item, DockGroup ) and (len( item.contents ) == 1): contents[ contents.index( item ) ] = item.contents[0] else: contents.remove( item ) if self.parent is not None: if len( contents ) <= 1: self.parent.remove( self ) elif (len( contents ) == 0) and (self.dock_window is not None): self.dock_window.dock_window_empty() #--------------------------------------------------------------------------- # Sets the visibility of the group: #--------------------------------------------------------------------------- def set_visibility ( self, visible ): """ Sets the visibility of the group. """ self._visible = visible for item in self.contents: item.set_visibility( visible ) #--------------------------------------------------------------------------- # Returns a copy of the section 'structure', minus the actual content: #--------------------------------------------------------------------------- def get_structure ( self ): """ Returns a copy of the section 'structure', minus the actual content. """ return self.clone_traits( [ 'is_row', 'width', 'height' ] ).set( contents = [ item.get_structure() for item in self.contents ], splitters = [ item.get_structure() for item in self.splitters ] ) #--------------------------------------------------------------------------- # Gets the maximum bounds that a splitter bar is allowed to be dragged: #--------------------------------------------------------------------------- def get_splitter_bounds ( self, splitter ): """ Gets the maximum bounds that a splitter bar is allowed to be dragged. """ x, y, dx, dy = splitter.bounds i = self.splitters.index( splitter ) contents = self.visible_contents item1 = contents[ i ] item2 = contents[ i + 1 ] bx, by, bdx, bdy = item2.bounds if self.is_row: x = item1.bounds[0] dx = bx + bdx - x else: y = item1.bounds[1] dy = by + bdy - y return ( x, y, dx, dy ) #--------------------------------------------------------------------------- # Updates the affected regions when a splitter bar is released: #--------------------------------------------------------------------------- def update_splitter ( self, splitter, window ): """ Updates the affected regions when a splitter bar is released. """ x, y, dx, dy = splitter.bounds i = self.splitters.index( splitter ) contents = self.visible_contents item1 = contents[ i ] item2 = contents[ i + 1 ] ix1, iy1, idx1, idy1 = item1.bounds ix2, iy2, idx2, idy2 = item2.bounds window.Freeze() if self.is_row: item1.recalc_sizes( ix1, iy1, x - ix1, idy1 ) item2.recalc_sizes( x + dx, iy2, ix2 + idx2 - x - dx, idy2 ) else: item1.recalc_sizes( ix1, iy1, idx1, y - iy1 ) item2.recalc_sizes( ix2, y + dy, idx2, iy2 + idy2 - y - dy ) window.Thaw() if splitter.style == 'horizontal': dx = 0 else: dy = 0 window.RefreshRect( wx.Rect( ix1 - dx, iy1 - dy, ix2 + idx2 - ix1 + 2 * dx, iy2 + idy2 - iy1 + 2 * dy ) ) #--------------------------------------------------------------------------- # Prints the contents of the section: #--------------------------------------------------------------------------- def dump ( self, indent = 0 ): """ Prints the contents of the section. """ print '%sSection( %08X, is_row = %s, width = %d, height = %d )' % ( ' ' * indent, id( self ), self.is_row, self.width, self.height ) for item in self.contents: item.dump( indent + 3 ) #--------------------------------------------------------------------------- # Sets the correct visiblity for all contained items: #--------------------------------------------------------------------------- def _set_visibility ( self ): """ Sets the correct visiblity for all contained items. """ for item in self.contents: item.set_visibility( item.visible ) #--------------------------------------------------------------------------- # Handles the 'contents' trait being changed: #--------------------------------------------------------------------------- def _contents_changed ( self ): """ Handles the 'contents' trait being changed. """ for item in self.contents: item.parent = self self.calc_min( True ) self.modified = True def _contents_items_changed ( self, event ): """ Handles the 'contents' trait being changed. """ for item in event.added: item.parent = self self.calc_min( True ) self.modified = True #--------------------------------------------------------------------------- # Handles the 'splitters' trait being changed: #--------------------------------------------------------------------------- def _splitters_changed ( self ): """ Handles the 'splitters' trait being changed. """ for item in self.splitters: item.parent = self def _splitters_items_changed ( self, event ): """ Handles the 'splitters' trait being changed. """ for item in event.added: item.parent = self #--------------------------------------------------------------------------- # Implementation of the 'modified' property: #--------------------------------------------------------------------------- def _set_modified ( self, value ): self._resizable = None if self.parent is not None: self.parent.modified = True #------------------------------------------------------------------------------- # 'DockInfo' class: #------------------------------------------------------------------------------- class DockInfo ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # Dock kind: kind = Range( DOCK_TOP, DOCK_EXPORT ) # Dock bounds: bounds = Bounds # Tab bounds (if needed): tab_bounds = Bounds # Dock Region: region = Instance( DockRegion ) # Dock Control: control = Instance( DockItem ) def __init__(self, **kw): super(DockInfo, self).__init__(**kw) #--------------------------------------------------------------------------- # Draws the DockInfo on the display: #--------------------------------------------------------------------------- def draw ( self, window, bitmap = None ): """ Draws the DockInfo on the display. """ if DOCK_TOP <= self.kind <= DOCK_TABADD: if bitmap is None: bitmap = self._bitmap if bitmap is None: return else: self._bitmap = bitmap sdc, bx, by = get_dc( window ) bdc = wx.MemoryDC() bdc2 = wx.MemoryDC() bdx, bdy = bitmap.GetWidth(), bitmap.GetHeight() bitmap2 = wx.EmptyBitmap( bdx, bdy ) bdc.SelectObject( bitmap ) bdc2.SelectObject( bitmap2 ) bdc2.Blit( 0, 0, bdx, bdy, bdc, 0, 0 ) try: bdc3 = wx.GCDC( bdc2 ) bdc3.SetPen( wx.TRANSPARENT_PEN ) bdc3.SetBrush( wx.Brush( wx.Colour( *DockColorBrush ) ) ) x, y, dx, dy = self.bounds if DOCK_TAB <= self.kind <= DOCK_TABADD: tx, ty, tdx, tdy = self.tab_bounds bdc3.DrawRoundedRectangle( tx, ty, tdx, tdy, 4 ) else: bdc3.DrawRoundedRectangle( x, y, dx, dy, 8 ) except Exception: pass sdc.Blit( bx, by, bdx, bdy, bdc2, 0, 0 ) #--------------------------------------------------------------------------- # Docks the specified control: #--------------------------------------------------------------------------- def dock ( self, control, window ): """ Docks the specified control. """ the_control = control kind = self.kind if kind < DOCK_NONE: the_parent = control.parent region = self.region if (kind == DOCK_TAB) or (kind == DOCK_BAR): region.add( control, self.control ) elif kind == DOCK_TABADD: item = self.control if isinstance( item, DockControl ): if isinstance( control, DockControl ): control = DockRegion( contents = [ control ] ) i = region.contents.index( item ) region.contents[ i ] = item = DockSection( contents = [ DockRegion( contents = [ item ] ), control ], is_row = True ) elif isinstance( item, DockSection ): if (isinstance( control, DockSection ) and (item.is_row == control.is_row)): item.contents.extend( control.contents ) else: if isinstance( control, DockControl ): control = DockRegion( contents = [ control ] ) item.contents.append( control ) else: item.contents.append( control ) region.active = region.contents.index( item ) elif region is not None: region.parent.add( control, region, kind ) else: sizer = window.GetSizer() section = sizer._contents if ((section.is_row and ((kind == DOCK_TOP) or (kind == DOCK_BOTTOM))) or ((not section.is_row) and ((kind == DOCK_LEFT) or (kind == DOCK_RIGHT)))): if len( section.contents ) > 0: sizer._contents = section = DockSection( is_row = not section.is_row ).set( contents = [ section ] ) if len( section.contents ) > 0: i = 0 if (kind == DOCK_RIGHT) or (kind == DOCK_BOTTOM): i = -1 section.add( control, section.contents[ i ], kind ) else: section.is_row = not section.is_row section.contents = [ DockRegion( contents = [ control ] ) ] section = None if ((the_parent is not None) and (the_parent is not the_control.parent)): the_parent.remove( the_control ) # Force the main window to be laid out and redrawn: window.Layout() window.Refresh() # Create a reusable DockInfo indicating no information available: no_dock_info = DockInfo( kind = DOCK_NONE ) #------------------------------------------------------------------------------- # 'SetStructureHandler' class #------------------------------------------------------------------------------- class SetStructureHandler ( object ): #--------------------------------------------------------------------------- # Resolves an unresolved DockControl id: #--------------------------------------------------------------------------- def resolve_id ( self, id ): """ Resolves an unresolved DockControl id. """ return None #--------------------------------------------------------------------------- # Resolves extra, unused DockControls not referenced by the structure: #--------------------------------------------------------------------------- def resolve_extras ( self, structure, extras ): """ Resolves extra, unused DockControls not referenced by the structure. """ for dock_control in extras: if dock_control.control is not None: dock_control.control.Show( False ) #------------------------------------------------------------------------------- # 'DockSizer' class: #------------------------------------------------------------------------------- class DockSizer ( wx.PySizer ): #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, contents = None ): super( DockSizer, self ).__init__() # Make sure the DockImages singleton has been initialized: DockImages.init() # Finish initializing the sizer itself: self._contents = self._structure = self._max_structure = None if contents is not None: self.SetContents( contents ) #--------------------------------------------------------------------------- # Calculates the minimum size needed by the sizer: #--------------------------------------------------------------------------- def CalcMin ( self ): if self._contents is None: return wx.Size( 20, 20 ) dx, dy = self._contents.calc_min() return wx.Size( dx, dy ) #--------------------------------------------------------------------------- # Layout the contents of the sizer based on the sizer's current size and # position: #--------------------------------------------------------------------------- def RecalcSizes ( self ): """ Layout the contents of the sizer based on the sizer's current size and position. """ if self._contents is None: return x, y = self.GetPositionTuple() dx, dy = self.GetSizeTuple() self._contents.recalc_sizes( x, y, dx, dy ) #--------------------------------------------------------------------------- # Returns the current sizer contents: #--------------------------------------------------------------------------- def GetContents ( self ): """ Returns the current sizer contents. """ return self._contents #--------------------------------------------------------------------------- # Initializes the layout of a DockWindow from a content list: #--------------------------------------------------------------------------- def SetContents ( self, contents ): """ Initializes the layout of a DockWindow from a content list. """ if isinstance( contents, DockGroup ): self._contents = contents elif isinstance( contents, tuple ): self._contents = self._set_region( contents ) elif isinstance( contents, list ): self._contents = self._set_section( contents, True ) elif isinstance( contents, DockControl ): self._contents = self._set_section( [ contents ], True ) else: raise TypeError # Set the owner DockWindow for the top-level group (if possible) # so that it can notify the owner when the DockWindow becomes empty: control = self._contents.control if control is not None: self._contents.dock_window = control.GetParent().owner # If no saved structure exists yet, save the current one: if self._structure is None: self._structure = self.GetStructure() def _set_region ( self, contents ): items = [] for item in contents: if isinstance( item, tuple ): items.append( self._set_region( item ) ) elif isinstance( item, list ): items.append( self._set_section( item, True ) ) elif isinstance( item, DockItem ): items.append( item ) else: raise TypeError return DockRegion( contents = items ) def _set_section ( self, contents, is_row ): items = [] for item in contents: if isinstance( item, tuple ): items.append( self._set_region( item ) ) elif isinstance( item, list ): items.append( self._set_section( item, not is_row ) ) elif isinstance( item, DockControl ): items.append( DockRegion( contents = [ item ] ) ) else: raise TypeError return DockSection( is_row = is_row ).set( contents = items ) #--------------------------------------------------------------------------- # Returns a copy of the layout 'structure', minus the actual content # (i.e. controls, splitters, bounds). This method is intended for use in # persisting the current user layout, so that it can be restored in a # future session: #--------------------------------------------------------------------------- def GetStructure ( self ): """ Returns a copy of the layout 'structure', minus the actual content (i.e. controls, splitters, bounds). This method is intended for use in persisting the current user layout, so that it can be restored in a future session. """ if self._contents is not None: return self._contents.get_structure() return DockSection() #--------------------------------------------------------------------------- # Takes a previously saved 'GetStructure' result and applies it to the # contents of the sizer in order to restore a previous layout using a # new set of controls: #--------------------------------------------------------------------------- def SetStructure ( self, window, structure, handler = None ): """ Takes a previously saved 'GetStructure' result and applies it to the contents of the sizer in order to restore a previous layout using a new set of controls. """ section = self._contents if (section is None) or (not isinstance( structure, DockGroup )): return # Make sure that DockSections, which have a separate layout algorithm # for the first layout, are set as initialized. structure.initialized = True # Save the current structure in case a 'ResetStructure' call is made # later: self._structure = self.GetStructure() extras = [] # Create a mapping for all the DockControls in the new structure: map = {} for control in structure.get_controls( False ): if control.id in map: control.parent.remove( control ) else: map[ control.id ] = control # Try to map each current item into an equivalent item in the saved # preferences: for control in section.get_controls( False ): mapped_control = map.get( control.id ) if mapped_control is not None: control.set( **mapped_control.get( 'visible', 'locked', 'closeable', 'resizable', 'width', 'height' ) ) if mapped_control.user_name: control.name = mapped_control.name if mapped_control.user_style: control.style = mapped_control.style structure.replace_control( mapped_control, control ) del map[ control.id ] else: extras.append( control ) # Try to resolve all unused saved items: for id, item in map.items(): # If there is a handler, see if it can resolve it: if handler is not None: control = handler.resolve_id( id ) if control is not None: item.control = control continue # If nobody knows what it is, just remove it: item.parent.remove( item ) # Check if there are any new items that we have never seen before: if len( extras ) > 0: if handler is not None: # Allow the handler to decide their fate: handler.resolve_extras( structure, extras ) else: # Otherwise, add them to the top level as a new region (let the # user re-arrange them): structure.contents.append( DockRegion( contents = extras ) ) # Finally, replace the original structure with the updated structure: self.SetContents( structure ) #--------------------------------------------------------------------------- # Restores the previously saved structure (if any): #--------------------------------------------------------------------------- def ResetStructure ( self, window ): """ Restores the previously saved structure (if any). """ if self._structure is not None: self.SetStructure( window, self._structure ) #--------------------------------------------------------------------------- # Toggles the current 'lock' setting of the contents: #--------------------------------------------------------------------------- def ToggleLock ( self ): """ Toggles the current 'lock' setting of the contents. """ if self._contents is not None: self._contents.toggle_lock() #--------------------------------------------------------------------------- # Draws the contents of the sizer: #--------------------------------------------------------------------------- def Draw ( self, window ): """ Draws the contents of the sizer. """ if self._contents is not None: self._contents.draw( set_standard_font( wx.PaintDC( window ) ) ) else: clear_window( window ) #--------------------------------------------------------------------------- # Returns the object at a specified x, y position: #--------------------------------------------------------------------------- def ObjectAt ( self, x, y, force = False ): """ Returns the object at a specified window position. """ if self._contents is not None: return self._contents.object_at( x, y, force ) return None #--------------------------------------------------------------------------- # Gets a DockInfo object at a specified x, y position: #--------------------------------------------------------------------------- def DockInfoAt ( self, x, y, size, is_control ): """ Gets a DockInfo object at a specified x, y position. """ if self._contents is not None: return self._contents.dock_info_at( x, y, size, is_control, True ) return no_dock_info #--------------------------------------------------------------------------- # Minimizes/Maximizes a specified DockControl: #--------------------------------------------------------------------------- def MinMax ( self, window, dock_control ): """ Minimizes/Maximizes a specified DockControl. """ if self._max_structure is None: self._max_structure = self.GetStructure() for control in self.GetContents().get_controls(): control.visible = (control is dock_control) else: self.Reset( window ) #--------------------------------------------------------------------------- # Resets the DockSizer to a known state: #--------------------------------------------------------------------------- def Reset ( self, window ): """ Resets the DockSizer to a known state. """ if self._max_structure is not None: self.SetStructure( window, self._max_structure ) self._max_structure = None #--------------------------------------------------------------------------- # Returns whether the sizer can be maximized now: #--------------------------------------------------------------------------- def IsMaximizable ( self ): """ Returns whether the sizer can be maximized now. """ return (self._max_structure is None) def top_level_window_for ( control ): """ Returns the top-level window for a specified control. """ parent = control.GetParent() while parent is not None: control = parent parent = control.GetParent() return control pyface-4.1.0/pyface/i_split_widget.py0000644000175100001440000000451011674463363020610 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Mix-in class for split widgets. """ # Enthought library imports. from traits.api import Callable, Enum, Float, Interface class ISplitWidget(Interface): """ Mix-in class for split widgets. A split widget is one that is split in two either horizontally or vertically. """ #### 'ISplitWidget' interface ############################################# # The direction in which the widget is split. # # Splitting vertically means there will be a left hand panel and a right # hand panel, splitting horizontally means there will be a top panel and # a bottom panel. direction = Enum('vertical', 'vertical', 'horizontal') # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.5) # An optional callable that provides the left hand/top panel. lhs = Callable # An optional callable that provides the right hand/bottom panel. rhs = Callable ########################################################################### # Protected 'ISplitWidget' interface. ########################################################################### def _create_splitter(self, parent): """ Create the toolkit-specific control that represents the widget. """ def _create_lhs(self, parent): """ Creates the left hand/top panel depending on the direction. """ def _create_rhs(self, parent): """ Creates the right hand/bottom panel depending on the direction. """ class MSplitWidget(object): """ The mixin class that contains common code for toolkit specific implementations of the ISplitWidget interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/python_editor.py0000644000175100001440000000160311674463363020471 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A widget for editing Python code. """ # Import the toolkit specific version. from toolkit import toolkit_object PythonEditor = toolkit_object('python_editor:PythonEditor') #### EOF ###################################################################### pyface-4.1.0/pyface/application_window.py0000644000175100001440000000164611674463363021503 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a top-level application window. """ # Import the toolkit specific version. from toolkit import toolkit_object ApplicationWindow = toolkit_object('application_window:ApplicationWindow') #### EOF ###################################################################### pyface-4.1.0/pyface/i_application_window.py0000644000175100001440000001050411674463363022004 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface of a top-level application window. """ # Enthought library imports. from traits.api import Instance, List # Local imports. from action.api import MenuBarManager, StatusBarManager, ToolBarManager from image_resource import ImageResource from i_window import IWindow class IApplicationWindow(IWindow): """ The interface for a top-level application window. The application window has support for a menu bar, tool bar and a status bar (all of which are optional). Usage: Create a sub-class of this class and override the protected '_create_contents' method. """ #### 'IApplicationWindow' interface ####################################### # The window icon. The default is toolkit specific. icon = Instance(ImageResource) # The menu bar manager (None iff there is no menu bar). menu_bar_manager = Instance(MenuBarManager) # The status bar manager (None iff there is no status bar). status_bar_manager = Instance(StatusBarManager) # The tool bar manager (None iff there is no tool bar). tool_bar_manager = Instance(ToolBarManager) # If the underlying toolkit supports multiple toolbars, you can use this # list instead of the single ToolBarManager instance above. tool_bar_managers = List(ToolBarManager) ########################################################################### # Protected 'IApplicationWindow' interface. ########################################################################### def _create_contents(self, parent): """ Create and return the window's contents. parent is the parent control. """ def _create_menu_bar(self, parent): """ Creates the menu bar (if required). parent is the parent control. """ def _create_status_bar(self, parent): """ Creates the status bar (if required). parent is the parent control. """ def _create_tool_bar(self, parent): """ Creates the tool bar (if required). parent is the parent control. """ def _create_trim_widgets(self, parent): """ Creates the 'trim' widgets (the widgets around the window). parent is the parent control. """ def _set_window_icon(self): """ Sets the window icon (if required). """ class MApplicationWindow(object): """ The mixin class that contains common code for toolkit specific implementations of the IApplicationWindow interface. Implements: destroy(), _create_trim_widgets() """ ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): """ Destroy the control if it exists. """ if self.menu_bar_manager is not None: self.menu_bar_manager.destroy() if self.tool_bar_manager is not None: self.tool_bar_manager.destroy() for tool_bar_manager in self.tool_bar_managers: tool_bar_manager.destroy() super(MApplicationWindow, self).destroy() ########################################################################### # Protected 'IApplicationWindow' interface. ########################################################################### def _create_trim_widgets(self, parent): """ Creates the 'trim' widgets (the widgets around the window). """ # All of these are optional. self._set_window_icon() self._create_menu_bar(parent) self._create_tool_bar(parent) self._create_status_bar(parent) return #### EOF ###################################################################### pyface-4.1.0/pyface/list_box.py0000644000175100001440000000771611674463363017440 0ustar ischnellusers00000000000000""" A simple list box widget with a model-view architecture. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports. from traits.api import Event, Instance, Int # Local imports. from .list_box_model import ListBoxModel from .widget import Widget class ListBox(Widget): """ A simple list box widget with a model-view architecture. """ # The model that provides the data for the list box. model = Instance(ListBoxModel) # The objects currently selected in the list. selection = Int(-1) # Events. # An item has been activated. item_activated = Event # Default style. STYLE = wx.LB_SINGLE | wx.LB_HSCROLL | wx.LB_NEEDED_SB def __init__(self, parent, **traits): """ Creates a new list box. """ # Base-class constructors. super(ListBox, self).__init__(**traits) # Create the widget! self._create_control(parent) # Listen for changes to the model. self.model.on_trait_change(self._on_model_changed, "list_changed") return def dispose(self): self.model.on_trait_change(self._on_model_changed, "list_changed", remove = True) self.model.dispose() return ########################################################################### # 'ListBox' interface. ########################################################################### def refresh(self): """ Refreshes the list box. """ # For now we just clear out the entire list. self.control.Clear() # Populate the list. self._populate() return ########################################################################### # wx event handlers. ########################################################################### def _on_item_selected(self, event): """ Called when an item in the list is selected. """ listbox = event.GetEventObject() self.selection = listbox.GetSelection() return def _on_item_activated(self, event): """ Called when an item in the list is activated. """ listbox = event.GetEventObject() index = listbox.GetSelection() # Trait event notification. self.item_activated = index return ########################################################################### # Trait handlers. ########################################################################### #### Static ############################################################### def _selection_changed(self, index): """ Called when the selected item is changed. """ if index != -1: self.control.SetSelection(index) return #### Dynamic ############################################################## def _on_model_changed(self, event): """ Called when the model has changed. """ # For now we just clear out the entire list. self.refresh() return ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Creates the widget. """ self.control = wx.ListBox(parent, -1, style = self.STYLE) # Wire it up! wx.EVT_LISTBOX(self.control, self.control.GetId(), self._on_item_selected) wx.EVT_LISTBOX_DCLICK(self.control, self.control.GetId(), self._on_item_activated) # Populate the list. self._populate() return def _populate(self): """ Populates the list box. """ for index in range(self.model.get_item_count()): label, item = self.model.get_item_at(index) self.control.Append(label, item) return #### EOF ###################################################################### pyface-4.1.0/pyface/api.py0000644000175100001440000000473511674463363016364 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from about_dialog import AboutDialog from application_window import ApplicationWindow from clipboard import clipboard, Clipboard from confirmation_dialog import confirm, ConfirmationDialog from constant import OK, CANCEL, YES, NO from dialog import Dialog from directory_dialog import DirectoryDialog from file_dialog import FileDialog from filter import Filter from gui import GUI from heading_text import HeadingText from image_cache import ImageCache from image_resource import ImageResource from key_pressed_event import KeyPressedEvent from message_dialog import error, information, warning, MessageDialog from progress_dialog import ProgressDialog from python_editor import PythonEditor from python_shell import PythonShell from sorter import Sorter from splash_screen import SplashScreen from split_application_window import SplitApplicationWindow from split_dialog import SplitDialog from split_panel import SplitPanel from system_metrics import SystemMetrics from window import Window from widget import Widget ############################################################################### # This part of the module handles widgets that are still wx specific. This # will all be removed when everything has been ported to PyQt and pyface # becomes toolkit agnostic. ############################################################################### from traits.etsconfig.api import ETSConfig if ETSConfig.toolkit == 'wx': from expandable_panel import ExpandablePanel from image_widget import ImageWidget from layered_panel import LayeredPanel from mdi_application_window import MDIApplicationWindow from mdi_window_menu import MDIWindowMenu from multi_toolbar_window import MultiToolbarWindow from single_choice_dialog import SingleChoiceDialog # Fix for broken Pycrust introspect module. import util.fix_introspect_bug del ETSConfig pyface-4.1.0/pyface/image_list.py0000644000175100001440000000677011674463363017731 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A cached image list. """ from __future__ import absolute_import # Major package imports. import wx # Local imports from .image_resource import ImageResource # fixme: rename to 'CachedImageList'?!? class ImageList(wx.ImageList): """ A cached image list. """ def __init__(self, width, height): """ Creates a new cached image list. """ # Base-class constructor. wx.ImageList.__init__(self, width, height) self._width = width self._height = height # Cache of the indexes of the images in the list! self._cache = {} # {filename : index} return ########################################################################### # 'ImageList' interface. ########################################################################### def GetIndex(self, filename): """ Returns the index of the specified image. The image will be loaded and added to the image list if it is not already there. """ # Try the cache first. index = self._cache.get(filename) if index is None: # Were we passed an image resource? if isinstance(filename, ImageResource): # Create an image. image = filename.create_image(size=(self._width, self._height)) # If the filename is a string then it is the filename of some kind # of image (e.g 'foo.gif', 'image/foo.png' etc). elif isinstance(filename, basestring): # Load the image from the file. image = wx.Image(filename, wx.BITMAP_TYPE_ANY) # Otherwise the filename is *actually* an icon (in our case, # probably related to a MIME type). else: # Create a bitmap from the icon. bmp = wx.EmptyBitmap(self._width, self._height) bmp.CopyFromIcon(filename) # Turn it into an image so that we can scale it. image = wx.ImageFromBitmap(bmp) # We force all images in the cache to be the same size. self._scale(image) # We also force them to be bitmaps! bmp = image.ConvertToBitmap() # Add the bitmap to the actual list... index = self.Add(bmp) # ... and update the cache. self._cache[filename] = index return index ########################################################################### # Private interface. ########################################################################### def _scale(self, image): """ Scales the specified image (if necessary). """ if image.GetWidth() != self._width or image.GetHeight()!= self._height: image.Rescale(self._width, self._height) return image #### EOF ###################################################################### pyface-4.1.0/pyface/splash_screen_log_handler.py0000644000175100001440000000256611674463363023002 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A log handler that emits records to a splash screen. """ # Standard library imports. from logging import Handler class SplashScreenLogHandler(Handler): """ A log handler that displays log messages on a splash screen. """ def __init__(self, splash_screen): """ Creates a new handler for a splash screen. """ # Base class constructor. Handler.__init__(self) # The splash screen that we will display log messages on. self._splash_screen = splash_screen return def emit(self, record): """ Emits the log record. """ self._splash_screen.text = str(record.getMessage()) + '...' return #### EOF ###################################################################### pyface-4.1.0/pyface/i_widget.py0000644000175100001440000000457511674463363017410 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base interface for all pyface widgets. """ # Enthought library imports. from traits.api import Any, Interface class IWidget(Interface): """ The base interface for all pyface widgets. Pyface widgets delegate to a toolkit specific control. """ #### 'IWidget' interface ################################################## # The toolkit specific control that represents the widget. control = Any # The control's optional parent control. parent = Any ########################################################################### # 'IWidget' interface. ########################################################################### def destroy(self): """ Destroy the control if it exists. """ ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): """ Creates the toolkit specific control. """ def _create_control(self, parent): """ Create and return the toolkit specific control that represents the widget. """ class MWidget(object): """ The mixin class that contains common code for toolkit specific implementations of the IWidget interface. Implements: _create() """ ########################################################################### # Protected 'IWidget' interface. ########################################################################### def _create(self): self.control = self._create_control(self.parent) def _create_control(self, parent): raise NotImplementedError #### EOF ###################################################################### pyface-4.1.0/pyface/file_dialog.py0000644000175100001440000000164711674463363020050 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a dialog that allows the user to open/save files etc. """ # Import the toolkit specific version. from toolkit import toolkit_object FileDialog = toolkit_object('file_dialog:FileDialog') #### EOF ###################################################################### pyface-4.1.0/pyface/split_panel.py0000644000175100001440000000267711674463363020130 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A panel that is split in two either horizontally or vertically. """ # Local imports. from split_widget import SplitWidget from widget import Widget class SplitPanel(Widget, SplitWidget): """ A panel that is split in two either horizontally or vertically. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new panel. """ # Base class constructor. super(SplitPanel, self).__init__(**traits) # Create the widget's toolkit-specific control. self.control = self._create_splitter(parent) return #### EOF ###################################################################### pyface-4.1.0/pyface/resource_manager.py0000644000175100001440000000213211674463363021121 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a shared resource manager. """ # Enthought library imports. from pyface.resource.api import ResourceManager # Import the toolkit specific version. from toolkit import toolkit_object PyfaceResourceFactory = toolkit_object('resource_manager:PyfaceResourceFactory') # A shared instance. resource_manager = ResourceManager(resource_factory=PyfaceResourceFactory()) #### EOF ###################################################################### pyface-4.1.0/pyface/split_widget.py0000644000175100001440000000157611674463363020311 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Mix-in class for split widgets. """ # Import the toolkit specific version. from toolkit import toolkit_object SplitWidget = toolkit_object('split_widget:SplitWidget') #### EOF ###################################################################### pyface-4.1.0/pyface/toolkit.py0000644000175100001440000000746711674463363017305 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # #------------------------------------------------------------------------------ # Standard library imports. import os import sys # Enthought library imports. from traits.etsconfig.api import ETSConfig # This is set to the root part of the module path for the selected backend. _toolkit_backend = None def _init_toolkit(): """ Initialise the current toolkit. """ def import_toolkit(tk): try: # Try and import the toolkit's pyface backend init module. be = 'pyface.ui.%s.' % tk __import__(be + 'init') except: raise return be # Get the toolkit. if ETSConfig.toolkit: be = import_toolkit(ETSConfig.toolkit) else: # Toolkits to check for if none is explicitly specified. known_toolkits = ('wx', 'qt4', 'null') for tk in known_toolkits: try: be = import_toolkit(tk) # In case we have just decided on a toolkit, tell everybody else. ETSConfig.toolkit = tk break except (SystemExit, ImportError): import traceback print >>sys.stderr, ('Warning: Unable to import the %s backend ' 'for pyface due to traceback: %s\n') % (tk, traceback.format_exc().strip().replace('\n', '\n\t')) else: # Try to import the null toolkit but don't set the ETSConfig toolkit try: be = import_toolkit('null') print >>sys.stderr, ("Info: Unable to import any backend (%s) " "for pyface; using the 'null' toolkit instead.\n") % ", ".join(known_toolkits) except: raise ImportError("Unable to import a pyface backend for any " "of the %s toolkits" % ", ".join(known_toolkits)) # Save the imported toolkit module. global _toolkit_backend _toolkit_backend = be # Do this once then disappear. _init_toolkit() del _init_toolkit def toolkit_object(name): """ Return the toolkit specific object with the given name. The name consists of the relative module path and the object name separated by a colon. """ mname, oname = name.split(':') be_mname = _toolkit_backend + mname class Unimplemented(object): """ This is returned if an object isn't implemented by the selected toolkit. It raises an exception if it is ever instantiated. """ def __init__(self, *args, **kwargs): raise NotImplementedError("the %s pyface backend doesn't implement %s" % (ETSConfig.toolkit, oname)) be_obj = Unimplemented try: __import__(be_mname) try: be_obj = getattr(sys.modules[be_mname], oname) except AttributeError: pass except ImportError, e: # Ignore *ANY* errors unless a debug ENV variable is set. if 'ETS_DEBUG' in os.environ: # Attempt to only skip errors in importing the backend modules. # The idea here is that this only happens when the last entry in # the traceback's stack frame mentions the toolkit in question. import traceback frames = traceback.extract_tb(sys.exc_traceback) filename, lineno, function, text = frames[-1] if not _toolkit_backend in filename: raise return be_obj pyface-4.1.0/pyface/tree/0000755000175100001440000000000011674463363016167 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tree/node_event.py0000644000175100001440000000237311674463363020674 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The event fired by the tree models/node monitors etc. """ # Enthought library imports. from traits.api import Any, HasTraits, Int, List # Classes for event traits. class NodeEvent(HasTraits): """ The event fired by the tree models/node monitors etc. """ # The node that has changed. node = Any # The nodes (if any) that have been inserted/removed/changed. children = List # The nodes (if any) that have been replaced. old_children = List # The starting index for nodes that have been inserted. index = Int #### EOF ###################################################################### pyface-4.1.0/pyface/tree/trait_dict_node_type.py0000644000175100001440000000306511674463363022741 0ustar ischnellusers00000000000000""" The node type for a trait dictionary. """ # Enthought library imports. from traits.api import Any, Str # Local imports. from node_type import NodeType class TraitDictNodeType(NodeType): """ The node type for a trait dictionary. """ #### 'TraitDictNodeType' interface ######################################## # The type of object that provides the trait dictionary. klass = Any # The label text. text = Str # The trait name. trait_name = Str ########################################################################### # 'NodeType' interface. ########################################################################### def is_type_for(self, node): """ Returns True if this node type recognizes a node. """ is_type_for = isinstance(node, dict) \ and hasattr(node, 'object') \ and isinstance(node.object, self.klass) \ and node.name == self.trait_name return is_type_for def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return True def has_children(self, node): """ Returns True if a node has children, otherwise False. """ return len(node) > 0 def get_children(self, node): """ Returns the children of a node. """ return node.values() def get_text(self, node): """ Returns the label text for a node. """ return self.text ##### EOF ##################################################################### pyface-4.1.0/pyface/tree/images/0000755000175100001440000000000011674463363017434 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tree/images/open_folder.png0000644000175100001440000000115111674463363022434 0ustar ischnellusers00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxbd```.ˀJ>h@ ? qs1 31#~I0r ܊@2x/!W؀C@>ӿ>  q2N1H; h(#+Xُ[{`W]h3j?5ge`bv;5\ۇ p/wyW8D H~aՎ )@C?$a/ o~%H3_ 4~<?°7b? ? `Qǁ@ x3î? V z+~lalH!PHw :t @۫ D@L cĠ&D@?y(8^ `DC @L3(0IENDB`pyface-4.1.0/pyface/tree/images/document.png0000644000175100001440000000051511674463363021761 0ustar ischnellusers00000000000000PNG  IHDRabKGD pHYs  tIME8`&BIDAT8˭=n0H[!!l"A!QsL$^Xj,{|3m1 Z;騬8xt/5ǒ4MW(w=ιURW/P&AH৞"Bbh H(j;`ÀBFpXq R@ 6( ]qD q? P%K-` H @l@,D@|,chvaPb 4r@2s^IENDB`pyface-4.1.0/pyface/tree/images/image_LICENSE.txt0000644000175100001440000000136011674463363022421 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- GV (Gael Varoquaux) Public Domain N/A Nuvola LGPL image_LICENSE_Nuvola.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/tree/images: closed_folder.png | Nuvola document.png | GV open_folder.png | Nuvola pyface-4.1.0/pyface/tree/api.py0000644000175100001440000000217411674463363017316 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from node_event import NodeEvent from node_monitor import NodeMonitor from node_manager import NodeManager from node_tree import NodeTree from node_tree_model import NodeTreeModel from node_type import NodeType from trait_dict_node_type import TraitDictNodeType from trait_list_node_type import TraitListNodeType from tree_model import TreeModel from traits.etsconfig.api import ETSConfig if ETSConfig.toolkit == 'wx': # Tree has not yet been ported to qt from tree import Tree del ETSConfig pyface-4.1.0/pyface/tree/tree.py0000644000175100001440000012376211674463363017513 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tree control with a model/ui architecture. """ # Standard library imports. import logging import os # Major package imports. import wx # Enthought library imports. from pyface.api import Filter, GUI, KeyPressedEvent, Sorter, Widget from pyface.image_list import ImageList from traits.api import Any, Bool, Callable, Enum, Event, Instance from traits.api import List, Property, Callable, Str, Trait, Tuple from pyface.wx.drag_and_drop import PythonDropSource, PythonDropTarget # Local imports. from tree_model import TreeModel # Create a logger for this module. logger = logging.getLogger(__name__) class _Tree(wx.TreeCtrl): """ The wx tree control that we delegate to. We use this derived class so that we can detect the destruction of the tree and remove model listeners etc. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, tree, parent, wxid, style): """ Creates a new tree. """ # Base class constructor. wx.TreeCtrl.__init__(self, parent, wxid, style=style) # The tree that we are the toolkit-specific delegate for. self._tree = tree return def __del__(self): """ Destructor. """ # Stop listenting to the model! self._tree._remove_model_listeners(self._tree.model) return class Tree(Widget): """ A tree control with a model/ui architecture. """ # The default tree style. STYLE = wx.TR_EDIT_LABELS | wx.TR_HAS_BUTTONS | wx.CLIP_CHILDREN #### 'Tree' interface ##################################################### # The tree's filters (empty if no filtering is required). filters = List(Filter) # Mode for lines connecting tree nodes which emphasize hierarchy: # 'appearance' - only on when lines look good, # 'on' - always on, 'off' - always off # NOTE: on and off are ignored in favor of show_lines for now lines_mode = Enum('appearance', 'on', 'off') # The model that provides the data for the tree. model = Instance(TreeModel, ()) # The root of the tree (this is for convenience, it just delegates to # the tree's model). root = Property(Any) # The objects currently selected in the tree. selection = List # Selection mode. selection_mode = Enum('single', 'extended') # Should an image be shown for each node? show_images = Bool(True) # Should lines be drawn between levels in the tree. show_lines = Bool(True) # Should the root of the tree be shown? show_root = Bool(True) # The tree's sorter (None if no sorting is required). sorter = Instance(Sorter) #### Events #### # A right-click occurred on the control (not a node!). control_right_clicked = Event#(Point) # A key was pressed while the tree has focus. key_pressed = Event(KeyPressedEvent) # A node has been activated (ie. double-clicked). node_activated = Event#(Any) # A drag operation was started on a node. node_begin_drag = Event#(Any) # A (non-leaf) node has been collapsed. node_collapsed = Event#(Any) # A (non-leaf) node has been expanded. node_expanded = Event#(Any) # A left-click occurred on a node. # # Tuple(node, point). node_left_clicked = Event#(Tuple) # A right-click occurred on a node. # # Tuple(node, point) node_right_clicked = Event#(Tuple) #### Private interface #################################################### # A name to distinguish the tree for debugging! # # fixme: This turns out to be kinda useful... Should 'Widget' have a name # trait? _name = Str('Anonymous tree') # An optional callback to detect the end of a label edit. This is # useful because the callback will be invoked even if the node label was # not actually changed. _label_edit_callback = Trait(None, Callable, None) # Flag for allowing selection events to be ignored _ignore_selection_events = Bool(False) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, image_size=(16, 16), **traits): """ Creates a new tree. 'parent' is the toolkit-specific control that is the tree's parent. 'image_size' is a tuple in the form (int width, int height) that specifies the size of the images (if required) displayed in the tree. """ # Base class constructors. super(Tree, self).__init__(**traits) # Get our wx Id. wxid = wx.NewId() # Create the toolkit-specific control. self.control = tree = _Tree(self, parent, wxid,style=self._get_style()) # Wire up the wx tree events. wx.EVT_CHAR(tree, self._on_char) wx.EVT_LEFT_DOWN(tree, self._on_left_down) # fixme: This is not technically correct as context menus etc should # appear on a right up (or right click). Unfortunately, if we # change this to 'EVT_RIGHT_UP' wx does not fire the event unless the # right mouse button is double clicked 8^() Sad, but true! wx.EVT_RIGHT_DOWN(tree, self._on_right_down) # fixme: This is not technically correct as we would really like to use # 'EVT_TREE_ITEM_ACTIVATED'. Unfortunately, (in 2.6 at least), it # throws an exception when the 'Enter' key is pressed as the wx tree # item Id in the event seems to be invalid. It also seems to cause # any child frames that my be created in response to the event to # appear *behind* the parent window, which is, errrr, not great ;^) wx.EVT_LEFT_DCLICK(tree, self._on_tree_item_activated) #wx.EVT_TREE_ITEM_ACTIVATED(tree, wxid, self._on_tree_item_activated) wx.EVT_TREE_ITEM_COLLAPSING(tree, wxid, self._on_tree_item_collapsing) wx.EVT_TREE_ITEM_COLLAPSED(tree, wxid, self._on_tree_item_collapsed) wx.EVT_TREE_ITEM_EXPANDING(tree, wxid, self._on_tree_item_expanding) wx.EVT_TREE_ITEM_EXPANDED(tree, wxid, self._on_tree_item_expanded) wx.EVT_TREE_BEGIN_LABEL_EDIT(tree, wxid,self._on_tree_begin_label_edit) wx.EVT_TREE_END_LABEL_EDIT(tree, wxid, self._on_tree_end_label_edit) wx.EVT_TREE_BEGIN_DRAG(tree, wxid, self._on_tree_begin_drag) wx.EVT_TREE_SEL_CHANGED(tree, wxid, self._on_tree_sel_changed) wx.EVT_TREE_DELETE_ITEM(tree, wxid, self._on_tree_delete_item) # Enable the tree as a drag and drop target. self.control.SetDropTarget(PythonDropTarget(self)) # The image list is a wxPython-ism that caches all images used in the # control. self._image_list = ImageList(image_size[0], image_size[1]) if self.show_images: tree.AssignImageList(self._image_list) # Mapping from node to wx tree item Ids. self._node_to_id_map = {} # Add the root node. if self.root is not None: self._add_root_node(self.root) # Listen for changes to the model. self._add_model_listeners(self.model) return ########################################################################### # 'Tree' interface. ########################################################################### #### Properties ########################################################### def _get_root(self): """ Returns the root node of the tree. """ return self.model.root def _set_root(self, root): """ Sets the root node of the tree. """ self.model.root = root return #### Methods ############################################################## def collapse(self, node): """ Collapses the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.Collapse(wxid) return def edit_label(self, node, callback=None): """ Edits the label of the specified node. If a callback is specified it will be called when the label edit completes WHETHER OR NOT the label was actually changed. The callback must take exactly 3 arguments:- (tree, node, label) """ wxid = self._get_wxid(node) if wxid is not None: self._label_edit_callback = callback self.control.EditLabel(wxid) return def expand(self, node): """ Expands the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.Expand(wxid) return def expand_all(self): """ Expands every node in the tree. """ if self.show_root: self._expand_item(self._get_wxid(self.root)) else: for child in self._get_children(self.root): self._expand_item(self._get_wxid(child)) return def get_parent(self, node): """ Returns the parent of a node. This will only work iff the node has been displayed in the tree. If it hasn't then None is returned. """ # Has the node actually appeared in the tree yet? wxid = self._get_wxid(node) if wxid is not None: pid = self.control.GetItemParent(wxid) # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. populated, parent = self.control.GetPyData(pid) else: parent = None return parent def is_expanded(self, node): """ Returns True if the node is expanded, otherwise False. """ wxid = self._get_wxid(node) if wxid is not None: # If the root node is hidden then it is always expanded! if node is self.root and not self.show_root: is_expanded = True else: is_expanded = self.control.IsExpanded(wxid) else: is_expanded = False return is_expanded def is_selected(self, node): """ Returns True if the node is selected, otherwise False. """ wxid = self._get_wxid(node) if wxid is not None: is_selected = self.control.IsSelected(wxid) else: is_selected = False return is_selected def refresh(self, node): """ Refresh the tree starting from the specified node. Call this when the structure of the content has changed DRAMATICALLY. """ # Has the node actually appeared in the tree yet? pid = self._get_wxid(node) if pid is not None: # Delete all of the node's children and re-add them. self.control.DeleteChildren(pid) self.control.SetPyData(pid, (False, node)) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(pid, has_children) # fixme: At least on Windows, wx does not fire an expanding # event for a hidden root node, so we have to populate the node # manually. if node is self.root and not self.show_root: # Add the child nodes. for child in self._get_children(node): self._add_node(pid, child) else: # Expand it. if self.control.IsExpanded(pid): self.control.Collapse(pid) self.control.Expand(pid) return def select(self, node): """ Selects the specified node. """ wxid = self._get_wxid(node) if wxid is not None: self.control.SelectItem(wxid) return def set_selection(self, list): """ Selects the specified list of nodes. """ logger.debug('Setting selection to [%s] within Tree [%s]', list, self) # Update the control to reflect the target list by unselecting # everything and then selecting each item in the list. During this # process, we want to avoid changing our own selection. self._ignore_selection_events = True self.control.UnselectAll() for node in list: try: self.select(node) except: logger.exception('Unable to select node [%s]', node) self._ignore_selection_events = False # Update our selection to reflect the final selection state. self.selection = self._get_selection() ########################################################################### # 'PythonDropTarget' interface. ########################################################################### def on_drag_over(self, x, y, obj, default_drag_result): """ Called when a node is dragged over the tree. """ result = wx.DragNone # Find the node that we are dragging over... node = self._get_drag_drop_node(x, y) if node is not None: # Ask the model if the node allows the object to be dropped onto # it. if self.model.can_drop(node, obj): result = default_drag_result return result def on_drop(self, x, y, obj, default_drag_result): """ Called when a node is dropped on the tree. """ # Find the node that we are dragging over... node = self._get_drag_drop_node(x, y) if node is not None: self.model.drop(node, obj) return default_drag_result ########################################################################### # Private interface. ########################################################################### def _get_wxid(self, node): """ Returns the wxid for the specified node. Returns None if the node has not yet appeared in the tree. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) return self._node_to_id_map.get(key, None) def _set_wxid(self, node, wxid): """ Sets the wxid for the specified node. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) self._node_to_id_map[key] = wxid return def _remove_wxid(self, node): """ Removes the wxid for the specified node. """ # The model must generate a unique key for each node (unique within the # model). key = self.model.get_key(node) try: del self._node_to_id_map[key] except KeyError: # fixme: No, really, this is a serious one... How do we get in this # situation. It came up when using the canvas stuff... logger.warn('removing node: %s' % str(node)) return def _get_style(self): """ Returns the wx style flags for creating the tree control. """ # Start with the default flags. style = self.STYLE # Turn lines off for appearance on *nix. # ...for now, show_lines determines if lines are on or off, but # eventually lines_mode may eliminate the need for show_lines if self.lines_mode == 'appearance' and os.name == 'posix': self.show_lines = False if not self.show_lines: style = style | wx.TR_NO_LINES if not self.show_root: # fixme: It looks a little weird, but it we don't have the # 'lines at root' style then wx won't draw the expand/collapse # image on non-leaf nodes at the root level 8^() style = style | wx.TR_HIDE_ROOT | wx.TR_LINES_AT_ROOT if self.selection_mode != 'single': style = style | wx.TR_MULTIPLE | wx.TR_EXTENDED return style def _add_model_listeners(self, model): """ Adds listeners for model changes. """ # Listen for changes to the model. model.on_trait_change(self._on_root_changed, 'root') model.on_trait_change(self._on_nodes_changed, 'nodes_changed') model.on_trait_change(self._on_nodes_inserted, 'nodes_inserted') model.on_trait_change(self._on_nodes_removed, 'nodes_removed') model.on_trait_change(self._on_nodes_replaced, 'nodes_replaced') model.on_trait_change(self._on_structure_changed, 'structure_changed') return def _remove_model_listeners(self, model): """ Removes listeners for model changes. """ # Unhook the model event listeners. model.on_trait_change( self._on_root_changed, 'root', remove=True ) model.on_trait_change( self._on_nodes_changed, 'nodes_changed', remove=True ) model.on_trait_change( self._on_nodes_inserted, 'nodes_inserted', remove=True ) model.on_trait_change( self._on_nodes_removed, 'nodes_removed', remove=True ) model.on_trait_change( self._on_nodes_replaced, 'nodes_replaced', remove=True ) model.on_trait_change( self._on_structure_changed, 'structure_changed', remove=True ) return def _add_root_node(self, node): """ Adds the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.AddRoot(text, image_index, image_index) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # If the root node is hidden, get its children. if not self.show_root: # Add the child nodes. for child in self._get_children(node): self._add_node(wxid, child) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). if self.show_root: self.control.SetPyData(wxid, (not self.show_root, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) # Automatically expand the root. if self.show_root: self.control.Expand(wxid) return def _add_node(self, pid, node): """ Adds 'node' as a child of the node identified by 'pid'. If 'pid' is None then we are adding the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.AppendItem(pid, text, image_index, image_index) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). self.control.SetPyData(wxid, (False, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) return def _insert_node(self, pid, node, index): """ Inserts 'node' as a child of the node identified by 'pid'. If 'pid' is None then we are adding the root node. """ # Get the tree item image index and the label text. image_index = self._get_image_index(node) text = self._get_text(node) # Add the node. wxid = self.control.InsertItemBefore( pid, index, text, image_index, image_index ) # This gives the model a chance to wire up trait handlers etc. self.model.add_listener(node) # Does the node have any children? has_children = self._has_children(node) self.control.SetItemHasChildren(wxid, has_children) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data (which in our case is an arbitrary # Python object provided by the tree model). self.control.SetPyData(wxid, (False, node)) # Make sure that we can find the node's Id. self._set_wxid(node, wxid) return def _remove_node(self, wxid, node): """ Removes a node from the tree. """ # This gives the model a chance to remove trait handlers etc. self.model.remove_listener(node) # Remove the reference to the item's data. self._remove_wxid(node) self.control.SetPyData(wxid, None) return def _update_node(self, wxid, node): """ Updates the image and text of the specified node. """ # Get the tree item image index. image_index = self._get_image_index(node) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Normal) self.control.SetItemImage(wxid, image_index, wx.TreeItemIcon_Selected) # Get the tree item text. text = self._get_text(node) self.control.SetItemText(wxid, text) return def _has_children(self, node): """ Returns True if a node has children. """ # fixme: To be correct we *should* apply filtering here, but that # seems to blow a hole throught models that have some efficient # mechanism for determining whether or not they have children. There # is also a precedent for doing it this way in Windoze, where a node # gets marked as though it can be expanded, even thought when the # expansion occurs, no children are present! return self.model.has_children(node) def _get_children(self, node): """ Get the children of a node. """ children = self.model.get_children(node) # Filtering.... filtered_children = [] for child in children: for filter in self.filters: if not filter.select(self, node, child): break else: filtered_children.append(child) # Sorting... if self.sorter is not None: self.sorter.sort(self, node, filtered_children) return filtered_children def _get_image_index(self, node): """ Returns the tree item image index for a node. """ expanded = self.is_expanded(node) selected = self.is_selected(node) # Get the image used to represent the node. image = self.model.get_image(node, selected, expanded) if image is not None: image_index = self._image_list.GetIndex(image) else: image_index = -1 return image_index def _get_drag_drop_node(self, x, y): """ Returns the node that is being dragged/dropped on. Returns None if the cursor is not over the icon or label of a node. """ data, wxid, flags, point = self._hit_test((x, y)) if data is not None: populated, node = data else: node = None return node def _get_text(self, node): """ Returns the tree item text for a node. """ text = self.model.get_text(node) if text is None: text = '' return text def _unpack_event(self, event, wxid=None): """ Unpacks the event to see whether a tree item was involved. """ try: point = event.GetPosition() except: point = event.GetPoint() return self._hit_test(point, wxid) def _hit_test(self, point, wxid=None): """ Determines whether a point is within a node's label or icon. """ flags = wx.TREE_HITTEST_ONITEMLABEL if (wxid is None) or (not wxid.IsOk()): wxid, flags = self.control.HitTest(point) # Warning: On GTK we have to check the flags before we call 'GetPyData' # because if we call it when the hit test returns 'nowhere' it will # barf (on Windows it simply returns 'None' 8^() if flags & wx.TREE_HITTEST_NOWHERE: data = None elif flags & wx.TREE_HITTEST_ONITEMICON \ or flags & wx.TREE_HITTEST_ONITEMLABEL: data = self.control.GetPyData(wxid) # fixme: Not sure why 'TREE_HITTEST_NOWHERE' doesn't catch everything! else: data = None return data, wxid, flags, point def _get_selection(self): """ Returns a list of the selected nodes """ selection = [] for wxid in self.control.GetSelections(): data = self.control.GetPyData(wxid) if data is not None: populated, node = data selection.append(self.model.get_selection_value(node)) return selection def _expand_item(self, wxid): """ Recursively expand a tree item. """ self.control.Expand(wxid) cid, cookie = self.control.GetFirstChild(wxid) while cid.IsOk(): self._expand_item(cid) cid, cookie = self.control.GetNextChild(wxid, cookie) return #### Trait event handlers ################################################# def _on_root_changed(self, root): """ Called when the root of the model has changed. """ # Delete everything... if self.control is not None: self.control.DeleteAllItems() self._node_to_id_map = {} # ... and then add the root item back in. if root is not None: self._add_root_node(root) return def _on_nodes_changed(self, event): """ Called when nodes have been changed. """ self._update_node(self._get_wxid(event.node), event.node) for child in event.children: cid = self._get_wxid(child) if cid is not None: self._update_node(cid, child) return def _on_nodes_inserted(self, event): """ Called when nodes have been inserted. """ parent = event.node children = event.children index = event.index # Has the node actually appeared in the tree yet? pid = self._get_wxid(parent) if pid is not None: # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. if self.show_root or parent is not self.root: populated, node = self.control.GetPyData(pid) else: populated = True # If the node is not yet populated then just get the children and # add them. if not populated: for child in self._get_children(parent): self._add_node(pid, child) # Otherwise, insert them. else: # An index of -1 means append! if index == -1: index = self.control.GetChildrenCount(pid, False) for child in children: self._insert_node(pid, child, index) index += 1 # The element is now populated! if self.show_root or parent is not self.root: self.control.SetPyData(pid, (True, parent)) # Does the node have any children now? has_children = self.control.GetChildrenCount(pid) > 0 self.control.SetItemHasChildren(pid, has_children) # If the node is not expanded then expand it. if not self.is_expanded(parent): self.expand(parent) return def _on_nodes_removed(self, event): """ Called when nodes have been removed. """ parent = event.node children = event.children # Has the node actually appeared in the tree yet? pid = self._get_wxid(parent) if pid is not None: for child in event.children: cid = self._get_wxid(child) if cid is not None: self.control.Delete(cid) # Does the node have any children left? has_children = self.control.GetChildrenCount(pid) > 0 self.control.SetItemHasChildren(pid, has_children) return def _on_nodes_replaced(self, event): """ Called when nodes have been replaced. """ for old_child, new_child in zip(event.old_children, event.children): cid = self._get_wxid(old_child) if cid is not None: # Remove listeners from the old node. self.model.remove_listener(old_child) # Delete all of the node's children. self.control.DeleteChildren(cid) # Update the visual appearance of the node. self._update_node(cid, new_child) # Update the node data. # # The item data is a tuple. The first element indicates # whether or not we have already populated the item with its # children. The second element is the actual item data (which # in our case is an arbitrary Python object provided by the # tree model). self.control.SetPyData(cid, (False, new_child)) # Remove the old node from the node to Id map. self._remove_wxid(old_child) # Add the new node to the node to Id map. self._set_wxid(new_child, cid) # Add listeners to the new node. self.model.add_listener(new_child) # Does the new node have any children? has_children = self._has_children(new_child) self.control.SetItemHasChildren(cid, has_children) # Update the tree's selection (in case the old node that was replaced # was selected, the selection should now include the new node). self.selection = self._get_selection() return def _on_structure_changed(self, event): """ Called when the structure of a node has changed drastically. """ self.refresh(event.node) return #### wx event handlers #################################################### def _on_char(self, event): """ Called when a key is pressed when the tree has focus. """ self.key_pressed = KeyPressedEvent( alt_down = event.m_altDown == 1, control_down = event.m_controlDown == 1, shift_down = event.m_shiftDown == 1, key_code = event.m_keyCode ) event.Skip() return def _on_left_down(self, event): """ Called when the left mouse button is clicked on the tree. """ data, id, flags, point = self._unpack_event(event) # Save point for tree_begin_drag method to workaround a bug in ?? when # wx.TreeEvent.GetPoint returns only (0,0). This happens under linux # when using wx-2.4.2.4, for instance. self._point_left_clicked = point # Did the left click occur on a tree item? if data is not None: populated, node = data # Trait event notification. self.node_left_clicked = node, point # Give other event handlers a chance. event.Skip() return def _on_right_down(self, event): """ Called when the right mouse button is clicked on the tree. """ data, id, flags, point = self._unpack_event(event) # Did the right click occur on a tree item? if data is not None: populated, node = data # Trait event notification. self.node_right_clicked = node, point # Otherwise notify that the control itself was clicked else: self.control_right_clicked = point # Give other event handlers a chance. event.Skip() return def _on_tree_item_activated(self, event): """ Called when a tree item is activated (i.e., double clicked). """ # fixme: See the comment where the events are wired up for more # information. ## # Which item was activated? ## wxid = event.GetItem() # Which item was activated. point = event.GetPosition() wxid, flags = self.control.HitTest(point) # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Trait event notiification. self.node_activated = node return def _on_tree_item_collapsing(self, event): """ Called when a tree item is about to collapse. """ # Which item is collapsing? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Give the model a chance to veto the collapse. if not self.model.is_collapsible(node): event.Veto() return def _on_tree_item_collapsed(self, event): """ Called when a tree item has been collapsed. """ # Which item was collapsed? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Make sure that the item's 'closed' icon is displayed etc. self._update_node(wxid, node) # Trait event notification. self.node_collapsed = node return def _on_tree_item_expanding(self, event): """ Called when a tree item is about to expand. """ # Which item is expanding? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Give the model a chance to veto the expansion. if self.model.is_expandable(node): # Lazily populate the item's children. if not populated: # Add the child nodes. for child in self._get_children(node): self._add_node(wxid, child) # The element is now populated! self.control.SetPyData(wxid, (True, node)) else: event.Veto() return def _on_tree_item_expanded(self, event): """ Called when a tree item has been expanded. """ # Which item was expanded? wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Make sure that the node's 'open' icon is displayed etc. self._update_node(wxid, node) # Trait event notification. self.node_expanded = node return def _on_tree_begin_label_edit(self, event): """ Called when the user has started editing an item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Give the model a chance to veto the edit. if not self.model.is_editable(node): event.Veto() return def _on_tree_end_label_edit(self, event): """ Called when the user has finished editing am item's label. """ wxid = event.GetItem() # The item data is a tuple. The first element indicates whether or not # we have already populated the item with its children. The second # element is the actual item data. populated, node = self.control.GetPyData(wxid) # Give the model a chance to veto the edit. label = event.GetLabel() # Making sure the new label is not an empty string if label is not None and len(label) > 0 and \ self.model.can_set_text(node, label): def end_label_edit(): """ Called to complete the label edit. """ # Set the node's text. self.model.set_text(node, label) # If a label edit callback was specified (in the call to # 'edit_label'), then call it). if self._label_edit_callback is not None: self._label_edit_callback(self, node, label) return # We use a deffered call here, because a name change can trigger # the structure of a node to change, and hence the actual tree # nodes might get moved/deleted before the label edit operation has # completed. When this happens wx gets very confused! By using # 'invoke_later' we allow the label edit to complete. GUI.invoke_later(end_label_edit) else: event.Veto() # If a label edit callback was specified (in the call to # 'edit_label'), then call it). if self._label_edit_callback is not None: self._label_edit_callback(self, node, label) return def _on_tree_begin_drag(self, event): """ Called when a drag operation is starting on a tree item. """ # Get the node, its id and the point where the event occurred. data, wxid, flags, point = self._unpack_event(event, event.GetItem()) if point == (0,0): # Apply workaround for GTK. point = self.point_left_clicked wxid, flags = self.HitTest(point) data = self.control.GetPyData(wxid) if data is not None: populated, node = data # Give the model a chance to veto the drag. if self.model.is_draggable(node): # We ask the model for the actual value to drag. drag_value = self.model.get_drag_value(node) # fixme: This is a terrible hack to get the binding x passed # during a drag operation. Bindings should probably *always* # be dragged and our drag and drop mechanism should allow # extendable ways to extract the actual data. from pyface.wx.drag_and_drop import clipboard clipboard.node = [node] # Make sure that the tree selection is updated before we start # the drag. If we don't do this then if the first thing a # user does is drag a tree item (i.e., without a separate click # to select it first) then the selection appears empty. self.selection = self._get_selection() # Start the drag. PythonDropSource(self.control, drag_value, self) # Trait event notification. self.node_begin_drag = node else: event.Veto() return # fixme: This is part of the drag and drop hack... def on_dropped(self): """ Callback invoked when a drag/drop operation has completed. """ from pyface.wx.drag_and_drop import clipboard clipboard.node = None return def _on_tree_sel_changed(self, event): """ Called when the selection is changed. """ # Update our record of the selection to whatever was selected in the # tree UNLESS we are ignoring selection events. if not self._ignore_selection_events: # Trait notification. self.selection = self._get_selection() return def _on_tree_delete_item(self, event): """ Called when a tree item is being been deleted. """ # Which item is being deleted? wxid = event.GetItem() # Check if GetPyData() returned a valid to tuple to unpack # ...if so, remove the node from the tree, otherwise just return # # fixme: Whoever addeed this code (and the comment above) didn't say # when this was occurring. This is method is called in response to a wx # event to delete an item and hence the item data should never be None # surely?!? Was it happening just on one platform?!? data = self.control.GetPyData(wxid) if data is not None: # The item data is a tuple. The first element indicates whether or # not we have already populated the item with its children. The # second element is the actual item data. populated, node = data # Remove the node. self._remove_node(wxid, node) return #### EOF ###################################################################### pyface-4.1.0/pyface/tree/tree_model.py0000644000175100001440000001266211674463363020667 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Model for tree views. """ # Enthought library imports. from traits.api import Any, HasTraits, Event # Local imports. from node_event import NodeEvent class TreeModel(HasTraits): """ Model for tree views. """ #### 'TreeModel' interface ################################################ # The root of the model. root = Any # Fired when nodes in the tree have changed in some way that affects their # appearance but NOT their structure or position in the tree. nodes_changed = Event(NodeEvent) # Fired when nodes have been inserted into the tree. nodes_inserted = Event(NodeEvent) # Fired when nodes have been removed from the tree. nodes_removed = Event(NodeEvent) # Fired when nodes have been replaced in the tree. nodes_replaced = Event(NodeEvent) # Fire when the structure of the tree has changed DRASTICALLY from a given # node down. structure_changed = Event(NodeEvent) ######################################################################### # 'TreeModel' interface. ######################################################################### def has_children(self, node): """ Returns True if a node has children, otherwise False. This method is provided in case the model has an efficient way to determine whether or not a node has any children without having to actually get the children themselves. """ raise NotImplementedError def get_children(self, node): """ Returns the children of a node. """ raise NotImplementedError def get_drag_value(self, node): """ Get the value that is dragged for a node. By default the drag value is the node itself. """ return node def can_drop(self, node, obj): """ Returns True if a node allows an object to be dropped onto it. """ return False def drop(self, node, obj): """ Drops an object onto a node. """ raise NotImplementedError def get_image(self, node, selected, expanded): """ Returns the label image for a node. Return None (the default) if no image is required. """ return None def get_key(self, node): """ Generate a unique key for a node. """ try: key = hash(node) except: key = id(node) return key def get_selection_value(self, node): """ Get the value that is used when a node is selected. By default the selection value is the node itself. """ return node def get_text(self, node): """ Returns the label text for a node. Return None if no text is required. By default we return 'str(node)'. """ return str(node) def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ return len(text.strip()) > 0 def set_text(self, node, text): """ Sets the label text for a node. """ pass def is_collapsible(self, node): """ Returns True if the node is collapsible, otherwise False. """ return True def is_draggable(self, node): """ Returns True if the node is draggable, otherwise False. """ return True def is_editable(self, node): """ Returns True if the node is editable, otherwise False. If the node is editable, its text can be set via the UI. """ return False def is_expandable(self, node): """ Returns True if the node is expandanble, otherwise False. """ return True def add_listener(self, node): """ Adds a listener for changes to a node. """ pass def remove_listener(self, node): """ Removes a listener for changes to a node. """ pass def fire_nodes_changed(self, node, children): """ Fires the nodes changed event. """ self.nodes_changed = NodeEvent(node=node, children=children) return def fire_nodes_inserted(self, node, children): """ Fires the nodes inserted event. """ self.nodes_inserted = NodeEvent(node=node, children=children) return def fire_nodes_removed(self, parent, children): """ Fires the nodes removed event. """ self.nodes_removed = NodeEvent(node=node, children=children) return def fire_nodes_replaced(self, node, old_children, new_children): """ Fires the nodes removed event. """ self.nodes_replaced = NodeEvent( node=node, old_children=old_children, children=new_children ) return def fire_structure_changed(self, node): """ Fires the structure changed event. """ self.structure_changed = NodeEvent(node=node) return #### EOF #################################################################### pyface-4.1.0/pyface/tree/node_manager.py0000644000175100001440000001111211674463363021154 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The node manager looks after a collection of node types. """ # Standard library imports. import logging # Enthought library imports. from traits.api import HasPrivateTraits, List # Local imports from node_type import NodeType # Create a logger for this module. logger = logging.getLogger(__name__) class NodeManager(HasPrivateTraits): """ The node manager looks after a collection of node types. """ #### 'NodeManager' interface ########################################## # All registered node types. node_types = List(NodeType) # fixme: Where should the system actions go? The node tree, the node # tree model, here?!? system_actions = List ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new tree model. """ # Base class constructor. super(NodeManager, self).__init__(**traits) # This saves looking up a node's type every time. If we ever have # nodes that change type dynamically then we will obviously have to # re-think this (although we should probably re-think dynamic type # changes first ;^). self._node_to_type_map = {} # { Any node : NodeType node_type } return ########################################################################### # 'NodeManager' interface. ########################################################################### # fixme: This is the only API call that we currently have that manipulates # the manager's node types. Should we make the 'node_types' list # available via the public API? def add_node_type(self, node_type): """ Adds a new node type to the manager. """ node_type.node_manager = self self.node_types.append(node_type) return def get_node_type(self, node): """ Returns the node's type. Returns None if none of the manager's node types recognize the node. """ # Generate the key for the node to type map. key = self.get_key(node) # Check the cache first. node_type = self._node_to_type_map.get(key, None) if node_type is None: # If we haven't seen this node before then attempt to find a node # type that 'recognizes' it. # # fixme: We currently take the first node type that recognizes the # node. This obviously means that ordering of node types is # important, but we don't have an interface for controlling the # order. Maybe sort on some 'precedence' trait on the node type? for node_type in self.node_types: if node_type.is_type_for(node): self._node_to_type_map[key] = node_type break else: node_type = None if node_type is None: logger.warn('no node type for %s' % str(node)) return node_type def get_key(self, node): """ Generates a unique key for a node. In this case, 'unique' means unqiue within the node manager. """ # We do it like this 'cos, for example, using id() on a string doesn't # give us what we want, but things like lists aren't hashable, so we # can't always use hash()). try: key = hash(node) except: key = id(node) return key ########################################################################### # Private interface. ########################################################################### def _node_types_changed(self, new): """ Called when the entire list of node types has been changed. """ for node_type in new: node_type.node_manager = self return #### EOF ###################################################################### pyface-4.1.0/pyface/tree/node_tree.py0000644000175100001440000001337211674463363020513 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tree control with extensible node types. """ # Standard library imports. import inspect # Enthought library imports. from pyface.action.api import ActionEvent from traits.api import Instance, List, Property # Local imports. from node_manager import NodeManager from node_type import NodeType from node_tree_model import NodeTreeModel from tree import Tree class NodeTree(Tree): """ A tree control with extensible node types. """ #### 'Tree' interface ##################################################### # The model that provides the data for the tree. model = Instance(NodeTreeModel, ()) #### 'NodeTree' interface ################################################# # The node manager looks after all node types. node_manager = Property(Instance(NodeManager)) # The node types in the tree. node_types = Property(List(NodeType)) ########################################################################### # 'NodeTree' interface. ########################################################################### #### Properties ########################################################### # node_manager def _get_node_manager(self): """ Returns the root node of the tree. """ return self.model.node_manager def _set_node_manager(self, node_manager): """ Sets the root node of the tree. """ self.model.node_manager = node_manager return # node_types def _get_node_types(self): """ Returns the node types in the tree. """ return self.model.node_manager.node_types def _set_node_types(self, node_types): """ Sets the node types in the tree. """ self.model.node_manager.node_types = node_types return ########################################################################### # 'Tree' interface. ########################################################################### #### Trait event handlers ################################################# def _node_activated_changed(self, obj): """ Called when a node has been activated (i.e., double-clicked). """ default_action = self.model.get_default_action(obj) if default_action is not None: self._perform_default_action(default_action, obj) return def _node_right_clicked_changed(self, (obj, point)): """ Called when the right mouse button is clicked on the tree. """ # Add the node that the right-click occurred on to the selection. self.select(obj) # fixme: This is a hack to allow us to attach the node that the # right-clicked occurred on to the action event. self._context = obj # Ask the model for the node's context menu. menu_manager = self.model.get_context_menu(obj) if menu_manager is not None: self._popup_menu(menu_manager, obj, point) return ########################################################################### # 'ActionController' interface. ########################################################################### def add_to_menu(self, menu_item): """ Adds a menu item to a menu bar. """ pass def add_to_toolbar(self, toolvar_item): """ Adds a tool bar item to a tool bar. """ pass def can_add_to_menu(self, action): """ Returns True iff an action can be added to the menu. """ return True def perform(self, action, event): """ Perform an action. """ # fixme: We need a more formal event structure! event.widget = self event.context = self._context # fixme: the 'perform' method without taking an event is deprecated! args, varargs, varkw, defaults = inspect.getargspec(action.perform) # If the only argument is 'self' then this is the DEPRECATED # interface. if len(args) == 1: action.perform() else: action.perform(event) return ########################################################################### # Private interface. ########################################################################### def _create_action_event(self, obj): """ Return a new action event for the specified object. """ return ActionEvent(widget=self, context=obj) def _perform_default_action(self, action, obj): """ Perform the default action on the specified object. """ action.perform(self._create_action_event(obj)) return def _popup_menu(self, menu_manager, obj, point): """ Popup the menu described by the menu manager. """ # Create the actual menu control. menu = menu_manager.create_menu(self.control, self) if not menu.is_empty(): # Show the menu. If an action is selected it will be performed # *before* this call returns. menu.show(*point) # This gives the actions in the menu manager a chance to cleanup # any event listeners etc. menu_manager.destroy() return #### EOF ###################################################################### pyface-4.1.0/pyface/tree/node_monitor.py0000644000175100001440000001041711674463363021240 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A monitor for appearance and structural changes to a node. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Any, Event, HasTraits # Local imports. from node_event import NodeEvent # Create a logger for this module. logger = logging.getLogger(__name__) class NodeMonitor(HasTraits): """ A monitor for appearance and structural changes to a node. """ #### 'NodeMonitor' interface ############################################## # The node that we are monitoring. node = Any #### Events #### # Fired when child nodes in the node that we are monitoring have changed in # some way that affects their appearance but NOT their structure. nodes_changed = Event(NodeEvent) # Fired when child nodes have been inserted into the node that we are # monitoring. nodes_inserted = Event(NodeEvent) # Fired when child nodes have been removed from the node that we are # monitoring. nodes_removed = Event(NodeEvent) # Fired when child nodes have been replaced in the node that we are # monitoring. nodes_replaced = Event(NodeEvent) # Fired when the structure of the node that we are monitoring has changed # DRASTICALLY (i.e., we do not have enough information to make individual # changes/inserts/removals). structure_changed = Event(NodeEvent) ########################################################################### # 'NodeMonitor' interface. ########################################################################### #### public methods ####################################################### def start(self): """ Start listening to changes to the node. """ if self.node.obj is not None: self._setup_trait_change_handlers(self.node.obj) return def stop(self): """ Stop listening to changes to the node. """ if self.node.obj is not None: self._setup_trait_change_handlers(self.node.obj, remove=True) return def fire_nodes_changed(self, children=[]): """ Fires the nodes changed event. """ self.nodes_changed = NodeEvent(node=self.node, children=children) return def fire_nodes_inserted(self, children, index=-1): """ Fires the nodes inserted event. If the index is -1 it means the nodes were appended. fixme: The tree and model should probably have an 'appended' event. """ self.nodes_inserted = NodeEvent( node=self.node, children=children, index=index ) return def fire_nodes_removed(self, children): """ Fires the nodes removed event. """ self.nodes_removed = NodeEvent(node=self.node, children=children) return def fire_nodes_replaced(self, old_children, new_children): """ Fires the nodes replaced event. """ self.nodes_replaced = NodeEvent( node=self.node, old_children=old_children, children=new_children ) return def fire_structure_changed(self): """ Fires the structure changed event. """ self.structure_changed = NodeEvent(node=self.node) return #### protected methods #################################################### def _setup_trait_change_handlers(self, obj, remove=False): """ Add or remove trait change handlers to/from a node. """ logger.debug('%s trait listeners on (%s) in NodeMonitor (%s)', (remove and 'Removing' or 'Adding'), obj, self) pass # derived classes should do something here! return #### EOF ###################################################################### pyface-4.1.0/pyface/tree/__init__.py0000644000175100001440000000000011674463363020266 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/tree/node_type.py0000644000175100001440000002001111674463363020521 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all node types. """ # Enthought library imports. from traits.api import Any, HasPrivateTraits, Instance, List from pyface.api import ImageResource from pyface.action.api import Action, ActionManagerItem, Group from pyface.action.api import MenuManager class NodeType(HasPrivateTraits): """ The base class for all node types. """ # The default image used to represent nodes that DO NOT allow children. DOCUMENT = ImageResource('document') # The default image used to represent nodes that allow children and are NOT # expanded. CLOSED_FOLDER = ImageResource('closed_folder') # The default image used to represent nodes that allow children and ARE # expanded. OPEN_FOLDER = ImageResource('open_folder') #### 'NodeType' interface ################################################# # The node manager that the type belongs to. node_manager = Instance('pyface.tree.node_manager.NodeManager') # The image used to represent nodes that DO NOT allow children. image = Instance(ImageResource) # The image used to represent nodes that allow children and are NOT # expanded. closed_image = Instance(ImageResource) # The image used to represent nodes that allow children and ARE expanded. open_image = Instance(ImageResource) # The default actions/groups/menus available on nodes of this type (shown # on the context menu). actions = Any#List # The default action for nodes of this type. The default action is # performed when a node is activated (i.e., double-clicked). default_action = Instance(Action) # The default actions/groups/menus for creating new children within nodes # of this type (shown in the 'New' menu of the context menu). new_actions = Any#List ########################################################################### # 'NodeType' interface. ########################################################################### #### These methods are specific to the 'NodeType' interface ############### def is_type_for(self, node): """ Returns True if a node is deemed to be of this type. """ raise NotImplementedError def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return False def get_actions(self, node): """ Returns the node-specific actions for a node. """ return self.actions def get_context_menu(self, node): """ Returns the context menu for a node. """ sat = Group(id='SystemActionsTop') nsa = Group(id='NodeSpecificActions') sab = Group(id='SystemActionsBottom') # The 'New' menu. new_actions = self.get_new_actions(node) if new_actions is not None and len(new_actions) > 0: sat.append( MenuManager( name = 'New', *new_actions ), ) # Node-specific actions. actions = self.get_actions(node) if actions is not None and len(actions) > 0: for item in actions: nsa.append(item) # System actions (actions available on ALL nodes). system_actions = self.node_manager.system_actions if len(system_actions) > 0: for item in system_actions: sab.append(item) context_menu = MenuManager(sat, nsa, sab) context_menu.dump() return context_menu def get_copy_value(self, node): """ Get the value that is copied for a node. By default, returns the node itself. """ return node def get_default_action(self, node): """ Returns the default action for a node. """ return self.default_action def get_new_actions(self, node): """ Returns the new actions for a node. """ return self.new_actions def get_paste_value(self, node): """ Get the value that is pasted for a node. By default, returns the node itself. """ return node def get_monitor(self, node): """ Returns a monitor that detects changes to a node. Returns None by default, which indicates that the node is not monitored. """ return None #### These methods are exactly the same as the 'TreeModel' interface ###### def has_children(self, node): """ Returns True if a node has children, otherwise False. You only need to implement this method if children are allowed for the node (ie. 'allows_children' returns True). """ return False def get_children(self, node): """ Returns the children of a node. You only need to implement this method if children are allowed for the node. """ raise NotImplementedError def get_drag_value(self, node): """ Get the value that is dragged for a node. By default, returns the node itself. """ return node def can_drop(self, node, data): """ Returns True if a node allows an object to be dropped onto it. """ return False def drop(self, obj, data): """ Drops an object onto a node. """ raise NotImplementedError def get_image(self, node, selected, expanded): """ Returns the label image for a node. """ if self.allows_children(node): if expanded: order = ['open_image', 'closed_image', 'image'] default = self.OPEN_FOLDER else: order = ['closed_image', 'open_image', 'image'] default = self.CLOSED_FOLDER else: order = ['image', 'open_image', 'closed_image'] default = self.DOCUMENT # Use the search order to look for a trait that is NOT None. for name in order: image = getattr(self, name) if image is not None: break # If no such trait is found then use the default image. else: image = default return image def get_selection_value(self, node): """ Get the value that is used when a node is selected. By default the selection value is the node itself. """ return node def get_text(self, node): """ Returns the label text for a node. """ return str(node) def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ return len(text.strip()) > 0 def set_text(self, node, text): """ Sets the label text for a node. """ pass def is_collapsible(self, node): """ Returns True if the node is collapsible, otherwise False. """ return True def is_draggable(self, node): """ Returns True if the node is draggablee, otherwise False. """ return True def can_rename(self, node): """ Returns True if the node can be renamed, otherwise False. """ return False def is_editable(self, node): """ Returns True if the node is editable, otherwise False. If the node is editable, its text can be set via the UI. DEPRECATED: Use 'can_rename'. """ return self.can_rename(node) def is_expandable(self, node): """ Returns True if the node is expandanble, otherwise False. """ return True #### EOF ###################################################################### pyface-4.1.0/pyface/tree/node_tree_model.py0000644000175100001440000002470411674463363021674 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The model for a tree control with extensible node types. """ # Enthought library imports. from traits.api import Dict, Instance # Local imports. from node_manager import NodeManager from tree_model import TreeModel class NodeTreeModel(TreeModel): """ The model for a tree control with extensible node types. """ #### 'NodeTreeModel' interface ############################################ # The node manager looks after all node types. node_manager = Instance(NodeManager, ()) #### Private interface #################################################### # Node monitors. _monitors = Dict ########################################################################### # 'TreeModel' interface. ########################################################################### def has_children(self, node): """ Returns True if a node has children, otherwise False. This method is provided in case the model has an efficient way to determine whether or not a node has any children without having to actually get the children themselves. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) if node_type.allows_children(node): has_children = node_type.has_children(node) else: has_children = False return has_children def get_children(self, node): """ Returns the children of a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) # Does the node allow children (ie. a folder allows children, a file # does not)? if node_type.allows_children(node): # Get the node's children. children = node_type.get_children(node) else: children = [] return children def get_default_action(self, node): """ Returns the default action for a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_default_action(node) def get_drag_value(self, node): """ Get the value that is dragged for a node. By default the drag value is the node itself. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_drag_value(node) def can_drop(self, node, data): """ Returns True if a node allows an object to be dropped onto it. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.can_drop(node, data) def drop(self, node, data): """ Drops an object onto a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) node_type.drop(node, data) return def get_image(self, node, selected, expanded): """ Returns the label image for a node. Return None (the default) if no image is required. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_image(node, selected, expanded) def get_key(self, node): """ Generate a unique key for a node. """ return self.node_manager.get_key(node) def get_selection_value(self, node): """ Get the value that is used when a node is selected. By default the selection value is the node itself. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_selection_value(node) def get_text(self, node): """ Returns the label text for a node. Return None if no text is required. By default we return 'str(node)'. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_text(node) def can_set_text(self, node, text): """ Returns True if the node's label can be set. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.can_set_text(node, text) def set_text(self, node, text): """ Sets the label text for a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.set_text(node, text) def is_collapsible(self, node): """ Returns True if the node is collapsible, otherwise False. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.is_collapsible(node) def is_draggable(self, node): """ Returns True if the node is draggable, otherwise False. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.is_draggable(node) def is_editable(self, node): """ Returns True if the node is editable, otherwise False. If the node is editable, its text can be set via the UI. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.is_editable(node) def is_expandable(self, node): """ Returns True if the node is expandanble, otherwise False. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.is_expandable(node) def add_listener(self, node): """ Adds a listener for changes to a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) # Create a monitor to listen for changes to the node. monitor = node_type.get_monitor(node) if monitor is not None: self._start_monitor(monitor) self._monitors[self.node_manager.get_key(node)] = monitor return def remove_listener(self, node): """ Removes a listener for changes to a node. """ key = self.node_manager.get_key(node) monitor = self._monitors.get(key) if monitor is not None: self._stop_monitor(monitor) del self._monitors[key] return ######################################################################### # 'NodeTreeModel' interface. ######################################################################### def get_context_menu(self, node): """ Returns the context menu for a node. """ # Determine the node type for this node. node_type = self.node_manager.get_node_type(node) return node_type.get_context_menu(node) ########################################################################### # 'Private' interface ########################################################################### def _start_monitor(self, monitor): """ Starts a monitor. """ monitor.on_trait_change( self._on_nodes_changed, 'nodes_changed' ) monitor.on_trait_change( self._on_nodes_inserted, 'nodes_inserted' ) monitor.on_trait_change( self._on_nodes_removed, 'nodes_removed' ) monitor.on_trait_change( self._on_nodes_replaced, 'nodes_replaced' ) monitor.on_trait_change( self._on_structure_changed, 'structure_changed' ) monitor.start() return def _stop_monitor(self, monitor): """ Stops a monitor. """ monitor.on_trait_change( self._on_nodes_changed, 'nodes_changed', remove=True ) monitor.on_trait_change( self._on_nodes_inserted, 'nodes_inserted', remove=True ) monitor.on_trait_change( self._on_nodes_removed, 'nodes_removed', remove=True ) monitor.on_trait_change( self._on_nodes_replaced, 'nodes_replaced', remove=True ) monitor.on_trait_change( self._on_structure_changed, 'structure_changed', remove=True ) monitor.stop() return #### Trait event handlers ################################################# #### Static #### # fixme: Commented this out as listeners are added and removed by the tree. # This caused duplicate monitors to be created for the root node. ## def _root_changed(self, old, new): ## """ Called when the root of the model has been changed. """ ## if old is not None: ## # Remove a listener for structure/appearance changes ## self.remove_listener(old) ## if new is not None: ## # Wire up a listener for structure/appearance changes ## self.add_listener(new) ## return #### Dynamic #### def _on_nodes_changed(self, monitor, trait_name, event): """ Called when nodes have changed. """ self.nodes_changed = event return def _on_nodes_inserted(self, monitor, trait_name, event): """ Called when nodes have been inserted. """ self.nodes_inserted = event return def _on_nodes_removed(self, monitor, trait_name, event): """ Called when nodes have been removed. """ self.nodes_removed = event return def _on_nodes_replaced(self, monitor, trait_name, event): """ Called when nodes have been replaced. """ self.nodes_replaced = event return def _on_structure_changed(self, monitor, trait_name, event): """ Called when the structure of a node has changed drastically. """ self.structure_changed = event return #### EOF ###################################################################### pyface-4.1.0/pyface/tree/trait_list_node_type.py0000644000175100001440000000304111674463363022763 0ustar ischnellusers00000000000000""" The node type for a trait list. """ # Enthought library imports. from traits.api import Any, Str # Local imports. from node_type import NodeType class TraitListNodeType(NodeType): """ The node type for a trait list. """ #### 'TraitListNodeType' interface ######################################## # The type of object that provides the trait list. klass = Any # The label text. text = Str # The name of the trait. trait_name = Str ########################################################################### # 'NodeType' interface. ########################################################################### def is_type_for(self, node): """ Returns True if this node type recognizes a node. """ is_type_for = isinstance(node, list) \ and hasattr(node, 'object') \ and isinstance(node.object, self.klass) \ and node.name == self.trait_name return is_type_for def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return True def has_children(self, node): """ Returns True if a node has children, otherwise False. """ return len(node) > 0 def get_children(self, node): """ Returns the children of a node. """ return node def get_text(self, node): """ Returns the label text for a node. """ return self.text ##### EOF ##################################################################### pyface-4.1.0/pyface/gui.py0000644000175100001440000000155111674463363016370 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a pyface GUI. """ # Import the toolkit specific version. from toolkit import toolkit_object GUI = toolkit_object('gui:GUI') #### EOF ###################################################################### pyface-4.1.0/pyface/i_file_dialog.py0000644000175100001440000000717011674463363020355 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a dialog that allows the user to open/save files etc. """ # Standard library imports. import sys # Enthought library imports. from traits.api import Enum, Unicode, Int # Local imports. from i_dialog import IDialog class IFileDialog(IDialog): """ The interface for a dialog that allows the user to open/save files etc. """ #### 'IFileDialog' interface ############################################## # The 'action' that the user is peforming on the directory. action = Enum('open', 'save as') # The default directory. default_directory = Unicode # The default filename. default_filename = Unicode # The default path (directory and filename) of the chosen file. This is # only used when the *default_directory* and *default_filename* are not set # and is equivalent to setting both. default_path = Unicode # The directory containing the chosen file. directory = Unicode # The name of the chosen file. filename = Unicode # The path (directory and filename) of the chosen file. path = Unicode # The wildcard used to restrict the set of files. wildcard = Unicode # The index of the selected wildcard. wildcard_index = Int(0) class MFileDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IFileDialog interface. Implements: create_wildcard() """ # FIXME v3: These are referenced elsewhere so maybe should be part of the # interface. The format is toolkit specific and so shouldn't be exposed. # The create_wildcard() class method (why isn't it a static method?) should # be the basis for this - but nothing seems to use it. For now the PyQt # implementation will convert the wx format to its own. Actually we need # the format to be portable between toolkits - so stick with PyQt # supporting the wx format or invent a data structure. # A file dialog wildcard for Python files. WILDCARD_PY = "Python files (*.py)|*.py|" # A file dialog wildcard for text files. WILDCARD_TXT = "Text files (*.txt)|*.txt|" # A file dialog wildcard for all files. if sys.platform == 'win32': WILDCARD_ALL = "All files (*.*)|*.*|" else: WILDCARD_ALL = "All files (*)|*" # A file dialog wildcard for Zip archives. WILDCARD_ZIP = "Zip files (*.zip)|*.zip|" ########################################################################### # 'MFileDialog' *CLASS* interface. ########################################################################### def create_wildcard(cls, description, extension): """ Creates a wildcard for a given extension. """ if isinstance(extension, basestring): pattern = extension else: pattern = ';'.join(extension) return "%s (%s)|%s|" % (description, pattern, pattern) create_wildcard = classmethod(create_wildcard) #### EOF ###################################################################### pyface-4.1.0/pyface/ipython_widget.py0000644000175100001440000000212111674463363020633 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2008, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of an IPython shell. """ # Import the toolkit specific version. try: import IPython.frontend except ImportError: raise ImportError, ''' ________________________________________________________________________________ Could not load the Wx frontend for ipython. You need to have ipython >= 0.9 installed to use the ipython widget.''' from toolkit import toolkit_object IPythonWidget= toolkit_object('ipython_widget:IPythonWidget') pyface-4.1.0/pyface/about_dialog.py0000644000175100001440000000161511674463363020236 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a simple 'About' dialog. """ # Import the toolkit specific version. from toolkit import toolkit_object AboutDialog = toolkit_object('about_dialog:AboutDialog') ### EOF ####################################################################### pyface-4.1.0/pyface/i_message_dialog.py0000644000175100001440000000304211674463363021054 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a dialog that displays a message. """ # Enthought library imports. from traits.api import Enum, Unicode # Local imports. from i_dialog import IDialog class IMessageDialog(IDialog): """ The interface for a dialog that displays a message. """ #### 'IMessageDialog' interface ########################################### # The message to display in the dialog. message = Unicode # More information about the message to be displayed. informative = Unicode # More detail about the message to be displayed in the dialog. detail = Unicode # The severity of the message. severity = Enum('information', 'warning', 'error') class MMessageDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IMessageDialog interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/layered_panel.py0000644000175100001440000001326711674463363020417 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A Layered panel. """ from __future__ import absolute_import # Major package imports. import wx from wx.lib.scrolledpanel import ScrolledPanel # Enthought library imports. from traits.api import Any, Str, Int # Local imports. from .widget import Widget class LayeredPanel(Widget): """ A Layered panel. A layered panel contains one or more named layers, with only one layer visible at any one time (think of a 'tab' control minus the tabs!). Each layer is a toolkit-specific control. """ # The default style. STYLE = wx.CLIP_CHILDREN #### "Layered Panel' interface ############################################ # The toolkit-specific control of the currently displayed layer. current_layer = Any # The name of the currently displayed layer. current_layer_name = Str # The minimum for the panel, which is the maximum of the minimum # sizes of the layers min_width = Int(0) min_height = Int(0) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new LayeredPanel. """ # Base class constructor. super(LayeredPanel, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) # The layers in the panel. # # { str name : wx.Window layer } self._layers = {} return ########################################################################### # 'LayeredPanel' interface. ########################################################################### def add_layer(self, name, layer): """ Adds a layer with the specified name. All layers are hidden when they are added. Use 'show_layer' to make a layer visible. """ # Add the layer to our sizer. sizer = self.control.GetSizer() sizer.Add(layer, 1, wx.EXPAND) # All layers are hidden when they are added. Use 'show_layer' to make # a layer visible. sizer.Show(layer, False) # fixme: Should we warn if a layer is being overridden? self._layers[name] = layer # fixme: The minimum size stuff that was added for linux broke the # sizing on Windows (at least for the preference dialog). The # preference dialog now sets the minimum width and height to -1 so # that this layout code doesn't get executed. if self.min_width != -1 or self.min_height != -1: if layer.GetSizer() is None: return layer min_size = layer.GetSizer().CalcMin() needs_layout = False if min_size.GetWidth() > self.min_width: self.min_width = min_size.GetWidth() needs_layout = True if min_size.GetHeight() > self.min_height: self.min_height = min_size.GetHeight() needs_layout = True if needs_layout: # Reset our size hints and relayout self.control.SetSizeHints(self.min_width, self.min_height) self.control.GetSizer().Layout() # fixme: Force our parent to reset it's size hints to its # minimum parent = self.control.GetParent() parent.GetSizer().SetSizeHints(parent) parent.GetSizer().Layout() return layer def show_layer(self, name): """ Shows the layer with the specified name. """ # Hide the current layer (if one is displayed). if self.current_layer is not None: self._hide_layer(self.current_layer) # Show the specified layer. layer = self._show_layer(name, self._layers[name]) return layer def has_layer(self, name): """ Does the panel contain a layer with the specified name? """ return self._layers.has_key(name) ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ panel = ScrolledPanel(parent, -1, style=self.STYLE) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) panel.SetupScrolling() return panel def _hide_layer(self, layer): """ Hides the specified layer. """ sizer = self.control.GetSizer() sizer.Show(layer, False) sizer.Layout() return def _show_layer(self, name, layer): """ Shows the specified layer. """ sizer = self.control.GetSizer() sizer.Show(layer, True) sizer.Layout() self.current_layer = layer self.current_layer_name = name return layer #### EOF ###################################################################### pyface-4.1.0/pyface/split_dialog.py0000644000175100001440000000247211674463363020261 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A dialog that is split in two either horizontally or vertically. """ # Local imports. from dialog import Dialog from split_widget import SplitWidget class SplitDialog(Dialog, SplitWidget): """ A dialog that is split in two either horizontally or vertically. """ ########################################################################### # Protected 'Dialog' interface. ########################################################################### def _create_dialog_area(self, parent): """ Creates the main content of the dialog. """ return self._create_splitter(parent) #### EOF ###################################################################### pyface-4.1.0/pyface/constant.py0000644000175100001440000000153211674463363017434 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Common constants. """ # Standard results for Ok/Cancel, Yes/No operations etc. OK = 10 CANCEL = 20 YES = 30 NO = 40 #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/0000755000175100001440000000000011674463363017212 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/i_perspective_item.py0000644000175100001440000000356011674463363023447 0ustar ischnellusers00000000000000""" The interface for perspective items. """ # Enthought library imports. from traits.api import Enum, Float, Interface, Str class IPerspectiveItem(Interface): """ The interface for perspective items. """ # The Id of the view to display in the perspective. id = Str # The position of the view relative to the item specified in the # 'relative_to' trait. # # 'top' puts the view above the 'relative_to' item. # 'bottom' puts the view below the 'relative_to' item. # 'left' puts the view to the left of the 'relative_to' item. # 'right' puts the view to the right of the 'relative_to' item. # 'with' puts the view in the same region as the 'relative_to' item. # # If the position is specified as 'with' you must specify a 'relative_to' # item other than the editor area (i.e., you cannot position a view 'with' # the editor area). position = Enum('left', 'top', 'bottom', 'right', 'with') # The Id of the view to position relative to. If this is not specified # (or if no view exists with this Id) then the view will be placed relative # to the editor area. relative_to = Str # The width of the item (as a fraction of the window width). # # e.g. 0.5 == half the window width. # # Note that this is treated as a suggestion, and it may not be possible # for the workbench to allocate the space requested. width = Float(-1) # The height of the item (as a fraction of the window height). # # e.g. 0.5 == half the window height. # # Note that this is treated as a suggestion, and it may not be possible # for the workbench to allocate the space requested. height = Float(-1) # The style of the dock window. style_hint = Enum('tab', 'horizontal', 'vertical', 'fixed') #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/debug/0000755000175100001440000000000011674463363020300 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/debug/debug_view.py0000644000175100001440000000517611674463363023003 0ustar ischnellusers00000000000000""" A view containing a main walter canvas. """ # Enthought library imports. from pyface.workbench.api import View, WorkbenchWindow from traits.api import HasTraits, Instance, Str, on_trait_change from traitsui.api import View as TraitsView class DebugViewModel(HasTraits): """ The model for the debug view! """ #### 'Model' interface #################################################### active_editor = Str active_part = Str active_view = Str window = Instance(WorkbenchWindow) ########################################################################### # 'Model' interface. ########################################################################### @on_trait_change( 'window.active_editor', 'window.active_part', 'window.active_view' ) def refresh(self): """ Refresh the model. """ self.active_editor = self._get_id(self.window.active_editor) self.active_part = self._get_id(self.window.active_part) self.active_view = self._get_id(self.window.active_view) return def _window_changed(self): """ Window changed! """ self.refresh() return ########################################################################### # Private interface. ########################################################################### def _get_id(self, obj): """ Return the Id of an object. """ if obj is None: id = 'None' else: id = obj.id return id class DebugView(View): """ A view containing a main walter canvas. """ #### 'IWorkbenchPart' interface ########################################### # The part's name (displayed to the user). name = 'Debug' #### 'DebugView' interface ################################################ # The model for the debug view! model = Instance(DebugViewModel) ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def create_control(self, parent): """ Creates the toolkit-specific control that represents the view. 'parent' is the toolkit-specific control that is the view's parent. """ self.model = DebugViewModel(window=self.window) ui = self.model.edit_traits( parent = parent, kind = 'subpanel', view = TraitsView('active_part', 'active_editor', 'active_view') ) return ui.control #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/debug/api.py0000644000175100001440000000004111674463363021416 0ustar ischnellusers00000000000000from debug_view import DebugView pyface-4.1.0/pyface/workbench/debug/__init__.py0000644000175100001440000000000011674463363022377 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/editor_manager.py0000755000175100001440000000550511674463363022554 0ustar ischnellusers00000000000000""" The default editor manager. """ # Standard library imports. import weakref # Enthought library imports. from traits.api import HasTraits, Instance, implements # Local imports. from i_editor_manager import IEditorManager from traits_ui_editor import TraitsUIEditor class EditorManager(HasTraits): """ The default editor manager. """ implements(IEditorManager) #### 'IEditorManager' interface ########################################### # The workbench window that the editor manager manages editors for ;^) window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Constructor. """ super(EditorManager, self).__init__(**traits) # A mapping from editor to editor kind (the factory that created them). self._editor_to_kind_map = weakref.WeakKeyDictionary() return ########################################################################### # 'IEditorManager' interface. ########################################################################### def add_editor(self, editor, kind): """ Registers an existing editor. """ self._editor_to_kind_map[editor] = kind def create_editor(self, window, obj, kind): """ Create an editor for an object. """ editor = TraitsUIEditor(window=window, obj=obj) self.add_editor(editor, kind) return editor def get_editor(self, window, obj, kind): """ Get the editor that is currently editing an object. """ for editor in window.editors: if self._is_editing(editor, obj, kind): break else: editor = None return editor def get_editor_kind(self, editor): """ Return the 'kind' associated with 'editor'. """ return self._editor_to_kind_map[editor] def get_editor_memento(self, editor): """ Return the state of an editor suitable for pickling etc. By default we don't save the state of editors. """ return None def set_editor_memento(self, memento): """ Restore the state of an editor from a memento. By default we don't try to restore the state of editors. """ return None ########################################################################### # 'Protected' 'EditorManager' interface. ########################################################################### def _is_editing(self, editor, obj, kind): """ Return True if the editor is editing the object. """ return editor.obj == obj #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/i_view.py0000755000175100001440000001002411674463363021046 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for workbench views. """ # Standard library imports. import logging # Enthought library imports. from pyface.api import ImageResource from traits.api import Bool, Enum, Float, Instance, List, Str, \ implements from traits.util.camel_case import camel_case_to_words # Local imports. from i_perspective_item import IPerspectiveItem from i_workbench_part import IWorkbenchPart, MWorkbenchPart from perspective_item import PerspectiveItem # Logging. logger = logging.getLogger(__name__) class IView(IWorkbenchPart, IPerspectiveItem): """ The interface for workbench views. """ # Is the view busy? (i.e., should the busy cursor (often an hourglass) be # displayed?). busy = Bool(False) # The category that the view belongs to (this can used to group views when # they are displayed to the user). category = Str('General') # An image used to represent the view to the user (shown in the view tab # and in the view chooser etc). image = Instance(ImageResource) # Whether the view is visible or not. visible = Bool(False) ########################################################################### # 'IView' interface. ########################################################################### def activate(self): """ Activate the view. """ def hide(self): """ Hide the view. """ def show(self): """ Show the view. """ class MView(MWorkbenchPart, PerspectiveItem): """ Mixin containing common code for toolkit-specific implementations. """ implements(IView) #### 'IView' interface #################################################### # Is the view busy? (i.e., should the busy cursor (often an hourglass) be # displayed?). busy = Bool(False) # The category that the view belongs to (this can be used to group views # when they are displayed to the user). category = Str('General') # An image used to represent the view to the user (shown in the view tab # and in the view chooser etc). image = Instance(ImageResource) # Whether the view is visible or not. visible = Bool(False) ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def _id_default(self): """ Trait initializer. """ id = '%s.%s' % (type(self).__module__, type(self).__name__) logger.warn('view %s has no Id - using <%s>' % (self, id)) # If no Id is specified then use the name. return id def _name_default(self): """ Trait initializer. """ name = camel_case_to_words(type(self).__name__) logger.warn('view %s has no name - using <%s>' % (self, name)) return name ########################################################################### # 'IView' interface. ########################################################################### def activate(self): """ Activate the view. """ self.window.activate_view(self) return def hide(self): """ Hide the view. """ self.window.hide_view(self) return def show(self): """ Show the view. """ self.window.show_view(self) return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/i_perspective.py0000755000175100001440000000227611674463363022437 0ustar ischnellusers00000000000000""" The perspective interface. """ # Enthought library imports. from traits.api import Bool, Interface, List, Str, Tuple # Local imports. from perspective_item import PerspectiveItem class IPerspective(Interface): """ The perspective interface. """ # The perspective's unique identifier (unique within a workbench window). id = Str # The perspective's name. name = Str # The contents of the perspective. contents = List(PerspectiveItem) # The size of the editor area in this perspective. A value of (-1, -1) # indicates that the workbench window should choose an appropriate size # based on the sizes of the views in the perspective. editor_area_size = Tuple # Is the perspective enabled? enabled = Bool # Should the editor area be shown in this perspective? show_editor_area = Bool #### Methods ############################################################## def create(self, window): """ Create the perspective in a workbench window. """ def show(self, window): """ Called when the perspective is shown in a workbench window. """ #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/view.py0000755000175100001440000000160111674463363020537 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a workbench view. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object View = toolkit_object('workbench.view:View') ### EOF ####################################################################### pyface-4.1.0/pyface/workbench/window_event.py0000644000175100001440000000112511674463363022273 0ustar ischnellusers00000000000000""" Window events. """ # Enthought library imports. from traits.api import HasTraits, Instance, Vetoable # Local imports. from workbench_window import WorkbenchWindow class WindowEvent(HasTraits): """ A window lifecycle event. """ #### 'WindowEvent' interface ############################################## # The window that the event occurred on. window = Instance(WorkbenchWindow) class VetoableWindowEvent(WindowEvent, Vetoable): """ A vetoable window lifecycle event. """ pass #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/api.py0000755000175100001440000000077511674463363020351 0ustar ischnellusers00000000000000from i_editor import IEditor from editor import Editor from i_editor_manager import IEditorManager from editor_manager import EditorManager from i_perspective import IPerspective from perspective import Perspective from perspective_item import PerspectiveItem from i_view import IView from view import View from i_workbench import IWorkbench from workbench import Workbench from workbench_window import WorkbenchWindow from traits_ui_editor import TraitsUIEditor from traits_ui_view import TraitsUIView pyface-4.1.0/pyface/workbench/workbench.py0000755000175100001440000003266611674463363021566 0ustar ischnellusers00000000000000""" A workbench. """ # Standard library imports. import cPickle import logging import os # Enthought library imports. from traits.etsconfig.api import ETSConfig from pyface.api import NO from traits.api import Bool, Callable, Event, HasTraits, implements from traits.api import Instance, List, Unicode, Vetoable from traits.api import VetoableEvent # Local imports. from i_editor_manager import IEditorManager from i_workbench import IWorkbench from user_perspective_manager import UserPerspectiveManager from workbench_window import WorkbenchWindow from window_event import WindowEvent, VetoableWindowEvent # Logging. logger = logging.getLogger(__name__) class Workbench(HasTraits): """ A workbench. There is exactly *one* workbench per application. The workbench can create any number of workbench windows. """ implements(IWorkbench) #### 'IWorkbench' interface ############################################### # The active workbench window (the last one to get focus). active_window = Instance(WorkbenchWindow) # The editor manager is used to create/restore editors. editor_manager = Instance(IEditorManager) # The optional application scripting manager. script_manager = Instance('apptools.appscripting.api.IScriptManager') # A directory on the local file system that we can read and write to at # will. This is used to persist window layout information, etc. state_location = Unicode # The optional undo manager. undo_manager = Instance('apptools.undo.api.IUndoManager') # The user-defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) # All of the workbench windows created by the workbench. windows = List(WorkbenchWindow) #### Workbench lifecycle events ########################################### # Fired when the workbench is about to exit. # # This can be caused by either:- # # a) The 'exit' method being called. # b) The last open window being closed. # exiting = VetoableEvent # Fired when the workbench has exited. exited = Event #### Window lifecycle events ############################################## # Fired when a workbench window has been created. window_created = Event(WindowEvent) # Fired when a workbench window is opening. window_opening = Event(VetoableWindowEvent) # Fired when a workbench window has been opened. window_opened = Event(WindowEvent) # Fired when a workbench window is closing. window_closing = Event(VetoableWindowEvent) # Fired when a workbench window has been closed. window_closed = Event(WindowEvent) #### 'Workbench' interface ################################################ # The factory that is used to create workbench windows. This is used in # the default implementation of 'create_window'. If you override that # method then you obviously don't need to set this trait! window_factory = Callable #### Private interface #################################################### # An 'explicit' exit is when the the 'exit' method is called. # An 'implicit' exit is when the user closes the last open window. _explicit_exit = Bool(False) ########################################################################### # 'IWorkbench' interface. ########################################################################### def create_window(self, **kw): """ Factory method that creates a new workbench window. """ window = self.window_factory(workbench=self, **kw) # Add on any user-defined perspectives. window.perspectives.extend(self.user_perspective_manager.perspectives) # Restore the saved window memento (if there is one). self._restore_window_layout(window) # Listen for the window being activated/opened/closed etc. Activated in # this context means 'gets the focus'. # # NOTE: 'activated' is not fired on a window when the window first # opens and gets focus. It is only fired when the window comes from # lower in the stack to be the active window. window.on_trait_change(self._on_window_activated, 'activated') window.on_trait_change(self._on_window_opening, 'opening') window.on_trait_change(self._on_window_opened, 'opened') window.on_trait_change(self._on_window_closing, 'closing') window.on_trait_change(self._on_window_closed, 'closed') # Event notification. self.window_created = WindowEvent(window=window) return window def exit(self): """ Exits the workbench. This closes all open workbench windows. This method is not called when the user clicks the close icon. Nor when they do an Alt+F4 in Windows. It is only called when the application menu File->Exit item is selected. Returns True if the exit succeeded, False if it was vetoed. """ logger.debug('**** exiting the workbench ****') # Event notification. self.exiting = event = Vetoable() if not event.veto: # This flag is checked in '_on_window_closing' to see what kind of # exit is being performed. self._explicit_exit = True if len(self.windows) > 0: exited = self._close_all_windows() # The degenerate case where no workbench windows have ever been # created! else: # Trait notification. self.exited = self exited = True # Whether the exit succeeded or not, we are no longer in the # process of exiting! self._explicit_exit = False else: exited = False if not exited: logger.debug('**** exit of the workbench vetoed ****') return exited #### Convenience methods on the active window ############################# def edit(self, obj, kind=None, use_existing=True): """ Edit an object in the active workbench window. """ return self.active_window.edit(obj, kind, use_existing) def get_editor(self, obj, kind=None): """ Return the editor that is editing an object. Returns None if no such editor exists. """ if self.active_window is None: return None return self.active_window.get_editor(obj, kind) def get_editor_by_id(self, id): """ Return the editor with the specified Id. Returns None if no such editor exists. """ return self.active_window.get_editor_by_id(id) #### Message dialogs #### def confirm(self, message, title=None, cancel=False, default=NO): """ Convenience method to show a confirmation dialog. """ return self.active_window.confirm(message, title, cancel, default) def information(self, message, title='Information'): """ Convenience method to show an information message dialog. """ return self.active_window.information(message, title) def warning(self, message, title='Warning'): """ Convenience method to show a warning message dialog. """ return self.active_window.warning(message, title) def error(self, message, title='Error'): """ Convenience method to show an error message dialog. """ return self.active_window.error(message, title) ########################################################################### # 'Workbench' interface. ########################################################################### #### Initializers ######################################################### def _state_location_default(self): """ Trait initializer. """ # It would be preferable to base this on GUI.state_location. state_location = os.path.join( ETSConfig.application_home, 'pyface', 'workbench', ETSConfig.toolkit ) if not os.path.exists(state_location): os.makedirs(state_location) logger.debug('workbench state location is %s', state_location) return state_location def _undo_manager_default(self): """ Trait initializer. """ # We make sure the undo package is entirely optional. try: from apptools.undo.api import UndoManager except ImportError: return None return UndoManager() def _user_perspective_manager_default(self): """ Trait initializer. """ return UserPerspectiveManager(state_location=self.state_location) ########################################################################### # Protected 'Workbench' interface. ########################################################################### def _create_window(self, **kw): """ Factory method that creates a new workbench window. """ raise NotImplementedError ########################################################################### # Private interface. ########################################################################### def _close_all_windows(self): """ Closes all open windows. Returns True if all windows were closed, False if the user changed their mind ;^) """ # We take a copy of the windows list because as windows are closed # they are removed from it! windows = self.windows[:] windows.reverse() for window in windows: # We give the user chance to cancel the exit as each window is # closed. if not window.close(): all_closed = False break else: all_closed = True return all_closed def _restore_window_layout(self, window): """ Restore the window layout. """ filename = os.path.join(self.state_location, 'window_memento') if os.path.exists(filename): try: # If the memento class itself has been modified then there # is a chance that the unpickle will fail. If so then we just # carry on as if there was no memento! f = file(filename, 'r') memento = cPickle.load(f) f.close() # The memento doesn't actually get used until the window is # opened, so there is nothing to go wrong in this step! window.set_memento(memento) # If *anything* goes wrong then simply log the error and carry on # with no memento! except: logger.exception('restoring window layout from %s', filename) return def _save_window_layout(self, window): """ Save the window layout. """ # Save the window layout. f = file(os.path.join(self.state_location, 'window_memento'), 'w') cPickle.dump(window.get_memento(), f) f.close() return #### Trait change handlers ################################################ def _on_window_activated(self, window, trait_name, event): """ Dynamic trait change handler. """ logger.debug('window %s activated', window) self.active_window = window return def _on_window_opening(self, window, trait_name, event): """ Dynamic trait change handler. """ # Event notification. self.window_opening = window_event = VetoableWindowEvent(window=window) if window_event.veto: event.veto = True return def _on_window_opened(self, window, trait_name, event): """ Dynamic trait change handler. """ # We maintain a list of all open windows so that (amongst other things) # we can detect when the user is attempting to close the last one. self.windows.append(window) # This is necessary because the activated event is not fired when a # window is first opened and gets focus. It is only fired when the # window comes from lower in the stack to be the active window. self.active_window = window # Event notification. self.window_opened = WindowEvent(window=window) return def _on_window_closing(self, window, trait_name, event): """ Dynamic trait change handler. """ # Event notification. self.window_closing = window_event = VetoableWindowEvent(window=window) if window_event.veto: event.veto = True else: # Is this the last open window? if len(self.windows) == 1: # If this is an 'implicit exit' then make sure that we fire the # appropriate workbench lifecycle events. if not self._explicit_exit: # Event notification. self.exiting = window_event = Vetoable() if window_event.veto: event.veto = True if not event.veto: # Save the window size, position and layout. self._save_window_layout(window) return def _on_window_closed(self, window, trait_name, event): """ Dynamic trait change handler. """ self.windows.remove(window) # Event notification. self.window_closed = WindowEvent(window=window) # Was this the last window? if len(self.windows) == 0: # Event notification. self.exited = self return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/workbench_window.py0000755000175100001440000006557711674463363023164 0ustar ischnellusers00000000000000""" A workbench window. """ # Standard library imports. import logging # Enthought library imports. from pyface.api import ApplicationWindow, GUI from traits.api import Callable, Constant, Delegate, Event, Instance from traits.api import List, Str, Tuple, Unicode, Vetoable from traits.api import implements, on_trait_change # Local imports. from i_editor import IEditor from i_editor_manager import IEditorManager from i_perspective import IPerspective from i_view import IView from i_workbench_part import IWorkbenchPart from perspective import Perspective from workbench_window_layout import WorkbenchWindowLayout from workbench_window_memento import WorkbenchWindowMemento # Logging. logger = logging.getLogger(__name__) class WorkbenchWindow(ApplicationWindow): """ A workbench window. """ #### 'IWorkbenchWindow' interface ######################################### # The view or editor that currently has the focus. active_part = Instance(IWorkbenchPart) # The editor manager is used to create/restore editors. editor_manager = Instance(IEditorManager) # The current selection within the window. selection = List # The workbench that the window belongs to. workbench = Instance('pyface.workbench.api.IWorkbench') #### Editors ####################### # The active editor. active_editor = Instance(IEditor) # The visible (open) editors. editors = List(IEditor) # The Id of the editor area. editor_area_id = Constant('pyface.workbench.editors') # The (initial) size of the editor area (the user is free to resize it of # course). editor_area_size = Tuple((100, 100)) # Fired when an editor is about to be opened (or restored). editor_opening = Delegate('layout') # Event(IEditor) # Fired when an editor has been opened (or restored). editor_opened = Delegate('layout') # Event(IEditor) # Fired when an editor is about to be closed. editor_closing = Delegate('layout') # Event(IEditor) # Fired when an editor has been closed. editor_closed = Delegate('layout') # Event(IEditor) #### Views ######################### # The active view. active_view = Instance(IView) # The available views (note that this is *all* of the views, not just those # currently visible). # # Views *cannot* be shared between windows as each view has a reference to # its toolkit-specific control etc. views = List(IView) #### Perspectives ################## # The active perspective. active_perspective = Instance(IPerspective) # The available perspectives. If no perspectives are specified then the # a single instance of the 'Perspective' class is created. perspectives = List(IPerspective) # The Id of the default perspective. # # There are two situations in which this is used: # # 1. When the window is being created from scratch (i.e., not restored). # # If this is the empty string, then the first perspective in the list of # perspectives is shown (if there are no perspectives then an instance # of the default 'Perspective' class is used). If this is *not* the # empty string then the perspective with this Id is shown. # # 2. When the window is being restored. # # If this is the empty string, then the last perspective that was # visible when the window last closed is shown. If this is not the empty # string then the perspective with this Id is shown. # default_perspective_id = Str #### 'WorkbenchWindow' interface ########################################## # The window layout is responsible for creating and managing the internal # structure of the window (i.e., it knows how to add and remove views and # editors etc). layout = Instance(WorkbenchWindowLayout) #### 'Private' interface ################################################## # The state of the window suitable for pickling etc. _memento = Instance(WorkbenchWindowMemento) ########################################################################### # 'Window' interface. ########################################################################### def open(self): """ Open the window. Overridden to make the 'opening' event vetoable. Return True if the window opened successfully; False if the open event was vetoed. """ logger.debug('window %s opening', self) # Trait notification. self.opening = event = Vetoable() if not event.veto: if self.control is None: self._create() self.show(True) # Trait notification. self.opened = self logger.debug('window %s opened', self) else: logger.debug('window %s open was vetoed', self) # fixme: This is not actually part of the Pyface 'Window' API (but # maybe it should be). We return this to indicate whether the window # actually opened. return self.control is not None def close(self): """ Closes the window. Overridden to make the 'closing' event vetoable. Return True if the window closed successfully (or was not even open!), False if the close event was vetoed. """ logger.debug('window %s closing', self) if self.control is not None: # Trait notification. self.closing = event = Vetoable() # fixme: Hack to mimic vetoable events! if not event.veto: # Give views and editors a chance to cleanup after themselves. self.destroy_views(self.views) self.destroy_editors(self.editors) # Cleanup the window layout (event handlers, etc.) self.layout.close() # Cleanup the toolkit-specific control. self.destroy() # Cleanup our reference to the control so that we can (at least # in theory!) be opened again. self.control = None # Trait notification. self.closed = self logger.debug('window %s closed', self) else: logger.debug('window %s close was vetoed', self) else: logger.debug('window %s is not open', self) # FIXME v3: This is not actually part of the Pyface 'Window' API (but # maybe it should be). We return this to indicate whether the window # actually closed. return self.control is None ########################################################################### # Protected 'Window' interface. ########################################################################### def _create_contents(self, parent): """ Create and return the window contents. """ # Create the initial window layout. contents = self.layout.create_initial_layout(parent) # Save the initial window layout so that we can reset it when changing # to a perspective that has not been seen yet. self._initial_layout = self.layout.get_view_memento() # Are we creating the window from scratch or restoring it from a # memento? if self._memento is None: self._memento = WorkbenchWindowMemento() else: self._restore_contents() # Set the initial perspective. self.active_perspective = self._get_initial_perspective() return contents ########################################################################### # 'WorkbenchWindow' interface. ########################################################################### #### Initializers ######################################################### def _editor_manager_default(self): """ Trait initializer. """ from editor_manager import EditorManager return EditorManager(window=self) def _layout_default(self): """ Trait initializer. """ return WorkbenchWindowLayout(window=self) #### Methods ############################################################## def activate_editor(self, editor): """ Activates an editor. """ self.layout.activate_editor(editor) return def activate_view(self, view): """ Activates a view. """ self.layout.activate_view(view) return def add_editor(self, editor, title=None): """ Adds an editor. If no title is specified, the editor's name is used. """ if title is None: title = editor.name self.layout.add_editor(editor, title) self.editors.append(editor) return def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): """ Adds a view. """ self.layout.add_view(view, position, relative_to, size) # This case allows for views that are created and added dynamically # (i.e. they were not even known about when the window was created). if not view in self.views: self.views.append(view) return def close_editor(self, editor): """ Closes an editor. """ self.layout.close_editor(editor) return def close_view(self, view): """ Closes a view. fixme: Currently views are never 'closed' in the same sense as an editor is closed. Views are merely hidden. """ self.hide_view(view) return def create_editor(self, obj, kind=None): """ Create an editor for an object. Return None if no editor can be created for the object. """ return self.editor_manager.create_editor(self, obj, kind) def destroy_editors(self, editors): """ Destroy a list of editors. """ for editor in editors: if editor.control is not None: editor.destroy_control() return def destroy_views(self, views): """ Destroy a list of views. """ for view in views: if view.control is not None: view.destroy_control() return def edit(self, obj, kind=None, use_existing=True): """ Edit an object. 'kind' is simply passed through to the window's editor manager to allow it to create a particular kind of editor depending on context etc. If 'use_existing' is True and the object is already being edited in the window then the existing editor will be activated (i.e., given focus, brought to the front, etc.). If 'use_existing' is False, then a new editor will be created even if one already exists. """ if use_existing: # Is the object already being edited in the window? editor = self.get_editor(obj, kind) if editor is not None: # If so, activate the existing editor (i.e., bring it to the # front, give it the focus etc). self.activate_editor(editor) return editor # Otherwise, create an editor for it. editor = self.create_editor(obj, kind) if editor is None: logger.warn('no editor for object %s', obj) self.add_editor(editor) self.activate_editor(editor) return editor def get_editor(self, obj, kind=None): """ Return the editor that is editing an object. Return None if no such editor exists. """ return self.editor_manager.get_editor(self, obj, kind) def get_editor_by_id(self, id): """ Return the editor with the specified Id. Return None if no such editor exists. """ for editor in self.editors: if editor.id == id: break else: editor = None return editor def get_part_by_id(self, id): """ Return the workbench part with the specified Id. Return None if no such part exists. """ return self.get_view_by_id(id) or self.get_editor_by_id(id) def get_perspective_by_id(self, id): """ Return the perspective with the specified Id. Return None if no such perspective exists. """ for perspective in self.perspectives: if perspective.id == id: break else: if id == Perspective.DEFAULT_ID: perspective = Perspective() else: perspective = None return perspective def get_perspective_by_name(self, name): """ Return the perspective with the specified name. Return None if no such perspective exists. """ for perspective in self.perspectives: if perspective.name == name: break else: perspective = None return perspective def get_view_by_id(self, id): """ Return the view with the specified Id. Return None if no such view exists. """ for view in self.views: if view.id == id: break else: view = None return view def hide_editor_area(self): """ Hide the editor area. """ self.layout.hide_editor_area() return def hide_view(self, view): """ Hide a view. """ self.layout.hide_view(view) return def refresh(self): """ Refresh the window to reflect any changes. """ self.layout.refresh() return def reset_active_perspective(self): """ Reset the active perspective back to its original contents. """ perspective = self.active_perspective # If the perspective has been seen before then delete its memento. if perspective.id in self._memento.perspective_mementos: # Remove the perspective's memento. del self._memento.perspective_mementos[perspective.id] # Re-display the perspective (because a memento no longer exists for # the perspective, its 'create_contents' method will be called again). self._show_perspective(perspective, perspective) return def reset_all_perspectives(self): """ Reset all perspectives back to their original contents. """ # Remove all perspective mementos (except user perspectives). for id in self._memento.perspective_mementos.keys(): if not id.startswith('__user_perspective'): del self._memento.perspective_mementos[id] # Re-display the active perspective. self._show_perspective(self.active_perspective,self.active_perspective) return def reset_editors(self): """ Activate the first editor in every tab. """ self.layout.reset_editors() return def reset_views(self): """ Activate the first view in every tab. """ self.layout.reset_views() return def show_editor_area(self): """ Show the editor area. """ self.layout.show_editor_area() return def show_view(self, view): """ Show a view. """ # If the view is already in the window layout, but hidden, then just # show it. # # fixme: This is a little gorpy, reaching into the window layout here, # but currently this is the only thing that knows whether or not the # view exists but is hidden. if self.layout.contains_view(view): self.layout.show_view(view) # Otherwise, we have to add the view to the layout. else: self._add_view_in_default_position(view) self.refresh() return #### Methods for saving and restoring the layout ########################## def get_memento(self): """ Return the state of the window suitable for pickling etc. """ # The size and position of the window. self._memento.size = self.size self._memento.position = self.position # The Id of the active perspective. self._memento.active_perspective_id = self.active_perspective.id # The layout of the active perspective. self._memento.perspective_mementos[self.active_perspective.id] = ( self.layout.get_view_memento(), self.active_view and self.active_view.id or None ) # The layout of the editor area. self._memento.editor_area_memento = self.layout.get_editor_memento() # Any extra toolkit-specific data. self._memento.toolkit_data = self.layout.get_toolkit_memento() return self._memento def set_memento(self, memento): """ Restore the state of the window from a memento. """ # All we do here is save a reference to the memento - we don't actually # do anything with it until the window is opened. # # This obviously means that you can't set the memento of a window # that is already open, but I can't see a use case for that anyway! self._memento = memento return ########################################################################### # Private interface. ########################################################################### def _add_view_in_default_position(self, view): """ Adds a view in its 'default' position. """ # Is the view in the current perspectives contents list? If it is then # we use the positioning information in the perspective item. Otherwise # we will use the default positioning specified in the view itself. item = self._get_perspective_item(self.active_perspective, view) if item is None: item = view # fixme: This only works because 'PerspectiveItem' and 'View' have the # identical 'position', 'relative_to', 'width' and 'height' traits! We # need to unify these somehow! relative_to = self.get_view_by_id(item.relative_to) size = (item.width, item.height) self.add_view(view, item.position, relative_to, size) return def _get_initial_perspective(self, *methods): """ Return the initial perspective. """ methods = [ # If a default perspective was specified then we prefer that over # any other perspective. self._get_default_perspective, # If there was no default perspective then try the perspective that # was active the last time the application was run. self._get_previous_perspective, # If there was no previous perspective, then try the first one that # we know about. self._get_first_perspective ] for method in methods: perspective = method() if perspective is not None: break # If we have no known perspectives, make a new blank one up. else: logger.warn('no known perspectives - creating a new one') perspective = Perspective() return perspective def _get_default_perspective(self): """ Return the default perspective. Return None if no default perspective was specified or it no longer exists. """ id = self.default_perspective_id if len(id) > 0: perspective = self.get_perspective_by_id(id) if perspective is None: logger.warn('default perspective %s no longer available', id) else: perspective = None return perspective def _get_previous_perspective(self): """ Return the previous perspective. Return None if there has been no previous perspective or it no longer exists. """ id = self._memento.active_perspective_id if len(id) > 0: perspective = self.get_perspective_by_id(id) if perspective is None: logger.warn('previous perspective %s no longer available', id) else: perspective = None return perspective def _get_first_perspective(self): """ Return the first perspective in our list of perspectives. Return None if no perspectives have been defined. """ if len(self.perspectives) > 0: perspective = self.perspectives[0] else: perspective = None return perspective def _get_perspective_item(self, perspective, view): """ Return the perspective item for a view. Return None if the view is not mentioned in the perspectives contents. """ # fixme: Errrr, shouldn't this be a method on the window?!? for item in perspective.contents: if item.id == view.id: break else: item = None return item def _hide_perspective(self, perspective): """ Hide a perspective. """ # fixme: This is a bit ugly but... when we restore the layout we ignore # the default view visibility. for view in self.views: view.visible = False # Save the current layout of the perspective. self._memento.perspective_mementos[perspective.id] = ( self.layout.get_view_memento(), self.active_view and self.active_view.id or None ) return def _show_perspective(self, old, new): """ Show a perspective. """ # If the perspective has been seen before then restore it. memento = self._memento.perspective_mementos.get(new.id) if memento is not None: # Show the editor area? if new.show_editor_area: self.show_editor_area() else: self.hide_editor_area() self.active_editor = None view_memento, active_view_id = memento self.layout.set_view_memento(view_memento) # Make sure the active part, view and editor reflect the new # perspective. view = self.get_view_by_id(active_view_id) if view is not None: self.active_view = view # Otherwise, this is the first time the perspective has been seen # so create it. else: if old is not None: # Reset the window layout to its initial state. self.layout.set_view_memento(self._initial_layout) # Create the perspective in the window. new.create(self) # Make sure the active part, view and editor reflect the new # perspective. self.active_view = None # Show the editor area? if new.show_editor_area: self.show_editor_area() else: self.hide_editor_area() self.active_editor = None # Inform the perspective that it has been shown. new.show(self) # This forces the dock window to update its layout. if old is not None: self.refresh() return def _restore_contents(self): """ Restore the contents of the window. """ self.layout.set_editor_memento(self._memento.editor_area_memento) self.size = self._memento.size self.position = self._memento.position # Set the toolkit-specific data last because it may override the generic # implementation. # FIXME: The primary use case is to let Qt restore the window's geometry # wholesale, including maximization state. If we ever go Qt-only, this # is a good area to refactor. self.layout.set_toolkit_memento(self._memento) return #### Trait change handlers ################################################ #### Static #### def _active_perspective_changed(self, old, new): """ Static trait change handler. """ logger.debug('active perspective changed from <%s> to <%s>', old, new) # Hide the old perspective... if old is not None: self._hide_perspective(old) # ... and show the new one. if new is not None: self._show_perspective(old, new) return def _active_editor_changed(self, old, new): """ Static trait change handler. """ logger.debug('active editor changed from <%s> to <%s>', old, new) self.active_part = new return def _active_part_changed(self, old, new): """ Static trait change handler. """ if new is None: self.selection = [] else: self.selection = new.selection logger.debug('active part changed from <%s> to <%s>', old, new) return def _active_view_changed(self, old, new): """ Static trait change handler. """ logger.debug('active view changed from <%s> to <%s>', old, new) self.active_part = new return def _views_changed(self, old, new): """ Static trait change handler. """ # Cleanup any old views. for view in old: view.window = None # Initialize any new views. for view in new: view.window = self return def _views_items_changed(self, event): """ Static trait change handler. """ # Cleanup any old views. for view in event.removed: view.window = None # Initialize any new views. for view in event.added: view.window = self return #### Dynamic #### @on_trait_change('layout.editor_closed') def _on_editor_closed(self, editor): """ Dynamic trait change handler. """ index = self.editors.index(editor) del self.editors[index] if editor is self.active_editor: if len(self.editors) > 0: index = min(index, len(self.editors) - 1) # If the user closed the editor manually then this method is # being called from a toolkit-specific event handler. Because # of that we have to make sure that we don't change the focus # from within this method directly hence we activate the editor # later in the GUI thread. GUI.invoke_later(self.activate_editor, self.editors[index]) else: self.active_editor = None return @on_trait_change('editors.has_focus') def _on_editor_has_focus_changed(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ if trait_name == 'has_focus' and new: self.active_editor = obj return @on_trait_change('views.has_focus') def _has_focus_changed_for_view(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ if trait_name == 'has_focus' and new: self.active_view = obj return @on_trait_change('views.visible') def _visible_changed_for_view(self, obj, trait_name, old, new): """ Dynamic trait change handler. """ if trait_name == 'visible': if not new: if obj is self.active_view: self.active_view = None return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/editor.py0000755000175100001440000000161111674463363021054 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a workbench editor. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object Editor = toolkit_object('workbench.editor:Editor') ### EOF ####################################################################### pyface-4.1.0/pyface/workbench/workbench_window_memento.py0000644000175100001440000000150411674463363024661 0ustar ischnellusers00000000000000""" A memento for a workbench window. """ # Enthought library imports. from traits.api import Any, Dict, HasTraits, Str, Tuple class WorkbenchWindowMemento(HasTraits): """ A memento for a workbench window. """ # The Id of the active perspective. active_perspective_id = Str # The memento for the editor area. editor_area_memento = Any # Mementos for each perspective that has been seen. # # The keys are the perspective Ids, the values are the toolkit-specific # mementos. perspective_mementos = Dict(Str, Any) # The position of the window. position = Tuple # The size of the window. size = Tuple # Any extra data the toolkit implementation may want to keep. toolkit_data = Any() #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/traits_ui_view.py0000644000175100001440000000603511674463363022625 0ustar ischnellusers00000000000000""" A view whose content is provided by a traits UI. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Any, Instance, Str from traitsui.api import UI # Local imports. from view import View # Logging. logger = logging.getLogger(__name__) class TraitsUIView(View): """ A view whose content is provided by a traits UI. """ #### 'TraitsUIView' interface ############################################# # The object that we povide a traits UI of (this defaults to the view # iteself ie. 'self'). obj = Any # The traits UI that represents the view. # # The framework sets this to the value returned by 'create_ui'. ui = Instance(UI) # The name of the traits UI view used to create the UI (if not specified, # the default traits UI view is used). view = Str ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### #### Trait initializers ################################################### def _name_default(self): """ Trait initializer. """ return str(self.obj) #### Methods ############################################################## def create_control(self, parent): """ Creates the toolkit-specific control that represents the editor. 'parent' is the toolkit-specific control that is the editor's parent. Overridden to call 'create_ui' to get the traits UI. """ self.ui = self.create_ui(parent) return self.ui.control def destroy_control(self): """ Destroys the toolkit-specific control that represents the editor. Overridden to call 'dispose' on the traits UI. """ # Give the traits UI a chance to clean itself up. if self.ui is not None: logger.debug('disposing traits UI for view [%s]', self) self.ui.dispose() self.ui = None # Break reference to the control, so the view is created afresh # next time. self.control = None return ########################################################################### # 'TraitsUIView' interface. ########################################################################### #### Trait initializers ################################################### def _obj_default(self): """ Trait initializer. """ return self #### Methods ############################################################## def create_ui(self, parent): """ Creates the traits UI that represents the editor. By default it calls 'edit_traits' on the view's 'model'. If you want more control over the creation of the traits UI then override! """ ui = self.obj.edit_traits( parent=parent, view=self.view, kind='subpanel' ) return ui #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/workbench_window_layout.py0000755000175100001440000000170611674463363024541 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a workbench window layout. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object WorkbenchWindowLayout = toolkit_object( 'workbench.workbench_window_layout:WorkbenchWindowLayout' ) ### EOF ####################################################################### pyface-4.1.0/pyface/workbench/i_editor.py0000755000175100001440000001104111674463363021362 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface of a workbench editor. """ # standard library imports import uuid # Enthought library imports. from traits.api import Any, Bool, Event, VetoableEvent, Vetoable, \ HasTraits, Instance, Interface from traits.api import implements # Local imports. from i_workbench_part import IWorkbenchPart, MWorkbenchPart class IEditor(IWorkbenchPart): """ The interface of a workbench editor. """ # The optional command stack. command_stack = Instance('apptools.undo.api.ICommandStack') # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? dirty = Bool(False) # The object that the editor is editing. # # The framework sets this when the editor is created. obj = Any #### Editor Lifecycle Events ############################################## # Fired when the editor is closing. closing = VetoableEvent # Fired when the editor is closed. closed = Event #### Methods ############################################################## def close(self): """ Close the editor. This method is not currently called by the framework itself as the user is normally in control of the editor lifecycle. Call this if you want to control the editor lifecycle programmatically. """ class MEditor(MWorkbenchPart): """ Mixin containing common code for toolkit-specific implementations. """ implements(IEditor) #### 'IEditor' interface ################################################## # The optional command stack. command_stack = Instance('apptools.undo.api.ICommandStack') # Is the object that the editor is editing 'dirty' i.e., has it been # modified but not saved? dirty = Bool(False) # The object that the editor is editing. # # The framework sets this when the editor is created. obj = Any #### Editor Lifecycle Events ############################################## # Fired when the editor is opening. opening = VetoableEvent # Fired when the editor has been opened. open = Event # Fired when the editor is closing. closing = Event(VetoableEvent) # Fired when the editor is closed. closed = Event ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Return an informal string representation of the object. """ return 'Editor(%s)' % self.id ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### def _id_default(self): """ Trait initializer. """ # If no Id is specified then use a random uuid # this gaurantees (barring *really* unusual cases) that there are no # collisions between the ids of editors. return uuid.uuid4().hex ########################################################################### # 'IEditor' interface. ########################################################################### def close(self): """ Close the editor. """ if self.control is not None: self.closing = event = Vetoable() if not event.veto: self.window.close_editor(self) self.closed = True return #### Initializers ######################################################### def _command_stack_default(self): """ Trait initializer. """ # We make sure the undo package is entirely optional. try: from apptools.undo.api import CommandStack except ImportError: return None return CommandStack(undo_manager=self.window.workbench.undo_manager) #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/traits_ui_editor.py0000644000175100001440000000507411674463363023143 0ustar ischnellusers00000000000000""" An editor whose content is provided by a traits UI. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Instance, Str from traitsui.api import UI # Local imports. from editor import Editor # Logging. logger = logging.getLogger(__name__) class TraitsUIEditor(Editor): """ An editor whose content is provided by a traits UI. """ #### 'TraitsUIEditor' interface ########################################### # The traits UI that represents the editor. # # The framework sets this to the value returned by 'create_ui'. ui = Instance(UI) # The name of the traits UI view used to create the UI (if not specified, # the default traits UI view is used). view = Str ########################################################################### # 'IWorkbenchPart' interface. ########################################################################### #### Trait initializers ################################################### def _name_default(self): """ Trait initializer. """ return str(self.obj) #### Methods ############################################################## def create_control(self, parent): """ Creates the toolkit-specific control that represents the editor. 'parent' is the toolkit-specific control that is the editor's parent. Overridden to call 'create_ui' to get the traits UI. """ self.ui = self.create_ui(parent) return self.ui.control def destroy_control(self): """ Destroys the toolkit-specific control that represents the editor. Overridden to call 'dispose' on the traits UI. """ # Give the traits UI a chance to clean itself up. if self.ui is not None: logger.debug('disposing traits UI for editor [%s]', self) self.ui.dispose() self.ui = None return ########################################################################### # 'TraitsUIEditor' interface. ########################################################################### def create_ui(self, parent): """ Creates the traits UI that represents the editor. By default it calls 'edit_traits' on the editor's 'obj'. If you want more control over the creation of the traits UI then override! """ ui = self.obj.edit_traits( parent=parent, view=self.view, kind='subpanel' ) return ui #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/user_perspective_manager.py0000644000175100001440000001507411674463363024654 0ustar ischnellusers00000000000000""" Manages a set of user perspectives. """ # Standard library imports. import logging import os # Enthought library imports. from pyface.workbench.api import Perspective from traits.api import Any, Dict, HasTraits, Int, List, Property from traits.api import Unicode # Logging. logger = logging.getLogger(__name__) class UserPerspectiveManager(HasTraits): """ Manages a set of user perspectives. """ #### 'UserPerspective' interface ########################################## # A directory on the local file system that we can read and write to at # will. This is used to persist window layout information, etc. state_location = Unicode # Next available user perspective id. next_id = Property(Int) # Dictionary mapping perspective id to user defined perspective definition. id_to_perspective = Property(Dict) # The list of user defined perspective definitions. perspectives = Property(List) # The name of the user defined perspectives definition file. file_name = Property(Unicode) #### Private interface #################################################### # Shadow trait for the 'id_to_perspective' property. _id_to_perspective = Any ########################################################################### # 'UserPerspective' interface. ########################################################################### #### Properties ########################################################### def _get_next_id ( self ): """ Property getter. """ # Get all of the current perspective ids: ids = self.id_to_perspective.keys() # If there are none: if len( ids ) == 0: # Return the starting id: return 1 # Else return the current highest id + 1 as the next id: ids.sort() return int( ids[-1][19:-2] ) + 1 def _get_id_to_perspective ( self ): """ Property getter. """ if self._id_to_perspective is None: self._id_to_perspective = dic = {} try: fh = file( self.file_name, 'r' ) for line in fh: data = line.split( ':', 1 ) if len( data ) == 2: id, name = data[0].strip(), data[1].strip() dic[ id ] = Perspective( id = id, name = name, show_editor_area = False ) fh.close() except: pass return self._id_to_perspective def _get_perspectives ( self ): """ Property getter. """ return self.id_to_perspective.values() def _get_file_name ( self ): """ Property getter. """ return os.path.join(self.state_location, '__user_perspective__') #### Methods ############################################################## def create_perspective(self, name, show_editor_area=True): """ Create a new (and empty) user-defined perspective. """ perspective = Perspective( id = '__user_perspective_%09d__' % self.next_id, name = name, show_editor_area = show_editor_area ) # Add the perspective to the map. self.id_to_perspective[perspective.id] = perspective # Update the persistent file information. self._update_persistent_data() return perspective def clone_perspective(self, window, perspective, **traits): """ Clone a perspective as a user perspective. """ clone = perspective.clone_traits() # Give the clone a special user perspective Id! clone.id = '__user_perspective_%09d__' % self.next_id # Set any traits specified as keyword arguments. clone.set(**traits) # Add the perspective to the map. self.id_to_perspective[clone.id] = clone # fixme: This needs to be pushed into the window API!!!!!!! window._memento.perspective_mementos[clone.id] = ( window.layout.get_view_memento(), window.active_view and window.active_view.id or None ) # Update the persistent file information. self._update_persistent_data() return clone def save(self): """ Persist the current state of the user perspectives. """ self._update_persistent_data() return def add(self, perspective, name=None): """ Add a perspective with an optional name. """ # Define the id for the new perspective: perspective.id = id = '__user_perspective_%09d__' % self.next_id # Save the new name (if specified): if name is not None: perspective.name = name # Create the perspective: self.id_to_perspective[id] = perspective # Update the persistent file information: self._update_persistent_data() # Return the new perspective created: return perspective def rename(self, perspective, name): """ Rename the user perspective with the specified id. """ perspective.name = name self.id_to_perspective[perspective.id].name = name # Update the persistent file information: self._update_persistent_data() return def remove(self, id): """ Remove the user perspective with the specified id. This method also updates the persistent data. """ if id in self.id_to_perspective: del self.id_to_perspective[id] # Update the persistent file information: self._update_persistent_data() # Try to delete the associated perspective layout file: try: os.remove(os.path.join(self.state_location, id)) except: pass return ########################################################################### # Private interface. ########################################################################### def _update_persistent_data(self): """ Update the persistent file information. """ try: fh = file( self.file_name, 'w' ) fh.write( '\n'.join( [ '%s: %s' % ( p.id, p.name ) for p in self.perspectives ] ) ) fh.close() except: logger.error( "Could not write the user defined perspective " "definition file: " + self.file_name ) return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/i_workbench_window_layout.py0000755000175100001440000002511411674463363025050 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The workbench window layout interface. """ # Enthought library imports. from traits.api import Event, HasTraits, Instance, Interface, Str from traits.api import implements # Local imports. from i_editor import IEditor from i_view import IView class IWorkbenchWindowLayout(Interface): """ The workbench window layout interface. Window layouts are responsible for creating and managing the internal structure of a workbench window (it knows how to add and remove views and editors etc). """ # The Id of the editor area. # FIXME v3: This is toolkit specific. editor_area_id = Str # The workbench window that this is the layout for. window = Instance('pyface.workbench.api.WorkbenchWindow') #### Events #### # Fired when an editor is about to be opened (or restored). editor_opening = Event(IEditor) # Fired when an editor has been opened (or restored). editor_opened = Event(IEditor) # Fired when an editor is about to be closed. editor_closing = Event(IEditor) # Fired when an editor has been closed. editor_closed = Event(IEditor) # Fired when a view is about to be opened (or restored). view_opening = Event(IView) # Fired when a view has been opened (or restored). view_opened = Event(IView) # Fired when a view is about to be closed (*not* hidden!). view_closing = Event(IView) # Fired when a view has been closed (*not* hidden!). view_closed = Event(IView) # FIXME v3: The "just for convenience" returns are a really bad idea. # # Why? They allow the call to be used on the LHS of an expression... # Because they have nothing to do with what the call is supposed to be # doing, they are unlikely to be used (because they are so unexpected and # inconsistently implemented), and only serve to replace two shorter lines # of code with one long one, arguably making code more difficult to read. def activate_editor(self, editor): """ Activate an editor. Returns the editor (just for convenience). """ def activate_view(self, view): """ Activate a view. Returns the view (just for convenience). """ def add_editor(self, editor, title): """ Add an editor. Returns the editor (just for convenience). """ def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): """ Add a view. Returns the view (just for convenience). """ def close_editor(self, editor): """ Close an editor. Returns the editor (just for convenience). """ def close_view(self, view): """ Close a view. FIXME v3: Currently views are never 'closed' in the same sense as an editor is closed. When we close an editor, we destroy its control. When we close a view, we merely hide its control. I'm not sure if this is a good idea or not. It came about after discussion with Dave P. and he mentioned that some views might find it hard to persist enough state that they can be re-created exactly as they were when they are shown again. Returns the view (just for convenience). """ def close(self): """ Close the entire window layout. FIXME v3: Should this be called 'destroy'? """ def create_initial_layout(self, parent): """ Create the initial window layout. Returns the layout. """ def contains_view(self, view): """ Return True if the view exists in the window layout. Note that this returns True even if the view is hidden. """ def hide_editor_area(self): """ Hide the editor area. """ def hide_view(self, view): """ Hide a view. Returns the view (just for convenience). """ def refresh(self): """ Refresh the window layout to reflect any changes. """ def reset_editors(self): """ Activate the first editor in every group. """ def reset_views(self): """ Activate the first view in every region. """ def show_editor_area(self): """ Show the editor area. """ def show_view(self, view): """ Show a view. """ #### Methods for saving and restoring the layout ########################## def get_view_memento(self): """ Returns the state of the views. """ def set_view_memento(self, memento): """ Restores the state of the views. """ def get_editor_memento(self): """ Returns the state of the editors. """ def set_editor_memento(self, memento): """ Restores the state of the editors. """ def get_toolkit_memento(self): """ Return any toolkit-specific data that should be part of the memento. """ def set_toolkit_memento(self, memento): """ Restores any toolkit-specific data. """ class MWorkbenchWindowLayout(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ implements(IWorkbenchWindowLayout) #### 'IWorkbenchWindowLayout' interface ################################### # The Id of the editor area. # FIXME v3: This is toolkit specific. editor_area_id = Str # The workbench window that this is the layout for. window = Instance('pyface.workbench.api.WorkbenchWindow') #### Events #### # Fired when an editor is about to be opened (or restored). editor_opening = Event(IEditor) # Fired when an editor has been opened (or restored). editor_opened = Event(IEditor) # Fired when an editor is about to be closed. editor_closing = Event(IEditor) # Fired when an editor has been closed. editor_closed = Event(IEditor) # Fired when a view is about to be opened (or restored). view_opening = Event(IView) # Fired when a view has been opened (or restored). view_opened = Event(IView) # Fired when a view is about to be closed (*not* hidden!). view_closing = Event(IView) # Fired when a view has been closed (*not* hidden!). view_closed = Event(IView) ########################################################################### # 'IWorkbenchWindowLayout' interface. ########################################################################### def activate_editor(self, editor): """ Activate an editor. """ raise NotImplementedError def activate_view(self, view): """ Activate a view. """ raise NotImplementedError def add_editor(self, editor, title): """ Add an editor. """ raise NotImplementedError def add_view(self, view, position=None, relative_to=None, size=(-1, -1)): """ Add a view. """ raise NotImplementedError def close_editor(self, editor): """ Close an editor. """ raise NotImplementedError def close_view(self, view): """ Close a view. """ raise NotImplementedError def close(self): """ Close the entire window layout. """ raise NotImplementedError def create_initial_layout(self, parent): """ Create the initial window layout. """ raise NotImplementedError def contains_view(self, view): """ Return True if the view exists in the window layout. """ raise NotImplementedError def hide_editor_area(self): """ Hide the editor area. """ raise NotImplementedError def hide_view(self, view): """ Hide a view. """ raise NotImplementedError def refresh(self): """ Refresh the window layout to reflect any changes. """ raise NotImplementedError def reset_editors(self): """ Activate the first editor in every group. """ raise NotImplementedError def reset_views(self): """ Activate the first view in every region. """ raise NotImplementedError def show_editor_area(self): """ Show the editor area. """ raise NotImplementedError def show_view(self, view): """ Show a view. """ raise NotImplementedError #### Methods for saving and restoring the layout ########################## def get_view_memento(self): """ Returns the state of the views. """ raise NotImplementedError def set_view_memento(self, memento): """ Restores the state of the views. """ raise NotImplementedError def get_editor_memento(self): """ Returns the state of the editors. """ raise NotImplementedError def set_editor_memento(self, memento): """ Restores the state of the editors. """ raise NotImplementedError def get_toolkit_memento(self): """ Return any toolkit-specific data that should be part of the memento. """ return None def set_toolkit_memento(self, memento): """ Restores any toolkit-specific data. """ return ########################################################################### # Protected 'MWorkbenchWindowLayout' interface. ########################################################################### def _get_editor_references(self): """ Returns a reference to every editor. """ editor_manager = self.window.editor_manager editor_references = {} for editor in self.window.editors: # Create the editor reference. # # If the editor manager returns 'None' instead of a resource # reference then this editor will not appear the next time the # workbench starts up. This is useful for things like text files # that have an editor but have NEVER been saved. editor_reference = editor_manager.get_editor_memento(editor) if editor_reference is not None: editor_references[editor.id] = editor_reference return editor_references #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/0000755000175100001440000000000011674463363020467 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/action/user_perspective_name.py0000644000175100001440000000552211674463363025434 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ Object with views for naming or renaming a user perspective. """ # Enthought library imports. from traits.api import Bool, HasTraits, Trait, TraitError, Constant from traitsui.api import View, Item, VGroup #### Trait definitions ######################################################## def not_empty_string(object, name, value): """a not-empty string""" if isinstance(value, basestring) and (value.strip() != ''): return value raise TraitError # Define a trait which can not be the empty string: NotEmptyString = Trait('', not_empty_string) class UserPerspectiveName(HasTraits): """ Object with views for naming or renaming a user perspective. """ ########################################################################### # 'UserPerspectiveName' interface. ########################################################################### # The name of the new user perspective. name = NotEmptyString # Should the editor area be shown in this perpsective? show_editor_area = Bool(True) # Help notes when creating a new view. new_help = Constant("""Note: - The new perspective will initially be empty. - Add new views to the perspective by selecting them from the 'View' menu. - Drag the notebook tabs and splitter bars to arrange the views within the perspective.""") #### Traits views ######################################################### new_view = View( VGroup( VGroup( 'name', 'show_editor_area' ), VGroup( '_', Item( 'new_help', style = 'readonly' ), show_labels = False ) ), title = 'New User Perspective', id = 'envisage.workbench.action.' 'new_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', width = 300 ) save_as_view = View( 'name', title = 'Save User Perspective As', id = 'envisage.workbench.action.' 'save_as_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', width = 300 ) rename_view = View( 'name', title = 'Rename User Perspective', id = 'envisage.workbench.action.' 'rename_user_perspective_action.UserPerspectiveName', buttons = [ 'OK', 'Cancel' ], kind = 'livemodal', width = 300 ) #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/action/rename_user_perspective_action.py0000644000175100001440000000302411674463363027313 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ An action that renames a user perspective. """ # Local imports. from user_perspective_action import UserPerspectiveAction from user_perspective_name import UserPerspectiveName class RenameUserPerspectiveAction(UserPerspectiveAction): """ An action that renames a user perspective. """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = 'pyface.workbench.action.rename_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Rename Perspective...' ########################################################################### # 'Action' interface. ########################################################################### def perform( self, event): """ Perform the action. """ window = event.window manager = window.workbench.user_perspective_manager # Get the new name. upn = UserPerspectiveName(name=window.active_perspective.name) if upn.edit_traits(view='rename_view').result: manager.rename(window.active_perspective, upn.name.strip()) return #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/action/reset_active_perspective_action.py0000644000175100001440000000227311674463363027470 0ustar ischnellusers00000000000000""" An action that resets the active perspective. """ # Enthought library imports. from pyface.api import YES # Local imports. from workbench_action import WorkbenchAction # The message used when confirming the action. MESSAGE = 'Do you want to reset the current "%s" perspective to its defaults?' class ResetActivePerspectiveAction(WorkbenchAction): """ An action that resets the active perspective. """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = 'pyface.workbench.action.reset_active_perspective' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset Perspective' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ window = self.window if window.confirm(MESSAGE % window.active_perspective.name) == YES: window.reset_active_perspective() return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/setattr_action.py0000644000175100001440000000165711674463363024075 0ustar ischnellusers00000000000000""" An action that sets an attribute. """ # Enthought library imports. from traits.api import Any, Str # Local imports. from workbench_action import WorkbenchAction class SetattrAction(WorkbenchAction): """ An action that sets an attribute. """ #### 'SetattrAction' interface ############################################ # The object that we set the attribute on. obj = Any # The name of the attribute that we set. attribute_name = Str # The value that we set the attribute to. value = Any ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Performs the action. """ setattr(self.obj, self.attribute_name, self.value) return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/api.py0000644000175100001440000000020611674463363021610 0ustar ischnellusers00000000000000from menu_bar_manager import MenuBarManager from tool_bar_manager import ToolBarManager from view_menu_manager import ViewMenuManager pyface-4.1.0/pyface/workbench/action/view_menu_manager.py0000644000175100001440000001074711674463363024542 0ustar ischnellusers00000000000000""" The 'View' menu """ # Standard library imports. import logging # Enthought library imports. from pyface.action.api import Group, MenuManager from traits.api import Any, Bool, Instance, List, Str, Unicode from traits.api import on_trait_change # Local imports. from perspective_menu_manager import PerspectiveMenuManager from show_view_action import ShowViewAction from toggle_view_visibility_action import ToggleViewVisibilityAction # Logging. logger = logging.getLogger(__name__) class ViewMenuManager(MenuManager): """ The 'View' menu. By default, this menu is displayed on the main menu bar. """ #### 'ActionManager' interface ############################################ # All of the groups in the manager. groups = List(Group) # The manager's unique identifier (if it has one). id = Str('View') #### 'MenuManager' interface ############################################## # The menu manager's name (if the manager is a sub-menu, this is what its # label will be). name = Unicode('&View') #### 'ViewMenuManager' interface ########################################## # Should the perspective menu be shown? show_perspective_menu = Bool(True) # The workbench window that the menu is part of. window = Instance('pyface.workbench.api.WorkbenchWindow') #### 'Private' interface ################################################## # The group containing the view hide/show actions. _view_group = Any ########################################################################### # 'ActionManager' interface. ########################################################################### def _groups_default(self): """ Trait initializer. """ groups = [] # Add a group containing the perspective menu (if requested). if self.show_perspective_menu and len(self.window.perspectives) > 0: groups.append(Group(PerspectiveMenuManager(window=self.window))) # Add a group containing a 'toggler' for all visible views. self._view_group = self._create_view_group(self.window) groups.append(self._view_group) # Add a group containing an 'Other...' item that will launch a dialog # to allow the user to choose a view to show. groups.append(self._create_other_group(self.window)) return groups ########################################################################### # 'ViewMenuManager' interface. ########################################################################### @on_trait_change( 'window.active_perspective,window.active_part,' 'window.views,window.views_items' ) def refresh(self): """ Refreshes the checked state of the actions in the menu. """ logger.debug('refreshing view menu') if self._view_group is not None: self._clear_group(self._view_group) self._initialize_view_group(self.window, self._view_group) self.changed = True return ########################################################################### # Private interface. ########################################################################### def _clear_group(self, group): """ Remove all items in a group. """ # fixme: Fix this API in PyFace so there is only one call! group.destroy() group.clear() return def _create_other_group(self, window): """ Creates a group containing the 'Other...' action. """ group = Group() group.append(ShowViewAction(name='Other...', window=window)) return group def _create_view_group(self, window): """ Creates a group containing the view 'togglers'. """ group = Group() self._initialize_view_group(window, group) return group def _initialize_view_group(self, window, group): """ Initializes a group containing the view 'togglers'. """ views = window.views[:] views.sort(None, lambda view: view.name) for view in views: # fixme: It seems a little smelly to be reaching in to the window # layout here. Should the 'contains_view' method be part of the # window interface? if window.layout.contains_view(view): group.append( ToggleViewVisibilityAction(view=view, window=window) ) return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/action_controller.py0000644000175100001440000000243511674463363024565 0ustar ischnellusers00000000000000""" The action controller for workbench menu and tool bars. """ # Enthought library imports. from pyface.action.api import ActionController from pyface.workbench.api import WorkbenchWindow from traits.api import HasTraits, Instance class ActionController(ActionController): """ The action controller for workbench menu and tool bars. The controller is used to 'hook' the invocation of every action on the menu and tool bars. This is done so that additional (and workbench specific) information can be added to action events. Currently, we attach a reference to the workbench window. """ #### 'ActionController' interface ######################################### # The workbench window that this is the controller for. window = Instance(WorkbenchWindow) ########################################################################### # 'ActionController' interface. ########################################################################### def perform(self, action, event): """ Control an action invocation. """ # Add a reference to the window and the application to the event. event.window = self.window return action.perform(event) #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/toggle_view_visibility_action.py0000644000175100001440000000572011674463363027164 0ustar ischnellusers00000000000000""" An action that toggles a view's visibility (ie. hides/shows it). """ # Enthought library imports. from pyface.workbench.api import IView from traits.api import Delegate, Instance # Local imports. from workbench_action import WorkbenchAction class ToggleViewVisibilityAction(WorkbenchAction): """ An action that toggles a view's visibility (ie. hides/shows it). """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = Delegate('view', modify=True) # The action's name (displayed on menus/tool bar tools etc). name = Delegate('view', modify=True) # The action's style. style = 'toggle' #### 'ViewAction' interface ############################################### # The view that we toggle the visibility for. view = Instance(IView) ########################################################################### # 'Action' interface. ########################################################################### def destroy(self): """ Called when the action is no longer required. """ if self.view is not None: self._remove_view_listeners(self.view) return def perform(self, event): """ Perform the action. """ self._toggle_view_visibility(self.view) return ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ def _view_changed(self, old, new): """ Static trait change handler. """ if old is not None: self._remove_view_listeners(old) if new is not None: self._add_view_listeners(new) self._refresh_checked() return #### Methods ############################################################## def _add_view_listeners(self, view): """ Add listeners for trait events on a view. """ view.on_trait_change(self._refresh_checked, 'visible') view.on_trait_change(self._refresh_checked, 'window') return def _remove_view_listeners(self, view): """ Add listeners for trait events on a view. """ view.on_trait_change(self._refresh_checked, 'visible', remove=True) view.on_trait_change(self._refresh_checked, 'window', remove=True) return def _refresh_checked(self): """ Refresh the checked state of the action. """ self.checked = self.view is not None \ and self.view.window is not None \ and self.view.visible return def _toggle_view_visibility(self, view): """ Toggle the visibility of a view. """ if view.visible: view.hide() else: view.show() return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/workbench_action.py0000644000175100001440000000111611674463363024357 0ustar ischnellusers00000000000000""" Abstract base class for all workbench actions. """ # Enthought library imports. from pyface.workbench.api import WorkbenchWindow from pyface.action.api import Action from traits.api import Instance class WorkbenchAction(Action): """ Abstract base class for all workbench actions. """ #### 'WorkbenchAction' interface ########################################## # The workbench window that the action is in. # # This is set by the framework. window = Instance(WorkbenchWindow) #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/reset_all_perspectives_action.py0000644000175100001440000000220711674463363027145 0ustar ischnellusers00000000000000""" An action that resets *all* perspectives. """ # Enthought library imports. from pyface.api import YES # Local imports. from workbench_action import WorkbenchAction # The message used when confirming the action. MESSAGE = 'Do you want to reset ALL perspectives to their defaults?' class ResetAllPerspectivesAction(WorkbenchAction): """ An action that resets *all* perspectives. """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = 'pyface.workbench.action.reset_all_perspectives' # The action's name (displayed on menus/tool bar tools etc). name = 'Reset All Perspectives' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ window = self.window if window.confirm(MESSAGE) == YES: window.reset_all_perspectives() return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/save_as_user_perspective_action.py0000644000175100001440000000362211674463363027471 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ An action that saves the active perspective as a user perspective. """ # Local imports. from user_perspective_name import UserPerspectiveName from workbench_action import WorkbenchAction class SaveAsUserPerspectiveAction(WorkbenchAction): """ An action that saves the active perspective as a user perspective. """ #### 'Action' interface ################################################### # The action's unique identifier. id = 'pyface.workbench.action.save_as_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Save Perspective As...' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ window = event.window manager = window.workbench.user_perspective_manager # Get the name of the new perspective. upn = UserPerspectiveName(name=window.active_perspective.name) if upn.edit_traits(view='save_as_view').result: # Make a clone of the active perspective, but give it the new name. perspective = manager.clone_perspective( window, window.active_perspective, name=upn.name.strip() ) # Add it to the window... window.perspectives.append(perspective) # ... and make it the active perspective. window.active_perspective = perspective return #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/action/delete_user_perspective_action.py0000644000175100001440000000524011674463363027310 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ An action that deletes a user perspective. """ # Enthought library imports. from pyface.api import YES # Local imports. from user_perspective_action import UserPerspectiveAction class DeleteUserPerspectiveAction(UserPerspectiveAction): """ An action that deletes a user perspective. """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = 'pyface.workbench.action.delete_user_perspective_action' # The action's name (displayed on menus/tool bar tools etc). name = 'Delete Perspective' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ window = event.window manager = window.workbench.user_perspective_manager # The perspective to delete. perspective = window.active_perspective # Make sure that the user isn't having second thoughts! message = 'Are you sure you want to delete the "%s" perspective?' % \ perspective.name answer = window.confirm(message, title='Confirm Delete') if answer == YES: # Set the active perspective to be the first remaining perspective. # # There is always a default NON-user perspective (even if no # perspectives are explicitly defined) so we should never(!) not # be able to find one! window.active_perspective = self._get_next_perspective(window) # Remove the perspective from the window. window.perspectives.remove(perspective) # Remove it from the user perspective manager. manager.remove(perspective.id) return ########################################################################### # Private interface. ########################################################################### def _get_next_perspective(self, window): """ Return the first perspective that is not the active one! """ if window.active_perspective is window.perspectives[0]: index = 1 else: index = 0 return window.perspectives[index] #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/action/set_active_perspective_action.py0000644000175100001440000000374411674463363027145 0ustar ischnellusers00000000000000""" An action that sets the active perspective. """ # Enthought library imports. from pyface.workbench.api import IPerspective from traits.api import Delegate, Instance, on_trait_change # Local imports. from workbench_action import WorkbenchAction class SetActivePerspectiveAction(WorkbenchAction): """ An action that sets the active perspective. """ #### 'Action' interface ################################################### # Is the action enabled? enabled = Delegate('perspective') # The action's unique identifier (may be None). id = Delegate('perspective') # The action's name (displayed on menus/tool bar tools etc). name = Delegate('perspective') # The action's style. style = 'radio' #### 'SetActivePerspectiveAction' interface ############################### # The perspective that we set the active perspective to. perspective = Instance(IPerspective) ########################################################################### # 'Action' interface. ########################################################################### def destroy(self): """ Destroy the action. """ self.window = None return def perform(self, event): """ Perform the action. """ self.window.active_perspective = self.perspective return ########################################################################### # Private interface. ########################################################################### @on_trait_change('perspective,window.active_perspective') def _refresh_checked(self): """ Refresh the checked state of the action. """ self.checked = self.perspective is not None \ and self.window is not None \ and self.window.active_perspective is not None \ and self.perspective.id is self.window.active_perspective.id return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/menu_bar_manager.py0000644000175100001440000000230611674463363024324 0ustar ischnellusers00000000000000""" The menu bar manager for Envisage workbench windows. """ # Enthought library imports. from pyface.action.api import MenuBarManager as BaseMenuBarManager from traits.api import Instance # Local imports. from action_controller import ActionController class MenuBarManager(BaseMenuBarManager): """ The menu bar manager for Envisage workbench windows. """ #### 'MenuBarManager' interface ########################################### # The workbench window that we are the menu bar manager for. window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'MenuBarManager' interface. ########################################################################### def create_menu_bar(self, parent): """ Creates a menu bar representation of the manager. """ # The controller handles the invocation of every action. controller = ActionController(window=self.window) menu_bar = super(MenuBarManager, self).create_menu_bar( parent, controller=controller ) return menu_bar #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/__init__.py0000755000175100001440000000000011674463363022571 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/action/perspective_menu_manager.py0000644000175100001440000001113211674463363026106 0ustar ischnellusers00000000000000""" The default perspective menu for a workbench window. """ # Enthought library imports. from pyface.action.api import Group, MenuManager from traits.api import Instance, List, on_trait_change # Local imports. from delete_user_perspective_action import DeleteUserPerspectiveAction from new_user_perspective_action import NewUserPerspectiveAction from rename_user_perspective_action import RenameUserPerspectiveAction from reset_all_perspectives_action import ResetAllPerspectivesAction from reset_active_perspective_action import ResetActivePerspectiveAction from save_as_user_perspective_action import SaveAsUserPerspectiveAction from set_active_perspective_action import SetActivePerspectiveAction class PerspectiveMenuManager(MenuManager): """ The default perspective menu for a workbench window. """ #### 'ActionManager' interface ############################################ # All of the groups in the manager. groups = List(Group) # The manager's unique identifier. id = 'PerspectivesMenu' #### 'MenuManager' interface ############################################## # The menu manager's name. name = 'Perspectives' #### 'PerspectiveMenuManager' interface ################################### # The workbench window that the manager is part of. window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'ActionManager' interface. ########################################################################### def _groups_default(self): """ Trait initializer. """ groups = [ # Create a group containing the actions that switch to specific # perspectives. self._create_perspective_group(self.window), # Create a group containing the user perspective create/save/rename # /delete actions. self._create_user_perspective_group(self.window), # Create a group containing the reset actions. self._create_reset_perspective_group(self.window) ] return groups ########################################################################### # 'PerspectiveMenuManager' interface. ########################################################################### @on_trait_change('window.perspectives') @on_trait_change('window.perspectives_items') def rebuild(self): """ Rebuild the menu. This is called when user perspectives have been added or removed. """ # Clear out the old menu. This gives any actions that have trait # listeners (i.e. the rename and delete actions!) a chance to unhook # them. self.destroy() # Resetting the trait allows the initializer to run again (which will # happen just as soon as we fire the 'changed' event). self.reset_traits(['groups']) # Let the associated menu know that we have changed. self.changed = True return ########################################################################### # Private interface. ########################################################################### def _create_perspective_group(self, window): """ Create the actions that switch to specific perspectives. """ # fixme: Not sure if alphabetic sorting is appropriate in all cases, # but it will do for now! perspectives = window.perspectives[:] perspectives.sort(lambda x, y: cmp(x.name, y.name)) # For each perspective, create an action that sets the active # perspective to it. group = Group() for perspective in perspectives: group.append( SetActivePerspectiveAction( perspective=perspective, window=window ) ) return group def _create_user_perspective_group(self, window): """ Create the user perspective create/save/rename/delete actions. """ group = Group( NewUserPerspectiveAction(window=window), SaveAsUserPerspectiveAction(window=window), RenameUserPerspectiveAction(window=window), DeleteUserPerspectiveAction(window=window) ) return group def _create_reset_perspective_group(self, window): """ Create the reset perspective actions. """ group = Group( ResetActivePerspectiveAction(window=window), ResetAllPerspectivesAction(window=window) ) return group #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/tool_bar_manager.py0000644000175100001440000000234311674463363024336 0ustar ischnellusers00000000000000""" The tool bar manager for the Envisage workbench window. """ # Enthought library imports. import pyface.action.api as pyface from traits.api import Instance # Local imports. from action_controller import ActionController class ToolBarManager(pyface.ToolBarManager): """ The tool bar manager for the Envisage workbench window. """ #### 'ToolBarManager' interface ########################################### # The workbench window that we are the tool bar manager for. window = Instance('pyface.workbench.api.WorkbenchWindow') ########################################################################### # 'ToolBarManager' interface. ########################################################################### def create_tool_bar(self, parent, controller=None): """ Creates a tool bar representation of the manager. """ # The controller handles the invocation of every action. if controller is None: controller = ActionController(window=self.window) tool_bar = super(ToolBarManager, self).create_tool_bar( parent, controller=controller ) return tool_bar #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/view_chooser.py0000644000175100001440000001434311674463363023542 0ustar ischnellusers00000000000000""" A UI that allows the user to choose a view. """ # Enthought library imports. from pyface.workbench.api import IView, WorkbenchWindow from traits.api import Any, HasTraits, Instance, List, Str from traits.api import TraitError, Undefined from traitsui.api import Item, TreeEditor, TreeNode, View from traitsui.menu import Action # fixme: Non-api import! class Category(HasTraits): """ A view category. """ # The name of the category. name = Str # The views in the category. views = List class WorkbenchWindowTreeNode(TreeNode): """ A tree node for workbench windows that displays the window's views. The views are grouped by their category. """ #### 'TreeNode' interface ################################################# # List of object classes that the node applies to. node_for = [WorkbenchWindow] ########################################################################### # 'TreeNode' interface. ########################################################################### def get_children(self, object): """ Get the object's children. """ # Collate the window's views into categories. categories_by_name = self._get_categories_by_name(object) categories = categories_by_name.values() categories.sort(key=lambda category: category.name) return categories ########################################################################### # Private interface. ########################################################################### def _get_categories_by_name(self, window): """ Return a dictionary containing all categories keyed by name. """ categories_by_name = {} for view in window.views: category = categories_by_name.get(view.category) if category is None: category = Category(name=view.category) categories_by_name[view.category] = category category.views.append(view) return categories_by_name class IViewTreeNode(TreeNode): """ A tree node for objects that implement the 'IView' interface. This node does *not* recognise objects that can be *adapted* to the 'IView' interface, only those that actually implement it. If we wanted to allow for adaptation we would have to work out a way for the rest of the 'TreeNode' code to access the adapter, not the original object. We could, of course override every method, but that seems a little, errr, tedious. We could probably do with something like in the PyFace tree where there is a method that returns the actual object that we want to manipulate. """ def is_node_for(self, obj): """ Returns whether this is the node that handles a specified object. """ # By checking for 'is obj' here, we are *not* allowing adaptation (if # we were allowing adaptation it would be 'is not None'). See the class # doc string for details. return IView(obj, Undefined) is obj def get_icon(self, obj, is_expanded): """ Returns the icon for a specified object. """ if obj.image is not None: icon = obj.image else: # fixme: A bit of magic here! Is there a better way to say 'use # the default leaf icon'? icon = '' return icon class ViewChooser(HasTraits): """ Allow the user to choose a view. This implementation shows views in a tree grouped by category. """ # The window that contains the views to choose from. window = Instance('pyface.workbench.api.WorkbenchWindow') # The currently selected tree item (at any point in time this might be # either None, a view category, or a view). selected = Any # The selected view (None if the selected item is not a view). view = Instance(IView) #### Traits UI views ###################################################### traits_ui_view = View( Item( name = 'window', editor = TreeEditor( nodes = [ WorkbenchWindowTreeNode( auto_open = True, label = '=Views', rename = False, copy = False, delete = False, insert = False, menu = None, ), TreeNode( node_for = [Category], auto_open = True, children = 'views', label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ), IViewTreeNode( auto_open = False, label = 'name', rename = False, copy = False, delete = False, insert = False, menu = None, ) ], editable = False, hide_root = True, selected = 'selected', show_icons = True ), show_label = False ), buttons = [ Action(name='OK', enabled_when='view is not None'), 'Cancel' ], resizable = True, style = 'custom', title = 'Show View', width = .2, height = .4 ) ########################################################################### # 'ViewChooser' interface. ########################################################################### def _selected_changed(self, old, new): """ Static trait change handler. """ # If the assignment fails then the selected object does *not* implement # the 'IView' interface. try: self.view = new except TraitError: self.view = None return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/user_perspective_action.py0000644000175100001440000000364211674463363025772 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ The base class for user perspective actions. """ # Enthought library imports. from traits.api import on_trait_change # Local imports. from workbench_action import WorkbenchAction class UserPerspectiveAction(WorkbenchAction): """ The base class for user perspective actions. Instances of this class (or its subclasses ;^) are enabled only when the active perspective is a user perspective. """ ########################################################################### # 'Action' interface. ########################################################################### def destroy(self): """ Destroy the action. """ # This removes the active perspective listener. self.window = None return ########################################################################### # Private interface. ########################################################################### def _is_user_perspective(self, perspective): """ Is the specified perspective a user perspective? """ # fixme: This seems a bit of a smelly way to make the determinaction! id = perspective.id return ((id[:19] == '__user_perspective_') and (id[-2:] == '__')) @on_trait_change('window.active_perspective') def _refresh_enabled(self): """ Refresh the enabled state of the action. """ self.enabled = self.window is not None \ and self.window.active_perspective is not None \ and self._is_user_perspective(self.window.active_perspective) return #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/action/show_view_action.py0000644000175100001440000000300311674463363024404 0ustar ischnellusers00000000000000""" An action that shows a dialog to allow the user to choose a view. """ # Local imports. from view_chooser import ViewChooser from workbench_action import WorkbenchAction class ShowViewAction(WorkbenchAction): """ An action that shows a dialog to allow the user to choose a view. """ #### 'Action' interface ################################################### # The action's unique identifier (may be None). id = 'pyface.workbench.action.show_view' # The action's name (displayed on menus/tool bar tools etc). name = 'Show View' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Perform the action. """ chooser = ViewChooser(window=self.window) ui = chooser.edit_traits(parent=self.window.control, kind='livemodal') # If the user closes the dialog by using the window manager's close button # (e.g. the little [x] in the top corner), ui.result is True, but chooser.view # might be None, so we need an explicit check for that. if ui.result and chooser.view is not None: # This shows the view... chooser.view.show() # ... and this makes it active (brings it to the front, gives it # focus etc). chooser.view.activate() return #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/action/new_user_perspective_action.py0000644000175100001440000000345611674463363026646 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005-2006 by Enthought, Inc. # All rights reserved. # # Author: David C. Morrill # #----------------------------------------------------------------------------- """ An action that creates a new (and empty) user perspective. """ # Local imports. from user_perspective_name import UserPerspectiveName from workbench_action import WorkbenchAction class NewUserPerspectiveAction(WorkbenchAction): """ An action that creates a new (and empty) user perspective. """ #### 'Action' interface ################################################### # The action's unique identifier. id = 'pyface.workbench.action.new_user_perspective_action' # The action's name. name = 'New Perspective...' ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Peform the action. """ window = event.window manager = window.workbench.user_perspective_manager # Get the details of the new perspective. upn = UserPerspectiveName(name='User Perspective %d' % manager.next_id) if upn.edit_traits(view='new_view').result: # Create a new (and empty) user perspective. perspective = manager.create_perspective( upn.name.strip(), upn.show_editor_area ) # Add it to the window... window.perspectives.append(perspective) # ... and make it the active perspective. window.active_perspective = perspective return #### EOF ##################################################################### pyface-4.1.0/pyface/workbench/perspective_item.py0000755000175100001440000000375611674463363023151 0ustar ischnellusers00000000000000""" An item in a Perspective contents list. """ # Enthought library imports. from traits.api import Enum, Float, HasTraits, Str, implements # Local imports. from i_perspective_item import IPerspectiveItem class PerspectiveItem(HasTraits): """ An item in a Perspective contents list. """ implements(IPerspectiveItem) # The Id of the view to display in the perspective. id = Str # The position of the view relative to the item specified in the # 'relative_to' trait. # # 'top' puts the view above the 'relative_to' item. # 'bottom' puts the view below the 'relative_to' item. # 'left' puts the view to the left of the 'relative_to' item. # 'right' puts the view to the right of the 'relative_to' item. # 'with' puts the view in the same region as the 'relative_to' item. # # If the position is specified as 'with' you must specify a 'relative_to' # item other than the editor area (i.e., you cannot position a view 'with' # the editor area). position = Enum('left', 'top', 'bottom', 'right', 'with') # The Id of the view to position relative to. If this is not specified # (or if no view exists with this Id) then the view will be placed relative # to the editor area. relative_to = Str # The width of the item (as a fraction of the window width). # # e.g. 0.5 == half the window width. # # Note that this is treated as a suggestion, and it may not be possible # for the workbench to allocate the space requested. width = Float(-1) # The height of the item (as a fraction of the window height). # # e.g. 0.5 == half the window height. # # Note that this is treated as a suggestion, and it may not be possible # for the workbench to allocate the space requested. height = Float(-1) # The style of the dock control created. style_hint = Enum('tab', 'vertical', 'horizontal', 'fixed') #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/i_workbench.py0000644000175100001440000000624411674463363022064 0ustar ischnellusers00000000000000""" The workbench interface. """ # Enthought library imports. from traits.api import Event, Instance, Interface, List, Str from traits.api import VetoableEvent, implements # Local imports. from user_perspective_manager import UserPerspectiveManager from window_event import WindowEvent, VetoableWindowEvent from workbench_window import WorkbenchWindow class IWorkbench(Interface): """ The workbench interface. """ #### 'IWorkbench' interface ############################################### # The active workbench window (the last one to get focus). active_window = Instance(WorkbenchWindow) # The optional application scripting manager. script_manager = Instance('apptools.appscripting.api.IScriptManager') # A directory on the local file system that we can read and write to at # will. This is used to persist window layout information, etc. state_location = Str # The optional undo manager. undo_manager = Instance('apptools.undo.api.IUndoManager') # The user defined perspectives manager. user_perspective_manager = Instance(UserPerspectiveManager) # All of the workbench windows created by the workbench. windows = List(WorkbenchWindow) #### Workbench lifecycle events #### # Fired when the workbench is about to exit. # # This can be caused by either:- # # a) The 'exit' method being called. # b) The last open window being closed. exiting = VetoableEvent # Fired when the workbench has exited. # # This is fired after the last open window has been closed. exited = Event #### Window lifecycle events #### # Fired when a workbench window has been created. window_created = Event(WindowEvent) # Fired when a workbench window is opening. window_opening = Event(VetoableWindowEvent) # Fired when a workbench window has been opened. window_opened = Event(WindowEvent) # Fired when a workbench window is closing. window_closing = Event(VetoableWindowEvent) # Fired when a workbench window has been closed. window_closed = Event(WindowEvent) ########################################################################### # 'IWorkbench' interface. ########################################################################### def create_window(self, **kw): """ Factory method that creates a new workbench window. """ def edit(self, obj, kind=None, use_existing=True): """ Edit an object in the active workbench window. """ def exit(self): """ Exit the workbench. This closes all open workbench windows. This method is not called when the user clicks the close icon. Nor when they do an Alt+F4 in Windows. It is only called when the application menu File->Exit item is selected. """ def get_editor(self, obj, kind=None): """ Return the editor that is editing an object. Returns None if no such editor exists. """ def get_editor_by_id(self, id): """ Return the editor with the specified Id. Returns None if no such editor exists. """ #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/__init__.py0000755000175100001440000000000011674463363021314 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/workbench/i_editor_manager.py0000755000175100001440000000267411674463363023070 0ustar ischnellusers00000000000000""" The editor manager interface. """ # Enthought library imports. from traits.api import Instance, Interface class IEditorManager(Interface): """ The editor manager interface. """ # The workbench window that the editor manager manages editors for ;^) window = Instance('pyface.workbench.api.WorkbenchWindow') def add_editor(self, editor, kind): """ Registers an existing editor. """ def create_editor(self, window, obj, kind): """ Create an editor for an object. 'kind' optionally contains any data required by the specific editor manager implementation to decide what type of editor to create. Returns None if no editor can be created for the resource. """ def get_editor(self, window, obj, kind): """ Get the editor that is currently editing an object. 'kind' optionally contains any data required by the specific editor manager implementation to decide what type of editor to create. Returns None if no such editor exists. """ def get_editor_kind(self, editor): """ Return the 'kind' associated with 'editor'. """ def get_editor_memento(self, editor): """ Return the state of an editor suitable for pickling etc. """ def set_editor_memento(self, memento): """ Restore an editor from a memento and return it. """ #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/i_workbench_part.py0000644000175100001440000000713211674463363023107 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for workbench parts. """ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, Interface from traits.api import List, Str, Unicode, implements class IWorkbenchPart(Interface): """ The interface for workbench parts. A workbench part is a visual section within the workbench. There are two sub-types, 'View' and 'Editor'. """ # The toolkit-specific control that represents the part. # # The framework sets this to the value returned by 'create_control'. control = Any # Does the part currently have the focus? has_focus = Bool(False) # The part's globally unique identifier. id = Str # The part's name (displayed to the user). name = Unicode # The current selection within the part. selection = List # The workbench window that the part is in. # # The framework sets this when the part is created. window = Instance('pyface.workbench.api.WorkbenchWindow') #### Methods ############################################################## def create_control(self, parent): """ Create the toolkit-specific control that represents the part. The parameter *parent* is the toolkit-specific control that is the parts's parent. Return the toolkit-specific control. """ def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. Return None. """ def set_focus(self): """ Set the focus to the appropriate control in the part. Return None. """ class MWorkbenchPart(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ implements(IWorkbenchPart) #### 'IWorkbenchPart' interface ########################################### # The toolkit-specific control that represents the part. # # The framework sets this to the value returned by 'create_control'. control = Any # Does the part currently have the focus? has_focus = Bool(False) # The part's globally unique identifier. id = Str # The part's name (displayed to the user). name = Unicode # The current selection within the part. selection = List # The workbench window that the part is in. # # The framework sets this when the part is created. window = Instance('pyface.workbench.api.WorkbenchWindow') #### Methods ############################################################## def create_control(self, parent): """ Create the toolkit-specific control that represents the part. """ raise NotImplementedError def destroy_control(self): """ Destroy the toolkit-specific control that represents the part. """ raise NotImplementedError def set_focus(self): """ Set the focus to the appropriate control in the part. """ raise NotImplementedError #### EOF ###################################################################### pyface-4.1.0/pyface/workbench/perspective.py0000755000175100001440000001422411674463363022123 0ustar ischnellusers00000000000000""" The default perspective. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Bool, HasTraits, List, Str, Tuple, implements # Local imports. from i_perspective import IPerspective from perspective_item import PerspectiveItem # Logging. logger = logging.getLogger(__name__) class Perspective(HasTraits): """ The default perspective. """ implements(IPerspective) # The ID of the default perspective. DEFAULT_ID = 'pyface.workbench.default' # The name of the default perspective. DEFAULT_NAME = 'Default' #### 'IPerspective' interface ############################################# # The perspective's unique identifier (unique within a workbench window). id = Str(DEFAULT_ID) # The perspective's name. name = Str(DEFAULT_NAME) # The contents of the perspective. contents = List(PerspectiveItem) # The size of the editor area in this perspective. A value of (-1, -1) # indicates that the workbench window should choose an appropriate size # based on the sizes of the views in the perspective. editor_area_size = Tuple((-1, -1)) # Is the perspective enabled? enabled = Bool(True) # Should the editor area be shown in this perspective? show_editor_area = Bool(True) ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Return an informal string representation of the object. """ return 'Perspective(%s)' % self.id ########################################################################### # 'Perspective' interface. ########################################################################### #### Initializers ######################################################### def _id_default(self): """ Trait initializer. """ # If no Id is specified then use the name. return self.name #### Methods ############################################################## def create(self, window): """ Create the perspective in a workbench window. For most cases you should just be able to set the 'contents' trait to lay out views as required. However, you can override this method if you want to have complete control over how the perspective is created. """ # Set the size of the editor area. if self.editor_area_size != (-1, -1): window.editor_area_size = self.editor_area_size # If the perspective has specific contents then add just those. if len(self.contents) > 0: self._add_contents(window, self.contents) # Otherwise, add all of the views defined in the window at their # default positions realtive to the editor area. else: self._add_all(window) # Activate the first view in every region. window.reset_views() return def show(self, window): """ Called when the perspective is shown in a workbench window. The default implementation does nothing, but you can override this method if you want to do something whenever the perspective is activated. """ return ########################################################################### # Private interface. ########################################################################### def _add_contents(self, window, contents): """ Adds the specified contents. """ # If we are adding specific contents then we ignore any default view # visibility. # # fixme: This is a bit ugly! Why don't we pass the visibility in to # 'window.add_view'? for view in window.views: view.visible = False for item in contents: self._add_perspective_item(window, item) return def _add_perspective_item(self, window, item): """ Adds a perspective item to a window. """ # If no 'relative_to' is specified then the view is positioned # relative to the editor area. if len(item.relative_to) > 0: relative_to = window.get_view_by_id(item.relative_to) else: relative_to = None # fixme: This seems a bit ugly, having to reach back up to the # window to get the view. Maybe its not that bad? view = window.get_view_by_id(item.id) if view is not None: # fixme: This is probably not the ideal way to sync view traits # and perspective_item traits. view.style_hint = item.style_hint # Add the view to the window. window.add_view( view, item.position, relative_to, (item.width, item.height) ) else: # The reason that we don't just barf here is that a perspective # might use views from multiple plugins, and we probably want to # continue even if one or two of them aren't present. # # fixme: This is worth keeping an eye on though. If we end up with # a strict mode that throws exceptions early and often for # developers, then this might be a good place to throw one ;^) logger.error('missing view for perspective item <%s>' % item.id) return def _add_all(self, window): """ Adds *all* of the window's views defined in the window. """ for view in window.views: if view.visible: self._add_view(window, view) return def _add_view(self, window, view): """ Adds a view to a window. """ # If no 'relative_to' is specified then the view is positioned # relative to the editor area. if len(view.relative_to) > 0: relative_to = window.get_view_by_id(view.relative_to) else: relative_to = None # Add the view to the window. window.add_view( view, view.position, relative_to, (view.width, view.height) ) return #### EOF ###################################################################### pyface-4.1.0/pyface/i_splash_screen.py0000644000175100001440000000643111674463363020747 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a splash screen. """ # Standard library imports. import logging # Enthought library imports. from traits.api import Any, Bool, Instance, Int, Tuple, Unicode # Local imports. from image_resource import ImageResource from splash_screen_log_handler import SplashScreenLogHandler from i_window import IWindow class ISplashScreen(IWindow): """ The interface for a splash screen. """ #### 'ISplashScreen' interface ############################################ # The image to display on the splash screen. image = Instance(ImageResource, ImageResource('splash')) # If log messages are to be displayed then this is the logging level. See # the Python documentation for the 'logging' module for more details. log_level = Int(logging.DEBUG) # Should the splash screen display log messages in the splash text? show_log_messages = Bool(True) # Optional text to display on top of the splash image. text = Unicode # The text color. # FIXME v3: When TraitsUI supports PyQt then change this to 'Color', # (unless that needs the toolkit to be selected too soon, in which case it # may need to stay as Any - or Str?) #text_color = WxColor('black') text_color = Any # The text font. # FIXME v3: When TraitsUI supports PyQt then change this back to # 'Font(None)' with the actual default being toolkit specific. #text_font = Font(None) text_font = Any # The x, y location where the text will be drawn. # FIXME v3: Remove this. text_location = Tuple(5, 5) class MSplashScreen(object): """ The mixin class that contains common code for toolkit specific implementations of the ISplashScreen interface. Reimplements: open(), close() """ ########################################################################### # 'IWindow' interface. ########################################################################### def open(self): """ Creates the toolkit-specific control for the widget. """ super(MSplashScreen, self).open() if self.show_log_messages: self._log_handler = SplashScreenLogHandler(self) self._log_handler.setLevel(self.log_level) # Get the root logger. logger = logging.getLogger() logger.addHandler(self._log_handler) def close(self): """ Close the window. """ if self.show_log_messages: # Get the root logger. logger = logging.getLogger() logger.removeHandler(self._log_handler) super(MSplashScreen, self).close() #### EOF ###################################################################### pyface-4.1.0/pyface/image_widget.py0000644000175100001440000001636511674463363020242 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A clickable/draggable widget containing an image. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports. from traits.api import Any, Bool, Event # Locak imports. from .widget import Widget class ImageWidget(Widget): """ A clickable/draggable widget containing an image. """ #### 'ImageWidget' interface ############################################## # The bitmap. bitmap = Any # Is the widget selected? selected = Bool(False) #### Events #### # A key was pressed while the tree is in focus. key_pressed = Event # A node has been activated (ie. double-clicked). node_activated = Event # A drag operation was started on a node. node_begin_drag = Event # A (non-leaf) node has been collapsed. node_collapsed = Event # A (non-leaf) node has been expanded. node_expanded = Event # A left-click occurred on a node. node_left_clicked = Event # A right-click occurred on a node. node_right_clicked = Event #### Private interface #################################################### _selected = Any ########################################################################### # 'object' interface. ########################################################################### def __init__ (self, parent, **traits): """ Creates a new widget. """ # Base class constructors. super(ImageWidget, self).__init__(**traits) # Add some padding around the image. size = (self.bitmap.GetWidth() + 10, self.bitmap.GetHeight() + 10) # Create the toolkit-specific control. self.control = wx.Window(parent, -1, size=size) self.control.__tag__ = 'hack' self._mouse_over = False self._button_down = False # Set up mouse event handlers: wx.EVT_ENTER_WINDOW(self.control, self._on_enter_window) wx.EVT_LEAVE_WINDOW(self.control, self._on_leave_window) wx.EVT_LEFT_DCLICK(self.control, self._on_left_dclick) wx.EVT_LEFT_DOWN(self.control, self._on_left_down) wx.EVT_LEFT_UP(self.control, self._on_left_up) wx.EVT_PAINT(self.control, self._on_paint) # Pens used to draw the 'selection' marker: # ZZZ: Make these class instances when moved to the wx toolkit code. self._selectedPenDark = wx.Pen( wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DSHADOW), 1, wx.SOLID ) self._selectedPenLight = wx.Pen( wx.SystemSettings_GetColour(wx.SYS_COLOUR_3DHIGHLIGHT), 1, wx.SOLID ) return ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _bitmap_changed(self, bitmap): """ Called when the widget's bitmap is changed. """ if self.control is not None: self.control.Refresh() return def _selected_changed(self, selected): """ Called when the selected state of the widget is changed. """ if selected: for control in self.GetParent().GetChildren(): if hasattr(control, '__tag__'): if control.Selected(): control.Selected(False) break self.Refresh() return #### wx event handlers #################################################### def _on_enter_window(self, event): """ Called when the mouse enters the widget. """ if self._selected is not None: self._mouse_over = True self.Refresh() return def _on_leave_window(self, event): """ Called when the mouse leaves the widget. """ if self._mouse_over: self._mouse_over = False self.Refresh() return def _on_left_dclick(self, event): """ Called when the left mouse button is double-clicked. """ #print 'left dclick' event.Skip() return def _on_left_down ( self, event = None ): """ Called when the left mouse button goes down on the widget. """ #print 'left down' if self._selected is not None: self.CaptureMouse() self._button_down = True self.Refresh() event.Skip() return def _on_left_up ( self, event = None ): """ Called when the left mouse button goes up on the widget. """ #print 'left up' need_refresh = self._button_down if need_refresh: self.ReleaseMouse() self._button_down = False if self._selected is not None: wdx, wdy = self.GetClientSizeTuple() x = event.GetX() y = event.GetY() if (0 <= x < wdx) and (0 <= y < wdy): if self._selected != -1: self.Selected( True ) elif need_refresh: self.Refresh() return if need_refresh: self.Refresh() event.Skip() return def _on_paint ( self, event = None ): """ Called when the widget needs repainting. """ wdc = wx.PaintDC( self.control ) wdx, wdy = self.control.GetClientSizeTuple() bitmap = self.bitmap bdx = bitmap.GetWidth() bdy = bitmap.GetHeight() wdc.DrawBitmap( bitmap, (wdx - bdx) / 2, (wdy - bdy) / 2, True ) pens = [ self._selectedPenLight, self._selectedPenDark ] bd = self._button_down if self._mouse_over: wdc.SetBrush( wx.TRANSPARENT_BRUSH ) wdc.SetPen( pens[ bd ] ) wdc.DrawLine( 0, 0, wdx, 0 ) wdc.DrawLine( 0, 1, 0, wdy ) wdc.SetPen( pens[ 1 - bd ] ) wdc.DrawLine( wdx - 1, 1, wdx - 1, wdy ) wdc.DrawLine( 1, wdy - 1, wdx - 1, wdy - 1 ) if self._selected == True: wdc.SetBrush( wx.TRANSPARENT_BRUSH ) wdc.SetPen( pens[ bd ] ) wdc.DrawLine( 1, 1, wdx - 1, 1 ) wdc.DrawLine( 1, 1, 1, wdy - 1 ) wdc.DrawLine( 2, 2, wdx - 2, 2 ) wdc.DrawLine( 2, 2, 2, wdy - 2 ) wdc.SetPen( pens[ 1 - bd ] ) wdc.DrawLine( wdx - 2, 2, wdx - 2, wdy - 1 ) wdc.DrawLine( 2, wdy - 2, wdx - 2, wdy - 2 ) wdc.DrawLine( wdx - 3, 3, wdx - 3, wdy - 2 ) wdc.DrawLine( 3, wdy - 3, wdx - 3, wdy - 3 ) return #### EOF ###################################################################### pyface-4.1.0/pyface/i_system_metrics.py0000644000175100001440000000300711674463363021164 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface to system metrics (screen width and height etc). """ # Enthought library imports. from traits.api import Int, Interface, Tuple class ISystemMetrics(Interface): """ The interface to system metrics (screen width and height etc). """ #### 'ISystemMetrics' interface ########################################### # The width of the screen in pixels. screen_width = Int # The height of the screen in pixels. screen_height = Int # Background color of a standard dialog window as a tuple of RGB values # between 0.0 and 1.0. # FIXME v3: Why isn't this a traits colour? dialog_background_color = Tuple class MSystemMetrics(object): """ The mixin class that contains common code for toolkit specific implementations of the ISystemMetrics interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/sorter.py0000644000175100001440000000673511674463363017133 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all sorters. """ # Enthought library imports. from traits.api import HasTraits class Sorter(HasTraits): """ Abstract base class for all sorters. """ ########################################################################### # 'ViewerSorter' interface. ########################################################################### def sort(self, widget, parent, nodes): """ Sorts a list of nodes IN PLACE. 'widget' is the widget that we are sorting nodes for. 'parent' is the parent node. 'nodes' is the list of nodes to sort. Returns the list that was sorted IN PLACE (for convenience). """ # This creates a comparison function with the names 'widget' and # 'parent' bound to the corresponding arguments to this method. def comparator(node_a, node_b): """ Comparator. """ return self.compare(widget, parent, node_a, node_b) nodes.sort(comparator) return nodes def compare(self, widget, parent, node_a, node_b): """ Returns the result of comparing two nodes. 'widget' is the widget that we are sorting nodes for. 'parent' is the parent node. 'node_a' is the the first node to compare. 'node_b' is the the second node to compare. """ # Get the category for each node. category_a = self.category(widget, parent, node_a) category_b = self.category(widget, parent, node_b) # If they are not in the same category then return the result of # comparing the categories. if category_a != category_b: result = cmp(category_a, category_b) else: label_a = widget.model.get_text(node_a) label_b = widget.model.get_text(node_b) # Compare the label text. result = cmp(label_a, label_b) return result def category(self, widget, parent, node): """ Returns the category (an integer) for an node. 'parent' is the parent node. 'nodes' is the node to return the category for. Categories are used to sort nodes into bins. The bins are arranged in ascending numeric order. The nodes within a bin are arranged as dictated by the sorter's 'compare' method. By default all nodes are given the same category (0). """ return 0 def is_sorter_trait(self, node, trait_name): """ Is the sorter affected by changes to a node's trait? 'node' is the node. 'trait_name' is the name of the trait. Returns True if the sorter would be affected by changes to the trait named 'trait_name' on the specified node. By default we return False. """ return False #### EOF ###################################################################### pyface-4.1.0/pyface/splash_screen.py0000644000175100001440000000161011674463363020431 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a splash screen. """ # Import the toolkit specific version. from toolkit import toolkit_object SplashScreen = toolkit_object('splash_screen:SplashScreen') #### EOF ###################################################################### pyface-4.1.0/pyface/sizers/0000755000175100001440000000000011674463363016547 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/sizers/flow.py0000644000175100001440000001377711674463363020107 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Defines a horizontal or vertical flow layout sizer for wxPython # # Written by: David C. Morrill # # Date: 01/12/2006 # # (c) Copyright 2006 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx from pyface.timer.api \ import do_later #------------------------------------------------------------------------------- # 'FlowSizer' class: #------------------------------------------------------------------------------- class FlowSizer ( wx.PySizer ): #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, orient = wx.HORIZONTAL ): super( FlowSizer, self ).__init__() self._orient = orient self._frozen = False self._needed_size = None #--------------------------------------------------------------------------- # Calculates the minimum size needed by the sizer: #--------------------------------------------------------------------------- def CalcMin ( self ): """ Calculates the minimum size needed by the sizer. """ if self._needed_size is not None: return self._needed_size horizontal = (self._orient == wx.HORIZONTAL) dx = dy = 0 for item in self.GetChildren() : idx, idy = item.CalcMin() if horizontal: dy = max( dy, idy ) else: dx = max( dx, idx ) return wx.Size( dx, dy ) #--------------------------------------------------------------------------- # Layout the contents of the sizer based on the sizer's current size and # position: #--------------------------------------------------------------------------- def RecalcSizes ( self ): """ Layout the contents of the sizer based on the sizer's current size and position. """ horizontal = (self._orient == wx.HORIZONTAL) x, y = self.GetPosition() dx, dy = self.GetSize() x0, y0 = x, y ex = x + dx ey = y + dy mdx = mdy = sdx = sdy = 0 visible = True cur_max = 0 for item in self.GetChildren() : idx, idy = item.CalcMin() expand = item.GetFlag() & wx.EXPAND if horizontal: if (x > x0) and ((x + idx) > ex): x = x0 y += (mdy + sdy) mdy = sdy = 0 if y >= ey: visible = False cur_max = max( idy, cur_max ) if expand: idy = cur_max if item.IsSpacer(): sdy = max( sdy, idy ) if x == x0: idx = 0 item.SetDimension( wx.Point( x, y ), wx.Size( idx, idy ) ) item.Show( visible ) x += idx mdy = max( mdy, idy ) else: if (y > y0) and ((y + idy) > ey): y = y0 x += (mdx + sdx) mdx = sdx = 0 if x >= ex: visible = False cur_max = max( idx, cur_max ) if expand: idx = cur_max if item.IsSpacer(): sdx = max( sdx, idx ) if y == y0: idy = 0 item.SetDimension( wx.Point( x, y ), wx.Size( idx, idy ) ) item.Show( visible ) y += idy mdx = max( mdx, idx ) if (not visible) and (self._needed_size is None): max_dx = max_dy = 0 if horizontal: max_dy = max( dy, y + mdy + sdy - y0 ) else: max_dx = max( dx, x + mdx + sdx - x0 ) self._needed_size = wx.Size( max_dx, max_dy ) if not self._frozen: self._do_parent( '_freeze' ) do_later( self._do_parent, '_thaw' ) else: self._needed_size = None #--------------------------------------------------------------------------- # Prevents the specified window from doing any further screen updates: #--------------------------------------------------------------------------- def _freeze ( self, window ): """ Prevents the specified window from doing any further screen updates. """ window.Freeze() self._frozen = True #--------------------------------------------------------------------------- # Lays out a specified window and then allows it to be updated again: #--------------------------------------------------------------------------- def _thaw ( self, window ): """ Lays out a specified window and then allows it to be updated again. """ window.Layout() window.Refresh() if self._frozen: self._frozen = False window.Thaw() #--------------------------------------------------------------------------- # Does a specified operation on the sizer's parent window: #--------------------------------------------------------------------------- def _do_parent ( self, method ): """ Does a specified operation on the sizer's parent window. """ i = 0 while True: try: item = self.GetItem( i ) if item is None: break i += 1 except: return if item.IsWindow(): getattr( self, method )( item.GetWindow().GetParent() ) return pyface-4.1.0/pyface/sizers/__init__.py0000644000175100001440000000000111674463363020647 0ustar ischnellusers00000000000000 pyface-4.1.0/pyface/widget.py0000644000175100001440000000157511674463363017075 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base implementation of all pyface widgets. """ # Import the toolkit specific version. from toolkit import toolkit_object Widget = toolkit_object('widget:Widget') #### EOF ###################################################################### pyface-4.1.0/pyface/message_dialog.py0000644000175100001440000000326411674463363020552 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a dialog that displays a message. """ # Convenience functions. def information(parent, message, title='Information'): """ Convenience function to show an information message dialog. """ dialog = MessageDialog( parent=parent, message=message, title=title, severity='information' ) dialog.open() return def warning(parent, message, title='Warning'): """ Convenience function to show a warning message dialog. """ dialog = MessageDialog( parent=parent, message=message, title=title, severity='warning' ) dialog.open() return def error(parent, message, title='Error'): """ Convenience function to show an error message dialog. """ dialog = MessageDialog( parent=parent, message=message, title=title, severity='error' ) dialog.open() return # Import the toolkit specific version. from toolkit import toolkit_object MessageDialog = toolkit_object('message_dialog:MessageDialog') #### EOF ###################################################################### pyface-4.1.0/pyface/action/0000755000175100001440000000000011674463363016505 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/action/window_action.py0000644000175100001440000000217711674463363021732 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all window actions. """ # Enthought library imports. from pyface.window import Window from traits.api import Instance # Local imports. from action import Action class WindowAction(Action): """ Abstract base class for all window actions. """ #### 'WindowAction' interface ############################################# # The window that the action is in. window = Instance(Window) #### EOF ###################################################################### pyface-4.1.0/pyface/action/action_item.py0000644000175100001440000001174311674463363021360 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enth373ought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ An action manager item that represents an actual action. """ # Enthought library imports. from traits.api import Any, Instance, List, Property, Str, on_trait_change # Local imports. from action import Action from action_manager_item import ActionManagerItem # Import the toolkit specific versions of the internal classes. from pyface.toolkit import toolkit_object _MenuItem = toolkit_object('action.action_item:_MenuItem') _Tool = toolkit_object('action.action_item:_Tool') _PaletteTool = toolkit_object('action.action_item:_PaletteTool') class ActionItem(ActionManagerItem): """ An action manager item that represents an actual action. """ #### 'ActionManagerItem' interface ######################################## # The item's unique identifier ('unique' in this case means unique within # its group). id = Property(Str) #### 'ActionItem' interface ############################################### # The action! action = Instance(Action) # The toolkit specific control created for this item. control = Any # The toolkit specific Id of the control created for this item. # # We have to keep the Id as well as the control because wx tool bar tools # are created as 'wxObjectPtr's which do not have Ids, and the Id is # required to manipulate the state of a tool via the tool bar 8^( # FIXME v3: Why is this part of the public interface? control_id = Any #### Private interface #################################################### # All of the internal instances that wrap this item. _wrappers = List(Any) ########################################################################### # 'ActionManagerItem' interface. ########################################################################### #### Trait properties ##################################################### def _get_id(self): """ Return's the item's Id. """ return self.action.id #### Trait change handlers ################################################ def _enabled_changed(self, trait_name, old, new): """ Static trait change handler. """ self.action.enabled = new return def _visible_changed(self, trait_name, old, new): """ Static trait change handler. """ self.action.visible = True return @on_trait_change('_wrappers.control') def _on_destroy(self, object, name, old, new): """ Handle the destruction of the wrapper. """ if name == 'control' and new is None: self._wrappers.remove(object) ########################################################################### # 'ActionItem' interface. ########################################################################### def add_to_menu(self, parent, menu, controller): """ Adds the item to a menu. """ if (controller is None) or controller.can_add_to_menu(self.action): wrapper = _MenuItem(parent, menu, self, controller) # fixme: Martin, who uses this information? if controller is None: self.control = wrapper.control self.control_id = wrapper.control_id self._wrappers.append(wrapper) return def add_to_toolbar(self, parent, tool_bar, image_cache, controller, show_labels=True): """ Adds the item to a tool bar. """ if (controller is None) or controller.can_add_to_toolbar(self.action): wrapper = _Tool( parent, tool_bar, image_cache, self, controller, show_labels ) # fixme: Martin, who uses this information? if controller is None: self.control = wrapper.control self.control_id = wrapper.control_id self._wrappers.append(wrapper) return def add_to_palette(self, tool_palette, image_cache, show_labels=True): """ Adds the item to a tool palette. """ wrapper = _PaletteTool(tool_palette, image_cache, self, show_labels) self._wrappers.append(wrapper) return def destroy(self): """ Called when the action is no longer required. By default this method calls 'destroy' on the action itself. """ self.action.destroy() return #### EOF ###################################################################### pyface-4.1.0/pyface/action/status_bar_manager.py0000644000175100001440000000167211674463363022726 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A status bar manager realizes itself in a status bar control. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object StatusBarManager = toolkit_object('action.status_bar_manager:StatusBarManager') ### EOF ####################################################################### pyface-4.1.0/pyface/action/images/0000755000175100001440000000000011674463363017752 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/action/images/action.png0000644000175100001440000000116311674463363021736 0ustar ischnellusers00000000000000PNG  IHDRabKGDxӖ+ pHYs  tIME 7|lIDAT8˥MkQ;4 Zt&I1P[њ ".DDHopnŝ`h~`mLT$չ.1"}އsJ)^זbo>PۘC:&lƹy.!{ſXmX.z@h޵'`#;v]QsgEG I%% x[9dv>{O68cD ٰXXM; O1鋠P@pDGtvCEHS+uZ!,;œ7#pqi+N {d%mIܶ ;17U!S" NQ&=ĩ8氟$jLs)vM"gINaT`']FnR<]~MjMQte܋J^YQi6Ȕ%Ju2eIhegkj)Lw$NŹu.4_.%.kIENDB`pyface-4.1.0/pyface/action/images/image_LICENSE.txt0000644000175100001440000000115011674463363022734 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Eclipse Eclipse Public License image_LICENSE_Eclipse.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- enthought/pyface/action/images: action.png | Eclipse pyface-4.1.0/pyface/action/api.py0000644000175100001440000000223111674463363017626 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from action import Action from action_controller import ActionController from action_event import ActionEvent from action_item import ActionItem from action_manager import ActionManager from action_manager_item import ActionManagerItem from group import Group, Separator from menu_manager import MenuManager from menu_bar_manager import MenuBarManager from status_bar_manager import StatusBarManager from tool_bar_manager import ToolBarManager from tool_palette_manager import ToolPaletteManager from window_action import WindowAction pyface-4.1.0/pyface/action/action_controller.py0000644000175100001440000000221611674463363022600 0ustar ischnellusers00000000000000""" The default action controller for menus, menu bars and tool bars. """ # Enthought library imports. from traits.api import HasTraits class ActionController(HasTraits): """ The default action controller for menus, menu bars and tool bars. """ ########################################################################### # 'ActionController' interface. ########################################################################### def perform(self, action, event): """ Control an action invocation. """ return action.perform(event) def can_add_to_menu(self, action): """ Returns True if the action can be added to a menu/menubar. """ return True def add_to_menu(self, action): """ Called when an action is added to the a menu/menubar. """ return def can_add_to_toolbar(self, action): """ Returns True if the action can be added to a toolbar. """ return True def add_to_toolbar(self, action): """ Called when an action is added to the a toolbar. """ return #### EOF ###################################################################### pyface-4.1.0/pyface/action/group.py0000644000175100001440000001350011674463363020212 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A group of action manager items. """ # Enthought library imports. from traits.api import Any, Bool, HasTraits, Instance, List, Property from traits.api import Str from traits.trait_base import user_name_for # Local imports. from action import Action from action_item import ActionItem from action_manager_item import ActionManagerItem class Group(HasTraits): """ A group of action manager items. By default, a group declares itself as requiring a separator when it is visualized, but this can be changed by setting its 'separator' trait to False. """ #### 'Group' interface #### # Is the group enabled? enabled = Bool(True) # Is the group visible? visible = Bool(True) # The group's unique identifier (only needs to be unique within the action # manager that the group belongs to). id = Str # All of the items in the group. items = Property # The action manager that the group belongs to. parent = Any#Instance('pyface.action.ActionManager') # Does this group require a separator when it is visualized? separator = Bool(True) #### Private interface #### # All of the items in the group. _items = List#(ActionManagerItem) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *items, **traits): """ Creates a new menu manager. """ # Base class constructor. super(Group, self).__init__(**traits) # Add any specified items. for item in items: self.append(item) return ########################################################################### # 'Group' interface. ########################################################################### #### Trait Properties ##################################################### def _get_items(self): """ Returns the items in the group. """ return self._items[:] #### Trait change handlers ################################################ def _enabled_changed(self, trait_name, old, new): """ Static trait change handler. """ for item in self.items: item.enabled = new return #### Methods ############################################################## def append(self, item): """ Appends an item to the group. See the documentation for 'insert'. """ return self.insert(len(self._items), item) def clear(self): """ Remove all items from the group. """ self._items = [] return def destroy(self): """ Called when the manager is no longer required. By default this method simply calls 'destroy' on all items in the group. """ for item in self.items: item.destroy() return def insert(self, index, item): """ Inserts an item into the group at the specified index. 1) An 'ActionManagerItem' instance. In which case the item is simply inserted into the group. 2) An 'Action' instance. In which case an 'ActionItem' instance is created with the action and then inserted into the group. 3) A Python callable (ie.'callable(item)' returns True). In which case an 'Action' is created that calls the callable when it is performed, and the action is then wrapped as in 2). """ if isinstance(item, Action): item = ActionItem(action=item) elif callable(item): text = user_name_for(item.func_name) item = ActionItem(action=Action(text=text, on_perform=item)) item.parent = self self._items.insert(index, item) return item def remove(self, item): """ Removes an item from the group. """ self._items.remove(item) item.parent = None return def insert_before(self, before, item): """ Inserts an item into the group before the specified item. See the documentation for 'insert'. """ index = self._items.index(before) self.insert(index, item) return (index, item) def insert_after(self, after, item): """ Inserts an item into the group after the specified item. See the documentation for 'insert'. """ index = self._items.index(after) self.insert(index + 1, item) return (index, item) def find(self, id): """ Returns the item with the specified Id. Returns None if no such item exists. """ for item in self._items: if item.id == id: break else: item = None return item class Separator(Group): """ A convenience class. This is only used in 'cheap and cheerful' applications that create menus like:: file_menu = MenuManager( CopyAction(), Separator(), ExitAction() ) Hopefully, 'Separator' is more readable than 'Group'... """ pass #### EOF ###################################################################### pyface-4.1.0/pyface/action/action.py0000644000175100001440000000634511674463363020344 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The base class for all actions. """ # Enthought library imports. from traits.api import Bool, Callable, Enum, HasTraits, Instance, Str from traits.api import Unicode from traitsui.ui_traits import Image class Action(HasTraits): """ The base class for all actions. An action is the non-UI side of a command which can be triggered by the end user. Actions are typically associated with buttons, menu items and tool bar tools. When the user triggers the command via the UI, the action's 'perform' method is invoked to do the actual work. """ #### 'Action' interface ################################################### # Keyboard accelerator (by default the action has NO accelerator). accelerator = Unicode # Is the action checked? This is only relevant if the action style is # 'radio' or 'toggle'. checked = Bool(False) # A longer description of the action (used for context sensitive help etc). # If no description is specified, the tooltip is used instead (and if there # is no tooltip, then well, maybe you just hate your users ;^). description = Unicode # Is the action enabled? enabled = Bool(True) # Is the action visible? visible = Bool(True) # The action's unique identifier (may be None). id = Str # The action's image (displayed on tool bar tools etc). image = Image # The action's name (displayed on menus/tool bar tools etc). name = Unicode # An (optional) callable that will be invoked when the action is performed. on_perform = Callable # The action's style. style = Enum('push', 'radio', 'toggle') # A short description of the action used for tooltip text etc. tooltip = Unicode ########################################################################### # 'Action' interface. ########################################################################### #### Initializers ######################################################### def _id_default(self): """ Initializes the 'id' trait. """ return self.name #### Methods ############################################################## def destroy(self): """ Called when the action is no longer required. By default this method does nothing, but this would be a great place to unhook trait listeners etc. """ return def perform(self, event): """ Performs the action. """ if self.on_perform is not None: self.on_perform() return #### EOF ###################################################################### pyface-4.1.0/pyface/action/action_event.py0000644000175100001440000000226111674463363021536 0ustar ischnellusers00000000000000""" The event passed to an action's 'perform' method. """ # Standard library imports. import time # Enthought library imports. from traits.api import Float, HasTraits, Int class ActionEvent(HasTraits): """ The event passed to an action's 'perform' method. """ #### 'ActionEvent' interface ############################################## # When the action was performed (time.time()). when = Float ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new action event. Note: Every keyword argument becoames a public attribute of the event. """ # Base-class constructor. super(ActionEvent, self).__init__(**traits) # fixme: We currently allow anything to be tagged onto the event, which # is going to make code very hard to read. self.__dict__.update(traits) # When the action was performed. self.when = time.time() return #### EOF ###################################################################### pyface-4.1.0/pyface/action/tool_palette_manager.py0000644000175100001440000000170011674463363023242 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tool bar manager realizes itself in a tool palette control. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object ToolPaletteManager = toolkit_object('action.tool_palette_manager:ToolPaletteManager') ### EOF ####################################################################### pyface-4.1.0/pyface/action/menu_bar_manager.py0000644000175100001440000000166611674463363022352 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A menu bar manager realizes itself in errr, a menu bar control. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object MenuBarManager = toolkit_object('action.menu_bar_manager:MenuBarManager') #### EOF ###################################################################### pyface-4.1.0/pyface/action/__init__.py0000644000175100001440000000010211674463363020607 0ustar ischnellusers00000000000000# Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. pyface-4.1.0/pyface/action/menu_manager.py0000644000175100001440000000163611674463363021523 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A menu manager realizes itself in a menu control. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object MenuManager = toolkit_object('action.menu_manager:MenuManager') ### EOF ####################################################################### pyface-4.1.0/pyface/action/action_manager_item.py0000644000175100001440000000415611674463363023052 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all action manager items. """ # Enthought library imports. from traits.api import Bool, HasTraits, Instance, Str class ActionManagerItem(HasTraits): """ Abstract base class for all action manager items. An action manager item represents a contribution to a shared UI resource such as a menu bar, menu or tool bar. Action manager items know how to add themselves to menu bars, menus and tool bars. In a tool bar a contribution item is represented as a tool or a separator. In a menu bar a contribution item is a menu, and in a menu a contribution item is a menu item or separator. """ # The item's unique identifier ('unique' in this case means unique within # its group) id = Str # The group the item belongs to. parent = Instance('pyface.action.api.Group') # Is the item enabled? enabled = Bool(True) # Is the item visible? visible = Bool(True) ########################################################################### # 'ActionManagerItem' interface. ########################################################################### def add_to_menu(self, parent, menu, controller): """ Adds the item to a menu. """ raise NotImplementedError def add_to_toolbar(self, parent, tool_bar, image_cache, controller): """ Adds the item to a tool bar. """ raise NotImplementedError #### EOF ###################################################################### pyface-4.1.0/pyface/action/tool_bar_manager.py0000644000175100001440000000166011674463363022355 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tool bar manager realizes itself in a tool bar control. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object ToolBarManager = toolkit_object('action.tool_bar_manager:ToolBarManager') ### EOF ####################################################################### pyface-4.1.0/pyface/action/action_manager.py0000644000175100001440000002354711674463363022041 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Abstract base class for all action managers. """ # Enthought library imports. from traits.api import Bool, Constant, Event, HasTraits, Instance from traits.api import List, Property, Str # Local imports. from action_controller import ActionController from group import Group class ActionManager(HasTraits): """ Abstract base class for all action managers. An action manager contains a list of groups, with each group containing a list of items. There are currently three concrete sub-classes:- 1) 'MenuBarManager' 2) 'MenuManager' 3) 'ToolBarManager' """ #### 'ActionManager' interface ############################################ # The Id of the default group. DEFAULT_GROUP = Constant('additions') # The action controller (if any) used to control how actions are performed. controller = Instance(ActionController) # Is the action manager enabled? enabled = Bool(True) # All of the contribution groups in the manager. groups = Property(List(Group)) # The manager's unique identifier (if it has one). id = Str # Is the action manager visible? visible = Bool(True) #### Events #### # fixme: We probably need more granular events than this! changed = Event #### Private interface #################################################### # All of the contribution groups in the manager. _groups = List(Group) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, *args, **traits): """ Creates a new menu manager. """ # Base class constructor. super(ActionManager, self).__init__(**traits) # The last group in every manager is the group with Id 'additions'. # # fixme: The side-effect of this is to ensure that the 'additions' # group has been created. Is the 'additions' group even a good idea? group = self._get_default_group() # Add all items to the manager. for arg in args: # We allow a group to be defined by simply specifying a string (its # Id). if isinstance(arg, basestring): # Create a group with the specified Id. arg = Group(id=arg) # If the item is a group then add it just before the default group # (ie. we always keep the default group as the last group in the # manager). if isinstance(arg, Group): self.insert(-1, arg) group = arg # Otherwise, the item is an action manager item so add it to the # current group. else: ## # If no group has been created then add one. This is only ## # relevant when using the 'shorthand' way to define menus. ## if group is None: ## group = Group(id='__first__') ## self.insert(-1, group) group.append(arg) return ########################################################################### # 'ActionManager' interface. ########################################################################### #### Trait properties ##################################################### def _get_groups(self): """ Returns the groups in the manager. """ return self._groups[:] #### Trait change handlers ################################################ def _enabled_changed(self, trait_name, old, new): """ Static trait change handler. """ for group in self._groups: group.enabled = new return def _visible_changed(self, trait_name, old, new): """ Static trait change handler. """ for group in self._groups: group.visible = new return #### Methods ############################################################## def append(self, item): """ Append an item to the manager. See the documentation for 'insert'. """ return self.insert(len(self._groups), item) def destroy(self): """ Called when the manager is no longer required. By default this method simply calls 'destroy' on all of the manager's groups. """ for group in self.groups: group.destroy() return def insert(self, index, item): """ Insert an item into the manager at the specified index. The item can be:- 1) A 'Group' instance. In which case the group is inserted into the manager's list of groups. 2) A string. In which case a 'Group' instance is created with that Id, and then inserted into the manager's list of groups. 3) An 'ActionManagerItem' instance. In which case the item is inserted into the manager's default group. """ # 1) The item is a 'Group' instance. if isinstance(item, Group): group = item # Insert the group into the manager. group.parent = self self._groups.insert(index, item) # 2) The item is a string. elif isinstance(item, basestring): # Create a group with that Id. group = Group(id=item) # Insert the group into the manager. group.parent = self self._groups.insert(index, group) # 3) The item is an 'ActionManagerItem' instance. else: # Find the default group. group = self._get_default_group() # Insert the item into the default group. group.insert(index, item) return group def find_group(self, id): """ Return the group with the specified Id. Return None if no such group exists. """ for group in self._groups: if group.id == id: break else: group = None return group def find_item(self, path): """ Return the item found at the specified path. 'path' is a '/' separated list of contribution Ids. Returns None if any component of the path is not found. """ components = path.split('/') # If there is only one component, then the path is just an Id so look # it up in this manager. if len(components) > 0: item = self._find_item(components[0]) if len(components) > 1 and item is not None: item = item.find_item('/'.join(components[1:])) else: item = None return item def walk(self, fn): """ Walk the manager applying a function at every item. """ fn(self) for group in self._groups: self.walk_group(group, fn) return def walk_group(self, group, fn): """ Walk a group applying a function at every item. """ fn(group) for item in group.items: if isinstance(item, Group): self.walk_group(item, fn) else: self.walk_item(item, fn) return def walk_item(self, item, fn): """ Walk an item (may be a sub-menu manager remember!). """ if hasattr(item, 'groups'): item.walk(fn) else: fn(item) return ########################################################################### # Private interface. ########################################################################### def _get_default_group(self): """ Returns the manager's default group. """ group = self.find_group(self.DEFAULT_GROUP) if group is None: group = Group(id=self.DEFAULT_GROUP) self.append(group) return group def _find_item(self, id): """ Returns the item with the specified Id. Returns None if no such item exists. """ for group in self.groups: item = group.find(id) if item is not None: break else: item = None return item ########################################################################### # Debugging interface. ########################################################################### def dump(self, indent=''): """ Render a manager! """ print indent, 'Manager', self.id indent += ' ' for group in self._groups: self.render_group(group, indent) return def render_group(self, group, indent=''): """ Render a group! """ print indent, 'Group', group.id indent += ' ' for item in group.items: if isinstance(item, Group): print 'Surely, a group cannot contain another group!!!!' self.render_group(item, indent) else: self.render_item(item, indent) return def render_item(self, item, indent=''): """ Render an item! """ if hasattr(item, 'groups'): item.dump(indent) else: print indent, 'Item', item.id return #### EOF ###################################################################### pyface-4.1.0/pyface/i_directory_dialog.py0000644000175100001440000000350711674463363021442 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a dialog that allows the user to browse for a directory. """ # Enthought library imports. from traits.api import Bool, Unicode # Local imports. from i_dialog import IDialog class IDirectoryDialog(IDialog): """ The interface for a dialog that allows the user to browse for a directory. """ #### 'IDirectoryDialog' interface ######################################### # The default path. The default (ie. the default default path) is toolkit # specific. # FIXME v3: The default should be the current directory. (It seems wx is # the problem, although the file dialog does the right thing.) default_path = Unicode # The message to display in the dialog. The default is toolkit specific. message = Unicode # True iff the dialog should include a button that allows the user to # create a new directory. new_directory = Bool(True) # The path of the chosen directory. path = Unicode class MDirectoryDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IDirectoryDialog interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/i_progress_dialog.py0000644000175100001440000000471411674463363021303 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a dialog that allows the user to open/save files etc. """ # Enthought library imports. from traits.api import Any, Bool, Int, Str # Local imports. from i_dialog import IDialog class IProgressDialog(IDialog): """ A simple progress dialog window which allows itself to be updated """ #### 'IProgressDialog' interface ################################## title = Str message = Str min = Int max = Int margin = Int(5) can_cancel = Bool(False) show_time = Bool(False) show_percent = Bool(False) # Label for the 'cancel' button cancel_button_label = Str ########################################################################### # 'IProgressDialog' interface. ########################################################################### def update(self, value): """ updates the progress bar to the desired value. If the value is >= the maximum and the progress bar is not contained in another panel the parent window will be closed """ class MProgressDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IProgressDialog interface. Implements: update() """ progress_bar = Any ########################################################################### # 'IProgressDialog' interface. ########################################################################### def update(self, value): """ updates the progress bar to the desired value. If the value is >= the maximum and the progress bar is not contained in another panel the parent window will be closed """ if self.progress_bar is not None: self.progress_bar.update(value) if value >= self.max: self.close() pyface-4.1.0/pyface/qt/0000755000175100001440000000000011674463363015654 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/qt/QtNetwork.py0000644000175100001440000000017311674463363020165 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtNetwork import * else: from PySide.QtNetwork import * pyface-4.1.0/pyface/qt/QtCore.py0000644000175100001440000000103111674463363017416 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtCore import * from PyQt4.QtCore import pyqtProperty as Property from PyQt4.QtCore import pyqtSignal as Signal from PyQt4.QtCore import pyqtSlot as Slot from PyQt4.Qt import QCoreApplication from PyQt4.Qt import Qt __version__ = QT_VERSION_STR __version_info__ = tuple(map(int, QT_VERSION_STR.split('.'))) else: try: from PySide import __version__, __version_info__ except ImportError: pass from PySide.QtCore import * pyface-4.1.0/pyface/qt/QtTest.py0000644000175100001440000000016511674463363017454 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtTest import * else: from PySide.QtTest import * pyface-4.1.0/pyface/qt/QtGui.py0000644000175100001440000000024611674463363017261 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.Qt import QKeySequence, QTextCursor from PyQt4.QtGui import * else: from PySide.QtGui import * pyface-4.1.0/pyface/qt/QtScript.py0000644000175100001440000000017111674463363017776 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtScript import * else: from PySide.QtScript import * pyface-4.1.0/pyface/qt/QtSvg.py0000644000175100001440000000016311674463363017272 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtSvg import * else: from PySide.QtSvg import * pyface-4.1.0/pyface/qt/__init__.py0000644000175100001440000000203511674463363017765 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2010, Enthought Inc # All rights reserved. # # This software is provided without warranty under the terms of the BSD license. # # Author: Enthought Inc # Description: Qt API selector. Can be used to switch between pyQt and PySide #------------------------------------------------------------------------------ import os def prepare_pyqt4(): # Set PySide compatible APIs. import sip sip.setapi('QString', 2) sip.setapi('QVariant', 2) qt_api = os.environ.get('QT_API') if qt_api is None: try: import PySide qt_api = 'pyside' except ImportError: try: prepare_pyqt4() import PyQt4 qt_api = 'pyqt' except ImportError: raise ImportError('Cannot import PySide or PyQt4') elif qt_api == 'pyqt': prepare_pyqt4() elif qt_api != 'pyside': raise RuntimeError("Invalid Qt API %r, valid values are: 'pyqt' or 'pyside'" % qt_api) pyface-4.1.0/pyface/qt/QtOpenGL.py0000644000175100001440000000017111674463363017656 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtOpenGL import * else: from PySide.QtOpenGL import * pyface-4.1.0/pyface/qt/QtWebKit.py0000644000175100001440000000017111674463363017717 0ustar ischnellusers00000000000000from . import qt_api if qt_api == 'pyqt': from PyQt4.QtWebKit import * else: from PySide.QtWebKit import * pyface-4.1.0/pyface/tasks/0000755000175100001440000000000011674463363016355 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/tests/0000755000175100001440000000000011674463363017517 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/tests/test_action_manager_builder.py0000644000175100001440000001242511674463363025611 0ustar ischnellusers00000000000000# Standard library imports. import unittest # Enthought library imports. from pyface.action.api import Action, ActionItem, ActionManager, Group, \ MenuManager, MenuBarManager from pyface.tasks.action.api import GroupSchema, MenuSchema, MenuBarSchema, \ SchemaAddition from pyface.tasks.action.task_action_manager_builder import \ TaskActionManagerBuilder from pyface.tasks.api import Task class ActionManagerBuilderTestCase(unittest.TestCase): def assertActionElementsEqual(self, first, second): """ Checks that two action managers are (logically) equivalent. """ children1 = children2 = [] self.assertEquals(type(first), type(second)) self.assertEquals(first.id, second.id) if isinstance(first, ActionItem): self.assertEquals(first.action.name, second.action.name) elif isinstance(first, ActionManager): if not isinstance(first, MenuBarManager): self.assertEquals(first.name, second.name) children1, children2 = first.groups, second.groups elif isinstance(first, Group): self.assertEquals(first.separator, second.separator) children1, children2 = first.items, second.items self.assertEquals(len(children1), len(children2)) for i in xrange(len(children1)): self.assertActionElementsEqual(children1[i], children2[i]) def setUp(self): """ Create some dummy actions to use while testing. """ for i in xrange(1, 6): action_id = 'action%i' % i setattr(self, action_id, Action(id=action_id, name='Action %i' % i)) def test_simple_menu_bar(self): """ Does constructing a simple menu with no additions work? """ schema = MenuBarSchema( MenuSchema(self.action1, self.action2, id='File', name='&File'), MenuSchema(self.action3, self.action4, id='Edit', name='&Edit')) builder = TaskActionManagerBuilder(task=Task(menu_bar=schema)) actual = builder.create_menu_bar_manager() desired = MenuBarManager(MenuManager(self.action1, self.action2, id='File', name='&File'), MenuManager(self.action3, self.action4, id='Edit', name='&Edit'), id='MenuBar') self.assertActionElementsEqual(actual, desired) def test_additions_menu_bar(self): """ Does constructing a menu with a few additions work? """ schema = MenuBarSchema( MenuSchema(GroupSchema(self.action1, self.action2, id='FileGroup'), id='File')) extras = [ SchemaAddition(factory=lambda: self.action3, before='action1', path='MenuBar/File/FileGroup'), SchemaAddition(factory=lambda: self.action4, before='action1', path='MenuBar/File/FileGroup'), SchemaAddition(factory=lambda: self.action5, path='MenuBar/File/FileGroup')] builder = TaskActionManagerBuilder(task=Task(menu_bar=schema, extra_actions=extras)) actual = builder.create_menu_bar_manager() desired = MenuBarManager(MenuManager(Group(self.action3, self.action4, self.action1, self.action2, self.action5, id='FileGroup'), id='File'), id='MenuBar') self.assertActionElementsEqual(actual, desired) def test_absolute_ordering(self): """ Does specifying absolute_position work? """ schema = MenuBarSchema( MenuSchema(GroupSchema(self.action1, self.action2, id='FileGroup'), id='File')) extras = [ SchemaAddition(factory=lambda: self.action3, absolute_position='last', path='MenuBar/File/FileGroup'), SchemaAddition(factory=lambda: self.action4, absolute_position='first', path='MenuBar/File/FileGroup'), SchemaAddition(factory=lambda: self.action5, absolute_position='first', path='MenuBar/File/FileGroup')] builder = TaskActionManagerBuilder(task=Task(menu_bar=schema, extra_actions=extras)) actual = builder.create_menu_bar_manager() desired = MenuBarManager(MenuManager(Group(self.action4, self.action5, self.action1, self.action2, self.action3, id='FileGroup'), id='File'), id='MenuBar') self.assertActionElementsEqual(actual, desired) if __name__ == '__main__': unittest.main() pyface-4.1.0/pyface/tasks/tests/test_topological_sort.py0000644000175100001440000000467311674463363024525 0ustar ischnellusers00000000000000# Standard library imports. import unittest # Local imports. from traits.api import HasTraits, Int from pyface.tasks.topological_sort import before_after_sort, \ topological_sort class TestItem(HasTraits): id = Int before = Int after = Int def __init__(self, id, **traits): super(TestItem, self).__init__(id=id, **traits) def __eq__(self, other): return self.id == other.id def __repr__(self): return repr(self.id) class TopologicalSortTestCase(unittest.TestCase): def test_before_after_sort_1(self): """ Does the before-after sort work? """ items = [ TestItem(1), TestItem(2), TestItem(3, before=2), TestItem(4, after=1), TestItem(5) ] actual = before_after_sort(items) desired = [ TestItem(1), TestItem(3), TestItem(4), TestItem(2), TestItem(5) ] self.assertEquals(actual, desired) def test_before_after_sort_2(self): """ Does the before-after sort work when both 'before' and 'after' are set? """ items = [ TestItem(1), TestItem(2), TestItem(3), TestItem(4, after=2, before=3) ] actual = before_after_sort(items) desired = [ TestItem(1), TestItem(2), TestItem(4), TestItem(3) ] self.assertEquals(actual, desired) def test_before_after_sort_3(self): """ Does the degenerate case for the before-after sort work? """ actual = before_after_sort([ TestItem(1) ]) desired = [ TestItem(1) ] self.assertEquals(actual, desired) def test_topological_sort_1(self): """ Does a basic topological sort work? """ pairs = [ (1,2), (3,5), (4,6), (1,3), (1,4), (1,6), (2,4) ] result, has_cycles = topological_sort(pairs) self.assert_(not has_cycles) self.assertEquals(result, [1, 2, 3, 4, 5, 6]) def test_topological_sort_2(self): """ Does another basic topological sort work? """ pairs = [ (1,2), (1,3), (2,4), (3,4), (5,6), (4,5) ] result, has_cycles = topological_sort(pairs) self.assert_(not has_cycles) self.assertEquals(result, [1, 2, 3, 4, 5, 6]) def test_topological_sort_3(self): """ Does cycle detection work? """ pairs = [ (1,2), (2,3), (3,1) ] result, has_cycles = topological_sort(pairs) self.assert_(has_cycles) if __name__ == '__main__': unittest.main() pyface-4.1.0/pyface/tasks/tests/test_editor_area_pane.py0000644000175100001440000000161011674463363024407 0ustar ischnellusers00000000000000# Standard library imports. import unittest # Enthought library imports. from pyface.tasks.api import Editor, EditorAreaPane class EditorAreaPaneTestCase(unittest.TestCase): def test_create_editor(self): """ Does creating an editor work? """ area = EditorAreaPane() area.register_factory(Editor, lambda obj: isinstance(obj, int)) self.assert_(isinstance(area.create_editor(0), Editor)) def test_factories(self): """ Does registering and unregistering factories work? """ area = EditorAreaPane() area.register_factory(Editor, lambda obj: isinstance(obj, int)) self.assertEqual(area.get_factory(0), Editor) self.assertEqual(area.get_factory('foo'), None) area.unregister_factory(Editor) self.assertEqual(area.get_factory(0), None) if __name__ == '__main__': unittest.main() pyface-4.1.0/pyface/tasks/i_task_pane.py0000644000175100001440000000267411674463363021215 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Any, Bool, HasTraits, Interface, Instance, \ Str, Unicode # Local imports. from task import Task class ITaskPane(Interface): """ The base interface for all panes (central and dock) in a Task. """ # The pane's identifier, unique within a Task. id = Str # The pane's user-visible name. name = Unicode # The toolkit-specific control that represents the pane. control = Any # Does the pane currently have focus? has_focus = Bool # The task with which the pane is associated. Set by the framework. task = Instance(Task) ########################################################################### # 'ITaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ def set_focus(self): """ Gives focus to the control that represents the pane. """ class MTaskPane(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ #### 'ITaskPane' interface ################################################ id = Str name = Unicode control = Any has_focus = Bool(False) task = Instance(Task) pyface-4.1.0/pyface/tasks/task_window_layout.py0000644000175100001440000000277511674463363022670 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Either, List, Str, Tuple # Local imports. from task_layout import LayoutContainer, TaskLayout class TaskWindowLayout(LayoutContainer): """ The layout of a TaskWindow. """ # The ID of the active task. If unspecified, the first task will be active. active_task = Str # The tasks contained in the window. If an ID is specified, the task will # use its default layout. Otherwise, it will use the specified TaskLayout. items = List(Either(Str, TaskLayout), pretty_skip=True) # The position of the window. position = Tuple(-1, -1) # The size of the window. size = Tuple(800, 600) def get_active_task(self): """ Returns the ID of the active task in the layout, or None if there is no active task. """ if self.active_task: return self.active_task elif self.items: first = self.items[0] return first if isinstance(first, basestring) else first.id return None def get_tasks(self): """ Returns the IDs of the tasks in the layout. """ return [ (item if isinstance(item, basestring) else item.id) for item in self.items ] def is_equivalent_to(self, layout): """ Returns whether two layouts are equivalent, i.e. whether they contain the same tasks. """ return isinstance(layout, TaskWindowLayout) and \ set(self.get_tasks()) == set(layout.get_tasks()) pyface-4.1.0/pyface/tasks/task_window_backend.py0000644000175100001440000000025011674463363022724 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object TaskWindowBackend = toolkit_object( 'tasks.task_window_backend:TaskWindowBackend') pyface-4.1.0/pyface/tasks/api.py0000644000175100001440000000124511674463363017502 0ustar ischnellusers00000000000000# Local imports. from advanced_editor_area_pane import AdvancedEditorAreaPane from dock_pane import DockPane from editor import Editor from editor_area_pane import EditorAreaPane from i_dock_pane import IDockPane from i_editor import IEditor from i_editor_area_pane import IEditorAreaPane from i_task_pane import ITaskPane from task import Task from task_layout import TaskLayout, PaneItem, Tabbed, Splitter, HSplitter, \ VSplitter from task_pane import TaskPane from task_window import TaskWindow from task_window_layout import TaskWindowLayout from traits_dock_pane import TraitsDockPane from traits_editor import TraitsEditor from traits_task_pane import TraitsTaskPane pyface-4.1.0/pyface/tasks/task_window.py0000644000175100001440000004017611674463363021270 0ustar ischnellusers00000000000000# Standard library imports. import logging # Enthought library imports. from pyface.action.api import MenuBarManager, StatusBarManager, ToolBarManager from pyface.api import ApplicationWindow from traits.api import Bool, Callable, HasTraits, HasStrictTraits, Instance, \ List, Property, Unicode, Vetoable, on_trait_change # Local imports. from action.task_action_manager_builder import TaskActionManagerBuilder from i_dock_pane import IDockPane from i_task_pane import ITaskPane from task import Task, TaskLayout from task_window_backend import TaskWindowBackend from task_window_layout import TaskWindowLayout # Logging. logger = logging.getLogger(__name__) class TaskWindow(ApplicationWindow): """ The the top-level window to which tasks can be assigned. A TaskWindow is responsible for creating and the managing the controls of its tasks. """ #### IWindow interface #################################################### # Unless a title is specifically assigned, delegate to the active task. title = Property(Unicode, depends_on='active_task, _title') #### TaskWindow interface ################################################ # The pane (central or dock) in the active task that currently has focus. active_pane = Instance(ITaskPane) # The active task for this window. active_task = Instance(Task) # The list of all tasks currently attached to this window. All panes of the # inactive tasks are hidden. tasks = List(Task) # The central pane of the active task, which is always visible. central_pane = Instance(ITaskPane) # The list of all dock panes in the active task, which may or may not be # visible. dock_panes = List(IDockPane) # The factory for the window's TaskActionManagerBuilder, which is # instantiated to translate menu and tool bar schemas into Pyface action # managers. This attribute can overridden to introduce custom logic into # the translation process, although this is not usually necessary. action_manager_builder_factory = Callable(TaskActionManagerBuilder) #### Protected traits ##################################################### _active_state = Instance('pyface.tasks.task_window.TaskState') _states = List(Instance('pyface.tasks.task_window.TaskState')) _title = Unicode _window_backend = Instance(TaskWindowBackend) ########################################################################### # 'Widget' interface. ########################################################################### def destroy(self): """ Overridden to ensure that all task panes are cleanly destroyed. """ # Allow the TaskWindowBackend to clean up first. self._window_backend.destroy() # Don't use 'remove_task' here to avoid changing the active state and # thereby removing the window's menus and toolbars. This can lead to # undesirable animations when the window is being closed. for state in self._states: self._destroy_state(state) super(TaskWindow, self).destroy() ########################################################################### # 'Window' interface. ########################################################################### def open(self): """ Opens the window. Overridden to make the 'opening' event vetoable and to activate a task if one has not already been activated. Returns whether the window was opened. """ self.opening = event = Vetoable() if not event.veto: # Create the control, if necessary. if self.control is None: self._create() # Activate a task, if necessary. if self._active_state is None and self._states: self.activate_task(self._states[0].task) self.show(True) self.opened = self return self.control is not None def close(self): """ Closes the window. Overriden to make the 'closing' event vetoable. Returns whether the window was closed. """ if self.control is not None: self.closing = event = Vetoable() if not event.veto: self.destroy() self.closed = self return self.control is None ########################################################################### # Protected 'IApplicationWindow' interface. ########################################################################### def _create_contents(self, parent): """ Delegate to the TaskWindowBackend. """ return self._window_backend.create_contents(parent) ########################################################################### # 'TaskWindow' interface. ########################################################################### def activate_task(self, task): """ Activates a task that has already been added to the window. """ state = self._get_state(task) if state and state != self._active_state: # Hide the panes of the currently active task, if necessary. if self._active_state is not None: self._window_backend.hide_task(self._active_state) # Initialize the new task, if necessary. if not state.initialized: task.initialized() state.initialized = True # Display the panes of the new task. self._window_backend.show_task(state) # Activate the new task. The menus, toolbars, and status bar will be # replaced at this time. self._active_state = state task.activated() elif not state: logger.warn("Cannot activate task %r: task does not belong to the " "window." % task) def add_task(self, task): """ Adds a task to the window. The task is not activated. """ if task.window is not None: logger.error("Cannot add task %r: task has already been added " "to a window!" % task) return task.window = self state = TaskState(task=task, layout=task.default_layout) self._states.append(state) # Make sure the underlying control has been created, even if it is not # yet visible. if self.control is None: self._create() # Create the central pane. state.central_pane = task.create_central_pane() state.central_pane.task = task state.central_pane.create(self.control) # Create the dock panes. state.dock_panes = task.create_dock_panes() for dock_pane_factory in task.extra_dock_pane_factories: state.dock_panes.append(dock_pane_factory(task=task)) for dock_pane in state.dock_panes: dock_pane.task = task dock_pane.create(self.control) # Build the menu and tool bars. builder = self.action_manager_builder_factory(task=task) state.menu_bar_manager = builder.create_menu_bar_manager() state.status_bar_manager = task.status_bar state.tool_bar_managers = builder.create_tool_bar_managers() def remove_task(self, task): """ Removes a task that has already been added to the window. All the task's panes are destroyed. """ state = self._get_state(task) if state: # If the task is active, make sure it is de-activated before # deleting its controls. if self._active_state == state: self._window_backend.hide_task(state) self._active_state = None self._destroy_state(state) self._states.remove(state) else: logger.warn("Cannot remove task %r: task does not belong to the " "window." % task) def focus_next_pane(self): """ Shifts focus to the "next" pane, taking into account the active pane and the pane geometry. """ if self._active_state: panes = self._get_pane_ring() index = 0 if self.active_pane: index = (panes.index(self.active_pane) + 1) % len(panes) panes[index].set_focus() def focus_previous_pane(self): """ Shifts focus to the "previous" pane, taking into account the active pane and the pane geometry. """ if self._active_state: panes = self._get_pane_ring() index = -1 if self.active_pane: index = panes.index(self.active_pane) - 1 panes[index].set_focus() def get_central_pane(self, task): """ Returns the central pane for the specified task. """ state = self._get_state(task) return state.central_pane if state else None def get_dock_pane(self, id, task=None): """ Returns the dock pane in the task with the specified ID, or None if no such dock pane exists. If a task is not specified, the active task is used. """ if task is None: state = self._active_state else: state = self._get_state(task) return state.get_dock_pane(id) if state else None def get_dock_panes(self, task): """ Returns the dock panes for the specified task. """ state = self._get_state(task) return state.dock_panes[:] if state else [] def get_task(self, id): """ Returns the task with the specified ID, or None if no such task exists. """ state = self._get_state(id) return state.task if state else None #### Methods for saving and restoring the layout ########################## def get_layout(self): """ Returns a TaskLayout (for the active task) that reflects the state of the window. """ if self._active_state: return self._window_backend.get_layout() return None def set_layout(self, layout): """ Applies a TaskLayout (which should be suitable for the active task) to the window. """ if self._active_state: self._window_backend.set_layout(layout) def reset_layout(self): """ Restores the active task's default TaskLayout. """ if self.active_task: self.set_layout(self.active_task.default_layout) def get_window_layout(self): """ Returns a TaskWindowLayout for the current state of the window. """ result = TaskWindowLayout(position=self.position, size=self.size) for state in self._states: if state == self._active_state: result.active_task = state.task.id layout = self._window_backend.get_layout() else: layout = state.layout.clone_traits() layout.id = state.task.id result.items.append(layout) return result def set_window_layout(self, window_layout): """ Applies a TaskWindowLayout to the window. """ # Set window size before laying it out. self.position = window_layout.position self.size = window_layout.size # Store layouts for the tasks, including the active task. for layout in window_layout.items: if isinstance(layout, basestring): continue state = self._get_state(layout.id) if state: state.layout = layout else: logger.warn("Cannot apply layout for task %r: task does not " "belong to the window." % task) # Attempt to activate the requested task. state = self._get_state(window_layout.get_active_task()) if state: # If the requested active task is already active, calling # ``activate`` is a no-op, so we must force a re-layout. if state == self._active_state: self._window_backend.set_layout(state.layout) else: self.activate_task(state.task) ########################################################################### # Protected 'TaskWindow' interface. ########################################################################### def _destroy_state(self, state): """ Destroy all controls associated with a Task state. """ # Notify the task that it is about to be destroyed. state.task.prepare_destroy() # Destroy action managers associated with the task, unless the task is # active, in which case this will be handled by our superclass. if state != self._active_state: if state.menu_bar_manager: state.menu_bar_manager.destroy() for tool_bar_manager in state.tool_bar_managers: tool_bar_manager.destroy() # Destroy all controls associated with the task. for dock_pane in state.dock_panes: dock_pane.destroy() state.central_pane.destroy() state.task.window = None def _get_pane_ring(self): """ Returns a list of visible panes ordered for focus switching. """ # Proceed clockwise through the dock areas. # TODO: Also take into account ordering within dock areas. panes = [] if self._active_state: layout = self.get_layout() panes.append(self.central_pane) for area in ('top', 'right', 'bottom', 'left'): item = getattr(layout, area) if item: panes.extend([ self.get_dock_pane(pane_item.id) for pane_item in item.iterleaves() ]) return panes def _get_state(self, id_or_task): """ Returns the TaskState that contains the specified Task, or None if no such state exists. """ for state in self._states: if state.task == id_or_task or state.task.id == id_or_task: return state return None #### Trait initializers ################################################### def __window_backend_default(self): return TaskWindowBackend(window=self) #### Trait property getters/setters ####################################### def _get_title(self): if self._title or self.active_task is None: return self._title return self.active_task.name def _set_title(self, title): self._title = title #### Trait change handlers ################################################ def __active_state_changed(self, state): if state is None: self.active_task = self.central_pane = None self.dock_panes = [] self.menu_bar_manager = self.status_bar_manager = None self.tool_bar_managers = [] else: self.active_task = state.task self.central_pane = state.central_pane self.dock_panes = state.dock_panes self.menu_bar_manager = state.menu_bar_manager self.status_bar_manager = state.status_bar_manager self.tool_bar_managers = state.tool_bar_managers @on_trait_change('central_pane.has_focus, dock_panes.has_focus') def _focus_updated(self, obj, name, old, new): if name == 'has_focus' and new: self.active_pane = obj @on_trait_change('_states[]') def _states_updated(self): self.tasks = [ state.task for state in self._states ] class TaskState(HasStrictTraits): """ An object used internally by TaskWindow to maintain the state associated with an attached Task. """ task = Instance(Task) layout = Instance(TaskLayout) initialized = Bool(False) central_pane = Instance(ITaskPane) dock_panes = List(IDockPane) menu_bar_manager = Instance(MenuBarManager) status_bar_manager = Instance(StatusBarManager) tool_bar_managers = List(ToolBarManager) def get_dock_pane(self, id): """ Returns the dock pane with the specified id, or None if no such dock pane exists. """ for pane in self.dock_panes: if pane.id == id: return pane return None pyface-4.1.0/pyface/tasks/editor.py0000644000175100001440000000020011674463363020205 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object Editor = toolkit_object('tasks.editor:Editor') pyface-4.1.0/pyface/tasks/editor_area_pane.py0000644000175100001440000000023211674463363022205 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object EditorAreaPane = toolkit_object('tasks.editor_area_pane:EditorAreaPane') pyface-4.1.0/pyface/tasks/i_editor_area_pane.py0000644000175100001440000001452411674463363022526 0ustar ischnellusers00000000000000# Standard library imports. import logging # Enthought library imports. from traits.api import Bool, Callable, Dict, Event, File, HasTraits, Instance, \ List, Str # Local imports. from i_editor import IEditor from i_task_pane import ITaskPane # Logger. logger = logging.getLogger(__name__) class IEditorAreaPane(ITaskPane): """ A central pane that contains tabbed editors. There are currently two implementations of this interface in Tasks. EditorAreaPane provides a simple, tabbed editor area. AdvancedEditorAreaPane additionally permits arbitrary splitting of the editor area so that editors can be displayed side-by-side. """ #### 'IEditorAreaPane' interface ########################################## # The currently active editor. active_editor = Instance(IEditor) # The list of all the visible editors in the pane. editors = List(IEditor) # A list of extensions for file types to accept via drag and drop. # Note: This functionality is provided because it is very common, but drag # and drop support is in general highly toolkit-specific. If more # sophisticated support is required, subclass an editor area implementation. file_drop_extensions = List(Str) # A file with a supported extension was dropped into the editor area. file_dropped = Event(File) # Whether to hide the tab bar when there is only a single editor. hide_tab_bar = Bool(False) ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def activate_editor(self, editor): """ Activates the specified editor in the pane. """ def add_editor(self, editor): """ Adds an editor to the pane. """ def create_editor(self, obj, factory=None): """ Creates an editor for an object. If a factory is specified, it will be used instead of the editor factory registry. Otherwise, this method will return None if a suitable factory cannot be found in the registry. Note that the editor is not added to the pane. """ def edit(self, obj, factory=None, use_existing=True): """ Edit an object. This is a convenience method that creates and adds an editor for the specified object. If 'use_existing' is set and the object is already being edited, then that editor will be activated and a new editor will not be created. Returns the (possibly new) editor for the object. """ def get_editor(self, obj): """ Returns the editor for an object. Returns None if the object is not being edited. """ def get_factory(self, obj): """ Returns an editor factory suitable for editing an object. Returns None if there is no such editor factory. """ def register_factory(self, factory, filter): """ Registers a factory for creating editors. The 'factory' parameter is a callabe of form: callable(editor_area=editor_area, obj=obj) -> IEditor Often, factory will be a class that implements the 'IEditor' interface. The 'filter' parameter is a callable of form: callable(obj) -> bool that indicates whether the editor factory is suitable for an object. If multiple factories apply to a single object, it is undefined which factory is used. On the other hand, multiple filters may be registered for a single factory, in which case only one must apply for the factory to be selected. """ def remove_editor(self, editor): """ Removes an editor from the pane. """ def unregister_factory(self, factory): """ Unregisters a factory for creating editors. """ class MEditorAreaPane(HasTraits): #### 'IEditorAreaPane' interface ########################################## active_editor = Instance(IEditor) editors = List(IEditor) file_drop_extensions = List(Str) file_dropped = Event(File) hide_tab_bar = Bool(False) #### Protected traits ##################################################### _factory_map = Dict(Callable, List(Callable)) ########################################################################### # 'IEditorAreaPane' interface. ########################################################################### def create_editor(self, obj, factory=None): """ Creates an editor for an object. """ if factory is None: factory = self.get_factory(obj) if factory is not None: return factory(editor_area=self, obj=obj) return None def edit(self, obj, factory=None, use_existing=True): """ Edit an object. """ if use_existing: # Is the object already being edited in the window? editor = self.get_editor(obj) if editor is not None: self.activate_editor(editor) return editor # If not, create an editor for it. editor = self.create_editor(obj, factory) if editor is None: logger.warn('Cannot create editor for obj %r', obj) else: self.add_editor(editor) self.activate_editor(editor) return editor def get_editor(self, obj): """ Returns the editor for an object. """ for editor in self.editors: if editor.obj == obj: return editor return None def get_factory(self, obj): """ Returns an editor factory suitable for editing an object. """ for factory, filters in self._factory_map.iteritems(): for filter in filters: # FIXME: We should swallow exceptions, but silently? try: if filter(obj): return factory except: pass return None def register_factory(self, factory, filter): """ Registers a factory for creating editors. """ self._factory_map.setdefault(factory, []).append(filter) def unregister_factory(self, factory): """ Unregisters a factory for creating editors. """ if factory in self._factory_map: del self._factory_map[factory] pyface-4.1.0/pyface/tasks/task_layout.py0000644000175100001440000001160111674463363021265 0ustar ischnellusers00000000000000# Standard library imports. from cStringIO import StringIO import sys # Enthought library imports. from traits.api import Either, Enum, HasStrictTraits, Int, List, Str, This class LayoutItem(HasStrictTraits): """ The base class for all Task-related layout objects. """ def __repr__(self): return self.pformat() def iterleaves(self): yield self def pargs(self): return [] def pformat(self, indent=0, multiline=False): """ Pretty-format the layout item. Returns a string. """ stream = StringIO() self.pstream(stream, indent, multiline) return stream.getvalue() def pprint(self, indent=0, multiline=False): """ Pretty-prints the layout item. """ self.pstream(sys.stdout, indent, multiline) def pstream(self, stream, indent=0, multiline=False): """ Pretty-formats the layout item to a stream. """ call = self.__class__.__name__ + '(' indent += len(call) stream.write(call) args = [ (None, arg) for arg in self.pargs() ] traits = [] for name, trait in sorted(self.traits().iteritems()): if not trait.pretty_skip and not trait.transient: value = getattr(self, name) if trait.default != value: traits.append((name, value)) traits.sort() args.extend(traits) for i, (name, value) in enumerate(args): arg_indent = indent if name: arg_indent += len(name) + 1 stream.write(name + '=') if isinstance(value, LayoutItem): value.pstream(stream, arg_indent, multiline) else: stream.write(repr(value)) if i < len(args) - 1: stream.write(',') if multiline: stream.write('\n' + indent * ' ') else: stream.write(' ') stream.write(')') class LayoutContainer(LayoutItem): """ The base class for all layout items that contain other layout items. """ items = List(pretty_skip=True) def __init__(self, *items, **traits): super(LayoutContainer, self).__init__(**traits) self.items = list(items) def iterleaves(self): for item in self.items: for leaf in item.iterleaves(): yield leaf def pargs(self): return self.items class PaneItem(LayoutItem): """ A pane in a Task layout. """ # The ID of the item. If the item refers to a TaskPane, this is the ID of # that TaskPane. id = Either(Str, Int, default='', pretty_skip=True) # The width of the pane in pixels. If not specified, the pane will be sized # according to its size hint. width = Int(-1) # The height of the pane in pixels. If not specified, the pane will be sized # according to its size hint. height = Int(-1) def __init__(self, id='', **traits): super(PaneItem, self).__init__(**traits) self.id = id def pargs(self): return [ self.id ] class Tabbed(LayoutContainer): """ A tab area in a Task layout. """ # A tabbed layout can only contain PaneItems as sub-items. Splitters and # other Tabbed layouts are not allowed. items = List(PaneItem, pretty_skip=True) # The ID of the TaskPane which is active in layout. If not specified, the # first pane is active. active_tab = Either(Str, Int, default='') class Splitter(LayoutContainer): """ A split area in a Task layout. """ # The orientation of the splitter. orientation = Enum('horizontal', 'vertical') # The sub-items of the splitter, which are PaneItems, Tabbed layouts, and # other Splitters. items = List(Either(PaneItem, Tabbed, This), pretty_skip=True) class HSplitter(Splitter): """ A convenience class for horizontal splitters. """ orientation = Str('horizontal') class VSplitter(Splitter): """ A convenience class for vertical splitters. """ orientation = Str('vertical') class DockLayout(LayoutItem): """ The layout for a main window's dock area. """ # The layouts for the task's dock panes. left = Either(PaneItem, Tabbed, Splitter) right = Either(PaneItem, Tabbed, Splitter) top = Either(PaneItem, Tabbed, Splitter) bottom = Either(PaneItem, Tabbed, Splitter) # Assignments of dock areas to the window's corners. By default, the top and # bottom dock areas extend into both of the top and both of the bottom # corners, respectively. top_left_corner = Enum('top', 'left') top_right_corner = Enum('top', 'right') bottom_left_corner = Enum('bottom', 'left') bottom_right_corner = Enum('bottom', 'right') class TaskLayout(DockLayout): """ The layout for a Task. """ # The ID of the task for which this is a layout. id = Str pyface-4.1.0/pyface/tasks/task.py0000644000175100001440000000573511674463363017703 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.action.api import StatusBarManager from traits.api import Callable, HasTraits, Instance, List, Str, \ Unicode # Local imports. from action.schema import MenuSchema, MenuBarSchema, ToolBarSchema from action.schema_addition import SchemaAddition from task_layout import TaskLayout class Task(HasTraits): """ A collection of pane, menu, tool bar, and status bar factories. The central class in the Tasks plugin, a Task is responsible for describing a set of user interface elements, as well as mediating between its view (a TaskWindow) and an application-specific model. """ # The task's identifier. id = Str # The task's user-visible name. name = Unicode # The default layout to use for the task. If not overridden, only the # central pane is displayed. default_layout = Instance(TaskLayout, ()) # A list of extra IDockPane factories for the task. These dock panes are # used in conjunction with the dock panes returned by create_dock_panes(). extra_dock_pane_factories = List(Callable) # The window to which the task is attached. Set by the framework. window = Instance('pyface.tasks.task_window.TaskWindow') #### Actions ############################################################## # The menu bar for the task. menu_bar = Instance(MenuBarSchema) # The (optional) status bar for the task. status_bar = Instance(StatusBarManager) # The list of tool bars for the tasks. tool_bars = List(ToolBarSchema) # A list of extra actions, groups, and menus that are inserted into menu # bars and tool bars constructed from the above schemas. extra_actions = List(SchemaAddition) ########################################################################### # 'Task' interface. ########################################################################### def activated(self): """ Called after the task has been activated in a TaskWindow. """ pass def create_central_pane(self): """ Create and return the central pane, which must implement ITaskPane. """ raise NotImplementedError def create_dock_panes(self): """ Create and return the task's dock panes (IDockPane instances). This method is called *after* create_central_pane() when the task is added to a TaskWindow. """ return [] def initialized(self): """ Called when the task is about to be activated in a TaskWindow for the first time. Override this method to perform any initialization that requires the Task's panes to be instantiated. Note that this method, when called, is called before activated(). """ pass def prepare_destroy(self): """ Called when the task is about to be removed from its TaskWindow. Override this method to perform any cleanup before the task's controls are destroyed. """ pass pyface-4.1.0/pyface/tasks/advanced_editor_area_pane.py0000644000175100001440000000033611674463363024037 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object AdvancedEditorAreaPane = toolkit_object('tasks.advanced_editor_area_pane:' 'AdvancedEditorAreaPane') pyface-4.1.0/pyface/tasks/i_editor.py0000644000175100001440000000552511674463363020534 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Any, Bool, Event, HasTraits, Interface, \ Instance, Property, Unicode, Vetoable, VetoableEvent, cached_property class IEditor(Interface): """ The base interface for all panes (central and dock) in a Task. """ # The editor's user-visible name. name = Unicode # The tooltip to show for the editor's tab, if any. tooltip = Unicode # The toolkit-specific control that represents the editor. control = Any # The object that the editor is editing. obj = Any # Has the editor's object been modified but not saved? dirty = Bool # The editor area to which the editor belongs. editor_area = Instance( 'pyface.tasks.i_editor_area_pane.IEditorAreaPane') # Is the editor active in the editor area? is_active = Bool # Does the editor currently have the focus? has_focus = Bool # Fired when the editor has been requested to close. closing = VetoableEvent # Fired when the editor has been closed. closed = Event ########################################################################### # 'IEditor' interface. ########################################################################### def close(self): """ Close the editor. """ def create(self, parent): """ Create and set the toolkit-specific control that represents the editor. """ def destroy(self): """ Destroy the toolkit-specific control that represents the editor. """ class MEditor(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ #### 'IEditor' interface ################################################## name = Unicode tooltip = Unicode control = Any obj = Any dirty = Bool(False) editor_area = Instance( 'pyface.tasks.i_editor_area_pane.IEditorAreaPane') is_active = Property(Bool, depends_on='editor_area.active_editor') has_focus = Bool(False) closing = VetoableEvent closed = Event ########################################################################### # 'IEditor' interface. ########################################################################### def close(self): """ Close the editor. """ if self.control is not None: self.closing = event = Vetoable() if not event.veto: self.editor_area.remove_editor(self) self.closed = True ########################################################################### # Private interface. ########################################################################### @cached_property def _get_is_active(self): if self.editor_area is not None: return self.editor_area.active_editor == self return False pyface-4.1.0/pyface/tasks/traits_dock_pane.py0000644000175100001440000000356311674463363022247 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import HasTraits, Instance from traitsui.api import UI # Local imports. from dock_pane import DockPane class TraitsDockPane(DockPane): """ A DockPane that displays a Traits UI View. """ #### TraitsDockPane interface ############################################# # The model object to view. If not specified, the pane is used instead. model = Instance(HasTraits) # The UI object associated with the Traits view, if it has been constructed. ui = Instance(UI) ########################################################################### # 'HasTraits' interface. ########################################################################### def trait_context(self): """ Use the model object for the Traits UI context, if appropriate. """ if self.model: return { 'object': self.model, 'pane': self } return super(TraitsDockPane, self).trait_context() ########################################################################### # 'ITaskPane' interface. ########################################################################### def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ # Destroy the Traits-generated control inside the dock control. self.ui.dispose() self.ui = None # Destroy the dock control. super(TraitsDockPane, self).destroy() ########################################################################### # 'IDockPane' interface. ########################################################################### def create_contents(self, parent): """ Create and return the toolkit-specific contents of the dock pane. """ self.ui = self.edit_traits(kind='subpanel', parent=parent) return self.ui.control pyface-4.1.0/pyface/tasks/dock_pane.py0000644000175100001440000000020711674463363020651 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object DockPane = toolkit_object('tasks.dock_pane:DockPane') pyface-4.1.0/pyface/tasks/contrib/0000755000175100001440000000000011674463363020015 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/contrib/README.txt0000644000175100001440000000034711674463363021517 0ustar ischnellusers00000000000000This subpackage is for Tasks components that are generic and re-usable, but not required for the proper functioning of Tasks. In other words, if this subpackage were to be completely removed, there should be no breakage in Tasks. pyface-4.1.0/pyface/tasks/contrib/__init__.py0000644000175100001440000000000011674463363022114 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/contrib/python_shell.py0000644000175100001440000000654111674463363023105 0ustar ischnellusers00000000000000""" Module defining a simple Python shell task. This task provides a view with a simple Python shell. This shouldn't be confused with a more full-featured shell, such as those provided by IPython. """ # Std lib imports import logging # Enthought library imports. from traits.api import Str, List, Dict, Instance from pyface.api import PythonShell, FileDialog, OK from pyface.tasks.api import Task, TaskPane from pyface.tasks.action.api import SMenuBar, SMenu, TaskAction # set up logging logger = logging.getLogger() class PythonShellPane(TaskPane): """ A Tasks Pane containing a Pyface PythonShell """ id = 'pyface.tasks.contrib.python_shell.pane' name = 'Python Shell' editor = Instance(PythonShell) bindings = List(Dict) commands = List(Str) def create(self, parent): """ Create the python shell task pane This wraps the standard pyface PythonShell """ logger.debug('PythonShellPane: creating python shell pane') self.editor = PythonShell(parent) self.control = self.editor.control # bind namespace logger.debug('PythonShellPane: binding variables') for binding in self.bindings: for name, value in binding.items(): self.editor.bind(name, value) # execute commands logger.debug('PythonShellPane: executing startup commands') for command in self.commands: self.editor.execute_command(command) logger.debug('PythonShellPane: created') def destroy(self): """ Destroy the python shell task pane """ logger.debug('PythonShellPane: destroying python shell pane') self.editor.destroy() self.control = self.editor = None logger.debug('PythonShellPane: destroyed') class PythonShellTask(Task): """ A task which provides a simple Python Shell to the user. """ # Task Interface id = 'pyface.tasks.contrib.python_shell' name = 'Python Shell' # The list of bindings for the shell bindings = List(Dict) # The list of commands to run on shell startup commands = List(Str) # the IPythonShell instance that we are interacting with pane = Instance(PythonShellPane) # Task Interface menu_bar = SMenuBar(SMenu(TaskAction(name='Open...', method='open', accelerator='Ctrl+O'), id='File', name='&File'), SMenu(id='View', name='&View')) def create_central_pane(self): """ Create a view pane with a Python shell """ logger.debug("Creating Python shell pane in central pane") self.pane = PythonShellPane(bindings=self.bindings, commands=self.commands) return self.pane # PythonShellTask API def open(self): """ Shows a dialog to open a file. """ logger.debug('PythonShellTask: opening file') dialog = FileDialog(parent=self.window.control, wildcard='*.py') if dialog.open() == OK: self._open_file(dialog.path) # Private API def _open_file(self, path): """ Execute the selected file in the editor's interpreter """ logger.debug('PythonShellTask: executing file "%s"' % path) self.pane.editor.execute_file(path)pyface-4.1.0/pyface/tasks/i_dock_pane.py0000644000175100001440000000471411674463363021170 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Bool, Enum, HasTraits, Str, Tuple # Local imports. from i_task_pane import ITaskPane class IDockPane(ITaskPane): """ A pane that is useful but unessential for a task. Dock panes are arranged around the central pane in dock areas, and can, in general, be moved, resized, and hidden by the user. """ # If enabled, the pane will have a button to close it, and a visibility # toggle button will be added to the View menu. Otherwise, the pane's # visibility will only be adjustable programmatically, though the 'visible' # attribute. closable = Bool(True) # The dock area in which the pane is currently present. dock_area = Enum('left', 'right', 'top', 'bottom') # Whether the pane can be detached from the main window. floatable = Bool(True) # Whether the pane is currently detached from the main window. floating = Bool(False) # Whether the pane can be moved from one dock area to another. movable = Bool(True) # The size of the dock pane. Note that this value is read-only. size = Tuple # Whether the pane is currently visible. visible = Bool(False) ########################################################################### # 'IDockPane' interface. ########################################################################### def create_contents(self, parent): """ Create and return the toolkit-specific contents of the dock pane. """ def hide(self): """ Convenience method to hide the dock pane. """ def show(self): """ Convenience method to show the dock pane. """ class MDockPane(HasTraits): """ Mixin containing common code for toolkit-specific implementations. """ #### 'IDockPane' interface ################################################ closable = Bool(True) dock_area = Enum('left', 'right', 'top', 'bottom') floatable = Bool(True) floating = Bool(False) movable = Bool(True) size = Tuple visible = Bool(False) ########################################################################### # 'IDockPane' interface. ########################################################################### def hide(self): """ Convenience method to hide the dock pane. """ self.visible = False def show(self): """ Convenience method to show the dock pane. """ self.visible = True pyface-4.1.0/pyface/tasks/action/0000755000175100001440000000000011674463363017632 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/action/task_action.py0000644000175100001440000001040711674463363022505 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.tasks.api import Editor, Task, TaskPane from traits.api import Bool, Instance, Property, Str, cached_property # Local imports. from listening_action import ListeningAction class TaskAction(ListeningAction): """ An Action that makes a callback to a Task. Note that this is a convenience class. Actions associated with a Task need not inherit TaskAction, although they must, of course, inherit Action. """ #### ListeningAction interface ############################################ object = Property(depends_on='task') #### TaskAction interface ################################################# # The Task with which the action is associated. Set by the framework. task = Instance(Task) ########################################################################### # Protected interface. ########################################################################### def _get_object(self): return self.task class TaskWindowAction(TaskAction): """ An Action that makes a callback to a Task's window. """ #### ListeningAction interface ############################################ object = Property(depends_on='task.window') ########################################################################### # Protected interface. ########################################################################### def _get_object(self): if self.task: return self.task.window return None class CentralPaneAction(TaskAction): """ An Action that makes a callback to a Task's central pane. """ #### ListeningAction interface ############################################ object = Property(depends_on='central_pane') #### CentralPaneAction interface ########################################## # The central pane with which the action is associated. central_pane = Property(Instance(TaskPane), depends_on='task') ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_central_pane(self): if self.task: return self.task.window.get_central_pane(self.task) return None def _get_object(self): return self.central_pane class DockPaneAction(TaskAction): """ An Action the makes a callback to one of a Task's dock panes. """ #### ListeningAction interface ############################################ object = Property(depends_on='dock_pane') #### DockPaneAction interface ############################################# # The dock pane with which the action is associated. Set by the framework. dock_pane = Property(Instance(TaskPane), depends_on='task') # The ID of the dock pane with which the action is associated. dock_pane_id = Str ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_dock_pane(self): if self.task: return self.task.window.get_dock_pane(self.dock_pane_id, self.task) return None def _get_object(self): return self.dock_pane class EditorAction(CentralPaneAction): """ An action that makes a callback to the active editor in an editor pane. """ #### ListeningAction interface ############################################ object = Property(depends_on='active_editor') #### EditorAction interface ############################################### # The active editor in the central pane with which the action is associated. active_editor = Property(Instance(Editor), depends_on='central_pane.active_editor') ########################################################################### # Protected interface. ########################################################################### @cached_property def _get_active_editor(self): if self.central_pane is not None: return self.central_pane.active_editor return None def _get_object(self): return self.active_editor pyface-4.1.0/pyface/tasks/action/schema.py0000644000175100001440000000676211674463363021457 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.action.api import Action, ActionItem, Group, \ MenuManager, MenuBarManager, ToolBarManager from traits.api import Bool, Callable, Enum, HasTraits, Instance, \ List, Str, Trait, Tuple, Unicode # Trait definitions. SubSchema = Trait(None, Action, ActionItem, Group, MenuManager, Instance('pyface.tasks.action.schema.GroupSchema'), Instance('pyface.tasks.action.schema.MenuSchema')) class Schema(HasTraits): """ The abstract base class for all Tasks action schemas. """ # The schema's identifier (unique within its parent schema). id = Str # The list of sub-items in the schema. These items can be other # (non-top-level) schema or concrete instances from the Pyface API. items = List(SubSchema) def __init__(self, *items, **traits): """ Creates a new schema. """ super(Schema, self).__init__(**traits) self.items.extend(items) def create(self, children): """ Create the appropriate pyface.action instance with the specified child items. """ raise NotImplementedError class GroupSchema(Schema): """ A schema for a Pyface Group. """ # A factory for instantiating a pyface Group. group_factory = Callable(Group) # Does the group require a separator when it is visualized? separator = Bool(True) def create(self, children): traits = dict(id=self.id, separator=self.separator) return self.group_factory(*children, **traits) class MenuSchema(Schema): """ A schema for a Pyface MenuManager. """ # The menu's user visible name. name = Unicode # A factory for instantiating a pyface MenuManager. menu_manager_factory = Callable(MenuManager) def create(self, children): traits = dict(id=self.id, name=self.name) return self.menu_manager_factory(*children, **traits) class MenuBarSchema(Schema): """ A schema for a Pyface MenuBarManager. """ # Assign a default ID for menu bar schemas. id = 'MenuBar' # A factory for instantiating a pyface MenuBarManager. menu_bar_manager_factory = Callable(MenuBarManager) def create(self, children): traits = dict(id=self.id) return self.menu_bar_manager_factory(*children, **traits) class ToolBarSchema(Schema): """ A schema for a Pyface ToolBarManager. """ # Assign a default ID for tool bar schemas. id = 'ToolBar' # The tool bar's user visible name. Note that this name may not be used on # all platforms. name = Unicode('Tool Bar') # The size of tool images (width, height). image_size = Tuple((16, 16)) # The orientation of the toolbar. orientation = Enum('horizontal', 'vertical') # Should we display the horizontal divider? show_divider = Bool(True) # Should we display the name of each tool bar tool under its image? show_tool_names = Bool(True) # A factory for instantiating a pyfce ToolBarManager tool_bar_manager_factory = Callable(ToolBarManager) def create(self, children): traits = dict(id=self.id, name=self.name, image_size=self.image_size, orientation=self.orientation, show_divider=self.show_divider, show_tool_names=self.show_tool_names) return self.tool_bar_manager_factory(*children, **traits) # Convenience abbreviations. SGroup = GroupSchema SMenu = MenuSchema SMenuBar = MenuBarSchema SToolBar = ToolBarSchema pyface-4.1.0/pyface/tasks/action/task_action_controller.py0000644000175100001440000000274511674463363024756 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.action.api import ActionController from traits.api import Instance # Local imports. from pyface.tasks.task import Task from task_action import TaskAction class TaskActionController(ActionController): """ An action controller for menu and tool bars. The controller is used to 'hook' the invocation of every action on the menu and tool bars. This is done so that additional, Task-specific information can be added to action events. Currently, we attach a reference to the Task. """ #### TaskActionController interface ####################################### # The task that this is the controller for. task = Instance(Task) ########################################################################### # 'ActionController' interface. ########################################################################### def perform(self, action, event): """ Control an action invocation. """ event.task = self.task return action.perform(event) def add_to_menu(self, item): """ Called when an action item is added to a menu/menubar. """ action = item.item.action if isinstance(action, TaskAction): action.task = self.task def add_to_toolbar(self, item): """ Called when an action item is added to a toolbar. """ action = item.item.action if isinstance(action, TaskAction): action.task = self.task pyface-4.1.0/pyface/tasks/action/api.py0000644000175100001440000000077211674463363020763 0ustar ischnellusers00000000000000# Local imports. from dock_pane_toggle_group import DockPaneToggleGroup from schema import GroupSchema, MenuSchema, MenuBarSchema, ToolBarSchema, \ SGroup, SMenu, SMenuBar, SToolBar from schema_addition import SchemaAddition from task_action import CentralPaneAction, DockPaneAction, EditorAction, \ TaskAction, TaskWindowAction from task_action_controller import TaskActionController from task_action_manager_builder import TaskActionManagerBuilder from task_toggle_group import TaskToggleGroup pyface-4.1.0/pyface/tasks/action/task_action_manager_builder.py0000644000175100001440000001207511674463363025710 0ustar ischnellusers00000000000000# Standard library imports. from collections import defaultdict import logging # Enthought library imports. from pyface.action.api import ActionController, ActionManager from traits.api import HasTraits, Instance # Local imports. from pyface.tasks.task import Task from pyface.tasks.topological_sort import before_after_sort from schema import Schema, ToolBarSchema from schema_addition import SchemaAddition # Logging. logger = logging.getLogger(__name__) class TaskActionManagerBuilder(HasTraits): """ Builds menu bars and tool bars from menu bar and tool bar schema, along with any additions provided by the task. """ # The controller to assign to the menubar and toolbars. controller = Instance(ActionController) # The Task to build menubars and toolbars for. task = Instance(Task) ########################################################################### # 'TaskActionManagerBuilder' interface. ########################################################################### def create_action_manager(self, schema): """ Create a manager for the given schema using the task's additions. """ additions_map = defaultdict(list) for addition in self.task.extra_actions: if addition.path: additions_map[addition.path].append(addition) manager = self._create_action_manager_recurse(schema, additions_map) manager.controller = self.controller return manager def create_menu_bar_manager(self): """ Create a menu bar manager from the task's menu bar schema and additions. """ if self.task.menu_bar: return self.create_action_manager(self.task.menu_bar) return None def create_tool_bar_managers(self): """ Create tool bar managers from the tasks's tool bar schemas and additions. """ schemas = self.task.tool_bars[:] for addition in self.task.extra_actions: if not addition.path: schema = addition.factory() if isinstance(schema, ToolBarSchema): schemas.append(schema) else: logger.error('Invalid top-level schema addition: %r. Only ' 'ToolBar schemas can be path-less.', schema) return [ self.create_action_manager(schema) for schema in self._get_ordered_schemas(schemas) ] def prepare_item(self, item, path): """ Called immediately after a concrete Pyface item has been created (or, in the case of items that are not produced from schemas, immediately before they are processed). This hook can be used to perform last-minute transformations or configuration. Returns a concrete Pyface item. """ return item ########################################################################### # Private interface. ########################################################################### def _create_action_manager_recurse(self, schema, additions, path=''): """ Recursively create a manager for the given schema and additions map. """ # Compute the new action path. if path: path += '/' path += schema.id # Determine the order of the items at this path. items = schema.items if additions[path]: items = self._get_ordered_schemas(items + additions[path]) # Create the actual children by calling factory items. children = [] for item in items: # Unpack additions first, since they may themselves be schemas. if isinstance(item, SchemaAddition): item = item.factory() if isinstance(item, Schema): item = self._create_action_manager_recurse(item,additions,path) else: item = self.prepare_item(item, path+'/'+item.id) if isinstance(item, ActionManager): # Give even non-root action managers a reference to the # controller so that custom Groups, MenuManagers, etc. can get # access to their Tasks. item.controller = self.controller children.append(item) # Finally, create the pyface.action instance for this schema. return self.prepare_item(schema.create(children), path) def _get_ordered_schemas(self, schemas): begin = [] middle = [] end = [] for schema in schemas: absolute_position = getattr(schema, 'absolute_position', None) if absolute_position is None: middle.append(schema) elif absolute_position == 'last': end.append(schema) else: begin.append(schema) return before_after_sort(begin + middle + end) #### Trait initializers ################################################### def _controller_default(self): from task_action_controller import TaskActionController return TaskActionController(task=self.task) pyface-4.1.0/pyface/tasks/action/schema_addition.py0000644000175100001440000000373211674463363023324 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Callable, HasTraits, Str, Enum class SchemaAddition(HasTraits): """ An addition to an existing menu bar or tool bar schema. """ # The schema addition's identifier. This optional, but if left unspecified, # other schema additions will be unable to refer to this one. id = Str # A callable to create the item. Should have the following signature: # callable() -> Action | ActionItem | Group | MenuManager | # GroupSchema | MenuSchema # If the result is a schema, it will itself admit of extension by other # additions. If not, the result will be fixed. factory = Callable # A forward-slash-separated path through the action hierarchy to the menu # to add the action, group or menu to. For example: # - To add an item to the menu bar: ``path = "MenuBar"`` # - To add an item to the tool bar: ``path = "ToolBar"`` # - To add an item to a sub-menu: ``path = "MenuBar/File/New"`` path = Str # The item appears after the item with this ID. # - for groups, this is the ID of another group. # - for menus and actions, this is the ID of another menu or action. after = Str # The action appears before the item with this ID. # - for groups, this is the ID of another group. # - for menus and actions, this is the ID of another menu or action. before = Str # The action appears at the absolute specified position first or last. # This is useful for example to keep the File menu the first menu # in a menubar, the help menu the last etc. # If multiple actions in a schema have absolute_position 'first', # they will appear in the same order specified; likewise for 'last'. # Absolute positions are subject to subsequent topological reordering # if 'after' or 'before' traits are also specified, hence it must not # be used alongwith after and before traits absolute_position = Enum(None, 'first', 'last') pyface-4.1.0/pyface/tasks/action/task_toggle_group.py0000644000175100001440000000575711674463363023741 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.action.api import Action, ActionItem, Group from traits.api import Any, Bool, List, Instance, Property, Unicode, \ on_trait_change # Local imports. from pyface.tasks.task import Task from pyface.tasks.task_window import TaskWindow class TaskToggleAction(Action): """ An action for activating a task. """ #### 'Action' interface ################################################### name = Property(Unicode, depends_on='task.name') style = 'toggle' tooltip = Property(Unicode, depends_on='name') #### 'TaskActivateAction' interface ####################################### task = Instance(Task) ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event=None): window = self.task.window window.activate_task(self.task) ########################################################################### # Private interface. ########################################################################### def _get_name(self): return self.task.name def _get_tooltip(self): return u'Switch to the %s task.' % self.name @on_trait_change('task.window.active_task') def _update_checked(self): window = self.task.window self.checked = window is not None and window.active_task == self.task class TaskToggleGroup(Group): """ A menu for changing the active task in a task window. """ #### 'ActionManager' interface ############################################ id = 'TaskToggleGroup' items = List #### 'TaskChangeMenuManager' interface #################################### # The ActionManager to which the group belongs. manager = Any # The window that contains the group. window = Instance(TaskWindow) ########################################################################### # Private interface. ########################################################################### def _get_items(self): items = [] for task in self.window.tasks: action = TaskToggleAction(task=task) items.append(ActionItem(action=action)) return items def _rebuild(self): # Clear out the old group, then build the new one. self.destroy() self.items = self._get_items() # Inform our manager that it needs to be rebuilt. self.manager.changed = True #### Trait initializers ################################################### def _items_default(self): self.window.on_trait_change(self._rebuild, 'tasks[]') return self._get_items() def _manager_default(self): manager = self while isinstance(manager, Group): manager = manager.parent return manager def _window_default(self): return self.manager.controller.task.window pyface-4.1.0/pyface/tasks/action/dock_pane_toggle_group.py0000644000175100001440000000472211674463363024711 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.action.api import Action, ActionItem, Group from traits.api import Bool, Instance, List, Property, Unicode, on_trait_change # Local imports. from pyface.tasks.i_dock_pane import IDockPane class DockPaneToggleAction(Action): """ An Action for toggling the visibily of a dock pane. """ #### 'DockPaneToggleAction' interface ##################################### dock_pane = Instance(IDockPane) #### 'Action' interface ################################################### name = Property(Unicode, depends_on='dock_pane.name') style = 'toggle' tooltip = Property(Unicode, depends_on='name') ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event=None): if self.dock_pane: self.dock_pane.visible = not self.dock_pane.visible ########################################################################### # Protected interface. ########################################################################### def _get_name(self): return self.dock_pane.name def _get_tooltip(self): return u'Toggles the visibilty of the %s pane.' % self.name @on_trait_change('dock_pane.visible') def _update_checked(self): self.checked = self.dock_pane.visible @on_trait_change('dock_pane.closable') def _update_visible(self): self.visible = self.dock_pane.closable class DockPaneToggleGroup(Group): """ A Group for toggling the visibility of a task's dock panes. """ #### 'Group' interface #################################################### id = 'DockPaneToggleGroup' items = List ########################################################################### # Protected interface. ########################################################################### def _items_default(self): """ Create a DockPaneToggleAction for each dock pane. """ manager = self while isinstance(manager, Group): manager = manager.parent task = manager.controller.task items = [] for dock_pane in task.window.get_dock_panes(task): action = DockPaneToggleAction(dock_pane=dock_pane) items.append(ActionItem(action=action)) items.sort(key=lambda item: item.action.name) return items pyface-4.1.0/pyface/tasks/action/__init__.py0000644000175100001440000000000011674463363021731 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/action/listening_action.py0000644000175100001440000000762111674463363023543 0ustar ischnellusers00000000000000# Standard library imports. import logging # Enthought library imports. from pyface.action.api import Action from traits.api import Any, Str # Logging. logger = logging.getLogger(__name__) class ListeningAction(Action): """ An Action that listens and makes a callback to an object. """ #### ListeningAction interface ############################################ # The (extended) name of the method to call. By default, the on_perform # function will be called with the event. method = Str # The (extended) name of the attribute that determines whether the action is # enabled. By default, the action is always enabled when an object is set. enabled_name = Str # The (extended) name of the attribute that determines whether the action is # visible. By default, the action is always visible. visible_name = Str # The object to which the names above apply. object = Any ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event=None): """ Call the appropriate function. """ if self.method != '': method = self._get_attr(self.object, self.method) if method: method() else: super(ListeningAction, self).perform(event) ########################################################################### # Protected interface. ########################################################################### def _get_attr(self, obj, name, default=None): try: for attr in name.split('.'): # Perform the access in the Trait name style: if the object is # None, assume it simply hasn't been initialized and don't show # the warning. if obj is None: return default else: obj = getattr(obj, attr) except AttributeError: logger.error("Did not find name %r on %r" % (attr, obj)) return default return obj #### Trait change handlers ################################################ def _enabled_name_changed(self, old, new): obj = self.object if obj is not None: if old: obj.on_trait_change(self._enabled_update, old, remove=True) if new: obj.on_trait_change(self._enabled_update, new) self._enabled_update() def _visible_name_changed(self, old, new): obj = self.object if obj is not None: if old: obj.on_trait_change(self._visible_update, old, remove=True) if new: obj.on_trait_change(self._visible_update, new) self._visible_update() def _object_changed(self, old, new): for kind in ('enabled', 'visible'): method = getattr(self, '_%s_update' % kind) name = getattr(self, '%s_name' % kind) if name: if old: old.on_trait_change(method, name, remove=True) if new: new.on_trait_change(method, name) method() def _enabled_update(self): if self.enabled_name: if self.object: self.enabled = bool(self._get_attr(self.object, self.enabled_name, False)) else: self.enabled = False else: self.enabled = bool(self.object) def _visible_update(self): if self.visible_name: if self.object: self.visible = bool(self._get_attr(self.object, self.visible_name, False)) else: self.visible = False else: self.visible = True pyface-4.1.0/pyface/tasks/__init__.py0000644000175100001440000000000011674463363020454 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/tasks/topological_sort.py0000644000175100001440000000635311674463363022321 0ustar ischnellusers00000000000000# Standard library imports. from collections import OrderedDict, defaultdict import logging # Logging. logger = logging.getLogger(__name__) def before_after_sort(items): """ Sort a sequence of items with 'before', 'after', and 'id' attributes. The sort is topological. If an item does not specify a 'before' or 'after', it is placed after the preceding item. If a cycle is found in the dependecies, a warning is logged and the order of the items is undefined. """ # Handle a degenerate case for which the logic below will fail (because # prev_item will not be set). if len(items) < 2: return items # Build a set of pairs representing the graph. item_map = dict((item.id, item) for item in items if item.id) pairs = [] prev_item = None for item in items: # Attempt to use 'before' and 'after' to make pairs. new_pairs = [] if hasattr(item, 'before') and item.before: parent, child = item, item_map.get(item.before) if child: new_pairs.append((parent, child)) if hasattr(item, 'after') and item.after: parent, child = item_map.get(item.after), item if parent: new_pairs.append((parent, child)) # If we have any pairs, use them. Otherwise, use the previous unmatched # item as a parent, if possible. if new_pairs: pairs.extend(new_pairs) else: if prev_item: pairs.append((prev_item, item)) prev_item = item # Now perform the actual sort. result, has_cycle = topological_sort(pairs) if has_cycle: logger.warning('Cycle in before/after sort for items %r', items) return result def topological_sort(pairs): """ Topologically sort a list of (parent, child) pairs. Returns a tuple containing the list of elements sorted in dependency order (parent to child order), if possible, and a boolean indicating whether the graph contains cycles. A simple algorithm due to Kahn, in which vertices are chosen from the graph in the same order as the eventual topological sort, is used. Note that this implementation is stable in the following sense: if we have the input list [..., (parent, child1), ..., (parent, child2), ...], then child1 will be before child2 in the output list (if there there is no additional dependency forcing another ordering). """ # Represent the graph in dictionary form. graph = OrderedDict() num_parents = defaultdict(int) for parent, child in pairs: graph.setdefault(parent, []).append(child) num_parents[child] += 1 # Begin with the parent-less items. result = [ item for item in graph if num_parents[item] == 0 ] # Descend through graph, removing parents as we go. for parent in result: if graph.has_key(parent): for child in graph[parent]: num_parents[child] -= 1 if num_parents[child] == 0: result.append(child) del graph[parent] # If there's a cycle, just throw in whatever is left over. has_cycle = bool(graph) if has_cycle: result.extend(graph.keys()) return result, has_cycle pyface-4.1.0/pyface/tasks/i_advanced_editor_area_pane.py0000644000175100001440000000313211674463363024344 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Interface # Local imports. from i_editor_area_pane import IEditorAreaPane class IAdvancedEditorAreaPane(IEditorAreaPane): """ A splitable central pane that contains tabbed editors. """ ########################################################################### # 'IAdvancedEditorAreaPane' interface. ########################################################################### def get_layout(self): """ Returns a LayoutItem that reflects the current state of the editors. Because editors do not have IDs, they are identified by their index in the list of editors. For example, PaneItem(0) refers to the first editor. If there are no open editors, returns None. """ def set_layout(self, layout): """ Applies a LayoutItem to the editors in the pane. The layout should have panes with IDs as described in ``get_layout()``. For example, if one wanted to open two editors side by side, with the first to the left of the right, something like this would appropriate:: editor_area.edit(File('foo.py')) editor_area.edit(File('bar.py')) editor_area.set_layout(VSplitter(PaneItem(0), PaneItem(1))) Editors that are not included in the layout will be tabbified with other editors in an undefined manner. If the layout is None, this method is a no-op. Hence it is always safe to call this method with the results of ``get_layout()``. """ pyface-4.1.0/pyface/tasks/traits_editor.py0000644000175100001440000000304711674463363021607 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import HasTraits, Instance from traitsui.api import UI # Local imports. from editor import Editor class TraitsEditor(Editor): """ An Editor that displays a Traits UI View. """ #### TraitsEditor interface ############################################### # The model object to view. If not specified, the editor is used instead. model = Instance(HasTraits) # The UI object associated with the Traits view, if it has been constructed. ui = Instance(UI) ########################################################################### # 'HasTraits' interface. ########################################################################### def trait_context(self): """ Use the model object for the Traits UI context, if appropriate. """ if self.model: return { 'object': self.model, 'editor': self } return super(TraitsEditor, self).trait_context() ########################################################################### # 'IEditor' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific contents of the editor. """ self.ui = self.edit_traits(kind='subpanel', parent=parent) self.control = self.ui.control def destroy(self): """ Destroy the toolkit-specific control that represents the editor. """ self.control = None self.ui.dispose() self.ui = None pyface-4.1.0/pyface/tasks/i_task_window_backend.py0000644000175100001440000000502611674463363023242 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import Any, DelegatesTo, HasTraits, Instance, Interface, \ implements class ITaskWindowBackend(Interface): """ The TaskWindow layout interface. TaskWindow delegates to an ITaskWindowBackend object for toolkit-specific layout functionality. """ # The root control of the TaskWindow to which the layout belongs. control = Any # The TaskWindow to which the layout belongs. window = Instance('pyface.tasks.task_window.TaskWindow') ########################################################################### # 'ITaskWindowBackend' interface. ########################################################################### def create_contents(self, parent): """ Create and return the TaskWindow's contents. (See IWindow.) """ def destroy(self): """ Destroy the backend. Note that TaskWindow will destroy the widget created in create_contents, but this method may be used to perform additional cleanup. """ def hide_task(self, state): """ Assuming the specified TaskState is active, hide its controls. """ def show_task(self, state): """ Assumming no task is currently active, show the controls of the specified TaskState. """ #### Methods for saving and restoring the layout ########################## def get_layout(self): """ Returns a TaskLayout for the current state of the window. """ def set_layout(self, layout): """ Applies a TaskLayout (which should be suitable for the active task) to the window. """ class MTaskWindowBackend(HasTraits): """ Mixin containing common coe for toolkit-specific implementations. """ implements(ITaskWindowBackend) #### 'ITaskWindowBackend' interface ####################################### control = DelegatesTo('window') window = Instance('pyface.tasks.task_window.TaskWindow') ########################################################################### # 'ITaskWindowBackend' interface. ########################################################################### def create_contents(self, parent): raise NotImplementedError def destroy(self): pass def hide_task(self, state): raise NotImplementedError def show_task(self, state): raise NotImplementedError def get_layout(self): raise NotImplementedError def set_layout(self, layout): raise NotImplementedError pyface-4.1.0/pyface/tasks/task_pane.py0000644000175100001440000000020711674463363020673 0ustar ischnellusers00000000000000# Import the toolkit specific version. from pyface.toolkit import toolkit_object TaskPane = toolkit_object('tasks.task_pane:TaskPane') pyface-4.1.0/pyface/tasks/traits_task_pane.py0000644000175100001440000000307011674463363022262 0ustar ischnellusers00000000000000# Enthought library imports. from traits.api import HasTraits, Instance from traitsui.api import UI # Local imports. from task_pane import TaskPane class TraitsTaskPane(TaskPane): """ A TaskPane that displays a Traits UI View. """ #### TraitsTaskPane interface ############################################# # The model object to view. If not specified, the pane is used instead. model = Instance(HasTraits) # The UI object associated with the Traits view, if it has been constructed. ui = Instance(UI) ########################################################################### # 'HasTraits' interface. ########################################################################### def trait_context(self): """ Use the model object for the Traits UI context, if appropriate. """ if self.model: return { 'object': self.model, 'pane': self } return super(TraitsTaskPane, self).trait_context() ########################################################################### # 'ITaskPane' interface. ########################################################################### def create(self, parent): """ Create and set the toolkit-specific control that represents the pane. """ self.ui = self.edit_traits(kind='subpanel', parent=parent) self.control = self.ui.control def destroy(self): """ Destroy the toolkit-specific control that represents the pane. """ self.ui.dispose() self.control = self.ui = None pyface-4.1.0/pyface/expandable_panel.py0000644000175100001440000001146211674463363021070 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A Layered panel. """ from __future__ import absolute_import # Major package imports. import wx from traits.api import Instance # Local imports. from .expandable_header import ExpandableHeader from .image_resource import ImageResource from .widget import Widget class ExpandablePanel(Widget): """ An expandable panel. """ # The default style. STYLE = wx.CLIP_CHILDREN collapsed_image = Instance(ImageResource, ImageResource('mycarat1')) expanded_image = Instance(ImageResource, ImageResource('mycarat2')) ########################################################################### # 'object' interface. ########################################################################### def __init__(self, parent, **traits): """ Creates a new LayeredPanel. """ # Base class constructor. super(ExpandablePanel, self).__init__(**traits) # Create the toolkit-specific control that represents the widget. self.control = self._create_control(parent) # The layers in the panel. # # { str name : wx.Window layer } self._layers = {} self._headers = {} return ########################################################################### # 'Expandale' interface. ########################################################################### def add_panel(self, name, layer): """ Adds a layer with the specified name. All layers are hidden when they are added. Use 'show_layer' to make a layer visible. """ parent = self.control sizer = self.control.GetSizer() # Add the heading text. header = self._create_header(parent, text=name) sizer.Add(header, 0, wx.EXPAND) # Add the layer to our sizer. sizer.Add(layer, 1, wx.EXPAND) # All layers are hidden when they are added. Use 'show_layer' to make # a layer visible. sizer.Show(layer, False) # fixme: Should we warn if a layer is being overridden? self._layers[name] = layer return layer def remove_panel(self, name): """ Removes a layer and its header from the container.""" if not self._layers.has_key(name): return sizer = self.control.GetSizer() panel = self._layers[name] header = self._headers[name] sizer.Remove(panel) panel.Destroy() sizer.Remove(header) header.Destroy() sizer.Layout() return ########################################################################### # Private interface. ########################################################################### def _create_control(self, parent): """ Create the toolkit-specific control that represents the widget. """ panel = wx.Panel(parent, -1, style=self.STYLE) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) return panel def _create_header(self, parent, text): """ Creates a panel header. """ sizer = wx.BoxSizer(wx.HORIZONTAL) panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) panel.SetSizer(sizer) panel.SetAutoLayout(True) # Add the panel header. heading = ExpandableHeader(panel, self, title = text) sizer.Add(heading.control, 1, wx.EXPAND) heading.on_trait_change(self._on_button, 'panel_expanded') # Resize the panel to match the sizer's minimum size. sizer.Fit(panel) # hang onto it for when we destroy self._headers[text] = panel return panel #### wx event handlers #################################################### def _on_button(self, event): """ called when one of the expand/contract buttons is pressed. """ header = event name = header.title visible = header.state sizer = self.control.GetSizer() sizer.Show(self._layers[name], visible) sizer.Layout() # fixme: Errrr, maybe we can NOT do this! w, h = self.control.GetSize() self.control.SetSize((w+1, h+1)) self.control.SetSize((w, h)) pyface-4.1.0/pyface/filter.py0000644000175100001440000000426411674463363017075 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Base class for all filters. """ # Enthought library imports. from traits.api import HasPrivateTraits class Filter(HasPrivateTraits): """ Abstract base class for all filters. """ ########################################################################### # 'Filter' interface. ########################################################################### def filter(self, widget, parent, nodes): """ Filters a list of nodes. 'widget'is the widget that we are filtering nodes for. 'parent'is the parent node. 'nodes' is the list of nodes to filter. Returns a list containing only those nodes for which 'select' returns True. """ return [e for e in nodes if self.select(widget, parent, e)] def select(self, widget, parent, node): """ Returns True if the node is 'allowed' (ie. NOT filtered). 'widget' is the widget that we are filtering nodes for. 'parent' is the parent node. 'node' is the node to select. By default we return True. """ return True def is_filter_trait(self, node, trait_name): """ Is the filter affected by changes to a node's trait? 'node' is the node. 'trait_name' is the name of the trait. Returns True if the filter would be affected by changes to the trait named 'trait_name' on the specified node. By default we return False. """ return False #### EOF ###################################################################### pyface-4.1.0/pyface/i_python_shell.py0000644000175100001440000000556411674463363020634 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for an interactive Python shell. """ # Enthought library imports. from traits.api import Event # Local imports. from key_pressed_event import KeyPressedEvent from i_widget import IWidget class IPythonShell(IWidget): """ The interface for an interactive Python shell. """ #### 'IPythonShell' interface ############################################# # A command has been executed. command_executed = Event # A key has been pressed. key_pressed = Event(KeyPressedEvent) ########################################################################### # 'IPythonShell' interface. ########################################################################### def interpreter(self): """ Returns the code.InteractiveInterpreter instance. """ def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ def execute_command(self, command, hidden=True): """ Execute a command in the interpreter. If 'hidden' is True then nothing is shown in the shell - not even a blank line. """ def execute_file(self, path, hidden=True): """ Execute a file in the interpeter. If 'hidden' is True then nothing is shown in the shell - not even a blank line. """ class MPythonShell(object): """ The mixin class that contains common code for toolkit specific implementations of the IPythonShell interface. Implements: bind(), _on_command_executed() """ ########################################################################### # 'IPythonShell' interface. ########################################################################### def bind(self, name, value): """ Binds a name to a value in the interpreter's namespace. """ self.interpreter().locals[name] = value ########################################################################### # Private interface. ########################################################################### def _on_command_executed(self): """ Called when a command has been executed in the shell. """ self.command_executed = self #### EOF ###################################################################### pyface-4.1.0/pyface/i_gui.py0000644000175100001440000001134511674463363016702 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface of a pyface GUI. """ # Standard library imports. import logging import os # Enthought library imports. from traits.etsconfig.api import ETSConfig from traits.api import Bool, Interface, Unicode # Logging. logger = logging.getLogger(__name__) class IGUI(Interface): """ The interface of a pyface GUI. """ #### 'GUI' interface ###################################################### # Is the GUI busy (i.e. should the busy cursor, often an hourglass, be # displayed)? busy = Bool(False) # Has the GUI's event loop been started? started = Bool(False) # A directory on the local file system that we can read and write to at # will. This is used to persist layout information etc. Note that # individual toolkits will have their own directory. state_location = Unicode ########################################################################### # 'object' interface. ########################################################################### def __init__(self, splash_screen=None): """ Initialise a new GUI. splash_screen is an optional splash screen that will be displayed until the event loop is started. """ ########################################################################### # 'GUI' class interface. ########################################################################### def allow_interrupt(): """ Override the SIGINT handler to ensure the process can be interrupted. This prevents GUI toolkits from swallowing KeyboardInterrupt exceptions. Warning: do not call this method if you intend your application to be run interactively. """ allow_interrupt = staticmethod(allow_interrupt) def invoke_after(cls, millisecs, callable, *args, **kw): """ Call a callable after a specific delay in the main GUI thread. """ invoke_after = classmethod(invoke_after) def invoke_later(cls, callable, *args, **kw): """ Call a callable in the main GUI thread. """ invoke_later = classmethod(invoke_later) def set_trait_after(cls, millisecs, obj, trait_name, new): """ Sets a trait after a specific delay in the main GUI thread. """ set_trait_after = classmethod(set_trait_after) def set_trait_later(cls, obj, trait_name, new): """ Sets a trait in the main GUI thread. """ set_trait_later = classmethod(set_trait_later) def process_events(allow_user_events=True): """ Process any pending GUI events. If allow_user_events is False then user generated events are not processed. """ process_events = staticmethod(process_events) def set_busy(busy=True): """Specify if the GUI is busy. If `True` is passed, the cursor is set to a 'busy' cursor. Passing `False` will reset the cursor to the default. """ set_busy = staticmethod(set_busy) ########################################################################### # 'GUI' interface. ########################################################################### def start_event_loop(self): """ Start the GUI event loop. """ def stop_event_loop(self): """ Stop the GUI event loop. """ class MGUI(object): """ The mixin class that contains common code for toolkit specific implementations of the IGUI interface. Implements: _default_state_location() """ def allow_interrupt(): """ Override the SIGINT handler to ensure the process can be interrupted. This prevents GUI toolkits from swallowing KeyboardInterrupt exceptions. """ import signal signal.signal(signal.SIGINT, signal.SIG_DFL) allow_interrupt = staticmethod(allow_interrupt) def _default_state_location(self): """ Return the default state location. """ state_location = os.path.join(ETSConfig.application_home, 'pyface', ETSConfig.toolkit) if not os.path.exists(state_location): os.makedirs(state_location) logger.debug('GUI state location is <%s>', state_location) return state_location pyface-4.1.0/pyface/i_image_resource.py0000644000175100001440000001167211674463363021112 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005-2009 by Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! #------------------------------------------------------------------------------ """ The interface for an image resource. """ import operator from pyface.resource_manager import resource_manager from pyface.resource.resource_path import resource_module, resource_path from traits.api import Interface, List, Unicode class IImageResource(Interface): """ The interface for an image resource. An image resource describes the location of an image and provides a way to create a toolkit-specific image on demand. """ #### 'ImageResource' interface ############################################ # The absolute path to the image. absolute_path = Unicode # The name of the image. name = Unicode # A list of directories, classes or instances that will be used to search # for the image (see the resource manager for more details). search_path = List ########################################################################### # 'object' interface. ########################################################################### def __init__(self, name, search_path=None): """ Creates a new image resource. """ ########################################################################### # 'ImageResource' interface. ########################################################################### def create_image(self, size=None): """ Creates a toolkit specific image for this resource. """ # FIXME v3: The need to distinguish between bitmaps and images is toolkit # specific so, strictly speaking, the conversion to a bitmap should be done # wherever the toolkit actually needs it. def create_bitmap(self, size=None): """ Creates a toolkit specific bitmap for this resource. """ def create_icon(self, size=None): """ Creates a toolkit-specific icon for this resource. """ class MImageResource(object): """ The mixin class that contains common code for toolkit specific implementations of the IImageResource interface. Implements: __init__(), create_image() """ #### Private interface #################################################### # The image-not-found image. Note that it is not a trait. _image_not_found = None ########################################################################### # 'object' interface. ########################################################################### def __init__(self, name, search_path=None): self.name = name if search_path is not None and operator.isSequenceType(search_path): _path = search_path elif search_path is not None: _path = [search_path] else: _path = [resource_path()] self.search_path = _path + [resource_module()] ########################################################################### # 'ImageResource' interface. ########################################################################### def create_image(self, size=None): """ Creates a toolkit specific image for this resource. """ ref = self._get_ref(size) if ref is not None: image = ref.load() else: image = self._get_image_not_found_image() return image ########################################################################### # Private interface. ########################################################################### def _get_ref(self, size=None): """ Return the resource manager reference to the image. """ if self._ref is None: self._ref = resource_manager.locate_image(self.name, self.search_path, size) return self._ref def _get_image_not_found_image(self): """ Returns the 'image not found' image. """ not_found = self._get_image_not_found() if self is not not_found: image = not_found.create_image() else: raise ValueError("cannot locate the file for 'image_not_found'") return image def _get_image_not_found(cls): """ Returns the 'image not found' image resource. """ if cls._image_not_found is None: from pyface.image_resource import ImageResource cls._image_not_found = ImageResource('image_not_found') return cls._image_not_found _get_image_not_found = classmethod(_get_image_not_found) #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/0000755000175100001440000000000011674463363016530 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/wizard/i_wizard.py0000644000175100001440000001167711674463363020726 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for all pyface wizards. """ # Enthought library imports. from traits.api import Bool, Instance, List, Unicode from pyface.i_dialog import IDialog # Local imports. from i_wizard_controller import IWizardController from i_wizard_page import IWizardPage class IWizard(IDialog): """ The interface for all pyface wizards. """ #### 'IWizard' interface ################################################## # The pages in the wizard. pages = List(IWizardPage) # The wizard controller provides the pages displayed in the wizard, and # determines when the wizard is complete etc. controller = Instance(IWizardController) # Should the 'Cancel' button be displayed? show_cancel = Bool(True) #### 'IWindow' interface ################################################## # The dialog title. title = Unicode('Wizard') ########################################################################### # 'IWizard' interface. ########################################################################### def next(self): """ Advance to the next page in the wizard. """ def previous(self): """ Return to the previous page in the wizard. """ class MWizard(object): """ The mixin class that contains common code for toolkit specific implementations of the IWizard interface. Implements: next(), previous() Reimplements: _create_contents() """ ########################################################################### # 'IWizard' interface. ########################################################################### def next(self): """ Advance to the next page in the wizard. """ page = self.controller.get_next_page(self.controller.current_page) self._show_page(page) return def previous(self): """ Return to the previous page in the wizard. """ page = self.controller.get_previous_page(self.controller.current_page) self._show_page(page) return ########################################################################### # Protected 'IWindow' interface. ########################################################################### def _create_contents(self, parent): """ Creates the window contents. """ # This creates the dialog and button areas. super(MWizard, self)._create_contents(parent) # Wire up the controller. self._initialize_controller(self.controller) # Show the first page. self._show_page(self.controller.get_first_page()) return ########################################################################### # Protected MWizard interface. ########################################################################### def _show_page(self, page): """ Show the specified page. """ # Set the current page in the controller. # # fixme: Shouldn't this interface be reversed? Maybe calling # 'next_page' on the controller should cause it to set its own current # page? self.controller.current_page = page def _update(self): """ Enables/disables buttons depending on the state of the wizard. """ pass ########################################################################### # Private interface. ########################################################################### def _initialize_controller(self, controller): """ Initializes the wizard controller. """ controller.on_trait_change(self._update, 'complete') controller.on_trait_change( self._on_current_page_changed, 'current_page' ) return #### Trait event handlers ################################################# def _on_current_page_changed(self, obj, trait_name, old, new): """ Called when the current page is changed. """ if old is not None: old.on_trait_change(self._update, 'complete', remove=True) if new is not None: new.on_trait_change(self._update, 'complete') self._update() return def _on_closed_changed(self): """ Called when the wizard is closed. """ self.controller.dispose_pages() return #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/wizard_controller.py0000644000175100001440000001207211674463363022647 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A wizard controller that has a static list of pages. """ # Enthought library imports. from traits.api import Bool, HasTraits, implements, Instance, List, \ Property # Local imports. from i_wizard_controller import IWizardController from i_wizard_page import IWizardPage class WizardController(HasTraits): """ A wizard controller that has a static list of pages. """ implements(IWizardController) #### 'IWizardController' interface ######################################## # The pages under the control of this controller. pages = Property(List(IWizardPage)) # The current page. current_page = Instance(IWizardPage) # Set if the wizard is complete. complete = Bool(False) #### Protected 'IWizardController' interface ############################## # Shadow trait for the 'pages' property. _pages = List(IWizardPage) ########################################################################### # 'IWizardController' interface. ########################################################################### def get_first_page(self): """ Returns the first page. """ if self._pages: return self._pages[0] return None def get_next_page(self, page): """ Returns the next page. """ if page.last_page: pass elif page.next_id: for p in self._pages: if p.id == page.next_id: return p else: index = self._pages.index(page) + 1 if index < len(self._pages): return self._pages[index] return None def get_previous_page(self, page): """ Returns the previous page. """ for p in self._pages: next = self.get_next_page(p) if next is page: return p return None def is_first_page(self, page): """ Is the page the first page? """ return page is self._pages[0] def is_last_page(self, page): """ Is the page the last page? """ if page.last_page: return True if page.next_id: return False return page is self._pages[-1] def dispose_pages(self): """ Dispose the wizard pages. """ for page in self._pages: page.dispose_page() return ########################################################################### # 'WizardController' interface. ########################################################################### def _get_pages(self): """ Returns the pages in the wizard. """ return self._pages[:] def _set_pages(self, pages): """ Sets the pages in the wizard. """ self._pages = pages # Make sure the current page is valid. # If the current page is None (i.e., the current page has # not been set yet), do not set it here. The current page will # get set when the wizard calls _show_page. if self.current_page is not None and \ self.current_page not in self._pages: self.current_page = self._pages[0] else: self._update() return ########################################################################### # Private interface. ########################################################################### def _update(self): """ Checks the completion status of the controller. """ # The entire wizard is complete when the last page is complete. if self.current_page is None: self.complete = False elif self.is_last_page(self.current_page): self.complete = self.current_page.complete else: self.complete = False return #### Trait event handlers ################################################# #### Static #### def _current_page_changed(self, old, new): """ Called when the current page is changed. """ if old is not None: old.on_trait_change(self._on_page_complete, 'complete',remove=True) if new is not None: new.on_trait_change(self._on_page_complete, 'complete') self._update() return #### Dynamic #### def _on_page_complete(self, obj, trait_name, old, new): """ Called when the current page is complete. """ self._update() return #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/wizard_page.py0000644000175100001440000000162311674463363021400 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a page in a wizard. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object WizardPage = toolkit_object('wizard.wizard_page:WizardPage') #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/api.py0000644000175100001440000000216111674463363017653 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from i_wizard_page import IWizardPage from wizard_page import WizardPage from i_wizard import IWizard from wizard import Wizard from i_wizard_controller import IWizardController from wizard_controller import WizardController from chained_wizard import ChainedWizard from chained_wizard_controller import ChainedWizardController # These are deprecated. Use Wizard and WizardController instead. from simple_wizard import SimpleWizard from simple_wizard_controller import SimpleWizardController pyface-4.1.0/pyface/wizard/i_wizard_controller.py0000644000175100001440000000367411674463363023167 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for all pyface wizard controllers. """ # Enthought library imports. from traits.api import Bool, Interface, Instance, List # Local imports. from i_wizard_page import IWizardPage class IWizardController(Interface): """ The interface for all pyface wizard controllers. """ #### 'IWizardController' interface ######################################## # The pages under the control of this controller. pages = List(IWizardPage) # The current page. current_page = Instance(IWizardPage) # Set if the wizard complete. complete = Bool(False) ########################################################################### # 'IWizardController' interface. ########################################################################### def get_first_page(self): """ Returns the first page in the model. """ def get_next_page(self, page): """ Returns the next page. """ def get_previous_page(self, page): """ Returns the previous page. """ def is_first_page(self, page): """ Is the page the first page? """ def is_last_page(self, page): """ Is the page the last page? """ def dispose_pages(self): """ Dispose all the pages. """ #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/simple_wizard.py0000644000175100001440000000141111674463363021750 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ SimpleWizard is deprecated. Use Wizard instead. """ # Local imports. from wizard import Wizard class SimpleWizard(Wizard): pass pyface-4.1.0/pyface/wizard/chained_wizard_controller.py0000644000175100001440000001501411674463363024321 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A wizard controller that can be chained with others. """ # Enthought library imports. from traits.api import Instance # Local imports. from i_wizard_controller import IWizardController from wizard_controller import WizardController class ChainedWizardController(WizardController): """ A wizard controller that can be chained with others. """ #### 'ChainedWizardController' interface ################################## # The next chained wizard controller. next_controller = Instance(IWizardController) ########################################################################### # 'IWizardController' interface. ########################################################################### def get_next_page(self, page): """ Returns the next page. """ next_page = None if page in self._pages: if page is not self._pages[-1]: index = self._pages.index(page) next_page = self._pages[index + 1] else: if self.next_controller is not None: next_page = self.next_controller.get_first_page() else: if self.next_controller is not None: next_page = self.next_controller.get_next_page(page) return next_page def get_previous_page(self, page): """ Returns the previous page. """ if page in self._pages: index = self._pages.index(page) previous_page = self._pages[index - 1] else: if self.next_controller is not None: if self.next_controller.is_first_page(page): previous_page = self._pages[-1] else: previous_page = self.next_controller.get_previous_page(page) else: previous_page = None return previous_page def is_first_page(self, page): """ Is the page the first page? """ return page is self._pages[0] def is_last_page(self, page): """ Is the page the last page? """ if page in self._pages: # If page is not this controller's last page, then it cannot be # *the* last page. if not page is self._pages[-1]: is_last = False # Otherwise, it is *the* last page if this controller has no next # controller or the next controller has no pages. else: if self.next_controller is None: is_last = True else: is_last = self.next_controller.is_last_page(page) else: if self.next_controller is not None: is_last = self.next_controller.is_last_page(page) elif len(self._pages) > 0: is_last = False else: is_last = True return is_last def dispose_pages(self): """ Dispose the wizard's pages. """ for page in self._pages: page.dispose_page() if self.next_controller is not None: self.next_controller.dispose_pages() return ########################################################################### # 'ChainedWizardController' interface. ########################################################################### def _get_pages(self): """ Returns the pages in the wizard. """ pages = self._pages[:] if self.next_controller is not None: pages.extend(self.next_controller.pages) return pages def _set_pages(self, pages): """ Sets the pages in the wizard. """ self._pages = pages return ########################################################################### # Private interface. ########################################################################### def _update(self): """ Checks the completion status of the controller. """ # The entire wizard is complete when ALL pages are complete. for page in self._pages: if not page.complete: self.complete = False break else: if self.next_controller is not None: # fixme: This is a abstraction leak point, since _update is not # part of the wizard_controller interface! self.next_controller._update() self.complete = self.next_controller.complete else: self.complete = True return #### Trait event handlers ################################################# #### Static #### def _current_page_changed(self, old, new): """ Called when the current page is changed. """ if old is not None: old.on_trait_change( self._on_page_complete, 'complete',remove=True ) if new is not None: new.on_trait_change(self._on_page_complete, 'complete') if self.next_controller is not None: self.next_controller.current_page = new self._update() return def _next_controller_changed(self, old, new): """ Called when the next controller is changed. """ if old is not None: old.on_trait_change( self._on_controller_complete, 'complete', remove=True ) if new is not None: new.on_trait_change( self._on_controller_complete, 'complete' ) self._update() return #### Dynamic #### def _on_controller_complete(self, obj, trait_name, old, new): """ Called when the next controller's complete state changes. """ self._update() return def _on_page_complete(self, obj, trait_name, old, new): """ Called when the current page is complete. """ self._update() return #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/i_wizard_page.py0000644000175100001440000000546011674463363021713 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a page in a wizard. """ # Enthought library imports. from traits.api import Bool, Interface, Str, Tuple, Unicode class IWizardPage(Interface): """ The interface for a page in a wizard. """ #### 'IWizardPage' interface ############################################## # The unique Id of the page within the wizard. id = Str # The Id of the next page. next_id = Str # Set if this is the last page of the wizard. It can be ignored for # simple linear wizards. last_page = Bool(False) # Is the page complete (i.e. should the 'Next' button be enabled)? complete = Bool(False) # The page heading. heading = Unicode # The page sub-heading. subheading = Unicode # The size of the page. size = Tuple ########################################################################### # 'IWizardPage' interface. ########################################################################### def create_page(self, parent): """ Creates the wizard page. """ def dispose_page(self): """ Disposes the wizard page. Subclasses are expected to override this method if they need to dispose of the contents of a page. """ ########################################################################### # Protected 'IWizardPage' interface. ########################################################################### def _create_page_content(self, parent): """ Creates the actual page content. """ class MWizardPage(object): """ The mixin class that contains common code for toolkit specific implementations of the IWizardPage interface. Implements: dispose_page() """ ########################################################################### # 'IWizardPage' interface. ########################################################################### def dispose_page(self): """ Disposes the wizard page. Subclasses are expected to override this method if they need to dispose of the contents of a page. """ pass #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/chained_wizard.py0000644000175100001440000000533111674463363022057 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A wizard model that can be chained with other wizards. """ # Enthought library imports. from traits.api import Instance # Local imports. from i_wizard import IWizard from wizard import Wizard class ChainedWizard(Wizard): """ A wizard model that can be chained with other wizards. """ #### 'ChainedWizard' interface ############################################ # The wizard following this wizard in the chain. next_wizard = Instance(IWizard) ########################################################################### # 'ChainedWizard' interface. ########################################################################### #### Trait handlers. ###################################################### def _controller_default(self): """ Provide a default controller. """ from chained_wizard_controller import ChainedWizardController return ChainedWizardController() #### Trait event handlers. ################################################ #### Static #### def _next_wizard_changed(self, old, new): """ Handle the next wizard being changed. """ if new is not None: self.controller.next_controller = new.controller if self.control is not None: # FIXME: Do we need to call _create_buttons? Buttons would have # added when the main dialog area was created (for the first # wizard), and calling update should update the state of these # buttons. Do we need to check if buttons are already present in # the dialog area? What is use case for calling _create_buttons? # self._create_buttons(self.control) self._update() return def _controller_changed(self, old, new): """ handle the controller being changed. """ if new is not None and self.next_wizard is not None: self.controller.next_controller = self.next_wizard.controller if self.control is not None: self._update() return #### EOF ###################################################################### pyface-4.1.0/pyface/wizard/simple_wizard_controller.py0000644000175100001440000000150611674463363024220 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ SimpleWizardController is deprecated. Use WizardController instead. """ # Local imports. from wizard_controller import WizardController class SimpleWizardController(WizardController): pass pyface-4.1.0/pyface/wizard/__init__.py0000644000175100001440000000120111674463363020633 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/wizard/wizard.py0000644000175100001440000000157311674463363020410 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a wizard. """ # Import the toolkit specific version. from pyface.toolkit import toolkit_object Wizard = toolkit_object('wizard.wizard:Wizard') #### EOF ###################################################################### pyface-4.1.0/pyface/image_button.py0000644000175100001440000002350411674463363020263 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: David C. Morrill # # Description: Image and text-based pyface button/toolbar/radio button control. # #------------------------------------------------------------------------------ """ An image and text-based control that can be used as a normal, radio or toolbar button. """ from __future__ import absolute_import import wx from numpy import array, fromstring, reshape, ravel, dtype from traits.api import Str, Range, Enum, Instance, Event, false from .widget import Widget from .image_resource import ImageResource #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- # Text color used when a button is disabled: DisabledTextColor = wx.Colour( 128, 128, 128 ) #------------------------------------------------------------------------------- # 'ImageButton' class: #------------------------------------------------------------------------------- class ImageButton ( Widget ): """ An image and text-based control that can be used as a normal, radio or toolbar button. """ # Pens used to draw the 'selection' marker: _selectedPenDark = wx.Pen( wx.SystemSettings_GetColour( wx.SYS_COLOUR_3DSHADOW ), 1, wx.SOLID ) _selectedPenLight = wx.Pen( wx.SystemSettings_GetColour( wx.SYS_COLOUR_3DHIGHLIGHT ), 1, wx.SOLID ) #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- # The image: image = Instance( ImageResource, allow_none = True ) # The (optional) label: label = Str # Extra padding to add to both the left and right sides: width_padding = Range( 0, 31, 7 ) # Extra padding to add to both the top and bottom sides: height_padding = Range( 0, 31, 5 ) # Presentation style: style = Enum( 'button', 'radio', 'toolbar', 'checkbox' ) # Orientation of the text relative to the image: orientation = Enum( 'vertical', 'horizontal' ) # Is the control selected ('radio' or 'checkbox' style)? selected = false # Fired when a 'button' or 'toolbar' style control is clicked: clicked = Event #--------------------------------------------------------------------------- # Initializes the object: #--------------------------------------------------------------------------- def __init__ ( self, parent, **traits ): """ Creates a new image control. """ self._image = None super( ImageButton, self ).__init__( **traits ) # Calculate the size of the button: idx = idy = tdx = tdy = 0 if self._image is not None: idx = self._image.GetWidth() idy = self._image.GetHeight() if self.label != '': dc = wx.ScreenDC() dc.SetFont( wx.NORMAL_FONT ) tdx, tdy = dc.GetTextExtent( self.label ) wp2 = self.width_padding + 2 hp2 = self.height_padding + 2 if self.orientation == 'horizontal': self._ix = wp2 spacing = (idx > 0) * (tdx > 0) * 4 self._tx = self._ix + idx + spacing dx = idx + tdx + spacing dy = max( idy, tdy ) self._iy = hp2 + ((dy - idy) / 2) self._ty = hp2 + ((dy - tdy) / 2) else: self._iy = hp2 spacing = (idy > 0) * (tdy > 0) * 2 self._ty = self._iy + idy + spacing dx = max( idx, tdx ) dy = idy + tdy + spacing self._ix = wp2 + ((dx - idx) / 2) self._tx = wp2 + ((dx - tdx) / 2) # Create the toolkit-specific control: self._dx = dx + wp2 + wp2 self._dy = dy + hp2 + hp2 self.control = wx.Window( parent, -1, size = wx.Size( self._dx, self._dy ) ) self.control._owner = self self._mouse_over = self._button_down = False # Set up mouse event handlers: wx.EVT_ENTER_WINDOW( self.control, self._on_enter_window ) wx.EVT_LEAVE_WINDOW( self.control, self._on_leave_window ) wx.EVT_LEFT_DOWN( self.control, self._on_left_down ) wx.EVT_LEFT_UP( self.control, self._on_left_up ) wx.EVT_PAINT( self.control, self._on_paint ) #--------------------------------------------------------------------------- # Handles the 'image' trait being changed: #--------------------------------------------------------------------------- def _image_changed ( self, image ): self._image = self._mono_image = None if image is not None: self._img = image.create_image() self._image = self._img.ConvertToBitmap() if self.control is not None: self.control.Refresh() #--------------------------------------------------------------------------- # Handles the 'selected' trait being changed: #--------------------------------------------------------------------------- def _selected_changed ( self, selected ): """ Handles the 'selected' trait being changed. """ if selected and (self.style == 'radio'): for control in self.control.GetParent().GetChildren(): owner = getattr( control, '_owner', None ) if (isinstance( owner, ImageButton ) and owner.selected and (owner is not self)): owner.selected = False break self.control.Refresh() #-- wx event handlers ---------------------------------------------------------- def _on_enter_window ( self, event ): """ Called when the mouse enters the widget. """ if self.style != 'button': self._mouse_over = True self.control.Refresh() def _on_leave_window ( self, event ): """ Called when the mouse leaves the widget. """ if self._mouse_over: self._mouse_over = False self.control.Refresh() def _on_left_down ( self, event ): """ Called when the left mouse button goes down on the widget. """ self._button_down = True self.control.CaptureMouse() self.control.Refresh() def _on_left_up ( self, event ): """ Called when the left mouse button goes up on the widget. """ control = self.control control.ReleaseMouse() self._button_down = False wdx, wdy = control.GetClientSizeTuple() x, y = event.GetX(), event.GetY() control.Refresh() if (0 <= x < wdx) and (0 <= y < wdy): if self.style == 'radio': self.selected = True elif self.style == 'checkbox': self.selected = not self.selected else: self.clicked = True def _on_paint ( self, event ): """ Called when the widget needs repainting. """ wdc = wx.PaintDC( self.control ) wdx, wdy = self.control.GetClientSizeTuple() ox = (wdx - self._dx) / 2 oy = (wdy - self._dy) / 2 disabled = (not self.control.IsEnabled()) if self._image is not None: image = self._image if disabled: if self._mono_image is None: img = self._img data = reshape(fromstring(img.GetData(), dtype('uint8')), (-1, 3)) * array([[ 0.297, 0.589, 0.114 ]]) g = data[ :, 0 ] + data[ :, 1 ] + data[ :, 2 ] data[ :, 0 ] = data[ :, 1 ] = data[ :, 2 ] = g img.SetData(ravel(data.astype(dtype('uint8'))).tostring()) img.SetMaskColour(0, 0, 0) self._mono_image = img.ConvertToBitmap() self._img = None image = self._mono_image wdc.DrawBitmap( image, ox + self._ix, oy + self._iy, True ) if self.label != '': if disabled: wdc.SetTextForeground( DisabledTextColor ) wdc.SetFont( wx.NORMAL_FONT ) wdc.DrawText( self.label, ox + self._tx, oy + self._ty ) pens = [ self._selectedPenLight, self._selectedPenDark ] bd = self._button_down style = self.style is_rc = (style in ( 'radio', 'checkbox' )) if bd or (style == 'button') or (is_rc and self.selected): if is_rc: bd = 1 - bd wdc.SetBrush( wx.TRANSPARENT_BRUSH ) wdc.SetPen( pens[ bd ] ) wdc.DrawLine( 1, 1, wdx - 1, 1 ) wdc.DrawLine( 1, 1, 1, wdy - 1 ) wdc.DrawLine( 2, 2, wdx - 2, 2 ) wdc.DrawLine( 2, 2, 2, wdy - 2 ) wdc.SetPen( pens[ 1 - bd ] ) wdc.DrawLine( wdx - 2, 2, wdx - 2, wdy - 1 ) wdc.DrawLine( 2, wdy - 2, wdx - 2, wdy - 2 ) wdc.DrawLine( wdx - 3, 3, wdx - 3, wdy - 2 ) wdc.DrawLine( 3, wdy - 3, wdx - 3, wdy - 3 ) elif self._mouse_over and (not self.selected): wdc.SetBrush( wx.TRANSPARENT_BRUSH ) wdc.SetPen( pens[ bd ] ) wdc.DrawLine( 0, 0, wdx, 0 ) wdc.DrawLine( 0, 1, 0, wdy ) wdc.SetPen( pens[ 1 - bd ] ) wdc.DrawLine( wdx - 1, 1, wdx - 1, wdy ) wdc.DrawLine( 1, wdy - 1, wdx - 1, wdy - 1 ) pyface-4.1.0/pyface/grid/0000755000175100001440000000000011674463363016155 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/grid/checkbox_image_renderer.py0000644000175100001440000000024011674463363023341 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.checkbox_image_renderer import * pyface-4.1.0/pyface/grid/edit_image_renderer.py0000644000175100001440000000023411674463363022503 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.edit_image_renderer import * pyface-4.1.0/pyface/grid/combobox_focus_handler.py0000644000175100001440000000023711674463363023235 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.combobox_focus_handler import * pyface-4.1.0/pyface/grid/mapped_grid_cell_image_renderer.py0000644000175100001440000000025011674463363025026 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.mapped_grid_cell_image_renderer import * pyface-4.1.0/pyface/grid/api.py0000644000175100001440000000021411674463363017275 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.api import * pyface-4.1.0/pyface/grid/grid.py0000644000175100001440000000021511674463363017452 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.grid import * pyface-4.1.0/pyface/grid/simple_grid_model.py0000644000175100001440000000023211674463363022202 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.simple_grid_model import * pyface-4.1.0/pyface/grid/trait_grid_cell_adapter.py0000644000175100001440000000024011674463363023352 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.trait_grid_cell_adapter import * pyface-4.1.0/pyface/grid/edit_renderer.py0000644000175100001440000000022611674463363021342 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.edit_renderer import * pyface-4.1.0/pyface/grid/grid_cell_image_renderer.py0000644000175100001440000000024111674463363023500 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.grid_cell_image_renderer import * pyface-4.1.0/pyface/grid/grid_cell_renderer.py0000644000175100001440000000023311674463363022337 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.grid_cell_renderer import * pyface-4.1.0/pyface/grid/__init__.py0000644000175100001440000000000011674463363020254 0ustar ischnellusers00000000000000pyface-4.1.0/pyface/grid/inverted_grid_model.py0000644000175100001440000000023411674463363022533 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.inverted_grid_model import * pyface-4.1.0/pyface/grid/checkbox_renderer.py0000644000175100001440000000023211674463363022200 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.checkbox_renderer import * pyface-4.1.0/pyface/grid/grid_model.py0000644000175100001440000000022311674463363020631 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.grid_model import * pyface-4.1.0/pyface/grid/trait_grid_model.py0000644000175100001440000000023111674463363022033 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.trait_grid_model import * pyface-4.1.0/pyface/grid/composite_grid_model.py0000644000175100001440000000023511674463363022716 0ustar ischnellusers00000000000000import logging logging.warn('DEPRECATED: pyface.grid, ' 'use pyface.ui.wx.grid instead.') from pyface.ui.wx.grid.composite_grid_model import * pyface-4.1.0/pyface/i_python_editor.py0000644000175100001440000000472411674463363021010 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A widget for editing Python code. """ # Enthought library imports. from traits.api import Bool, Event, Interface, Unicode # Local imports. from key_pressed_event import KeyPressedEvent class IPythonEditor(Interface): """ A widget for editing Python code. """ #### 'IPythonEditor' interface ############################################ # Has the file in the editor been modified? dirty = Bool(False) # The pathname of the file being edited. path = Unicode # Should line numbers be shown in the margin? show_line_numbers = Bool(True) #### Events #### # The contents of the editor has changed. changed = Event # A key has been pressed. key_pressed = Event(KeyPressedEvent) ########################################################################### # 'IPythonEditor' interface. ########################################################################### def load(self, path=None): """ Loads the contents of the editor. """ def save(self, path=None): """ Saves the contents of the editor. """ # FIXME v3: This is very dependent on the underlying implementation. def set_style(self, n, fore, back): """ Set the foreground and background colors for a particular style and set the font and size to default values. """ def select_line(self, lineno): """ Selects the specified line. """ class MPythonEditor(object): """ The mixin class that contains common code for toolkit specific implementations of the IPythonEditor interface. Implements: _changed_path() """ def _changed_path(self): """ Called when the path to the file is changed. """ if self.control is not None: self.load() return #### EOF ###################################################################### pyface-4.1.0/pyface/heading_text.py0000644000175100001440000000155411674463363020252 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Heading text. """ # Import the toolkit specific version. from toolkit import toolkit_object HeadingText = toolkit_object('heading_text:HeadingText') #### EOF ###################################################################### pyface-4.1.0/pyface/util/0000755000175100001440000000000011674463363016205 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/util/guisupport.py0000644000175100001440000001377511674463363021015 0ustar ischnellusers00000000000000""" Support for creating GUI apps and starting event loops. IPython's GUI integration allows interative plotting and GUI usage in IPython session. IPython has two different types of GUI integration: 1. The terminal based IPython supports GUI event loops through Python's PyOS_InputHook. PyOS_InputHook is a hook that Python calls periodically whenever raw_input is waiting for a user to type code. We implement GUI support in the terminal by setting PyOS_InputHook to a function that iterates the event loop for a short while. It is important to note that in this situation, the real GUI event loop is NOT run in the normal manner, so you can't use the normal means to detect that it is running. 2. In the two process IPython kernel/frontend, the GUI event loop is run in the kernel. In this case, the event loop is run in the normal manner by calling the function or method of the GUI toolkit that starts the event loop. In addition to starting the GUI event loops in one of these two ways, IPython will *always* create an appropriate GUI application object when GUi integration is enabled. If you want your GUI apps to run in IPython you need to do two things: 1. Test to see if there is already an existing main application object. If there is, you should use it. If there is not an existing application object you should create one. 2. Test to see if the GUI event loop is running. If it is, you should not start it. If the event loop is not running you may start it. This module contains functions for each toolkit that perform these things in a consistent manner. Because of how PyOS_InputHook runs the event loop you cannot detect if the event loop is running using the traditional calls (such as ``wx.GetApp.IsMainLoopRunning()`` in wxPython). If PyOS_InputHook is set These methods will return a false negative. That is, they will say the event loop is not running, when is actually is. To work around this limitation we proposed the following informal protocol: * Whenever someone starts the event loop, they *must* set the ``_in_event_loop`` attribute of the main application object to ``True``. This should be done regardless of how the event loop is actually run. * Whenever someone stops the event loop, they *must* set the ``_in_event_loop`` attribute of the main application object to ``False``. * If you want to see if the event loop is running, you *must* use ``hasattr`` to see if ``_in_event_loop`` attribute has been set. If it is set, you *must* use its value. If it has not been set, you can query the toolkit in the normal manner. * If you want GUI support and no one else has created an application or started the event loop you *must* do this. We don't want projects to attempt to defer these things to someone else if they themselves need it. The functions below implement this logic for each GUI toolkit. If you need to create custom application subclasses, you will likely have to modify this code for your own purposes. This code can be copied into your own project so you don't have to depend on IPython. """ #----------------------------------------------------------------------------- # Copyright (C) 2008-2010 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- # Prevent name conflict with local wx package. from __future__ import absolute_import #----------------------------------------------------------------------------- # wx #----------------------------------------------------------------------------- def get_app_wx(*args, **kwargs): """Create a new wx app or return an exiting one.""" import wx app = wx.GetApp() if app is None: if not kwargs.has_key('redirect'): kwargs['redirect'] = False app = wx.PySimpleApp(*args, **kwargs) return app def is_event_loop_running_wx(app=None): """Is the wx event loop running.""" if app is None: app = get_app_wx() if hasattr(app, '_in_event_loop'): return app._in_event_loop else: return app.IsMainLoopRunning() def start_event_loop_wx(app=None): """Start the wx event loop in a consistent manner.""" if app is None: app = get_app_wx() if not is_event_loop_running_wx(app): app._in_event_loop = True app.MainLoop() app._in_event_loop = False else: app._in_event_loop = True #----------------------------------------------------------------------------- # qt4 #----------------------------------------------------------------------------- def get_app_qt4(*args, **kwargs): """Create a new qt4 app or return an existing one.""" from pyface.qt import QtGui app = QtGui.QApplication.instance() if app is None: if not args: args = ([''],) app = QtGui.QApplication(*args, **kwargs) return app def is_event_loop_running_qt4(app=None): """Is the qt4 event loop running.""" if app is None: app = get_app_qt4(['']) if hasattr(app, '_in_event_loop'): return app._in_event_loop else: # Does qt4 provide a other way to detect this? return False def start_event_loop_qt4(app=None): """Start the qt4 event loop in a consistent manner.""" if app is None: app = get_app_qt4(['']) if not is_event_loop_running_qt4(app): app._in_event_loop = True app.exec_() app._in_event_loop = False else: app._in_event_loop = True #----------------------------------------------------------------------------- # Tk #----------------------------------------------------------------------------- #----------------------------------------------------------------------------- # gtk #----------------------------------------------------------------------------- pyface-4.1.0/pyface/util/python_stc.py0000644000175100001440000003522211674463363020755 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx from wx import stc import keyword #---------------------------------------------------------------------- demoText = """\ ## This version of the editor has been set up to edit Python source ## code. Here is a copy of wxPython/demo/Main.py to play with. """ #---------------------------------------------------------------------- if wx.Platform == '__WXMSW__': faces = { 'times': 'Times New Roman', 'mono' : 'Courier New', 'helv' : 'Arial', 'other': 'Comic Sans MS', 'size' : 10, 'size2': 8, } else: faces = { 'times': 'Times', 'mono' : 'Courier', 'helv' : 'Helvetica', 'other': 'new century schoolbook', 'size' : 12, 'size2': 10, } #---------------------------------------------------------------------- class PythonSTC(stc.StyledTextCtrl): def __init__(self, parent, ID): stc.StyledTextCtrl.__init__(self, parent, ID, style = wx.NO_FULL_REPAINT_ON_RESIZE) self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN) self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT) self.SetLexer(stc.STC_LEX_PYTHON) self.SetKeyWords(0, " ".join(keyword.kwlist)) self.SetProperty("fold", "1") self.SetProperty("tab.timmy.whinge.level", "1") self.SetMargins(0,0) self.SetViewWhiteSpace(False) #self.SetBufferedDraw(False) self.SetEdgeMode(stc.STC_EDGE_BACKGROUND) self.SetEdgeColumn(78) # Setup a margin to hold fold markers ### WHAT IS THIS VALUE? WHAT ARE THE OTHER FLAGS? DOES IT MATTER? self.SetFoldFlags(16) #mic #self.SetMarginType(2, stc.STC_MARGIN_SYMBOL) #self.SetMarginMask(2, stc.STC_MASK_FOLDERS) #self.SetMarginSensitive(2, True) #self.SetMarginWidth(2, 12) # line numbers in the margin self.SetMarginType(1, stc.STC_MARGIN_NUMBER) self.SetMarginWidth(1, 25) if 0: # simple folder marks, like the old version self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_ARROW, "navy", "navy") self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_ARROWDOWN, "navy", "navy") # Set these to an invisible mark self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BACKGROUND, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_BACKGROUND, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_BACKGROUND, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_BACKGROUND, "white", "black") elif 0: # more involved "outlining" folder marks self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "black") self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "black") stc.EVT_STC_UPDATEUI(self, ID, self.OnUpdateUI) stc.EVT_STC_MARGINCLICK(self, ID, self.OnMarginClick) # Make some styles, The lexer defines what each style is used for, we # just have to define what each style looks like. This set is adapted # from Scintilla sample property files. self.StyleClearAll() # Global default styles for all languages self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % faces) self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % faces) self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % faces) self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold") self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold") # Python styles # White space self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#808080,face:%(helv)s,size:%(size)d" % faces) # Comment self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % faces) # Number self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % faces) # String self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,italic,face:%(times)s,size:%(size)d" % faces) # Single quoted string self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,italic,face:%(times)s,size:%(size)d" % faces) # Keyword self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % faces) # Triple quotes self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % faces) # Triple double quotes self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % faces) # Class name definition self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % faces) # Function or method name definition self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % faces) # Operators self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % faces) # Identifiers self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#808080,face:%(helv)s,size:%(size)d" % faces) # Comment-blocks self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % faces) # End of line where string is not closed self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % faces) self.SetCaretForeground("BLUE") wx.EVT_KEY_DOWN(self, self.OnKeyPressed) def OnKeyPressed(self, event): if self.CallTipActive(): self.CallTipCancel() # KeyCode used to be a method. Now it is an integer. # Handle either case. if type(event.KeyCode) is int: # wx2.8+ key = event.KeyCode else: # wx2.6 key = event.KeyCode() if key == 32 and event.ControlDown(): pos = self.GetCurrentPos() # Tips if event.ShiftDown(): self.CallTipSetBackground("yellow") self.CallTipShow(pos, 'param1, param2') # Code completion else: # fixme: What is this mess!!! #lst = [] #for x in range(50000): # lst.append('%05d' % x) #st = " ".join(lst) #print len(st) #self.AutoCompShow(0, st) # fixme: What is this mess!!! kw = keyword.kwlist[:] kw.append("zzzzzz") kw.append("aaaaa") kw.append("__init__") kw.append("zzaaaaa") kw.append("zzbaaaa") kw.append("this_is_a_longer_value") kw.append("this_is_a_much_much_much_much_longer_value") kw.sort() # Python sorts are case sensitive self.AutoCompSetIgnoreCase(False) # so this needs to match self.AutoCompShow(0, " ".join(kw)) else: event.Skip() def OnUpdateUI(self, evt): # check for matching braces braceAtCaret = -1 braceOpposite = -1 charBefore = None caretPos = self.GetCurrentPos() if caretPos > 0: charBefore = self.GetCharAt(caretPos - 1) styleBefore = self.GetStyleAt(caretPos - 1) # check before if (charBefore and chr(charBefore) in "[]{}()" and styleBefore==stc.STC_P_OPERATOR): braceAtCaret = caretPos - 1 # check after if braceAtCaret < 0: charAfter = self.GetCharAt(caretPos) styleAfter = self.GetStyleAt(caretPos) if (charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR): braceAtCaret = caretPos if braceAtCaret >= 0: braceOpposite = self.BraceMatch(braceAtCaret) if braceAtCaret != -1 and braceOpposite == -1: self.BraceBadLight(braceAtCaret) else: self.BraceHighlight(braceAtCaret, braceOpposite) #pt = self.PointFromPosition(braceOpposite) #self.Refresh(True, Rect(pt.x, pt.y, 5,5)) #print pt #self.Refresh(False) def OnMarginClick(self, evt): # fold and unfold as needed if evt.GetMargin() == 2: if evt.GetShift() and evt.GetControl(): self.FoldAll() else: lineClicked = self.LineFromPosition(evt.GetPosition()) if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG: if evt.GetShift(): self.SetFoldExpanded(lineClicked, True) self.Expand(lineClicked, True, True, 1) elif evt.GetControl(): if self.GetFoldExpanded(lineClicked): self.SetFoldExpanded(lineClicked, False) self.Expand(lineClicked, False, True, 0) else: self.SetFoldExpanded(lineClicked, True) self.Expand(lineClicked, True, True, 100) else: self.ToggleFold(lineClicked) def FoldAll(self): lineCount = self.GetLineCount() expanding = True # find out if we are folding or unfolding for lineNum in range(lineCount): if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG: expanding = not self.GetFoldExpanded(lineNum) break; lineNum = 0 while lineNum < lineCount: level = self.GetFoldLevel(lineNum) if level & stc.STC_FOLDLEVELHEADERFLAG and \ (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE: if expanding: self.SetFoldExpanded(lineNum, True) lineNum = self.Expand(lineNum, True) lineNum = lineNum - 1 else: lastChild = self.GetLastChild(lineNum, -1) self.SetFoldExpanded(lineNum, False) if lastChild > lineNum: self.HideLines(lineNum+1, lastChild) lineNum = lineNum + 1 def Expand(self, line, doExpand, force=False, visLevels=0, level=-1): lastChild = self.GetLastChild(line, level) line = line + 1 while line <= lastChild: if force: if visLevels > 0: self.ShowLines(line, line) else: self.HideLines(line, line) else: if doExpand: self.ShowLines(line, line) if level == -1: level = self.GetFoldLevel(line) if level & stc.STC_FOLDLEVELHEADERFLAG: if force: if visLevels > 1: self.SetFoldExpanded(line, True) else: self.SetFoldExpanded(line, False) line = self.Expand(line, doExpand, force, visLevels-1) else: if doExpand and self.GetFoldExpanded(line): line = self.Expand(line, True, force, visLevels-1) else: line = self.Expand(line, False, force, visLevels-1) else: line = line + 1; return line #---------------------------------------------------------------------- _USE_PANEL = 1 def runTest(frame, nb, log): if not _USE_PANEL: ed = p = stc.PythonSTC(nb, -1) else: p = wx.Panel(nb, -1, style = wx.NO_FULL_REPAINT_ON_RESIZE) ed = PythonSTC(p, -1) s = wx.BoxSizer(wx.HORIZONTAL) s.Add(ed, 1, wx.EXPAND) p.SetSizer(s) p.SetAutoLayout(True) ed.SetText(demoText + open('Main.py').read()) ed.EmptyUndoBuffer() ed.Colourise(0, -1) # line numbers in the margin ed.SetMarginType(1, stc.STC_MARGIN_NUMBER) ed.SetMarginWidth(1, 25) return p #---------------------------------------------------------------------- overview = """\ Once again, no docs yet. Sorry. But this and this should be helpful. """ if __name__ == '__main__': # fixme: This has been re-factored into not working. No run module. import sys,os import run run.main(['', os.path.basename(sys.argv[0])]) #---------------------------------------------------------------------- pyface-4.1.0/pyface/util/fix_introspect_bug.py0000644000175100001440000001355211674463363022462 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Prabhu Ramachandran # Description: #------------------------------------------------------------------------------ """This module adds a fix for wx.py's introspect module. In order to do code-completion, the function `introspect.getAttributeName` accesses all the attributes of the current object. This causes severe problems for modules like tvtk which depend on lazy importing. The original introspect module also has severe problems with large Numeric arrays because it calls str() on the Numeric object in order to find its methods. This file defines a fixed function that works fine with lazy objects and large Numeric arrays. This fixed function is injected into the introspect module. """ # Import introspect. from wx.py import introspect import types # The fixed function. def getAttributeNames(object, includeMagic=1, includeSingle=1, includeDouble=1): """Return list of unique attributes, including inherited, for object.""" attributes = [] dict = {} if not introspect.hasattrAlwaysReturnsTrue(object): # Add some attributes that don't always get picked up. special_attrs = ['__bases__', '__class__', '__dict__', '__name__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name'] attributes += [attr for attr in special_attrs \ if hasattr(object, attr)] # For objects that have traits, get all the trait names since # these do not show up in dir(object). if hasattr(object, 'trait_names'): try: attributes += object.trait_names() except TypeError: pass if includeMagic: try: attributes += object._getAttributeNames() except: pass # Get all attribute names. attrdict = getAllAttributeNames(object) # Store the object's dir. object_dir = dir(object) for (obj_type_name, technique, count), attrlist in attrdict.items(): # This complexity is necessary to avoid accessing all the # attributes of the object. This is very handy for objects # whose attributes are lazily evaluated. if type(object).__name__ == obj_type_name and technique == 'dir': attributes += attrlist else: attributes += [attr for attr in attrlist \ if attr not in object_dir and \ hasattr(object, attr)] # Remove duplicates from the attribute list. for item in attributes: dict[item] = None attributes = dict.keys() # new-style swig wrappings can result in non-string attributes # e.g. ITK http://www.itk.org/ attributes = [attribute for attribute in attributes \ if isinstance(attribute, basestring)] attributes.sort(lambda x, y: cmp(x.upper(), y.upper())) if not includeSingle: attributes = filter(lambda item: item[0]!='_' \ or item[1]=='_', attributes) if not includeDouble: attributes = filter(lambda item: item[:2]!='__', attributes) return attributes # Replace introspect's version with ours. introspect.getAttributeNames = getAttributeNames # This is also a modified version of the function which does not use # str(object). def getAllAttributeNames(object): """Return dict of all attributes, including inherited, for an object. Recursively walk through a class and all base classes. """ attrdict = {} # (object, technique, count): [list of attributes] # !!! # Do Not use hasattr() as a test anywhere in this function, # because it is unreliable with remote objects: xmlrpc, soap, etc. # They always return true for hasattr(). # !!! try: # This could(?) fail if the type is poorly defined without # even a name. key = type(object).__name__ except: key = 'anonymous' # Wake up sleepy objects - a hack for ZODB objects in "ghost" state. wakeupcall = dir(object) del wakeupcall # Get attributes available through the normal convention. attributes = dir(object) attrdict[(key, 'dir', len(attributes))] = attributes # Get attributes from the object's dictionary, if it has one. try: attributes = object.__dict__.keys() attributes.sort() except: # Must catch all because object might have __getattr__. pass else: attrdict[(key, '__dict__', len(attributes))] = attributes # For a class instance, get the attributes for the class. try: klass = object.__class__ except: # Must catch all because object might have __getattr__. pass else: if klass is object: # Break a circular reference. This happens with extension # classes. pass else: attrdict.update(getAllAttributeNames(klass)) # Also get attributes from any and all parent classes. try: bases = object.__bases__ except: # Must catch all because object might have __getattr__. pass else: if isinstance(bases, types.TupleType): for base in bases: if type(base) is types.TypeType: # Break a circular reference. Happens in Python 2.2. pass else: attrdict.update(getAllAttributeNames(base)) return attrdict pyface-4.1.0/pyface/util/grid/0000755000175100001440000000000011674463363017132 5ustar ischnellusers00000000000000pyface-4.1.0/pyface/util/grid/grid_column.py0000644000175100001440000000207111674463363022006 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A description of a column in a grid. """ # Enthought library imports. from traits.api import Bool, HasTraits, Str class GridColumn(HasTraits): """ A description of a column in a grid. """ # Column header. label = Str # Type name of data allowed in the column. type = Str # Is the column read-only? readonly = Bool(False) #### EOF ###################################################################### pyface-4.1.0/pyface/util/grid/api.py0000644000175100001440000000136711674463363020264 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ from grid import Grid from grid_column import GridColumn from grid_model import GridModel from grid_row import GridRow pyface-4.1.0/pyface/util/grid/grid.py0000644000175100001440000002631211674463363020435 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A grid (spreadsheet) widget. """ # Major package imports. import wx from wx.grid import Grid as wxGrid # Local imports. from grid_model import GridModel class Grid(wxGrid): """ A grid (spreadsheet) widget. """ def __init__(self, parent, model): """ Constructor. """ # Base class constructor. wxGrid.__init__(self, parent, -1) # The model that provides the data and row/column information. self.model = None # Automatically size columns and rows to fit their content. # # fixme: wx seems sensitive to the location of these two lines. Put # them *before* the call to 'SetTable', otherwise the grid takes # forever to initialize! ##self.AutoSizeColumns() ##self.AutoSizeRows() # Don't display any extra space around the rows and columns. self.SetMargins(0, 0) # Tell the grid to get its data from the model. # # N.B The terminology used in the wxPython API is a little confusing! # --- The 'SetTable' method is actually setting the model used by # the grid (which is the view)! # # The second parameter to 'SetTable' tells the grid to take ownership # of the model and to destroy it when it is done. Otherwise you would # need to keep a reference to the model and manually destroy it later # (by calling it's Destroy method). # # fixme: We should create a default model if one is not supplied. self.SetTable(model._grid_table_base, True) model.on_trait_change(self._on_model_changed, 'model_changed') wx.grid.EVT_GRID_CELL_CHANGE(self, self._on_cell_change) wx.grid.EVT_GRID_SELECT_CELL(self, self._on_select_cell) # This starts the cell editor on a double-click as well as on a second # click. wx.grid.EVT_GRID_CELL_LEFT_DCLICK(self, self._on_cell_left_dclick) # This pops up a context menu. #wx.grid.EVT_GRID_CELL_RIGHT_CLICK(self, self._on_cell_right_click) # We handle key presses to change the behavior of the and # keys to make manual data entry smoother. wx.EVT_KEY_DOWN(self, self._on_key_down) # Initialize the row and column models. self._initialize_rows(model) self._initialize_columns(model) self._initialize_fonts() return def _initialize_fonts(self): """ Initialize the label fonts. """ self.SetLabelFont(wx.Font(8, wx.SWISS, wx.NORMAL, wx.BOLD)) self.SetGridLineColour("blue") self.SetColLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.SetRowLabelAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE) return def _initialize_rows(self, model): """ Initialize the row headers. """ if not model.show_row_headers: self.SetRowLabelSize(0) else: for index, row in enumerate(model.rows): if row.readonly: attr = wx.grid.GridCellAttr() attr.SetReadOnly() attr.SetRenderer(None) attr.SetBackgroundColour('linen') self.SetRowAttr(index, attr) return def _initialize_columns(self, model): """ Initialize the column headers. """ if not model.show_column_headers: self.SetColLabelSize(0) else: for index, column in enumerate(model.columns): if column.readonly: attr = wx.grid.GridCellAttr() attr.SetReadOnly() attr.SetRenderer(None) attr.SetBackgroundColour('linen') self.SetColAttr(index, attr) return ########################################################################### # wx event handlers. ########################################################################### def _on_cell_change(self, evt): """ Called when the contents of a cell have been changed. """ row = evt.GetRow() col = evt.GetCol() ##print 'Cell changed at', row, col value = self.GetTable().GetValue(row, col) ##print 'New value', value ##print 'Type', type(value) evt.Skip() return def _on_select_cell(self, evt): """ Called when the user has moved to another cell. """ ##row = evt.GetRow() ##col = evt.GetCol() ##print 'Cell selected at', row, col evt.Skip() return def _on_cell_left_dclick(self, evt): """ Called when the left mouse button was double-clicked. From the wxPython demo code:- 'I do this because I don't like the default behaviour of not starting the cell editor on double clicks, but only a second click.' Fair enuff! """ if self.CanEnableCellControl(): self.EnableCellEditControl() return def _on_cell_right_click(self, evt): """ Called when a right click occurred in a cell. """ row = evt.GetRow() # The last row in the table is not part of the actual data, it is just # there to allow the user to enter a new row. Hence they cannot delete # it! if row < self.GetNumberRows() - 1: # Complete the edit on the current cell. self.DisableCellEditControl() # Make the row the ONLY one selected. self.SelectRow(row) # Popup a context menu allowing the user to delete the row. menu = wx.Menu() menu.Append(101, "Delete Row") wx.EVT_MENU(self, 101, self._on_delete_row) self.PopupMenu(menu, evt.GetPosition()) return def _on_key_down(self, evt): """ Called when a key is pressed. """ # This changes the behaviour of the and keys to make # manual data entry smoother! # # Don't change the behavior if the key is pressed as this # has meaning to the edit control. key_code = evt.GetKeyCode() if key_code == wx.WXK_RETURN and not evt.ControlDown(): self._move_to_next_cell(evt.ShiftDown()) elif key_code == wx.WXK_TAB and not evt.ControlDown(): if evt.ShiftDown(): self._move_to_previous_cell() else: self._move_to_next_cell() else: evt.Skip() return def _on_delete_row(self, evt): """ Called when the 'Delete Row' context menu item is selected. """ # Get the selected row (there must be exactly one at this point!). selected_rows = self.GetSelectedRows() if len(selected_rows) == 1: self.DeleteRows(selected_rows[0], 1) return ########################################################################### # Trait event handlers. ########################################################################### def _on_model_changed(self, message): """ Called when the model has changed. """ self.BeginBatch() self.ProcessTableMessage(message) self.EndBatch() return ########################################################################### # 'Grid' interface. ########################################################################### def Reset(self): print 'Reset' #attr = grid.GridCellAttr() #renderer = MyRenderer() #attr.SetRenderer(renderer) #self.SetColSize(0, 50) #self.SetColAttr(0, attr) self.ForceRefresh() return def ResetView(self, grid): """ (wxGrid) -> Reset the grid view. Call this to update the grid if rows and columns have been added or deleted """ print '*************************VirtualModel.reset_view' grid = self grid.BeginBatch() for current, new, delmsg, addmsg in [ (self._rows, self.GetNumberRows(), GRIDTABLE_NOTIFY_ROWS_DELETED, GRIDTABLE_NOTIFY_ROWS_APPENDED), (self._cols, self.GetNumberCols(), GRIDTABLE_NOTIFY_COLS_DELETED, GRIDTABLE_NOTIFY_COLS_APPENDED), ]: if new < current: msg = GridTableMessage(self,delmsg,new,current-new) grid.ProcessTableMessage(msg) elif new > current: msg = GridTableMessage(self,addmsg,new-current) grid.ProcessTableMessage(msg) self.UpdateValues(grid) grid.EndBatch() self._rows = self.GetNumberRows() self._cols = self.GetNumberCols() # update the renderers # self._updateColAttrs(grid) # self._updateRowAttrs(grid) too expensive to use on a large grid # update the scrollbars and the displayed part of the grid grid.AdjustScrollbars() grid.ForceRefresh() return ########################################################################### # Protected interface. ########################################################################### def _move_to_next_cell(self, expandSelection=False): """ Move to the 'next' cell. """ # Complete the edit on the current cell. self.DisableCellEditControl() # Try to move to the next column. success = self.MoveCursorRight(expandSelection) # If the move failed then we must be at the end of a row. if not success: # Move to the first column in the next row. newRow = self.GetGridCursorRow() + 1 if newRow < self.GetNumberRows(): self.SetGridCursor(newRow, 0) self.MakeCellVisible(newRow, 0) else: # This would be a good place to add a new row if your app # needs to do that. pass return success def _move_to_previous_cell(self, expandSelection=False): """ Move to the 'previous' cell. """ # Complete the edit on the current cell. self.DisableCellEditControl() # Try to move to the previous column (without expanding the current # selection). success = self.MoveCursorLeft(expandSelection) # If the move failed then we must be at the start of a row. if not success: # Move to the last column in the previous row. newRow = self.GetGridCursorRow() - 1 if newRow >= 0: self.SetGridCursor(newRow, self.GetNumberCols() - 1) self.MakeCellVisible(newRow, self.GetNumberCols() - 1) return #### EOF ###################################################################### pyface-4.1.0/pyface/util/grid/__init__.py0000644000175100001440000000120011674463363021234 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/util/grid/grid_model.py0000644000175100001440000002037011674463363021613 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A model that provides data for a grid. """ # Major package imports. import wx from wx.grid import PyGridTableBase, GridTableMessage, GRIDTABLE_NOTIFY_ROWS_APPENDED # Enthought library imports. from traits.api import Any, Bool, HasTraits, Trait, Event, List from traits.standard import false_trait, true_trait # Local imports. from grid_column import GridColumn from grid_row import GridRow class GridModel(HasTraits): """ A model that provides data for a grid. """ # fixme : factor this default model into "SimpleGridModel" or similar # An optional 2-dimensional list/array containing the grid data. data = Any # The rows in the model. rows = List(GridRow) # The columns in the model. columns = List(GridColumn) # Show row headers? show_row_headers = Bool(True) # Show column headers? show_column_headers = Bool(True) # Fired when the data in the model has changed. model_changed = Event def __init__(self, **traits): """ Create a new grid model. """ # Base class constructors. HasTraits.__init__(self, **traits) # The wx virtual table hook. self._grid_table_base = _GridTableBase(self) if len(self.columns) == 0 and self.data is not None: print "Building default table column model" columns = [] # Assume data is rectangular and use the length of the first row. for i in range(len(self.data[0])): columns.append(GridColumn(label=str(i))) self.columns = columns return ########################################################################### # 'wxPyGridTableBase' interface. ########################################################################### def GetNumberRows(self): """ Return the number of rows in the model. """ return len(self.data) def GetNumberCols(self): """ Return the number of columns in the model. """ return len(self.columns) def IsEmptyCell(self, row, col): """ Is the specified cell empty? """ try: return not self.data[row][col] except IndexError: return True # Get/Set values in the table. The Python versions of these methods can # handle any data-type, (as long as the Editor and Renderer understands the # type too,) not just strings as in the C++ version. def GetValue(self, row, col): """ Get the value at the specified row and column. """ try: return self.data[row][col] except IndexError: pass return '' def SetValue(self, row, col, value): """ Set the value at the specified row and column. """ label = self.GetColLabelValue(col) try: self.data[row][col] = value except IndexError: # Add a new row. self.data.append([0] * self.GetNumberCols()) self.data[row][col] = value # Tell the grid that we've added a row. # # N.B wxGridTableMessage(table, whatWeDid, howMany) message = GridTableMessage( self, GRIDTABLE_NOTIFY_ROWS_APPENDED, 1 ) # Trait event notification. self.model_changed = message return def GetRowLabelValue(self, row): """ Called when the grid needs to display a row label. """ return str(row) def GetColLabelValue(self, col): """ Called when the grid needs to display a column label. """ return self.columns[col].label def GetTypeName(self, row, col): """ Called to determine the kind of editor/renderer to use. This doesn't necessarily have to be the same type used natively by the editor/renderer if they know how to convert. """ return self.columns[col].type def CanGetValueAs(self, row, col, type_name): """ Called to determine how the data can be fetched. This allows you to enforce some type-safety in the grid. """ column_typename = self.GetTypeName(row, col) return type_name == column_typename def CanSetValueAs(self, row, col, type_name): """ Called to determine how the data can be stored. This allows you to enforce some type-safety in the grid. """ return self.CanGetValueAs(row, col, type_name) def DeleteRows(self, pos, num_rows): """ Called when the view is deleting rows. """ del self.data[pos:pos + num_rows] # Tell the grid that we've deleted some rows. # # N.B Because of a bug in wxPython we have to send a "rows appended" # --- message with a negative number, instead of the "rows deleted" # message 8^() TINSTAFS! message = GridTableMessage( self, GRIDTABLE_NOTIFY_ROWS_APPENDED, -num_rows ) # Trait event notification. self.model_changed = message return True class _GridTableBase(PyGridTableBase): """ A model that provides data for a grid. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, model): """ Creates a new table base. """ # Base class constructor. PyGridTableBase.__init__(self) # The Pyface model that provides the data. self.model = model return ########################################################################### # 'wxPyGridTableBase' interface. ########################################################################### def GetNumberRows(self): """ Return the number of rows in the model. """ return self.model.GetNumberRows() def GetNumberCols(self): """ Return the number of columns in the model. """ return self.model.GetNumberCols() def IsEmptyCell(self, row, col): """ Is the specified cell empty? """ return self.model.IsEmptyCell(row, col) def GetValue(self, row, col): """ Get the value at the specified row and column. """ return self.model.GetValue(row, col) def SetValue(self, row, col, value): """ Set the value at the specified row and column. """ return self.model.SetValue(row, col, value) def GetRowLabelValue(self, row): """ Called when the grid needs to display a row label. """ return self.model.GetRowLabelValue(row) def GetColLabelValue(self, col): """ Called when the grid needs to display a column label. """ return self.model.GetColLabelValue(col) def GetTypeName(self, row, col): """ Called to determine the kind of editor/renderer to use. This doesn't necessarily have to be the same type used natively by the editor/renderer if they know how to convert. """ return self.model.GetTypeName(row, col) def CanGetValueAs(self, row, col, type_name): """ Called to determine how the data can be fetched. This allows you to enforce some type-safety in the grid. """ return self.model.CanGetValueAs(row, col, type_name) def CanSetValueAs(self, row, col, type_name): """ Called to determine how the data can be stored. This allows you to enforce some type-safety in the grid. """ return self.model.CanSetValueAs(row, col, type_name) def DeleteRows(self, pos, num_rows): """ Called when the view is deleting rows. """ return self.model.DeleteRows(pos, num_rows) #### EOF ###################################################################### pyface-4.1.0/pyface/util/grid/grid_row.py0000644000175100001440000000206611674463363021324 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A description of a row in a grid. """ # Enthought library imports. from traits.api import HasTraits from traits.standard import false_trait, true_trait class GridRow(HasTraits): """ A description of a row in a grid. """ def __init__(self, row_data): """ Create a new row. """ self.__dict__.update(row_data) return #### EOF ###################################################################### pyface-4.1.0/pyface/util/__init__.py0000644000175100001440000000120011674463363020307 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ pyface-4.1.0/pyface/util/font_helper.py0000644000175100001440000000245511674463363021072 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Utility functions for working with wx Fonts. """ # Major package imports. import wx def new_font_like(font, **kw): """ Creates a new font, like another one, only different. Maybe. """ point_size = kw.get('point_size', font.GetPointSize()) family = kw.get('family', font.GetFamily()) style = kw.get('style', font.GetStyle()) weight = kw.get('weight', font.GetWeight()) underline = kw.get('underline', font.GetUnderlined()) face_name = kw.get('face_name', font.GetFaceName()) return wx.Font(point_size, family, style, weight, underline, face_name) ### EOF ####################################################################### pyface-4.1.0/pyface/__init__.py0000644000175100001440000000150211674463363017337 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005-2011, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Reusable MVC-based components for Traits-based applications. Part of the TraitsGUI project of the Enthought Tool Suite. """ __version__ = '4.1.0' __requires__ = [ 'traits', ] pyface-4.1.0/pyface/directory_dialog.py0000644000175100001440000000167111674463363021132 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of a dialog that allows the user to browse for a directory. """ # Import the toolkit specific version. from toolkit import toolkit_object DirectoryDialog = toolkit_object('directory_dialog:DirectoryDialog') #### EOF ###################################################################### pyface-4.1.0/pyface/python_shell.py0000644000175100001440000000162111674463363020312 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The implementation of an interactive Python shell. """ # Import the toolkit specific version. from toolkit import toolkit_object PythonShell = toolkit_object('python_shell:PythonShell') #### EOF ###################################################################### pyface-4.1.0/pyface/key_pressed_event.py0000644000175100001440000000121711674463363021321 0ustar ischnellusers00000000000000""" The event that is generated when a key is pressed. """ # Enthought library imports. from traits.api import Bool, HasTraits, Int, Any class KeyPressedEvent(HasTraits): """ The event that is generated when a key is pressed. """ #### 'KeyPressedEvent' interface ########################################## # Is the alt key down? alt_down = Bool # Is the control key down? control_down = Bool # Is the shift key down? shift_down = Bool # The keycode. key_code = Int # The original toolkit specific event. event = Any #### EOF ###################################################################### pyface-4.1.0/pyface/window.py0000644000175100001440000000161311674463363017112 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The abstract implementation of all pyface top-level windows. """ # Import the toolkit specific version. from toolkit import toolkit_object Window = toolkit_object('window:Window') #### EOF ###################################################################### pyface-4.1.0/pyface/i_confirmation_dialog.py0000644000175100001440000000414311674463363022123 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The interface for a dialog that prompts the user for confirmation. """ # Enthought library imports. from traits.api import Bool, Enum, Instance, Unicode # Local imports. from constant import CANCEL, NO, YES from i_dialog import IDialog from image_resource import ImageResource class IConfirmationDialog(IDialog): """ The interface for a dialog that prompts the user for confirmation. """ #### 'IConfirmationDialog' interface ###################################### # Should the cancel button be displayed? cancel = Bool(False) # The default button. default = Enum(NO, YES, CANCEL) # The image displayed with the message. The default is toolkit specific. image = Instance(ImageResource) # The message displayed in the body of the dialog (use the inherited # 'title' trait to set the title of the dialog itself). message = Unicode # Some informative text to display below the main message informative = Unicode # Some additional details that can be exposed by the user detail = Unicode # The label for the 'no' button. The default is toolkit specific. no_label = Unicode # The label for the 'yes' button. The default is toolkit specific. yes_label = Unicode class MConfirmationDialog(object): """ The mixin class that contains common code for toolkit specific implementations of the IConfirmationDialog interface. """ #### EOF ###################################################################### pyface-4.1.0/pyface/split_application_window.py0000644000175100001440000000254411674463363022714 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A window that is split in two either horizontally or vertically. """ # Local imports. from application_window import ApplicationWindow from split_widget import SplitWidget class SplitApplicationWindow(ApplicationWindow, SplitWidget): """ A window that is split in two either horizontally or vertically. """ ########################################################################### # Protected 'ApplicationWindow' interface. ########################################################################### def _create_contents(self, parent): """ Creates the window contents. """ return self._create_splitter(parent) #### EOF ###################################################################### pyface-4.1.0/pyface/multi_toolbar_window.py0000644000175100001440000001316711674463363022055 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A top-level application window that supports multiple toolbars. """ from __future__ import absolute_import # Major package imports. import wx # Enthought library imports from pyface.action.api import ToolBarManager from traits.api import Trait, TraitDict, TraitEnum, TraitList # Local imports from .application_window import ApplicationWindow class MultiToolbarWindow(ApplicationWindow): """ A top-level application window that supports multiple toolbars. The multi-toolbar window has support for a menu bar, status bar, and multiple toolbars (all of which are optional). """ # The toolbars in the order they were added to the window. _tool_bar_managers = Trait([], TraitList(Trait(ToolBarManager))) # Map of toolbar to screen location. _tool_bar_locations = Trait({}, TraitDict(Trait(ToolBarManager), TraitEnum('top', 'bottom', 'left', 'right'))) ########################################################################### # Protected 'Window' interface. ########################################################################### def _create_contents(self, parent): panel = super(MultiToolbarWindow, self)._create_contents(parent) self._create_trim_widgets(parent) return panel def _create_trim_widgets(self, parent): # The frame's icon. self._set_window_icon() # Add the (optional) menu bar. self._create_menu_bar(parent) # Add the (optional) status bar. self._create_status_bar(parent) # Add the (optional) tool bars. self.sizer = self._create_tool_bars(parent) return def _create_tool_bars(self, parent): """ Create the tool bars for this window. """ if len(self._tool_bar_managers) > 0: # Create a top level sizer to handle to main layout and attach # it to the parent frame. self.main_sizer = sizer = wx.BoxSizer(wx.VERTICAL) parent.SetSizer(sizer) parent.SetAutoLayout(True) for tool_bar_manager in self._tool_bar_managers: location = self._tool_bar_locations[tool_bar_manager] sizer = self._create_tool_bar(parent, sizer, tool_bar_manager, location) return sizer return None def _create_tool_bar(self, parent, sizer, tool_bar_manager, location): """ Create and add the toolbar to the parent window at the specified location. Returns the sizer where the remaining content should be added. For 'top' and 'left' toolbars, we can return the same sizer that contains the toolbar, because subsequent additions will be added below or to the right of those toolbars. For 'right' and 'bottom' toolbars, we create a spacer toolbar to hold the content. """ tool_bar = tool_bar_manager.create_tool_bar(parent) if location == 'top': child_sizer = wx.BoxSizer(wx.VERTICAL) child_sizer.Add(tool_bar, 0, wx.ALL | wx.ALIGN_LEFT | wx.EXPAND) sizer.Add(child_sizer, 1, wx.ALL | wx.EXPAND) if location == 'bottom': toolbar_sizer = wx.BoxSizer(wx.VERTICAL) # Add the placeholder for the content before adding the toolbar. child_sizer = self._create_content_spacer(toolbar_sizer) # Add the tool bar. toolbar_sizer.Add(tool_bar, 0, wx.ALL | wx.ALIGN_TOP | wx.EXPAND) sizer.Add(toolbar_sizer, 1, wx.ALL | wx.EXPAND) if location == 'left': child_sizer = wx.BoxSizer(wx.HORIZONTAL) child_sizer.Add(tool_bar, 0, wx.ALL | wx.ALIGN_TOP | wx.EXPAND) sizer.Add(child_sizer, 1, wx.ALL | wx.EXPAND) if location == 'right': toolbar_sizer = wx.BoxSizer(wx.HORIZONTAL) # Add the placeholder for the content before adding the toolbar. child_sizer = self._create_content_spacer(toolbar_sizer) # Add the tool bar. toolbar_sizer.Add(tool_bar, 0, wx.ALL | wx.ALIGN_TOP | wx.EXPAND) sizer.Add(toolbar_sizer, 1, wx.ALL | wx.EXPAND) return child_sizer def _create_content_spacer(self, sizer): spacer = wx.BoxSizer(wx.VERTICAL) sizer.Add(spacer, 1, wx.ALL | wx.EXPAND) return spacer ########################################################################### # Public MultiToolbarWindow interface ########################################################################### def add_tool_bar(self, tool_bar_manager, location='top'): """ Add a toolbar in the specified location. Valid locations are 'top', 'bottom', 'left', and 'right' """ self._tool_bar_managers.append(tool_bar_manager) self._tool_bar_locations[tool_bar_manager] = location #### EOF ###################################################################### pyface-4.1.0/pyface/dialog.py0000644000175100001440000000160111674463363017037 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ The abstract implementation of all pyface dialogs. """ # Import the toolkit specific version. from toolkit import toolkit_object Dialog = toolkit_object('dialog:Dialog') #### EOF ###################################################################### pyface-4.1.0/pyface/progress_dialog.py0000644000175100001440000000165111674463363020770 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A simple progress dialog window which allows itself to be updated """ # Import the toolkit specific version. from toolkit import toolkit_object ProgressDialog = toolkit_object('progress_dialog:ProgressDialog') #### EOF ###################################################################### pyface-4.1.0/pyface/clipboard.py0000644000175100001440000000154611674463363017547 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Evan Patterson # Date: 06/26/09 #------------------------------------------------------------------------------ """ The interface for manipulating the toolkit clipboard. """ # Import the toolkit specific version from toolkit import toolkit_object Clipboard = toolkit_object('clipboard:Clipboard') # Create a singleton clipboard object for convenience clipboard = Clipboard() pyface-4.1.0/pyface/xrc_dialog.py0000644000175100001440000000750411674463363017723 0ustar ischnellusers00000000000000#----------------------------------------------------------------------------- # # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # Author: Scott Swarts # #----------------------------------------------------------------------------- """A dialog that is loaded from an XRC resource file. """ from __future__ import absolute_import # Standard library imports. import os.path # Major packages. import wx import wx.xrc # Enthought library imports from traits.api import Instance, Str import traits.util.resource # Local imports. from .dialog import Dialog ############################################################################## # class 'XrcDialog' ############################################################################## class XrcDialog(Dialog): """A dialog that is loaded from an XRC resource file. """ ########################################################################## # Traits ########################################################################## ### 'XrcDialog' interface ############################################ # Path to the xrc file relative to the class's module xrc_file = Str # The ID of the dialog in the file id = Str("dialog") # The resource object resource = Instance(wx.xrc.XmlResource) ########################################################################## # 'Dialog' interface ########################################################################## def _create_control(self, parent): """ Creates the dialog and loads it in from the resource file. """ classpath = traits.util.resource.get_path( self ) path = os.path.join( classpath, self.xrc_file ) self.resource = wx.xrc.XmlResource( path ) return self.resource.LoadDialog(parent, self.id) def _create_contents(self, dialog): """ Calls add_handlers. The actual content is created in _create_control by loading a resource file. """ # Wire up the standard buttons # We change the ID on OK and CANCEL to the standard ids # so we get the default behavior okbutton = self.XRCCTRL("OK") if okbutton is not None: # Change the ID and set the handler okbutton.SetId(wx.ID_OK) wx.EVT_BUTTON(self.control, okbutton.GetId(), self._on_ok) cancelbutton = self.XRCCTRL("CANCEL") if cancelbutton is not None: # Change the ID and set the handler cancelbutton.SetId(wx.ID_CANCEL) wx.EVT_BUTTON(self.control, cancelbutton.GetId(), self._on_cancel) helpbutton = self.XRCCTRL("HELP") if helpbutton is not None: wx.EVT_BUTTON(self.control, helpbutton.GetId(), self._on_help) self._add_handlers() ########################################################################## # 'XrcDialog' interface ########################################################################## def XRCID(self, name): """ Returns the numeric widget id for the given name. """ return wx.xrc.XRCID(name) def XRCCTRL(self, name): """ Returns the control with the given name. """ return self.control.FindWindowById(self.XRCID(name)) def set_validator(self, name, validator): """ Sets the validator on the named control. """ self.XRCCTRL(name).SetValidator(validator) ########################################################################## # 'XrcDialog' protected interface ########################################################################## def _add_handlers(self): """ Override to add event handlers. """ return #### EOF ###################################################################### pyface-4.1.0/pyface/mdi_window_menu.py0000644000175100001440000001311511674463363020767 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A menu that mimics the standard MDI window menu. This is the menu that has the tile/cascade actions etc. """ # Enthought library imports. from traits.api import Str # Local imports. from action.api import MenuManager, Separator, WindowAction class Cascade(WindowAction): """ Cascades the windows. """ #### 'Action' interface ################################################### name = Str("Ca&scade") tooltip = Str("Cascade the windows") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Cascades the windows. """ self.window.control.Cascade() return class Tile(WindowAction): """ Tiles the windows horizontally. """ #### 'Action' interface ################################################### name = Str("&Tile") tooltip = Str("Tile the windows") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Tiles the windows horizontally. """ self.window.control.Tile() return class ArrangeIcons(WindowAction): """ Arranges the icons. """ #### 'Action' interface ################################################### name = Str("&Arrange Icons") tooltip = Str("Arrange the icons") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Arranges the icons. """ self.window.control.ArrangeIcons() return class Next(WindowAction): """ Activates the next window. """ #### 'Action' interface ################################################### name = Str("&Next") tooltip = Str("Activate the next window") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Activates the next window. """ self.window.control.ActivateNext() return class Previous(WindowAction): """ Activates the previous window. """ #### 'Action' interface ################################################### name = Str("&Previous") tooltip = Str("Activate the previous window") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Activates the previous window. """ self.window.control.ActivatePrevious() return class Close(WindowAction): """ Closes the current window. """ #### 'Action' interface ################################################### name = Str("&Close") tooltip = Str("Close the current window") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Closes the current window. """ page = self.window.control.GetActiveChild() if page is not None: page.Close() return class CloseAll(WindowAction): """ Closes all of the child windows. """ #### 'Action' interface ################################################### name = Str("Close A&ll") tooltip = Str("Close all of the windows.") ########################################################################### # 'Action' interface. ########################################################################### def perform(self, event): """ Closes the child windows. """ for page in self.window.control.GetChildren(): page.Close() return class MDIWindowMenu(MenuManager): """ A menu that mimics the standard MDI window menus. This is the menu that has the tile/cascade actions etc. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, window): """ Creates a new MDI window menu. """ # Base class constructor. super(MDIWindowMenu, self).__init__( Cascade(window=window), Tile(window=window), Separator(), ArrangeIcons(window=window), Next(window=window), Previous(window=window), Close(window=window), CloseAll(window=window), name = '&Window' ) #### EOF ###################################################################### pyface-4.1.0/README.rst0000644000175100001440000000122511674463363015450 0ustar ischnellusers00000000000000========================================== pyface: traits-capable windowing framework ========================================== The pyface project contains a toolkit-independent GUI abstraction layer, which is used to support the "visualization" features of the Traits package. Thus, you can write code in terms of the Traits API (views, items, editors, etc.), and let pyface and your selected toolkit and back-end take care of the details of displaying them. The following GUI backends are supported: - wxPython - PyQt - PySide Prerequisites ------------- If you want to run pyface, you must also install `traits `_. pyface-4.1.0/setup.py0000644000175100001440000000310311674463363015470 0ustar ischnellusers00000000000000# Copyright (c) 2008-2011 by Enthought, Inc. # All rights reserved. from os.path import join from setuptools import setup, find_packages info = {} execfile(join('pyface', '__init__.py'), info) setup( name = 'pyface', version = info['__version__'], url = 'http://code.enthought.com/projects/traits_gui', author = 'David C. Morrill, et al.', author_email = 'dmorrill@enthought.com', classifiers = [c.strip() for c in """\ Development Status :: 5 - Production/Stable Intended Audience :: Developers Intended Audience :: Science/Research License :: OSI Approved :: BSD License Operating System :: MacOS Operating System :: Microsoft :: Windows Operating System :: OS Independent Operating System :: POSIX Operating System :: Unix Programming Language :: Python Topic :: Scientific/Engineering Topic :: Software Development Topic :: Software Development :: Libraries """.splitlines() if len(c.split()) > 0], description = 'traits-capable windowing framework', long_description = open('README.rst').read(), download_url = ('http://www.enthought.com/repo/ets/pyface-%s.tar.gz' % info['__version__']), install_requires = info['__requires__'], license = 'BSD', maintainer = 'ETS Developers', maintainer_email = 'enthought-dev@enthought.com', package_data = { '': ['images/*'], }, packages = find_packages(), platforms = ["Windows", "Linux", "Mac OS-X", "Unix", "Solaris"], zip_safe = False, ) pyface-4.1.0/image_LICENSE_Nuvola.txt0000644000175100001440000005664511674463363020312 0ustar ischnellusers00000000000000GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: * a) The modified work must itself be a software library. * b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. * c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. * d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: * a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) * b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. * c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. * d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. * e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: * a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. * b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. pyface-4.1.0/examples/0000755000175100001440000000000011674463363015577 5ustar ischnellusers00000000000000pyface-4.1.0/examples/tree_viewer.py0000644000175100001440000000603611674463363020476 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tree viewer example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, PythonShell, SplitApplicationWindow from traits.api import Float, Str # Local imports. from file_tree_viewer import FileTreeViewer from file_sorters import FileSorter class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the panel is split. direction = Str('vertical') ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the style. """ self._tree_viewer = FileTreeViewer( parent, input=os.path.abspath(os.curdir), sorter=FileSorter() ) self._tree_viewer.on_trait_change(self._on_tree_anytrait_changed) return self._tree_viewer.control def _create_rhs(self, parent): """ Creates the right hand side or bottom depending on the style. """ self._python_shell = PythonShell(parent) self._python_shell.bind('widget', self._tree_viewer) self._python_shell.bind('w', self._tree_viewer) return self._python_shell.control ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_tree_anytrait_changed(self, viewer, trait_name, old, new): """ Called when any trait on the tree has changed. """ print 'trait', trait_name, 'value', new return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/ipython_shell.py0000644000175100001440000000320511674463363021032 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # #------------------------------------------------------------------------------ """ IPython widget example. """ # Enthought library imports. from pyface.api import ApplicationWindow, GUI from pyface.ipython_widget import IPythonWidget class MainWindow(ApplicationWindow): """ The main application window. """ #### 'IWindow' interface ################################################## # The size of the window. size = (640, 480) # The window title. title = 'IPython' ########################################################################### # Protected 'IApplication' interface. ########################################################################### def _create_contents(self, parent): """ Create the editor. """ self._shell = IPythonWidget(parent) return self._shell.control # Application entry point. if __name__ == '__main__': # Create the GUI. gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/mdi_application_window.py0000644000175100001440000000427011674463363022677 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Application window example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import MDIApplicationWindow, MDIWindowMenu, GUI from pyface.action.api import Action, MenuManager, MenuBarManager class MainWindow(MDIApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self.close), name = '&File', ), MDIWindowMenu(self) ) # Set the size of the window self.size = (640, 480) return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Add some child windows for i in range(2): child = window.create_child_window() child.Show() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/images/0000755000175100001440000000000011674463363017044 5ustar ischnellusers00000000000000pyface-4.1.0/examples/images/closed_folder_24x24.png0000644000175100001440000000213511674463363023222 0ustar ischnellusers00000000000000PNG  IHDRw=+tEXtCreation TimeDo 21 Nov 2002 11:45:45 +0100tIME &^. pHYs B4gAMA aIDATxڥUKo[E=k'mb"AD% HEW{$l*,@-laQ ViZ*qmㄼln;ޱ}ݘ>`;;7c?Կ7xt_+W+֦ Hgxf҅{nkZ,_|EX(#>n\QY0˄O+`<7c1Vum>,8"ͅ|Q' tqw?p(!fN%"pgX9y:J6c_~ ZXp*@R9#R(|?K)giUQ/,|WWϬ>Na7|C#aM>H9AcQזD8&9;xd"qvYNX%B "3`=JX$#W`>LqCr:BqeD!YH Rm0E0:)ƩI P E'0"s}f7ˤ:s$''v@,U%jN.2-ր@}NBzX$<',  }۳`@h(ly׉%„?uMъ۴F:HȪa&yQz wwjwv|os@6 2 x&bvvJ]6׎poyxZڇHyh@ ? qs1 31#~I0r ܊@2x/!W؀C@>ӿ>  q2N1H; h(#+Xُ[{`W]h3j?5ge`bv;5\ۇ p/wyW8D H~aՎ )@C?$a/ o~%H3_ 4~<?°7b? ? `Qǁ@ x3î? V z+~lalH!PHw :t @۫ D@L cĠ&D@?y(8^ `DC @L3(0IENDB`pyface-4.1.0/examples/images/splash.png0000644000175100001440000012110311674463363021042 0ustar ischnellusers00000000000000PNG  IHDRBƓsRGB pHYs  tIME .l IDATxg]u&|rN7߾@ $۴lkF'W[嚩o!-ɲFV%iH$DhvߜONuH X$49ggֳE?'00000P````` .&\ Lp100000b```` .&\ Lp100000b```` .&\ Lp10000b```` .&\ Lp10000b````` .&\ Lp10000b````` .&\ Lp100000b```` .&\ Lp100000b````O0`{qI<؏yʿ„kK$Z̼C>?;8ׅ 㝃$U$I$IiAxqĝGL$IR4~p]ADqa@.&\wR,8Џ | Ð IeYa̹ O. C? p]1˲ 2 C4EQG.IqGQDaH0uaŸJSE MÒ8 C۶}uǴ0 DQ$A!ƇIRE4M M$8㸎3(bYV$I8gYbRCQ40ERT.E94 `YVeibŸ(bcYe j8.gΜy'~_ۚ٬(rGrseYKׯٶiS67u]dJGLB@Z7676d2Nzӟկi(&It1b}^x~{G(2E'Ν۩V_{uqld$IuOc_/0 CY"0<|NJ…W_\p0 ຈ8h(bi{J d\74 YyQ|ϗ.&\{IiXwzݮI*LS󆦕Rt>3/r뙌E(Bzs1}~qR=eY(!c~>=yF6q,,7΅T0瞻t"#ZmuuuyeV---}gn//C(kqoq~_zfgffN8q̙gϪr7666677o,-X\A 㶑 v^8|Cg|cz+˷o ]{իaqL4IQ{t½ZNKcMqt}\pt@Wzk;Cd0O:8777??OQVZ ͆QzA,7,Z8)xW xʆS>}ɓ'f^߭V~EQq 1 }Lޣ8\2 \e&Ξ>}=P.{嗷ժy_~$I,,]}t#\ė! l8twHL {x4vE,Ź'NLMMU*[nu^t:8/^} DIJmM=#izeyٱm"eY.J秦| m۶/_|c?>hG Uc$Mq,+J\;q\WVt:nmWzܹs0&AE9%]d(Dq*( ( (0 0 0v_E7NZP>I$n iZSR455555U.p8mu]|w4MZ}'mˊXQU0E" ajX,w]W%m{k{ޭ[^OVMeYf9X=b^˲CREG8.IQmGQha7.#XMQ M#`0ث׻Nnۃs]u=9NEYs|XBiPqGGQ5yiGp[8Nǩb64MN}ߧH2 áeЍs[DS. LF4a%0t]www 4( C(C&7z eYEQr\>f:ABm7N!xiahtƍ۷n T0d*F4EӐ)OL,,,/,vdWw7ADQi,I4M4MQa!"'Hm CDz\}q\ BHz,2qPDMh"anDIM4M4] |ZQ@D$2$I 5odA:v<)8 1 0 0,øs7.]8;_qǍs, K|UP!fެ_pfJϯAou zi8K2S]%)I2  qMG(ZQA0 hblqkGxyKR̨jm[ ~ )dD`p] ,K ( |#Y(0,˳,˲W^yŋi: a8AEIdYp<ϱ,˲.IFQFQﻞ繮y﷛__zI'z*յ0$!2$qp!gS]>G3[:8n)@&+_1! zضAQP ';q=b%Q#\!\yܼq[[?}7n:NYɒis.p "q]qؙ-Ӵu ޸tIQ'yP,i#T{\ٖ8r4M 7܉!Zm34DZpq{A%cº%09v3D s8M?zh8t'"eYT5dl.f ]u]eY Q~XlAxy\4ma"fgΟ?_qu]0dECG0޺wqИݎs,l8 $sAeLӤh`( xGm|T7Ճ@[9$|^g[V,M1 YpAxOkVE%uqU}?i|ϟS<m4˿a8'H7 15Ä.MQ$A^c{!=,p'͍ s4\iU (r\FliP-dIJZWɄ!>E((eysssy}}muVXZٮVACGitxׄű4Eyq$Nx)\ؒ$Ba41'\F`YV8e_x{ݮeYq 1bXT@\(<A\YrwXQ4DLTbSım+^[M b[cKIJ,J64#-|,2q:NZ]___]]]__7p8Dn 4P7 wP8 \ν[KDqb=K$8Шnkkk4|6UUնh.(Avc*չ@nzj<ϣiڶB020 A. t  kuo_ :Ǒp' [h4B0=55??????== ,~kkLDeVhIK ˲,"6MpguhjvvvVWWvwwp8 $IUU]~<ϧ;10>HN{Q2B<tmTgq>x.9NVi&E8V*L&nn `4p.=@2%38^hlnnnmm5 a/iC?Ltlb}zeeeeee}}}kkkwwv0 099 =AH )]8=ϲhiF=!viܹs^/&II4]t]e{Z```|870wqLM%_^:(5mzzgϞ}G]v֭e'?"o4'j/,,:lv=oV+ uijifɹ9Q4Mu0] y~<4M~?"I2\.72Y9[ QV!8 N¥iIx?ceyy8Q$L=sɓ']v7n4^gYTx9EQ^ީV=σ80, $8|qQрBX,OLm6-ZGnдl<1Q*3٬(컚u`b8&ø)*GcM,[zݮ$œ'O߸ysccC$0Evf[i(1-wL5żiop]ehX5P$'c f<ϋ }q Hr#}A1mć^$I"IҲ,uã.ѻG<{8qҍTo-: |0$ V2 ˖eA |&UU;ԓ?hSv(eYbX*ey0d˲GN{}Ny@lsz?֖cQ8C7GR}^k7QQ' `& p 0]1~;!$\>>%LlwE$"l]P<0$~Z%bh[` a[5f>2MǶ=Yib,XM[p\7 C* SPV YQUe2YY @] B\z)~ܹp$B25Um{jZǵ[7k YsDR*JAnq(iyeygeVWz ((aLOOOOOյ5׶0$HI$]zhǙJԷM MWu;Vj2,I155U{zQ2l( R _4բA,lo_z+V Ҡq2 N9SP8TL M ]72Ynܼ[~,Cn~4cMXQ$ ΦL …8(r,k[ֵ7.]\ĝD@ j4668EQUթO}SW^ݫV9eσcпr$ItZ\7"Iu]r`%*8eI5EQPd8NBT* R&Al{NiF>(4m\"aTfyh4:17wW=%XyJUIZ__vիWGá(Qa$n8NRO~vUxA%IeA8?3WtE++n޼uV|?L')j﷚M]oMyч~lFQLe9.f._VmEl\ike!]2b2y]ק&'gffzuu9( IDATK^HK 0%Yt 75M+uKj "IF_իi!B8eloE; g~S?Ob(MLC^y'ݏLl[$)i0&GEزp ØghضM a\.7$q;bcW0TE(dT2 Cea\%)NxCӧN[\im˂H/_KxSs3 ahzK>c+["˰6wڱ&pI5ŋWz4Mu ";0l:?WI6~ŷ._D++aQ<4<Ҕ4(认)KjY~,J ɲ(*"˲,˒$ArOO-w%eY]'''^$Y&HhI瑏pc_iZ\.EQ& Zf6˲TF"ַ_ Đ-8Ie|H(" $D6Rቂj[[zF?NE3$ a~8NQ10 l %WQAH)oa q/_|q4s\>=ybRf0(8>F~hMEY!/$I6" -!;s--]DҔ#bw5?W_=Dn*F˲Q!\- axA ,$@>@V)JT$Iwa;p;~={Dpn[VBm!OA;#KR48eYr&f2\+|d68-!ʞol<˫τ`qk:sqVY~}8Cqf! PUb\D^]ZeYiYmaȰ$I<[[g2w۲9`+?4IsJR*ЧMsP=`pT< 庮8 f Cuo,-'ш$IIUU% m[B:E? NA0 l9OafnE R(D\.m2A@5ĩ$rEn7"qڭ=>2%;(x*P|?p R%#T- N~W8&:ah,W]%vokt+{M`{{BrIlXD6_,elw8Nh+uÀX024q c *c̹G8/XQ*f" 4_@$>0mpDV=׿ U EE_DW\l6шaivvPy]acض ϑH(A.lp7=yu\ljX x?("S3?sq\. P,Pg\&z"~ ҀmYAXj޸t);PKҒp bh MXH2=&}PSAE x?99)rd3 D$QcCEHv2DG$k$AF,ġE( [Rb=G6/!y^^/Jz9LO?n{g<׿7/_zP$(J.fJ211Q,! 'V`^r\.PvwwAhۏ=+8&E6M wͦ9"'NONNE0`S+A=) $Tt:<@p C U4 LӴ- \@~ht *.a~k_ I4|&xH MG){,P@? 4Eٖh4OVvaY>qZg 0|fffuy9 ô=JOlm _jM b^x<ϱyA$J"gLw@"iyws4**t 4'P1aREUl2G#M{n+S`0|802q@m& i6ͱ5Er |tV`0v:zZ0 HT+-dNl.kALӴҰ8Ƕ5%ޭq~A<3e<pIMQEF#@m@P򁴛e/<>1s4Z(~8Q$\oAӧ_UǶ$vW.]F8Z^ߨ՜(1c:)Jy(gs98~n000Q.+AW0(L"z=qRWyi,;~o#VBDZ0Aq+Mb[Q676k_퍆8$I*SSSsssH,bӹyfG#@N1> :pA!۶MzFcww׶z?58BI_'$I8Y]]4 (RebbĉO>u"I7oބн^o4f! Jѱiptym^DE˲c/ (½܎{AA=$/b>4eYDt.#XS8A?} N?}#\8Q8ɓ'Lf@EA)"5˲dZVC㺁w8SEa<vXl>?}㺝nwS$2qw#iFRMQq#ߍA8h,1Lk%("+9n ';__76q]iDq7|TM+Jsss dZl۽^p S 9t0~u`@oj5ΤOGRZ=}rA+++kkk[[[z2>2"\]KRFe ]ׁs pZftf{6~29Yis!iVU(EQr8pP&GX#\T؀"I]vM7 ۲(& z>;dǞoln'޸xf;B&fzBR̝I鉛m,;QA/.\ϟ:}'ye۠?L8 B4?"J.P= %mQcaǒE B@_F_' v:I"֫dPao~I>Y,,,@tnnnzzz4]~}oo:7MxQ FhT&\cEG3€hq4 } zs`8$) }$: nܸa6^J%H}T*0n߾q=0wg",Dm _vR@_$lۮ;;;jl* 4 8Rs({?hug'}Uu#At@w:Q)vlhn6%)1?`(ށ׫۪~oݺt8V 1wrڅ}#rD%)e_`4at& D~dv= g ߏC"mAqep$ކäv l "l<.|JHuE DF#"48UU7 IQT!У4 8N7r`^н]hO{@?Q۶A٬VrX f9rl*y2&S.z i>CѨV`U[ D,f2v9A@yi,?$gF8V˶pD9'\ Q^Y/}K/q<`ѽ>`)Z$(pJǗEE뺮8`7|^h5>4]"88>kfoۍFV-DD)r GKu!]$m2lZկ~~n]Y%Q@dM #|{1==}ԩӧO/..„[nollv :Ó ٶm8Br9m:' *}۲0i QEӛ[[Ў! B6Ϳa(C#,D@Bazzb@˲%^Dzp87qf S6|OXuBn@0&ż@`p:NV]ZBBepH..AH3Zt {G.AںzZ&-˹|^I}w]Q$2EQhPh۵ZmwwVjt:tr ǁ(W.h |> ru]V$ b4]EUw>0yG7`0B7ɩ'ODEeo߾΄v:+N$I̜>}z~~> ۷o_~^d>W P0P{B^:#miax'% K՝sS I`GRhiԧ&''a븮iYw QǝnVᕫWvjj5؉ "pEWLNSspV !]E `y( %FVH Vj`i8Aa½ch35Gϟ=h< N pH3/ہ?JǼضQ=#b?*R6="YI"@f ٖeuF>CU̐;;0ڰXSH.3гŋxp8i$}nsO<8V521'O8qe۷oߺukee՝P,˟'N8y^߿u*R4+77b/qډݔ gj5HPQ @ fp]4 @a={駟X]]F '(4!=%.]lp00- - `xǛͦeYAhxV+0t]w86-777a 3C=c^~>I_/_R\rC yɭ@DnLf', 2ᦃ,AeEwsO>IQ8!t(AAuyt8(&t[āv#tvy//?فTq& eE_|[Q(yjYV܄1mY !+ @a+W QJq. r5MWa;; I#%S>/W|^aȒT*DQ\^^}͛7oݺ l4py晓'Ov:M45̛A緷=׵ms|>vt@u]t0yLqcjaTآ  Q#,"WtF8H6S{z^?%E %)*FsR8gwwwuuuyy;z:_O=<䷶` ɛ7o>ӟ?JRT*ؐ6CQEaVVVdYg8C]anVMV{/]qx 333P"3 cuu[(A.,,|,2WII/ (,%&bxW^ye8@} 9+mѳ+ pz,@{:M#+%4\ij'WU(: pcc`m#ad20yԡc_E02ݽG{lɋ/QD3 tV~\4߼}JB9 C5pOTҊǫc|3T’( s33[[p8("K*$VCMhZ핕孭-ZQ6cEQ>4۷oAL4E,{mۃV[0(I\.W(h!2q}pχ^j dtT*8oܼ9 |ߏRmXVz>lǁcq6X%H2C!>Ȳ$|/NMN4V3 Pa`97o VX\6!0zETԅV71Ea<8H#\`anooC8}ѧ~FDa㳡$~R,[k4ÅXVu]h8-sit,ce Fařr S(@6ͶAq.[YYt:4-˲rl.gd2΄yYTEY}A@D.C&TVEoZׄ.=1 0r,ˎR{/J|>(&"0MUQZiQz#W0, !;;;{70$?qwww߿oڕPAp><;+^(8AR%n|>σ݆a( D`juccUҥˇ~3f+^EU3`gY%IeUQοr |_ӴBUŴ0 Sc$%X6\A!Qmmu:NNM}ԩ7/_u&LeݟIHs.,?N* S(S)UUQ,VUEQ )P Zɟmkkk~}euUճl.fLFQK,K&j.d:^Ҧ( TuggRY@avm5o\d&Am.FB,(f Pl.W*KBb[R4&~@^0"*EN&j-0֢q~v}}^[ͲfR EWYTYp4vQ_֏p9 8iFTp8HSzA~? k4< nL9w|!j.I0u]JlW( _?%"`ÎADq<@b T}|+ZZ Qd|P,y0 *רcY(j6АvvC2y"]DZ-˶mdJn^t 3iPVHoRΝ+kkkNIDR(3(BiMR5 3͹6p!Ihx%A,+Jz&GQE^v@tݮyA|c7N$A9ibfXV$M!G%w;۽dIZZ&! _ iYL\LNK%!"taI0c`I`tƏێ&{ K2'(CU2Mu--Q4==pD!wj5[ph,˶,HN¢TfM èT*HEHȔ.,e9yovYs4e'| "95ݚWW+++{{^ EUsBT*˹|0 IQ2o4Z*.8nѐe"Ig^ ^ptْ$rrF!@ջh m2 *?y[= tTs,$G')<"}4 "5vAVɧ~W"Idgn~p śhR0gydcc< RĀ,ˆOo=#I=@aoo/]{J/ʆe,D295UJ{k0rե%qc(B>8p_酱JƗ5 e ׁYL&ͦ0A0h8V3xreYF0E>SC(J'&&8d9M¥#Aض -AȊR,'*DP@?q$`RzBkp+Mθs3$+)t}0R~N{8E47jɇ> Cu9rJ25U*3 ;d}K~:X ]=ЯQ K'P^\vs㤔GOu"bټu8O NGEa2wTx8pc߂a0@6hEg5MV *J%+ɰ@?DPu s@7 kU ۧ$mJ]ۆY^QQ1pHL<B\UӌLr\.xE$O1)eOs IiH1e0SOLȊ"4 /lEQh:(D1OLLLNME0 #-fGd&Ȳ~]xLQ.3 4/Qau]8j֊lvz‰0Q!+e=iVwZV:u{ԩb2 T4P*M$lj̍%7$9κDK Q#w83(EQ`"x9zy>**JuaTqڴ \DZm{Z9ۈQRQnfp0u:K0R$Iw~޷RofbP02){K Ri-䈑OBop8x>"u5"*ZҒEI 7 GGyd}}}8!rP(˅Rd仰ޛ6Ir硹oY p@F#,*ֵuo}8/t%KL*QI@bbl=oU]{Vey?Rٙ.E 1a݁^UğZXpD >vի׿ץrZI@" + a~rZ:h鴬(("4Lj (#~"g:rC,=N2~_I8&Xi66TUŀ5t0 .g pȲ狥҈p/,ϳt'iՁpEQ %ōy׶A&qjdEbt j6=߿kkk- #e%f{12^GL%0KKKjÐQ,ʕ zI-{,KKEČ1O)Y}ѱuL1īuD UAxVG|~yښ h1 I:ɥp;N>;<<-kֆA%qQ%;eNo8L!]}4lheY۶Gو6p9k53, *PrA%9are(fPaF-(x:ZAp;tpb@vT5׫J>cAN&!Od=r02LPu0}h몢ofIC7n89Ht'D! iPnnb\bbLJtI\oE8;H>[|wihˏDzEBgC2eOB ðP,޼qdD0<ێ3 9N@$Yf)H1\8%ĉH~z?s8r<n M2CšSQ@D"ZW2qj:6a}-…Ac&*YC˲,I@ЪEpv 1#z&r^_N](s8qnmJ2Qnj5?u]?@@.Y<σ0e!q t i&TZq0g%xΤm>̌eY~uZ",yE$R" sp\J_n%K&+1u&$l .J\.ж5M?տ_fsn~ޫV|*Iu/Ln򨏂uݦRzjuuusgFX,J|~bzw =ߑB{d qz4O_=G%{儿/Qł0q<]|O>8CЄtEpp](ED]M}b:zrQQƏ,cWr[.Tt:}hDQ$"ˑѽ S'b0405֨k̽Kwfv8J8k/1N.@ AEjel-5pl|?~vmh Sb'̄<8~7ziA*劥R>W|M+cp;>|5GE66OhGp'.Q[ߵz8.)˶4L: fDǵBwV+++BQ1qw&8rش`)G1p!b;NBgO麞ϲ$ˤ0' Jjqؽ3SEU<%IV~+Y^z[7oˆ7 !H!NMklhVHӌt8w{g.\7Ca2mTHa4011vZ?x~8zeBP,pkZ"~&ο>ˇAP4- Co6f4B*%h?J`,9.G#ʛLbMF+&Vc.x9fqd'HwĸK0r[-PibMXATC%o2TK?Nle! =Ț%az#uOI߆!\%qþ ’6b6ن%K>B p /-R&BTNqk٤MNOc! B8DǏ.jZ_^ZEq{glbP*idDDyb`R=%ikkkeyygoyx$0D6E:ҧD۱:48ffwYNLmhEl M& 1vB"YJG8xb_RazwhBryJrGD6 "rE>Kb1y 1!2JO5K~<#0t&9 ] cV'riXFIE eO鐗0d!T@ecY^FHH*tuq-- bJ JsrK{752\*}}=Ğ\!W^/\2J@cgn@O6{t>cecvI?G\BcMJH%YQ5{Zuٔ߹tk@7 QtDoߺuFnz&&'f q\O,R`"_&,{Gx8c Im#&@옃h A;qݥHjB_? DDEYY}F~>^~ş{ft:݌vB1+ 7,yX8H!z#qH3[Yl{"Bn)qAWKDv[ĸ >5u%A(±lD)X#<`G<$b_vvԅ`#$ɪ麑NnoZ|~a~͛fj0 .ZR@ɍ-6 ȉM>1oIV6)3?L1L5HOq 5=a(򢈉yq\(*H.ux+OYyzu4^pP)[ЅG* w?=FaN C!V m$#]t:MĪGp%ȜTɆaR$ ؔ} Qo,31UHJG0QB#; 0M`ի>S7>HRkBs_ۿ/ф[Xe&(.$Z[#&=gJRtyAC $Jx;UT) C׶/_x$KKΝv7nܸyf^7MI Ôe۶ %>< UQnVU%APd9i lo=B#{#=h+$|gg{J\I~n`܄Ni)U\{K.-Ђ)V0<ЫndYR3v,ўrPQ\$.1=kIu09ha߇<G QNzoSO=eY 0!9jQ}t:eat$YELd0(M w2o x_Q}{0 z_D(zĮ\m<2( OD7۱ wmU{\._>u쟡CBR0}_EϦ.Jl6 x`L9kh!yI%P\!쀗|8B~>:15NJ D OL4MWM }s\14;c۶׿:NHp ?3- 5i6ID=RNGζ&t:M(b\ IDATD{,īٶ 2[?3exknܸQF+Fz?Ӄ x؂wo5~aL6;;;?L\nZ筭?~ׯ*qæ8 *?u:jjTMKyA3 S,&H )# .c$-mtGb<?=T̨Ӂ He|>OFTH \. B5ADQ4-˕etA`јO)nnƲ,%"C61T0sGuMH*N3 1b&"4a\xaeA05Gj:??zr( qD;~Qedz|&b 압daЩ{~gy7I6A@?-Ngs9GO#-J5IEQ2L\}Ȩb(I8a8AϕNKb۵,kcccvf^aSʍ*}<YIR|8qQQM0 OfCZ`DB7|`qj8,KӴMĚeYoģ A\f4](`)l4!҈C޵ {ԣ(p<%/  Z-yY{'?ᥗ_}h ŹYAj:eErb".k`)qryaaaaaaffX,jY!IR&ӪpX3 #J(Gj$}8k_#5-(?60aŅ .ϗJ%0(BYnby}%4S#/ g4V&bG{Q>կ`;ȱ,OѪhZf].//ýaEI4 PPՔ[t$pib0lE\&$,{9H$U%D A/\$ ei5YLꈞ&g&wu]|w]uǟYAgY?I;a 1דՑ,2 ԏ ΐpeqLb(u9. }JVt#HxH&֘r <ኴoť(׵#Z frRV ?wz^,2XIEJHί8醡2ac# l1s$D!LEqgg}Ƕ0$)mry~~ܹsΝT*lVEooo;h0Q76|% 8*qNEpQ4m}}]xbD'cVBnȲyo!)@ih4 ܹs}e]#MZO.|'{0.RyR,$}rOO0tX,fs9UUpBI~;Cˊ&T2DP,+Z۷/]d;!ElŽxĜ ;h?A "q<3P`YҩT\P#a#8Z`4 ؔI~ NPrv7^8uܜ9>v$m۪zT(Gw2 #(FQj 0)s\>VӦG&L&#bEhN8uO>i4Me^b7'N3LA9n{kիN׳CKk F0fggL,l쬯mmm?N[Y/]8Qe)+&Q*4w :Nh^+dYe&$-a>x=B .\Vb.;88cVkZϟ? X3DޑzS(j C 8UU=ի9,-DQF;\2*,I}|wۖv1,(MӅB\.k: Œ 7@Y?\[7onom}&sΟ?h6]C8J4’62H$ݾu -KBL0AG LQ4:9jp1@ .<$Õe9LcY'A ] 3 F(pUJm/ o2v|S!޼""g@/DQVT*zJUM(naQ&sI+-WZ͵mIyQ`I= s\_ںuWz=q< 7ZӴL:!RV.A4JAIv Wnkf)]@uPwz \,DQJn#չ(|Aı3"p`pxxWȅ鸎qHS>J{Ir (DZ.? q>1ECY4MW_y[WWq$$޾ul$qx=\}a ]uT*>>~ߏXQUx8F{j0PO@l.8dm|^UUz:Ap=/ Cgҙ eUYZ͏?x{{]Tt\./////,,yGh]:#Jf3uǶ{вB~㏷67ۭcۊ,g3rR7$1ÄA88xmYQ.>BTy>"8;͛7֬`8-e6}NCJL!=LxLya #Z%y7|7&?VQrJ1K2i:y$ ƨ놑NX#2s0D m퍍}xKo|hZ-dErR)W()U$4/t|nh oQȲNgٙjܹsk U\PN(Hmh,2p0YQٙ-荔EP)`U9*07~X 3rEeEA%{{l4FmFM+3bVa 1qJ| ^ OGQjFXlnMs0؎32BHa ^ӴJ4iWEQ|G'4v]m:A o~ӲZ6,A3, >"M֣X\TtO4UrN4Mh&$ *M2L>/JjZ̴;wpH}k= XSF UadLq,1J+\6n; 2 &Ei1SƼf׳( axEQEy^d bu]>h8t݂eya+YCgffVVV.^8??lX#H^?!뺮LaƱq("S#ӉB^2B=w|E&t@D&h펦)<Ȥ;M8(mLt E1[TxO4xLJ 6AFə!޻iE1σJ5W\ϿLel0>8_[_go\1qLyG{ynYp8 AB;R]JRB<rʉ9<4MfEVvwwY.WUmt&S.+jTJg2qۙp^(-+q^"<ҰIVF.ҥK#JNi* ]>3!#$%: EQ={ L H]zcTj6Ch`%EE|>OހDl'DA@S38qlvaa[YY۷^jշʫnllDq'J=J*ZxPіI" p=/U%qN<"t3&qB\-BICWw0zvB Ģov>tRTgg˕ d~|$HHy'[RБG0 ollNrt]vww3J\>o 3*5N|Rn$~i20({'meYcXl0@ # A|^$4I=qD; ;/]N2~T:83E󆶐_"yI_(۷oooo7 p]\\4 EQTMKR#N|gus|BUD< BFAE|A撔w(h3,&D aZ(C#dDskk<&STRY^^pP"y xJky% NlsFe1,[o6tiksaYqT ԘNg//ĝSc&xjDSfѶ^uZ'58~'{Fò,u(W3ssՙBhӲ9݇!ےk%7O 0Ħںst Y|_Ql6q#8$6|>$p?!t@ݣ /8!8.QzyeX*DeL0pZG\ObVڵkDd:iЬr9Ltn>|rogg83t&s4 sd QkRS Mv֭[Nj AAx饗z꣚fzQGrIh)8F{-ބb0'#(P,M6%mB*y& `2bg,@KR QQ?FiO=V&:y@ƒFE0E͛+;Ev:Nbc]i⛄rI$$=$c!(ICGK9iꫯ魭V.LT͕t&hc1mI9.M$=σX:88@osst$aá LP,Kl6{s8 L<eYx=<<G%wzJ FXtm;c7i:{GN Z'%tP4[nBcqQs1j IDAT"ȑ]j0l%W k8 J}+_G^/N)}jmB~L㞣X]]m4xqiqk_mxds9= #DATUyȤeY{Vܙ#jqպ+Wit]?w.aZ4q~ss\VE\@@Ęi/QTc&L6%0 R"ԒhjZģ_._|faf,gR\WAJtEO]H_U>Te,ACo>yK֖i Q*ʕJP0 CIzuEDžBIB &$ Y,GD25]BȊ>7;+"qZ yRd?v<dL0$B|%~weww7oxW0tǶ^o_җ6UExtǡu&;L-z|1Mֳq_. [[[Hs\63 d# pyy_]XXm5I/]e6. 5h{yy_"cJ@6Im6\djy)8.]Zs02 hDe&)moߺK//{M;# tKQU#vlƭ[zQG^W`RD6dC&Q#rۅ A,# /fۢz&/JjT xn}"8 bɕ"m rV8>x$ VӉHPZ,٬:8N (: ,"Z__p|~hA@9Ǻasʹa|WƁKdϐB<}h<"ʐH 4 a\aMo0yܣ# 83I Mg^|BO #xZfx?6@,BK"$EE&E@w#Ǿ/qwxxF,t=_(JRR(Q~Z=EL Q) O4LKҾ#~IHn@&=zrkry{{naiZTT|]KLl /tme8u֭['o#iT՝LV|z+UӲyo7Q\׍(-FhMG)peBZ # /YL|ssGZX,9Dz23q?S,-.<%;dӧ{\Rà"&B$Uo4:,yARbXTJr&!x=nVp"K ?_֭[8l@5#ŋ ,+ C YCg( 0DT.}$dT8m0 QsQDX Ju=ocs) !p{{&"ȲXԇ?w. :DV_K/n5QTze,q ,D[@"̃?Y~v3M<놑dBTBg)NkD y.!^y逍$'GX$se,LQ7xC흝NiRifffff6uF_ Frq-,,\r壏>}6f6Ғyeq 4c7\nE`@JcOY!E/u'; e^C a`j:i"30t\XQS0 767wvvg`X!(9W c#:{HrxVq׾V#:3S aWq"q$]rG]~}mm (63~~.A)GeUU?rEU/o]rsrpc#19aG4}ĖEھ\^^tL߇(AE<al6_( \>Ni6c⋭Vkss?QRLXϗ!J'QE姟`~~?裏>䓵5L@'>,ڌ8H)cHG1WQ2,(6я*R6y5GX&aR`IM/| O?40iԅ 6TUp0m40F򻹹\.~YX5<_7^^p&ZN~NH1dNV 8c\$I/eY10bfvvv~T.gYw=$HlqinFbEs_wķ CǶΤ Ƶkn޼ T!%)R\Bd*s0w^|b8K9|w!%1WITJan}jUS{q.9O5I:@SjHK@pyqz8ПASU02L64b ߝK@h4<#Rbo>9{2+ /B&C8.NlZp Oٵ?ɗ{V?8 ")$q/}-FSF62H,˽N~xr8eYDAK8p+9e O?}9˲r=帵^|6,qR,կeYNi( 4 ?HzM#IFw#Bn.TU}g.\nWWW~}^R^(juf\==IRf?~jYyW988 膑fØn~_{%Iqo޼vz昂0$|hr<ϰ ~UM $iwҥK US)~.M$HG}Uv:NqWV|.= c F ;~NR)|\ dcs# )]7l.dҙ ĺ )$Or€fT²( !.MHI֓jK_L8>8{zeeA1TU^} pFW!CJ[^^ƆeY%I$-t]Uի׮J啕 ˂( /a'x_~ PKt(HiMYa17?f{=58Ne6~r0_WyG0nwooYG=+3E2!=uM}rntm2 2)L ZжG7È( |ϳmhMӶmeST&T*jpdqpL 9*`sg' B8;77778sD4bEw$EDHu]OCh1IODo (Ȋ2٬eY1Dz$ Xu죪(BvA_J`quq VLX*bBjvEUYEqLӳ mHd BR)U*lV$+DTUMyN;3jʢdڂog(X  >,Y&NIF09|p8PVIL00DvLҬBRɨUFHST5J s9Ď8W._g31cjFQ0$,Kg Tn⢒nfRY*\, @n&(<%4q,Hz5\6d?94%$Z*) "8) S45ܱjaHzÑмE8G2NB :N6(N>P{"qzC(IlR4/1fI9"M.8QC%m@HEn#@FeXV#nREc&"(OEJ3 Qa&{s ȊR7"Lqı!aXH2 r(g%9P'AvA9( RqWPT0(Ѿ q̟II AH\cQ'#Q$}ћb{(8.Q=EN^-HY~ȯ}XǧӅb1_(Ȳ#0ב 4*b(ewPFo$p H1 9 PMl%uȩ,b9q>۾x4 AaҊ4BApj2phjS>hAUU#|/ ΛO.Dcw cX%co=b {tS?9ZTe>%&o(^[qܱ OG Ƈ4pQEXR8}=s~8PK:cfKG?1S.!r 3/H uG;IpKz|)|^[4?6?1C9~}>M`9ȨMQ`1Eþ_xs?ӷ| 0H4g&d7>uu5y}%c܅s^?5<%Nn#8]ۡ?ȃ  )%NLtO|rkkpkkwkkwkkpkkwkkwkkpA.ژkz7kw|ehvkp,vRHpkwΞ2 Շ'+ݥkkp I)ѓe0 8Lhi䝮:KzQ)#(x:cY #O@m1wkp[>'*$ C4[ag)0]5 u 7"]0?0vVݭjNǶ푏(A@癓P隮]<` ((<ݽZjL9vii'<<~6MR78d2(0AJvMSWU4uӴmNY2sg xNs0TZ0HkX펇C]487 1&󋢸Bbh H(j;`ÀBFpXq R@ 6( ]qD q? P%K-` H @l@,D@|,chvaPb 4r@2s^IENDB`pyface-4.1.0/examples/images/image_LICENSE.txt0000644000175100001440000000142011674463363022026 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- GV (Gael Varoquaux) Public Domain N/A Nuvola LGPL image_LICENSE_Nuvola.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- examples/images: closed_folder_24x24.png | Nuvola closed_folder.png | Nuvola document.png | GV open_folder.png | Nuvola pyface-4.1.0/examples/dock/0000755000175100001440000000000011674463363016517 5ustar ischnellusers00000000000000pyface-4.1.0/examples/dock/images/0000755000175100001440000000000011674463363017764 5ustar ischnellusers00000000000000pyface-4.1.0/examples/dock/images/image_LICENSE.txt0000644000175100001440000000121111674463363022744 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Nuvola LGPL image_LICENSE_Nuvola.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- examples/dock/images: folder.png | Nuvola gear.png | Nuvola pyface-4.1.0/examples/dock/images/gear.png0000644000175100001440000000202111674463363021403 0ustar ischnellusers00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxb?% XJK; f100+rrR8e55i+;x~GbIŮ55dcc>~|]RA##T%a* rrꊞ*< L >ccc)w X~Ġ1p -FF`^GG6>>N70^ׯ&߿4$$$W mãG}:ADAZZAHt!!aǏoܸ| @A @3,[wihY\#'' 6`ݺ_ϟg & ر**2.]:=a߾ˁ1.!!Y ӇoY޽{k/_]AFFMCC_'$$AO߿c^F ,-YLÇ>b>u7o^~5+_JIIs XZZ)(H1/2,Z(@xx?VPPa`aaPY hjwA\\AJJ(W _5W qq `%++.Gϟ89ف)ܹ8q`ӧ˯_?,X0@\moQAQQ 8gP`FP͛gW^jy X@ĭ[WWp缼ڥrr߿bsP>0a2<{f2@0 ܹss %(qrr1̜9=z/F=ã  ,-mqssCX=;&cg+IENDB`pyface-4.1.0/examples/dock/images/folder.png0000644000175100001440000000115111674463363021743 0ustar ischnellusers00000000000000PNG  IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxbd```.ˀJ>h@ ? qs1 31#~I0r ܊@2x/!W؀C@>ӿ>  q2N1H; h(#+Xُ[{`W]h3j?5ge`bv;5\ۇ p/wyW8D H~aՎ )@C?$a/ o~%H3_ 4~<?°7b? ? `Qǁ@ x3î? V z+~lalH!PHw :t @۫ D@L cĠ&D@?y(8^ `DC @L3(0IENDB`pyface-4.1.0/examples/dock/dock_test3.py0000644000175100001440000000567511674463363021150 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Test the DockWindow. # # Written by: David C. Morrill # # Date: 10/20/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import sys from traits.api \ import * from traitsui.api \ import * from traitsui.menu \ import * from pyface.image_resource \ import ImageResource from etsdevtools.developer.tools.ui_debugger import UIDebugger #------------------------------------------------------------------------------- # Constants: #------------------------------------------------------------------------------- image1 = ImageResource( 'folder' ) image2 = ImageResource( 'gear' ) #------------------------------------------------------------------------------- # 'TestDock' class: #------------------------------------------------------------------------------- class TestDock ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- button1 = Button button2 = Button button3 = Button button4 = Button button5 = Button button6 = Button button7 = Button button8 = Button button9 = Button button10 = Button button11 = Button button12 = Button code1 = Code code2 = Code debug = Instance(UIDebugger) #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( HSplit( VSplit( Tabbed( 'button1', 'button2', image = image1 ), Tabbed( 'button3', 'button4', image = image2 ) ), Tabbed( VSplit( 'button5', 'button6' ), Tabbed( 'button7', 'button8' ), HSplit( 'button9', 'button10' ), Group( 'code1@', '|<>', image = image1 ), Group( 'code2@', '|<>', image = image2 ), Group( 'debug', '|<>' ), Group( 'button11', 'button12' ) ), id = 'dock_window' ), title = 'DockWindow Test', id = 'pyface.dock.dock_test3', dock = 'horizontal', resizable = True, width = 0.5, height = 0.5, buttons = NoButtons ) #------------------------------------------------------------------------------- # Run the test program: #------------------------------------------------------------------------------- if __name__ == '__main__': TestDock().configure_traits() pyface-4.1.0/examples/dock/dock_test2.py0000644000175100001440000000363111674463363021135 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Test the DockWindow. # # Written by: David C. Morrill # # Date: 10/20/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import sys from traits.api \ import * from traitsui.api \ import * from traitsui.menu \ import * #------------------------------------------------------------------------------- # 'TestDock' class: #------------------------------------------------------------------------------- class TestDock ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- button1 = Button button2 = Button button3 = Button button4 = Button button5 = Button button6 = Button #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( [ 'button1' ], [ 'button2' ], [ 'button3' ], [ 'button4' ], [ 'button5' ], [ 'button6' ], title = 'DockWindow Test', resizable = True, width = 0.5, height = 0.5, buttons = NoButtons ) #------------------------------------------------------------------------------- # Run the test program: #------------------------------------------------------------------------------- if __name__ == '__main__': TestDock().configure_traits() pyface-4.1.0/examples/dock/drag_test.py0000644000175100001440000001746611674463363021063 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Test the DockWindow. # # Written by: David C. Morrill # # Date: 10/20/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx from traits.api \ import * from traitsui.api \ import * from traitsui.menu \ import * from traitsui.dockable_view_element \ import DockableViewElement from pyface.image_resource \ import ImageResource from pyface.dock.api \ import * #------------------------------------------------------------------------------- # Global data: #------------------------------------------------------------------------------- # DockControl style to use: style = 'tab' image1 = ImageResource( 'folder' ) image2 = ImageResource( 'gear' ) #------------------------------------------------------------------------------- # 'AnEditor' class: #------------------------------------------------------------------------------- class AnEditor ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- code1 = Code code2 = Code name = Str( 'Mike Noggins' ) address = Str( '1313 Drury Lane' ) shell = PythonValue #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- traits_view = View( VSplit( VGroup( 'code1@', '|<>' ), VGroup( 'code2@', '|<>' ), VGroup( 'name', 'address' ), VGroup( 'shell', '|{Python Shell}<>' ), export = 'editor', show_labels = False ), kind = 'subpanel', resizable = True, buttons = NoButtons, dock = 'horizontal' ) #------------------------------------------------------------------------------- # 'AView' class: #------------------------------------------------------------------------------- class AView ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- code1 = Code code2 = Code #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- traits_view = View( VSplit( 'code1@', 'code2@', show_labels = False ), imports = [ 'editor' ], dock = 'horizontal', kind = 'subpanel' ) #------------------------------------------------------------------------------- # Creates a DockWindow as a Traits UI widget: #------------------------------------------------------------------------------- def create_dock_window ( parent, editor ): """ Creates a window for editing a workflow canvas. """ try: main = DockWindow( parent ).control view_uis = [ AView().edit_traits( parent = main ) for i in range( 6 ) ] views = [ ui.control for ui in view_uis ] edit = DockWindow( main ).control editors = [ AnEditor().edit_traits( parent = edit ) for i in range( 6 ) ] controls = [] for i in range( 6 ): dockable = DockableViewElement( ui = editors[i] ) controls.append( DockControl( name = 'Editor %d' % (i + 1), image = image1, closeable = True, on_close = dockable.close_dock_control, control = editors[i].control, export = 'editor', dockable = dockable, style = style ) ) edit_sizer = DockSizer( contents = [ tuple( controls ) ] ) dve0 = DockableViewElement( ui = view_uis[0] ) dve1 = DockableViewElement( ui = view_uis[1] ) main_sizer = DockSizer( contents = [ [ DockControl( name = 'View 1', image = image1, closeable = True, on_close = dve0.close_dock_control, dockable = dve0, control = views[0], style = style ), DockControl( name = 'View 2', image = image1, closeable = True, on_close = dve1.close_dock_control, dockable = dve1, height = 400, control = views[1], style = style ) ], [ DockControl( name = 'Editors', image = image1, control = edit, style = 'fixed' ), [ DockControl( name = 'View 3', image = image2, control = views[2], style = style ), DockControl( name = 'View 4', image = image2, control = views[3], style = style ) ] ], [ DockControl( name = 'View 5', control = views[4], style = style ), DockControl( name = 'View 6', control = views[5], style = style ) ] ] ) edit.SetSizer( edit_sizer ) main.SetSizer( main_sizer ) return main except: import traceback traceback.print_exc() raise #------------------------------------------------------------------------------- # 'EnvisageDock' class: #------------------------------------------------------------------------------- class EnvisageDock ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- dummy = Int #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( [ Item( 'dummy', resizable = True, editor = CustomEditor( create_dock_window ) ), '|<>' ], title = 'Envisage DockWindow Mock-up', resizable = True, width = 1.00, height = 1.00, buttons = NoButtons ) #------------------------------------------------------------------------------- # Run the test program: #------------------------------------------------------------------------------- if __name__ == '__main__': EnvisageDock().configure_traits() pyface-4.1.0/examples/dock/dock_test.py0000644000175100001440000001142511674463363021053 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Test the DockWindow. # # Written by: David C. Morrill # # Date: 10/20/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx import sys from traits.api \ import * from traitsui.api \ import * from traitsui.menu \ import * from pyface.dock.api \ import * from pyface.image_resource \ import ImageResource #------------------------------------------------------------------------------- # Global data: #------------------------------------------------------------------------------- # DockControl style to use: style1 = 'horizontal' style2 = 'vertical' image1 = ImageResource( 'folder' ) image2 = ImageResource( 'gear' ) #------------------------------------------------------------------------------- # Creates a DockWindow as a Traits UI widget: #------------------------------------------------------------------------------- def create_dock_window ( parent, editor ): """ Creates a window for editing a workflow canvas. """ window = DockWindow( parent ).control button1 = wx.Button( window, -1, 'Button 1' ) button2 = wx.Button( window, -1, 'Button 2' ) button3 = wx.Button( window, -1, 'Button 3' ) button4 = wx.Button( window, -1, 'Button 4' ) button5 = wx.Button( window, -1, 'Button 5' ) button6 = wx.Button( window, -1, 'Button 6' ) sizer = DockSizer( contents = [ DockControl( name = 'Button 1', image = image1, closeable = True, control = button1, style = style1 ), [ DockControl( name = 'Button 2', image = image1, closeable = True, height = 400, control = button2, style = style1 ), ( [ DockControl( name = 'Button 3', image = image2, resizable = False, control = button3, style = style2 ), DockControl( name = 'Button 4', image = image2, resizable = False, control = button4, style = style2 ) ], [ DockControl( name = 'Button 5', resizable = False, control = button5, style = style2 ), DockControl( name = 'Button 6', resizable = False, control = button6, style = style2 ) ] ) ] ] ) window.SetSizer( sizer ) window.SetAutoLayout( True ) return window #------------------------------------------------------------------------------- # 'TestDock' class: #------------------------------------------------------------------------------- class TestDock ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- dummy = Int #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( [ Item( 'dummy', resizable = True, editor = CustomEditor( create_dock_window ) ), '|<>' ], title = 'DockWindow Test', resizable = True, width = 0.5, height = 0.5, buttons = NoButtons ) #------------------------------------------------------------------------------- # Run the test program: #------------------------------------------------------------------------------- if __name__ == '__main__': if len( sys.argv ) > 1: style1 = style2 = sys.argv[1] if len( sys.argv ) > 2: style2 = sys.argv[2] TestDock().configure_traits() pyface-4.1.0/examples/dock/envisage_test.py0000644000175100001440000001157411674463363021741 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------- # # Test the DockWindow. # # Written by: David C. Morrill # # Date: 10/20/2005 # # (c) Copyright 2005 by Enthought, Inc. # #------------------------------------------------------------------------------- #------------------------------------------------------------------------------- # Imports: #------------------------------------------------------------------------------- import wx from traits.api \ import * from traitsui.api \ import * from traitsui.menu \ import * from pyface.image_resource \ import ImageResource from pyface.dock.api \ import * #------------------------------------------------------------------------------- # Global data: #------------------------------------------------------------------------------- # DockControl style to use: style = 'tab' image1 = ImageResource( 'folder' ) image2 = ImageResource( 'gear' ) #------------------------------------------------------------------------------- # Creates a DockWindow as a Traits UI widget: #------------------------------------------------------------------------------- def create_dock_window ( parent, editor ): """ Creates a window for editing a workflow canvas. """ main = DockWindow( parent ).control views = [ wx.Button( main, -1, 'View %d' % (i + 1) ) for i in range( 6 ) ] edit = DockWindow( main ).control editors = [ wx.Button( edit, -1, 'Editor %d' % (i + 1) ) for i in range( 6 ) ] controls = [ DockControl( name = 'Editor %d' % (i + 1), image = image1, closeable = True, control = editors[i], style = style ) for i in range( 6 ) ] controls[0].export = 'any' edit_sizer = DockSizer( contents = [ tuple( controls ) ] ) main_sizer = DockSizer( contents = [ [ DockControl( name = 'View 1', image = image1, closeable = True, control = views[0], style = style ), DockControl( name = 'View 2', image = image1, closeable = True, height = 400, control = views[1], style = style ) ], [ DockControl( name = 'Editors', image = image1, control = edit, style = 'fixed' ), [ DockControl( name = 'View 3', image = image2, control = views[2], style = style ), DockControl( name = 'View 4', image = image2, control = views[3], style = style ) ] ], [ DockControl( name = 'View 5', control = views[4], style = style ), DockControl( name = 'View 6', control = views[5], style = style ) ] ] ) edit.SetSizer( edit_sizer ) main.SetSizer( main_sizer ) return main #------------------------------------------------------------------------------- # 'EnvisageDock' class: #------------------------------------------------------------------------------- class EnvisageDock ( HasPrivateTraits ): #--------------------------------------------------------------------------- # Trait definitions: #--------------------------------------------------------------------------- dummy = Int #--------------------------------------------------------------------------- # Traits view definitions: #--------------------------------------------------------------------------- view = View( [ Item( 'dummy', resizable = True, editor = CustomEditor( create_dock_window ) ), '|<>' ], title = 'Envisage DockWindow Mock-up', resizable = True, width = 1.00, height = 1.00, buttons = NoButtons ) #------------------------------------------------------------------------------- # Run the test program: #------------------------------------------------------------------------------- if __name__ == '__main__': EnvisageDock().configure_traits() pyface-4.1.0/examples/python_editor.py0000644000175100001440000000624511674463363021047 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2007, Riverbank Computing Limited. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Riverbank Computing Limited. # Description: #------------------------------------------------------------------------------ """ Python editor example. """ # Enthought library imports. from pyface.api import ApplicationWindow, FileDialog, GUI, OK, \ PythonEditor from pyface.action.api import Action, MenuManager, MenuBarManager class MainWindow(ApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager(MenuManager( Action(name='&Open...', on_perform=self._open_file), Action(name='&Save', on_perform=self._save_file), Action(name='E&xit', on_perform=self.close), name='&File')) return ########################################################################### # Protected 'IApplication' interface. ########################################################################### def _create_contents(self, parent): """ Create the editor. """ self._editor = PythonEditor(parent) return self._editor.control ########################################################################### # Private interface. ########################################################################### def _open_file(self): """ Open a new file. """ if self.control: dlg = FileDialog(parent=self.control, wildcard="*.py") if dlg.open() == OK: self._editor.path = dlg.path def _save_file(self): """ Save the file. """ if self.control: try: self._editor.save() except IOError, e: # If you are trying to save to a file that doesn't exist, # open up a FileDialog with a 'save as' action. dlg = FileDialog(parent=self.control, action='save as', wildcard="*.py") if dlg.open() == OK: self._editor.save(dlg.path) # Application entry point. if __name__ == '__main__': # Create the GUI. gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/application_window.py0000644000175100001440000000473411674463363022053 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Application window example. """ # Enthought library imports. from pyface.api import ApplicationWindow, GUI from pyface.action.api import Action, MenuManager, MenuBarManager from pyface.action.api import StatusBarManager, ToolBarManager class MainWindow(ApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Create an action that exits the application. exit_action = Action(name='E&xit', on_perform=self.close) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager(exit_action, name='&File') ) # Add some tool bars. self.tool_bar_managers = [ ToolBarManager( exit_action, name='Tool Bar 1', show_tool_names=False ), ToolBarManager( exit_action, name='Tool Bar 2', show_tool_names=False ), ToolBarManager( exit_action, name='Tool Bar 3', show_tool_names=False ), ] # Add a status bar. self.status_bar_manager = StatusBarManager() self.status_bar_manager.message = 'Example application window' return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/timer.py0000644000175100001440000000337611674463363017302 0ustar ischnellusers00000000000000"""Example of using a pyface Timer.""" from pyface.timer.api import Timer from pyface.api import GUI, ApplicationWindow from pyface.action.api import Action, MenuManager, MenuBarManager from traits.api import Any, Int class MainWindow(ApplicationWindow): """ The main application window. """ # The pyface Timer. my_timer = Any() # Count each time the timer task executes. counter = Int def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='Start Timer', on_perform=self._start_timer), Action(name='Stop Timer', on_perform=self._stop_timer), Action(name='E&xit', on_perform=self.close), name = '&File', ) ) return def _start_timer(self): """Called when the user selects "Start Timer" from the menu.""" if self.my_timer is None: # First call, so create a Timer. It starts automatically. self.my_timer = Timer(500, self._timer_task) else: self.my_timer.Start() def _stop_timer(self): """Called when the user selecte "Stop Timer" from the menu.""" if self.my_timer is not None: self.my_timer.Stop() def _timer_task(self): """The method run periodically by the timer.""" self.counter += 1 print "counter = %d" % self.counter if __name__ == "__main__": gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() pyface-4.1.0/examples/file_tree_viewer.py0000644000175100001440000000732311674463363021475 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A tree viewer for local file systems. """ # Standard library imports. from os import listdir from os.path import basename, dirname, isdir, join # Enthought library imports. from pyface.api import ImageResource from pyface.viewer.api import TreeContentProvider, TreeLabelProvider from pyface.viewer.api import TreeViewer from traits.api import Instance class FileTreeContentProvider(TreeContentProvider): """ A tree content provider for local file systems. """ ######################################################################### # 'TreeContentProvider' interface. ######################################################################### def get_parent(self, element): """ Returns the parent of an element. """ return dirname(element) def get_children(self, element): """ Returns the children of an element. """ return [join(element, filename) for filename in listdir(element)] def has_children(self, element): """ Returns True iff the element has children, otherwise False. """ if isdir(element): for filename in listdir(element): if isdir(join(element, filename)): has_children = True break else: has_children = False else: has_children = False return has_children class FileTreeLabelProvider(TreeLabelProvider): """ A tree label provider for local file systems. """ # The image used to represent folders that are NOT expanded. CLOSED_FOLDER = ImageResource('closed_folder') # The image used to represent folders that ARE expanded. OPEN_FOLDER = ImageResource('open_folder') # The image used to represent documents (ie. NON-'folder') elements. DOCUMENT = ImageResource('document') ########################################################################### # 'TreeLabelProvider' interface. ########################################################################### def get_image(self, viewer, element): """ Returns the filename of the label image for an element. """ selected = viewer.is_selected(element) expanded = viewer.is_expanded(element) if isdir(element): if expanded: image = self.OPEN_FOLDER else: image = self.CLOSED_FOLDER else: image = self.DOCUMENT return image def get_text(self, viewer, element): """ Returns the label text for an element. """ return basename(element) class FileTreeViewer(TreeViewer): """ A tree viewer for local file systems. """ #### 'TreeViewer' interface ############################################### # The content provider provides the actual tree data. content_provider = Instance(FileTreeContentProvider, ()) # The label provider provides, err, the labels for the items in the tree # (a label can have text and/or an image). label_provider = Instance(FileTreeLabelProvider, ()) ##### EOF ##################################################################### pyface-4.1.0/examples/grid.py0000644000175100001440000001112211674463363017073 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Expandable example. """ # Standard library imports. import os, sys # Major package imports. import wx # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, PythonShell, SplitApplicationWindow from pyface.ui.wx.grid.api import Grid, TraitGridModel, \ SimpleGridModel, GridRow, GridColumn, TraitGridColumn from traits.api import Float, Str class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the window is split. direction = Str('vertical') # The data used to create the SimpleGridModel data = [['bob', 1, True, Float], ['sarah', 45, True, Str], ['jonas', -3, False, direction]] rows = [GridRow(name='Row 1'), GridRow(name='Row 2'), GridRow(name='Row 3')] cols = [GridColumn(name='Name'), GridColumn(name='Index', read_only=True), GridColumn(name='Veracity'), GridColumn(name='Object')] # The data used to create the TraitGridModel trait_data = [GridRow(name='Bob', index=1, veracity=True, object=Float), GridRow(name='Sarah', index=45, veracity=True, object=Str), GridRow(name='Jonas', index=-3, veracity=False, object=direction)] trait_col = [TraitGridColumn(name='name', label='Name'), TraitGridColumn(name='index', label='Index', read_only=True), TraitGridColumn(name='veracity', label='Veracity'), TraitGridColumn(name='object', label='Object')] ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the split. """ #self._model = model = SimpleGridModel(data = self.data, # rows = self.rows, # columns = self.cols) self._model = model = TraitGridModel(data = self.trait_data, columns = self.trait_col, row_name_trait = 'name') self._grid = grid = Grid(parent, model = model) self._grid.on_trait_change(self._on_grid_anytrait_changed) return grid.control def _create_rhs(self, parent): """ Creates the right hand side or bottom depending on the split. """ widget = self._grid self._python_shell = PythonShell(parent) self._python_shell.bind('widget', widget) self._python_shell.bind('w', widget) return self._python_shell.control ########################################################################### # Private interface. ########################################################################### def _create_content(self, parent): """ Create some context for an expandable panel. """ tree = FileTree(parent, root=os.path.abspath(os.curdir)) return tree.control #### Trait event handlers ################################################# def _on_grid_anytrait_changed(self, tree, trait_name, old, new): """ Called when any trait on the tree has changed. """ print 'trait', trait_name, 'value', new return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/file_sorters.py0000644000175100001440000000301211674463363020645 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Sorters for local file system viewers. """ # Standard library imports. from os.path import isdir # Enthought library imports. from pyface.viewer.api import ViewerSorter class FileSorter(ViewerSorter): """ A sorter that arranges folders before files. """ def category(self, viewer, parent, element): """ Returns the category (an integer) for an element. 'parent' is the parent element. 'elements' is the element to return the category for. Categories are used to sort elements into bins. The bins are arranged in ascending numeric order. The elements within a bin are arranged as dictated by the sorter's 'compare' method. """ if isdir(element): category = 0 else: category = 1 return category #### EOF ###################################################################### pyface-4.1.0/examples/tree.py0000644000175100001440000000564711674463363017124 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Tree example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, PythonShell, SplitApplicationWindow from traits.api import Float, Str # Local imports. from file_tree import FileTree class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the window is split. direction = Str('vertical') ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the split. """ self._tree = FileTree( parent, root=os.path.abspath(os.curdir), ) self._tree.on_trait_change(self._on_tree_anytrait_changed) return self._tree.control def _create_rhs(self, parent): """ Creates the right hand side or bottom depending on the split. """ self._python_shell = PythonShell(parent) self._python_shell.bind('widget', self._tree) self._python_shell.bind('w', self._tree) return self._python_shell.control ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_tree_anytrait_changed(self, tree, trait_name, old, new): """ Called when any trait on the tree has changed. """ print 'trait', trait_name, 'value', new return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/expandable.py0000644000175100001440000000577511674463363020272 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Expandable example. """ # Standard library imports. import os, sys # Major package imports. import wx # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, PythonShell, SplitApplicationWindow from pyface.expandable_panel import ExpandablePanel from traits.api import Float, Str # Local imports. from file_tree import FileTree class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the window is split. direction = Str('vertical') ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the split. """ self._expandable = expandable = ExpandablePanel(parent) for i in range(10): panel = self._create_content(expandable.control) expandable.add_panel('Panel %d' % i, panel) return expandable.control def _create_rhs(self, parent): """ Creates the right hand side or bottom depending on the split. """ widget = self._expandable self._python_shell = PythonShell(parent) self._python_shell.bind('widget', widget) self._python_shell.bind('w', widget) return self._python_shell.control ########################################################################### # Private interface. ########################################################################### def _create_content(self, parent): """ Create some context for an expandable panel. """ tree = FileTree(parent, root=os.path.abspath(os.curdir)) return tree.control # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/file_node_tree.py0000644000175100001440000000507411674463363021122 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A file system tree. """ # Standard library imports. from os import listdir from os.path import basename, isdir, isfile, join # Enthought library imports. from pyface.tree.api import NodeManager, NodeType class FileNode(NodeType): """ Node type for files. """ ########################################################################### # 'NodeType' interface. ########################################################################### def is_type_for(self, node): """ Returns True if this node type recognizes a node. """ return isfile(node) def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return False def get_text(self, node): """ Returns the label text for a node. """ return basename(node) class FolderNode(NodeType): """ Node type for folders. """ ######################################################################### # 'NodeType' interface. ######################################################################### def is_type_for(self, node): """ Returns True if this node type recognizes a node. """ return isdir(node) def allows_children(self, node): """ Does the node allow children (ie. a folder vs a file). """ return True def has_children(self, node): """ Returns True if a node has children, otherwise False. """ return len(listdir(node)) > 0 def get_children(self, node): """ Returns the children of a node. """ return [join(node, filename) for filename in listdir(node)] def get_text(self, node): """ Returns the label text for a node. """ return basename(node) # Add all types to the node manager. node_manager = NodeManager() node_manager.add_node_type(FileNode()) node_manager.add_node_type(FolderNode()) ##### EOF ##################################################################### pyface-4.1.0/examples/file_table_viewer.py0000644000175100001440000001133711674463363021625 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A table viewer for local file systems. """ # Standard library imports. from os import listdir, stat from os.path import basename, isdir, join from time import localtime, strftime # Enthought library imports. from pyface.api import ImageResource from pyface.viewer.api import TableColumnProvider, TableContentProvider from pyface.viewer.api import TableLabelProvider, TableViewer from traits.api import Instance, Str class FileTableContentProvider(TableContentProvider): """ A table content provider for local file systems. """ ######################################################################### # 'TableContentProvider' interface. ######################################################################### def get_elements(self, element): """ Returns the label for an element. """ if isdir(element): elements = [ join(element, filename) for filename in listdir(element) ] else: elements = [] return elements class FileTableLabelProvider(TableLabelProvider): """ A table label provider for local file systems. """ # The icon used to represent 'folder' elements. FOLDER = ImageResource('closed_folder') # The icon used to represent 'document' elements. DOCUMENT = ImageResource('document') ########################################################################### # 'TableLabelProvider' interface. ########################################################################### def get_image(self, viewer, element, column_index=0): """ Returns the filename of the label image for an element. """ if isdir(element): image = self.FOLDER else: image = self.DOCUMENT return image def get_text(self, viewer, element, column_index=0): """ Returns the label text for an element. """ details = stat(element) if column_index == 0: label = basename(element) elif column_index == 1: label = str(int(details.st_size) / 1000) + ' KB' else: # Format is: mm/dd/yyyy HH:MM AM eg. '12/31/2004 12:00 PM' label = strftime('%m/%d/%Y %I:%M %p', localtime(details.st_mtime)) return label class FileTableColumnProvider(TableColumnProvider): """ A table column provider for local file systems. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self): """ Creates a new column provider. """ # Column labels. self._column_labels = ['Name', 'Size', 'Date Modified'] # The number of columns. self.column_count = len(self._column_labels) return ########################################################################### # 'TableColumnProvider' interface. ########################################################################### def get_label(self, viewer, column_index): """ Returns the label for a column. """ return self._column_labels[column_index] def get_alignment(self, viewer, column_index): """ Returns the alignment of the column header and cells. Returns, 'left', 'right', 'centre' or 'center' ('left' by default). """ if column_index == 1: alignment = 'right' else: alignment = 'left' return alignment class FileTableViewer(TableViewer): """ A table viewer for local file systems. """ #### 'TableViewer' interface ############################################## # The column provider. column_provider = Instance(FileTableColumnProvider, ()) # The content provider provides the actual table data. content_provider = Instance(FileTableContentProvider, ()) # The label provider provides, err, the labels for the items in the tree # (a label can have text and/or an image). label_provider = Instance(FileTableLabelProvider, ()) ##### EOF ##################################################################### pyface-4.1.0/examples/workbench/0000755000175100001440000000000011674463363017561 5ustar ischnellusers00000000000000pyface-4.1.0/examples/workbench/green_view.py0000644000175100001440000000076411674463363022274 0ustar ischnellusers00000000000000""" A view containing a green panel! """ # Local imports. from color_view import ColorView class GreenView(ColorView): """ A view containing a green panel! """ #### 'IView' interface #################################################### # The view's name. name = 'Green' # The default position of the view relative to the item specified in the # 'relative_to' trait. position = 'left' #### EOF ###################################################################### pyface-4.1.0/examples/workbench/person.py0000644000175100001440000000121411674463363021437 0ustar ischnellusers00000000000000""" A simple example of some object model. """ # Enthought library imports. from traits.api import HasTraits, Int, Str class Person(HasTraits): """ A simple example of some object model! """ # Age in years. age = Int # Name. name = Str ########################################################################### # 'object' interface. ########################################################################### def __str__(self): """ Return an informal string representation of the object. """ return self.name #### EOF ###################################################################### pyface-4.1.0/examples/workbench/images/0000755000175100001440000000000011674463363021026 5ustar ischnellusers00000000000000pyface-4.1.0/examples/workbench/images/example.ico0000644000175100001440000001006611674463363023160 0ustar ischnellusers00000000000000h&  ( @--------------C/mruuxx]uux-mmurTbOF~m-zemrT]:2|\>n m-mb ø9 ~-ze]fs`-]m9$ڌ-T eHۃu突fK80󶤾 ~-zeKL:SB-cQ𮚋 $KqWeMx-B|g K-!9]f|eeMr-zeK| xzUmKgUqZr-Kn̻q/%K鑃0#O6r-zeKH4 xW K9s̻$u-]K SH突 @-zeܧ -zebbTK/%g̻ϼze<4 r~-]bfbTTbT]]mx-|e|e|eze|e|e|e]edAg^( @ DDDDDDI DDDk QEEEEEELELLQQLQQUXQUUXX`\UEEH xxXX\\^^`^addfddfpnjnnqq|sldUExxdXX\\^^`^adddd^fnljnnqq|svvaEEgS&`XXZ\^^```fd\X} Ƚ'aflnnv|qvvnXI Љu?+ZXXX\^^```\U ǻN;ݞ|ann|sqsvvZDΉu?+XZZXZ\^aUQ䳣ښ O7 avvqqsvvZDΉu?+XX^^\\UL ӔޡsLsfޡF3fnqqsvvZDΉu?+XXX\\Uv 䳣ܙxU\a } a\Q #------------------------------------------------------------------------------ """ Filters for local file system viewers. """ # Standard library imports. from os.path import isdir # Enthought library imports. from pyface.viewer.api import ViewerFilter class AllowOnlyFolders(ViewerFilter): """ A viewer filter that allows only folders (not files). """ def select(self, viewer, parent, element): """ Returns True if the element is 'allowed' (ie. NOT filtered). 'viewer' is the viewer that we are filtering elements for. 'parent' is the parent element. 'element' is the element to select. """ return isdir(element) #### EOF ###################################################################### pyface-4.1.0/examples/chained_wizard.py0000644000175100001440000000640311674463363021127 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Chained wizard example. """ # Standard library imports. import os import sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, OK from pyface.wizard.api import ChainedWizard, Wizard, WizardPage from traits.api import Color, HasTraits, Int, Str class Details(HasTraits): """ Some test data. """ name = Str color = Color class SimpleWizardPage(WizardPage): """ A simple wizard page. """ #### 'SimpleWizardPage' interface ######################################### # The page color. color = Color ########################################################################### # 'IWizardPage' interface. ########################################################################### def _create_page_content(self, parent): """ Create the wizard page. """ details = Details(color=self.color) details.on_trait_change(self._on_name_changed, 'name') return details.edit_traits(parent=parent, kind='subpanel').control ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_name_changed(self, new): """ Called when the name has been changed. """ self.complete = len(new.strip()) > 0 return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() wizard = ChainedWizard( parent = None, title = 'Chained wizard root', pages = [ SimpleWizardPage(id='foo', color='red', heading="The Red Page", subheading="The default color on this page is red.") ] ) next_wizard = Wizard( parent = None, title = 'Chained wizard child.', pages = [ SimpleWizardPage(id='bar', color='yellow', heading="The Yellow Page", subheading="The default color on this page is yellow."), SimpleWizardPage(id='baz', color='green', heading="The Green Page", subheading="The default color on this page is green.") ] ) wizard.next_wizard = next_wizard # Open the wizard. if wizard.open() == OK: print 'Wizard completed successfully' else: print 'Wizard cancelled' #### EOF ###################################################################### pyface-4.1.0/examples/image_widget.py0000644000175100001440000001072711674463363020605 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Application window example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Major package imports. import wx import wx.html import wx.lib.wxpTag # Enthought library imports. from pyface.api import ApplicationWindow, GUI, ImageResource, ImageWidget from pyface.action.api import Action, MenuManager, MenuBarManager # HTML templates. HTML = """ %s """ PART = """""" class MainWindow(ApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self.close), name = '&File', ) ) return ########################################################################### # Protected 'Window' interface. ########################################################################### def _create_contents(self, parent): """ Creates the window contents. This method is intended to be overridden if necessary. By default we just create an empty (and blue!) panel. """ panel = wx.Panel(parent, -1, style=wx.CLIP_CHILDREN) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) panel.SetAutoLayout(True) N = 100 wxid = wx.NewId() # Create the HTML. parts = [] for i in range(N): parts.append(PART % str(wxid+i)) html = HTML % "".join(parts) # Create the HTML window. html_window = wx.html.HtmlWindow(panel, -1, style=wx.CLIP_CHILDREN) html_window.SetPage(html) # Initialize all embedded wx controls. for i in range(N): self._initialize_window(html_window, wxid+i) sizer.Add(html_window, 1, wx.EXPAND) # Resize the panel to fit the sizer's minimum size. sizer.Fit(panel) return panel ########################################################################### # Private interface. ########################################################################### def _initialize_window(self, parent, wxid): """ Initialize the window with the specified Id. """ window = parent.FindWindowById(wxid) sizer = wx.BoxSizer(wx.VERTICAL) window.SetSizer(sizer) window.SetAutoLayout(True) window.SetBackgroundColour('white') window.SetWindowStyleFlag(wx.CLIP_CHILDREN) window.Refresh() image = ImageResource('closed_folder_24x24') bitmap = image.create_image().ConvertToBitmap() image_widget = ImageWidget(window, bitmap=bitmap) image_widget.control.SetBackgroundColour('white') sizer.Add(image_widget.control, 0, wx.EXPAND) text = wx.StaticText(window, -1, "Blah", style=wx.ALIGN_CENTRE) sizer.Add(text, 0, wx.EXPAND) # Resize the window to match the sizer's minimum size. sizer.Fit(window) return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/file_tree.py0000644000175100001440000000516311674463363020114 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A file system tree. """ # Standard library imports. from os import listdir from os.path import basename, isdir, join # Enthought library imports. from pyface.api import ImageResource from pyface.tree.api import Tree, TreeModel from traits.api import Instance class FileTreeModel(TreeModel): """ A tree model for local file systems. """ # The image used to represent folders that are NOT expanded. CLOSED_FOLDER = ImageResource('closed_folder') # The image used to represent folders that ARE expanded. OPEN_FOLDER = ImageResource('open_folder') # The image used to represent documents (ie. NON-'folder') nodes. DOCUMENT = ImageResource('document') ######################################################################### # 'TreeModel' interface. ######################################################################### def get_children(self, node): """ Returns the children of a node. """ return [join(node, filename) for filename in listdir(node)] def has_children(self, node): """ Returns True if a node has children, otherwise False. """ if isdir(node): has_children = len(listdir(node)) > 0 else: has_children = False return has_children def get_image(self, node, selected, expanded): """ Returns the label image for a node. """ if isdir(node): if expanded: image = self.OPEN_FOLDER else: image = self.CLOSED_FOLDER else: image = self.DOCUMENT return image def get_text(self, node): """ Returns the label text for a node. """ return basename(node) class FileTree(Tree): """ A file system tree. """ #### 'Tree' interface ##################################################### # The model that provides the data for the tree. model = Instance(FileTreeModel, ()) ##### EOF ##################################################################### pyface-4.1.0/examples/splash_screen.py0000644000175100001440000000375011674463363021007 0ustar ischnellusers00000000000000"""Example of using a splash screen (and the use of pyface Timer).""" import time from pyface.timer.api import Timer from pyface.api import GUI, ApplicationWindow, ImageResource, SplashScreen from pyface.action.api import Action, MenuManager, MenuBarManager from traits.api import Any, Int splash_screen = SplashScreen(image=ImageResource('images/splash')) class MainWindow(ApplicationWindow): """ The main application window. """ # The pyface Timer. my_timer = Any() # Count each time the timer task executes. counter = Int def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='Start Timer', on_perform=self._start_timer), Action(name='Stop Timer', on_perform=self._stop_timer), Action(name='E&xit', on_perform=self.close), name = '&File', ) ) return def _start_timer(self): """Called when the user selects "Start Timer" from the menu.""" if self.my_timer is None: # First call, so create a Timer. It starts automatically. self.my_timer = Timer(500, self._timer_task) else: self.my_timer.Start() def _stop_timer(self): """Called when the user selecte "Stop Timer" from the menu.""" if self.my_timer is not None: self.my_timer.Stop() def _timer_task(self): """The method run periodically by the timer.""" self.counter += 1 print "counter = %d" % self.counter if __name__ == "__main__": gui = GUI(splash_screen=splash_screen) # Create and open the main window. window = MainWindow() # Simulate a busy window initialization. time.sleep(5) window.open() # Start the GUI event loop! gui.start_event_loop() pyface-4.1.0/examples/tasks/0000755000175100001440000000000011674463363016724 5ustar ischnellusers00000000000000pyface-4.1.0/examples/tasks/example_task.py0000644000175100001440000001165511674463363021763 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.tasks.api import Task, TaskLayout, PaneItem from pyface.tasks.action.api import DockPaneToggleGroup, SMenuBar, \ SMenu, SToolBar, TaskAction from pyface.api import ConfirmationDialog, FileDialog, \ ImageResource, YES, OK, CANCEL from traits.api import on_trait_change # Local imports. from example_panes import PythonEditorPane, PythonScriptBrowserPane class ExampleTask(Task): """ A simple task for editing Python code. """ #### Task interface ####################################################### id = 'example.example_task' name = 'Python Script Editor' #default_layout = TaskLayout( # left=PaneItem('example.python_script_browser_pane')) menu_bar = SMenuBar(SMenu(TaskAction(name='Open...', method='open', accelerator='Ctrl+O'), TaskAction(name='Save', method='save', accelerator='Ctrl+S'), id='File', name='&File'), SMenu(DockPaneToggleGroup(), id='View', name='&View')) tool_bars = [ SToolBar(TaskAction(method='open', tooltip='Open a file', image=ImageResource('document_open')), TaskAction(method='save', tooltip='Save the current file', image=ImageResource('document_save'))) ] ########################################################################### # 'Task' interface. ########################################################################### def _default_layout_default(self): return TaskLayout( left=PaneItem('example.python_script_browser_pane')) def activated(self): """ Overriden to set the window's title. """ filename = self.window.central_pane.editor.path self.window.title = filename if filename else 'Untitled' def create_central_pane(self): """ Create the central pane: the script editor. """ return PythonEditorPane() def create_dock_panes(self): """ Create the file browser and connect to its double click event. """ browser = PythonScriptBrowserPane() handler = lambda: self._open_file(browser.selected_file) browser.on_trait_change(handler, 'activated') return [ browser ] ########################################################################### # 'ExampleTask' interface. ########################################################################### def open(self): """ Shows a dialog to open a file. """ dialog = FileDialog(parent=self.window.control, wildcard='*.py') if dialog.open() == OK: self._open_file(dialog.path) def save(self): """ Attempts to save the current file, prompting for a path if necessary. Returns whether the file was saved. """ editor = self.window.central_pane.editor try: editor.save() except IOError: # If you are trying to save to a file that doesn't exist, open up a # FileDialog with a 'save as' action. dialog = FileDialog(parent=self.window.control, action='save as', wildcard='*.py') if dialog.open() == OK: editor.save(dialog.path) else: return False return True ########################################################################### # Protected interface. ########################################################################### def _open_file(self, filename): """ Opens the file at the specified path in the editor. """ if self._prompt_for_save(): self.window.central_pane.editor.path = filename self.activated() def _prompt_for_save(self): """ Prompts the user to save if necessary. Returns whether the dialog was cancelled. """ if self.window.central_pane.editor.dirty: message = 'The current file has unsaved changes. ' \ 'Do you want to save your changes?' dialog = ConfirmationDialog(parent=self.window.control, message=message, cancel=True, default=CANCEL, title='Save Changes?') result = dialog.open() if result == CANCEL: return False elif result == YES: if not self.save(): return self._prompt_for_save() return True @on_trait_change('window:closing') def _prompt_on_close(self, event): """ Prompt the user to save when exiting. """ if not self._prompt_for_save(): event.veto = True pyface-4.1.0/examples/tasks/images/0000755000175100001440000000000011674463363020171 5ustar ischnellusers00000000000000pyface-4.1.0/examples/tasks/images/document_open.png0000644000175100001440000000210011674463363023527 0ustar ischnellusers00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxŖM\E9Uu?{1a•0;E'vKW#J! H .id:=龷NyN);h4ũy[g]( a L _{M0f(xC9>R}_LJdSO߯F4#& 0ns2NXK6mUsy3q!j<*H#[.N{7@[V2 c.]^3W0P 6/7>no}k `pFF{80oOn?U5X['o:pbNW=?.|FڢCĚ70>WԻ̼րjfO˩L׾{4%ʬX$} CR<\1Ɓ>j 4˚xgc<=fɬjKܰؤ}gu"2- JbW-|ŋy`]N]B"Pfd=d*3B˜i4Pd7 4EH͋,>yW.$* :ΚɌ)TYO&yq*"%$䀔[̘?OMtH@v)^Wv''E뻖3vgd]v{,*G#:јu   \d 9_ɪIENDB`pyface-4.1.0/examples/tasks/images/image_LICENSE.txt0000644000175100001440000000130711674463363023157 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Enthought BSD 3-Clause LICENSE.txt Oxygen CC-SA 3.0 http://creativecommons.org/licenses/by-sa/3.0/ Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- document_open.png | Oxygen document_save.png | Oxygen pyface-4.1.0/examples/tasks/images/document_save.png0000644000175100001440000000235711674463363023542 0ustar ischnellusers00000000000000PNG  IHDR szzsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<lIDATxVoEmvbv " 7gԗ T*oE4""Lq[fFzwBM3w775 @_D]λΛ, KxgHlQ {?iֹo~4ý:8߹ۯ:ȍYN,~/S6Hf$IVc웠yOgy~`n3@s -n,zž&D9a4Kh74 a pm V`5ɨC1S o%\(.qRCQә1<:WJP>4$Ȃ PъOtSq1u+ ^]1.Bi5' "ڎZ]Y95}Qw<2-HZMU.BpL5WDd<`u!9 hQt%a 'C׃K/0S]['=jO~z ŠV+8[I}k@X^?):50Mjқο0\`ⱊ/+0.saST, ,_ CZŗtܸyV(>2o3 ON( KS((N)x QKD-(dg0!#jj:   l@G$ +L"({4tࢢ8>|q$ +¢K $9_BdEdM FX_/z`V ǁ1Z2a]4M/I |9ߞv>46˗.}P־RU-E&ωP( Y+2Mv,^lULƣ?766~gx0 9=BjDП>(h*]2ѐC4wh4<"+츮빎(]a'/8"06}۱gxX`:Лap 壻f;4GTOD#K@4յ^xψȝL&5pgߚғCIENDB`pyface-4.1.0/examples/tasks/run.py0000644000175100001440000000113311674463363020100 0ustar ischnellusers00000000000000# Enthought library imports. from pyface.api import GUI from pyface.tasks.api import TaskWindow # Local imports. from example_task import ExampleTask def main(argv): """ A simple example of using Tasks. """ # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create a Task and add it to a TaskWindow. task = ExampleTask() window = TaskWindow(size=(800, 600)) window.add_task(task) # Show the window. window.open() # Start the GUI event loop. gui.start_event_loop() if __name__ == '__main__': import sys main(sys.argv) pyface-4.1.0/examples/tasks/example_panes.py0000644000175100001440000000434711674463363022127 0ustar ischnellusers00000000000000# Standard library imports. import os.path # Enthought library imports. from pyface.api import PythonEditor from pyface.tasks.api import TaskPane, TraitsDockPane from traits.api import Event, File, Instance, List, Str from traitsui.api import View, Item, FileEditor class FileBrowserPane(TraitsDockPane): """ A simple file browser pane. """ #### TaskPane interface ################################################### id = 'example.file_browser_pane' name = 'File Browser' #### FileBrowserPane interface ############################################ # Fired when a file is double-clicked. activated = Event # The list of wildcard filters for filenames. filters = List(Str) # The currently selected file. selected_file = File(os.path.expanduser('~')) # The view used to construct the dock pane's widget. view = View(Item('selected_file', editor=FileEditor(dclick_name='activated', filter_name='filters'), style='custom', show_label=False), resizable=True) class PythonScriptBrowserPane(FileBrowserPane): """ A file browser pane restricted to Python scripts. """ #### TaskPane interface ################################################### id = 'example.python_script_browser_pane' name = 'Script Browser' #### FileBrowserPane interface ############################################ filters = [ '*.py' ] class PythonEditorPane(TaskPane): """ A wrapper around the Pyface Python editor. """ #### TaskPane interface ################################################### id = 'example.python_editor_pane' name = 'Python Editor' #### PythonEditorPane interface ########################################### editor = Instance(PythonEditor) ########################################################################### # 'ITaskPane' interface. ########################################################################### def create(self, parent): self.editor = PythonEditor(parent) self.control = self.editor.control def destroy(self): self.editor.destroy() self.control = self.editor = None pyface-4.1.0/examples/node_tree.py0000644000175100001440000000604711674463363020124 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Node tree example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, PythonShell, SplitApplicationWindow from traits.api import Float, Str # Local imports. from file_node_tree import node_manager from pyface.tree.api import NodeTree, NodeTreeModel class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the window is split. direction = Str('vertical') ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the split. """ model = NodeTreeModel(node_manager=node_manager) model.root = os.path.abspath(os.curdir) self._tree = NodeTree(parent, model=model) self._tree.on_trait_change(self._on_tree_anytrait_changed) return self._tree.control def _create_rhs(self, parent): """ Creates the right hand side or bottom depending on the split. """ self._python_shell = PythonShell(parent) self._python_shell.bind('widget', self._tree) self._python_shell.bind('w', self._tree) return self._python_shell.control ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_tree_anytrait_changed(self, tree, trait_name, old, new): """ Called when any trait on the tree has changed. """ print 'trait', trait_name, 'value', new return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/tool_palette.py0000644000175100001440000000413011674463363020642 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ import wx from pyface.api import ApplicationWindow, GUI, ImageResource from pyface.action.api import Action, MenuManager, MenuBarManager from pyface.action.api import ToolPaletteManager class MainWindow(ApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self.close), name = '&File', ) ) return def _create_contents(self, parent): """ Creates the window contents. """ actions = [] for i in range(25): actions.append(Action(name='Foo', style='radio', image=ImageResource('document'))) tool_palette = ToolPaletteManager( *actions ) return tool_palette.create_tool_palette(parent).control # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() pyface-4.1.0/examples/menu_manager.py0000644000175100001440000000455211674463363020615 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Local imports. from pyface.action.api import Action from pyface.action.api import Group, MenuManager, Separator file_menu = MenuManager( Group( Action(name='New Project...'), Action(name='Open Project...'), id = 'OpenGroup', ), Group( Action(name='Close Project'), Action(name='Close Active Editor'), id = 'CloseGroup' ), Group( Action(name='Export to HTML...'), Action(name='Print...'), id = 'ExportGroup' ), Group( Action(name='Exit'), id = 'ExitGroup' ), ) file_menu.dump() ############################################################################### file_menu = MenuManager( Action(name='New Project...'), Action(name='Open Project...'), Separator(), Action(name='Close Project'), Action(name='Close Active Editor'), Separator(), Action(name='Export to HTML...'), Action(name='Print...'), Separator(), Action(name='Exit'), ) file_menu.dump() ############################################################################### def new_project(): print 'new project' def open_project(): print 'open project' def close_project(): print 'close project' def close_active_editor(): print 'close active editor' def export_to_html(): print 'export to html' def printit(): print 'print' def exit(): print 'exit' file_menu = MenuManager( open_project, Separator(), close_project, close_active_editor, Separator(), export_to_html, printit, Separator(), exit, ) file_menu.dump() pyface-4.1.0/examples/python_shell.py0000644000175100001440000000323711674463363020666 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # # Copyright (c) 2009, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # # Thanks for using Enthought open source! # #------------------------------------------------------------------------------ """ Python shell example. """ # Enthought library imports. from pyface.api import ApplicationWindow, GUI, PythonShell from pyface.action.api import Action, MenuManager, MenuBarManager class MainWindow(ApplicationWindow): """ The main application window. """ #### 'IWindow' interface ################################################## # The size of the window. size = (640, 480) # The window title. title = 'Python' ########################################################################### # Protected 'IApplication' interface. ########################################################################### def _create_contents(self, parent): """ Create the editor. """ self._shell = PythonShell(parent) return self._shell.control # Application entry point. if __name__ == '__main__': # Create the GUI. gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/explorer.py0000644000175100001440000001433511674463363020017 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ A file explorer example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import ApplicationWindow, GUI, PythonShell, SplashScreen from pyface.api import SplitApplicationWindow, SplitPanel from pyface.action.api import Action, Group, MenuBarManager, MenuManager from pyface.action.api import Separator, StatusBarManager, ToolBarManager from traits.api import Float, Str # Local imports. from file_filters import AllowOnlyFolders from file_sorters import FileSorter from file_table_viewer import FileTableViewer from file_tree_viewer import FileTreeViewer class ExampleAction(Action): """ An example action. """ accelerator = Str('Ctrl-K') def perform(self): """ Performs the action. """ print 'Performing', self.name return class MainWindow(SplitApplicationWindow): """ The main application window. """ #### 'SplitApplicationWindow' interface ################################### # The ratio of the size of the left/top pane to the right/bottom pane. ratio = Float(0.3) # The direction in which the panel is split. direction = Str('vertical') ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Create the window's menu, tool and status bars. self._create_action_bars() return ########################################################################### # Protected 'SplitApplicationWindow' interface. ########################################################################### def _create_lhs(self, parent): """ Creates the left hand side or top depending on the style. """ return self._create_file_tree(parent, os.path.abspath(os.curdir)) def _create_rhs(self, parent): """ Creates the panel containing the selected preference page. """ self._rhs = SplitPanel( parent = parent, lhs = self._create_file_table, rhs = self._create_python_shell, direction = 'horizontal' ) return self._rhs.control ########################################################################### # Private interface. ########################################################################### def _create_action_bars(self): """ Creates the window's menu, tool and status bars. """ # Common actions. highest = Action(name='Highest', style='radio') higher = Action(name='Higher', style='radio', checked=True) lower = Action(name='Lower', style='radio') lowest = Action(name='Lowest', style='radio') self._actions = [highest, higher, lower, lowest] # Menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( ExampleAction(name='Foogle'), Separator(), highest, higher, lower, lowest, Separator(), Action(name='E&xit', on_perform=self.close), name = '&File', ) ) # Tool bar. self.tool_bar_manager = ToolBarManager( ExampleAction(name='Foo'), Separator(), ExampleAction(name='Bar'), Separator(), ExampleAction(name='Baz'), Separator(), highest, higher, lower, lowest ) # Status bar. self.status_bar_manager = StatusBarManager() return def _create_file_tree(self, parent, dirname): """ Creates the file tree. """ self._tree_viewer = tree_viewer = FileTreeViewer( parent, input = os.path.abspath(os.curdir), filters = [AllowOnlyFolders()] ) tree_viewer.on_trait_change(self._on_selection_changed, 'selection') return tree_viewer.control def _create_file_table(self, parent): """ Creates the file table. """ self._table_viewer = table_viewer = FileTableViewer( parent, sorter = FileSorter(), odd_row_background = "white" ) return table_viewer.control def _create_python_shell(self, parent): """ Creates the Python shell. """ self._python_shell = python_shell = PythonShell(parent) python_shell.bind('widget', self._tree_viewer) python_shell.bind('w', self._tree_viewer) python_shell.bind('window', self) python_shell.bind('actions', self._actions) return python_shell.control #### Trait event handlers ################################################# def _on_selection_changed(self, selection): """ Called when the selection in the tree is changed. """ if len(selection) > 0: self._table_viewer.input = selection[0] return # Application entry point. if __name__ == '__main__': # Create the GUI and put up a splash screen (this does NOT start the GUI # event loop). gui = GUI(splash_screen=SplashScreen()) # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/wizard.py0000644000175100001440000000613611674463363017457 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Wizard example. """ # Standard library imports. import os import sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import GUI, OK from pyface.wizard.api import SimpleWizard, WizardPage from traits.api import Color, HasTraits, Int, Str class Details(HasTraits): """ Some test data. """ name = Str color = Color class SimpleWizardPage(WizardPage): """ A simple wizard page. """ #### 'SimpleWizardPage' interface ######################################### # The page color. color = Color ########################################################################### # 'IWizardPage' interface. ########################################################################### def _create_page_content(self, parent): """ Create the wizard page. """ details = Details(color=self.color) details.on_trait_change(self._on_name_changed, 'name') return details.edit_traits(parent=parent, kind='subpanel').control ########################################################################### # Private interface. ########################################################################### #### Trait event handlers ################################################# def _on_name_changed(self, new): """ Called when the name has been changed. """ self.complete = len(new.strip()) > 0 return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() wizard = SimpleWizard( parent = None, title = 'Create something magical', pages = [ SimpleWizardPage(id='foo', color='red', heading="The Red Page", subheading="The default color on this page is red."), SimpleWizardPage(id='bar', color='yellow', heading="The Yellow Page", subheading="The default color on this page is yellow."), SimpleWizardPage(id='baz', color='green', heading="The Green Page", subheading="The default color on this page is green.") ] ) # Create and open the wizard. if wizard.open() == OK: print 'Wizard completed successfully' else: print 'Wizard cancelled' ##### EOF ##################################################################### pyface-4.1.0/examples/multi_toolbar_window.py0000644000175100001440000000533711674463363022424 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Mulit-tool bar example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # FIXME: This is a hack to disable the AUI module which causes the example to # not layout correctly. try: import wx sys.modules['wx.aui'] = None except: pass # Enthought library imports. from pyface.api import MultiToolbarWindow, GUI from pyface.action.api import Action, MenuManager, MenuBarManager from pyface.action.api import ToolBarManager class MainWindow(MultiToolbarWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self.close), name = '&File', ) ) # Add a menu bar at each location. self.add_tool_bar( ToolBarManager(Action(name='Foo'), orientation='horizontal') ) self.add_tool_bar( ToolBarManager(Action(name='Bar'), orientation='horizontal'), location = 'bottom' ) self.add_tool_bar( ToolBarManager(Action(name='Baz'), orientation='vertical'), location = 'left' ) self.add_tool_bar( ToolBarManager(Action(name='Buz'), orientation='vertical'), location = 'right' ) return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop. gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/dialog.py0000644000175100001440000000476511674463363017424 0ustar ischnellusers00000000000000#------------------------------------------------------------------------------ # Copyright (c) 2005, Enthought, Inc. # All rights reserved. # # This software is provided without warranty under the terms of the BSD # license included in enthought/LICENSE.txt and may be redistributed only # under the conditions described in the aforementioned license. The license # is also available online at http://www.enthought.com/licenses/BSD.txt # Thanks for using Enthought open source! # # Author: Enthought, Inc. # Description: #------------------------------------------------------------------------------ """ Dialog example. """ # Standard library imports. import os, sys # Put the Enthought library on the Python path. sys.path.append(os.path.abspath(r'..\..\..')) # Enthought library imports. from pyface.api import confirm, error, information, warning, YES from pyface.api import ApplicationWindow, GUI from pyface.action.api import Action, MenuBarManager, MenuManager class MainWindow(ApplicationWindow): """ The main application window. """ ########################################################################### # 'object' interface. ########################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self._on_exit), name = '&File', ) ) return ########################################################################### # Private interface. ########################################################################### def _on_exit(self): """ Called when the exit action is invoked. """ parent = self.control information(parent, 'Going...') warning(parent, 'Going......') error(parent, 'Gone!') if confirm(parent, 'Should I exit?') == YES: self.close() return # Application entry point. if __name__ == '__main__': # Create the GUI (this does NOT start the GUI event loop). gui = GUI() # Create and open the main window. window = MainWindow() window.open() # Start the GUI event loop! gui.start_event_loop() ##### EOF ##################################################################### pyface-4.1.0/examples/progress_dialog.py0000644000175100001440000000265711674463363021346 0ustar ischnellusers00000000000000# # simple example of its use # import time from pyface.api import GUI, ApplicationWindow, ProgressDialog from pyface.action.api import Action, MenuManager, MenuBarManager def task_func(t): progress = ProgressDialog(title="progress", message="counting to %d"%t, max=t, show_time=True, can_cancel=True) progress.open() for i in range(0,t+1): time.sleep(1) print i (cont, skip) = progress.update(i) if not cont or skip: break progress.update(t) def _main(): task_func(10) class MainWindow(ApplicationWindow): """ The main application window. """ ###################################################################### # 'object' interface. ###################################################################### def __init__(self, **traits): """ Creates a new application window. """ # Base class constructor. super(MainWindow, self).__init__(**traits) # Add a menu bar. self.menu_bar_manager = MenuBarManager( MenuManager( Action(name='E&xit', on_perform=self.close), Action(name='DoIt', on_perform=_main), name = '&File', ) ) return if __name__ == "__main__": gui = GUI() # Create and open the main window. window = MainWindow() window.open() _main() # Start the GUI event loop! gui.start_event_loop() pyface-4.1.0/image_LICENSE.txt0000644000175100001440000000622111674463363016747 0ustar ischnellusers00000000000000The icons are mostly derived work from other icons. As such they are licensed accordingly to the original license: Project License File ---------------------------------------------------------------------------- Eclipse Eclipse Public License image_LICENSE_Eclipse.txt Enthought BSD 3-Clause LICENSE.txt GV (Gael Varoquaux) Public Domain N/A Nuvola LGPL image_LICENSE_Nuvola.txt OOo LGPL image_LICENSE_OOo.txt Unless stated in this file, icons are the work of Enthought, and are released under a 3 clause BSD license. Files and orginal authors: ---------------------------------------------------------------------------- pyface/action/images: action.png | Eclipse pyface/images: about.jpg | Enthought background.jpg | Enthought caret_close.png | Enthought caret_open.png | Enthought close.png | GV image_not_found.png | Nuvola panel_gradient.png | Enthought panel_gradient_over.png | Enthought question.png | GV splash.png | Enthought pyface/dock/images: bar_feature_changed.png | Enthought bar_feature_disabled.png | Enthought bar_feature_drop.png | Enthought bar_feature_no_drop.png | Enthought bar_feature_normal.png | Enthought close_drag.png | GV close_tab.png | GV feature_tool.png | GV sh_bottom.png | Enthought sh_middle.png | Enthought sh_top.png | Enthought shell.ico | Enthought sv_left.png | Enthought sv_middle.png | Enthought sv_right.png | Enthought tab_feature_changed.png | Enthought tab_feature_disabled.png | Enthought tab_feature_drop.png | Enthought tab_feature_no_drop.png | OOo tab_feature_normal.png | OOo tab_scroll_l.png | Enthought tab_scroll_lr.png | Enthought tab_scroll_r.png | Enthought window.png | Eclipse pyface/ui/wx/grid/images: checked.png | GV image_not_found.png | Nuvola table_edit.png | OOo unchecked.png | GV pyface/tree/images: closed_folder.png | Nuvola document.png | GV open_folder.png | Nuvola examples/images: closed_folder_24x24.png | Nuvola closed_folder.png | Nuvola document.png | GV open_folder.png | Nuvola examples/dock/images: folder.png | Nuvola gear.png | Nuvola examples/workbench/images: example.ico | Enthought pyface-4.1.0/docs/0000755000175100001440000000000011674463363014711 5ustar ischnellusers00000000000000pyface-4.1.0/docs/DockWindow.ppt0000644000175100001440000615100011674463363017511 0ustar ischnellusers00000000000000ࡱ>                F؛ Rz 8ݎJFIF``ExifMM*bj(1r2i``Adobe Photoshop 7.02004:08:31 17:31:47jS(&HHJFIFHH Adobe_CMAdobed            `"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?o _yE/k~b+ؒRca|w߭_mW$?{0;o _yE/k~b+ؒKߟv_g/"?Ňֱw^?x=};b([}h% #b-_*o9#Q?IzH'شffi?vO^?}_oܰXGlt%!?d_}TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$T?=x2.}7DIy}|2.?s{2C?7eЇr}#$o(ޔ߻dW}s>s5ޕ6>|nC؟}}^G֏mIvR^>Kqitײuc:l-k63{l~Neme6Xv}5Kr,czP/$~ϒFH9`4I n6Q$~!>a Photoshop 3.08BIM8BIM%F &Vڰw8BIM``8BIM&?8BIM 8BIM8BIM 8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM8BIM8BIM@@8BIM8BIMYSjblue_blue_gradientjSnullboundsObjcRct1Top longLeftlongBtomlongSRghtlongjslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongSRghtlongjurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM8BIM8BIM `JFIFHH Adobe_CMAdobed            `"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?o _yE/k~b+ؒRca|w߭_mW$?{0;o _yE/k~b+ؒKߟv_g/"?Ňֱw^?x=};b([}h% #b-_*o9#Q?IzH'شffi?vO^?}_oܰXGlt%!?d_}TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$TI%)$IJI$RI$I%)$IJI$RI$T?=x2.}7DIy}|2.?s{2C?7eЇr}#$o(ޔ߻dW}s>s5ޕ6>|nC؟}}^G֏mIvR^>Kqitײuc:l-k63{l~Neme6Xv}5Kr,czP/$~ϒFH9`4I n6Q$~!>a8BIM!UAdobe PhotoshopAdobe Photoshop 7.08BIMHhttp://ns.adobe.com/xap/1.0/ adobe:docid:photoshop:7ede65d5-fb9d-11d8-b5b7-f5f4d0bff19a Adobed@Sj     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?eu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ]~s=s{z~?d5}OY}}:?؟ݺAǻ~^]e⟏{>}T笋#ݺYGk=|YGu u_uvy(=k:̿ꇬ>:0}}u_N/k݇T=eSz?[Gê/?Cꇬ:2}=uCeOY=PΨzο_v:3ePaP:e}zοYk}=ھ~}PӬz:aMuo:q}}pꆹ2{q:a:ο[Mu~::wPOT=g_Osu}M׏_wT=H_w6zο゚?vP~{}~u}wBǻCua}Gxyoqg׬:8{uzξ:l=fQ˪u~?T>H_vꧬ-|:_Ce{oY_qꇬo>z̿~T>}f_ϻuC_>T=g?U=e_uC}}۪p*Ӫ=۪/3AuS}ꦝe?ᄒNz?݇T }OqQ_OuSAh }g=Sb{Z=uSA??zYS~kuW\Oݺ\ueu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ]~rO_{]kY>:?ȇ^׏{Ӭ)z?Ͻz?}1o?zʿTovX=e_OvT"}?O{0ažGYcU8YT*}nP-ֽTq}Pz?^:z̿}>z̿ϻz̿6}ϻy|0}ǗU>}fê/OӪYΨzξ:eaYqN:οv6z̿zο[Tx{t:aYOYE:lǻzο݇T4:}ߦzοO˪<:̿{ꇩ Ϭ:ꇩ #qY}ǻ:covuCuqOY~?ߦBqg?ꇬ_U>H_o{6|οnz̶`zY}ǻCu?q>:lgQS݇T>2|:[T9:mw]Pf_3ͽuCe}a:uva _x<ꧬ{#ݺ0}?޹q Ϭ?_݇T'=fw}PG_vTxnNg'/ϻzovu[|9a^e_~]T~=uSE>: }ݺ([qaOꧬz?MOv1֏Y˪1m]dcU?ˮc{Z뗽eu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ]!G߇z?=u_Yu^ aG?uR8_aOY[v/V<ּʿ{z(6oaOϬz̿w{aYGﯻz?_uC^/OxuqYWqOYGǻzΣ${NoYݽ=z:?{T=f_vꇬǻ|:?e_ovum~γ˪o݇MN+Ywꇩ/ꇎzο/T=f_wz:[m݇Mԅ}'{{]P:݇T=g_kzο~>Ba"{ꇬqzο[݇MY{}P#ǷT>}g_ſ}a:η}!6_ǗT?>(xfg[??ORqgm>:e?zlumcêYn=fSczu=ߦY?qYuCu:̿::agU5U?޽ߪOagϬz?\݇T=f_aOY:YGêlut yoNqUogU?> vꧬ}=Píu}?Sc7תeYG}u^#_ݺ?[ݺu}>_゙kS1O۞ꧮb}kreu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ]r>O\kS_}|}uSAa֏YݳOY׻PyS?{U }W*|??T=d힪z?=uSEa=۪oϻuOZu?:YGlm?pu~~1Y)}z2?vSe^>?Ǫ{vP?އS\텯uCa.z-P׬U=f_Ev6YSYuCeo)uquCua[݅=z:ǻ Cuc6z̿￯)Nv::ǻiu?O݇T8<:̿?PxooCuo:Y{nuÇ{]TӬ{5SPuovPQꧬßyYk݇T9zʿ}ϻc꧇Y$T>}e_OYGߏwS)cR:?s~{z*aYG{u${kb{ꇇYG]dz?{Yn=۪WcǻuY=T.}:?{Zeu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ]߽1|?s}kcUz爵dnz?U#1oϽӪ}>: #]WYGu^s_{U#YW)Sa?:Y݇T4f8CQ݇Z*{.-sP㬣aƝPҝf_quSe=uCeaY:2^=|U=g_tWYvU52ߟAϻSeya::>qgYê/T=g_v˪M޽pꇬ{ꇬcP~:ou#PG޽SCqꇬ=uCu}eꇩ :ο?wS^xCq^˩ ϻ2ۃ˦x!x|~݆zYzοæYǻzΧ=z@z}=zl>::+_>ov6YT=g_P:e~~=H_}uCe__vT:?=e[::ο_vSfO}Ps݇T=e_~zο8OvꇬuCU_wCe}nz?~?:*'ݩ0?OU=e_?wT[#YݫOYW?}uSa펩E}ov}T=dA?sEa݆~ޫ0}=s}=nAv>z/I o=}??}G`[ݺ]wo}>׺ԡm_3x_I[??>^|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/׿%n߿|G/׿|F/cj1?J'X{_/ V7_Ck1?J'X{_/ V@=s[WAd?Yx.Z/0^ k1qr T݇2r<_Zs[[yxO{˿]h1l_l1ooK=˿~]T1?{8%z?c_{r?e9c~pK@-+x SIgo/.e9g?韾skq8OIg/.gx.YOgYb4'b}˟Yx.ycc>yS?雿y¿f/}oO,Gae}T47<YGa?2;a<MEAO47<YGa_2[v_cOvD_UO3[@uG?{r8oW+Iae}T53<Yt0oOL=Gkoi,?/S.Y?o/́ݟ;#ݿ|M&e}T5Uɦ:̿˯_L]?Io_+KD?U<nM7U] [7vvr4CAC<Lde˷~~ d=sg*ȗo>yC?ow0L}so*ML?U<Ϳ4i/x3n<{UQ?Ldf_܍|ÿ+/tovʟS'w2ە?o |gWn?i)꧓#Sy:ʿ0}s)OL?T<\4Yx;I? 9ۿSyMg:ο?{'eۿL?Mʿ?e^>_4?ߝ9Kalg8),YGx?I o(SL?Uy+e_=?]ePm>y+GsY/?I>]6/a9˔?I?dw?%+|O'ves'WTT<·ug}g_wr?ϛ6*ꇒ9 g}e__xwUlꇑ9.?_YOΏ?Nw]/ʠTy3v\־/?,?{w_Wl꧐?_YoQexÞ9+ P<k* ?LW;v@v_mWo[:zo#}g_8 VoSs$epΨy|xk^;wǻz;epΪy[~6WnT?93#gMo?_YoOǞCÞ#-gU>1־7~|y{l+P}ϹFdwÏ#-a|Mevo?_YoGm׾Gi??~ '*?ow?Wo[:?|xwIs}s#g]lx+U3`ܞ|}}eo9mȮ_YW͏ߔ}~9k=nˑ"}f_5?R?vb^~_m6,Tw0\־ߚ'lK݇?r&kelۿp|y+aw@G?;`ßϝv,Tu0\֮4~Q^9??uS׸?o?EsZ̿i a!mel۟pu| x~?o?m,Ts0{\֮/?CN=>?}-gU[p =ȮWY?'k'Yo[:{Ld7?/ o!;em zAw/Oݿ?uSo_CsZ>F; ڿ{ uSo_CsZȿ f#'j?T>xuSzAg?ɿ= ڿ{?uSCuZ>|H9?{ ?u_ nWY__R~MnP[Ǹ|Met ֮?2Wɯn5}{[m[:o`7!]d d%| ;Wnv-Ti?0֮2??Lx |~nvo-hi?0֮1L?}q=[7Ϊ} nW\Ϙ&|_^G[7δ}?!]s >c&_ݿno-Wl?/!]s ~b&VW߿no-kl j]7hGiO5q}fo֏~>?u|.x_ҽmoZ>{|/Kb'|?{}MeO ]ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ӹ "/y_coan]q?;ܟ|d9u|c:u‰r}o?|c9u‰/B>ocoo o_n]q?ܞ|b:rf G޿Nˮ"/Bco{ ?_n]p6!'f?wg 1:ك_(f?wg 3'zG1ηs?.Q'?0/?ܺF8م_)f_Շ/?ܺG67ن_*f_׿yg ;ɿ7zS2s?.QMa{ }ٗ.? a{ ̿wϮ'oOA߿W̿ﭏs?>R-Lb?uc2s>>R=Lb ?}ٗ >>RULb{ _}ٗ >>RmL?b_-f_տn}p?ܗf/O^`”r_#ޏw2?}n}p?kܖ/{cc_K,ٌ/f ϿϮo@޿a]۟\)z%1/=t/=NS Jc{fՇ޳ ?=NSJ/@'go]o 0:L'ِ _2fտӮoo@7go]o 1?:M%ّ_3fՇޛ O=NSI/%Oݢٟu{np?+ܑjgo]X}:7ۧX)$_e)޿kE7ۧX)$?2[ <NSH?eu?4ynp?o@~?=GۯX*#2_io=X}+:OۯX*#3_io=l}:Oۯ\*$W_9Sw4?ynp?܍w4_ync?܌x٧o%*_ۯ\*J#3_io=[ _2zRٛ{_Oy'_zS ܋_.s޿xÿ#gۯ\*r"3^~?w٧o$?uO3>ko=oΛg3 =ǽ?n_y_ݺT\ǽwVxΝg ȇo[/ ;nUeCh?]_yDg w ݽ`?_xDg3 g`;gV :nUBGG?;?:Wu:nUͿBh]= O:hu®rI _[x*Σg_3 G/޿6_unp?/oW#_WV_4X٦r>?uٷuOο߮o_WuX:: ?:~VUA?j?\}boxۿX+6 5Ϳ~whX*VzX:?:g? ӷ [coՇW߮oVzX9UwlW_!?~kFٷwۿ\O+~OVzY/9_߬googo]g?î'ow_8/~=c\+OVzYo9c\O+OV~Yo9o9p?>kZo]gîo͇Wo]gîo͇Oo]gî'o͏Go]gîٲhÏV^6uz{Ǐoy f՟߿s/7{y f՟޿{/7l{_u¿mtٳgËnnpi_?nnp[i_AP{x k_APox f՗߿և]gǮ?7 f՗CPox??{;՗֋]U\0wͷ'Cu{?;m8Ãnogk¿]6p{ \zٷdÃno׿0wͿ'Cu{A໿mY=hۯ_ +oGP{wǯa_.VO~Z?8?vW={ w{T?^]7qW fՓ߿֏]U^¿]6p׿{ \zٷdÃno׿0wͿ'Cu{A໿mY=hۯ_ +oGP{wǯa_.VO~Z?8?vW={ w{T?^]7qW fՓ߿֏]U^¿]6p׿{ \zٷdÃno׿0wͿ'Cu{A໿mY=hۯ_ +oGP{wǯa_.VO~Z?8?vW={ w{T?^]7qW fՓ߿֏]U^¿]6p׿{ \zٷdÃno׿0wͿ'Cu{E *LҢ7l|e+abY7C^XGCǻϗ=e*?}ip^Lˣ ?%5=wraFGx7 4K<ԟ5Feo$xv!,wd }۶։/^CE ٸ|^w%yFiR1V+)4ytT¿k~Vzϔ+K[C]K#CUE[IQ:JYH$n`R2*EAHݯ ?.af=b*?}ip^L˯`oDξG8?^]|׿0|wJ_~ZFׯ>?r>;[Qs#׿u?u zҨ߿֑?u뺿ϟ\|VTAH_g]{޾+ uo?]z3s.A_U:7{wW9^*?}ip^L˯`oDξG8?^]|׿0|wJ_~ZFׯ>?r>;[Qs#׿u?u zҨ߿֑?u뺿ϟ\|VTAH_g]{޾+ uo?]z3s.A_U:7{wW9^*?}ip^L˯`oDξG8?^]|׿0|wJ_~ZFׯ>?r޿k[ʎ?OW?֝8i~m\(gc'?~Oz?oVޱ8<ՇXuìg}}nz6?﾿zX>zoX3ՇX߽}XcMӫub?lz ^:zXO{uqìGGۏu=Xu\yzu}yOu4Wak[5{Vao_q}ǺVao`?_Ϻ?X[5Nz }ӃuSՀ }>G`s^:[Wxu_OW~}Ǻ>ްqS Ӄuq}ot=\u޿9zȿl}Ӭ }\uaXg}}qCӃ Ou=Yz[ X[a u"m\u[:puG9ˬ-zqӟWՇQ۟ Suq?aoN Waoďu=О#1GQ7gW3_#OVbks.1a•Xaպo|Wc?Oz=l|)unzX׏Ϻaz=Xp}|=:돽u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^R鞐=ꮉM.qKd37 Z GR -=mdR@(o{딊xzOK,vti&nA?q=nG?+ivPaSk\~+fN?rZeb&IOMGBb`=Ķ![?LaO/鎮O?gWzpͽ.)Ar]]ecw/1 sh̴8 txڪ69yb^hUr1TTzɇkGܵ>%h$LhQ9G?A^{{^׺u{{^׺u{{^׺u{{^׺u{a(gc-zx=~~=׫>վ}p?޿u3?Sպ}u{Vb?[ޏVcn/[g?&ìG޺Ni?ՇX:{'޸u&>uq[Wa?>u=X|_ϫXSX[WWaouˬ-EyCX[ou=\7t=8Xz{_9SQ׺zSӃuN}n1?Ў1_uzpp8XC_ zmXOϫ>y?s">zpzX[{C }qN7CӋvCX[D}Qx{W`o<﾿q?A\zu'OW;}?ߺ=8:>}[-l}mX{Waot=\WaoO˫u>ì \u}{aou?>:ߟ5uq}?OtVaoǎ=.:xo'u>Xt=XuW=b?~z iX5׫0}#zߏuXǽua6}ޱՇXXu{Vy[kX[_ޫՇucpî/z=[Ӯu~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ[sx>L yogp4۳]hq Z(7T)kh Yfvws>]1DX6,8T!v`Lo]uS$ ƀ@$U?w(ʜFrrnt« t%8 >E(0qҿ'@@v@e 5ŭytp7[n,lOZ+:O{q|Tkߚu񛨷_jn9'כPĴ[Kl\ߙ)6ڀ5jIb$FU#=x6h vGL~J'o@e7;}c@?2=8K?ʶp|ﯖ9nCoTM/.`o+H3ޢG bJ@A`l,,vv.t梤;r'XVܡ1MT9'99tn΍3]ÐnNL,)CvrW lujΒm^^L^R`F|a~Uoe*fn$"V:?E@a֊y\Fc)AVW![c!h|]=&3A 54Q`$j T"h2I8'$OP[wg T3:o7_Jw_se~G˚퓹?1}AEW9 "dmYY",y%/kasG5I̛˳ŷm4frHu xANy\rѼn_:F)k!jJl^ݤ)gxGx7IdIz#T.j,tIuTu$gl$bEjHJtB8M̠8 Pժz18?.^Lw{utj; 5]MeQmuJijY3$_oݢH7"I!%QQ=FFQ Ӑ?.m1c[H8`:BTO[߹7jcz1U[Cg>Kt͵%:T*׭5I)\u6 X^sx2AP1e'~8a^PqLP+v`v@i~uf󬪢mGƫ#-(ή.^awkmv2Jֵ:{N:|6s%E F5M?)<( Z}(rzg5fޓi㲸-E&lg5Q<4s"kܱ.ѵl7 nnUHtQ/;maqrPڴIMe& *̀+=wG}!z{"so;hdpb6ܸ[ev/sXx:HQl0El7IuT Հ8 '͋'7M ݃2$ ЎʲO?5~Z?# wiwlro,e7m肆4A"}Ϛ?vlUٶw;AKQA>PH9VֿSmvn$(@ N :_>ѐiuzwt~I 619\G?|}d*bwN~ͪKE8ټEQC@ts7.ivN@1P`+릞g͋y-Yӷnnu[6͏6~Y; B*%1D{.s҈# @M*X2O;G($sjK)~t_l%h7%sn|%}^#A墥O54,l8#/eS4P3kBUI7{ 2̈HhA?궗L|r ԟ:lbp5-͹[M1vG0tP #f1S<<-9_o/`ypXң[4 PTT:Sv[9AGҋM8 :A_|^~j-FRdy֔QCp Ijl|TL55b{E,XvJqէW˥ ;!<0kLt5=ˋl|{7 to^n޶힕?f>8 j`ܙZ>tKQ,h +ȿypv}pK4y$:V$UX 軗onݷokז wtxu*Iqdžz/˳K;:^vGEAvz]Ż6fSobl*KA*2T l.gKRKjpO=wTfZE q8Q}__1z[jڵ]=o~GGIⶮzL]lɘI{5L۩EE Hll˛ea5&FeFU`zwZ6,{n'4{=+Džd|s$G. [ϣcK(j=sV.r~ƷV[xw%TPThj+O8uE'O"c7q@u(gc(oϿuǮ{}p#޺ϬGOzߟXu8c7c6Xqc?½b?s]Xu??zì,>sX.z Qu?z t4}a>zȿ~ou~}׫}u'7~z[kuq}8>ް_u=o-"C uza^゙zsoϫ ]\u_׺:7^ t=Xu>iӞ|:3Ǭ-?Gnt>}8>}aoW}q\Ot=\|?q>7q8:zXӃ p>7{:=q?v{ՇX}sq:pyu?_t=\uǺN8?t=\|Ot=8Xt=Xu=aaOW`o߁N7/u=[-ÏN ?~z?.C^>ΰ8Sӝ`?zX[CXxWXaaoᄒ~ޮ::[gս:z\oG}=ua3{V:މ}u]o{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺O92oO}Tx?eʵy=V]=u#!/>uob /L{OvԷ[`:{xAZX>ǒ]IzxNìj̾þ^}}N-?R k/CZ].6Syj,F2O[?|cyژ}X+ާmt_[Ozoۻ+j V5ŒTyI>(:E(,H8EI$bN hzvs_1ۻ {RY|Ybkc QYi*W4u {rG*,8h`i#'hB tfn>o]%ˡ#{dߔڒj(jw6f|-^<29Ȧ,=[jw;*_XFPRyq9dW9~g+OJօQ<j3owЧh>I|sek#r,衆 |T*DePmvG˗%G((ZԒp4=G[/ ~~b)5X-JF}*::"~dcW);wwZu퍡cw&X%TPJi1opQT"܇x%HJ9ݹ&)-7.e Y6Uhj L2:s8E6wi:++`ji_EHd_ݻЛ66ػқEGfv3-JFB& R$!*u,M滍⽖x%JVVd<37w;4x2e`8"C>=Nyr&Sv6;w?.;o+^n WG5^o 'LSLʓ%D iu05~~.ͪٸ.-J|,OoFAvU d -jF@=(:=~=qhNd6s~][xl|;I68 boe{o kW}jj !t$AuMRi,*ڐ5``ytc:Ӵ.-+Phq_#ѧ.ssvmgmZ%Zb*E2j+Ϡ/nSz> jJf}d'/ 5eX>6ʻoHlRSfٷך}v' u:ɯU=`; ܿ$'|݁08\u>v 4W ͭWQSh7wgrdR Ƣ`qP|ܡY]-4Y#ÓTQBz;:7|€~wL.ۻ{vb,ٻ7{o,n;)+fD;b2Qu>ػ+ylqB$ 3kʤӀᨏJ-9nym^FUtV4B}ƤcdV%Y5#}4sE1rARI:h<̎=[lmk-YyȲ)#i!B( #ʾag?_g>۟#:c7(+tuΨl®SQUdV)S'Yt)ܹ_q<mod.4 iLV6MK˴,U[dž(lG}t\uoʯ);sv>'ݹ=;sm1zݽAf&;ngr_Řr4NL?]*71eT0R `25EF+Ї`^_i4gA#1u.x4x@5S2OO6oZuFPo^z.ƃ. ISU;&!AC,wQFuQ;<˻T-]P*]5 <ӿ1n]$Vh6rQ LƜEj:-0ay=n]˞~qQ읹S- Ic,C&\ ǸOm,wQKJ溅hq_#g-umnԱ "iy6w;k5;gp|qa2Lo|~;3K5C_C* DNKV*A7#-jT+*"A w#}ݥDRA 0AV θcwTٛ>COn>CÚVW㤬L@`\rp[Sx,M cЗ+kIb(¾`9aA :(gc'\?q^2>޺}{ׯV{ou}c?[mziZunOz:[z?~=coϺ:uVuoޏWao^X[SՇXW ~u=Xu~}קX'u=\|XWao=a}:zpS-{ ?.mXu׿aoOt=\qXZ~}_Q{`o_CQE=qq${Ӄ z? cXC:X[ot?.`o{zQA{GoCǕ8y}ONvzϺӫaa?_t=\p}Ϻq}ot^=aNk+\uz{}OV}o{XZotVb?{u#yOu:}uq">\u`?gXn=뭏X}9C}=nǺ>:߽uoX|O|,uxS׺:}}u׽u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^?Gwv'J淆/ y~IS)Uv|oͽ}M}fG3ZV5U\YSwG)I1ijMSFapUD EJ?4{W,Ov%.hk?c1n;pdٍp9<~|nFRAPZZ7^UiwkoݕKl 1#^KKw /^'OJ1՛l.H=0zRXjg`xLSp^O۹,D=,R(&>Z[5-*$MH5Ş[lSTP5Lր EA)N9Wmm1u{wG\F`k2ݍ*h2;tE nGnMj4\#.V*1M$4 .6.]t7]ll~;Onl}ֲֲ H/;w0Yeh,QFi41ۣ^ŋRPž) ?zӓG4,TMIS_ kG=o[Wv+dsQU*GQ*+ :tBB$Vg2g2=~#ZOA@<''ĎSZ ,Ԟ$Мc{Ue0I ȭ|fg('}Z,isYQcgrmoUuSϻ)Qҡ}1-cMZhBjz^u7Al-_^+&\5vs٘|&a1xdIQUF@S%Pίqk8[!(I V cUkshmuիLb1 Uԙ>Ѩy=]ڑ[>3 dSpg+;YII'R9ilnRB]D` PR㑚}n߭quTX&9 1T-]IC"A#sQHՂ)GKT, jucRN hxQ,5_bct{7pnlnOᴘ-'ͿsjqjXjmrKH$nup]@R+8T]Mx9y /;3}IVקktIJ*w䨦KSQ2Kf2 ;w/ՍֵH]YzDh2 Z#JX__j>(gc/Xo~=o}cǺ:]Xp}ގ:[c>ՇX}׫??'޾έu?zX͏x~=X>iՇX[ՇXcou<:t?>:ou}:}{'ŭq }oOV?CXz s~?~ySXo{W:~=ӧˏX[>z:}?Oo.7ak}?SQWant8or}qӃ?N=zߟ>z>z;u\zuqW`o:{v[E \uq?ao7CӃ}ksXOuqz??>x~7Ƚqz?7n?u=8:{>}aot=XS-X?ӫ=ak_\ӫpzz[Ϭ-}qaﯺq0oVb?|_z<:=a?OSǫ.^8u}*u?ӁXu?ՇX뫎=׫GV:Ou}?îyunz\?޺z£ua?q|m}Y'o:M׭[׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u+etettb 2:0 ߽A z:qG#v]ӬtH;mRƑ cUl XdN)kϚ%J׻{A[M?EϝT|ORw,{>o` gрeox:c)Wesy1KYڵmC-eS==bɍ1"T =tK]Ph2ZSX U[]C^ݗ n w9˷C{M. @pbԟ2z$!׺u{{^׺u{{^׺u{{^׺u{{^׺Y(g}눣OǺ.^_ga]o׬g{zߧXxoNպOOOuìG?._OVc?_][ma=ϫ?O~cGsV:Շ_ՇX[|NaVao|}׫a?ᄋq}a[ޏV:￧}xq}^8a?zX| }W}O=zXO~~8?~tˬ'SՇX[?#C9v?Ǻ=\yu?}uaì=ӧ=:?6CՇߏu9}SXGoW;xaou=8:Om=\qo\yu~}-}?}uaì}gϧw??>׬/W)ot>}]z??u=9W`o:OWQOVaW`?~:}q?O ğ~:ߟ{ì'"`oCX[+?[nSպ?SӞaoߟ>&}}uo>Ϻu~O:O{ՇX}otì\N=q#?~?[=À[g'+7Xu?>:ߟ_N8u?"3돯t=Yx?~{c~?[Vƿ=ìMޏV.G|ȿ.w~LVE;,?E'U,j|Ѧ٧wſ< K?o{ߠ_ӟ˻_ˢӭƵ~g!7>/{Y.׿wKu>CVox}_˯vG]{?ϯ՛^~ݿ}{w^?fׇ߻o^!7>/׻Y.׿wKu>CVox}_˯vG]{?ϯ՛^~ݿ}{w^?fׇ߻o^!7>/׻Y.׿wKu>iC_Fɾ.|nC_^M1)yO4?PNG  IHDRxsRGB pHYs+IDATx^G.{DdȪ:Uu;H#4f7u_眚X$Ab{C d/Z1Gdd>?nݿ_G[T,vT8Cn̨]}HWG>i+Pn#;|7?_g,JhlT':E_Z?Il} `?$2hU-Ӑnb?֔L=+9EHwX85H$-rq&8;N d YԼZ-U_ʲ(ʟgEaGpw?=R;;fa7q/J `e%p;ڐ)@[,DqXt/AnD$]!hp,jl,ٮla&c0"bEpݡ! @7N+$Rr75??cg;6):Ԋwp˺M?V{_)ȽI {YAtjY7kT2 PP5A,_ؽ{7w$yno8*xxȲ,E߻W7w!MAֻ`XpM;YAC0 g~Chtyg%gg}/"*?x~q0QvdX4FQ2NaYiHYSv"Z߉C2Q2ܠ?R &ytr뜽ȵM'|4]6eoRb;U֣B.Yְ;,% C Po)EA > kG#icO<4 YW c94Ǒ$M)˥oedbMq(ԪnFh*% P~f)>xlҏEx: ,}tţζnDG9djs8=oAfk#/ ©G,? }!ÁMB~0]Bё~tT;^@jheB;~#jM9VCFd1{є/cYD׹:2[*T\gp6 P[Ѓ41VLn ~uQFjEMd5|HlM:_~j\G#yul~$H)̗>Ƭ"a{71\ 5b[Cju"DC HxuM9&{T %Zl jno,Pi G}ܰ+&\Gb!(c2!DF&(gW*W0XzE{%-tLRiH kN*Ȫ%LZ5c#=MdE4_XbU.DZ3wâ)GsX slmXc7 =ӵ+6<|TCPmF+È @0 ʫwvR-RăN5cEZ/Gš3ʁZ-#&r.L0Ew GzyLKd@fb9 L-J,ܬ~5k,\X ,tR2"U蜡^B~Ԥ)XQ_RXOB>&D*"fV8EFB-nR;Gڊ;ZwȌ6KC eEͿa%p 8'h[O Ybp ϱۣK~ne`ǯkD9ZE8TlV\8 !1 , {K;f)]* (UQ%aL~k6"vQq '"0F!`k4յ )q)%y=dVKy&-s?@QYcCԃ 'h򡀞?qI?4@vy7LVK3\':yYf8݁/Ar1kw#0BH8-Lb/9xzVRF%h)qC QG{3yu6a+j9Nh$C9#C3z+- {'8*>Ki.:dȪ_/V6 p w\/FO3At;R([lVR3LeH, 4rFId`D]fICmIo&49b&JLFHp8RyZ[N$~Bw=qe煷3]3rp9:iO! EJ?mH&swMExqj;wQF@WpV,`/H] HVIU[$&g1nIyruqYގ37C m&X;Hw9w${s9/[J"p^Qs W{0Y9((Pk!D9/YĈFCݠ3[z@\Wf qeǤ[feeqۄE6ýY~?"'^ ޽`يALU I09$m&\`ӡ5Lq7<{n'CGn$̳I(reó SgDs=jա1ьy-#s#6t3PZdx7 aX1̃?I?ZhSV:cU K7ai3,PiKK` GM3ߛ]*OlN>,iU?\bDv[B6"TI @0:4M8+oȰfll=gL&L<"0S2O#F‪ˑ]afIX2-|D5NعgtZ0`ahC:s"zim2^;op΍ 2T`IN3ޚB8xh=5P`!;St\ՁAd1JE]"6Gu< 6kmIPu $:9FψH51om *Ր): 9x[o9<ޣ&KRm#~%})$4Ip1]az77 = kOU4~.C_]؀I@3C?A߸8|ȉh/5_hG uvvIR 1ک'djwD⌹T7䳏> eN(0@C)eK8>΢R,K䏒&Cdh>Ҁ-@2 Z$YRvє*} =@dc:\6"6&6Y9hS`3~_mhQXF7W^ԸHA}oU~)$obrϰRuȦ$v.X|(Q dIdp&VplPۘ P2t+m@1G,drA!|~#B IeY+ YR,^!Y>gXrdβz#M wlVilF D9|1A/1ȶY*qq=]Peo=+JjbP "NqlO:aPJlN}$8{dxZ%jSmjPMRAb%ޡ9gmqԂ%"=j`;J\yܑ˫ZQjDr10x-YnU?:3lxaA@AY@̽~f [UO;>+?EPRV6 dZn5,8a\ Y!huh X\ܬ5(3穁 Ug9AŅbkM*8SGnl'_5)plxM0l l ;YR1}*Si$9Dl>J5j]}Z~ʱ804Uc#"{TU@vrTf[n.Xa"5&_F&VCtj0n" )q WeG09~Ju$&!l~FbƬ?t+py,ւ9(_:#( /tWdvW}48n,ohW}n(7Y# mP PgIPm:/$a|D>hdL'75Ih3_:d _~eh@; cD.aiMpB3*$Ԣq'i:}vqZџG4bc(/:"{/O1`\T8d 4\G`Xa t 0{R^!xbD]3*_{T%*6bBdN;K ʳ! ( .p tr2o3gImkR aJ0?bw?)lE=u(I`&RjѢSKtZS@7_tiFUѢNӖkkmTk>Q Ԏhƞ$iuDNiVbg97tfq=[((+ Z 68_{{U1Hj /܌usP+RP8n#{;OxƑbr)\!a_-W`?=+aQfPTllhpd&,RTɉ\ >@,#@a㨄)P D,lT z cc{D'H3vr_#Tc><م~Z\hQ43&N;ֈ/vr*~ T$twVXkJ(ǖ7p$ $R5#j- f*3.<uZ<iHMNXn52{^t4)rSSu=ˈ60!orϭN*K>(S##;߸'%4lŁijKQ-׭fe\ mmC Vۣ+MAl A+4xwh֌IXA;UFA<㢬8qT^:) pUw7:u0(rQԓwY݀Azl`1d / pz@aFN>6axrEŅnՠBm:v5z+2;)H1ǭbKtJVNw>CtJ >?ƓWZmNamHCkDB1 ]*09V>RHq,e/Od#z}@v:F,q`U%3n[@(|nM).matKzv&f&bP{R tQ^r1Z]1%=0򔁀&5@PVQԚ Gn:B"Yx5\EV0QU;Ζ܋C-? {a|*[4Z݇J/YS.pWki>UuH znʐKW֐j,R !pPQzLDs qſ2މ G{yaD[߁oy[DfmN <7tCPG`& x9e%HO 9p\b@C+/G\'AUuOBS5#FE%oxD\_[cnveM񼉡U6kQv>Yh#*C\E`6Hwttz: +zMQ,Fs3WqoYVzA ~yƭ~4 "Bw& F()"a%pziY-/ښ~*5S3Yߜˡg QXQVrwim*lMˡ5UI~O{G5yEْ4rҹ'P֤DIT 93*Hc휋moZ]eO1Taejx{H˝p?IhS*xg #}-NZ*qBYB~6F]h~oȷ*(\[<~C20%vRu` @G2+v"8NPuH&8(`:\?3'e@;k]!w+ O4꧉3ﴇj;/bm7P"g]FpقM{<ϖK[,Wj6n;d N:2NS gh-8j&(9dz~}^]oLǺ-0-_}I5z 2Σ1[>uH9@bjcP&)b&*v)+VTC0 TiyNC(coTRa&u e!ts0L4aOh`4%8 kG!ӛfͨKΣ0b>Tӹ:Ph"T1SgܬUnֆ>huu:x8O= Jw7noXLX l&6T\Ѽ5єȕ,S6V; O_X3 X̦M^_:Jo8\ B]dkC *xw])!I qDkh6@pCSG6F>O(;],-%Т4whWGH./7FU@Z^iQRU2H̉I[kW"/7'P\n9uvOlWa`"؉MK|m0'2E5Z,a NnT(Q ©Foy[ƔNYW; XrN1~HhD"|ؾsϞv#Sbh3M >m"VV״G˜`U+'w1oqdrW7A ՌlYAh͔MKK)d&eۮzSG-/B7jP]p$p;m~_WvbƶXzUʵmp y1WKd8NД7<3ޮ48yE;/-o(&[kBX˄2[ y.F8n$0Zd ,'h#গ0#R M[էQuƍM=[,Rd* *! R@5Mh!(b6y̳?.Bm"\tN r뫥FJALGOiYEQK9q fxI|7*)Ű<{cڙZ}P "UR4bsʆѝK-oe0ȴL9%=a&Gd"_A Y`G!l)L#3gpiŚȵxƁMQ99uHedҌky%)xձ@DMb BOJr;RˣCK 0LaD̈́GJ/УBC5-t)QmGl#:=!xEl e8ƭrNfv ,ì%'";\U̠%vk]dyC>fqFn2!oy˜P=z LioP5p Xc:\R41x5nP☒2x7/[[]%8D_ 5@u(bK]lK]ļEOݘd;6N,[E)GU fij @cB-VXr5Doq9WDd[ w<'_9 51ɀgm8|)clGN%%w~t5];(;'i蘊!b^*I?SN]R|/% Gr3W-y]hPJ/rP2nAvMe",:\.:ΒN񰫍ᩂ`# [4.`YKH,%;V%D6ylZf4⹇f_*,=2EfqXi!,2x5+,"o)À5|4C> R[9 $pEs, B3jI!,SϚ5%q3Fdp:J < (F@C2@JWR_E<+ /t\G&S"K/7GPQ!RqZَߕZ9ʤ=tLZ8 Cim7Ɲ @SRU43"ngc)cA6B5kUZv@dNA'<pG6'N蚌Ҏft/v+)0"/UDmQ{hڱk46 P4 e m HS2A yD2Wgʩ UF̝hd/*Nz0:WCtQ՟;BZVpq-O嗟q*EYifXJ8`qpM%s08 խTr"_g;H]bk%'"v'Y-S ]І!EZyj{P|rU8yl9܋ {sfKo&Cf7maCJ4gOEgF)q'EMƷ=\I?~W`ZY[ yF$}K9e mAǴ0rxVgwFhIĠ$A;E]!\Tx ƹ$ "CNraMčK!Ad}2zjZ@ .g+G"QJH:1ƜÌ]BK:1 #,Kw/CK1gyCԬ%nà٧`r D񦏔rVB@-gP %7![إf0ڃ*ݳѱRTdnDYm}p֢n*ra}xt;,Z`B-Ts8,S#E} pm%VJ,.9ZL:0k94Bެ[ $TPDU&g$Px$|Vq?d?"e18qp ]EΒsC:Y E^koH;[Hc?L0Mw6^ :x3\ߓ \T ;C'@iiA*+} 9RC$gΕÑ6zzĨrl{|qL!(x `Tjn( `?$ ΧH2` M=}{IPt!ڒAx36g!."rE֒.nb\9t\{')RCY`pBrӵXb'dF +@YG6 >B:"+XdѕBc,LfÖB<>jEcNT|AԱC-$1lX(,hi5n L#s.7(^ᭀSr /< .a2T.݁HCmM&_[1s4ZKXGyKn(vЂl;Bo y̠蝊[$dIhLX- ef 1k7J0o [YGMSEN9ѧ:90kD;s% b1.Lj9 vtLG_ykBcX|CcfXLFJX\ 7i:s9膘#83jo&[6.@D9PҩR{h2puԳQ?˹,. KDCg.t(P" ކe4E;@r1(2וr-&_:G*oTWwzΈ!Ӵ&S3Cpne{ 568q&ltm:!1[[=]CJ >-# Fߩk0N>@dm눬Nm^6*}d:x]塪Cp*b ` 2$g-\?t.N gN)j E?.QeթM0@'B⯙ WGř 5m;3:r*5PM9B:õ(N#" \$6?kzнp%|UE AJЎoc&<;6^_A\aԖO4/cfc/?H\d'f'G؋QEdGltjpx,`>\?{R51얄p]GƑ,glk0/܇9Vr'80D!:b|N9F|`L05o`ZD[X ~GI[4 TC";ӗVTךzTb/@ @ťȕդb}rMg̚RbE,k ֧F[&<INQzOݹ\-Hl]Z ʣ mx.HB &ce5yJqcrNvF'E]ӕ?N_ЀU E+aO~kPqC\WϝRz@KCG#R\gDf?C?eOšl_Hj=O?d8 1P![~ N%!2qZ*C:EjٲmcX(ðuM8(U-d*&= Q 2t޹bζC a(WzLuee?)0gO`DWN~k!o@TVJ5a4. 堞gBAհ'Sj./ԝ0v4޲_̨ѡ_aBRTM(")^ lftJqodƗ~QL P$] R{ JF7ʪG]iYnMb"To=<hL&*I?BP]}c.g t@5^\P^n) WԎ@+H:>}Yn.p#I]ыjib~j(NyB*lnn󴎞}vd7ݷGd:6OPg_ ]V /lҧjoGzTA#dnـ #IV#^<՝58bXC)bGe0{c St-*RϽ5xܒa"^eo-n/M)e9M9+iBǘ)SdOo{0[&冕2I)N@V=-~;H2t~==ut LKߋ'6zBs^G{f:%|NG=FU±-µL=%FJ&rc\!ȌNv4.3z vn8mNtc;^b(A~|2AN{Z2y϶ >| ^͏ r>h ٌ́]ш QρVi9H!U@g<عYc%ՉAӣhkq2NdCMmP, h'ǿr֨a$F4AzT(%Ab=*84z~孰j)t#Z8N=8%)MRƛ7ЬV.6W.&?-)Å9)z4 t4vt)h!#lJ6:[`&g: G+bTeiacn|p`Q?ο{yW@qHp:;W2Gޯȱ-\PF$qPVAi kB)1(P8Wr]m\Bb 9kF=D'Dl Q#(N |\"&z zғ;r\'WAdEҘB2h r*-oo`T<#kd"=)ߴp67D41)P Z@Ӽ*+QA!AGB,^5(HEA!Y0Ul!`/FCҍc{BzaC$RskFǽ7ac/w<1AJ$g ZFQ&A_žx9 X6 a}},5*(O$4̏fP3:.k,91d-RG/h+"B'E. :?`gY@ܧuL9&68ܗ<@ȯʱ m7%vD=4 %%Fø UN\S#dSxl3Q]&#I˧bf]Vp6 NA3SyC?+VrRV!wSF7{, wbISCG&.(H[ 9j+\u`D1‹E6ʲ0rmr<"5vѵ1:VK,P4Bك G M$Hi]JlzCF)l}N hty8n $36?i4݋2C;Q~ jspUgRV'en\9P6(n"ܼyʕ-F#4.^U)tQےC 5{Be>Y߮ 9a&&ǁcаڦzn1LyAM$`-5P7&w(h᭤ qm)S(I27z9m% >Sa /dߢZ}4,S곂75I;̺TVa$8}TdMg6ICz(2.O qppi ;2C-1@4lBC%SLWkV_"ϊ>I&1f-Mg8\Mxꥐd,MP\S^{@e+s-qtf ITG:װlpFXZjzfpJ :371Tp >I:81ҼDh@6&C̪3RAJSL2ZX#xf!ɋT{0ET|,˭J]ʣQPpNڣ|Z7KAY>p R~뼗{~gggZЅR E[gvAV.(:8&TpRB["= v^)f;yr @):(UU7OD)[P3^c74Y=߁}AY3=leGPӿehyE 7hlLH(YOZ[)sMtb=K? &#=?N?c!̫D=~A#pa":x  TغdJ ?IC\ Fa5&RE+E@PtKn2%* ΰ HRrIm%WiS(=q[ WpN5۰`d(7O/kc" 3(ҎfCVw9=ma q.mPq;iEpj'c_A ,!cDt XÂ}L 8>|lSxB7yt2G ,c?;MTQf8ff#QM~\u5ɫ6%[ aW O7gU"1C$UdV;&#ّSڳ@52vf;$5Y&>ե msuTz(pE4i$1]:!'~?=kNb9h8+ćGhPG 0piR9\22;'0ET,R̛[,/ c" @bWߪt^BꭤkAqCfDgȗ$[.d mP. ~Dp:zd," ) %@tUa'%ه;lY xt+˔wNM.)QQIo<1y/B T7s~Jh#O"@IKHMGGwAl=NS].J^@=&S=MUpبIbc͕lSf\z{ ;WexoxVyO(lx@yN6}i-=ըA_rae%-/14O%(e.jBa54m#x+΅lPsu+ hֱiDW Ot$n+2Vz0Pb̟OH 8QBj BKLc&,kӀfI,ՓaW|l^J+uZck첼83X 2;@lPq$Ysxvn||sN15N~4eI8I՘P tzĮj"TAN&EAj1J p9-͟JHFIsI~ryPM ʖId 89njFBmrx0]lkH1eqXnN! ;0śBRe[nb/A9N-'WДB QP < h,Y KPxp!1qK@%-c$mRBu(<Zz"VbHԜ3OUp6]Fh,66HB9fXӆ {4B.D\LwZF ,?)ِԕqND?{\i6R׫9b`Uk1m]TTU5 3gn md<Vh… y4L m.@rk%Tm9_4Tfs99 5XyﭵHbQVj|%(z躳*@=: Ado׎ؓP0: Yx̉)iwTpc:%ͿfRQ- pcrؠfl` )I/|iC#$3@Ld\ߑz6棆<rmbk(p{WOOz}lB#˝R"F&a=1 /a-^lᔡQ_]Cj:MЋVa bV"qOE@r'\"'m/wko6Ue6*JYڪea-b[32 RWJzKgQ԰{ m+DBͷpԺ6s5m c%Jr()p퐫rUP4\.3 ۺdd&1Ͼ(xÞgF|.6M_Y^ u_òƑ$:_$GX/89YD3Y$@[Y>!+cCО"kCHtdCe0p9⋊ J.jn{$guH yUh%>KFÈA` kKR\3kN'di!9^= 61j}㌴'J){7-&P*_}0AR>-K8jZ l@&q$Odh % !Ex:˱FՙE+K?WfY͋bs{v~W">B4o[ AuAl TldH/z4QKzɴs` 2V Q6Zj{V $w-/`?X뒰 @b8&ljMømhU.=2ƙ>T޶8!IW8QG t2_Xq=qXCMfp.a1 2_w<Fqǫ'Xag"7AhGB5:>ih^N&e!D&5eh#uJuYKASV8\PG h!'jRXZq;NݴY8XSz4c$& QIc8u4f2X.FCFZ :uTι69'P#AxلWQ?Gk ӌM]" =^dL Z^!نDo'84FW?2u30JYJHh(g1¸<1[0jR1\-˘#y cj nNtIY;4߳ۛyJdI#*qWN ̡\ 7rJ4ƿ=QGxUI-4]6ySR03LiHAOJ#"0^+ڑ4y {!?bSCѺf~S`]{ڄ.8IZӆ]7[?jZ*tU3c2!Lqtа\tH#&M܂kvcL*V3#I!,E ( ŒB馃 2\,3+@]^1h̆iTc㻦l!|(^4n+OfG(ՌV`ʼeJk" ҤSk66%,UHrnؿu[_$RNPK..ȑ\pM,g.7c ([cnY\bwi&9ѳILt~ÜO#Lc&l69gNmʥd˲(n˜Wܖ$g3rbtUU7OrKo4<@7{ M (˱x3P%D@ѱ]7 = M #Bh1*s?N#0-HxXef}.TD[se>@WQCZbY!g݊ s#uPgE|%n*p$o>-~$ֺW>~> "tydlRd"/4?Xۆl-%&uF Sniy QOqaPQ,%ܸ?17'mSdx Fi)ddʱ `qP9j2VcĠ5fm1X>.TIÿ"lzn )2[&N 8<58M! kNVP/^1u›+5~8V;`34^ g_M)ێJy %/,iRIw(Q>/(+:&,*ԹS:vìR6# -;r𯱚%1ʲd.㞘nhS_„.aaV)IŃ{hCM=̃s@\dɌ1h^c S(bXJ9) [Ueo~f{hYxv "Dckk}|ZSi_nנ'XV46՚I,(blOqy ; Ѿ "2д_ ܼfPuP2o786Ƙ]dnkJi2#uIiyi<#m>G15XQ>]y?kfò>Oed h䮳 kf.7$g1+_riEwvzN]3 %I "ˮ#[d]ܗ9eQeޡLFK04ɩd8}a`jn#YJFPǩ#rH63pc2y<>P ;y#"2m,ӹwN], W;Q;hQD?<(@U Uއ"8h|"[ݹMy*7Mh.I$]9c<ᤃXt[ ƻ{ڂ;'=OR'K+3,(؉p!M7 oɈT/3ϭ +y7$*씫/]^=OnLt?ggI͇(Nrw (.oē? zr / 1zi x0'=zDAv5yzr*J > \XYBNAѳ1WuR)DLeC-3u0U|!;LѩJT4߀ur7GGݨf  rY4ːɂl&+ڣ5DC!ʊX6g[?Tb5g6Ygı”9)58ۨ;\1S@I{Hն4PBrCaĞ?]S 'Cx/W['R:a2qɄ"5`I+hr'!gΘV1s,!,(NG,n{+zN:PdR.,AIc?յm+Jif$石g^jz n%me6|r[3~eBԅɽtb-a,'"}O"Tt|?c^gx58A(4xi= L;OO=c"ȵ:Qd~98$Z*_d,^'1 Ĭ 5k0ogFTzBk}GS!m6筺 ƞJtS _ˍZـ5}ÝwJ1tvu)]2 OxO r~)k[f"iYX;@V&@nf& -6Eu/˩W\3S7^t P!YNj" Կ޾K%+Hh<k3*J˺ :ĉHr^"&;z.Pgb2W[Vve@>ɌgKM#n:ZÓʨm]FVaqC.{8ȤV<5̂@TvP "'l]F[ C3͜ż)wȭ;$I0 km#Z(191k43oQ>Zq3k% ] HVjfa|*ӷi+ hںnγYGS)BRV[[мswNM?025֔Lhc;ۡs@,50렒$:}|LʡsHWKK+*?!oQA T"Rqʼ^OչQC+t0 ._r 輋OMA9הIZkyF-^&y<@o)j*e&  K/m?X8”ʙa@j9^ LGH08MGygH(,+.RN`KxxQ雼]xԆY$m|$eGLU`H4"dCY{35kr>_+9 5Q/[4͖NА8ģ XT P" ϺZy҅3&'7sfC[ _U~ 2gzx`J\R>F8 4֘-]HrDO)Vp.s֛RkW9!}n`N~i\%ɏzb> ]|v<;HH'?Ew7c7@`FˇǗ/_z}}&}0>]>|ٓǏW.ϹBCR*=uĽ %^<"Aq;Jt 0lܹb@-r0|te87"rX' MCR2x:*,ġl52Tӳ'W tt(W`~p0W?6YES?IV%W/Q6˳Q⑊H$fBF֑jTDELu6{PFL4]XꈧaȩV?#&onl#<0^[_ԡpGRأEG1^UvƬLgTmBXl1G&,ix ,ғ2)y !my,4LѠ 6kwPzsdJLއ:Ze8Ž e(9͗|Y#FV6sLҐ׮ 3`hBuwb%kyXO:Ydxg8BR)hny5}C 0pƴ9cI}):8E?` .Ctxqv9b7c[0L{7W^y ^ _}ٓ}@|W_}gl\0 ,ICHu^}`IE<ٚrXS(O5l1PP4-':% 0gT>ȠLYKYzkQeŒV+EK\1'̑#ʄeEi 8Nfcs,HK-~mL;rh5vG A;DуZ\aD~# vnmZnS䉇`(F^Ώ(RUi8meL/J OӊvLvlga CtjGö*BCt-j%F=o ;?wƝaj•1hefA(9RQ$Dj_;6 M !taEmNaՄEZÆoL:` >G=jbzF@X ,Si#OCC͟6uy,&9o*K.VFc 28<{L܋Á1p[ .5ng)_W -p['QJH܏%Ǻ觇y4+yPBd*"^IJL̶CQt^3,8d[Ч$몜hZ`ΙX?$ϗqwex+55j+-f9y?UiXATf`ڨ)>_60Er'p%=, F߽}U57Y܌k ΅[5|epM+b"/V~Xś|}s>}p~y <v~umC!O$k nE/]\+{Q;pL2xq%xm}SSRgU!%KH,c*,<m]80h%V90juk1[M}td`V@Cmzgx1` sWȻvUFr25uCysWr~)`Դ1mLW[skɼa'/ii*8zEkf@k=FyVf]\DU6$J!ɬ.f#,ᰃz?Ѽ')4h@P "7 U ɦWsVꊟJ$lLny J%$BȘ0jf<>^ Ľ"a3p`x}1{,D5E ?m[8?4u4cɉ%}I&ƙLr: s:|k؏^՛^|}h SG\^2'\I. |c!Yans~7ory4_޿ᓯ"xcēG~_} ClE.%\^#L^5Um#^oYy݇g0,ΫX0Hb^%j6,"$ &ftH{-I][p]1* DE\(Yn2nO:fBJ(@cB=pE']IkBm` MHK\ źjַ=*JXR_LeIw7T-J/{9+p%6R8nzpߠe%$S]ɫN`粐$\ɛDͩplCpw~||?^~ËoO/gfNpqy_p? byq+r8?)^ydY:?/h7o޼خ>{qzA,+ݏuxLBPRy>DM4wOo]~ͻc,#DVk I77WnP?\<Aim 5 73! h%-,r{FʐNG /t!5,׃Q4`jEܬГ(1cr?{w'pρυ8@ZQ-*buS''tɌfyWӡ :muTZƸX-[,UIM(L*Sˮri,`?Я"yXn bzM5[(~w$#O/<$zõPKqIzɝKD90 BsܒWL,}vLiabY6a#=a\icmaƁr".v=/sXPEvY^Knq$+Y؉>E`L &KN3)N2P6wCpO5$B]mϝ(n$Xd]4h"-hFaDG'`]Q*7@5G5 d\nbR"`i lԵUQyH~fR=q* a;Yw1D큑4?&\Qgn=l iktK0bJa ;>QnS$ /~Lf@:*):їp"R$pJ#{\ 7e!g2ҼZ]4n6$+%RyTڹZ#1jXD8ƕK2XZociTu hvĕ 2F1'}}ٱ̌cAsKU-v -ȏ@Zim4IMtBayR[iEʙJ(fĨA_ℾp@,KJ)@Z8߿ez7~͛w9|7*<{&^]s?|ٓrO.3[]=;"3y~$<}=W/cObA7_Wo>8{tyXՈu{1վyNsR+qu{QnKS%ncI7x#J*nH*12#c+qkȜX8jx<'qcK|@~֦lf2ihszRJy #dU!^r AqkHo0bp_LTHuFrJn&"qlX*C+^4C8:e}F "Ph&6:Eyv1e J2.IQ}G[rdV'%׺P0[n:Pɕ*MwV)/,³]谩^)hӊ gvs׷v `F:\X WQ\ 8b}k aSE5b@ UQFNϙ~ޖRU>ѨFi~i`Oj9!0ͽVPR xzHQ9 LEH>t'ͬM(8aفI*|zV8OY6qO`2n$56צò]o4[q ~tqv(0*-"2^5$8v֎ns r (XaR |W mcژ:@-^ /'olΥYʮeZ)0I8Ko3*Ko0ag ra |n*gVR^Dĭy[ʙY8F(MP,"p%$VEsZQC<ĥ6V0yGEt?޿}.n]x /^7p}v{{y,f[ 6x=ī>'_vGxbEld,0ˇE~{ gܵCL> bxDl,YyVZŃ /߽z^t݇{O\<%Xڈ9J"|̟A7^6-Dw?yœ'O9{}9WZĊǛwWWw^}& 훷|FH[1os6kϩ W)@@dFp8o_Ug,*dzW9@8lA'}XYXC9#~1WGe6Cqx6Z+ù)ٿ&ØdOXrFNku [ 5$\S45qj gP\1(!|]c Yq6c!VMآb]+@x34LR*;|hikq¢m"# lA cN/Q%+B;E16j(f_0u M"Y2gpga,nא1;U me*Tj2ӑh}-;T!zNIfN%+A[Q%!ȭUG(0Y-ߧV!K9"pHFALȆ)VoB5:XzH"e?'FcFR") iZN@y+ 0ߑ&o޽ `wo>{勫7o>ͽM"3OUȷ?͆œ޽G6<<;g{;7SXvl1Jnh*V_⮅~|W \Ggri<6 y pύk&[#jŋ'tW 6{)޼iޅ?{]<Ň^<ǽaq6M#!޽~՛Wz}4'ⶍ\Ans>C.Ģ1[N(QY< 9ˑ!@.(:X cA1WjVsvO)}*D.qχFtbS B$qy  XRD02H~U fm*Ƹ6ZfOYpԠd&BUPD,St?77 2 UD Vn 8sejYLVNJؾbްǪKeaOWzP:'D09^Mw\-@|ԑ3-%0J&'tPNvY?i+ioj# 6iz1;_lUg\Rd]`B~Di+jѭO|TG%d;SfWt5%!c-]H(z, 5j ZAH4Km<l0K}?=~?x|dOޅ=,0_+j{ɹ͕خٽI:raѐ&6ٶ$01y S&ņj̚"\lfؽKFO v}]%:̳V~;U+kC~jsD[3!lȵz˚+^WJ @ w,3#(u !ż\=\ξ`!͋O 1eʰu2_B vA/@C^qߞ( @DḌP WW?>e^\]")w\݋Q\Q+w6,x?)y2hxgϞ<}G0.XJ~q = 񔆘Ǯ{;2%=o铏泧DZ!#ba>!1ó"뙛h.6eOBPm|xqGG=Ĵ"wa侃[<1qw/^o޿ /=U X-M\H~& Xhcxmx\t΂|!֜:H܁XvWcD> v>J<O܄!lr4yi,{c 7*B9ɉ ?H QzpRQFKR~(Xب^G'Ifd6U][f[DUxX݂گ|xPSE;PVM:W ʝd7Sj5JvvӊT{C(e[w廒-9~qe[i$CCZ,q; nKŵ/=b1l^@ u³S[a0%i ^KbH:9p,-A^]+$G0BkOg'&[nTW6!Ic6[P6ӻڦb h{O,|+4C%j8i.ehZO&QLF?K:&b$KcV!X=mAMR8Mz>.Xw?\>~rѽxA<篞x.䦚cqŃGB3O>_?z  gOb#n<4")q~D<-X^y.p_~Y &S#cq$o(rI"pw^q|y凛}LsqF4r /#@A>H\c\Z"W3ueK+s9W"8CVlNJ^Jt/*HY .RӲ%y[pT8}K0Aԅ>F;G*%6hOQ@DIn١)Sg(Uq=虅q{}5 _CrA|*!Vᶭ6O:4.LQ1DBw8øc(T~0"FV4~reV}m0 TTU3$3 E.a"2IGcUS?/ !Y1FU4d]@륤 ޾EVѩ/ɕ&+q5%G4s`^ןYaU#<;HDżpZ ᄧ/&3~[Z"*÷%K0GBB)PcQݸoweyTݨBנdy)>Yx`PJ@$ pZ. //\7tcDʏlJI!uF7GsHh0*u n,U[ʢk5#w⿑|*&3Z&おjl5z $2HLAnXAccJ2 G@ikmζq9AƈI١n}0VRpFX샩 V$ӛE%1kX,Vq0iV6w_>\]=x30M,4C%{ggc6A 1 g-"nca<9Q<՛w=~ŗ_$cUp?X| ~],YkPZ2㳸&8b J 82z9h'5Nɀď1aKyR|(q_~b0L?lPߗK AI+"6dIJ|@'K58JU1Y8Il$fӛn D W}&s0_0 T+ܢ`BmN3 Qc bi|"vs ͸4o j=F)A.UH'2ZT+eeIKa1Σl[2BIQ`OϜj |hqVD};z@_*+BG2CSv@>qhZnm 3sOQk1X&WQ\J=wUMUx艠3¨26ox" 3͘|F,$-TQ'u R y KU^#FoqpO^xm/>~wg@,1s&U cbC{?zx|wwK\> ͢t9;D VErW\\xld~xp(nYܼ}>lb="by">{x滐8oo9թ|m4$ww[rWIc'Ľ"bD;{VLY5E9[A=MOSE1i ucOšNNno=BZESv6[*&4*\vSI-p۹{ߜBeUiˁ! 8L0IqAӁ]V~ѐ+֤ (o-MoK FtC|YcwXcUWꚀIJZqJڞJ!͔Dm*c[l.ZbPmF0E+*P $O52]! -}u棎K:cLIV|κ' NM&iVpbJm31Tr^BPc)hP+)bZ nPbXocAUMim5)OۓHQvka HnG _90$DjU<8Դgq-;x|ycLL+ȃ3ȸ{iRU8,7ZI6*;=ɮceV*dC̀Kf@?1T0?\}tQH9\cOnLamg 8G`UfbqXXqZ SYnx`Xe[eN&ذ3CٸcVyrYq 3)PNJ gv и57S;jf0s6젼7;phJ4Ȧ| Y҅%l~=H$E0X85Zix՜lpwƙF\?T@Ͱ^!tE J,+3yO5a &<^?b)FIjx"hq0ox{wBKJ,jE"2ㇷs_xK2S$mLxIn\Q޾~&b}oxB2f S\GWTl:7){yUȇ7c)!` 3= !^(;c!W׿+جM9[僸7#^p?_o߽WoO,0ܝk!b@HHj-n՛أ|6@~_gOIͻ|xv*^y],J᷿_>'4ē Ex V`< |Ã7Aă?x_Fܨa&D[e=ywT<{1wnb"/p7 I=vī/nrY :o8<ny]ӭ|7Ϥå=P_2j1N yKfrYR Sa+&;cH.7?Jyop*7L~ٮ1s"02]Aah5IƲq!\-/I4xE:n"Wւ";qX–K,NhG~A@iz34pq!-zHe)))bK 9"!lz1r%sWɖESٺp/1h u.'$XP6&`$22VXkŚ2R*$"41ƴ`$Jd(,$yyݕl!jJ"T8^@gCaH#Jz.AI$(t3?_s߽|uǘ~WE$:fq=>,"?~ѣTlU["6ؼ {6ܝ?| O?bILWbc^/fﯿ/㻷cBY<_~79{{8GO?;|ͻ븍"f(KO~gC#\ĂH6]!jl,/e !_zË}?w}ޅ Lj6|_f>!aBŋ6.aQ,BĒȻo߽O^>CxEYױ$31޽7e}uܶYf@:Äb )NEϴ"I]~"\"C*^1c!0[=Qk^Ji14yf;hR #qD>'!@@zLFacs mHOɩ >n%9t^P&QD-"R~b؉* Œ594;w__#POq5-]̭J17=*{ o8|*Q+{ǝg[rc-uڴot :2i4rŪISUWt+bO(ť0k0DZe`&"Ȑ25.sxn̵rEҌzlx.*t[c3Ic6%vxU9;<w$+)"w42ӑ #fqh'84rW zyeXZ}s̼6 m;{ ̡\1nd XcR7vb[@ItvS˧6l5l_\U"ԣs%u)Wl9/*(KPyM;ɨyWi'Z, !TV B7ºD$"#|rpl 8c~ZԛcOkJ0N^ao͛:.Ju[^?E&45ZϘSHp@_>NU{w@!V 1}O ?OlU)15nj@z5 j? Ȝq_~곧Ͼb q ?5IT\8=~\ܽz&V/~)=O?ⷿÿcO/b*i\?@>՗7^|YU2oM!bEs!74$> hz4ػ*-5t)6}ж*clp;{  e2,Rm1  0H& j*ޘgGiN o?;j}naVzMwe,eNOfjPςL=>ll:]bq=*ԏQWwvd5QM({[B:w,wS+! eL@{\fcs{&eљBT"1sSg$V/Gׅ-M})IC95lcEGBQRWJ;HIVd$p ڃ0PtYE&,i E|8WA(ضMI7Vj=h2M4GijD<9W8Ѭ!'0VktK@buJQ .&9GD$.1 ܀S. _3iyonNzDS#!HD-$GY.yݟ޼񪅸9"f۱ V5 qM<&y? n.o~ŭ㝑9-6M y ?E ,'ޘC'w?npwx2Gy?E̫saA/i뫿ozut_|_~_ܝ+onߘZ>zw_wnxwxDĶL,FxD< ~aOoOn_={~i<JUɓxfnIܘAq2# oܽ7/^//{(n{=vF2P+4K#7"r="$omy>b+I 씫zMWk1 Ǎ$MDΰ3H܋?`ET bDxPC2rhBك20ntD[m{;N,ڍF)ێNWo10^[n&uʠ@&/[0*ϸ IJ]V&P *t;OS"X[awgm//LS6T$(Qq%VԷ/TVjE z&ΫO8{ 6Dܟ8D]MMm=~ pT(-Kf;lbsOq<6rTo9'h\ Jo]$ɲnSCH&`u_XgE)abCv K>z/ xoeNlYv Uv d ՎqL)'{ E7fo(9R+D:=W?+qwCnX+A*?li=\\ޜ]݋?q+AϏ<.m C\.? R|.Yu^|6:yϞ=?$ة;E+?|w?~_Ͽ*$\z #ϟ>_7!?yW_}ٳg܃]_ŭOPB4?w_/=٣#x/HFBT= o'6ģ-./yw?}ocDV{a4J<2m ?+2hn!Vn?~ &N`& /\U kt =\mSʛV554SWBH;;g[$$c !qsˈNDGפ"r(DI eCp`m%ԀXMl4r6Z=OH 9ؖYz(S`Bb\݊V !:vx/TɘSH%rũĐ2OhӸj3wKh@QzX11_(gS#~̐W}lМ\c}Ѣ)֔}A0 mhV[ettБjJNz(N200xJ2K UaXԲnBa:)3V4+- |}L[P皫j-;lQaz\%a+WLOF^a%zM2l(9lڂ/;򩁓~u,4ą lك?x???G_< }xwS?[ r$e<8D~oۻ_?/_<,vjC]=|W_>~0&ǃG9Ս;S//?WG>]!g>36rjʇwsكǷ.b/?{Nj^o?|Ǜ"[/ngOJG+zpww*b(\bNl|ȟq+KeXGGS--p.qL/)xdv+)H*a^iF׆qE tDwd${c0k[=($6M#npv[ ؐhB0ԫz4'jS  Z1ךg OI 4D=UoR+Iq U,mZ-4 %|zdwE}ÆdbF\\LW^evT}>l-:Pdlk#nahP@dBN!SC$Ԋl=Y<О2Z qd6k.χݻ9dxXُ|y)^ 7oǃK^~x6Oym<2/3"q~+ x@ċ!&1bU (뻫7^n@[!c'籘N|C_ȇǓ~z6,}1% XFOU?_?//=G*fxc,xOu(b~o/?,<_w}'W_/ɓIد4.~~ۏ/>|/]]- gb%vj\X /ų,I^|.]^|w!7y>z1€h# ;k,y$쎕#5bG"ȧQBbGo!"0o<Ֆ4#ȒM+zAWE{#c+HB:)؆#"`rVE SFNP$5S4 hѓX?vSkJljd( PRתhUwɏk*%N,m:"2{Ƽ=hv؝Nظ:xDxՇҴPhؒ1*[/+nݘ Qc?&wP#tFx_ICcR-AIc3p,TF4((pYa" UKXR lKVV[I9Yqr iĨ.9 %*^r6!EPD5+6~E(-')jmv Kiɰ@2+<4/8N6I4o/U\lWNtҔHuAc4oY{G Cň2j*2Ig[e ŚEE{ِ@H9-*f ]eS6?PEQF1jַI Y \B4Jyd*Ur#nUaR球rx#+HFаEտ\)I 5ͣn>RO.|m=(UgUmRGDVIGܻ9` `DpQBVbPN#zgQ"y3sbl9&'9M\wl&!GÆBvE <.ϕ2|쾷(*}_)quXzUG~h?2Z;zcx{>Nk](4ň>;0q5E/^}q^ՌV:"3QXE҅J1~mOjDe߽Yq7$TcJ*HХi);,mpCdD9(UPv5KB#uBlqպb`3 5UZ2f gʯ6cʁs\U ,P. b:HUX3t*[;QTHTv14j 6fcr#5+>2hP>AHJFF%"H*fz-4^!}jh_Z j$*N;ZzLtt Y&7X&y܆4On`egf;r/78䩩1A扸u}x{e\>'?N'|`~3 ?"_ċ#.}.np?w/r+CLkx2I)݋q>b="ȝxJaRc~zG~A<2'{(_y׿}ի~UW_Œu,jDqA?>ˇ_s3\E{vxhLf{24g5N *W%G ]Y|XnM!-eΔOq UfI#

.>ld*¬̀?"+1Kf<>ʦƫt:!3: ;r*>^lmzhls .RD:a2vfVpsϼQmDRYlhAyb'YLb).ޗ{; 'FZR~`@t:^rbo.Y ٗ%? 6={}۷:C$$R!2//R0!  Q ],` xvxzǴ~̪9osMUVVV T2M/U}=(md(3{Tvd೙*令H,j)gq!+)dVtLDE[ J[1qP$6GƔ"q&ٔEt/TdDI|_T|VP30˳NEwyyua$莔 |q3Xigҋ 5 } ,d΂MdN*^x B4݅-uje둨⺶زr:`F0'4*#[|JN, DXpR[;xRUan| STbȶwY}k10% cG#`xdlxxЈϥފ1*_9PR?;GPR u;lnm,lo l*Ÿ~;sC㣕Z g(<:j&"[>hѐCY`֤hP\ Oqn!ӊokq(e+n1!e ۪SJ%FNC'fc򷕪˝%dxINg }٠pEُ)KPe3NqUUn)A|PPئ9M]I9MB-cgG9͏ dpF; 1eY|. 4@x9^"!A1ntQoP w(a|3m%AͨQmrt;WN'WXK k g0_:.(U;OI`i 1Eiaŏr11J7,1 )n0x稳H(nHRu!d,JGс#Gb9N2$vytHx'm% ."R/*Rjz/y?>:M~ A_fpt oΰ2v.̐L2$:qa)g&1]]4Hʋ_U2&H)An$S/''H%Qgs>úS48a-Xdsn` LE 85![o2T>8Rk#Kg5 H1z*yy `o X7,0 )^)whl=e&)]ǔҿ6a,~?$_.{FQʎP]bq̄0 IP$On^رb쉠df֧cԉhsB JcaEr?2ش“վQ(ELpyЄvaյ/67elH ކcFx@/ډ(f)/ `ד<< Z[:xؽ*ՑpX=D?*Gl58SA 7v(v%a [[y䔔,}8Q)W8 C'\SrBJkPF|8V^>om脞,ã3WG'[;;͓PmphGHQk\i# '&sHb _ϠժUfhg!D9_h%+4Z+ϗV׷vwvq [wn޽;61U 1 h!|3r_P@(dulm@] ]"B>Ƕ`\-g8ڂqD4D xE"RhDІ:):UYIb@߮ML3ЊtMdldwA(#1I@//4;y-aIlpΛ IOj΀ԁ3ja]S,-0ǯ&tdji9uDfe70>v $Tl"Bӏ8Okv~#$Cc%BOyteZtbN$0d\"{eȫ?kFt||W @ 0C.564ښx#oY/Crh0LGx*ʈ֔^ FeDŽ"xwNjIDyUr+R4~<:x-K,H(o~/fYBְӡ=FKl]WQ{qBT5H"lˆ6A \zANw`.ewv@ȱ17[]:' i G)1, wˆ).Rb.]]=uEtH}ΊK6РIUg?c&"̭L#Ao#Kק+)y$\c2vbHi։jМ~1-?o>AiH"yN hYɌ@\#fJLFB½"q: @Q949q: T/] AQ&M]18Cgx KpQi_qqRzنsh<̓c%lR%f:ĝu!wx(dGfO܎|AIDATj YCyIZFۅ9tmmrs9 AwFj  YiOXcR. dҤO8C ,zo$V.fHQ xp$qbE/'Gǰd5))oЄͮsH%%e|ŧ:Qŀ&~v6ٰ.3آG1ce8GGGϖW_OjZ?,t%F=lȾ6R* Vq4EI(& )1n$uD  织:,B_::pmʕ!ت0{O< #CC##\E#8j4[7}&!p۬ \ KDKի׮VOȠ€!{g(̀ T+׆+x^RLM!Cvw1W-WF9!'4G͓".Dh4v;Ս 3Ǧ_dnq:<,'L6LQx`eNNMRE<@"0'23A ?YQR{ܟ+10611633 &x|X ^Q2BK3q$H]K7N\dZ\aخ~`_7⢰ GLaPهlOy$g!<'w$E1d|X= ^\5Pr`>bLB2.é\2 }dqD7J,ǵJJdyšl`8erEm"]F#Y< mU $mŎ@UMU+9;ƜH;<(S 1%=HFzCRʙn ]h;3: c/h^e*b"2!L^nΌ-&ĕ`sFlOJݮKzV"ttaHỉ 3 2~#z]7HDDZcϝEtdB;i#w'd5+^ʒ &:# oK"yfh;Oʔw̼.Ţ~iQacofckǸv&ҠD5>>iוªdlRΘF;ʀG;J< eyv7̔[DًB!"7%WBt/(B5jv aWtߕU]FU$aﴨSMiÈI/$Xa! ֍^պbN/I[C~þqxt^8h-hO#Cc8>={&|A r4_VGF&F*%D 38Pu^:>d5/yZo {BehdjjydgrHخolmmrm$8@^b!qRel-| /lsQGDxӭRoajzu1Ϛ0%A-IaAyԪoPobec 4Ffgqr^<<;lQյuCtF 0k^Y+nLf gF% 9#4$ccJNG$L vb$Vh Ax|NmJNBR'C+?Mb.b^Wu6 6 uyx']rNV\,6>΁@/(Uerd[A2"7$PB^7 YH:АCHK'F8$I}RHAD\[y>Ô<[k%zO4Q!"H)2'_F&C] }4QAq8:D,(ڬ)@0/ZXf [wMe Aq8eVL[bg ącB_6Z{)ws+WN v-pN2d$ CWHY'Z6B+5!:Q($bw,q~c|;Jȯu͈ȏhjd3! Ȗ/VeGu@ R(gq/؄j_$sA~Ǧwh$'zpȁ9D`GE;WׁU PR@ttD>:;iۅ= (pl[s()C0d_  PaT+Tf2,NFJ@B$W;<=8lc{po׏v\X^^@ <S W4@>lX;MD4eHP=jQ:bx FdžFєrqX^>>܇fBrzs?4`1:_km56w^mm!{C=z[޺9>3]8;n oXѳ/3#!"|"+Aꛀsⴉ"N0PPs(h[!B\-1B 2bET%E/ghn ;@J_\1ZO;@4E6NCV,o.W,߉zu@W((肛 CAxvLpvB`P"~Ɍ !K6B|>^XxUbpܧg_93#L~C3} k".!,g&CzTn!ڵ LMcO$)9D4G1#=6pEb2>d~j LǑ2uqh>MÐvI֪Z`^$S2ej蠹CDx0UQ <>hR lB: m8eʪQNE`v*PLtZdؗM P+ع$Sg^tȣ(|ܓ"1R;ydF6FKc2Mx"|zqF M>Oc0#2;X?;ĖcaE(+NR򿝛6&#I1ہ q]xO=4`OQOU堮ZGx89AuXd?lXwQO nyciHγ20GZl:T>`{lK[)EvLT: 'Krzw6^\L"$y搽i1uNƭb/,sʁG5GsA99= ?UՆLDZҁqN$*((+q _\3Y&I:LH^FpAQ[n'"N1dAJy,i-*_X&7S)xg$f(b1CHl&''ҹ@ۿ(z$-ȩXw㾧'KssR66ɁbspV4 NS3lՐuu6H:꛻/?}Q+* b†-VDA00XC% /#61hQJ$ıGMDKCb?7–7NTPrέ7`hr0M+X:!)/T pGGkՑb!/KuS$_'Q}*t(-9pZat+W3k/[(oٔ xP+++G!g4}8k''g+/V66QC JA흽n}y,61p;]y*#< Vp3Dxš1E`c8b% ./7sm=av%Π;x|quٙYB]D҃1rBƩ/iH~IIH 14&&RUn7:/ ŕ"g\_\`D6~&:8#q%.f/]35A&wT שkm:>d^1\y$ւ1h {{hSQ|/} TS/# ѥn3XN/ \<N1 Ub$l41@RKn4h h0G~1`.7{Կҕ Yҝ̍Rur;d=gIi4ۊd3%m&'Ll@bUQ ,s7po\;jݷ'J+AN3-i#_WڢmcE/F3_`@"@JTO"PE %SH3Te.L6ptp=u %D oKgt8Lr{8/ F/leq:&aIE\t6u/q}-o߾I><<m/s)dƷҊ`B`eƽ[9򧏞ڛؐW&/ 27 u[wff&8#:5ɥ͏?}!Ǻ9% D]))BKCI/tVTD$*7H/6S# ͯ&_ R } jy \+;EK1ÞS3n+$ e!3 š;PtyCux-oa9D!8H:vR>.8k너4: AO PNh]ZS;L$ZKƢp_$'E>u|J}bB>AG a[n.0R -Ch(Po2 v#Pgt`4GH)JM'5I|BxC+K2p:lm~tikXHRED4D%I4}œBudm~tvW{s *a#I"B$:`*A9H 9DN>FB"Pࢆ+#0jIJ?:i5(}'}xTbP)]oGGOo4TzkS󋣓sվG/^|  [W*| ;׶n}kRj#0Z贝;h"C#Z@,H "RJFXmpd`t|驡 z=ŨbprB20d:QBR*Uj8ӲуJ 0sgI΁S԰H@pGCN ηs81[sӠDL `A0&jQH\ P#rBDgY9@npF+ ﲘ?.t͝KY^⸧9 `ph \- 2D,p-քks^mR){B( vr~w%Jy`ud0_X{2ʤiH[KvF Ad+Z;Գ *ϯ9(~ENr+i_iA*u_ܝPh=U!6}#:4)6&< VXAd`Nk/o3ԕB.zV[">M"Bmn"y)h)mL!eb%\l$)Wg 0E\MUn92Y8(Y#BxYЧ$vҋϙm[(!#&rauH*jZDwyVEp/\͐`f1d{n=suf4sGgҩQ`\ LD4|1Ai4ލ/f-pRfm_D_qςr20boDO޷'''`Jp$`w &GKeD[,wk+qL>P63@g"C>md.;{(N靈ф1$JѨYͰgϖ2: \;1tׯNqA(^v7n.SN1О>}IEĘ6CA ?$]J!}}P>|!𥜙 Һ*vvC9s͕8I$jǵA:9N+mʬsЪ^[[OGOҕMa)$8$LJ016Sq|D91#?0ph{6\)68@@!B y], ]a9zr%%T(CNh._`M9c.R>ۚ2J@Bv γ2KN,esa.H^apxf#ï[ QqKYRlQ bV+=\Dﭭ!d7ZgWIbEpb^,I\}Qy쬅(#yvZ)pO?90 }IS^RukW'm)(!Q)A d0uYԵɀr)GGFa=;ikc& 2GѨ|up:ZAKD"jEbQ \> Vԯ.-{9QP*sxbʵs˕%_F@c0~b]{o^US=Ҏq_GWsa1 ]fI?&.ډ!5 B'ͽ &eTgL]"!@oՙ;~ƯrMwztR.w_J>CAXz HP@ %"0uJ U.4B 628{LH@N: y%c1ڠX*-H]t`3֪էAY-LPIUmJo-.Kę]3|],.@Ռf-Ɓd)Z@o6Qcavfnf_$OJ9PGfjM I `p$DPbo/DA/yKڥm ˚H4D Q")+j<oh(!PA#w6&mؔD #B2^qFmkZlD5a*YVㅀGrY`[E|C4u9b/ä(H{$5|' F ﹀U(q08CKvhiҹ@X|j?rv D֌ݡ x Xt#Mg0=; v]ކL IQQmD. yQ((Eĸ}% iġ҄ UnW'&b䮈'|\:eb'/Ll 6<ϑ)CwA T:4 4HT0tLC6y0rOm2mGҡ= ݴooҕfFrI{7yC +UGhP_f2DE\Xl'afRG As=qI%%6iji"&4,o>/mBE!ӎ!gҖ='FIJx* Yp$EL]1]f)p]p5X\f^0!<4j: s'btlT Hx&j*U^<3; "/l%z~p**lB15Ӻ fB\ z3(9%*L9d=%NK"CaF1RE? ]O`(T6(™g}&Yq鋙6dw{/.(HΌ7茐bRMRn蔝p7Pu8d"^G$TH ^S.t 3!8c4lHAzh&^jSoLs\8M)roa[sS3c(Q8mn6Gp% SH(FRA0XAɤZ"6rg*pڏ*@F̒$T<ٕ]ᓍK\9>mN $~Oca9.afCc>Z?wȟ5 E>F26nORx}R/N!oWsW2C9);;-d;\ml%|U?acQ<׍ЈpQ[\0TdNPlP⟸}"mN&+qzDO:]CUpzAՅ֒31PF5[9a8.NE.?!A\:'1~M~:fJ\YaPA{%cFɣSU^ܦKwdk$(R·q.f\ϴEnXUֿ_7۪œ,?c*Mmv!TF[UtǸo{ͻPCDEʦhf_z CәU)1_.8iq/ y6U{>O)'  kLT  A648Qj5H&3"m4R̼DQ{ ''F_ggg^LI?CC_xdP=]4bheTI s;HA_˾2)鎙) bэ(cD'2plhЂ2z *4s`ɩ@:*\Q³ Jgo71Z ^mP^s5Q}P_d>_AIpY֩s-=PI8|']DI֕VMEہWJT_ /_N|E;Y㛍c{G4,9M ־RB2{ːTd*Si#un<\kj>jR t*Nr,9!!pz ضƆ|V;6y m2eY`~!Ne% O4#Ne4-$RBRLתH Yt2466OpE I}Y'K v7ky͛7nBjJ{!sg`vw;nPACn61fd!ls9сGfKDЀY`9qm6[;RؗU0bd~D@806<#ׯ}so߻{wtd!T\Y[?HU<b#Cr`P("fRh-0Q|rURAM{xE!1#%9ߊurTŸC(W2"oh%Љ55%DT VU‘?7|nųյ !6}l7<.1>PQet53Eˮή'-J[HIr%F !piLEf, bXq䢕{0L梭IzMG(Oti<@s  D|ʂ؇o<$bZ1Ŗ%qd1Fq_Î8)pQT(.c5ە +vEI֎Fl#:WVALC068w_;0J֧@@&葉p+k/pD_NK>e ʥ|RԐm 䴜>;Ѧl|-x:wLU^8W WNpv#'Zc$Zü%[qv6#a sY.;xq E-x:RS jJ3HҩPMFM+H(=?@IM3v6A܎:N8]7ڹt@8d|I{@1ou٦Vk|}jLhm4vxCBv׆%"tyciq3 gM0 AqTqO_ 0XUFb(mlm##p4HnNDidxISǦj<4T:zA-7SJdrJ hiTzF:0+*lD֓LtuV/4ŪΛcC 9y /JS#J CS^',R#I97_iw2^^2{}i'h#7fxPLp%jQ עگA\Hrsaˋo&:>ȀAFK e6(^O>f1A'L%,Cm"+4C$ )Pk2a=Z$˰IIz==0 (M*i [g[:D@pʐp4|_I2=Xu{pLXQ0 @:\{X( f ms% DD="$%qO}ԡ( Iq0:b$0xOOjLLsQ*M0sU& QcA*A@ttj T3ӳs@jjKYlqhxcme{}u >b)|ʼndREOג2 D B ySXzA&ZšdD@#G8ptP(#C G'|+W`}{cyuy{[d zFj$a1 -R  rB{ã'z!dth)j}x:580S,U*z%_3:\MM7QzG+"DIA(,?XJ A P)R@wcs ;{>Rxm[c/N1-Q?ԹoY@Z}ȗy|Fd٢s{LK ?™w$_W &umbU}Y2>G%J!q1EF5(PN[|hK+7J<jAz&k\V#V@gDҎ#< ebڐm.J鴭DڑKV^.=p 1E V!iaJ>PI0d*I†Υ\u$Sc::6J;5uI0XGtrDHXz͢9@dPJDЗdQ}X@ӂTӕz8dh #I3Ӂ,]ef!`̞kv')ue[t( Q=Sjb5辷viiG2]@CyE111יh~mA_q8V}V:0|WE4m?ڤ \H%X߹6asj (ω֙]ЛK8~K\~4M[Iu2OR> f:y4dQάTI4о'93. (ˑ(T.5[p~c- e-Z;/e:9NRQK[a23_DGz2T4/ÀT 8! II`<0n0p=cJW5#%yCpq2OJ]`w4ji*mtrc8sBAO:S,+uQrePfƵ+\:`F`IoA7g1gVۃ, $Wqg 5%ѢG!q6QB2GT*%:qUIg:̀NB2_۪hYw3 sj+a$B5,4n:;* b_$)et Pbaq|# 9YYȶұX^΅RCq\x/NqWKl C9Hw]rL_MV[DjB$?)şrv:jφ4y21Vt"ėGYV2{%Y5I\N~l\AFP$abb<[uu20<<'u]O'!+ )tqkǾ2]9d*(ĉo\C͢nb5@9A  /(9B6aK7APM|l|d hD?}?BUؖ>. [x~b뱁 /o,//\^_Z^[z2 [:7C#1Ѩru g'Dٔa P+ccBNQF{Vw߳*Cb >n5]>REQ*MvW2TeqY<-K-.Juȕ-}zQU@ٺцc@¤+'8i71.{NLN5 6lMJ`pZJ #G*$8{6 FGXC PqEo'.MKTXVFO+Ś1T27ev?\R5sq x'hcj#}Q}G&b6(p6@ S?hH(Z&Řlf-^?8>:_Bb}m;`+FFܸ177mn9 I|P@DBp'YpK-4;Y{skSUܾ )T~BpϗY=2%6…P(la…W`#U c?'k[pOh_ʁC8進X)N 6}/7Y_UQsW]Y+Wq^ElC WuB?/JcQXC9ٗ^qw_[0%XHSBnxx;?"i0*t*:N /$~pϛ(z;(s}a(n9붥aJz[Τ}2k5Q#M5-gi Mwix:*%mHG(+O0J*™!q\S&ȱjD (>la M@Rh$[$!uaa0t#_Y G s63#2DDڡg|h0H5vwLzB>w9u{4^ + k7%em8lGw4<&ԧSg?ek!:dIb^:)x4u Qw]BP/h8VwvF3+7yT1znQac{kT Z uƢ ד!qz%!NO!K(&WQX|P {￿I7/^|s<[z|./wvx(ZJ񻐃xKksw ȉ3+Ex_dSf͸dKC6C"uQ Wz ;# (ԁ" P/(d ߌ#j$[V[2L$tWzFTT)V4R]q,${}REWBo1W4Dw?kfGzÕ 8nzV2|9ENw6%j$GѰF+֕2 P%j<+L ;>4BB$ ׊' (w1iӪBHwzC C}{Yhr9n,LE9d+Qp j_f ai"ER$5@.9KR-0Ȱ|0Ȕ'0+v_i40qgSHvF;_o#Lkׇњ1@E\(ăŀk * p:ഽ3D\[s&5)h'FFGҏc `# A HxJL$`eL^}}u AT,Wgj͗k/~_9:llnnnU-bq|palh&&>9k'ͻ7QA@f\@ڹN};yxPx``pMr'/Ј£,s(2}-:$V*|w0<9 'j *k4ID~H\Ue""m<5kP=vv6wv7wg)ArR'5jH(q͕L@8,Vi U%"PQ`xjd0l2K[)w2r}!ߍGrngcR(x ;PvL]ҙz% ~oѱl(քrU֯^Ԁ`0&6D 5ci8(cW>,5J EX"#W ֐~5^H fUҜufSpǘ#Q.%4.ma+Ʊ.e!0''&8?CCW3cx:D{}=8 D|k4s$((cS6MéU=67eQu~ rD_Lۦ9o%!0lqс z$B>`~$t/u}cZnhNW- 'NrcؖML-HA%;+RfXY+>ǔKJ8w4G.?tǿ Xl@%H`$ I$I|4-W|G~{~{Zs|Hyes';^2`vfpgH*6P+qYRͦ8vNٔOWhCk)哇Ixgp@j2hR|[=bnz|hpP:Sg@P*D`bgPNנؠߩ-/mm(-~bv>|txP?=;;M/N O VJX.ojC#ޙCNIA)$)~?P~ 5\@kp^mT %L'"?T@ d8:[Hrc)6Q.^`ZA3Um!,i7[[[[;{P}pp{ />Hu ãU)';KRIvq>>][\@̦OŌ/0IwbYXւd趹Wt1qvΝRn{N G9qT QٖxZI=g0wB,ӮK00 ~3(]KQŐB',MIDK@,0DN!ș:d+a}e]|cfzILq~yѝ #hV/s%w%e! t'&} Żɢelo&PpV !9oJ؎ZSə!?/}^[75h‰3? 4^6Y7{5D_F-p^.D=;..18+.!"L6I!gk~rj ̟Fmͭm@wGF0ah/7yg^Mo͕JQgQec3%[)ɨ\)$ȋ}%cz$KڧビmZgU.)`(;+ݦ ^0usYe֎N gNQ.UnI=rG䪑wc IZF "H󸱷+J])Z)#_XJ}ԃEiIOL cuXy8>nlcBH)-f*X ADZcsuXkԁ=ɕvO_.n Ha|:;318<zG&]#{$S  >;&G@<9 FiO<|f}}|tso޻{ P!HE7>>x޺qU.T%JH$鋵TGjuxvzl|jtx^nD\W:eo=y`KcI ߝy}! R 501vvg3 W~O[71FTy̢\8PN kO +: F#̶G$-l XXP$ %v`~WDgH05C{ޭ7Qy*؄Hd !Aj$XEL.h&%;F!jD`r%9#>ϣ-ۜ@8N?k5R|JZU1,|Rs3PmI .zN{NV:9hK56GNd:gsFBI\###)̃BPggN&s>ك|Go)VQU$fL݄G Q$vՇ6^k.!Oy8ٳc+,L%bv G@ ưuLHpAi[V³ü\)ɸ$4Q`ֻ6lJ-˔PS\?\gd:o>?`d9"6V:z؅?.%F #8bjedIBU#Qѯd7 7;>擙gtѐeɺ'g˚۞܉rILXJU5jrK'/qOn XEv^ 2,}~KT̜#P )_JS$}.c0{`Ooa52mSޥ `".`kĒ[|/~3 CCYIa2A{5~WYW~*9ōs>s`Z2)Y p#g1=a80E\\y.O @'H)A' <\yիacɹv6ܹci|[f~%ABûW̽9r3j\e$-CԸ,-/덷ߜD8~&'&2?/_[2&lm!LE`aq2ydFT'B8?C|V7h4(x)pEr75.ff؀9>UPsA ]'Q0ׅ>K*cEjmلuQhX J>l BuvRb_ Ugz%g"`vJD&BE l#V}um{{Rb^'m~ooGBO p(ǧp6Τ@rV(²ܭ#C$ DEl RؒYQ'fИAC~xlwt g'a44; Y[Q#ڙoQgmH¬'ӐKL@2Gc5aԁ!IQl.5DRA0r4o}J, KŶ~Wv5vǷ PyO|"a/|x'·P9AHSi"ɟqG}=.Edgw{`G|k߻s}rrWqg<1!䟾ٹiĕt}ÿ @ɟKq^!<=IfZA2k} ojޭaƯ\;}7}"AΝy>4= |K~"b<̀>-}9w,/GAXo]҅yFOb˿|soB!.G"Hɴ 3=m"2x|f<P0U)*QLr !E [XJǣ` "9p$i,TNOL4%43(QFE *{ǍJWJT}b;yPrc$H/-)"X†C;hlop{ p͈U8U,%Vq FIs8\yDȡQN!e^N΀LwhJ]{ J¥qQ0QJ9'2( +)a^ nfd=m'džoܸvcb"RL$!|1/_"(*kNL刮${r֏H,DI(iq 82{iM7ai%Gl#5@OkW'OX*wjl4D99 9+pț)e::!r/sSCI* 7R5Q=.*v(,O?pb%-0`[a(q%Deǝ MѮ/sji; ]'Χ1;#6use [CN| onZz8/x0ജu&tFJ Wk3&'&MnSMC]TG,rE *D=[2Nhvk7}'IBF>2r,LtifhΈ 3mV&`?9D\<$;},}dkHқ2/m_v +s(1eNcϚ O}G8+{]Y{t )rԭ+Dzww؃{{v 2M;**z2Th8듩~t#l޼w SӣqWS] ;wo @WgxZrqs8rIMX?ٟe Jή0PVZ/kR $';o~S"Ե GH݌ԘA7WP3=;h}m2Ppf~~q2@#/Wd[R%r#s s=}TOaeDYDFW-}]X?C+sGbӢ K| I+ٷQQTpZO I5g$ՌQP9DEg3B,@^z;Fq-D`SCE=q9TF --+ONVR"#\_SI9t+b ?{&*# †:82:4:>0' Cs`C^`$@@ HNLƺpF&o)jkVUµ/Ase(@{In[Xƽ}W (5]?\fyjp~5hOtg:`7J=t]Df8܉S_!WdyrŻ_k0L+yBI33EPh58C-i:h@eyCήjR$ 83u:9Na[ Ͱdь8H,lrSM0[֯6kqd֊a4y!nf)'0' Gy:9YH8DwCŔLr2>ZڂS-m'55) 0ݼ l&eŢUK2 ;#}g?{t" :ClfDSX⪳ŕxCB6D+ Fn( L=\))Dp_Ͽu1u3Ac͠=$o0PzCK0"Wo\^QR֤ x[-ϟ/c*5M[ú]0PwO `YAQ|Rԟѓ~MNՊ<ćbO2 UR1s UMWVhaDD7]MM rЅu{Օ]hԘs*\4XGGMm#|AҲL88HYđY)n(7S## rm6TiH(|ިÓ) Kd|1^l> !CyGc[>:Q6G-m zIBC9 GFR6SqrF8GJW." hF-ak&󕥥!3:~P ;8P GD0f8@-*ֈI3 }5<>D^9$eP$lldZf}88>k/o7Ȃs+W¶Gt L06"<dkv?clXI?}g^OVtl/A1#o|zX"h]:.?8g16t] Ȯ_()G}H&U 㣗oG/h(h,`ccDҶvN#ר@:ןy"K}tiA;׷ĕKD{9v|M/#l}osO?998;g.C.`h!7v=y ӪҭH(.uUrQ@GX`R1z㖾gƷLip#EH]#O`o2e~F<|MQFN -ϧBⶊ#(p k/$Aj@A2hlAvd/7P/TA 9#r#ű&FEݯ4q8F˃3SeFZ Y vRA־ɩ8 y Ay~7rnn7`9a=Vݚ_q}qrrV+#>Fq65*+5MTh.c`%TUd#(*Ox푱Q<0Qi@hHCi2 l֐iY(a)L8^zGǑ1sptxAG s~d.J H'fk% ;k|gh< x} << ~R!e+*e uT#"H>q~T E0s@N"f E3GKϞl.89T\rmkoܘ9xKx8j!dp+7!^ Q|ZT|%}FrvH! oI\ *!ӂD'+rT}WAL8q|ۻ8#XU[Е#tb C3 /6+Ueʚ,4;YyI\M5)I\YΚ5,ngnY'H|5D=1'*E,)DfmP:rV@u8p1%jb:X% lL7'"E\xTcUMkw昙 x B[*w"q_ꔆNjV5@/C(ˑHMIfYs}k_|eRlzoaП&Coy ^9RlNdE7Юl+3 3{z%]rRG_\E3fO/ )HWM>qat>sq8$;Gj lO .ܸ,Xީ # mll "nt}~~qOc!H^ӳ>-ԋlkJےV/.ui%ҳcGAW  S`/ I Dw]!A.*;MBIeEy`b84O8"'7U*LrhA^ztĖ#zNLrkICV+bksg['׫q Vr cae,łdc0{ ݷWowa~K5|ed|7GƇdž+lO8NqcR1Ci_ƛ71<1 ~Տ<~:-0,GFXɓCCHrzwoܾ{ͻ޸zՉi}cٓ'?x'?}ry}{rh)Heg^" E줱bcmF)kFX $/yH׈C(8!&e$0 N^? |vk338 rHX䑙1787 +xXdPoNqBX'ُ&R@WxÏkkHoqfݫWn le 6j bf>/} 7n\=-plA_\ ׯuCN39dJQ!? ~<N@!$+Bwx FJ>vM$'~JHCJY0p{?yúT CrSirH ̴=XAϞs&f__UPpG kMf#l _dXia5r?5l*Y)ʵd>B!2ibq] 4¸\qsfK[;"' uKɾLy P4d4um-:D\rޤukKpzSՑXp0O$d}\&᪍DGkCz4v~i܀K_gP_.Df+ tI-hc c9{=%_@|(7 #;kPŇERi$.rdj>mT; ,ȖsD+gx6TEv ':Q_VZ#-.o=MŃ6YZqN PsJ dNLZS :p\]F%x ߅zؒ% G'^'޷u$dּ ̏ VV7d- n]sG'zĀ)>$all7w޿Xf9,&w;0q`L]v^ mzP詠bʂewa#~ym8葭E2ƶʃ 3Y(W3NehTRxc%zӺ93FjJ>9T Pv~ȁ+b!iC5dߕOÎçkdHN1&6=_^^f?bs,PNLAurmkR`Dy|Z(Af#N !}~~|p1HPq'&ؑD#CC5ȓPpvg[ǵB{b66X@ɳvNjFfF'FFo8?UO$J]! -Ƴsd5+9%^rru}ֵ7įIAzB#åq*;],FSPW0#MB@*90X:lRG$: c}d^}gX&F)4FgH̲d@&'[;G'9ypQjAKv*hOI()Q[GfਤD&><]E |4lnmn|lciiݵex:j}H¹řE WH p? efgfܹwUD!8%(?:={D4ፑlb/,תʼ: -t!$J"gf1^ER^h,j_?%$-q%2Ʀ'%C3#a#È#"* U& Hm_v9(T/gk0U8-3` '@? {bhPCl j!҇Xr,Ht ~j֪m1 )4,h?4ĢiyNi=HClĤtm6}Zm31V~֏҅,W!Eeoֳ!Fth=Nza"65l3c. c ߜ%疔o :ꊩ;Ϳ2ЖSeMw"k&|9܁}:|4q;yi)Qj(UKFђ-LKiXDh0ƔcvdAtd>ޢCØr47@ $l~<^G. _şh ?}!zi ;oui堻āyq>'/%J(+P BbUA̹yhgCϿsM-g\[}olqɈ'O,g?{ﲢQ )/_ *nݺڣ'b1<|iI*sp4Zo뷿-(w hCD ( G/g'7#ǻ} `L$*@\ k?b;_%ٚdh#AKo}/ ?x Ԓ1a1 ] ?{n˟{LyaVw 0m([?U`xfoGÃOIJ*yt9cD L^>Нwjf:`L2HOk{n/Ҧ=BO }dG#-*`zWDx$Pas!GG6D\ B<)¢;;!`%J 1"R#_+3N$ bva G'0e2[`_m9!YŤ溠jRABv8kC|Y8iqX@9X<7:890AبRZ&b_pdv,J@(v()y_??/#21@R '`Tҋ괾qws3%TSFObajuLwQi QɵZC.Y$ V1 &a RP˕-@^ j_ad>;ϟEYLK`CJbE)`q{L ()<4%@ b钫%(!h?6>6PHoI٫ cm񧟮xvTipy@^{unjrferT1νJ|6q*Q伏8%CaB_ ϵj =)z#OR0~vPKm 7ai 2x(ʖkk|tGͲ-ƁYGM lFK'95%U·=|虄dtS'£/zt455ٙKOD<'56w|G>G`'8`w|;Nj?hOD޹}힥"oqPA=4aRB?ԔȤ=Rl^ i>X}={aW!Cyd5_lS*)J@'1PYB:Xqʡ\*  R6YOϖ?5J $lE7 Y {rA= dbx=Ba ŋ$ "qJ\3D~W/~A ]]>+;;u=&+Rg<4LBBeTG>ܘaU nI BcunSx@8i .Dς!+ ~8ЈHwsvm!L%ZQ"M(ס8|gg=ٯ3'.)5?aTAX@ŤmNۅ\ &9N<^(d|opvha{F&\q1hĴd%\PlB7`a`FQ fm| L(ЏЊk3SKNMM VK[/76MD+4NM\089|֕7޺q}nbf矼ޓG_mmD~a;7_@E\0 > g@))!.b\ ã&`ċQGD=aR w) -d@ _" @vh`cgok` aNpbyxԨ=1=ˈ /I V_t+P\C!ù2<2})sX}r("Ԡ8Z[?{m,ON[צܽv֕v7a8;ƙ|ݷ:ocCEQ3@H}CB"rHP8;C ƈwݳ|_3Wjǐ/PCj}IiJ~S.Sʝ!g /Xp p>~*x:;?ȝ>A gx1wj3W pťyϨbTSW e uS]A\MΦSy1> vyokb ]pc0$I[zMTv9P/Pg9qŸ6ec idOL#&&УF<%I 5!VHӂ =s"EJfj>>1Nm0)c4Ih|h;a(C0M{R)-q#<mM!?dų4qFugZdNp6YNU6;ꄒ:wS8.o9Iuy1rS FedH!!3ĔWDDi?δ#hẏ="8/5ۡJx4z< h{VEND![w??~~pgh074+q,X` y&ʗ碅$v{h=/d&7>eN^ګ"KC_P|9JePs_h@iUhxW5|ѥ9&d%"W֌6{` 6}1,2a sa~bK[o+*wyDgģe$PxRە :Azr2DL$vON/K9?T%Hkpݗ䋢S># wg!\O>4m)uQfJ*oRD0f]U iNu҂}.,InOP+Nh(QTҥwIVK<ڮ#ᰈ%5~T8*A`~t$A(%l%!2K /@g-Jd]~LtwZ ({̋9qRJd|`yń ֆ|ęɉZ_~ssgkcd-fL`9%CAL` u GMvw[ /(Yo5OėZ@㰪s!3\q.y/GxR W)|sGO>xs}<#<џy{jB'(Q͠p&8L֑s<|+U+QT=\34\DKڋ+e=> 2ၜ5f=Bb<.Y6x Y]f"|9什DɀB4N~U6 X=wf@J#M lζLOWD"A"#Jen"3tK)!8Mm&X4 4̎1i?@"%>$@a\n|4d(\Gg!!D޸ Y# jETvɠQ8cO>dv d)pLc)B1ACSڈ :Hhur1 NtŇ㌾(uP_dWޡ)[k>]͋bQO̕ĝM|'tkN: K("49fZ$ˈ~O eK? FtɤkYyjU}r G5ug6n`JjBp&Otya@'61?*2j=ь%?~ZXV:E05Š֨ սE@Ȗ ?$?7Wm̫%˔[Ab2'b4VW70P%=_lWc e",2#֭l.BU[DyA;&Z@IR9*~~Isdzx`›צn̏̎ c IBd"8>jmGFFggQa+eT7UFKlQQ$ݐ8R(~-߁VAҝ(V hsf,@ T1ZRBZLO__s8kk * P-Kd=qΘ)sF?*Ǒ;L:|57P-eBfDSGJҫ@@ 4L cA ԦXCeb D0pȆ8]o UU׏vÑֿJIM2I̦#֓SOh5@e1MxEANV(߃} rc8NL564"˙(z0ȌV&Q O\"%dNzS@i&Xkf d#mq)8z%ddq eDũNnzˀ ׶HJ)ɿ.sS"h k"UO=@o `Fi@t瑝;6w֨0h1ཡZ]۴!?lgLz #TRgѸB2#:JJmTJ(>`bI0> =[իP1 ' CF_C%˨%LV-j#Ag ru~h:FD6] {hlhbf^x/N7Fܛ{Ƶk7D@ Fı >cd-IF g&|sӳ3H]@ 9 s'R,"/$萔KH#',p*'BB=7B89FjE#L[>dķb X2if3="'B%o$W59,bFJ]:Cg ҹ@FW*y|.hᢰ7x4e!/f! aJ!LQ4NJ7l׋7?5Y<:ٴԎүTvyv&R3Y1X BJӌ8E]`2#WgN7u)J1WO y{F Q*o!4ޅ;xs\ "j:ҟB4d(<$2#:y~ڠ!*%\sYlbOji^>V?>a޾/ ҡ+$P۠Lewz\p9JKaEcW`3mt.O =a|Cz҉:cw荓l+,hvi׸H0uՑ=q}t'*}B bХi|rIf9;˝wˑ|zk.Y&$J6}#1#gN|~t $pF5s-BUE3gEձǻ_ 2h1=8Q?Ck.OJzCu-X:j#Rh?V)_/ ɯc$'8NU>yl@n,Z4 {}tC@Cvy6@2JzՁ^יA CG U.6غr)5#Pq_P4?0턍RB֋ ҡ heP/ A͈]@B'W.2J" |/NAr  a*iC6JXȏ$ʽdRM6LV,̓a% ҜCJo$#hk+{tdCGQl*FF%]L l5):1O:؇Yޏ4U6lqck^6 "`2x$4ȷW*# U7 'La\5sZFμ _)L{)c|†W@5!6Ű}Xñd] VK,i_^22:ګ#FV0bDlbͷy{oxX'#sSCcHx$&D( /x[dB42Z#n8#gRhV-FmR#JqL3;v@aB4_Gvp) áC%q- TZk`lprZq>x*Z䄄@@آ܉-<p#Y(rhF1W+s=sefN|MIWW *=t iRrQh.<R@r6KHӑ:}z6Aiy 'Tx$|Gbu*%*H_,gvr+C~*x'/䰉 m1faAbHZ.Nѓm䈐p1pQy18Օ.o:lpJݨWU!hAmF`1`ks@v+&7-0S5 c=pm I#-+2&02D j1T~TXѹEI2fq_RLVqat"pԪ@arW]Q(tS#{!PSvD5G!~ޞ[k&TSN:YγIľ=f< 崧 dY‰+'(Ot~qs5tfSkF)Rf^~!V UZlut$|uhBC:N/y ^(R1EL!3Faۄ#E}^!$ZorPU= ޿i_-X)h1 ^ӊ&QFz G]Iy (ĩ{lx?Z?S^ ގtbu )֟P+G0GܿzoWo\7v{c?Ꞥؕ(bq1Bz-B0>|>;  2:;I2JF:rK}:A+lywph3'c/DYs*ig;xfF'x4qrcGQ[m *s_h5L*) !'Yeb-&eK9DNYM:lvnU}^=WgZUwjB7%'}z@Ĕ,ba^I2%uaG"N\( [Xr lx]f}U /"Zkl#}90960xڍ[A}ʼn!=402cEHp|@ (pgN=H=JІGqA8<+}!Ⱦ:=jԏ7˛;/6?yzѳ|0;xg*E P.h;Hvxtx|ty|/*;}(S*v]ôFfJY*YҠ+!BDHBJx ;̆}9cÈ򨝟N Tn^[?,ܻ^X "8Є-\!_h>Kqsg8ss"',kRߔM)rȬi0\MCB 8t5JMQe,⭑ԑ,i x9*< 7E)_-[}ELYݩoȉ x\VP:DĎ!WT7go)bw o> 1` ·ywN-W5m8@6y+63~'Q2H*,tBg9Zfɷ2:I9k@XMY7"Ε3#s\ٴsp ND6cL~1#Yr !q INdoIdc^!ξa)&GID60 ́Ph)%)oݖYeD…K)̸UkAppK(KQo=QZp>vg_}%.,..7(Vx;*|@4 %TdTYhcd14 t.D,%!GrT+zVj)j4Fٻ˞wa_%#u5֨{:Ӏ TqY^^G=Bw݈/㶃x/o\r9ZT'/VV Yz?\|7G.lH2`Q< 6Qbu>i|;xwo,nj[?) ]ߺzu5 +@%eGN9#=)MEjN]pd;ȪAB~MN/bnHi2J %ԅ!T7>:ӛUO#tAiRO>x 1;w~hk3Z{#angӕZhiue=[]ڬp])hc(LFmLp:!AF3ZL P iT,NOܹͻwff0F!M"V@Jq=#*7*6. aQH"X3Ԧ?.rrQb*zOx4i%L b@7=JlP߷3J xNqlh{iP88GŌ2 N7ŰwxP? Cd^Rpn0[spyL#Eԛ$2at戣5$jD]> I@,ьܦgը3BO]06-Y)G ذ8%G sRH)Š4< _F2h`Z=;)ӆP%&"gLʿ< G+[cRTK'Un "JvɁFEdǹiXzqjA%rOTH5ox^̱bM/pVK\KXW|PX\:=g' ; tkB::R|]F|FNhkg/{1\SltM&f  ehhn6ήSc? g){611攤~2I!A% PwfB'-x%*Rau7|(ETU(ʎ:mT ;; R{B?ɳ%:g<啑a|ChYi0;a=.=^Q(ݒŒ4z(6PAJ|grZnpG_{{?kQ]rx# QrW\^ZCx,h@ʋٯVw*$ i1|ǤjC!lVbqAkUR%;i:Y mSh:$!@ɢ/(0@W)<((ké>(,biX47&* ܗp2:xUN&fi._x%JXrY?hizAFhuzZ &*#|Db~P9/ޟb48A$.'HNGksS֣g+;EX%(Th^ϋf;jO'pjQ5*G+Fg@م=L,JYDC@[*R# R:W6ڛ(Otytx8<܇_oc]}?~K>?^ztѳ'/_W6GvG[c"(T(#jMJUGӦ6L):mVjNk=7W-?_|;\;"Ory,xmV_OQ۱&{8g!N!DN^>}zXWK 61ЈTA0 3gRfsu`ie3 SS'f). 4rL)d'kAiO~cO$. IiBTif$i*V8!gGcJJJ}3T ʃǹ~QXIr"w*Z__ lq'&btp\j5PG =R58fRub[2KL GJylggZ|P Ն3^`[ePdYT ԗXc\`$>ب2OЬD^(<<,3lBnj8ۏXDI4a"Kˊ rOc4y)bA  Íb^c% ꀙVM\IZbT\ V)DTc'":Z(t5m A,ysͫ8g.QK<N8bBHRe'0m Q:h'a%jQ1nfGq_+y*ud_05c6Q0i;JK^K<0i0^cHbߤL IiΎM2 ;DsiR+`N"&s z:8RrO.kVb6Ny);'e^Ғ=bS܃û2bjz?qolx>0fࣘe/[zrEvo SlrOFvbٰŷ?{?Oo?'K{O1抔|~J_1dM.u/٢hFL(b}u<U/mݗ (4?ܢJ^GV{Ϗ5$f*Md߼G7&;T)4q@H/\:b(x W8JfO3ץ[ tAiffwcZ_<_9'Ysov^˘l.|t᭮IPrrbZv ~_˿~~/_˿ʟߐiui؈8TٵLEpo*Y(T Pd3ma:?$z>]I+q"Ud0TUj_))9l"OR*aV)]3 ( i6>ޫ#% yuzx%('F,LNLN ʰjѨ%t غw[{GhrY$^ LOG Y{'C`ҠVTI *QذB\27h~p%崑r-sopS _:PAdpliis{4cd?@DW)JU N7ޮ ON~&;'9&!'XԨHvM{GÁs# j5 ;<(UC}}('Y6Q;?|SN)NCŁJqVU럯b<'Lj!r@󳽃O?|ɓg9[EFC$H- :pvdNT1@ GhTD% vk׆1QR2#<]ԋw>#$;b1؎dKz,OS7"i'ȗۅDOv5 (֪Ýy2Y yGz.(gUhL {8m1Mf!#T&nJafLҽ >@7촡y19!(37!7<׷#L{ʛ4WgYѰ %b6W1RV԰D] ~Lsaƍ h댩:)-C dfIXx"7gSIfQ)4FH#r}/(i}_fw>`+#-k3{c.y&;ҙ1s_.pb Z̼LWiǓuFZ@[D b|b?@7zu5j @8)tM~Ό2,k,;͓U5ЩkD% kBSq֍*4CآT**p+R;~O_tB?H ojʾW?/gܽ{E!,.(g(|yXScx@yL|ԍ×Oʯo,oяzFO C+0ukJU`CC.>>yRBuDIi;DnČp4 q' 7{jJju +"lp};inX "^UX$`bnZFYYܮ#9  0G5 0gEDG]Dt>cgfW.z9-  מ>{;'~O>у{ųZa~<3V/]-߽:+}k{7Gnsk&nܚuo͹AT X}9lWCLO!Cd୫w^řTmv2Q+Q8>Fo}g/6vwXN4[{;X#c(nc". 3- !Jʊ0;b mNLN^vmxb )M Me# cGC*XZ((1Ȭ R6A;*r)I9 -$4PJtb_8nm4Ww{!^($hU*Scóӓcp$nK`ex cHc>GRTP LfH؂-q umZ VOPV d\Tl M8.mFثvIJrMpڨqdkU#0C0d\7CM򂲩%$[|*SG0L}##Ĉi_y.Qj16/ }D^WYo[U(Cc7!c. mDHh }DZҎ|3'xP̽o䊭ٷn'nTp$)̒[~>7R~[aѧAKid{sFOat7e. %Y_@z"Ҁ}UoF#ZN:a[kDZdB.q!܀k;SH<3edzC-`/Hg K (H%$`4APSsaEꝾ_rBt5Q/p/] ۺ@<TdIY #Dt;;GK(_W|K_gבfSѲl077_|ƷoQR o~~+M]2w>dJŖ(ϿuehQX^6ʏ!Q[7y_=n3lI,-s@jq'iP4F{p"][F xJ?/so2лq}Ǥ`4D%~FNվS_걔@-Թ2,ƹyJXZZw {[]OI\l ?؆4f_TAI 0!GM*De I3 =9*Vw_Ccam9=>m#%^rxh\%"䘽TgPg B{:@>c|*[y줾5ؗK>e0F 1 %ފZ}$[)!d<;YXXz?~rk}h~|tx''b{n|d~fxf67141S#xuzr`,+ccb*}C}hqBKvԯL]~<4ّ[gnޛvkan~hr<;;233(Z;ngD4@Y γ[K:phln-LefX' <@/"%Z̹# O\^Y_Fߵ!y'nYA!xZȡX" #>@=bqQfwh֠DLx Хp30f1Γ|Ysu⥛^25iCf|@׬Y)dj٭jPU 62/?dWLJvFIA+:ꅯ$P]5usxZCb)3 id TGV!Ci2}KYrEP_Mqdd}^cb^/.qi[x)!A2/$%>d#%_"= dž) IZH866ڕ!`4إx%gqKAFV;T)@ڬfPܦũ@>O4/#JӬH L]Lg#}%XWx6ʹd :A\/@6lO8"m[,K2gwu!A8h+;]Y~L|4C L==߃&ʙ d`?s]id_W#Gd訐Vŷ?FiCGpT#=K3`VEJZbUM~|-O3$h0ϬBE 6!MeuJovA'up_\z~O˿3wx goܸb>6r /LLVZcE&\DWބ%!Ջ?5}SsuP ȡ M" v",ѩ:a_7ނ>xMe0t++L;%{e WZlS!S95iboΨ8)ME]%Q6cPTz*؁~1uX˜" r7Pe(!Ֆ hWF ƑR0240758?qlj.NAV}:pyH1~ Y)pRͣchR#%qj=b22\jͱ<[O@}pqtU+bĉ@$6.;Z8:\Vķۨ gc]:=?i[Hid/(PP*RyXi2Qqko>}?C*x&'g'#%\͌~޵xr9n\yE rG?%%gGjKaR󁒝S$ aDN@J"1 &-j- W`UjXc j8BId@s$q5I%nЀ:%H R'y}`ͥiHG Qros{'Ho)dNOHUƒUpT<= @A]RC\7TјJ"Gt%{BKWޫ_2GDO GRã4s]2p7JfOpQ^0̚شlc8Ys 7+$A>(3W;K̒.ː8J%p& [ևn;:E>j*,6~vaV^ɛ0*tRM>nЛV)EdoyS\=coM3=CP7 (C@}P+ȤMwUЮW0q+K:PtÔ;_.˵w⟶@z.}0NXeVF ?} ~xiPL)u!B}Y'#3]{m6K< JHv3i3dT-v5(I`7cmRTA F҉Rmw:@^4d0wb*d1|uD;Ϟ/NFvme+o}c_g|W/|o>Ǿo&6qi+ 7ݼu~㧿%Dbm^?r?s8ļM5 um ,{26:aIv[꨻~ҘOo!fRSSL4|N~|Ve@fSSbh>wb .C">S~s  zE4l'Zh#hD c?dk@,Uh1ŚѰoX~?ikFr\,.Fv~{Wʪ:?qdսoyVe<'#ūW;{?{]ȡ#Fg#`ndрiO_\sY꧈`W &s٤%1Z4\A<]0gCt8^ۿ տ=8ȥ%S}d}R~`,_~-#68:/| S~·Bٚm۽Gde& ;x3_JدP Z[ٚ TI"`[K:{ǂ#:gKLp1m5l&g֨ٲ[/}ALk^_:kTS$9%P$*DR=3V)}!V|1IdPGE^ y'PAtQǸ`P *9@/jϾmq99h2tMdtSn1V?Bg ÁCM2r+@bD E\QlR(Y8.o )fAps3^餼ZNxتv[{4P{)k;z I"RKPRY/ x tsu%jaʱ?"C[`:._ҫw26(׉` 4rqڜ?{O˝\/O7~85w?:r:GH7BB C~ Z 4  xZA1>xY\Ws^VA7$DzG6nn"PTꏰ,(Tzn9 3%a+A,B6Lmv{N=-O+a&\u⇌OX},=ݕAֽ6#C@S 9tT} Z=r%[^W@e8'WGo]KG77fd~a8tqim49Il[{ DP#c9hưDi_J͑aV Cw7^/-& ֢2qPHmA/GztWp5:I*9re^^puvԠFIs],N^Tz4Fl%Ӈ+ycOfc1f4§/-j8WaZ 4A7٢5uv#SB_1F Ӧ'-Xtj_YX0kvĴ fHs'Y2<]iF-`V6e{;ENg0ű'>Un~YX(>J-ٙ0_h;eF`2秪3'QE(u?OEK =wg:|GB>^F>†xI+&R2>W4>tWNNуTdc+퇱ù7Nf@ j9huڥb2O7nDeԏ /GE/nOo:>BF;A5~+84H3U+4/9;u_Tn+䎽|I?@nP5gS]/LR$4pR8Q;tRڢd]*\TVyNƆ\6 |_G8NpfzHy| n>,6;2r]QS1_`.a0_|d9!S!z@G8yaivjJ/!j@T+!C+$TIM"uh$I\(դ!e7 ^v1Eh݄M-4|Du94tv?6N(e\V-  .2ȕ,rL{޻-N^PaUkN|򽏑~m={ O~~7~~oO_~\~~>o~{uy;?vTE &REݙ z%%=z7;PW=Hr:)Ef\+wʫ7wռ5:RC @96(r.xKRE{(Je6{{_/rҋbTy0YoW:DHg7FZQmUHԠ\ X1+bZl0Oﴚ(T({mfөd3.dMcAW ~gFѫv"$朖OmW[}lC3?/A eGU{]`?tջKd[ѿɿswdra%XT=˿~M2H/0ra>Nq_~+nT uO/ _T +5mfjj,&8}N |Q.6g/ .s9|^>\vKv\'!-_~Gŷj^T\:}ԾÂsQ,=4d #jlB߈N l!#n gS6} Á0s$ѼZP o$@u=ayD+%AsQa|H}'fً=J:d w˄C*Z!>0E7-oWqNN7R/0֣z0/ Tzl~tz yp*953I㿀b"P:WLJ/P:5Q&|! a4Cpd Rs/|%L;n- n.o/ y#!XMJO7x#.({QAb䚙¼?}_186 @Bd԰ x4JGn>L~wy},rr}Qll7 s~?}g?G|)g\5WBQ4f{Tfy&%b@RȝiL2e6rvSRN6B Od$*ꢝ"IntX&u)N6ix5J: !0:iѦ(&IԆ6zcn@&>e2Fn!gUQ {kjBz|->^ŖM`˞Ah"5tx%FI(+Ωv`=F:l rw2[|RӔ-l'imd~N61p0Qmyyn?F'6Bcs's ǭm8$b-tW0J [fJYR~D,5pa8XIocq/> S*ujO=W,J@W wr><Or!e7 p??O5`'ߠQ}ĜA7߼Amh7+DZ};jf#ʿƗḵC\kAHS0 C$V0H?Mx-ST4ϸK#Kil8G?ɇ1#L e??#꼀 Z@>+? 'JT"kM`xZ6M|?KCS h >M\(t=7Kqa'D)L-AFDo(FṮ -3wb5w rq:Ԓ#3D'ĎXksԝD^\AuH@CS>SVW](nNYT <~hj h>)G~p: 'Ј zVW5P2I{7=T*CEɬ U-h t!d-a(Uk<d^||߬w;FDJ]uDO`R@du@XJr>ppȯ6kw@6h?_sү<vvku +jmQoU93G^XG$;e#hӰHh(Ub-_eDg`-.6y94`U6Wwcr7brW_ T,/Ke3/2טo氌 9qAUSk+B \!o>D @!FDO?tiԛ#ۯ}_|52._n{C}/x 7=ă|QՄE(1dyLgur+壓}]QLW~{շ=ߜ"'L 2jVq)n`jr2+Ԓqox/^ş|3oƅ aշHOE@EVϖvϊӓTT4C/Z77*WGLpKj1@<"}]t#fɓ0#S0_/^.\em>YLĴպ6GAiUh~2 q(vO8TS1[QkSM(*)|U P+@J8~BqH4a8,A-@W~G.9\2%5gJXo\Usu{HgFXҋ~40b#}&RԲU,418~ |z [lXZ?_u#p|pva> O24ʩ޺' wH7T)d'['LfBcI5, ;R)%.^\! d6`E4hUӒ=I ,=I;&S- NډPhJߴ=5_h&.u8qb(G,Ƣ5 f'uh1nIvs$wØ{M$h@һV.N}/?&t)ŭy~p¯w_20>o8??mPm 41h@g<77=ݺ x?{{OM?Tmۋ? i_|cmбn `92?L 8ORW`O)?l>Á>D~SU>?ks5ISs4;br/iwh@Չ5.)@䧀? ?}Ks M>wh?P?េ:z| t;}u}wq~ >_4n dPԉNЃ`gWHb}Zr7>YW3r@wYlsYߑ@s4 _{n'm?+"h^&$ט?[l ],U'r *&ti7aK8xZqV>o7ɴ_LG8'1~/gV|}xg]qC=$q­89 y`(֪j l3hKC+#c]O_zȋ+TjzY)Uo9CyL(rQb A4OcD.L9I#令f:}˟⛟|xq^\LM lfx6MoX#L#!6nՋݝ&A:;t‰fEh~+VJ̏ pTm•ńm k_:89` O6RWaa}IJC0+i(8.RfHhVbэ_xr USmX*W0%d)'Cf/k\IVdL&.N8R7(qRxCO=;L  |Uu.>gX϶*$5bpȷ[𨪣 D]%(Ƞ;I rWqñu \U?P8 ^n`r05x¾na!R?2}=>Ȍa7/49/32f&vk >5F ؈u(l ̕p&DG 5`̍T^ LrNnA` N@a : [!pyj@ʅ(8 ůlV AoKzxbֆp@Or!w"f鲼B1_o?]jTtGX@('Eqv/~8Nuq*:^*4c {54|? `H.D5^ِi@_承˞ނx,-'@N9L*!Vf?߼? !T5l26k̬׫nTK(miώ> 泿R Q ,h}§=0?P|Z)evYh 0l|#wAgCh;2w??è; !0 qC?_,z; ֕j8SgNM(ґgĤA}-c!?o/Ҽ<=әpw~׿@NU~!M}vv2֊-BUV0q?,Nj-qE\mmxg Th{& Y"anW RG_=#eX[_vSY?#N'F>bwf5hĪ\]UY ;1@TR#Ba=7Ntɨ4Rx}T\!I@>V"Uc^ ?ן]w6)G63 Ȥ @Œ.R,SG9a8FҪߞzp["l?s)V5NҭTxH P1-6tsXtmP8Z*jk-O2TjEۇvxv~w zCOWlOs' ?<.zz1:폦SW->𮇖9>4;Ђh$95˷wպԩCeW<`Y.NrtǮէwTը¡&n8dw=MVJHSoHiX?%L O`@e ,4?Ѡwt 12HT9(rk<Wo޼9۷_|~A&J}U4,..@8?X7d,kV_>;²EK@ Oe(Grj8,Jb"$g$*8#ɛWn $ˠ:\غłS6ӵR!VI\=(/^h3փ_bN3LH!Mmo+$B$Nqȭ,w+YX0ADٚ«oT154$5HbqЄ;DTs0D:V,վPӱGWF^^ɶ[7!n+C,UkBqU4yP h$' ^G $7J0kdLJO.8:\]WZVqˆxr!:l/blF`mMfi|1QG7MOAYKrȭ1Z WQ,adar92SF@bWu=qpiK)lbK6B/N} ?/n!׆lGQAD$F~W>s٧h +ar<\ K-&iP|6i/,H3_g8{عA" _a*LL `|?pg %? C6CXXRx;oVwR98dDp('/. 3 HeC#"r/[4IDATgꗱ0$Ґhbaԁ-ɾxv,Ow׶ڹ[r"6O%)|]r=DrNZmo{ )a'O$2LH hb9 j5AQC< [h6,7ϻ헇7A!Y& RQ%ՐpI9pǯaǽAzz(7gQ>'eXvʘż|+6vknըtbFY$!`(L{:>{7̆~f`}PKd4CQ5~\wWwDh¦]B uvڍE1˵ *]@'ZnU΋ŗoݨ%@!QBd) =&p|nr 9yd` H)L cA߄i=Ϧʙ`~[f h"D*c)"U"FE e/]xl_~\*W%̧y`kn_x{p8cUBRUv&u0Qt8Gy)jIϦ!/o:M 13CA"=ц6$ FD}x8KOUmbPPs+l"Bhf'p*d$4y>NLJjP-+R5mlT0U1 m򊶱6nYMX@2%XR$t'/Xj L[ކ.4ț}"-f驆&j~E!l+g$J储Qն]A.(vK_Clɐ}2 :5dزjђY9ظҕזFC27>}؃D1)AYxVv$l`_CSAL4$6:8ǩ!?1C)n2EW.a)9B{b6E;5x eGfvyUagNn_x %?H;U`aTeJ|PX`~"t$Qy37 Nd [êmt}ձ`_2 6lEٗDЌ#[30m-/4f\Y ;=e)DiE ~) Q cQut$?GrJx x$G&SgCD lIJ >"ѩl#Zfe'xeÑ E9ԸGf<5鎨>t{R'CdCfl6U=^ha'A !!jZIt6t'0@6yr B xҘNVk4@p, GjrR-[ *)=_aJNI>q- <Ҏ$WN.QDv UtT>:bA%o+K hW  {26~[/u\]f[A 0p!o#5XFb5iJ=ս` }ʵ*aK,(zZ+ AÇ$ƣܑi<{Wj˶JKˠ 5bE--ng%NqPONwN$YB*&X$u?fPhyKtNB62!OP"QcĬRt\51:7ad ( EB<,4DpK?WZ)T51af9ںI":q8do涖d2}Chk,įx87߶DDPpDuB$^l4F"^H8P&\葙\4k^YfY Cч& #렰dT@z8cN@ /Z EIV v~7!iC UQΠj*U5~PGC}O8W kG!"܌(`ca) -R,"CLYT^,VpɌ+x]_ Y@EopEHI~V,6D $Ą܂ QJ؞,J_3؁ZG! 3cI̐@VLkPeQLY+ r+l r Q9EI 7Y!/P,0qqt\@jmVvn#YA;z D,bX ~'EXs,,vO>FTXƗ믿bл9|:T 0sl@Ýkǘ@`_|ۛ~#a_z֨Z6fDH W̏0p1p8joUu/g&/ \Xj˖UOD=D&-DmniV+r)Y7gghAc`AB*Ԙ(|ѬJbq۠D{GTR";@ˢP)"9`z((p?B lJhnNP6"&TtQ;>>hVB*:5bszjp|TM Ɣ5p! ?6ytD#J/~H4tP!y>v*7}] oO[Ě6rmwT9MHÀ*. z[ܳ=Y޹-gT5wr|*EtK1*QTmoG.H ?7wH3 \K%<3RMsI&]c@@ucر!-$YX"gUEUTLrLB*ʷi/rTQTLO1L(* (=hjw0`(XHl>,yV#@w{1t6FP7bA4&xpJ1h0(I-^c_C?duҋ_ɱ\xo Ӑ𸪱Daq`Tz@F0ү0uR#7nRu"Ay^ T95Wvr<*0ZGmrS/"uy$9W;vLJ)DY@SrUe-BpZ 4Ճ `LzHQ7ݨՋFDzL]L$B ^ fՏ:(p`C26&2[>G$ e6#[/|fC*V.MYיľ7N1(X7|\ax̤l9iC &np̌s9x<;4se) ;9U@kRq].^ì(J'QB/5Sh('x/ 6D!BQ;9:|''vi< 4BؾpKDCnoAb4wwuY@ن[*r†)J6Q,@/5)J至Y.?7P̳vdzt9=v:\ "94WU2Zɖ0v; LPlR(B3q.v֩ڴ9 YKРZֆWh.=?c \l=%[slD>x8,/&odTts?)u*JEx;Uxp*A-Oe-Xӡ\tTJQ̗AP[aj+"YVrU1VG DC`~U%Bu_db{P]-%BB)e]8. Hh!nle9v?{. x֯* bHxT 6bb|I2X5;izmzԞKQmʅcݔt:ƵuM"˯ĐTG-h0FNL?e4 U$떐vX2TP Go2UĈX-)Lݔ.p6)254_P~_ڿ(w ځmHn0ElMQ5amlvA؝?Bf,R aPIn⛫ _Ă7熉 |%`{1¬'hUƁ"銣43GɒTCSh[ws61ªoc\o!|l`DWWG0@v6֞1=8zSzFc"KOV ;4cݣqN{*eX}9%ž;s)mstd@UB9Um>2)(aؔ5ތӄŇa2Вg̑]y)4avg㉦d[u1|ƷY0͑$otOFlSKfh^h6`@TSĖZlPMf&ep̈́hԳdL&.]\֬ Jʏh|Ƞ 盔 աpf |sauk[n9hY+DjI!gAʸ2AH9<:uA@0Ἢ90y͵JEՍj|QPc؇/BsefTm7QU1ea::D*1%AA5S5٫=QzԼbN@.~|B˅)A8b*yH RUPF A8TWK0Cr&ߨTj^G>ؓg/Q4F>6Xҥcf|ٔ('tz<1 X4sZeMo*6TH@%6Me!r()5QrLi,YdJO:#IyI"Rj%RE o01sY߰Yg0ѠCs rlV>Ў뒟04Dd`v갚eՐ<^*I ))Y߄9 eAe . ) 5f6Z$|.+`Ę! 5B\rA³CF [4%D.%K,;b:A 6ǧϺz9<h1E*% _v AHIa?{bz[?TQgF5h }jyQR^j7]Z\q+ 'B:6p;^wT h; Z؃=*.H}TYb2*iJR. %G4/NnSdi/`,Jت`hXM87~44eC$KL;PP(ѦuL i @bSC>[ oEG7mpP=GR2!)_rA(w ehQdЎ%Fda›6ge* !*V"''}IMwE99RCX$oDݴyc-v5F( %Áqp3UJ.$|L$Raip_a 0"Yҕä2&'E>{7Auqm܏L}>dD?" )$  n FGYBov"+>캲)CRU nmlʒ\I>pΌ">End-s)BFcLڥokglb{6C\@ jNQ`hNtUgHFRkjFi=s`\țeՕhZ\\VKU/:`&\͜_S =Tqak~.$s5ءnУuT&DwLHlY9W7_uENWO;4^'Ŵt]4j^K]-zlѪc Hwz01A5O? 5S<6$fU2^4ϼhaK2u(|h| H2KjXPEC9TϷSyXv2|iBX+4݄ Ĉ<$ Nr l̆p/jJ%TTGnGQAԋĬ;b$ qy7ytiܐkn? 8+Ö\Edre ‡#$PV;Rɮ,lhͪHxD1,ʒj" EeILGSh1Qx +D!e6 1-4IYynȍB{@$Q`-Iy Z4]z٨Ɏ<%Bl-Gf䐉HE')7p<] XJ+# nkOOOpzxgEJr.H⽰T WH7 =)Er UMR tAa`8k8P@$#tj M 젨j]\#T" ,&kRXG2@DM- J%Le.nRa/.~)΋IȀ"e0j )"zno>lHX(aVQYA8򋯾Rk?zT @NQ+ K0՘/'8y{4VSv^2+"l6zKyAu$@iN2Od`]NDe&y/' 60;D#+ *qy݁ & lmva^M Z4m|"l'lhW{R4ݪEAwܼTm;A]75gS xDS  Z.N7K@C3j 8L֍4]Wj]JYyV| c ņ [LyJPƒx]Jf(M]?*-^aqpT9 ҷTV(+<;Sp`8}0? U~ 7gA6A9Z佌FF 7669B$#ⶨc×i&p\h<) qZ7T߈|LS10O2"U4)"HQDŽM4#c%%m dT[upp~Cl$9wO3&GuOvJL549H H&D.Mm. sU>n:h_PoUEe/- JȳZx3BM5L,!89 QlZj*c VZ}@zi?ޑdH*XAAj;s1ǁ`"ѫ*yVWzS-K&~TvCb }Qk%M J\QDUt+@BN#.Xa.2D^"y6)\FZ'A վcٷR zGTMs7ߨ)j~R liU\t2r&MJ.SmR²78GI̭iAsG4o&u`!Cz9Ïǹ'ѹHMHt}sU8E3e ?cHb> 2BfhL,6#1 hsb T||b"oA>&+bzNhNj5d9 Z+"Jc5G0P]œdIZD-Y\BoiC$N \dGˏJ/M$Iv4gnʃJlf}5гhSFahGDA.u 8aYgw͗ϐƴdd':Ap-!"p RQr+rچ%;|9ТlŐE67|9AGhpUJ@n nϿdg{;27]gߡF}or7A>Nado'Օ<|=!qŒT}-4|z,l\ i8E%HF !|% zbP,c"-Xx&0 ˀb)!Glc!V=ஏ~/"VϖџO~73-['/v&{88wUd?=:i:up^ߡn3O TAH7Tڠ0'R(]%gyy+@>h!F]& gя8Ghݜ՛Q8|)V?`Bc+8OM`j9@NP`}^^1&ICjT!TB2 O`"p&X,.H L]!~p+5V%a2@V\ΗkF{CV!% ,OZ2w418YyFYAJ1s4mi E*H`AfKm)!nAM0P7! I&OLȷ,tle$5̄,V"L*J3I< S\ d3 vkW]tMYlL ^b>lܘuЦl)tMpCU^ğWiڡ*x]b(Ɣ(ܑ)ORb_=`h1s_J04PJUS&[Y@Wы85mJ$ej$6 JB^G%ȋc})Qs8y_}uv|q^fh_zpxuJEy+4P.:a> ` Z u?F.@TdS _[GGL`d@ ( J&au̧[562o O9,Djɹp4\O?9:z__/hMuka4ou_}rBPbLPӉp:'P *R"3[a똄&`ܠu MjwZ6trݗ_~[dT\OQt#vr~]a+Wjiq@zЯ1Z^1 /ΌXL*`Px8[go/0^AG;|Y`W7_ywNs* C.[C8JQG9x5 HHk>@~I$ȑxD$YLwt=b#5~|0'Aj0|{LwaVKP*B!y>݌u*jRY{+2荧S>N_(~iO)T2柸l ,>Jb+ycޡSPb"A(.ICWS+|\l}AŏƣvAڈQa9}c8truGH S=Up\P`7w#+%:Cn|LZ'{vџ:)@8 8M L6fZP ֒ [ r[oh0Ev͈7y-lT7SDr#M`JC2qH4Nѻum٩qm !ț`kuGl.` )Y:N3:I i6eOrNJGZDl| R Eۓܓ *bqFva=5Dfk/=吚2_N[B&2U^ūAήTYUEݔVm[Zz*T5TG+Uz-c@br~Tu/PoMb_VUGiA'rzըOI!ٸuѴa@{ikqESVeTӖ/xFaܥ Zi3MAӜ>~6O DjrUFlo|-)dYȦ ]Am_1 E-Nd&-ʥ6 Z rDB mjY,4) M8ߤ1p:{)ɬ[j=gN LBN?F-E \tصwȢG9Npt~O$2wZU`T-7 JN A`9"QPPs 3p^}q6ݣdۄ(E` ??)$*8AT!iza xH#j5ч!A ༿ZHsgG;5 GD+A IJ "zI<yhou63f`hM$-~1qtwjc@$,gfl~Qo,NG2"' ҡ oJu Dר:7Hz gaF-I)c %4C92djF";2As5ET|Q|_֓z>̔JK-\Ζs$\|{{u?\\(e< rz.lff fCd"D!R>&. >dD|{{~wL0.GB! 4A)!An?/``~pe1)HAa[ɡɊ)Na yNuQP E~dF۳ae40Td%)=RNfrJA'"\R$vmVGc[S5C ʠ>"tI9s =mQ IxEB=Ε~SW'7A&z M3 ;Fh!^Gt!{%Sf~& Ѩ@KV6$0I̩m&I(_s3t 0$&RN^,  Əht%y'JBO 60ĠMV6az"lDb&Qdh1 ' V"بe\0nS#;s5txyƞ؀z`Oa_SՍ؄/}`ܠrFsWZ6JB&JX6Ț iN \RpǤ]$.*NL}RS5KaQD`gˈd[er1 flOR;{W:;# Ljp"p}`2\)ʓ161Zh2zȽ;;U 7۩; GF|3lN^==ݥJܘ b\TJJ)c꙯4q)\o !/2hG`FqPv;4f gљ#bm W:^ &A1ԝM-\؈N:]4\D l$|:*mRw\4O!JOBJSC< T!Iԣ<,D n! 5V4U|DH0[ MJAA)cjRJj*֗ JN"Zv PIFMkj"f׉G2$l,ņlUNm3ھL)Iu$LXVyɷTv39j!nDyu@SڛEj̓ja賤8-zKSuoZhlYI;oR!;v[#E[M:G+_O62ҭnmRKK*Kc4:9}%jp$%aѝL@Ip4V |v$>9qL_>~24qVTH= vB"RBB=X'$‍ fD ;#R2Sa?O6ދϐ5ɑM,Y)r$aDV#"$\bT5Xl%̀kLX|/7k$F@U vvM\j2ƨ%, U" <#zxx#28TZfˬXF6+nCdqWpi>+;$oXAx,)V75ZԛZ&}Ifj/2~޾^{ꓝ>S{jp?\{G{ǝd=43PYTW~h5n),2*3.ɋ8֦? C\_ꫯ/F!zԄea@ʕbF+8 o|a5 GA}jtE=1РwauWZU(" /vw:Zer# "` &c(k4~0ÛBZ:{D3Wg?Lp5zKʡ"f"4 F Hv)R`40F~$F0>܌7ԆIN%$;OFZ~괻V-%Ҕ4& B}%}cvnٚG^q]}7ܜ}ɈH~^! Vg%J79(-RC/ab[V ahWWd^5 <U̚6'p!5"+ELT޿ѡ,*Dv ֺ[O:o{cp1ijkuC6D Tfס(Ey!߷EHvL;o#t."O8\jmONJgM= |HV fMHj*`) . 90aT3l%Չ =vA$sا- H2XKYmLIn;hmNLҶ#<$0*^JC$սK־)fLH?ّ0=B'?o )t5(BoE{K6ZL$*EIFj7f.[g&Zm^551j ` P^vcYwk]XPCWĖ#ݐ!=ȑޞ%1^$~mC1/RW >9l'*+8bњ9ç aI:3~ RR%)QAGlCyBcsiP!ZV -*fG4:Nd %+PPJM;(Q0m5D* 8gDuJ P]F.ϛ"!Cq3 DJ}yl- ]닽4ŬIƽEsQGrjRC%AmC ĥ4m0**e-𑆽 EYʹEΉâ Ž,i"Bm@|+.%Qla%%7s#by6+56_ʝ]|e1T mR R]@A^a= [xN$FȨl"2Gp?ikŪ"OpeB^ƊFFT5@YuX;#I$E-kB ᨸP׎v+@x{{{uuߓ> 9Bb0Ճ~P.3),+ѐ& 3!yeU({ÿ?6b;nWWL:@c}c32[p߾y{vq;D K Y$Z+оٴ2B/g%#jIJ&bT:>}C FEd !#"%hodR@ p0Rh}}O_={ emg̾8lǃi]smOs]wSE7:¯V[.`sL? '/_^R˓(!Oss`F/?}mT! +W&{ A:Y({o0g9Z-گ~ ֋.L%2(R]ֹz4|'@pAVFb{@*iPQ;FZ|/Rk76YNL.өHX!t-''CN81Gc9IO8%'y, [-wa= ׃m"mo#ֶ݉")AclŎ$d3fB#cq`fUDvo~ߺ?h/}xijnMSP%DqNtti<5lsE;TL7n-w`Km@ ::6j-A"]krui<0{iI92h#[4P˟ȱn[NU-a!&d^՚<." V{ QaD T.Bd:$x/P϶A xUSйO$M3HhP|x رܩ'B($P_'1] fªҴ:e仇?÷HdHqTBf,vj(}bRuB2Z!954a*;TDT)2d )1+j=ihW+ZU <7wHf)Gʄ'1UJRMT(p0__ǯ7#P ,MrBppi˰D<;Gc1jZ8w)~:v} EM D𦽳 h$ԛzLHaW``< @SP4uUil C GȀIЅgb )CjwDyd޹u#!gBYLX[\eM(u6fOKu'Ļ^oLGNmmN[5vg_yU$o>jЮ"Ö覸FƕHuɑ)e``(X5IE_hkn.Ux;CñG;@vH䕶6īYtPjcچu+â=L-6,}<6K h:t\')|X[ЩwȀD ҶFcS}jN$92P7i: <טW-q)–#)ܨAm89u~7^%he0aе#o%ah43>ȳ(򓞢EI!n_ 9 ڣ&#gd a Ҧ2+4}h[GC4/uWȒOG1C=yB򞈣Hoj;lp둾QI.NR/AksG*eKng7N5ƒUQh*UԄE;2;ǡSٍ?FWPSu8\B(;B i(udA<@/bP %yyƬT29FAi|^|ĆV-â˞iSE;d1=Z!!vݞՅyU~k4!I$H+ETw9{/ml>ij tYa!TsI2*MY- GHNESd*d:MzCD#]P)p\_8XBNgoɳ'0+!8xfV;H $#}S[#s[}[3EJ2yBDd+0u%:D* xҗ7g777ZR\qwr9q=]!/f|5^܍ή/`l )ꎲ%"7G"dղuKAv:udD2j=FSd1ilfWF$"EغCxѽaXBf7Jlj ,UɁH,9dP:o..D< \<HZ0ai;ZFE>?ٰT*sľj oQ@Z$2^dY&3|~WwWIJ5SjZ5(IPF :2!N&b_MN?;>0P1.RKPѦ<`y 59;]sDXIH9CF]Hg]:[fFpz {8>yH$HL\)j`[6r)h AjLF:d2;Na#Tk=rV"0 𵨡'fH,Om6ȌY5HC!%$O)aڐ\KT~`A.t纻]%8 DhGg'UU3 +w*)TpyEΥ Ih١!.h#M4qѦ0-z*SU?&ȥ;fTnGWJXO Ii m uӟqaI3&Y|k}sylER R͋'bPבr"z/H{@Yo41ud[Uf@m G45T<%XLAJvF9sl2a^$yQtɸ9)ݎ! bR'Sp`']:r?'9G+`؄ V4EJ|ϏF_+`kPi*(;pu5ҘʕKs h6j /)FxCoSW5z2,7XjIے&`' S?I_9Dvúu%'XT xƬr#sMzL@wkQb)qԶ&iҟ'Q#C#6[)(Afguc^hYaMk`3Mζ;[ќ~I)8am;l]Cgoj o u6UJpI-1=PGKA`,38$S-!xFū$1vI_5f50~1?DpKm ٜe<7P,JغT. 5&L4=h&΀tPV)m!+4TiŒi79l "3štwD~8<>=>>]zhZjѐ\v7+k%Wq3VKAK5 “TA5 'u{,<1zw}~d9fHjV&˅r![uq0Z^ގޜ_ݢp1)YjP­4f1ӭn$u|%j C|I02B[5?Ś'ZP%B5NZd먥ر3GjdF9.o{_7 ' Fl~2#, yП'(~Έ B ؎`hDot;,AR8?aY~~>g|fUPu(1vXpj\x . q' ]Š\rRB݆.e%a=BƏp Y"nWlg v\I,N IjH=E΅6)#E I0Enat7lh&!;^ӣfZdBc s:Wu{zz ⌑1!~('IQWxYihPoQõӪ@"͐b o`G^l+Hl"R}jE}R,:nkGIER-'pwF?*!4N=bOWw*E1S .TNdqQ5=?N:*f̑f` Nt½BX}) l_+n8$i)Һm.$B裼0<-/aO.FE|AܑŎhчu2f$ji k*ggW. 6(#KG.6W0&=|N~A{Wih nd$cy{(K."iڰų8%&#ҏF_:䔤Arr١uE?X{LjbpomRXOdHZ)/T i 7TQ0V 6c>Lf]GhmBd6_y1 }bx= oERgQfn2ڼ0I`QѠ♙[Q6W' ]2iCRpڭdDwVDifNaD^gr Qfe6!vQ-P QyJYH$,4%?46q/ߝ/~'/N^[vԬ$:orO~Ȕ׻A!v;үj-ti<Y@9|4 U MѤ "!9-go/no!"R*zyg택b D qc+MUf^KW˯xwvO)9*;x {ߩdvjveg z  QyAfbd [k-.0F +uQ*2]4?n#r:ppw}{}uywu} /s0#e,7w>fLhި4[P9`{@3Ԙ5A؃$L!./N/Qx{t- `>RpUKHYo!jY@PΕ5i)RH5Yr<ɩ$ CvR2j:7gAn4Q ()×a0Zpq+ËB~Qyvf Vs@T@Upz W0h?;ocQ%:@2#= )`0g $x@P\@9:pT.ØVW:j&RZbzJ [:bXli^WhxOhNʜ Un=zL"MsTIeFE셌""g.h)>}ɔ|0"ģAJPZ<7ɭOR}[62A]ij>FޕCaˁX(.WE +0dܱFneo E5Cȓ 6&84c3HķavgjUd#CU]~0k qgLz4# Q7f2 m|`L|-! $dTZO.0,%6{%缐pj@l@~Mr9ym׳5 w=Dy#t[*C+P,P^1ۮ[* ?3@m[#*SHbc(V$#Zڠb/xh6"te%rbuCa*T8< 7Ia͆wӿ=~M? ׷6sw=P40pPY)YGh.e=9)UUd7LɯSd'p0 4R~Y\$BԤ.fgHq{ n[)Eoz<pY-RdjG*(6sdD , Y0. 'XAOQHu9ul/I\k4v'T?<[FFbs#pbjOP_e[$e"EC$LL :aӂӈf ( BȜޏP\BZV $62q'D$0 D1@`R: Ӂ ft{}]-ڕbNvs%,$i8SڥVU7%"gB_bQM[P?WCJ=O;KL ;ڝ2* LUkjJo{ـAbRnpY*qa;[yG@; ~Qr!m%fV d ]$lǬ1c7ufk|S `.=)ǠvdXNsI͍D:P48c? <(Lbַݒd{o7%`-E XHBJg9'?e)`iv#vw2XuF[pLlLh5@(}ZElI)DɷхA%XClhJ,/x?e ?:k,|☌\Z$|jȐPG4?EIu$hPO,Հ%_h"N 8{aeOJxH4(% /ʄ d>n aMV/Ɣ*yQ]L wCp 2 tmtb誛jgqZX)lda(c)O0LC $ 6 Ƥo$ ʉ<$HieS5[C-"hmʘG7wV|rf;|QOytAH&LW̼8r/5(XppB0@F ۯS)WzB'\zWCsh#N f'RYǠD)A5.'(Xe;^Ix[,=WDb1koOP(KȚo_ #"?2II#]e,O~:.!kk)e`>Y2s-IM&q;BƼg°6:ie4]P)w;HWV0R)XFa6UfWoo{gW7Pq6ŠH(y7Jri{~mwfH}F! =)ᑥ'B u>::FD6bvtwps\^_7WWs$'LcXS(U%ºV08Fe(Y#(^L:*{n +C`5ʃPD]/zggg_}pp/E $޾}]Vy̅P5#bf\ZO)(_Jϵr {|9 T 6;Dԧb+" 5pi"Sf(րl"faj@Dh-VԚbW5^oWAxQ,*Aw6';^>߯w[йxBm0*E7rn<[6NxAuq4S@4["ΪʮVm֙9Ss]Ԋz[Ĭ@ vdc fd{ &$bQT cL“HˮKcB-j9iϬj *8[s.b.NF W'r[Ť44+D(NX!Jx6Z~+D _; .ژyO"m HI%~o*Pv\}r,{+QkaѢ{4iB~`Om`}4eNÑ&|U<_+ǔ2N0[c͖qoO BE;&uۊR<5ml!ⱤX[Rd: lʌi u{ffqKn,pxdFs9yg [@gm۞5v/ ,ec[Bf0ҳ'qK6pZX%hj~ӌo|Bm%ԯ x~|3/hڗc&ZPٟsQ'EAK}|/u-UFWiE1{3y 8M`7!@k0o/O@ȖK x{R##rb&&8S=[{YmEh3tA`:]@"IFCX0߮>>ˆMp=fX4A0cv۩+$ƇpJtCAYa" 0oG;ڋk/iDnRF \2 A(||]6w}:%^.%yqkmL[vo iHTο`-7=Si|w 2UR6@q!KkT Pw-fdNThZ <-m9#rO|;D|h%,SD# ~ x0jon2 H<ᇠ &X`Rd j>iwv.Ip<4XGv D[焥`O(d4q/, ~ph= ;(XBve=Zt4y+W G?O_<n]*|o|k6^{w|iHrprnnnfW_|yqy 7F,je ^¦Rr^,7ْVP奈a?AZ؄ǁ|04 Պ Ȝ bӀdC &*y/ Z^%P ½&$1E;fB_N>ߩW_|uwWBRCu\4Z"Sdn}K=kp:pA%}aJH1fϳojɱK&6![ -Z?dDZlf ZYb2hft__#(4nSW#%UڶFS+6~2JHl5>PNDWЫQ cH!#td kCj+CEt%;$Y[Mt9*Fh^xi+-4tzE7NDS޾ !^`B(WX8S69S#r WPmmqc:q5k,KH8JZL%ֺQ8WA~Pso" \H 5L0Ҙ0\f)rQfWIЗXp* 0Bm9ƹ1^oM hGV}w{p߄%4̅4[x@Gf/9q5o#ڃHp,0,gD"52'*Xb*fnoS{d3 I|4&5%<6@is)f}Oah|Bm /4]ӆV?k``hw~s~%9#!H= @C:'w)i-kT< 4[nF\Q 1GJ!([nۆ# nZBPuhӲJeCeA$붞JjY.@ib55ulu:ȓ S{hJqCZYql=MUOb㑪8(Rb8 NիNw)Ki@Gl Gl\Ft(HC5]ϥuKNm;ۍ  j <RA+dp09^nzu~ ޛo.N=-qS-wZ;mP:V)JfVKjA."U^2+fxk(!k^Vz8'/y[W*("gc4vZZnxP*lq#vw= =b-hg*mF2-Z|Y*dJ|}j$!'(@!Tb)W-Xi ;* Ana ~P̥$Lfp%>)%P#I8J 7K6N \|SlNզT )=;>8yu>+jb @MG`Lb&<)wN u@ ɟN]̫ {Nuՙ!L+5 # xpj791`>AT$ IQ9.hd2\"RI> p>9˺8,%7"s!(&@/LšQ)AVRg@&b|ڈJ6ܤQM[F ZH_E֌v^o0ʴJLB|P9\&S4O a@FW6|\Sf+I/7ݢOZԊ_?>6_] X鉟p:ļ4a5*kyz`F ^Gm=9HU| 5jJсgpk ׌#);ٚF~<sRJ6|^Ndr31DM0o Tt"YT3^5G?zJ]!1Yo7pbNSl04.xOlQ64p6ZBIΗVcJ\5} {H R,dx(4=0`Jg Yl9xEIM>uẌRzSD&.Fw.J'"J E#ak2)5HcI3V[M4dFrKDŚ}N$~p(oQ?4dm2l1CvBMcU6 |+021(NXNhNG؟&%RƄ#m #C5i>aP 6 ͋g I"f'sW63*2{Ex4(z $|ϑ8 [10BfVr^$EZm"<{îqȶŦٚt2>BJp.u\,[(HrkfhDzr -YtqިBb}!l$ܛ+,tڏh ?i@(wpq\ FC]W⠐OFde /;;]NUtɁR&T o<Z`j)*! zǧGǻJP3K.>9uU n{`si*,FϒFu!! f[h} zDH jO#ffru-sP[=a8yMFV#5DQBs>K, (vpЪan`!BU.P:Ŕ  GՈM@ GMdB]^l.B6zpzbo.zwO_<q$eKZ| xPtvPWp:{v) ƣ!2 "f,PgK$eA F2% ô C6*X@gjy I@*i/M9b%܀H(TR9$xF-Eb@sh4XY6uAA\iTOҀJf  !KX6 ףJv T?٣nnvږ C/%q4F8d< 44qA Q2|% T&"+ 9ҜQd4F & iyyPH(Rl]I$\}Ы}℠\'=nbFGqs֯vlеAq"U=q5@RȐIh}Q`tլyӧ1uoR U"S-6(t`#-p' !;ɰR˼7!{)d)mC׀0b+OMlF sxd ʇf_Dm$-=qrW[7ROAL FW*&yl-A*X`-xzVu3j?$5b*6HH1 '=?^ QřKDŽ3ȜkkFZb%HJ0}7 0_S'&Avcr[kC#>M*=4rtQ98ds bhH,󇄍wdJf @G3uJ$yt]|LM#e[[{~zAT8Q뎞quD'bR&ި߫L"+W6l`nġ=:)AI~1t97L׸CPyºx ^=HDX lC*[ҫFSbH1!b9'7 B7!D*)f2̺dNr:ܫH e&*m"g2(eīV %"S׽q7X>#qއ[~ @L5 :͓aqmwݥV)l,i4a(DĄ9JbrKGD̯z7 J2Jng۳ϏOayVJ@pd@G1Z=/B *w:uحv݃C@6Lǟ|!E$R&({̳"-5ĄCu΃4%M#5pʨX5z Q$夤(€UƑY6:`:@+A5gKh("7DHGXtր$+|+F^ yY*hb6XI) 1d{cLI# Q<89FueI!V=`RK{ vTtՊ':?U&(2-ZԾW{8-ځ] 8-qm" ŠCMv?M )'kX`tv*`d(1莴ЋfV5~_ Ƣ̧'O ;Ixa̚@Hvʅ NUe3Gpھr %HDtn8J Onҏ$eoJBIfG)~런܌mfR&6h8#FM`7QTc1b 靡Q59h #EҞ#Z<Rs#@Jj9wkR"j f IkҐMv\9ۺӇ m@zl#_Xs{H=xt)@#+5: S\h`2`x\1b$1Ť(`N{il˧܋8l]T  ZF.v܌~Cg7RŃM~~֜' Lj1h|5 D#$mx/5yc QČiYT#oq&;e[ӴORv4z% P۪oVPnP7%c 2h vC9;ׅx&?&[dm ;$(}(/wR)UPDw-@..3|F Ԗf}E%MPۂNgu, 'y>A@{-)SOx4xUp(d~X(DRR6:C@ %ب(Y('ΰ2&B7G>, AAxFM=0v٬|NZ.:x* ZA7onPcb1I =X %t2kfy6Zŝn\E#PqI/q.*_D"l ןCX"G$|nKI1Ip`N z92Qe!J[,!b3`nIP~@<5fw5^pų_7߼W>iAS`?@d`V5^O!gB2VDY9DQzB(ǵzԩ5`9@G=* #:ZƩF(N) xLAo8؂[$˅ KR۬g;0HӸ~3bUZR Pa)!e2tG;Az 8͈Ae\Ԙ@9FMVJV`ŤXLZm`)T=hir>l 04 QEw2t"*&umP{4hף Uĝ!8aٰ,4vOV2=$Oh`к&e &A/X)ȑۡhЏ4Lmaq ƨ=DSnBE\-l l̩6#x|_6o|TlLZ-je"3!ȌĪټbmx\x<`rtR2LF!&uVYƈPOg#c:Ht{\ a:Mq/>8i܅b$Ty,=**92~13 6?EbjErhQUDV7=Lj怃J+c'6L%n 4ӓ̨sPICRpa#jV8Gs&u"(x̄#_Ge:=)&6gԷH U%XQ$Kā9:yO0"k?lEwC2?wN]rf ^ASJP#5S_#d[65N2r`ᐤRKGXJgY>99 , r*$"WbUKP%`^9 C|%EJEq/,‰I$|\ˊ`}{sݻ$ݙu9-VʡN,"!PeIr ]΋xht  h7+݃\Bl9Ϟ덛/>澏@O^gu vK $l H_zF̱]o;G.0P3WDЅQ:U>R| =N1J pr )^Qa0Đ%5%Fh PLJÛ7ggg ] 9ؒNѫÃr.723!&]p ̈R{ !pJGh :;w*Kd B70]]Ҫv$ Ck4U^U0񌐬bB5%Pn"0HMM `_sJJ0l_.Kɯ pU[J8|O6Cb҈ ؊P>벋"ªJ).5M%>%?فB;ijhfP&wݶ:&nJeJi[S&ߕT́I /Q5W-pv[&Ta@Cc.ʝُoSGs3wEY|FJ|("F ] I)Axdk[F=Ag qm":bht.HPJ 1p襖@gZVy3:@.6)6'GL*/xUH l0"S8ҙE&HnDh:ֶY<}tg'}sa1 {uɖ)Ѣ ؍XߺGRvp:!JlۍRR x kbgb PWSDF((EUNeHIZ؂}N~:Yݏ/C (^>T*ο2?B,%_smQ 5$IT |3%=҄I-P"8 pd 3Ar%5╡o$[] ASabAs p[AB`&|-1$.욽OIt]dn|%wf ȗKоٸmhp4Mi+AXScvx0(cD к)\ 6tWr)Ӈ]\ _tSv&Fp D!>.adL=uoo'HK lq_rckXJ.@mIHaƉ6):JI'3HSGwل4ESc|@ SzK*G4 DV6 /7|X"5Y=qW\O$#1G=I\qq2$'0v >2y~iv{0[qFjJ$Urxum:3 0mlX|,#\\rp*&hO򐒆P0#82KgEA?, /ɒM8HG՞hocṚ7WЧDԅ8c{r^|"HKz|P^*v/B% Ls(ذ+B8^L=Hf(`\xVLN;՗.m&^t>G؀/ѦSQB߁8/ } ڡJ}9f))|k#ehsF?WA9"Is x.H/ŶF2r=PM1gr#.{ xh(PDB/ ׈mGTTD):v$ )|5(w=4mǸ| TeK2f`BIK3p@' $*-szXV(`>zbe-v0IMIDyTjӣW/vPšөV_{zf,pL 9 q#ڠ8fHQ cD̋C A)qwe~p0g0t;_˳ןvӏPw `qS41H]x߀HY |\#GVjn}xpӅ 28}d%DàM[h,B\ cI|޻]$l.h`B@:#RT )ptvy^+7w &Wel[BnTnARB2&꜖ #YΖ7g|&;dX3zbZǒ 0*Y C(:7Y^Ea<"nBm07bg+;] Lq z`ʃDolU 1 $–L'#P1A- Z}"T\wZì5^@GvRSE- &e"%d3 %EWףCu9j5rS<(GUȰ7 Fm!*.)`X03%BREJRhB`U&ԆI]tO~U[DЅx n6]h%[Lu";ey Bi_L 1;di4;GXLY#W°SjDO̘VbKf*ir rȽ{nLT8R !l#Hч^=;4MhUaK] ܾ ;Bgnч_!` BEc% Gԙ *]GQlUxSr:lǒl1jc7lk ~S#I= cx(N}jn0!P<)H%d4 8#TLcؚTtF}빘LpQbN‘\L?9RƠ hHfz7rb?ѝ-xO~a5FO K9cLֶق.ῧ\`8p,"&cm6謴c"bk SD$+ KD %ӇL5f=Gw-`&9 ~Pmc>M-6H8K:WIvO+Rńd&e*aO<l`Җr$ŒѰi^iOri{Z7}oD tG/AasFvϩń;PtAOrchٞF@ڄRެnL#R7ڌ5VpړDxIGs#FHx ^<^63#g8$u# %S+niwe(a<r-u(5\}B 'SI06-x—ŏ P8/xO%ttot:NryekxۣrrTFCH=_87@Wj F9+ٰeD6$J&H=-~|V8]Vhq&B WX@h*0q.A5w6Ew֗/a[D4v{} A$6l 3~-mZ) ڸ_'1w `pNdA2@QF[- (@ M@gûZQ2CG;RU@ߘMC(иZ~B3'-DBXb%g) 8؅|}|#AC$//)A1ٹJ ΰ7 Obq}4{zF%J=Rk <.//߽;áW/xJTӁ JJ5ps]]Q*`aϏk9I׏.KB!EZ1LǓd8A?5*G]$y<9>: K5ۗs"FPÄlx5Xo*RXZj1G&HM3H 7Xf)AGl2OySf`W&a*"hp4Lh Y}q +`dob/0ݥ AV/9iG8n#.ԫ HTwwcg54D5a(\0QD 6M֧榋>O j?VEb<zg*lSU4`h2-X!h_JgR9q;݇RP+ N%&@wozV@!3uLdnvݻs3r@>< T앃3j**t2M3ɕeThwz";G0ޔ*Pྰ{, >tWn6 CNN`;@{1tVx}.8@?,qqq2]TܑIU*@4XBaE3a@=qq`elnuOϧKU/̢b{kC(97h4*;,},XX0B ȫt}Wn}h PL 6`ڪ#iJ P1t# /#8kF/!slYJ\θ93XƪuZbT3]Q`xafciY)#O'gD /z;-D[W+R(f ʎ%ӳP-UR x1zat ;YH5n|_lR >GUpgksmFu" UaB/S!tdlw>rw{HG)P+"=d\!Q`XQA ~ `FR#JAʈ y lHƒ3gTWe =my b q>--Ü&xI-2EY}606,ׁmaYt;K; Ɉj۞>X0MO;vds-/{o6)hɽ:H|c= h ?IĶ("Y kmh)@h Ⓛ,c[&mrJJ;V\Hi*P],%f3p l9}֭]M^T'iNpp&Qc57ƉA#ȵ\n(ű=c׶D2Ի1`8mDdrgYJ5a9,n*3s g:- .<6xIsvIQI'!Br#ќ}JvテkfC}%3 J!a+f7WF"?a^1\97k@[Z4./uRC֠[ Kn9GNC ;Ex4uL˾35;_v rRX,>8[Q=ب޺g]]Z#J1HKa/gG #ߖƑCָ3B< @ڊۡC0r& -y90gG%at,łQcjP#Gcx!PaB{bzW"(zڷt`4Y,t(`W(暳 gRUg?ΰ5Ac 2:~sb~Ź2ƆV++N-g ȪZ/CƎ۷WT|c5e׃bNk3UFF_(6QWbvj~eazn)'M ҐY`Y;UX;Nq?MN&jj8 OR@Ev[r܋/Z-WD%,ߔkșY. 2Tyī}!0I![Nx" gFILGYzˆXPn("%TXyBLP083AՀ\bZaR\a1)97Y!5 .pތG~;9:)@ 0~@!GD ^wd5gff+Q9A+XeV҃$sX ٫*) eHD;;i6<0h3V*Q,xl w |#8nuP(VťeJNPa˕Н!Q?1sqZ#f!+_IY WzylGEھ`7j3].cfj9zPl`%&&WKN !Ez|3V72(fN' mo q$ޏ7胰$?$8+uO+bߧՕ q__|J |rlzj#v(g8ðfoC$gEEx$~|#$7܏4hڏ$0@;$ +k&;22;?iS6 )En[)vBOٓ^_¯F"4`n@Qh @Cqߚl- A"O&x[ކ& E"Iz76P"d.C̸J 4dfuz:# ?ө9`Pti)—&I\ڑhqg[z)]Fs}e|}ɐ=XԥB؉br~dq'sz/S{?ple-ǟaݛٲyiakNI e MͰE+d+q]t"ɪX2%<}Gi%Z_rԚo׺C Nzr'FRCFbF08|>&$90p̗S ̛NgUBFV2u7spd[Lɘ" J2qpk.,!D"|˿=0KAfxR+pYDKLfhoV0E,vW]72#?Nπ'aŁ*ȋwԡCB .:tp`s`08XBihK 2(773Yű8O85`Fyh@ǛՅ[Wo-L7"FΏetH̛\e`& ~&kC Q}tuh`H`A,ЯlYi[ Q<)v:]X2"%y8;llф>"JXN~1dCgKA N5gnݹOP~n#`(<'X}(tb(9R%,0.Rye6Q>$ s)[I $uT%NR6-hG#w$8M܈##6!u!YL#U<#3ya8%F=5SCjDB3w5 ]!<&+O{D|i:a @ "I}1f+#,I6b@9:8ޯW!LP*LL"-A:|89>^ݿ@k$S6ZYjo`Y,"M7@_7{nY^l.A%<}r!r[...ܹ{kjfˢ4#q(Ǩx ]aZJP FИO50xT.IbgP| 2űS8%پ-25<3woqzYא<6QFgю@ړh~Z Wnw:AF+Ύ;;`dZR h:*)|rFK /bg2_u;Sq?/ D 5KcUO֨L⸨ACIt, nRD>(6)?XXhJD Fc& D{-5L<7&JĄ(,QN6&'PV+)8y$[!k@PU2){qPJ4r^ܐI!s?~g"h$p+kR9<VDRJ^^\[{QZÃCx#>9@K,q@R:T6Ӌvxg{}83T(56`d10HwN6J*zfu;WWfkSS0('|F{h` V\X0{d%_iB)Ka;$]-c6#M*ö%}@<K)-.7AK2vWp^N@b*nmsa H!d1Ƈߒ}K'$lK#N0P"m& ؾ!}ةÎx`8ؘQI$a0i+ Ue0J mJ@(r5IZKo5중(;*P~$meo~EYnEC}aR|WQn71q9Mb[, AjՍb!D5g}q&M ?[j;܅?Edr||INÔAdtb1D7.eYvpd*-/w7 @Sh!%fuڻAy"&0gWJR*6D6 kFU⭲`8>FVh=O1[$7FO~l%OXYerf Cyz' Gq&i%J7QqʁBǿ>U_m!w2sR '֖ . +ˤͭqmX@roh2>+°̃Pb͡kG |cEһ\'=\ tǭ!։eZ 4%™ǗH)) #6CNS 8hޤ1;@D2[J}:60s8: ɘK ._-Yݫxiۍ3]о%x#]Sxz|=8 I᭥hjǦ 8D(Cx14zs>ΫU,z==><[>BYZ0_kԕQP <٦IR?NgwwզgI̛7RWW&c?PJ6("1h`sck}}9o/-޹w1۬4j(*]&M"Ҕ("QqfhI1P-5q ?e!.l d؋ GkGrɅ~ frea0[ r#aEor;`~ #XG1ҥE bDt.y>j _'NsizH_5E&lZAddӃf@2̓}^DY,Ϡ#fOE=bpz v Hv 41-'H."P͖+E%FG?}d *?2I6A1GHox|cC~{xmmd8:EDW`b(hscccf(Qlob~ƅ} 0[wܹ|{9?t 6IPRrN`䋰6h59 $$z*o/ CX+c~6 /4TLe`y7.rb#9cmF&W}v ,aGbK*o׻>k$„g앍xHD3hxFA5Lɀr"QfZM5iU[Q,>HrS˲L ||A#lI'BG E O:.ODq^w} ?SHhyhsܝk)ĕj-*p 5[o%>b#Ҕ5g8Xʉ$ ~Wf 2D!ɴ$;lb^uMD 4I6!|rG֫6 %ǫ)yrt %^]I&{_:Eg{zC$h:0;btonL3T ɛmzڐaa@"R7͜|a}]Q-'ͭ!fgxVFDѡ1{Q< pr6`pЁb4n]Eܠl.]a|"Fzy;h[xz)cfdBӗ,aEs3ewdow8R,bd'9nZ5QI^nodck4W"M@BmH"]PX,(XhpN_tscpn`tzzfa~LG.NyOng8o=9]9hnmom=}?xIZwc (: 8T*8*!wpxUsPL-MCG밽x&\H[3ν qEw n߻}:@C伀}& Dӕ1J uwI52!#a7ՠ.Fc8O&`58ۍ⳼NCƠ6cf6!b7@4uvzjq&COйS'FP ̣G3&HFO:8Gم{=טla@k3V[֢ )68N‡ 2"ӀK84pF,+AKd2DKGeHB !;[H2 ! lKL aN!H1D&C \_Q2ZB2l#QX wimo 睽Vk0z'Výݑz8CTNBN1p_Nz{b8:8la?ff=矛_Y k AÔ\ @wC 2AR wbzINeR0f`+sR7U$㶱 ̎eV˥i4D2դ I m{;6CmCF|<=,.6/L=hw LބD2qgrYtϰf{X68Hk@qKȽ;4v7!J9O{I)s"dT22a™!nNȌ 4m,o/M"}b6C5yspBtR ȂHu 4 H.f17 ec]q׵v%..-ߘE\5C]1R*Eqk`skBr%6*\T1=UAҿ1DR w-D/u]T)evPp>|ܡ^ 8F  _)(aӿ9i][nh󠵱u }cc飝'G[V12:;V-U|N %q\{޻٥y9ϟQ $0@VonlwLbJOW€ʨ(PFKpHZEÆR }?M R`uuX. _Q,)v \?:Z}gebjEKEl1QT T8=[Xu,d=>re~ Tqzo,eLQ G]l1.m(M*FY!J@HPf ~2O6#@DRD qzi<[p9h7Q%6$bJA A .ri~ iܻrBfgfwf~n~9ӜD͍2&0x9PE >8kgwcV!ꂢ8G\BEYˌwaVcD P9{omTmTf@Ƚ =pA`n-#Zd'Ѩ/EŐ d;l<,<~˅&wc:aة}cD5Kˠ)U|L+pl+\rŐw 1?SH]~#e;IͿ l;heYAs ~+~m7&F/6T-+wPu_WaNQ9KJ 9fH60]68`TmG:Y61k=!fF3"k#Mܧben(.\MF.!w[lpbИȚEf2h˓d<#.#F\M~W5 ;<@\p#WG#DhfJ Wn>IѠ`I YűomfՉ_j\)j5i3]vYǑr[+y&yYh+Gݟqp١ fZvA_;<glsAZ wk%tلldQ4a6sv3̆gfk/zSr2N =7%yjGa@U46e{E 9őhǀCq1I79|ݖ54'Fm$5g 6kdE|Hitp}!^#))t> 0Hq$CG#vݜ-=Y;:om!89U07ׄZ;!4QTQqO|N{g]1tQ U =%UihhupNvs:=cm}ӧ;O`l=j[vsAjo&0d>ۘ.K~oc! _^|入%z k,yFL6˰Lxf˔NEfN@uǽ@a~kV c8Fť入x@K=c%RXb 0Uʀd)Iq\dJ Edj-n.z';@[UJ*8ai3:X,Ɂ NlN/ƪ[s͙Uh0> oמcBMdl71@س}!kdٽN80lw+Ġ8ro#l.,a0DÃA͚gCޔ;dK_FOT0ЄÔ/u|:2vȅW,3- 'QHK2}^|P&A`[C,k$ѶcU$n~#-%|\,TG` ?ABQ` `Vl!kNA5^#u u|uX:5_}6"^J\Lr&Rb !H=ip@L̨fu?/Hts GF|8l~2."w8-pf\y0E QtcTQ$g*Y<ꜜdy>QAk bvv^3N/ 8Cn,d Gp0W26aPk33pY?9pϿ;^Dxi2HS?rWoq(8Q֪(q"!:uaafinnрzȶk਽Vk^o'摫VEA^{~ZDHbKVr~Ζi $7>~?.b/_R(ۧ' )"#<0\P @O疖34@x/>)īr.x GN6had ,tl&ņD#R&NO{Гdsjo dU?0' !%  SI*EF4UOIDuzg3R>i7[`_]K3 1M{2y,qrH5B{9,fgQBIB8JR֫U1z^pbKȠ*:SƘR1-ړ'08OKS2t;:0e>uT8_$[!Ӆfv~+k#']w_AE5Qay&NGcͶo0 [;8x #aå}%6DI./{L Ii&6Twb|I! ܄+aovx8ެd CH$"Bv$3 bA]S.Ǻ}u%HeaS'o8&@ץe}ġ$8`qsPi.sJ`$"kHN&2&rBOt 00 \V,BNB }9?FhW\J96qhW':0L|:+l bfKc(g䪓Z9]3aؚgp4k5u%"!ݽwk>>׃??$dL{|,ʖr7.=7gXrv/J SNfhAFgs$s=5^~Udw\ 8Hv ckbasCqeT/~߼GY"'ʳw] r;778cbgR$)AFx@p;wB!UsqQV>Kp, ͂g YO>y㑃ĮI;QJK> qlAiCtKsl$-ig__ L{e\mk |B @Im[wL>+lav;x̱Ŏ%2 r|]TjQ@Ȭ%;{0# a:e2 Qss}4Yܽ:73 @r Ï 4iY{@qg "HcЬaPUAhᓍcGϑp '|?:VE N9&P&pupu 3g>(G889om<܂r@G Ņٗ^J p#Y;#l kVDrF(Y*nשּׂ:l#|<8?xqG'KS(LQ*np3T(&2NvB*),gw ;=fjVI|r3gf58 <|ǧgWWyLN(pIu QYՒă7/3pX-PQ?ᓵ7lx/?׿q^~m/<7nP>J@s4K,M>"  =9l=!>1,@.`0<RE$xE[7U9!4fVL>#8 t  "c84b3yĈwaNSUQBo\oaDaJF`# oz\3IdgI>N}A{YdbXpj6^1lÞcihh'&&N'' ~\20pI0Lp N}n\nm.K@Dlvmr5&g!Hv[n,5`fP/Tѹt$ v= !yKK|2Ch3Fj˰f2\FmhlJ!ww\-hF"S3A4!b;ndlbJh1=s֐2;kg.|bXQ D%N܌ BvӢ%qu{yPj~[ C 5i7f,;-6#46ɩWϼTͮe@Chh4jwnBN܆dNjzΆA~0K:4dlըcƾK,8=vgLC{(2P/d[mfL+hq@LN4?Tгδ52׼Z-C 2Bw ޿'Ep2uk8܏-rRзb9TNOOŒusS~t_f 4cG"AGevF+( v;i:_‘ }}cy]Q^x x Bt5(*SÉٓПmcDJ-OWCӦ)?2B3UaӖZv)|'WkZPPiO6?dXC𶈋4ŭ=ݨ) XJijO![s>\+D sqހg 8N.  ٚ Nv6DA&fFPK!搟nYTQ#GIאovvjavj~)AS0ӄ 8w$J u=jpkKP*V''Qל}ٳŠp~Q/^aev,6[>|tL@VJř奩fgcdS t:܂6ۜ\hY'q XȔς/SQrW@TxP%d>Bjb<Dlk$`㣓㓉ZQ6Ft*G-:%kyrZ{ȭɥ &L55D \QQrvv1}x3%-oQ9=E J]yJaCir z<ئm1D6eqb4ՃZ3ra^0%( B[LaCL`#t4U-5  F"~5P AҠ߁6"0B3SrH̊QA? ~ղo-`xV޻{kn֭F}KJxPm,g</Ǥb\=f I42J=u-OWGW8LP0neyA.76) 5_ö=OsL>u&9 9+7cu$OQ•D3QmL֚雛A09p0HǮvT"k I ]Zaxp_%YlX]uk/w1rS=~a\Ӕӷ: B܅Q1"פ# Y EȄ$7ֶN s}kkc-y3䗮< K]Μ ؑuDH.] y@$Or}Ds2O4Q#ah)9qq'|EJaވr/T:gk{H[έefgF{kʯV.hSgS PJSԑ*y (e ;.! 6tqVPafnFd Ȁ U82f>òCbRk =Lp"j5֧Q{(iĈԪU,0Iڿi(AB=N 4A\ue ƦYAd@P4IDAT* u$(aggq`3P F5+5':tz4"#T6v0SjFi| k}rr1 BvQL Ad789|ez}daBjn! (MK aŮibHwf/>RR  X z"E$!k Z%{QUsQo>V釽5,G/ru[My³>CbVj?^whݸh7Vo\Q7R {CaDMo9qv&J+vnw[Nœ̵GbkM5|i-9RC#;^ 6çfwH4sMrzGazϣ&QB2$wrO(Y+PaetKՍ= a2i%+ wHoGfxFr } hy|qTnhG[DN$[º[MQZo*֨i)CvL J#SDB +-U߫dgL:%8cpg7Đm0JXu'-; 8S'tQRBJ8uGn8_2Fيwr>b1#fMF6]B`'W9Yȋ1 !0eJ )̶f@zy {Wp D|f:Dc$e?m@ ^[4_`Â)ېPb0@XgZ^DG7h vB Ge雛z=XD ]0<1)- k ]ǤHw: |)zU̺dCՅx^ԁ4XBq5vQC=]{BJVy)(Ȅ8՜DW(q,EjzX@.18[1lhZÈ {xAC N2Yt&"J͔c9fha+OY)33g ANQ!wtOE7H#lY2F[ǻ'^Q0b~i{#=#,eYdYz=+ `ie 0KQp ' 8>>Fr`D6QB ]p}qdK4 S! [\SDuR+(A8> csufSSW9UD Qn9UQK,Oa8Ah#\L7J27y1(⺶v3܋je$zs4L p@)'Eo-f0.lq!2+(:tpv,*;TB48ATgedE~S{} 4y'5G2(VdBaFOmQ eăR3fkC'P)&Lh^f|9L/L̹-"ala0q`j'+4 wW|vA^=g1:vE]4NԀ~m:8K_]͛Fm[d ⶟6l4FD"#oCóe {uMNR+=TX0 0MKa\mFc9hdJ!ۜg8҆Jc} %nI0{vV=< nZ./F 0xB kF2IyWĜ2U=q0&} ζGQ/dg[S8F=rhe{ paP: 4g+׿A.U'wn]܅[JsJGO`#8 `N@ -wlD&|snvR%(`mxsyέ[wL6iPT<$  ([:0  AwL"˘TܴT{@ݨZ075լWa螏Gz@vD33M"*GE-28]h fM<1̠Q s[}@^\ZUSj5'C Vtuz8ii3Ej.-!aš0k!bx''vPNWx -xT+W"dt{#N rP $<_Ej:62!繥+3ӓ̊KTNA{kLa$2.'qr Н2$c2褳O>y~H1[.7Qel`P22,ZF@|)\8yYFde/Y$a.ANM`˕TDGJ hh;=ul{YeR1 3%M҆l.62XpJxE E`M|6kFX<[x:իGaW p|QA6* aCB^6#+_Cox1dcS77߫v.+߀*xv,J*]˳ لEcErRHXEhIDrD.&LAvʣHӤ^#;]q7`XTqGK:ubb72X.g̤ 2@DMhb%3B;RD9d*+ΖX (uM'e%aM/ 4g3VY@8sZx<24n@cAv(L. e{&%ʉvТKd__s%mqd4`R0T\޵Ӳh |_ A^6aηSS,uܵH0iZw_qz|Xga+A©+#HP@uڽM H9;lp% XyϓI4V-ٕPaCt>~ %=:(VJ pۆ;RC 7dc}ܨҀ~63 hXÅ8s/ +12?~}wRN,/޹!SS"Fv`l'0׊> 2D Lg3G`}Y&yBwfUQI^w0(~Ö-Wz{fz&+4"ǻ$)pzMLQt]Η-՝[|$lՅ6sX36Lq ziF&4%!tQ1{ec;M\ jRY8ңi#I{zԛ'1,u Gj $@}/nr JW T#iQ/ݚJ{`\ ^qړm&ge!#-2x1HP"Ioc=FB`4pYz!k2<~VtuUo]z; b.YwW/&cChK)-a.Rt uMGTЛӋIFķqC_F{m]ոHcbci0b7tkL o1#XG FVfKB'F'q_C+$*=%Pt G1 _WgԳ `K|6+gU'.MhOE֓Qf|G3RgB*f##rc y\}O9) QiB.p@.HeGO5rik.Ha68kA|GXv/ܾr3}6=(;b >?OpZҘZQ@hۍ{B1[mMd4 ^刱ax?[Fr o^FbD?;h}c/,̡uj,z!⽫O&d3lD'6'iih"؊d* LȂR!¯,{?|xHiX0cu'ȯAdF |8Jl[Sd)g[__tI*y-H':<Œ2_/&*SG vZ(,N#X`P,Bb[*h4.P8gލIJǃQ)+)s95čP[ӿE KHuFĜn0GkX*2:2@{*M@S{t><ݛmT4;:r i5$kTjЦ2YH813=20J5/ 1\٢n, u\j e 3<%Cf.Q=>| Yr ^3<7iGm.Y3mjPWɲ6("y/:xІY?;CZZPClKmwP m0&wv6Qd{smpګEm;x2=?[01D.c}dq'. 0xۨĊII̡]eqC܆thn}QOTZVoxv,a07w權FǑgoakdiAyč[_sЦhIK=uTwX6Ggoȏ5(ڽ~s\fVMhKl}|Yz绀g$rnmڜ&ؽMD?A([vn`Fp_H9(m!ft/L_)4<gGiJ#wArcS$ab$˗hDd]#'G@m&MfĘJi2̀<㌌>&Ü9j qhZ%'_ 7[_O!H"1-GJUf_89RNÞe`Y>(Wa;4dB?טQQ()Yo y,yXƅi%}/e>g_d {_O8ewȐcH}@.YI3aUzQI~LsI3<=OH"ӎ883`#]}7;IFT绩;c73HƔ>؂IЖ%ښ ;woIyC5Hg# Gry3Ed);;04Mu*őYQT {{#x\[Xax:5P[/Mq }$z p?kHum&wXݻn(iO.{k;8 %J-D_OnܩhJ>$3L爂-aR"y]; Ӆhdqk[\/V_%ʉ3Ռg>~ AjTfз.`:,;7 v Gwv$d-"!c9筷lr,H_XAj@Ne|9wV4r^)Tr29QnÁť۫ˈFB t1B$/>LPK\ N Ȧ4;{pEoey ŢIWv$PP0Z؆=z*}XǦ(dP>X@KS+sROA(hFVY*UINQ t}ruuuvia^fP$=pB/yXu؜&<Lj4f+ 6vFlvs^5tl{CxQ)S{8k)+=?=o;8S(ç,~apA9r"u4y#8B:O$\F~ 12gÞ2=?WkΌItNpos$dlyXfB۲r).Ia!JFNcڔ !՝^`( '*D&'d9!g@!mn`"-(AW0@RM -a~ ";-nJ28 [ҋJAKM*DZ $]G:raQjEPQFt@18!cܗoVĊ6 \ʕ4|~vnam xnY, 55l6~I_x/J06&O)T3e;hk0&}ŨMLBgmXFmQ+ÑtJ#θ]dnp+؎ ɼ8Gu:BJ<^X;:jHsn:94284 CYP:֧g{njz:uΝ{.Y.\vA㉲y]lvm>a-m-w{~Y; STuͦ\BtsD0٢h I.6[V8,_l*_ R~*]x& *OFO(dQ3E^2  ruPG`3iSy2wac\_kjY..,-,LjT xn gۻkhu`1{{{{ʃ/:ṕ]]dM"Wccb}^uluB;GxE>S*%X? Rtz.')M 3l5Sqy֒lV,T,FRFZ3Mh`3-)p:9y%p~mG=^]0hۿ Gdmh6/\VmXR}cLJQØcTpk` = &04Ѐ^~ &:t!!)0 btg[MP1@@jNXFm0(gk6`)|Z&5wg=a+`.XO2h/sQN|V?\`6۫0 J&ǥb,-Dq½[.0~Ƹ}uA]t%'`6Fh4,qRaL⚇XS,.=w/~>]f( |x;f)fw5i۩]n +31 ?HbSeQ|;vbdhua `a- v~+IYX^" xеM`HvH߱3bmF}bq'mU\GУc`^,AAmeik*3 ?yv >Pjȿ2ց9Pd&C̓+9˔U ,=/CG8ZpiX#\*}m3Y&i͚eWUqPkoc2jN5DEFě'pT`"z!aB#_A&%qh0|J'iHbt M) Vmm7;MHp/9BɊ#Q G4y.= qln2.%xƏ&wø}&N4&x.5 "kf[70&s@7{\+{  *PZ6P4;<߄ I0*W ǢEzYwvf9=>9tGp.0|m(8SQ 5TVq u{vh *:Niɣb:9 1Χ'P){67Go4 X_`kk8X]cTLjVר$RڡoS+n5E.zŅohfx0/z4ìS SJ1Ic=Kd}39;/ $V, p qJm}=cZ f"dk|O^{룧O!،WjM'P{?ӑÏ?~x9]]J_*n6&p`mg2 h9Ɨ 5U2C lY!}) c(u>A* (,-:i =%$dV4>25AFB#Ba LGgp8(67_BI*ʌ`7dH) hg{{UapuR/ggggf`1P I1wu9e$F9`vZcvf9b~_$+a)~7xXL)!4|i e/.P:aDp;CtsbF>k36. Yd$ hgO*3pbgaGRWAwުu˾3mG2siM2c4O3wxv.9>ba0lhޠn.Gn-U߂: y4iÁFnf!x7>pAg~R㲱"mP1epC_GT҅kr]*RTHn:D4]p!7YآɌ&7{۶L=>dsΫP?\4B6H;3`&;sTVl績 Y$4C0&M&ӎ;ѡnXTrp7W;ܐ Y><4\J0&SqGj`;Xn  h<`85"Х,,+i'ʬ!ƕHӠ_lfo?_Nm-/}\:qf9ed&gMÕG߇@h㻱% ]#K8% .܎ ֎N8 a'S6r F-=܆ _qT hCzۭtVdÅ{VP~+ i/>> ޟoޠCc?!|wMg!R]oj`v: '&JW7{ aQϮLUH㥉B@9g}/0 )k@0x{wWH&Y7!D^lf`(s}}  b9&؂4)2 %R*' 1Gj1"'zA=^y7cj4f'~Pisfrey1ڛIWBĺA5lըa2Q ڨ#yb";w^$-{3/^,`bdD:F#!g F pjNdp8=[e ˕'g;{?hUGF@}g=Cf|XOVY?Xo W>+})4_r`bDd=8>ª[^4({rns -yY\T„QɛF7 )f&(TzpB٭y6dAd-*g 'm-ξ0]=89=#CGc`;(=./ X{[<⟫L|fy _`;o{o}_*nI gtrÇon>^Vk""ncn7-v0 ?p.ʀ4cp@~a[sϔ8G=+lcBa2dDlxKlZn[;u6|}F!66Kh6ˈLf:tڪ9_vZ$ekx nc8"Њb\Q5cJZhG_d0K+1r7Y_W?>󠾉l_74SHܒ,82 I[6~B' .'dԣ ,2dpփ*?_];Q력svW_}ieeDu󓫅MrWqW_C#8C|;;;/u6~7!(GNĐl8Apt ~^{ߺG#D.#5vtr?C,!6P*97L _5^ =79]H*Ͽ|sS_`2P&k=G?Y~ wW^y(}~}T=P 0ao/.n}k_G \L ~_\ &*E _+旯k  OLf <⇛ҧX4 AS\ tIF =@Z_3/5OOl|ȢN[C|각ĜV[ض&Ҩ7~!}90x:7.yň>qUqzDöcRf1nﴵ(VKg`jɣ'{ ݝN|:xj߰8{v:A -|ZfKZhOԽ{ttz>ܑaiiQV+$*ȵO;ftqlkkkh6|g>3 F7ZuwOQFдbJĽ+m!jLwA ǩ-dm' sӃYfDk f`] ĮRGI929͐Kd+OE<]I(&X,MՉWשּׁLQF"SG otu;-4e=űWW{Oz6]/W'܆7B1pqln{W?ٹYl<%e!/!%*؊bFь!ܭ ɝe2:h}6e"xa w66QYK:8-* ǧmvf'GOS\ B³13Dpw/WjtPV'˷nO4z@ Wut:H5Jϙ&jVIl@U*CJ2 }@ GX3SM 3ʝ/F-|2P3c׫3h 6U%dUGj "`3XGu͍d酻Ӎ>\%`;<9:FKJ_YLbTY6պ~rrAhEmVo42 14-"^3cE9e0|WN5 c$Ltqrsz3}6K>.g#fWŽE3QxHPl}3; -mw~v'`R1qYjzIIi!Ve`TS kMM3–(&]l9".uq*]=38!xGwdy'бޥGOG/# c_EF {h`uAb_m/\FxOԖAE(͏/xy:_oj}歷~jq 'ggo6\.B7[ZZ@5oDjc(֜ܺ-a<ܝ/}7ǡ691l1 ;[1/Uf<| gu{hǦ2lt f:l`Q@˕R 0ݽw-x`?) x_LMO..]7`e~P>& ̽랂~{g_}͍ (/IairC No.G8lPo>`;YbEG^c NM()5>e/t~:7XpnC;oGP"V#`I3phNֳΙ4 n|ib~q9l(U:ڿbbahL=<ه { :), wd\̀u`wxtnp>wxqX*XfIy;mHETt*Nrq܏XL<ۅ7,(rJw}wkNpp2#8ޝk ~py*EӁ\뜬esE/~`5 zj`x BxJEnGRffI0$2- ?TrF{@(D[8J[NTDC71z=<:\ 1ҩ1(#qp҃<,- 8 625q0==*U*tO=A^ P^9l!ˑ(JtzR}XsWiͱUX|V de0ppSX9jp IS* U$hޱq KI#, ý3"Qi2O"h"T!fhG0Pn"&Bhr@l:g(IEVRxm$xi0gȕ%x43ɔ*JࢯJX?Yz0ݻwo߽rb>NbVQ$er V<ףEcHAc$H?jtl2&(NNvzXBbP4Y'ac| |ǩN<6I:);@72{؉ 8/"s/ōML:uT׷Ɛ 3#lL6kN>r}Ӳc3lpn .D6ؘo0N փ ?Ͱ=7vx}wy@Þ{ 1T+Nܾ /.=y¤6p1ASnAg <4 (7~~//BRii"ZU^:p%=j u}D*|C+>t/2G(qXgmJKNua.q?f2R*]:؃A݊mHFUE0j9lNXWUft)) I v┳'?7PTw[ ۱WyV5ԓ D`WcoդsiJ#֯LUq)އC|qiZ0_ 7}KxŠ^Fi$$^f@6>zGD*1+98L'$7ч"$Se/SY $ڋ{? m8RynSEb̟ih@8\ /uHDFxs h@<&L1< ֭5=54|UfԗahxyZin:/lUL~ԁ( !1sN^gjC@̄u] YtQ.0njSRIh1/0qW4$·|/!3u ]"oL~겎Zzր7ntPs%k4ǫMPO;4۫m;S]4f48@ 5p{nb[Ȩ؆Sҭ[n^Yr i*W4[`eUd@Ƌ8:I_-$QW>{pƆǕľӧdT,WPWBمMnxm{ "-|X.-6ʃӱ>R.`CCmxoAś_.яzPk/+D7~m۾g3)", ? 10ap H̴DD0g(" *:X0Hq8(=<8;PiBa}>>q:EM# fd2rj$>vNWW*(o w|4fuY<|;wV*$;α9]x3foѺu !2)FXlǤo p[9P]oW`g67;{Mc5FSW鶭tzXxÏh%aޖ:3y:=hw!KV_j.|ˆLLo<Di7#w> =A9E/I6ϚMƸď)k1!}b;ϴLN {O;pɵ=EX*2X]e<5?hFKۇ'{)RPw(' Dbd{s1Ж ܝ޾9$4%ďW dkL69`t0xLZ|vfH5 vdg ; 9ɏ:VQ)a{sǣչ[ӍB% {8:ztr;d-.)Gf*:L"I#)q: DQ$GuR//- 5jI$QYBKnnu3w+EJ߈f,6]%H12z4 lN$V,?" P%QS.(Y8 hctuzQa;:=:]TGZ" tp̓v G%dSlNW+ P ˑ2,oVEȇWTL-޻MVmˉG ($'!0<쇖Y" t R>e6{+h06i DLL S)&e4AR0|)v أ@ 6PFЖ5WfNdhK3 LR;H:KKsS3U7h ь4u  & [{o0g^{ȮNͩ>ЖDW@pVŚ韁aG b!b@+';[OQ䘔5o^ E5&KpU!UF_Z i}h²|}Xl1(FRSaMm׉tp8WyF|sP o3 ^zx[" vI;f$,p*؈;sy@W\4{>950 }aJɝL7(#/!9B3,3ҒBaqtE48TLYfv5#óAiv2{7bcM4U9q}aiky\ƸjS\̀|# SOh2EQXh&%4Kth()J Z%LIlƄKֆ  K'xOǚCr=>f-HJ10m4O CC2#m&#%2a"0ƬfíM#WO?z͊ipNP!<t!Z*KW62ahr:CʀC԰wVoP>1^wyqS I08z\g_ pazS'/Ƀ'$"=x`f x^%=ͣo :,C÷ I> p->%`xzM~`( Bie7CG(8n04w?_S.}+޼ћ*hd 8*ќwy5 39X]]9`{{yCmbO|5frlF?8eWؒmкԮk$;7CRw\ְS:?9m[;vȶ=F]> Ơ9>^CJҜE&pj.D{'P%:[Y]iy=Z6Jn85 ֐\%Ys `"4lZPMY8j9X8p^dOہ-jA˵s3ȘP:`Өi"ZqlaҜ\lU K8wruH!jL7O #eKM(-ѫ(MW,lW=aX,@87b1fӢEDFNw S4pAd?`NoNc467QWPSVU{A]H "e3sFq3tWz4r[h6ʝ;A ¥jR)iɪC4*'ŪhkFu}|WD G ,"}0!+#`QLr >U jEд`*Muh R`|ɛaLNX fLiz1`2eAoln'{^1h)X N%1# 5w&CTaA~%LBAذDݝ2^[o2+#xsd܀;MlVDa[|3g(&h. |WңMq_23>dw-ZƦX`ΝFZ[F3A5s;@oq8okcJ @r#"׎+q\ޣ N5uuSBm.gZ?_=3gbyL"3-SG -UݫG'PݜT'$vĢ.ݳ^}'p}ֽS˖³4d~t?cFD9y>D3oԏV()8m+~s7 K7#~iFI\(mݣ⺧ JY-=?sC@oa,/&o~wkT  ZC1,y:@߇-04sljޤ0^3k!me];iAHW!"δALt8EƸq䠜}v1=]@Rj`mG8ŗUCم;+s YT'5'ћIX^@YD}2+ĉDݠ \t0E|D{q~?J,WAįR!i&x!&iw[6;i)J2LβL,$mʤ[4 58_+Z> ߆AP-ݝnxpvBq15@@T T?)T``0Z0>zͷz'o~??ypΑQxsՎ0MӦ v3 (?4jgty- ,iɮ a1q.sxF((F<'I8z b!arx0cQ cL# (`yWRLJEZ@lB/BGctϺ=`\x=l4qܾwX/ j59SVٜOFF`%Skk'459zӧO>y'dߓʼnř;n{2TL jqT/UKFX+Gʅ2 2H+7&1_ȷp`=ـ9љ\ VI23DCy$b}NG5."rB1S.K?n+AdNK"@=CvPy O9r#Sp[uLv-ܢn5&jm`J*-> .6PMJ4roEj0BLid:c37Pǟ)l\͵ 7;}]1} "W jH] BCx@l;(5`)(>2!a.hA|"QD% =Ԛǁz0ʆl"KP4!4GazF)Yʳ jCq#]AG)+;q+!E.7/t uO~ejf %Q^Cy! ] !^ <).;w=;QL'(sG!#9$.6ơq?EJ|Uz^z:X"A[8~Y:$?/_{[3?!]+/?/Oz7|;}mz3lрYFEܣdF7oy(seynFr4( Hb^w"ԗ }G0gЫ!%>ڢ[DQ:U3T(#ue{RH]U$ۭ=x7RVbag}k'ָҘs. DU˷xoq(F&P<%[GN;9͡mL fiRX3VJƮeX /wޞ,.ϣAiUzՌdHFX(2ncr!0fat^F)A)<$ `\M0?YZJCCqw-$g||C=dxz {-[SL2;J@Iw\hǥZ 9=h ڢOkʠD-l}",Gx Iz0|DZz6r(HKNʹ6*+\K$$d6 lrVgLcpRzXC{D_xS e;53iGB^͈ά Zf/cIW2H .m+.. BFOٸ(̮ ]E'l&ÛQx Y6ͰRҦ|ю_sR> 95DuVπ[Q/pF6l?[4䥕gQE$#r@|ȑdlFi'䠕EG ;~} &_C/ccl(qߜ:}zިDu#ȩ#6fL ɢvw7Q?~'B?!OYIR8}DHW?,׀x'O7~ 0u9|ݻ7<Ǐa Qn2R /ܹj2Z j: 釷o/X b0Ʋ24Ht)(0!8ƻ@(R9؉$oOch@ C_ ?)VB1BolXP^`H/f9!;RJ`h@(i}J'\ #i;({Iuk9Û\UU(͠#aKiuVjE3ÁQgM"+[|F3aD(Xt:by a QN$ v!@s==+n^X\~J7Mdo  4:v:R,.̳jUVkN&Ft:cBH$ڃńN4]u0D}о,6袩BtCۮEC%a"ӼԠmETkV{`oi KX1>E|絛8@\) /e&={xÀs+n&zN-Lc05 K&Ey^V)^釥fj-^mI*Z7Y<}粨oVLK&30~)iE}+"KF& a⧝R U٥2}A࠱}xstYt1SjA"f!JiVI& pf+,`&cEǥ<)8`rrdӺb!hsbP (c^ldʝ#S0/PսRChN8>FGO?zGl>~Jht4a\<<7wk~[ /nZ]Y‰4LXfp:T"ȗ9SXnLQcLkIi\~Ν=}W?ʗ_׿dE_KkoFMXAH`G.R19BGzr!c"' ȉos+JHZWM:sl <#KrGӃa3 X2D '+.: I0! -G $AiQkww2Bj"`!#2(Z/2;9(^[D`>6nb63LI\8|(U xS  Ss^[`$ D(3B'*0) 7 \DBk4!`\@ăq-qɤ1qIVb oz:>/|"FqOCC$ 蠱6˜h2ȇs"|eL:۟ac#t1Ϙ)se$-O)dL&;yM Ebfo.: %Lhr.9Lf;4Ck>'$m4rnlM7*1K0)ž1ӧhэzPKԖb ]l>8ZGL8$Ss#c;8Đ -#xY@26`7s0H$%x)F vMAx١^!%¹XNx&RlΚ1A7ކi $ZLTIx~b\YOGra7'@JA'm?۹y$ u$|]|dDƪ>uk[IV_+o_B[_YyIP xxkgOLf5r8CJtvc7l4Ԝ=W p(*_lG"eG ~a&%{N27pxlgL?.p^sBИ5Jg-oUqvrM`䨽DO>}h`te{euir *LeuK3F*Hx%\,UhF3 f8JMNMcz8iobelht:͑AoYE;fOT<T*#9%3"?| <P.kbZїv>_8G%<; -Αn C?hPTY;R+d!2d$€XgHa&ҫ8T/o\7aCK xm>G>DJo (|ﹻp:PL:cg#}t|{キ3ƅ3榖gssra}`o3#D 5أR-#bW>˿KO׿Ͼ*|P5q{}|P$Rr7wkmT@)r8Lz;Cty_Y0J;{곥 G‚tZCYc1;GRqXP"'+h+|G5VP;Ӑ F!!A {85gu:88tp*Q.C0OtXbS?c P?m>, i)|+X/U3Վd̴_2B ZkǴW>.?>[ %MdRiOn_AYڵ͆;BKǚ krYWJ<ƚa:$x 6Dnى1ЦEbv°KG(]wڱK1 R3ʀiy4n8\LA ;27Cs&1iEoC|~4Yvs#3X_c0 ㎤5@u#P<<Fl&orPc AjA m ]] (I'q vEe?*&;?v C{9py6̬u%RB9d}oJX95'P\Qƣ*F:HP"mGX) iKmvtSx:\""`ߺ1 $Hq< ~fi\0,3!T I D"BfՕŕAD9")|T"(GkW"ta/pv,p0w,D۶ݙ,Hb#DDEkD)Ʃ[\5!~c@Qb36~>jU肼pݏQISWZ4Q:tʠ|8P C8Q /Je$C@ FJx{'g:ON#9v$ҫ^!Ud}ze&@j,Rh>jȢ\7b׿eܔW;:>T~GIJ}bk- wלּ=D,8Gsr%Ēq8#gY?`}c Jpq>ULg2Y,ηQl~ gH}$V&X;_3O+_wN\9oOotXCea.( wVܝm1CGk6"?t%P$vJ҂sscv|I4Zf,.PGZ]T@XD #։ij}FːbwƵ9SCe\p}dMTgPMBsffr4&PQ08+  1.Z,UWa<'JmJAG0#Dl6G^D9|=$ L$GL1'); 䕎[y.CHuȌ"YH \6,P| 6[r)ڻdJ##D2\k)0}BjF&ڂލ4xF*sWU`Sp0..]ₓ\.rcGif_(9^[)E#, [U68\i]n\))nٿtLD#%*?}hp $rϚuӵvQd u>P)  , gP"%~Kܶ!kKy## 6ڙ Oפ+3MPƊUpbtPJW<{WqKUh7N.A٢0cE@ 'Dt]>1+Xʳ^ȝnYcω φ \ +KQvқnC^z"-9/߳"YBd7oO,t\/}^~~R(<($6~a??>kdݹ)$OGH < DeXⴺ{'Ow;&ySgo2 y_7~_ʫ}oǯ=~mR6h*YDEǑkwe OS{Ålv %1%QBF"1hcVBٔVJ4nO`S8zLV(KMcLp2CU-raz.FGQTroN }I62d :>CNqs=ÝXEJb-qмyⲡMRp88#&Bc*뀕)~5N)b%X 2lxNIX@L*bS0y4`[335cXP |+qg ߛX<ظyy4Za;Ӯljf0 3zXfD&yUֲӳxxNI:ӂ%NI]&F`3EeR,=N¾Nm-Ϯ1\5~_5|Sح,=5f-&Hyl1F& AFsv=So3 PWQq9=@ʭۋSSeeOzjbmF12%* D(S[Th|L֭YtR,ȇ>P#8oon~kOi0{{wc(9ڇK=m4Uqk{Ï>yq4<ɹ)$z7"V']B8I2#?~moo!R_cJa εY3|_/=繓1~{0'O[GmIS$x 7Ah"q7^{I_ |q4.mgfC7E`|-ZV77V]#|bTAdydE7PXXPL]E\0G=pa@ R4*N蠅(;F%lWA)Pc b='"_h7`PZÖg6Ϟ\q 0jZUnJQ(yLꭕSӈ@sL )7AFbV.+{ZҪ0e}u1G_8޻ 5:+$3aϥHOvF S&`Q|^Pb>Q ~OۄJGApКRݑGOnTk?!Zn>&V N:]U sNb2l@]Dn" @(9,qo|u{ _AR5sg`988SR߇lQ#0 ߧoR&ƚ'MAQ ֞MMjxhPͪ@"|+Óq?a+e[qDش%8B`ùMZM,OO_g'6dm*vG)ڳ~@ ξ0!'d}&ȈD ) m${:E4DrD'OB_>(V0TJz5l^tPw/i7> y\>& pfCG[Y^WgӳfZ׿\2YJ3yjqJѮES{D* ȻôZSPS] 1 1tۊ[L],ZMw#K$ȜvGj H8ڻ9y[8vefϖÛgbm40H(3aWPeto@$h{24j|aO/<7\dP0H'Sk9W<쿾mDCE ~k} QLXGF0FЩ_ѻY(3Lj32}V⎔gJZHvZiʿ6OL<0VB|\R!o~.ɛ;> p^ h:L":nWd+ *J s?wzsV̝{N ,Fb#CKÝULv>z(,'krqQlMκpKx_riRbpΛo"~-#Phص=^ _/|_y/תU$x "mMAmqQ J}=0㓬خ-[k$5$10!_ e$`ωph',CdE,SЌMhM8"4썓5PSuba:|8\\*@ sj+m/)%3[#U؀w!fk#\ J[1jf1qe0E CPc)}·h'Cĩvy5?א)́,].ͺH' %9t4]˳~&rGU+/~>>;蝄; Үmh g$3"'p3mW=Pd6Ipa 7c@o7^գZuA{Cx$N;)˾܂qX ̀j7c2_*!d@UUg&|jbULO|Us`̢ -r@P 7(60.%GJA`Tr H _s/A^QTX.rnR Zgpqv-&5 Te#ar*&PB.,veó(1jXV1'y@(`0;5C %:F“4>DewQ3ձzA@9OFpjuh<ʤL =qF҈1i2qoZ/ d͚o\Ὦ.ӁN2#3Rl'$&l96+sS0daaa-qC=bSKhMD'DO_*q_dk >06Li_2^3ӕlm5~?’w1M7&Im%ÍBqf>1jBKMn%o6)#ONOk9]N;$0Z]qtm{P{W:) sY# #d/5~v= 12HFɊ :g_'$a+%'0'Uq)]u !v=Et`H¯!tŶ3Et%h1ӫ23N]OڛKP.ŗt/@&Eoʪ -L6"А7f &?3ܿ w&V%0io&c4w]pnk_\tu* Szˡ`yӾtĴ6T="l6AipgrrLd "IkA$ pU+8)ӓL22@J~'/py IW\/(Ғ,'Hz݋ք_, O ᒀswՂ2Bn(6+0bFߢxe-Nll`L0mVV1C4O  4 g8ʯ{]bx; )C{;06ʹz>4aL@ߎࡍS\$!(!bг swelV H/9V3nf$ V!|h B 30%ѩD6`Ƕ> &j&NĬ7nfTN-4I9!i1e$.@EL(􇇧g/ϮJ~㷿Q[\D@dD@iي}r8.[ wv]#_X)fP+!͡4$')/`}e^ks~E(Yd'&q>aa`}{guyDszr3s9'`L[&sdqeg8YOLmӻiN jL%e *\^jU[Uf9[XED(0.֊s(ZbtmSۭ& [I!&djeq\\ :䬦fZ?܀ZWSD^fY00PVP%OY/\v#^,8!n0 Lo|Q|)𡂩TB^3 ff;tvN@lȁ8 u/cO dҰP_[dՕ lk-K6M“;R~ L7xEGnFrBbÝ%)rpGSIrWHB=lOLү{ ,!ȆtJ#˦@mZ~Jw / n\+{D|ǝ)`幆$78O,g!=Fт&}j#&*՜?kUyJđRH?;vō A sr֮4n''Qq!$5ȄH#Ev8]ǁf FؖNf+{'#kb"00\I}&"ڋ"hJl2 a|u %Ԏs6-EYFt2ED8Sx1&ۙV}+bVC8.h1#cCQ+1rWPҢ F?ƘYľ1z@0c$<>mu0|Km|uwtzrV5!~_ɟZwlJՙY2n8S \ û+:ãSdl@E|67R*4jk|Pz [8`G*ZeQ[-sh~Ѣ,)s\i8Pa9TޙByl=}4S??>9B6#֥&;%_\^ ԄEO)k|ɮ{n%*B ý82.KMImGՙEH(dzyP z"?0a!R\\wHi(`hfX y$6.S320g''/^#PR [3gz9?,8 P-R 4(/EbT؞gmcäh 8nGg8ia`4*\9+W@ѷ6Rr}-H@?f|Zz U'N3"f\[Bs4c!yI ֝bיjtm@f0$f+P>dY`j<|x ֣l%auUљ̅6ǿ20r:vT&wʯdtA-N;yJ`@:s';4$)##݆qm RJ ($c 2Ln6iW 9ߊWMc*eϔ):/5t87~ UG/?GPí{7~ܷܟOoj@Xz)Yt2e2ХpZ㳃CPB҄~Z8iJHaMǺ_(+_i)[&C/H^HWqq=lPN~ړ|cyyMV/O,(MLcS,?Y)x,ҔpkK07ML+$-7 3NfWJHx 织3",b=XYi"m*2Cҭd6$;{A.rkB+-6l[vOhW\ɄXoԄZ1K(A˵b0ݜ(jБ?Z`Q * PonX"#$rT-R ~H;[|K~3@ZɒP; ]lZ(jЌoFL 18Plǚaw;vmݺn[@ Yܪޟ b'''gΫSTpnj(.HjrꞜ]^wqAثX-,Pϗ*<`"]U+W*-4 PIiR93#*gȮ9bF皷%3\h&^/δyaA(b/#ja={/?<Zܺ\ches_N;haCޗ_"g>X-Ld~oK7H;9`!鍠{Ѯת[*–g{g__uۋiwtz'qw`|6AF N:N_㏟=bjCYKڲpEk*% ZnC#4A/Wy?kXx_7V*HFXR39`1 l&EJVTNkwWI$s3B-;d$Fcˣ ̃u  #}|go Grieu^cp zP LF9cWCؙ*|5.M˸8źHQZ'nLksz1_# n>̙•K"Z $!4GRF\(gWza.|:`r)hCL$]V$}"0@. kpƕľpb vylpܕԂD!Qڳ& ɇXov-)7a3hvdtvK]ʍȳq w#oh;:r_A-!д!/ғSeJk牼zK#X>R%Xkj>4Ӑ^Sf'?~|oV習1xkCz6_P 0:}ÊU=/w,ɨHD< 6g5uz:u\Vs5b<}{֋٫\*O}5@ :ByC7.䞯z?GPGgYo< nS8<}|~k߈_"AAqai hq":o.Bo po A'[kk+ZUj_@wu[2t`A5SA0aq`fse8^?a5i}C";@34 :\.6W=HBq(:K^. ο'ַٟ޷~⽯k-d\@Ns"5iP0<:5O܇MHя>CVi#MÀ:X t\b.1g]QT#w&{VA9jE]Tۑx]]n%C5+8BuП;|gj-_A nEl'Pjیf\#9Wu p6o+iϴHn /./a=!=8߿>?EL;F}7LFYs[u82js"a<\\qzp|~݃= R9R_Z熖N#B-ۙ`YV76Պ\lH~@ӖGw&E-JbgdcC)H07 kk htn7/|rzq~zyF}aaͧgp|+euu}Ui/Ώ/NP׹lz K+ݏc!ї (`ׁ%r uNx;Xm6חW{Cnpxrϒ.5 C;)v`'nA q7kt}q/NP"1B43B" LiQ~9%㛰K`W_[{2RHʯAZfgPjn A;Xn6[V u'̺ uz0ƞCEJh LiS왰 rvf(Y w5N: '?M]%UMU8;BP|_J,Z&g R!'t @dx<"fM٦ r;cЍ$Q$Ƙ߀!] 8 ]MV"&Ȁ " 4@*`wo k-@"D%}h/ÿQ[xܶ>4aTHȉ&M3P,Pq&B2L0b$5Vض_@ /o]b$HPF`30>ݹ/Tû;T'/D"bO|JʸEE00|hhȠքcx<'@ 7~7!%xu`h89ҙn 8;[[O]hpԊuBX=SM?x=|yqs%ON;p^ N%2PXL eE0dzeJ2^4ar%|@HRxY^@r$ooYO\zXƐ\zD=鱊CFHJ-aGDg21Ve?QĢF F2k4( uiuemlVȈTC;XhA.! n[=Zjyf˗`׼Z U\ 8qۺ<8A*nxJE@X2m;r6 [/ hMhtKŹ<*#(JhƗuXuXJKp GfLjky.KǏ+/MhXP؆Q: z| i\=9~^܀m7Y CJ QI}dz{<3QacC7k da&( ی * n9 '4qYi!CM*`Qˤ^">L81e0*v}j(|0@!KpOws.I")$X OGN=h'!ӯ=EY' MU% }5#lLH`9W&Μ8;uE\!nE#]MįԜ;@4ņvk55m! id\pqhvә7$%F[F}A/S~𓦂3 Ƨ?a 7n9 %br$ )!V#3cCw&z#<ᇟujhz?Bǿdp }yMOrC0V6z$ٿw`uu鍳5Ů|nO |\o,d*я>}}Tlakek)bMnhIl/o>AAC?w~៘w(#A%TYyR$q_l;գdovxţX [`YarFgkXȚҟ/?/pw3I& 4أ7aR&W)aߥs x Fr3ŌU; #SUӄ۬eY67@8(pu=hG\nZ¹(1="5hST[B)"qlLG5, !M7{rKZ8V1mǬp+")~u# O{r˳+Xvw^x?~ᇟ}1j`·"W]F |3WX/vE xg!XVEr &cŁ<--;yHb3xmRACuLz3vo:ݰgܘl\1Y|.]a`zѢ:t.suWeBlthGC0pެV0y3lC)$;d䬂*區X(vk4-5/m,(FWWitADy}~z}z>=A@$WNkm)s}wlLCLwЇU F1K;J0E !LUʕ,+6GǗ"@ h@$E>7s0 (!rS o˳6)p?B )|JjV N)a*g00ą 0D2]>Ks!{T_%' 'e/X@ǎGA.% @Go V3+@A&wGC CqD<1`c#TX@^2zH g#0r̓_r7`4X4OR!sH.fw{wz筧o=mesqqM|syg&'^a,"P^͕74'fKJT sVo4F% ?g~__]\)W?L*'t3{p$iPcuqݶʔ9T,nT-W*ե~jk啥R,/5KE^kJ5_*c^Q)q /g&L E! ]敢jAlRSV@FmePFC3ho=y'wཷz6[oo=œ?Ï_La:h9;\=~ddcsZPy%2"VG .X!pc}}ii^|˝H}bU]nr, ªv理R D0#"gw MۨaihTnE, #y^x%ezsBZKdIe|*fk(EDowl"=;5ޡ& 5,p8(e:;7 6t(EP*>1fn:sBpM x?7LlޔG_@&_cw] .z.@HO;T&&,K4rbLז+`D+Ic.@Rީ Z\kS]JupW3$'\/O$FqfU/F|EiBsʲ:aœEAC4Ab9) ;!ۜ>j 8;@Z-@+G}5 Zv ް5\ L.x-Ǐ=.dN5; k܁QXbAN(2TN1s2i0F&We"@Hk74>hx \ь71̀t'njдsQW|6hDsi_֚_ _e[A(~6G3$ђ?<9!5 xBaV`ح ?fA:^_5M ?U\&3@N_HM3Y5l_C'G]鐋ഹx^h<{2$1^Rzf$\Äx t8^F޹ჭ7?&-!nBƆ\8lc5 9tb( `9z0,[tzcyhr4]*hdL5j."GB',G#;bn qcO-_5)?',Ru6*loal }_=})$pڀ,XA4}d ˙.m6hyuNtW=6D[>dd ek~Zln]|绿?;*QD3}GҼ=[kUt fgAѦ5;?/_s⊤ֱ|2n7O($sO4~?Μ +?''0|A>ū}6+8zćd|/^Knq{r??ݽ#3XBMK`<ڈр_Hdɱ0`򙸣q<"1&/K~-pPN 2{D~8ͿdAjB^?u<8{kn8swB{jK~#,"^o8^Lv0߶pUN;eCNbtm;?pcߘR I@].4ܷ*%> 1f piʩ–V^-W Mu"5}r7t˷UԌdS|[YrBGDEdg }ip4 $t OVY\joo?zkOy[G[o6!Lb͎͙ˍ.b:7:%2ȜJ1eW[IRݍǨx]VEaƟwd yX57J%u\~iH͛ 1)!i@4W| r8>EG։/Tt]w $,?JppCt2L%9l[ S=!F0 9O^fxqMQ}];ݿ1r#zXA@G*s{8[a&Ee:pgfjFȰzmk²:Äŋ?s$ @_/C ϵh/D+KA{HXqϑ0RͿ{G } Ms')=w& l?v\gGQmo(: ~pE#)EldX'2߿sҏ$|#)//lp;=Çըk_t"7w9!t¬32}J' ePT ZaZ+ˁ.+waED# /otp\ 3[D/~Pqo×/<E܋;_Y'\ԎDɭ^Ag<ӕ86PfO3_t-"8'eB@Z_{^u4@^P:඀tZ\` J ӧo6j0@E' <{`B x+Y rG)9XPp @O}Tnz7sL{<۽'a(0n ;7Ej@~hƋ}}i0 ̋BP#mmjdpAm&.$i \vc O= Y r>_,cӃep X kX-Vƅh6j9iO4c@H߰ڨXZ+ř1ДDFvrPh+ST\BAmxb/>rwe?NQâtGggⓏ?{Y[*|-͒`7=R̥U[ɌA9]ٲN/ 0MydiE'llԫM͒+ ǗdMDyRcӥ 2LV'vhVK?13AZlgBXFԦF;ݡ}aU&&`Z$W_Hgv3GxU$lzd[fhHn2fc"*W,MfbmHf3wrs4eJxϮ-q0-@Rī@MOt'sh<em)ö04웈0 ?Dqd_H :b`# 2 '4w:M/h'P ϓzo _ᰈ R#ӦB"Z(}V56+=oYvGuxz@5_/r9oF8\Spw;C1AَB]^(z4ٰ?B~o|k[_[׫_JkPn4R{0܃:rɪۋCT'|{(RT.!<#/ 1([8͹[A??N8Tj??ZͪNIpjoG~J-b . .y8kGgp:?>V"FɊ>`a| 7/-6GK( G|{D2}m\!~thG3-Yth7tKsS0cSXWoHq # Dȣ*_."z _i# ]D蟆8*P`vo%o! ?Q9G IKeGGV.d#byU_qpxK.2͑>>@dm.% Et= R)U+x*0 ]\Rؤ6Gt]&Wѫ3M]45uG"?OJJ5_Wb  s%Alv?t^z7 ao6ZQ?׉1GNuo0rU@W  1j ~0<8C%B.qvcX'YѐT<#Ngc{kb}xBԏWkX4/^4A >R ƽΌh=EE_{\X+;tl"i"Ês0룭<֥iVD&'4OxbԎ-W,>!q5+D1>>zu[T>"{|ѓ{joY&3v0Bn=P4kt4δί>㓃ݕrj:e:% $ aʡ&q=Yɉ/V_ֽR.jw^t>|yQ?GX[ \_Q XfZdkabH1R?ݫVP5BsG[u*F`p$IE'|{X#| }$ݒ{dzSR) |TXgze -w@:>!k \| V`:"AmhFcP}}GB_$6$X zLh `Ǜ5t Ns B@mQ5v39@FӇ0|1z eKԪS hl3~)9]P?E=`S 6#drL> HX} LsSes!P)( !Zd1 "SHF:2(xW佈(-T^@ĢPjʫ ]LXgA,_RdXHaӯ Z@ׇTA(&t||)ʤSRJ ,søGA[aEӸL6XD) BG0PTM wpNp̖K NOj.h|y?53w,B]w>ƥA˛/1lCchV"~tnf mUz4cDXz8tT!ׂq- N JFN K 2qFPժCKxK܁OuSH.ҵLwjvS8 >wX@;F 0[23oʿ7R%!H& %+" +4,;D!Wi4DFCDdLv'pmM')݂ܙ戀+ӓҸTqn wjy>kJ?yZultXN J . Xfo0MzP5x=Ӧ$Jʀv^593 rFM/܁-QX@ѓ /l=~F90GSavKAl:%\`h8z񧧇 PGE4E` X;8'B C5׾>;9˗nsyǏZ[iTHtO>g~{u77wgs`&eW̨O~p7;~kcGՀ|qyh6}ln,pq@JYYt"<(o)$[lxkhlZM]kbgm's`qv{ݘyF, +?E4O /EzE55RMxjFo%Eɋ|Gؔ])!M/JW,-:LDpRR5.Dp3x` dZae">_}';/>(7Ţ7;l[s1! xڇG U&]Tҧ4LgUKX2^\ܼI %:q# ,n&jc͓Z<1lqÃ10Q_Xϡ_v(7'{A@z:;4)bى0 G Ö+wr*v5IJ>l"`C $؉o:7Mh$@2݄C`IӓC.R~dB+l5hڡ ,=&d/N8 < $)whrHF ''@&8c:bk}bB4nk"]wsr9=N1.Xd@MZ^8"uMqC?=Rn_~;.A= R I-)KVftm !WONqv}݆fTE@ϡ" Pr۰c6S|82DL-A=xv Y[=$;t=*4`xKRt!ʺ@?e:S5>N8D4d fkgA(oKk<))|mq,}gu?%F-fwD f `"s&k`:&DV<bELv ^Am="l)c#[/KʸބrƱ8IHكql+?9l)\A&OCcMfug҉9V =;޼Q:~./FH*:$)`3J' hx/]96ɾRoh&uVMBM8.xD&p c 4Qr%*X6Dz} mӔDBS=ZZr1[iD< kuJq>a -zw n<0AK5 YqZQ5P,e1ANc9; >RE dCvhdMNWe3N]|1,*"C#+ $J\Z*qUnoP*8 匿ˋJ AFAJi11C*iLΏ;fvj/H4lR54\Xsdk# Kߓf]v/c=)WyElHBZ,. tKLd.e8t,aL)ƂyK5rz*[ ff~Փ/O[N2Z>qk{̞b ô [Ps$'S [ Й1(b;P HӱlE_`|e1akrSG\ ZyϱI6 SgT+z45$rRe! 0#s昊kwcRX% Ȳޗ3 18IXJtbQ.[;GםfX)mV5';hv&i@nR(8. sA:u{w{r~b!jqfep}e}892a똰eP+P9Q\9T.R BÈ4xk+9&fp|]wbnZ-x:n~vneiqie\G\m߽h#IÉ5XĔhĢN47F@lH9^X[yxk0 Y^a0(LrXܶ4WY2˝L> =(LyTf\\]P&htKoOģ[;A,jc \C.QnKqQ>Q`~I8b?pTàȵ]~I@[kwi~}׈ܸ#qUOh$ ]>{H:}MԼ|$`8:d-:#-"H伱/t:~,ҁHu=IwqWtpJ+ ND&J%oba)'sCI,qAjȐDqO}x$[_g#cM G6 .u( j J2"{q/"5D@ K`jėq];8jszJqW#qPhΡ[-StdwsI{aEjLAP}p-4G &4WF1Q%B15L! -R 7'3Rm)y9z2]uZ#6c^i6;:3HSu\ɓ/;u5 gf,@6!}r G(˝{qK剳i9w/0!"XM3 9i_\X jErUFXbj+x7D4_nHn-.ϒB_$66iuC Bij5#Cg @ftB-F1Yج_G:L㤢q OfX6 $MPEXm)4޽BӄmB(ѥiɫj #2 [N@K {neh?qLЈO&x5G;ih$yv(266`=Y=ʖ:)u;9wOOGTtO5)G-Z9=%oDF7:HQ%k_D?S,tP/#UK}_sԱdNτT5yIT$Z)z%=^*eh#EҘbb%9{oO+\.q89=ȠMvZˋN~($Xm41]hQzB&@編hu[7b'Ѫf[o3p)WTJդz\@BhecR" 2D"#@ԺqލS3 h,mnԪ(gwY c:OON6)_@lVR )nx2q۵x 7Š-X“2nB k2Lc# GNHa6!czڂMɦgbQ>t}>?<99\l-{*~a7W5Ԑ7R Ɍ .'GއgTwXdۃo3ofzrty}f[oǸi Q"y {Z+HOTG΁fNReq /^^!iGru SRT,3`z"FeYyEvYN %>Ih}G(JY TEw2b#QT߉8jp%Wy[Pdb:NH{yuT} c acO8 X"$~>=>?8h`QΎO/.'2 <6L@Z/MjC勘ELDÔ`Ib%ۺB>y_!/e``%Ie6K̴_BSL0GBpvafˊZ bÎUЪ]嶚X@40=ߊK$۷qߡ]ɶDoȦe(6܆sPW4 ]vR{cQ6@" ;) DkY&BI84YdQ F8Mjp_|ܹ:NUGO<Ξ O|k!O0l9fRl boXDQdҩ9`/;5EQ )vb*b.>8uEl_ϔX2Q АO@DZG@ֳj":MS?^q8 3OqYݥ lv2GC"4OS!S;թezAh*;S<8rHve]g>M@z;UK?2AMD`,Lv%9$~+ R԰-rB z>ŰÉ7hHMr?:Te^f!5o}k]|jHھR2Y8P2Gb 5r92CxAG4w9ɊD8|BbHow/ft x^ٸ,5]>@(R4FG.wEp 4O{A4 6l*.3!Ɛ'H$DD65G{, ՞@d?{8d?ta$<a8?nwȨ$抿{G^ $,M @n`B# Y2ުD Buqs[nW]؊R Ȕ7:}Bn7.KOP)6~gnN.?P0[s7ό +;n,@Tq-j/B F_^0o}'*}u}sia&e+qk|DzbC!09xHsi /L~6Kt$k3΢=d8;=AR 75SڅRSyrPZ`<8Kέ@L,H[ N=̺IX`(2=&5SvIk-෢uAS0@kVr~ሁHdzc%NA R|QT1''mg4o.=' \PAWxia KK+/4x5P:sP-N Gku;;/_=EfǕOoblp3b L&p[qsЯ//G';{'ǰz)bIӟ,'׻/^v.Կ\+#b543{T-mӃ剧ʉ z:^j&8SR Hƭ -|ɇA @#wNEzqWGJPpf@]ULv>Qk RlGpR-j vn,FY?(E ́Eoqbr246"읋jh- Z D.0|X fwku/tvG';]d88;<><"m_!$Ҡ Ii~^U :/̧]*\!?){uUݿl8z\W;Ӕ+>*鳴|8>i #uHیF[)v9oI2q2ǬV 5ڞnͅ0.v ӼӖ13U 3>Dj䭉>Mi7MhKhfNb. ]Gs;cBwFQeA-BFҀ_i319i[AL2f=NN2C201 >,"4;Hh?ƱfNxk*u$Ȭ'=mK] -:F7ٮb^x1Ï9iJK]/-14 &L^1@MxaG;< &hO82NUY٢z a\)'kاz Fmd-1Txti*Pnb`h8^) 3ȏRn'fb`B??{~oJR208 (ts^FP[Zl.BN%+rLH>ZQ7S2 |D~MEZ&YklY(l ԌQ͓9aN";uw` ]d3ҥ j8gUg|Ä-:JOZs` L";Y,z.uC IuT:{h⩿QȕFNeؤcܩMv0IW݊HA+ 0`’I"'EM1)iʼnpER\ݒp9AS͂A'28h_,؇PB^ Hѧ5HݥȜS _mF:;qTiQ"gq$+ qgpD$,7qC5d^_}%Xi;ƿBpÕtz.~LA6ǁ"p\( ǡ-6ӫXi~8:?>>8:Ԧ=2c):-C+\Z mԨaFyY'A`A9nM| `hw@ x %s ظjw/Iʲ iF& }qxΓ̇ɏ\rx-G&LZZH.h:&dLIeKs VC >/R% B>bfҼI6 cl>iO:d% 0PkۀvJ'[04%hqcTx ,: v *6a<ZKtnch+?ʆ;jI(ZuQZ; 56HW6(3 #a >M~eNaGD2# .gP*ْ,51d1 |4G%w -C6l2IA|"݉ ;沁gBHSsZn^ޡ)FQMrч1uf踕 0Pr5d]Ϟ`@]7wWcʼ@gĒ]03"JҦx *5KGĜ84Əqr͂A ̀u![rlvN_Z(@f܂R >!#MӟfGYzruFsxs|0$՚,Acy[3|d95.1 p(CͅcQӒ$)E/[8aw8\;zn4P`    lQ5$ n5( Y dN Nn l"mVinQ>i#0dv2+H \m:L!+z"5p_7l4H!X-KyK!5!/dx0ѐ$#o{YR'3ش? " 7ɧQ4Mfh ʠA^^_]Z[[reGVAaPv8F^Vy{r:%QZ湛sk_wnM7 |yX( G>OTnrXBdxV0sIsA'.P4~,0Ywq8`I!0RAvvAEiX [ 8. n_~sLۈ51@?0A0 [  0!&6 ҝ\\/oV!cdq<6 sbm}Ҭ/+ g:`~Cp/\8 sYfP(jKpʁF -L|rXՠ`,q*y YKΗ_/C+pa6Jrsks;H݅=p=FZ1} .DH 3-pqL cZN_h㌪.h /!cz?CDej/ڠqY'I ~=7 '8U+Gpӱ;'*|/0"X@F{yJ~OFtL GE*aPT*e _ '"h> 5c.OPsJ ``0vO9+'vk8t8RL1 whY4m8V5$Ȝ"Òa;rZ0DgfTi!.o;[|z:*#qEp0k(9}HV=}`nr2XAƃ!?Wb<Ψ3R< `HiBl~搃Jt9!: bXSqrɢ,e E$E`7Qp&>޸u)B`K<v~ga>0TgfᛁTshVjpU(-UX. 2K(YS 6( vdj.t3 `Ԅ!ciFƂ% {` Y-hRf``Ri>,9ļ&M[AE!)/BUA;IwOiGhN4tBG08zS+rAیd(.4¼GtMqdў_3JsŮn@v 0#dƀ ri,|5g/6*C}R^ER6b3DiOS F!w'Gx 9Q;u&,\ 'eStj ^x@ty;x1 $eSNy7pG^] {"&= V+-HgjM!f'Ɠѐ7sš^Q.OtZ2FHiE:J >kG0OѦ)a-;؍SL_W|R]\Ǡ&E{ujFjO8FX~i{J8@\GIF ⒣*؀rZt*EVVY@*LVz)8PwMU⻙E|lYP/+Iؤ^dxoxOyٳENG΀h Z~8<>呂2OVLkBQ>$Vč ,fCi^8KykF²@/V-vs*AClp>\{N@Eė#e偤%ڣ6G!>"xbȾKǡ!W J6bBQ|ȥu*lXl9 jķ{/2fsJ0kH+}!1F(.qx%;30cp-Z5}7(w"PDjM7oϰ?\-"NqM_^OO{n,!/[FK@pt\FJ<LxT8MFpL(.FQCz EQn&N͹zh}Ɠ, cLi~0#40ToIQcjZc݄F@}qt((Vᘔxh>0\ŃdhF<).q<6'd*0YWJP@AZDjhR´2@^ )NPAHp@Տ-[u c,@zm7p@y P@vӻtofPaaaJB𵴕.ֆѕp/%W:r7[0uT Ll4%XD "bl+e`2l! d"eBv`8;>bUwܹDIwy~3D|6Xp A pWBfH//_-xnᚏf0 qn>L (قfCqrH1S]h|H-!#D=,b5:YSx(# cG|~6^.!d2ZP#5f\1ǃѠn ŵZQD乜#})t@(EjCxߛ+./ी3.^"/Oڭ p%2*>   b[e2V!K/aD1VBK̻bZ>lfi`"D1tk5[B5FUIq? ȯF3Os[7).!Xt FTEmN'Ekam9\oq)!-tByPbL\u哊 M̍Ok&yw{)a)'H7}V&A[W(,%i #ڬd1(5'@pa֖a rFX ۄ>|l6)*i;VkI)HEU@csGMH sS 2.wqq |̗S‘8LړP ($= ThŒQ!M֏&Z|; B1\yfO$Qؑ~X¡-V'SGf*ApUO%La$$tḂ$7R"Ƈ<9w#Q%'\ʒ4{oLr}/FC^(.Q NMy `Ҡғة 3J6v OH9r%K558PP|0>DfwmRޮ44SdHSx,"n APR5ɚ2(/p}&kC4PTʼn=zsF7 eCO6ؒ29:ڐdE3\pNp\l1فz ORrqt$fbpGI2Q6PNGK @ӤH\Vfei? #Y 9Ҏъ:E8\:y|@|X9|H;q,kh8'/>2cha89؅;~' zθzP=cm*Q|fr9^|ѻZ_Y~pka1>ߍեյŕ~-TyP_,OUggJ&zHSBa **%<ΑTY)pB> Y MВ5 U", p>GO V5 <9;!'dB.H c 7 a^^_`P}1PVM>G}tvXSf(B T`)JAbڴs&!ZfGDCx)*%pC?p@PyCP~9AK]b̍K&g#f:#F[c0,}Dv!ifXAUZ}>W76a@) &aiI{ <XW1l x/V| QZ)t3hTu,Ke8 (J`q;mPd֚BR@Q 1@)}z1P~CBI  r ԛ"::b s鴆hh8h} S\S@3@ƕ!Vuf+3>#+CZy'ذվKT/ UP2,%V*9ow}Z4O}p7ޚVv_P\ѝNPGVe!aţt|OC'):Zw˼w+`R]? 8kp&1i?~{mA`ILH Rx'\uk@e>fڷK^29&@~Kyc \+'BT͉W\᫸SOL>?],YF#apiW'cu,40@BA2`2YơFC1=o)] #NCjNP[/;Iķ?0Sn/s1&hW1g" Ǝ;XJwcr9` ֙-GYyc+sJ; ߄96z 2\:9h azSsVڈSyi+<%|L4=HP'Fm~<0@YU0q\b$g]SU$zsC9bGN6Q{i6@!|֪K\og[^(U^Þ}K2sX xlm]0 4: 0d>R776h݅eqs۽noUsF;2Rd>}t{(V.b@y톼u?|)=qJo#D##5rIvF= Q}4y!z`\6Ĵr( ˜IX5>PۭA݅njðaZp4{>";2AϻO/g 奕gc{ ^up@6Agzi'};- KE:04HExԨՠ޿^Yph`$hH`FjXfP3b߁o?c&@?x &;;. $u7?%փ {KܬH1bS9LMǫvo"l}΅MJOZGPun "K.4HmrRTEנfjfǗx%ؼd쑃ɸ`e|:ҡ~tHj?Yuj̕~<35i]Yw#)S<G(~||MzFÓ_>i]vsi.`'k6_v!x-O#D\6 eGFnm \IĖ3h ̼!0ׄ\sdpt$+CJ ҈E,jBX2)ѻ"N~ u,aRP}9qnރ#e|1Eϐ_N q҆xI Hprpg&׸A;'9X дZb;sLi8p'aҝ#Rp%Õ&E 2"e6.9Ӹ8e&)"}>Ќ8&ޣ[Dd^pNNOqf w*D`iJZ/1DG rUu+XAJXֽ{k띙HdPlrp2j9 ,4|Ӂn\ .<9G5ӳ3 'g'Vz~WwqytxQ^ 8  :FDgv9L&lY*Z#H?#6NLM0u,2ʥZd ^q}q >;9wX:IxP@ӄ>in({w3۽/!b bR}."2K@M !Ţ`suqʑAʥ تMnGJZchX'۰F(il@uH|f)smmG 6dZ)[tw}FAM?|@H(ذ" >r1sRFHb/ZEKD*g4zf/t11(UV+U(#0S?*z"U$~OOP;EwvvW8dKKp]XB E@K e"LXH uH8Su ~Xi@yUC[("sudq E~q̊6yL3dE1pu$mtyd@PDB ^KZ^XDXb bh|7;D5 !k9=^ZG@ xXPj$dpIaԾ9*"yfܷp+ʹR[N.gE#'bB{Bh&Ƌ`zdϸ[x<;1(wao.5Ԣ9܇2:gMٹ|[zpzJ.j<^ ;6|Xgqax=<7Â2c!zM/e>Lְ&&F& >1-͇EQ'B +aN.i66P)6"ϒ*A#舰 oR+.LNkɟpS&' %u!QՆڮG h:yMݒQ$۔"#bz%vFn 2N}$S |$O CsAHrjЦ/Q޹PEpǶji > tV$mnQJEgZcTL@l֌!)E z\37ŦUrĄ}?_z =Vil[.F=oR(|'5tF͠!N(2]"(ˑ8Ek>qL^q9h!PT$I%tu* Ju:T8[!e\)в``vyQ% i ɎA`nc8y3V߳o5`F0qD8t+)cvffIV)BxŘ[R5}zP-BE4 G^hm? S8&W]% kթ vgnr8w3XjZ; w0O˗/ahXC&TZ3YG!6F\m?@+9Q#@ZZDTڿ8<>F.J_nn,-׫bG*, )TA/6tpPw||??O`x@ $ >ȚC_CGP3ZYPiJy,FP7P#R% 1 Cnn3m#tOZr  4Wth[sHoFDl'MoJxXGXdcKi1KxPRH\ cGj K?LJ0s3 4V‡):} рީo} ubഢ 8Ye툶3fXD;]Ḣ>|ޔc Êc|)s\)ٴMW>Ќ@D0N"xS&s/a 3՜Gwap0 ,u$YUT58S* _'߱3prAX1L4<5H#0ɉu3i[lw爋_d$A aFo%9*_su%:Ofe FOZItt7N:nbZ`—$>lSt]'p&&i3 (yHx8%'-f6A|aڜ`g=`!dvцӄƴT64Y9v&,)K>i'Yd^{"'uE)>DVFc)`]o]M1Wl{\ȿr_#[/}zOȝ喱q|`^|i7+A%A+B#ޫ_N @\__)LnK mv#8:%uρ h(:#0g9 op7C++mB hX _@q?}(O|k9S4 0TYoYf( Tye-Jbe8=8<ϾtVZ\@k6̡oB-+wYRý|t vn3wJ6 97K ܸtV)Vr B%A< -4@ϟ/a`}s[ˍ+"R!Zje}vg0;:kGGȤg1=lڶХ%!a]SӃSI|\ *5*O쇖 -KPa/,$#4 KgG] rZ|,^ 9afpR˾`)Lt[`9KkZqֶ6˕jD v%r'L v`g*P%쓉`9 ?h77;G׭vVX[@E>c?!0җ]^z0XXwnF+Wk%K)wpLI.!{/^WCn 'YbEc+Ɏ #+-a Sy{bgEQ!iy>\^[h*[9>*NhڱIK&G5{ r<tos6J[ i6>U Q'^|(\߰#8>-0<&l\=^~$GŞ ^P@ b.m$Oƅ( Ļ0 R++IjzK&` Gv(Du D+ fadЈDž%{ķm]mIE0d!~ODE$ pnd4~g:6ISSO+a/Ie8%#a΢D;!hQnrL)51R{wf;> ;.R *8+Ȫ Kk<Rt&^1K_Q`D̿)P?{{xЦA =$CRSctdk&@вҶ_hH9EH' ݞvG-؊jX91ayBq|6;B^ l >%oRJvdELӨp7l=ЪCD" K\at5>h8DHa37y+z7!')aB~! &|$T7u݇Ǟ$#YF`Ⓧ+>lSp+KMY)Ƈ)O,0A@IJ~m=~<<JfyB>tIX1==Sx|^arMjTg'ȥÿe$@NqmJBP0(?B{0};gfJ5䴫[R5"B!m>hp'B1/wnU$z}`9'WwcD#"P, _tpREX[j`F 8f}cs{#YJP-HSMQwc0B BI; `@TODmMr ?J*ʰ4c PG:}0i]YFChuG}à-ܠ j1hnp@捃N!9gaLJ ~ƶ"RWG7LHci| L0~\ j0(3zy\9p8A,A+ (dOٷ`HVcFɳ">&8i6[l3Be䘡0m[>QII&R9m-@;F  3 Q_ɞ{%):R"m$irDu;t)dzɽr#qؚEfeȺ(I-fSx psbI^dS@ $N׉?y '%wf$騌R,oDEJ +D$Rp18DBF7aNAeA|IUk v TQΨeH pxPB7.6 ?q[xׁ :;"WX)&8l%=>0Go-@d)ջ1I) &9.A{r ! |06YV>u`1"hM>"|nj2FH/\2ӆی mj@>鰜DI9cX3  &wf!H@52(,:58 {}Ha݇\@ X5TJ"B$=-H-Lɩ܎PvA'@'qdl d6 n'*_ ֙7(#e`XPH87Dah؀gf0QłuJA2mY`8/"v}/xzL6Be2c<;By5V~8|QY^;L "*B:Q(1PՐ?q}sy `T%筎/W$?FZk3L9R>P %p' hJڙ&#ແ$a?rX) 6^9ѬWs{?8$o.#[A >9(#rjhog5i܍ amDv79;BU p;֖/f:>l Pij E Śf鲄 yX$P8 U,ydfh8"=)崁blݝ/_ oeuG+t(ˤLXGP_ԗD0(0C,d*nmY?dt^z7Ex4-ʿ 6@HdiE-%CRB$cMܚQ2E 1&c&}?}spΩm565^%NAbS7I\OQkv/8awH\IEKf|@UUoy fͦȕ f.SDЅ,H%,;ֽ9nOnC@'bl߆S yI 9ld[Q lľby@X#t` )] hpb )9e\1sh`At4 9A)ΰ#n|eǗ\d}*+l}fw9]J ƅ0QdÑSMNN#WIq9/$틆!E ܡ Eڃ7PT,U]m%Lt/x2JHC: 0kک=1AJZa /D9dw&לWb+aˌ܍OܗrߪM`>KZRrNN snaF |fbCrz MŁO]˘F6i)]@؄pc=^4)GH>-Ou!X $h35YnW $Lɫz'?%S֐4}Wj't)xfY9#&ЌЗ$̉| $pƧv[Jl&'N#& ASI$Ԋ4 ̴O!6dhpb֞I)p6IDv*"vPL&]:M>k7 UMLajT+P- 8@4c) *$4=+7VZd%jU tW-M$W -E9,@޹6THMygO6StW"\gt R* \n"B0 ՐbIƣae +J)x UBگ60i;cyԉ؄}}kck}m*G@VV~kkv/b&\p&2-XDƅ]Zj%iG"]R?A$Q[X4trrjg~g<~4i89s*(L?.|0M@ ӧXé.ƼVRģzi%-kӀI=6 B6MX b.!7 gYZm4PF ID W7j2شR%J&pVd:lrbLpI2 rn&4 ȗ_0y"/oǞ(1m2lƊE*9Dˆ 'W(p \V0q3.Ul)W5Hh [__gKŵ{Qg0Y! { "=3H OU7THn/qQYQaFGu.4֍u01m Q"p3h sE7ݗQSORDEE4D{ `b;ߜ}GGyVL! Oiƫ̀`ImI@6/ZP 14a{FQVmt/٧r~7օeL}KwXП֛R`8zp]l+>5: \dKnXfg \PAN583Pi;I9uLD%/bKF=9A:D_оK7w@@}Uػ_N] rPH>#Jx I:=:`:u&];8^=297(d7EfMn_V":~ Sʐ RO[h(n`q"W lڙS DDmz΍RԒ@JUzBlSpV T0u9S^j< ;ӾSܓؒhsGfBQctWȋti$ Kvw˚z =b)%R$WcDM"$pʉөE 7Ja|e=gMd`m&Ca+WT`-ji8J!:d}Hq ×6T2/E"H6 ISGw b!1W]}vpFQH/1(h [KlNCdy61X4>Ι[reWK"M)5q N4eLxm0a"лEcRC)Hfh@e :B\Zh ,L`%5Έm&h0cܤ{SQ43WjŋgϾVF% ro}Ud$Paj>15Aj'Q@I B\hb(D`KDa4x,QZݽϿx<_Zv;XZXCrǏ<}?  HNO>](cq#:#_' ez4jό:H P,G(n0! ,PP1 ;7cr)ZVzfvqm҄ f; qC6${@zC1"N~i7m9!Eͻ}d,0ɟ}%ؠ" _G<% CVPwiֶ >1%/sb9.09I<2 SH$%rpB^JO8[qA#vI}ne0* rFGyOMbWq8V8ùQFvq~cjXBL\ 5'NN_NLK\B){/Gg8l3R ĚFKSSx'nr䀾~%sKw8Հ8R~" ft- y"F,OpAQrw61s9gE"7j s>Ts^qx CC?gnT䠹 >'xdD?vy@y\ 'To5ϯ?;~i׿<ӟ &/47?6f𚿝G% zcaߠF []a gr-  6Kk+"|0PҜۙwyRYϣ<Lo,QRB)yluQ" qBf##d[$qt"" p>Ju<. z( ѹBTbQ 1NpݳKez[e]_Y3y H@8ӳ/kk+[[H{FOIw 6r?")=82`1Suotu!ҰѤҲg~ MN:I8\Piq NK4t5X#j%D{`ͥ%m h `(ߖL!EP)[Yb%ܖ9$4bF0=#o<`)a "d+.-:ybNjO4q$v+O2"O ZKIF*0vKB{}.اG4sSKDN >! n+;şP;H&k1|mh96A* pJI,!dc4f:W1(&s2SHuw|t)cQ:EHS;hv'KT}0:ٻ5: k Ga|}6I| )β->ZGɟbԁxA<P~wP03 yQα`(8exQ[YZF}aga.#!2,_s07D|g/:{o=l,6d-)߂3g"r>_䮈ၑidk88:@|A'ӈB,vLǑ<89 h^%L?M3>UveG.tDEnM L'6/;ma9hB?4Ca)ʅh@}\F: ̸,78QB6 _ "kr  s)k9C"}ls y7[aapcn?."_ R?*%-FHE#oyy0㎼}BJl>%=aM0ILghl&#e f'F*hyy:[$9 l4/!R^ {ZHOZҺNNT[Md&nN9;][YVHT(~IqT IRmX,jx<ΝhĮ=2.)Pм}Ҫi4L'' nOHwG=9,O$-_qJ͏:9xHn0X¤`)dD()cLr1ㇺŃ[%Gǟ|>jU=?Z߂ZiNۛb>?S?q}hW\6뚊>/./D&xFR,l$^B@R1?scE%St :rgdARW2M%h<_)07_d&ee&&CKؐ U h6vLvo("ͭh}*4 Akؖ Tn6druwTìsy{BR}Z],7?WXaOn56NNmVbs {o|/3+I`tHÖC2ȐYae))R(A6-P1 FuWWWUΙoɿa}eVu瞳5^;4mhVW,< =TGRm7i a *}X%&Z+g #kAn`#uU^^Vr}(̀q_9m)`%yclEѣ~HzظXA@sC#K6 l@UBAm2$p4r@GZčqH16 UyNBOC>&c!;=BAM7ic3rUlAY[_zUc4Rz/jIC֩|#rٸ̿\N I cº \ۚpfP(()LaJ+6_HMHLΦZܦ]FNB1H9R+(QOUabG %(*ۺ-;hcҥOH6zBkeRq1e8J~HQ :|#w׷Ty1yV:2ɒS yT h/W$*aJ316]^G5lAP̮=owC-&Gҟvpbo!bɌ "t[y' v$,Xd\ꌂm+¶ȡ69^ispC(* !dAΫ?w035\ll4۠z1܀"ϣJ ㇈gX͏0F+?vh#AF`CL`:(M{HgP_r%@x9taFP~}}u| wctbr5.-/wPQ!M;dQǫ|凫CG=6n^\ _uίS(蝁Aڳb@σPL!"רEQ?EBt5pDYkE< k{7sB}l*C 4bX7&b}a Q m1z%T )IF]XEP,Ljd?ĽV$Q9Ea>|.SiCXm[hE e8!S^|œ[;GӋLΎ!UB"b$/ 3IV.󫭃mtBnh# Տح`*A"s:X"dd sV*xZ|u$#7%,aCɐ.GAB +XVn])^Đ19) zmڦsٶ/)I${ VfYVX[CiV6 w|i_TFB$xDp>WAל6bu1quCҩt#a ds{b;_*ȷ` J(Km P5U8ԎR0 Fœ0A74Eg4ZKY tq_h7E5"pM,S[X Q@mPz[d4QsH.T()XnUc8@-V]L vm7526L۲4 -dHL$?g%3HEd*,)Ď,%?E+aL9Qpbǩ}"k:,ݐ?ٓwd[!瀃pv/Ugc5b)|M5-T,-"ɞYF9",RG`-aNepnñ䚎2M{ B?e)i!+<Ĩ2~6 Zy1 \D*אEn3"`D YezM!'| U5gk͵%MMf$dWca -zQX0%ۧтeL*tlLiq4:ҧɆSUUP]Gt""W9Hr\x-r;ڝ_q47[kzoNһAӘO\A_{"ϟ>>yr >ƒNj h UNoP"Ax#gtto}O_:{r1h*Zb"硃< gkI|!,C 8s3,guxPP?Z? 76661>xMg?brДP=K=H!N伏P;PmB0Snh"p E#ŀfP.ڸ%8JGyN]SHUP筚G'N;}9ZSc6Mي:O+Vy[Ϊ1#XkJpYtc ɤ~"ij(XSv jOW<(rlf&Lp-RVZfZ(|k%=$Gk(ht?#ac4 NF"PNFkE2.,jH@S`%؛VD~)4ZʙSl,k b % $$F c&Tc}7))[̛b9!讯]o,Q/2ڎ|nr-jw- !Lu{Gw@:ZA^ऒkۯvv]S# 0F8'OY2ILn!r>V,&, a(dIw# _ίQ+A5AAq75D7|6BiֈBECYW!e,pтD&pj ~>X ?}QbbhP)cDƇF#az }|Oi,;",_^sPv^ol]rqzqkwXɚ8ܦS@wAjn(S>_Ò1|Q!ymkb`PerB]5]1`Bݢ4o@*mK ﹪#T<&IQn_9MWDz55=E7D $K"+F&0يbcBc "[(0j20tAI)Ok2*S.Mq[Bu@rg ט]ޔih!Q hx09WFL0FcP RE͈%8ESOߒ.,y1D"[$s r9!(+v;Q?C yČjNIO [4N>G]F6[RZK3c[Dbw!@nM = ^pW*s誮 $ N WV@jE0ex([ #niR;*>t3$W͸-H$*AZ:n7n&]RRIBKLd"w* f5,Ǎ޴cUPrh\kyׂOY fBL̺a6U4^m eOQkFkI"%, ~Ѕ$)8SAa?1_IO +BfF; kr22L=ɳctݥ 7lCDS Kˋ<)ӏQZT悦NOx)5Qi o YTM_\yEѸj˪ф6Y|Hh6;yҲCA)L<aαi勩Vr$J)a0!ĔW 65d$)Zr/R?(xfl+V ٟh&k-sK6hEw|áҽ>pP%Ԍrg4[sA'W?Ʌ^$zGn^`j  I|]6艠>OZzńzJ |¸g.QҙIiWE#,I!sl;FW ui+\ Tk~A7Q#T|<;?<~pƎm)Y00C1x8wutոW,T߇?333>9:@$-9[|+ƄFЩ@9;>vMHPOW"ެ9p@kqx^0=*Q!2x\4 OO8zD\GH In@ pᵢxg '&^^oo8ݪ2܍@o:w tqbWU) 8rz򂍵-ITDac@F$RLL]nퟝ_a@^z G#Q_EYVViPLB%Սw@[qR"$?++̡R0P4u$-ѼRJfV@AZ)Ae!8oĪd7*̎su㭂Z5'4_Z%|)hCQG3eԓzҰFs1]BX%_L@7DQ(J%ԊI#Q02Vmx4 nAn]7Uh`41X50wh@t6_J(,y,[G0 NuG"tO"ܴh<{,BIިO@ ůhE鏅Ld= Jo))+HGIZfEzy{= %rIゥb_\Yd:C-yx H*.%]V„] veQ'Gp` 9Shr+e$"31V))6ZDnЂ'\Op3[!,R( 3ɷa$kƓ.7dl1G ^RǨD*2 Ue$ɒ O]"}IC K;:"!BJh컴W99nV+L\|Zˊ4DB: LՐ=}p\=<4puo~}Gf^j}m%h1̧Cy!4==]YG`S$La bE+x!PQ5ʍ xi>LhI<&dm;toؿW@9g Zgp% 8pstHpJq.NNM aÐByUJ !}S[ړ\1Zn+qy( GVnn#Bp?lΆ\٘ҷTHH~7 wLwFAHzw` 4+A0k@ePϫ>{ϯYE:^Y8xG&T&UV?Н$9 B;YBNq5^V@>G{{Gz7?7M6RBe9y捨  /_G_ fWcQPPp9>U&Q)BGp%fGN.7p4%Qq %VX7(g聧`I Y)Q/*~ŤG _ďw"DZ5aY[;]M65+!-֛*|Յ;L!sKDm~ w9Dm6AIr2?CnEWw7,Zy@|gޟ$@T*>k\SVc3pDg- cbUMk&.BI}ZZ[MAJ A%e9ŷ>)nzK4 XA2m븠ΘLM IE*3h86(/\ۨmTظic3 RJt4V[Nlb5讙}sA HZAj;c n@4C0zcDY m|?D!xr=ȶwpԫhߟj_Udm-׼{LJ\9 pKLQ&\r6:_wg~fT.IXT0o)ZVB|NR" C ՏmfLfd\<'լ[&p׃+ w߿>őyC8p<%6#pyy둞`5T?9yڔR{?p'g^bWp@ߋ v8nNgG'Ic93:"d[Ċd HLY\,DL'^^^;>ѩq u\ =O\ i^4`<<V& 6)9Lȋp0L|F#X_[?>:x4>X99;<9¼Jػ랧0T%pq) AuK85@VOqڙy% j[F#Bk{sz}u|hZL;9tb'!1S_0 n #x,*BKIv 4bp6Dr *K2ƂM0yae4.AI©LR !C8 4ֱd;NQ6sgϞ::,.:8_1hD !1+$R@%-R1Q4ScF:1;8qtѷ{rz9a" O{:W}C=x\c}S}s==W78bK*0N Y!u+H@Ŀ29J{t"a.麬ZҺ"tãpaZy[ "BZh'5 $ӈؑǭLAc,"$3捑ˆsjn]E[vkP@iU(dP:6`D畖 3m]ߧ>=&z M ˴kǶi: I[vUtq__ZIJQg,1/Z[uGzLNYP`,; QI,IĊ+BGo` Қ/O 3h\6=5Њؘh #? ͇_~KaȢ[ś] "hDzx!ָO,j$Ԑd2H.J֩k{V|c>~\Ս w &jC)M0+2"-!=rMh3Ѡil=oI#h6W:eUiE6)%w CSl'{OVP/\(Mhl5|\F@B>jhvW)nn@f,rt(PW)%A}ZjPX^IHX0 XsNdC `v̨O%|&|%TEp2W;=-xʄ5\Rn6h,XfPASX^p!t KcciݫC>eƹsٻrqvw^xyv|4?" (;Yp퇷7gՇ=z z><R&;:bބ&~dhgxdn\b}ڦ>Ϯ.Q|f:{sGp'1ٙl1QdbZ+JLsIz3!cV"سj&ѥŤǃJ| Z5re6{GfW-.8SffR@Rۈ3,-G:q*q0528c+9l\OG b32Џ,8?;>D>9)U{nC7#4p*&bI~XX#A)|8T Q~H@I_"Xӷ$vvpb?ˇlkslQߊ*Q.?vS f]r 6O6^4ye4ƴ ԉ!JK:8d3]^=xO,܊oe 7Pkߟ44eyJ,5ب~QTH U*5NoAoQT`µx϶ aߌ9H7%ZdbX -͇.5 7Z.I0cj@h 䎲0yѝzAIN^R-Yp|zkB i.HorA(5 BHrWצx^N.QR}"ւì&,aiNzߐs V5vϺ!mg)GCYHeDL}fL=s+~![L\` wxPIEǸ0)rf [Q#uz 4G #Y<-&t\"{QG?U+o) bw15v;m!ºMqݽmG% %T- mRI߫)!QޢJ*4V[, 'D*c3+iTThqO pr*9X4ls/zf~:q7m$Q.㮍7oFGܿ?2jz󪂗4 A rH^ < ?88>:~{sssp"q\ 1ģF CQPQ? 'kkcW9?^U5 ;0|q{C3F FGpw%E!erhYI:E1ZS 2 `bHJT쁃fym>  '8܍06Iۈ$l^CC|Fic>67=<HOhb!Ḙ@~$4?6,1fGQzaX!B R27Z,j,JR9aCYNR{Dfd'~cE7:՝K1VĀO@ta}FW~.Ť&C}4z<ԯd"T oh1wb!q\`'B}߅ C ]ͫvW([0EH#i (0{{ t%yՈ#Sؿє!3Y76o 5ѷQְ-ָtBiGRHL ݦ;DOެ@Ciޚ[Ƚn6=̾=Tn$nM"FuWSNEbAfXhϦL[MHO1 (̣溳)U# P6I) HI]rD8UsL÷d1e`/Nb៍,! .đ;BZ7)LpDMBc^*hrBOT)?-O$D_YEx.l],˽ePlз(4+dX}IⳞ[P8fH(JH W_ Gh6>ՏЬr!ﯽ^CùY@>ksGۯ^a &~~&c<2UզZ'[㺼.9f@bZVdԲF:܋XnvϯN/bj|cx 5ҔFx.LJLJ777.{N0244/=4+/{q2SSc砣_8)3v4J4vls~ m9\/rs{ydd?@3p}E4nVVV:-[3~42 0zcHUϳhEn3 'ǧG>P#Pqtt:"v+XXN܌E}`@fzj+ӓ#pa5 b2.*-PPcQb qj%try0*tFGPB >&[f6d!0@)< @q68>::=@f R$ !pcY~78+;8ZuP?UőXS O :87fQs`G 8%< ɐ4?-;?<?k*B9 ԡ4D89<81cPZYC;YT2NBF@Bz8@D%Q37?1 :VF4'b!8}RCqB=q[9p;R/Q_ kh}VQ)utdH Ͽ6p.sBK<3aC_5PqsJxSabg )Ր&x4e]2`A( ]Xͤvk髦j e460녚'^7 L ݕ o 0LC5pĄUEu3c;5K6 JQւj 0B وQ3}[X Oj:z6Gjgf5kx` Wʷb.T\[CMfw >iwy/M QmUׄ$o5U#X" ,'rk2=h#۬껡G9ȺEJQ&`-C2;Y>\??B~峧 \$$K=W0ʹMb|3t*xʜǶ~T(OL \Jex84?=d?I08 ow&=X]XyC.JW5iމPZU BɊUH p=U̜1\{O>yf~>7[,rsc& /3@!񾄫|4*8#K)bj:q 2tp8'S6>|02L&&?1??4z8.J,ُyd,O<}2?<U\V=`z"BS"' 9BQ +_n]}scW'fdB/TiQ@GV6,IH:x" }ӨA";pӃ ^omb*BP?j1/ g@; MOȀ0TAYL/vpXUpGʙ52wvم,.tF 1@[`7+>:mbr$r% ЄfA"na vzl勫Sl댢U6ALC8g;ALjL1-.@gxp|jlY%(F/,Cl~[-9>twǩ՜ bjx J+~5dD"ȕQ2@0ΕPS"gRwbv>otA*Vit_\ݎjBuAP[8n9(#J r 8z V- 9SS@,FSd)1Bsƪ_%#5&X251DYe Z6I|%"L56r֎M& n^Lh$Sxqk I cB1Rk ݁RQ[ iz7!tQNK>Ub#ZMb?#xe xdYQzu;;Uĝ g+H {6I%a.#Ԕ45 ts 7fLX"?W?`v&CJiWdM-2`MG`$`?su}#ص,~aƧľJyD FӜ@i*&Q ?Z_W9Ld,q}|xrnn#${Uom#.&N4\ֱjgt}#N\ ٹWxJģrx=Y ipI;;W'g#\eo0cC% (O:"q~1G  1DY{|b7 }7\A*9v#;؏`zpz~x+TB]dBPG$E0֩1:fTyyFL,ֿ&F,R8H;_ny88#*ZboW!ߴd[n&*"͎/V)"'84sGrD"vX\EPG<>y%7{Ϟ>E{:2\"c'ǁ <4w3#Wvv667/Yƞz&Ǘ;cpyc"us 4 eb> /AzH^|wiT q>jDɁa)(*B"1ᦇzy& Nɕ{p vՌISSCx[Y]<=330~֠qI'G(z{D еV0`eX;c|p2*ibaY#`M|Ņ鹹ygfpL vE$'ǏA:x/.4Bd8 co2t|^u%væbe,sW \kP, u5=ER0:L$0%bXvL2,k,i7Xw%4!*\ 2r +4>d} iѹ-]Ay7r #JIJЭTiFыiչ %* P2Qm=':qȉzt,X !4R4͕ZX 5(m\|DD*fp&bk㲪΄֘ !4clXP q+Us*~X"^Z4?pL]U{hf49;Y82AQᖖ w@5o*ϵzen 3ʼnb]yk҅rPS&ՐƕؚS}l:AX`-O*!g ʳAdvaB ¼9[A̿^P;ѭ'g HlY︷D-9 !$Hw[!h[o h-p*b) &f}+g1򀇖$Z)92ڬY j1Y@Pmnžs -U{9__P1d-3YI yT3hᾡ@MV0uB‹ RC;FcI439[ң]G!)=D/t_^ՌLe o]V9\3abG*á9 !'qMb:qa:(~11:?731=^' {[r/̭^$Q*<,zfٛ0, &@nDg38A nPTd@:# F{uFqfp/vIJ,ɡ"ӳ㓸$1t\ _NpDyOȫ34#II!l7:ʣ$ T8*Ba%'7[Z^@a!V wcPlT8@fgÔ @R*H6U[fFD??8r-NUnn(퇖)rEðڲ`pl@h𖠂V0h-H[c%hpyiEW*u!J C!A1tꡇ2~u)C34.W7b$%Ƹ/Gl#w5"?c-j-z1z,O3?GB|T o2J]PbE'HM{i,"n-'Mȷv0ջpj>)+XlHX!k7#&k!,.іDKHhZDj5dqTxU6QPߒ86bF_Q_+ 5۶0( ׀.0٤)ΡQvHpMs ᇠF0,$,!V C9qJBzDl=|tf4l*.T B^z/ߕ|^g6T pi]"ftϔE*oߢY%`ʬ"B%%R&>8cN5Vݢ/)]Mδ@QgҳqECO$r$A8n3{SOO)s?"-4k Xnx=M[O1 YE Ԧ__j& t]:xlI1XLRG%֌|.> Y+l_6* _UgsIB~qW^>ul912==:==)$sVnxҲW -.[Zka(p2tgAF0J a:QSi< Zm4 ˗8``sO6?=:\xy;ߛ[Ʋʼnѓ{xyaffGf'f'FF&NOvQFKr ÏCW)C XNUDj7Ng+*SL"UN2yb1;S\=+ 1~adNV=;9=7nس~vu>4$ձ9ӏ^K(iT<=3PBƥ ( dxj#Ai`s2lyEIk  hau$)KHʫӋ7o? 3aċ 'mD89 ּb1h 7qY@G!:dJwHM4H-ݧ+Fܨ8E(\T0C[8Gpalr'bw ytˁѡqA$E@NC 9}ƐXP;0Y5];6u'p)gPȃ)2MYfIn1|Ri D2p.) k[_aҽ{ȮXBnNe@Q 0/#d7Ȓ:;=A"vS4]cVH9<.,EdPJ&2 T qoBPT*Bق:tfrǤ =ӳeMj:zE bD4m54PVe ~g4k`Cun]uJ`>ʹ~Qm(6]V4gej؅`,Rn9/ ~f5&`1Tݴ۸ =o+s,y:q \Ͱ{ms?]YtlUFB״V&k5][zx) RfǴ\z5!] C# *lh&ÿrP`Տ%ہ4l :kWDcPx:,iRtdg0|JtKW : GDd|NѢV̉Jbm

9>:9C 'u F(CҒ؇_b6o4-D¡ey$/eM L/nYGР1=G㳣C G(O06Bq6֪P.5䲰LQAgyS~]s&&#XL I4-ms?r(P폏!46PrZGOR:4⼄~C2RdTYQ@a)*_ĎD{Q?iވX\(XIdS!T;vzn+0V@tf)Wb9l;Ĩ+mtH]OߓRpH,#/b-a& ǔn9<@7\^Uh+9gBU-f<e{V3) ǵ~J~Jj՟ZmjȡF4Vjl8w3[$THul]dkN!%;,9e KK?3zу=OT=/?JLi\21ScEC`eBjqDV%mT{FHI1 ! (nD\2RHB뎝6-̥:]o8WL]F'p+a{4K& >#}<rֶzHRcqtzMKA[}]+d+qG^ N]8 *  kFBqlΉ)5av2za;;ʧ7lX.2YG3av2Zkhܶ-/U9چC,`q`p`dpheu 8Y;NyO{/Ngg>xpiePO7=C>Ruۻ(&ѹ&P}vnp` -OI/fc &j@#󆏵z@8zG]bPcAh"ӆ )&C AZ~34=%dž} rv dhb` wD>s @0Zl_XXijmqԠ5EJVt[(zD2Jouyiytvxh% F | hw pJ]w\O`ψ˘aB#*ݫ!lqyyxL3`?3{Xy* jOÓc3Ĺ77cӳ Vs1xuvf{FGqrʝZ+2hDݥ*H{ b Nd3֒ĉZĚx\5A=df,,pbJ(Ωʹ+ !PLǁD9QczsPJ^p{c!jh$Rؔhy! A<4 Br&VڲQ?G}pF"Qw+T^+ ތL`ZMIۮa/x5:FE:A႘Vt_M4ݷ#5,CJJ̔BG@D!nƄپHB3l"2}R^Tq2[ Һ*L}C똉ph|Hp#d)C(r-g mZ.|2֬6j})>ccUTw+<F 6IWxwC ÐZlXSR,^l.2i  ]Wh=!YbVx?-hRz*0+&7g'qEU#*JN2^{+K?Dz<&ZF5$ 0~RzCjt}Uɮ:?ocù9偅We9v:3rUғţBa@?9` 5*|!{Bt"^z}#Y>X3@!}uJ;~AH0v#frtC}zsxKWFgFQ?I(Op` R80B!׊ 3DZ5m-07{pQ cjGFP06L`3V[S$bG45mG p\繎ىG+-/b/$6ln뗯 Bvc@77P99E,'Xr>@ \:EfeD]fY8Ɂ2 : gO~;(Rri `K 2U_`X,g}(V6q GwNΞx{ @_]L[TPJ/Ư]*x*0-e &5ࡖ-)Haŵ0x[ AFl0\a7+n.[ \],L 0(A,& X]K,Rs(’`T*DKB{tJ,v \%T]\ޜm Pa :B qAKl b5"WQ5\ 'gjNxuU3-6ܐא(#k3F mQl6E)u -괷S *2LE֘cIBBE`I*leD\ў@SRLX\Κh6˴X @I$xbi5^@(@FWk3[M"T˭Y^H2{jR 77?X="B\EvCKPi]ec )"5zmS'‚5RCWI_5{ W42ѤZx芑Fz9Z7Ub{:\BFO7V/T%#)kd{6]axC:aEᵯv@ѱwSArI3tF }E94-i0;P-CĢ6_lŝx!^ɷ/ĚPM X_<.ʘZnVm&8`bt>O Ou~O?^2@[/va ?g?_\^GԠ7)+Sxa!n&{ x9jY "-+Z0DzM>?7'WG/|rTgzW$s8$$;ZaG8  m,./vAǽrQ}8,CU[\!3n}R2qdcFJ[4t*{JEҎа^[#źv*9CL^Ei0)%GYO4f(Lb Ȋ%`m D[(zgնuXOsբlFY$J(^?J(qwp-4x=-%۴)k8DFg @;kM< P?ꞟ#HRV͸X4b7c!wrZd-tTp5 ,nLIzhlŀ&3$?7)8IܸIe4#C |.P!R,s j >)Zm \$I˰lsty [Tλh"gߖsnUfs* Bk%#WBM<nDiRg%b:礡.QdS:in-vq[sg 1YbȞC??P)X- zlBƋKMmH*溂,x, ȫ`fCribe8%BC!RİaM8>'Qi*&xśV/̄0mT[ IџP~0^R-pVT*Di7#˔XZ\s#D' ffv'~㇏V"[@K ' 3)d[zb{]0aaᦔxe{Z^N4UwhJ1k|s-2bɔMBvoҨ$=@v:Q)bv).x|eps/ #`.C m a)A뙬"^5sp  :` DTLQ6xϿ^߸<Țþz,;(lxg=Ggo}bbefk̙HfwfLNL^@* x̡2zlMTǝDS j﹄?p5dZK87'bNRE*"T!L#!*<tgyf(G ~˗/!m"jT)`ypy$6_ `wihו+A.oL>@-9K8dee'\ 9CF=xqYNǾ/ΰd *S#>BXpt'AK7ưv57}o6ྀ>@%a&Xz! J_A)@_BWhU0сsf]v+HWPa !>%K<τdϼǮNꇋt#`_CIXX! (f'=/Kڛ&""4ə幩E:8z囓cdSX:Cr8LMᴑ5wBW2'JK:Ŗcd9`3q,{xi)(#B^Ť36BjB"NN;0m蔔ަp!R:Rul R$K[ۡCLͩ@ Veh3{1]L6 kY$"@zv<4\D*u?ghc.a_ГmC]F̀jc`PpƄKШpeK1@XezJ^Vp^0H3)FK' !rԀwZ8Sy&3zhTcJ z$$Jmf a^`.IL,,I@)i|{4|6uZ"hb`T"EYG92`*H_T^6kZ[ԞsưiƞB 98 ^ϺךrDe2. xC!% Zh1]wP8S[ $/l䒼ZF+5 ; g6\xս^\717]i&jQaօL,MZ`.J9fNVirLB"[D҄΃"}-Թ3r[wBq B-k͒QV)5|Ã᧟m^??:AUAU V5d(!0=B#z`u6>s'>~R/_SY];=B l3w:\1~'ZpS9MS)s5c*\f{n!Ȕ#&au^@&²E`%eM vQD>"{]]^MN,ĽP>xg/vwQ)Ro3T9,9? uG' ./Q@a}}˗kk8cDvW'Ǩ Aɔ50;J 5D/;#ԃXZZ@~1Hĸ01P<;[۠8דA*FGQ@FX8b@޾!|=>y[W(9=98w"ږg7&x:8LW~ Z x[ l S>BZ?2$ DD@=ELGD 3/IOp B p In,H,)#XP Adpjnzqe"#9ivv:C}e:1$-Ip^ Z9 @h#'EՋ8Dq={w1~⻮Rl;%2(.%3aݔ1(4Ɩq$1Ess_c}q/.^Tbb@YWM(>^nR/ U4B Iq<<WPy<&n[Fs˸+`Srk(4AR%t ApC$XלpizpiKWT..{w:s뀤f\R c#c7vvv] NC'Z()8M xȔǯgۇV}3Ks#\>I&C௎CMT`mM્͝x7hE&L LA ->sw_ 1\P`pDćxI_&JM/fS$b mm\OtG yYECJ`+.}'E%lj@<$8<}?'O0TAWNOl$d X)nC93DjPVw`ӣsF`C ӝa>S1mSRT)a~E7:=RK0P@x h@@Q >r@Z;ozexӈ2(v$GV#6;#GZHt719(!ꖔ{2D z,乸*MŘ OlaG1bdP*dҿfD3 PKb5qɺ- SPa0˸$DomC$ =1~ۣ z,6LGaXXa#LQ!dH,RxmdPs&r܌ɔ<tOkܓhFM5mKi(6/=~c!DJ. RoY VxX1}ف e` _kdY n&LA"ƋǞ "oZR񣈱exßigu19_ Uss n?"1}ІowC̿kH78VK&ut]ݼ"#z240H[W0[WO 5-Dۻ\.wXFc!:@pړw=kRJmg0W<܈f"V! ѥC1cUoXtGlx]^1 *A-β:;Dz6NZOi^RjE n8~bVǵSgxπOw[9$5[`'o ɵDEIX 4}9/:$SBD+hG{8Ac1Z F65-eb)Z=Ђu;ߊZ=ߵx]` eIH(y+u=p'mL6&cLU»$jR7|"*t΃ʗ*^- |;!J\er7jґa#/rRu^%W}8q RtŃ;hYg'K[72|c.Z#~zv8#r{k1aR !_N8ENU덽'_y5r}ʣ{ PU4.nG`b])`ZZtD)29pL;G)78O~rmJaXZ\\>:;ƙr/5?O?Ͽ@v>w/_BZ r<$ĐZbC0hY{62\./`0_?koo;~{Hx͛ݽV%|1#3G;c\C|a{{Qbcx}gL|xG/-./[72~Fp– 3xV")"|~5ECH.kR.eQJћG]4)wKZ!I/c1KvNEڥo O'$V`V@WLJHj:Œ#gL>B(HO({fgfPr GY{F@)ί!Ā3&5֫#az*1G/݄ Bwےdp<ġo"D '~%ǩ=bIg@#%JH7˒YaQkgIXs`7LJRNodخT+7G!S04h8aqjCv3Q09tC4ˢM7V1ͫ7rhd~&R 2zX.a_?%KT=CԑpHbTcI̅PhZ P| #D;l(u>ka&ߔ4at_40 z$ԦwXG Oɔp˨,.AoYrnT_RqwϚoiEi;(" LwܝzD2rTEden.Vwa?zއ}059 ٓ/_x,w 3A֣U|om/6^O\'gHӏ@+}ܜ7R$ NT ^5EpyPc0*u0G hE'apˉq[3t xJmIU rV(o( K=3X<- Y{K 6gOPkk-ˣӓ#՟>}W{+ht~7Sh]nF\LBUT'goo [?_/`wox `xh{N/{/wO^lm:;=Ʋxgnynf+,%ji(1ȱZT[+wFYmzNPє6޶ bR b4a/Qh^ew'R"Y`>u ,ac"ťtpՊf Y yxު͞bpdtz~niy' śا 0:;<%267677q01 u,QK:G.B8]UN%Ҭ.Ryi1=YB˩Z+aDF+Ms)m",IqJ 0C\QϹQ`oT5Y7 #(FQlEC3\k[b|K@CYH!dpj $ƉвJF GAvcL|hGƂBi񹧡;1=$~Z Ө "Ԝ^i#MBAe0TZ~ABSζ{[`xHҿn.o2\aqI AGLr/HVZ̨5󠧤;Q&~wДWNe4CтTA%[n ڜ~ dk5f8}_ 'd) H31cRH,Oz5@cѯ@ش\ɟA~5|tĻb),ZY^N;Lhʶ hj!Fghr:Grх9"~{5r-/-n}}\TR4&B) #; d\Fn/Wr:/N” 2xO:Աa$khJ6+%nebw?_[ׅ3s\(.<&3*+"ف e S$ABmWH(P*V/ȧeBpW4ϯhȣzYs<=BTgp~rtqzbA×/_>{|ksJsq'޼^C`Bij,<ׯ_xyIJ{P쫞G`#p.5.oalggp~Fssk[[G<䦷B/^|x1탃/7lnfp}hmpxcxG$̍ܛ_y2>3 "!'Ǘ瑄Aq3 c$֒b$iFBJ$$]R6)rjonR MZ(//S<"FC[6-ϋ@Yoz3L{tg!G#xFF 5PLřɉ˓7o>?@F bGl`NN^ʤ 8&ǐ-3'@-)GXi+6-h4ҙ7zec,LŢ M$cюCEn__, &8'@Xe7/KQHѫ~R XOxJg+8V2dW6FbZ!m4rB7!͂x4g!tXL݁Mg8Ҩ{E MdK@9. 5ǃ)q9$ 歇LزKݮ$z&(XWZ$R(ɥ`yhpӞ G$gvp0V͜4l 3]lZ1GиGVȢR$:ِN5@!ZkǨ[b!K?Mn^L U.5dcHg,|Z̲5h=|W@]f>ABaAAʏ3izsf)u_Op46AjF4v) v w7(r0tUMZ9ĆlT7f(6@vWѸqcA!H.(]/(t>5Ѿ,uxWZYpy|uEd!0a^'qioa 1)YJJ[_(_c#UMMRa427sD048,@5eVeWt6 ~IG|,6:1Bwv` UBͫm,8n>T=,n@LT~v´Nn̨T🿩mt_jOAHÍUWZl殯[X+1C='q2&{%lRUP/R2uؓ x%)ʽw?B~d{M'U- Hx0TÇ+Ȝ|~xu%+-8mgf'f+_=8:1g(HfHʎn=TGK++xÇ8mqnn'T6Ga‡fgfƱPolddHgc$>D.,mb|lr #|ŧHg.RFwtfvyw bff٩ɉ#A^RHufpw|!ɚ;%VZ=5m_msLq!.J>+fAEq!mH̾!1gW`~Dq8ڰ-:?:90c8neVbRF(/ܪPKfc}#( 9F;=9Dܸ8=@҇>Ѿ$Gh'͢HCkCqGdzK+ &BH* ghJTX~%P?T!RꇍT  ª4)=Pb)]B?=y 2!/6ނ$LXeD.HOs 'sjկ/$S o]xéܞpˋٚ.PXYfOr%jD6Ccg_- WC$Кz%7W":F?jCn!:Y+3_ZqJd,6'غOˠp(mxUH*17\'*.TSC%H4CpR>0% PW AED׳݆<$TZ)&R}7{e(b# (3WqKd/̭ߞm .FkO-)9:IAnqHʷ Bӥ2;܉Ƿ`;(_hSDw Ylv-QJfrrgoz}b٭LS(Ъ]f(XtYXɟ: % pa/|`kڦ󣽬DpGfgAfadp)[_{pC sَZm2) Oc_ڣ>|83356BMxB?~@8?99=1208-RYݥHJ4:914

33 $mS(c"m;|}"!L?(/.QŧVIcp5B 2# {AO(742| 66壳AeGKae`l,10<:%q2&qx a }p;+yX"D8I4vI;U&~˗o]D?~u$)>Y 2¯Davz.x4+D  e2{N@[{DL>89~# B6)FJUBd#`:<&P: ^upp'ZyvԂiOw/Չn!"h"DA])'I?;y nFNw{x][%Q2")Ei&/q +cA[dDe8h䜒<$H4A77E Bۍ"ȅb\\\vhđ'(܋п>kP_:V [)WBȵ E$WS1(/2T"*yaժȡSv_X*ME؄Ï[qs-5x˱'ƚ-glkȼrShtՆq&"F6@]Q3 njB50P%2i4C#@ )\F K!)ߛ-e\AV!c &m2<4dw-ߝEVAyHN0EЎH 7I:PrAH4z6rznܩ|tWE'^ifR[k* +"FE"鯋lmx|! 'jb8ߊ3 # n7Wq$G|L(1Fow3n`oFΥ6HoF!GB 㴂ʷU ׻\~ge:_ _Mg8gԾK ¯MrI+o}?ٿ[wmO*xw˫?^kۍ#5(22zu39c~/Q *ڰ^h(6_"RG#R^>S\HJ,QbԷJ#,ǰ__729ӈ+s5)z-l)#/, 찎 e\hһtIuƣh'Np2AG޳TX@9s8 ^lgQiXNCE%G_65|Ox/ӽOnwgF>~,x~ruyR}&>* p7wr% |!:L V|  /kZt"9T'x}df3`ƻp'h V`.CF/a5 &S0G D'3ң}E W78(pցW㴅=@r xǺ=}xؚÃkU&Fp!ۯPCKlY{\< utKitN8X>?Ef E`/~ V\_#܌:J䱚\]׾G+H!҉0X$J~+7ɡ5 A5{Lx44,)\^S6Иl[hhS;bW8uS/b]7_mǵւG}jPj4Ln~X|{ %"b؈knLXq^}|qb:&o ?e3L7(6 k D\p0NJE_ee Ÿx2a5eb&(0phmzjP؈X͆D7h]udS5Irgoq[Zz\[  +]}[]D$4 fLǷfYg3eJr;KTFADJ(6yu[W4Dۮ0k:"p_pEղnm-?s2 (!/Y)G 5[4mɟ|_Mw݁ F PAU#Pp| "1UaG:[.=5t=wQbŚ?S_`ݹw/./,o](t>W`~%ͣ0'/*  0Бo/Z%1$(a3]|ʴQ##jBJv檙$St.j&u":4TCZF/al6mm%0Ib5fQH̑y*JJy5frXkEIk:0>@su  Yoj[_ t:25>cbu0>285:8=;֙¦I,US70|7ikaެbYL>#rrr0E#vO/6vi;FnxS3NNφ+RM^Mu8TW ^&g;\# I=7!&DW 96F@WgC5‹~V@#;O,Sf:$[mG G˙$JĴ.^3 EmwQe2ϢHfUeСs)@YR!$k-N@,~rFw^AaA\_{X# i>RF I(!:2,/xd%83Sc8ymthpia~=1uI:dK`RGʫɧXX)}ED<@Gj 3 -rrɷRN6Z,. (&"΀Uؑ3bt1|f*jL82 H(Gv$%=0]Xz18Ue挕DԦ^q4[(~Yȡl\WpApn=n5}#%<Ԝrssؚk\+Wųi<҈3X3f}ǤNsN^"N0lLqVla6SԁPvu@FļԑJ-Eߤﰧ"sI ;tRso)Y$BL<]״@| 1X)"Dn" Dd-C-R_nͩ+=%,C" *ڱ7f 1"̇qLΧLaA{Z;ڹ+eBFh*B;VZb>٨NE`)ʒ@=B957.WCU!1mSU}_ KnF[o҆hr_ `za;zj]ѥd͚oQ x_ُqJ|'>F@Y 1ϿxGyabr(hhɺx/ȭX5Z#+~t4ca#֘nE s.>c6;5XoDt^ .WXZQN+KK:̗Jl粒 `wjV+/XOz"wb+TdQ$ca +/bJ ^ 석k;Z1,V֭ ͸= uXrmz}\Q[-ëպP 07t¯p"XK?<y.xx|U.w+tmb -Ey>7>^h臏V1xu^F:}(xCo|> 1HH0.{Q>f5hK` DlY窿J&LOȹA`ŝ)<(uW0-6ekZgTGda%;nqњ "d-I]/P|ɉq[@?`xGa-:iat3NN,H0j8jhLRBlP 8!1no(HaAФ GC4' 43F`Tρ:{gص0+< z p66ܐ.#=׈;([P:4Pjm!8+r@2QԁSBw51f|dU;C|r %FyigH0Ǧ!rTOe~+Ma^R= 7JHO.̢1(f8ICI^Be2=|FJ t1p 4cZֳ.8\I#y\<5T+/K~Zpe 3gb*isg4lboQ-}&24n r74HS B.ݦöBfj@P/UCѵ6sSؾ[q"04bv%x1(4"|lyVCl˲mj 7Z[uK3f2$&pjR'PFC"^T.ZAn[5 )R䭙xɨ UD,69ݺ֓o?!>wtz -f+ÐxS~~zk@T ~~~y [nJkItc:T9rCR/麓_ن%l%ZgJdWRWcKSIKgmjƌ qsí9eUCYD}<EEG*Id D^ ֿ0"=)ց3O`5B_ZW_$kG͵~Nr[￷SX7:80G^{(ǘjUo._*uHS< DN/bo JA\a%k7?}nXڧ76aJ-L 9 u$N 6#҉J/4 k6 9j\| {XG"v "Ѐk#Ja"rOعb,A)xïV/ttFQSN0+[e"EB34<9ԙA}Y/tD+4G),tH@Ŵ?m^~3+?zHW1Pm9dR|en0"y!=Fi4!@8h`xNa)U;fE D~HTW$XFPP5!Ph05) PA 3hLaϸ ?!{z@WŀtzF^$ sgcTtjKd YbC0b:>`>| u @|c;i2t $ 2F%S%u9neGE, ^&[J/Q-5e~ɏԸl6jxu,>J3Gg*gy-S ,`$)_|`,6AK+-%\)b5 gPrZ^pl/o*D1]ok0FQ I8,eSV 6sFxgO g'JMu9_D0HnSV@>qL<hPIRSt#ЌEN6B*1K(ncZvk3\qVypizk[wUֈ9!LF3/ǚj>񶨴E&d`d퓺ƊZk^Dn=BlSmB<_u5kk -DD#%E4U_oP5t:(H?C$oN'_HT}"ƬxWZAF"d`§Jڢ噙idݾ/^?/?O?'(߈u}e/+ɓ?0"t``mO=u)$@hҶreY(p @|u7a4iI^ J8P-adhc*_kS)yН?rt-A;EDs& 735Nc=2;Ƥ 5H @7 1\MFʍ)ۉi 'hG=h:@%UDLu07ծZr ˇj.ĂMHe(ERKRGoGsgs`8Z܃gKG铥;E賚3ᰠ$p)=F_<*81j `YHStP5\ﻆdžnt' ]%g&5>,/}CoF{'{ƯnD6DufMve`p,E%\ 0܌z.љv;/bM+vpmyN[קz[?!ׄC;<0zB8xR$e[0D)@\֓00`elr>#//?o~gpx dM~ځ~yvt8%-n1mL_pG@5_/\!F!Fp.P7nR#Xd@f?<҃+8a C$X %=yP dё&NwZFf ~na xP޵D;^<2FwNÑ͊&9(yx|U@yymtY.z.{.{s!Jg~* $o3IBYil~BdIA1;GP_qӇRg8C!$b0b RUO4/XBqQ Bn@d2>YkF#rAtĜ:.VS9M7ye}7A.bĂ k Pi:E"喰XS fLQ#xef<(z(5 O10M/3Ԕ Cj)@! wtkηjrI$&BtvٸuO׃dl죻&N$!?-Sbj%%ůB-JԔpBOA}L»d!(Tm27i\i *Rl`CwPahH^& .qɈH 4QV;Ua:-5P](-[[hBT;zyro,[%xf۞嗬1wm+~! sDރR.CҪ="ȫoާ(H9q?)G{o{ PSH7V8bb|mOmm~}U GomXgqÞr[aZ"I$b[-ݘU|#դFB]eQy`fKEK\^ qw3i89h z'*,rz5_5WւCprv$Q DE+q9aSz;;9`Wq<˗KtA?fؙ2v]ze/Z ,_WaYW9=peza3Fλ*Oj9N*VCvpms x\8|ar{:{mC33g7k{ jJzpXC8eu&n!&ggp,WiTE mӶVφL7lD^HVƩXnzvvv6w 'p$NTA;quZ2m('ggPa u+Ќ*9:9FeȥAT8:L/{>"3S5EuwHdʖ 1E?=Cnzٗ/^? (m~~ÇKvN_o ļj(E{Dqgt*  Q+v7NI0%X\> ep8% hƚ$fefB;ň0WeG)PqIY`sg1*f haDܼk"Dw]mn힜#@䧚gPmsg`'8cCd*Pě{)v %0 F m*CY! T 67߬게\#Jr{ }!W28]'(NZi"G8T_7;o8='| xɣp|Q8䂿pfa\{D~-VNE*. Uťe )G!4F%/_$d'DQ.9"TZؔ 5 '`*j۬ 1?bFy@Ijsw+r bEo=* Ra ; A+_24B F_Jr99X1aQ(N4T\d[CGJ jd;)%tIheT0  @Ֆi>rAqGY%ž3(`0JBf'v%KRג&~g~oXXfSF!qSL%W;*t_ϳ W5;,x7Rq۞0E4)-TJm42H oV訋njI)`ºpBtnUR(9uqH+q}T~O7މs{` P}”5FU;59Ձ[xÎ +\R W'ol?{ve䄢&'r >T슃Q3 :C^|cB[pb#'pn֯گ~Q-.}[E7?X/.V-"YnMDh@Rݯ?phfR6PmOݽW6_yG9~;?_|Wn+^ŕw*-ye14E7P )I&!  7-Ny(rwJZЗ#b/t`[l#Uh7CDop˜½>-@b)) x12Զ=7cxUbAI xuX^. @r}kpwgtayqtjJVmұ?w$;eG\ r* 傕ߴˤp}r(Y86114:YE>{3E NϐS#$ +cHT91O^$qB&qzJ'zsM60$(@"%\E}—.*$cZߓ,TiJkS:g={;{ A&(Jx_/;vPG$$qJX rk:C*zy;ۧ;c gN..vw uF&fg;cͱ>#O;!Ʌ)OmGχAT7}ۻ8wbrpq*&bk;ϟb?px"ND u=l@|ir uPb#]3'kgGB8׷3:dS׽FKLX}g5JRmnnlno#DF&'A#v:>ٔ;.* v1x?oGM^5 O2g_}wfn|;;;;AӃmE % z<؉ +{'G'[ p/8=>?;>>c4v 8{GG{|xp!bt+g< hwogs{óC"?x|pggpo{wwv7͝}6v7wqe{ksmџC-"1A{(߭uAlKq Ca/F 4rJ&U0P )-QSOmBT!teWSAyV%}!VS96YƟgP$8t2s]EևŨ %%0Z6?B˭Ղx*^[5=4BJ3Ly)϶1a nIYw%"$#HW ֊QH4 vdNDj)/NsAn*yR2 ~(]^sf8 z”zB扊zQSŕ3 mQT_qfd"XnQB?:,-Rt\.% XM"mnh+(&eB ożT=x-X"}BTHUц[#s?}_3$p{|AS[V>5S/?GCHJ^3o ?l 8kBj0~` \,:U˙dxq5 PXZ_p{#hgSg~[o;cZ'tXͿ>fUb.wv;~^80_{x;S{(P?ϝoyaƯ $o/^Fg ~羵r?K}JDк(F W~oDi,a(hA'Qb?$3E!|=FJ7B|v6ˍ u]^kI% ڼ-r31/I"Xm)d+ u8K;]q\ӽ>5%)4ɼl# Un/80<6_!1]{`"o>K036N>k[̨5)&MS(`QT$p>֫7>`{XB-VAQr:WDI%enD͖;'Wo^>y\31pa8ݽ8^EJ<А%nF'f&;<ЕPQ|ҽL"~N?K9!'x+0B] )>֧BHba"~8ŒHrGEƔ_lz( 3҂lrHWnQ@@Paq>4tٕ[9[YZLeQ8^#ck?G?S~-?zpnna콖HJ\GJ>eiCDpl*d,vмZ[>Qpgkw~{?y767~޻4>4=rpx_uo}s3>Շq{$  b_=yώ__poM?Ft69Q0" htGPYn+J6p+LF l&bB d,@W3J;ZA\dV }3:n ͩ PH˓K(A 28PHz(2܃&;>=?>;C`RRx6b-Ɍa?pyugCY sQ'.dd}JcCtK#$|~}~e.Z0K^0pP%R(3m.+aTAT ?ӳ9R=AB+,=%LIZ{^^e: Tڤ`#?R_i|c¹h"QT,NpUŒ^ѵ d Н‚I.2n r>N2xQ_Cĭ6FH3MJʤx*WÉ+4OV^{+G־Y\!L&+'ʻ۪jk8w6ygw4L%w]ϴf[TQ]zy8ZctMh]6cFJ<@CTڿa6%Z,!z'B'oa9mM(SS\#3DWN؆̬ImPh.^\8?(7p2WʱY)'~>oLUi]qUG@(2>`\؀:(lL*V87aɒ_YY?'>W{F%h=5+_FۨessXYE; =_;h(ܟv'_| F%?>!Q{^l~_=U$O/.J!'(\bBZ! Q+.C*fP| Ϙh7Y' ަ]+}϶U'}ЗEx02DC2Xe-g[G :8{i Z ],`flX9ßy|E0*0~x2qiwmhU@=zm/̌^v`KgBAJG8V/(HH@)2&fF@cpx.ϸve\)^X6c99=Q}W<^p0{F04ĿAчogR:|{L'2\\aC: i}Ə q'y,àGz'M.L\32" S ypb^paHPqG.)P!V1qKqg<Ζ@9 ed3ԋ3W[`?|1YHcTg.0ld|-OM D@^/G榐0s"'G/ªPuЀ=}{W/_oϣ/~ŗld??S#8s?_:==^\XDhy|͗mB`ljgHcE A@o3a&&b}C PxaV8*XZQ0i_VںK}4Άoa Lp[9Bvx0z#S_v0{PzNɉ奥Y.5t0>#67|񑾙 xX\9|ƆBEBXn;hPn]]a(PcBE@5EN@N"hkS8mnį/gf',!H *AŴ/wggN hASR^u![)%׽"47>XyX~cqhnpRœ!uCb҂|G@CDbh_iBÖ)LoiΦg+eIm~Nm4vpAA')5T&RywTM*ՊF{Y5XAntwm٭܄lK 8&è٭uݪ̯xq 0L-cF@nFEoI~XvwgC,юDP.Hm8A7Zc5nd1XL$=&aUqѴ^^ ҙR7[t9NF(ᅚ ߚs߅vr<-46BSno?4j m X E=cU7 w8'P2! p+GhѽD]03Kh&yWSbwJEzcT"DŨxjW FQa##0ߑ_ҊX? k}~?wY+5C4tz;Z~ M?'Qw_[dz0|tտQ }#O??y6~?3WKStŸg?쉋r`|=a ۂTQׂIrAtC6@YE !b VUrR"TЕ3խ#՗FoW,ӥ:TC%{3ܠq[xi6uۈ3$:lT %}׫\x5dږЃ`m tL\<<>, 8J-rrUVv8YK:VBե34|Rkz2G| XpP nLsGR3e^ Ɔ5^?CFbſy^`X1:|}ĎtS<T4 ,aDz"m&YpdŃ*8cDv1݋[Θ}4(I0#&oS3` ,ke %K cu1j'X1?#w8B(z"!οO?2g-.տ79>Gp'Z:iV.tm졦O=y|dtrvjG[NgGmeM{~~F!b>"ھa47;" gQUzN_|g[kק/) ?b{U|P06/(32UA|BRjo .`-W7³-kP+6X*su856>;=G"imowp{cCF6 |~VVW<|`lD@hL;;O>`~~b~qft tn,$[&+>X~ȲmcVnAK}8;VVVQ\c55$}tm*də[F٥ݮGtEa9C4ehbzS,]!UIBeg7hjn-So~/~pg~F/এMvF'G'FGq)0ٱ?p}œ/ګWϱ!;14FƎ{=3x?zO4#С롪(7}c}_o;8a~r޽lAI#X]Q^: 33VV;cRu-E(njӆ.g.V0#:4˭dg|y9{zB76^o0)Ç!J*1(~|p3\774;,%E$'(5S#No0Cz,'@B~8W|k{+GTERHf,AX"> 4D)`=d`߽i^F8?*WGםi.5_ĸt+(*Q&d/Ҁ"YJE_[nuBg髣VǪA921f h5MOSr(rd}7J}']T](7kn1ҾaE]ef%۽*So.a{ '!/=zYuӶߏ[!&jĘϻd+Z $" $$hgD||{w`s+K֊Ri6&I %t\b'i^1Ä7v4w6Tusۇσt[0f`f Ei VXr(C`8VDHEH!$d 0X8fޛy{w̪:+tn;NUVVVVfVfVg.wSkFA~}I<#&stD/ūxhhXఁRͷEWQK7Zi}p3ꟼ?ߡ/gE(S?c7z l7h,7;" a ? V/ gP?uCJ_[O Z <9,#7(Y%uT;cRoc ̶l@E(M<ms7n6ܹ{))_A[P!Eu1k=XWQWa,K32hg%VnA\$!vc f/O>{oa9X\D&EQT$Y/JEnoͽ3L/Ӡ\57c稠iDl#'.c1e%[4W'{,,޹f/1,|O0xw㙕<[o|S^O%D8',fRY9QMhTBqXm[|_>oGߛIo f<dPX?*-l}68>|򵻫K7NώS`\Nb#ykjbdN̈́5@>q$ E'i%$Hp n(|D݀HEr'Q%6l c'?ǙZV!-q0z1,6rúw%C^C>%bw:JvVO~b w2%*!9cTMzށ)=,R%sC $wC6lӗaJbP?<PVJ+dz w|hNzs&ia@xKDM {q # {[-ti@ UTy.`{zJM%L2?j=J%j#)XXҾߵ~UYzubv,;?ZѻYh4عLog4=-4 ϝJvpB͹fz5YOĺV gV߾5{mU]`WV]y|'Q+wokNz݇VKp}km3V{Vq*~ՕYc Vr{>tk~ڝŕAŕՅŵ[uQطp"~VWVfVgoZXYsB3q|禱Q1;{>7{Ao`K&|fqN,-,-,b0MM I|S0087ض~1x`}v@p1>3Yx>$fg8%OOo}7>}kOEđ;WgǷ槾ѽ'~ϼ=y|!`3{B]kU80HB(XhڌWU,N6a|XQ9;EJdᄳ&.y:rKx BRy*9 }wˠ|"/0HM9cɈX܎n1h21y'WGg01ҾQb%Daă ybB,'8|F2l)dAĿh~x!L9`jr|ttĞퟝퟟ\ttl0pxv|uvpu~pu cڏ#,vsA`<'  F'c&mv x6HOItsЧY"N\a&(zO"DhH ʡr[ssbA&^U@)|)6I\k$Iw##3fH_%Vz;07|\KC.$jI,j܋b )EW) ^_Dѻ)ۆe Fsy"Y?_%Huv'#A6*xнKWY `HVR.4P@2SMΰ _2wF`>%")3H}'}Lk .YR2m&n)]b/T + 1&Jz%QKFT{;1^i/ߑ-B`JI=F+d1|1DJ:V.pvƧڋT^ NTMYu |pr2\w.eT=e4DdL@V3bC{s IgxA󀆾FW˅ :7t(nTk)W#L1+MxtEe`?eh\u #c>bBߵի7ӞFdH?MLU(E,5y|o)x?__$ցݔ$dN {u~ǧ?zt]/s % -7 G (:CApCE׉s}tl^KK)Ѝ0zZ+\.^qIj|ЅҀ*D˻|O O &OrG>ֵ=ry(A/]m-2;+Hp!9d46z¹!h 7'e% Y`s^[[;)~P\ a>+{x|'X$ uY';ƿ\aeRД-lZ.LNғg9 =cyLm @!l!8La) -.0Gjn^u|lk8s-1yFԠs*ǗX.N\]\)qy>b19 ez/a}b2P`q= ~8gzT2 "sĶi"UZc<%f\#LT[@Lx A:ՖZ9pz5q.`8 :J7)瘫10,)H *gG5| {F@7*IHfY+iRёՑәvu@aآ)6;\hT0d!BWun6S"(%&V  E9‡lVc>Rpa,TyТ y ǑSl G2)z?-QRuU+$RB8K=ֺ"jCc51-ݫS(Cii]lٸLjXNE91kg#3Maf(^xia`oop`~ l~vX,E e@q:&=ݐ͘!>A&--Z"XN s۹p IlVW+ HTqU맂1#DT&COw;VcM-zt:T e{LĠBG:9x m5xFU^ރv2zZb> kYV$z A|s>Ģ j̊Fmjf020i^Rj`D 0L a7niK"EX=o'9: u"3=|d/l^P7dw\!4+L =OSqܜ@ޙ+2TR® !_?7*4{b k׽aq# cLkN,Km,{C "3PW*򕕥;rs[]B6Ƹ`3T_7IN/يMssADl#raV_ # <1ܦdeyd~+`ATU )Y/RFcGM( .v _REEk=wlV:"xʬ\$xbY*DJc2r1Y;PxS@V|Y֢rP?;¾/h(p63m!"3Cس>FPd 1QP\L] [07^b`S8k=1a8d ;Ñ.OOIp 8A- cl{Bf~ː5 A2 o-<.CyCp'P-CAh'(|1*%$er3h>OT[S>k3lv cWt;.Ig2 Q+ ̂8p^?4)Sty85kiNT/# 7ksV~z7~0(|__O^oϾ OxxϜ^|Mسg &| "lH2M'p4 Jx"; q(3˱S0 5-_2:#)$%c=G1ؽa葹$6ŕ7uN<7_fq <`mAL M`$`Bc`./@#po!yP ccSӈ+Ay:905ELfzazfiznqz[s0>5@nx p'M_G(Tl Vs𺙜gs /2bˀYY1N9Xe á&h 3Aa~`|9dPIFڒ\y]R/hDgb2]s6Gk6/ Z*劑Erl|J [ա|lOeW~2*Iw4g1+F˳I}ΒЇhE*r(!B(IU#v+iS` eOw"S[(MqF,Bxodka~#Z?e"%Taik7!pmq&Qah!JT=h )4bEPHc-Xް Z©*D qlAvT|WsKz]@-SG-M ]mTR I8BYn 9m%D9 M@C, Ku*݅okAhc(j@^_1J]-,jf>')AP*"Ju"%7n&1FѰ /+tpC&X3w)!J#, R9L7s ڐ o_pC++?O?WߓCMiyVB[bO?:N-_},%I'72 /eјJ$yË0a }Κֵ\D 63k"azrԜ0w>~ XA)k rR"7ox%qŲɇɚŒ)-yQ>,J^iK0\lM,GH>bp+[h‹=Y寫b+>z" ihylJfS%3l~{H V,顄aТ2Ɓ:#3i9ϙ=dyçB&gvlBGJ+hEЦy\2$I(ܦU}^rbYڱ9X28 ٍ8sv O"B<WB,4k"ϯ8q|r|p|1{zE%xrd#A#P]ඁ\/]åL9;{:3w8\kBqd.<١N^L)lGO\MSY1Ƚ858/ggbgZNL!lb|b* 3pSxkÿ{HsDM.cs.&'Ϧf8c:%汏 H@g  zK3/?QhF@mgr)Cuf@։&eP:B :>G Z`ptovd)I&xpa"L*X|TyRy*TtP&&`n^az4ɅS LL &X&'KKfg6c[x1?15(%* F,%6 5!mČ&G'ӧWg8rt]̌ͯ2F,$+rcUԬB.3{ Vˏy(cΕ=;V#fd^^d[KZtPRXW\Ԗ\T 詡/U(Y5knGD^@C5ΙRq" X-gSYA^ Ro[LG P/Y]/WDhe ;֪2hϝWdBlY(D p1xj͎oERc7;qOQˁ97^d.Qw(sV@I+tZS[unubwH :=ry*^[gP&U5IK!7]M:7s2<D!rX.6iבvH"m0~h;ϧ' ّsНp%`#:#2 țOE.5my؜; !BqN67&DXA)sDO,|?dWwNS6Ķ>L%P͹GA< $r{SJ=(ƴLOtCI3عE.F|Be gN䘚6?c,`n N "P&>N/LNTEl/2[E<%p00ms 'gz͂$ s=71;7> ]Jw@Ev$g, :%ׂO`b}ʱD罹Abdn z>+0tG0(Mg`HL)tȫIB>;b8԰268>{ɩe(<454 ƕ9*x|ae__pEJQH *o쑏D R @(PWt ]q>;g5!K) |7$4+l".|BA1{>5{obj1=L!0w&,kzskg{):ރy ,M'gGpN:FcL_O_`& clP{2AI](0THة`b]`'p87%wˊE*GBdL /c^y,6q14=?i5Ma !FgDPq89 <|5uWG֤U<\R3ӕ=veⴧ <[BhLWԪ$}<+2QElݓ |d]PNUs)&QyXOeoEtEi*oӠG^3/'"=Cq#!AҲ>5!1)k`VgĻєW99>Ok Oq!Tfk FМyNfEf"b5 0vϦvХЍڨ=ף2LfWgK4MՆ:p 3Q px,>EB!6e ,`Jt\:CץlM e`r~ R;^zAvorF"I{u^pQM{@q+t)w;-gEFy Z2k3#`3[zKS\h\HH+ZY^VȋxƉ GE&'=~pݻ74+7 NSǠ&\" ~W{^'P[\.>ڗ/`J}{\Pko^C|tLo b[}@9 @`8|>9ğՕş|ڃ-4xߊYR.'>o{վPgs4m K* ̤כ/)uqacCLy6>K4ձy hCa,hf"TNp,0ıF efʂ>誃LA84a6H) .*;Rzug^pqpö9Mf TZُ$CUH?4 = X;Ôz̃ tMر~[++kksiɃ*OjnaSN62 m܎E?^p=v:|.&Ӌӓ}@(&@]AeIuOL[Rg⨋q8`\rvƉ9 \g|bbb Oix6(? s s"EE`"ذv w "?BL.,/HDcJ@N/tG'G;@Ӱ,//2J Urj n#SnH(x0`E^BD[lB 0C1fXL 5y>t8ӧ/6w@Fwo/Z_EmlUO^n#˭{'r8"7m>&.6w6^\cZS2p (#'{*4YJ#9}9gwzzr_T n1 Cj{8UxX8n QHGÃC7M§]9{';[{x[8dscckwkcϞ=;<<Hj'㬜mmyz5KjBH9.aȲ>ӂ^*IT8cc-}y9]yg%KK*?}3*{:ʢg'( d LC eⰞĊ@D3XӾ#b.[ҢG^rU s݈tL"VUr V P UO&FrL`k})v dPKK/CPe&M~0T됇Şn?S8 rH܀rGݒd8$USrRX0o{@ * ύy/ߊ~h@Rȓ2~i`v0/4z4D玴(/ٲn{PcjLd`- lWuȨҥޯx.1#kgF|D*{E3S? P?QZd02A؁T_wvo>uMNddLTM ?ǿ66{? !">dq/|歏?ܐڏdPaaao8̟ Ѕ~AK(x ۨ?C;C3?K?=IX98f1GǠtK%{_{O+v?KK:|;_9 Ǿ02ǣ'Vz&@)OLI18b`[wiF)f($*)9&*We!tD0(̼XTY|^#A As?n DC^s U{Q?-2RE\ ~SacrE/D[zo,cxIsgcWz.-o{3W4~xgg <]\6OP8G^py1w{f޸AyyȠP'J){XpQI{=S~m}r)"ǠQ=;}pUv ZZ|7 Dr>[aVy1 {HPJ7K}*Cczewqd&pB a*L@Z^}ofY2*SD ( B8ãGWW];!l@;|{8%4Ru~ mBMܾf76wq`mu~ihއ܄7cMOt{?/gg?9 -N-8d2`~C7zɏom<`ѣ޻ )s\`sd;uX")ƱlA{Etڭ[wnY\@n&%ѐj<Ӹ`qoܲjRhKB%ezr׷ ҂oono,M.``=ydG/ <( {3=tr9:Swc^@g,d":^w͙,zp^=h96 a<`q1~jĘ̈ԃ˖G T܊k 얄 $;^4)')E0O~ťᚵ+֪!(b,#%倆pA"ϯ:aU)X<[WR F*Q$cIgyD`J5+AreV.zTu~ q.Zs> C )qY@hi5D/PrCGk} M*IEB}`?4JNH=p)~7~ gA+vhÇ9$O(~?ôSE,A6)jq+Li5)z%uN[6B@8() EPzEsf#ñ&#aH`V!+ nJ7VD楬Z\̧Q}J?$BI|G%s`Nl˓' d9?+>zxg0L)," v1fn<߾/ yY} &b+>y|s{fj}i43X;,A6lRD%ȆcgJl6i&NN{ㅩ+8! 8 =?0tcbʃw}dyfOw_nln"27Tæy;ɸ^p?`cԕ#C8qx~Of܍`8Ƞ 751Y軏}icY5m̏!ţyX8GB=P S./}pk 0m^Psu87~ku(cWe`s0nZ7H,.Ay0^]͒ۡ(cU9X2H }8>1(J@'F t`ִ=@0u1TW-]E% S$X;ܡN(#PlQL}q|x0 `ÈiiNp9[A W p< ZfG!H#l 8G0'[! X C2bZ# ri((DE ]`HtR(<1#3ʪi)UàIgҤzsu ͇yAaYҰ$hUN5IPr&Mڹ段ʒq{KoQ<`6qˁnQ } Xqg{&x}q>u>!$H &PB;@=zGy«=9+q3^|+,Q|yZ%C,\gV(bD3L} IeC^VOaŨ'UBiFe"W@( 7jw{7-*g.* Fw[TYUo!%x} r&ecN;CD̽Iӹ{ 3T}+J&?"x#< *6IAu!WC؉u9pyAbWR}[sTussi쭂fG6oqust"Jl{4z@Y< c߷]7~7߾3?q_l©k <_yj ӡA ,`kp%.*'g槿|{}sߦ)V |9TȕBG_{Uie͏;G'|O|b8ү¸hhpKH{>sCHH흽~b| BIu?v.)MaQ$B˔'[n)k'fٗŠq3"E< Ohi [E@AЊwDrynzuI@ Ywr`z;vIYa?eEFMqEjʙw`u7?; %7I`l(fvrJ2.t%Lvv YMO(T\┱϶@[R ?9'0'Xy P z'I`b~?5+%jQ!д 2a25ơdDg0 R0vc` ?yb&4jEGBz+V%hJf 4N"騨q(48,Z3=CiTQ,t)"KST@Y[MUD}aKEi J ?p>LK ?*-,/ g#r{(,\ȣiR`p"G۹o-s<]"iòHQG89!W>(-C4~-- QqǓӥ:S_$ cD+N5h/A_]@z}+ZgƮ8@3cʯ3FQ+l1\7{׺1axԡH_&҈emg,/2Gu}~Co S~](Dp9KGZ筷M6ԹuNE@2OZyWoXg^. gosJ)!xlvO}͛3h׿kMk_%{64?|iiؠh60XV%3*dy/KɌyx@1\酸+s~%`vzSoY1<<F #xIgIxfB[šf|LM9(s/E~s1HNAޏN~h{^%~r57i.8p\H3(w-L͋9?eL:Ϛ+3gG l s<Ñg:d |\[[S[͌Dp3i!'*$x'-T/Q<p%y=`$ 6!;Bc /򐃐 hT*Ǚ/CylqF,g+7@I PI^A "2C Q wCd!H=D'HH;! Px&1Ł#}|<6=%n EE-pAh #\H_ G xNugF #~kn'Lb嗙=ietzfq0P`?z...VAoJc.D C h:D,j n9?8aHVSN`6VܠK+HML^yFHK[&< 8va\B1aJ"gZlAI< 2.EUG>NSDډFc=Оrxn1g02ʑ0`AP h_rgF1?q04]m*B=B\ -#փz*@QD~%Ml2}JKV(n:OԺ謌6]7ŪNHCh5vWDX?_nQϣ9=E%T;b-V#)5R&J\Pk3`y ʠ׶(*ߊAٛ.$& UrGCrZZ)h=U"HEӿho,hnMJSj+j KRبwCټ)9~|TTϝBIvx2E-O+4S`(/ d2>D?zM# 5bu#jhlLaC;m^WbPB/p q wLqLEPHK-cB kc9wihЙYH+ϣJ|0"x}$kr8]ȯ~ H<()ÓVHz/c4Az*c9!l|߂SI ?{{> dk#c/w}gKב RE{kv}!9t+m^-o4*%D Wy؊X+qm;O^ AlWa=Ӽ,<)5PO?lM7Y{ ^=ېcpT\uxH9C'I4^ɡU- "! | ʓu(Q*$;R[:xAiHFaǛ؟>P S @+TsPy^R Z[2)^=V\?P65<\ ?#+iTc(RV-#sATp-`qe D#LeU*=A :,swȈ 2Y.so#H\T1R<ͰE0}6pلjg#Āz,HϚO+8#JkKaPԆ"ۄTDh'@:>avYs :Rm8cb*Tu,0 I^GѮM"0Є*\3^ O΅ZO8Es[H`79??o]ig1rq 2kouc&2if4@#L4&Pc/tq}4& 5]k}'&rc(GMQ 1~\Y #T$ bC6:-py8+`4(rM W 9Vea”8"4 m<ۺ*v>*LrM-φBwzuѦ1h#xJ.R#̩$}.ڦo1tW}}c/| _/߅P9pdjHHww~秿=hw%b@.7(uizZ}y|p>=A}% s!ʑ#嗿oԊp__w|IAx){)|_};!՘J$\a[C9:Ӡ?37\-D|.*TRq5՝+gei;3oQeM9LE>vϢO @6^6`6|teXሹk꥜׉N/aQcGx@Eȡc;_v>`X%ƌhRP%.Tr3] o#e\CTn:Ku`"Xn6^_V) B&м)*WmSہo>*p7-l)*EK*QYgh"SZ~4؟X\dC` p$*PBL^h @gr;]SmZ:GDNiz(mBbE+t0~ 8S쓁t (i'BK|E0 XLЮ369٤aqzB'i~'JȌ4I@\d+VV,F꺨Nt:9̭Cl=&pt =xtWFs(,m&` Q qDf`E!" @b8A3(k{%U a-W\C8I`ɒOO=&ЈlLlGMs@?ڒЗ&ʀ ~HA8j4$!Ҹ$ wFG(Es$K.ϰލa0a[\RB_S!m@A2kKsN>C.}yy@+ (}3[Z[^F4 yEB; Squ-k!(@-_y|άir2yJUWȪӪXz$ĜS{~GAYOFWs `D:d\@v-1pcys) CۋwS𬪛ӿxgHBXop2bE %ᜏ|;Ɓ!ZwQ =0^S&n7aH*6-/q,Z)*x!+6TzSD5ѕJF˙ N1RYiDLTKV[q1Ҩy*4U bNSHmT"((2zsJ򺉤H л5$#hT)vğ8aV!7'mG+4+&(pڦ2MTeul  wQ/}|wrW[ O#w7ƌdE^Ƽ.$_QBS12G]ݕAﲜMRA~ .h}~ #Tapm汢, 5QI?P-M<k?v}(SD)"2~3y|~_Bj+u1Ywq_k)k:}cS ko p ˒U$q[|8s6فq#I7&ޑ)(eMLH^rks01. " 4)sHa|XGŃpS83txrgwV+ta@SP)04?8<i[#mVLGZA8L1Vx U̖Ļ>/_z\Y"jJDz"f]M 'KAzb𝍗[y^t40XO~^B-x(HKc3UߔW-AʣaؐF6,_,0?&hpŅg, s< AʅƼ}oS?)XBY{J|ӯW>< _z˄AXz4(U/p~gҋo!@vƅAQx2 =~9ܩנCp{x{+]n4Y@-8 F:;z0К#ppfKƨJh v0X! K$W'5ZUD;q[)5JLb"V$^g[bJҨ(ϥ $8d9j#^ar}bSݞU?[DF?@R[Y+LPpB IV4;Йk/aVB $> vVAmZ:X.R(5[~AidHɔЄq<&>,\^*6CbjP p*AQ O [soLAr @#{C/EaFSyrN*40 ~o DĉuTO[zcT%&# giLC~EJ-x$΀Q|nHB?u 0;Xn$`YKB |HuMN8C3(KGoP"}vtpw 5t8 uaqC/?u8b1M;B2.F^ A i" !’qQQmh6ǠU r`p|"`ˀYZ, ,@U4 /1Z@K<)AKw ri IDjr͖rE#PkHJ6%.H<H C:ڪbFT+T}^_n"e}mymeN Y٬⾅i99IE͍<_ ZPgWYЕFB;7.e9Mx`ue>LSfB:L >ͨLHBVv *7IڋI|]d 0&s1.SEՀ4ySDI'&LV8+D `ʹAr_ kc!\kΟ+WuDGke,(m&{SpѡZRKo`c>"UwњˈJ[̦~./5 m.F|b8(o7ri29LZru$wrĸ c3@XT8d>#C{V5vU/6,?TIܵUElrp|3 Zvz^{nK+ jךʚ1֐tPB ioh((Fl;[{+팦troez5%גoF3Ǜ=;ԑa.&>^$!scN1/.(݁¡a2T(H[͡N6pvYm۠onnQ`6i03W UEP;^kl8(wQ3 )iRXc:~z!d;:3n|AKExF8=1_5M)c| 1$e )=XDn+땖[M&OihZ$tVMfAVL)Y˝Ҷ2ŷZp:5"O7[Aϑ1yv0e")szF6Ź b`J67A*H! 3i̓B#[n0Me:9@? ϛSx MG"p}zbqvrqi0C$#$1SS3Wq!DP/NvwT;X\@Լ H5?<=فl$s728x0q' e9i;RT{2˺tRv@H,6vp(9VHZ4(6D@f 4Ap2>dwGXgd9T 6xU12,u: ;LrH,d(nW!I@*+&rb#zg< E@/<;F q6"RZ<`7$OT^Ie@*20h=9D4w<*0&Ҥ<`. I,c`j^Zx+4%(F o .X(!bSxiӓ\1Pg29ο#Xx>7:c[P倓S03sQ'A\St{k6uזWi`^{Ursߜz#7 0 0|dq6v`YwQFis&kmU>`qq.ϟ< ~xu>aKl|0!^\dޟM~[d\rnTk3N&CԬK *rk;Zd14$l 6<{ L,^3|&kH|f4`LZNāA,f '16\9>M)f#_+Kdbu 5:ɊbmKH.{BPJTڍ[ QS(,E!BѿGyj` 4A 3lk d-jZ,޵ڎKm9Π Z~SM}[U:v+w 1?m =©m,`75\ ,*k6< 0X*AN12vP]C L̽ohȶ%WV>.|*(,PB_W AxHtУ4^X^2{{*TsHF`$ߕ!خtݥ.m"^~s}CU)VJ{ڛW1r G1BhT%K}?Wh:[J'G%<zu>_ gsb8tv %)g| Zfh_ ;g>IKr!w\t*K3IaIy ,wX#\t wz%΍(f :!3̄OwM)c#{EnJeߤ]*vfa׊Hh/fTUh{'+q/1 <^Җ_T.iD,*oy"IѴ[L[l/(Q Z;/u3+T!P[,}je4 )қ߅~ovֺNtg@#4c3{>gSg;;GHտ01>[ eڦ8ޚ\1 {gs>2$ e_`$eމyl@.ѣݣ̝wqa{GO>PA-3Mhc&29ph5A,wOXϽe(BbܹI4Np0bj4J\P|{2) ĕ`ő&1#L@"*y< 8aSJ@ lv_)t,_Tp`&9gGoO671s;!#X`RJNW `}C<ar^`0Z:NA Ѐ!ϒ@ :qUR&к*hݹe'!ϵ̾[J\<4bf=ɸYyI-"lĦp  nuJ6Mk> WڍGNsQ:>?ܻwoeuEY>o9dqlww΢ ;4m]rdpiC4sMDJM5sN ND]`BC\={M ^{w b N BNu_`pvDZ\=d{gܾoaiɾ_t 3Jm;Q0S|WqӾd7͂D QulIĖW, V4`NZxFTuLz$=QN*%!rDW>r2\\C)w3{ݮQcyYυ"W?}=-~"ec4?:[i]hYF~ #|-zⵘj_[_ Wu^FfŇ>fud F*x탶:JO\x!wf.wkLF(zvӡkH4>;@P$L<*X黥jVERi:Kճu:ˈd}]nH CU+-H_kLvaɉoח+H?ťwZȐ{͔>W uⳁ7:8A"C@\g ҁYa{lqR\$'.H! +O \qb Մn(ۮ_zhڪO*Q~"{ja@ RLꫫO~ro~o1u6UhںO[yI#ԇDU w)l`v5:$rzWǹ;/ G#["ѮĕRrWByn;Dج؇[ZU$/\c/!0)]X6!ur̪/ϧ@KdB wYe'P9,Y8@@R 翲,H$0E .(xPz!B;l,G!~癧~:\rGuUoc#<*@"R9RSMVZd6jȳʱO[ K5֭.cKyrf\ϐ+if*r A/tT8gtéY<\DxY76sxL WfpR9' *W"h%H3JM~hKYr!CՏ̅ fOe1^3b@A)xxŵT}8rx]AGy֩!Ԓ`Yk[keH*E]ļMQlG~ uFCx{Nj~+WѨ(Iz2-5$a$&BIj[_EZ {;$i$ԡhR(41D;PGfYvl ۱w uRҪ1,Wq<.! - ̉>AQ望>6_y7$ *n}*$n()GCzG.;wn޹};'=ۑ(~מ={iΘi w,Jfa =RZRf?lKa) Ԉ {MA@H3D~0 ?v)B9hϓ@*ękSqHj5-I{Gp? ŕE Ӭ`bm2GYWF9!#PMQg*-:\NsO#R0 uo$K9 ȈGٝg/}2JLD^J?)wtcCc{.8*FyjmTPꊧ l?ebbi#طQo #h^z8<?KXt8N{O RfB9u'P. D&Q#M[kKs+K 2,Hks\YC, 3eq4 vK9;c{JQU]e&qЉ`E>~Bc]o&)L tzBxmvh) fgiNQ B34'LV^\hg"fpSI4ƘVվ/0}d4+\NtR# ٺסb *j Dǡq] GLɦ2c8;a U&9&`0!lYPDŽ-n4ˆzƕؚ]Pevvʀ4DRcW[KT/*JYHBQ`\2d!IβoTN Ӄ9Jwz"j&&1 RO/疹 5B|]Tber8iY,ަ(QITJ Ҕ!"&b6BA!2D?!<߰[d'–ΧJb@' eci_< }l $-Ͼg_sggwV"P~b3Fe-%N?QwᆊE/;+Ww3o=\_ DUuZS4e_b$~ɟp3>L^<0wyӡ0|G)LșlDAqmVTgs=EHYcKTSMK |aKw(U>|7A !1tg0%eBe&KT*eޥ\;c_B&'qfSTqVY$- (@` < ދ͝]zIω>~dcwyb0gw0=0hqi בpya)!i6m g6aZZPEu8U)th&f+O62qMB=J1#0{gMvr #%=)RFl @ER">bMĺCGHo~ ZDT gjƁ۷aXa) 2EJ+>=>؆gii&eRf.|"Mɞ NdI@ 0sKCygvwg$flgs *edՎG%hFvQ~ yMFV6<ܐ&F&WEC$ G6'(gI 5em?v]Hf:}#'ʕOǩŕJTk4륀nǭQZQ 6+(CA|+39IKJV"QM9ܧ ;ZRϕh*yHi]vGlU^D1Q~f ^;ׂs}XdWk:Vx=aRGN6S6:5 T۝RM~fJ}T#)_V/6Q/McajSCFj* q}X*A[ ߲|a$tKf Apg S^O,ċڕȫNrS>zwn AմO} vwoU֙,&r/CRRr `Ӻe+w r{D@NE]r e ޕ;ytS:+$??!"_wDch-y,ܖ%s!b@?PDXmAI$J'jwB8bl y3(! ȑL; LʝB>hsC)!u ~!€`% ɢvjOκ?ƨ`Kt& mf$"׮ 7yTQӜ%G Q)U$9fT|˨1U2}1ަh!d_%[EM5;ms-7[Ci-@dva(vHbܠUY "jҸ]M|T*^><^Yw\`V)jX"6TF)S9>=|cs`ufi_e3l @GpHbcY9h[553ara`CiZgF:#Ҽ2r Iv7(s'D2428 &+,ϬѲ@w_mD679yP'HnkXVaya6 0sPz>Yų2H:q pǃi%G\x<ÍA sr@LrȘuBN$@Mfrb}R;e~1*LD=@x#f٬)&.O\,AjpVࣂ6Ġz;/L(Ymi\:`35$H6l"6&#yd CF e‰ZeNk5-k,B$چ%nU[` fթfbF  (m(FCCמQ7ӶR߈@K6ł+յRx1u!fDUcѱԬPg@X9a^ @6%4RfgB䷢U x5hK]F\>?A*Т۝2nOTkuYLhV);*"%+*kj3x&oNEFOX2&yb(3;)#&> [ZiRRV}Qj^exmI!âTHb{҆߼<71˩s'ųKW|w+֖? HD䌭u_͑bjq %0~ۯ[Lԡgk}eHǶ;;5 clrs k8Wo~:;PcCC8<J' d_ ,`S|]XZ\ZBYlER4  JRc5 jx0D(NCy5jgg[|ٳc@<8[nllt>}<4ŝ[~KO=X F"&AQ3kh9Á`04 "3o}ȋpd21$̓T-MDv]0bv "\F:&}X cJk^/wѿft{p~: h/P$#,]S}zgjz$$Kwgv rb 8n˝x4̿ڽ[wJ @Ёpb8ǽ i+ d]_~Lgs{+qɋ 9 n2jLMSl &3 {9 3 '(⯎ A:bS^# #MM2<ژrw9l֞sZ&yL 5`@pP_hܧGwVK*Zn@;8j{Y,nB!5vcPb3 7^nBPa.N1?|̓29խ-.}ZX833 ˋ0srX4'0i! &72@ʳS8Bcn~,Tw&yF:~r|rppHc<X* ak5c@> ZuRN5)DTk F3qfH F%m|@-#qp2!pSIJs<tupx[prMI@2J9J | > X$G;pYyKȤ`,JxSpZ6yy\9;D<#{%@ 3WWaTAdVo6lae\#Gݖƥ$;  &;T/^U׫ qhxi)('Sv &DceAm f)IdWƚ[(2{e)W#j)=j:WaHZtȒ6F/JʦO*hRUÍ-ݾe"ϦxTa({|[&=t#.}ʺ<[^-*)ow i^*;pQY(7=, $a7{XYɰN6bH$Сr <s_xDRf( 0h _j8π놾yw|p\ nDf Y|3dۤߊ 3~nQ,Q4=IڛB(>bS *FjtHf~'o=RgL-34 M,R4y(:  i&WvG i$Ǫ"NWVZ֎ŀl(YJL'kw` ľ Reo cFx.:4NJ=`w0b=s5l+c (Wn8m#j9WHH3dY1rs3|q*~|xm'mi mr8h)H[~ܾyiďܰp;;)W#u#f@=y $0b0̬'TFG` 18,C8 1TICh?ԣyc&GI'{ )'O}5ih}WNb`vC" (}rˠ'Z3g#AiG/ǰK4\W<ƃ85@х" lO#9:Þ6_l>{&\, '\-={giy@オDsf;%gZu8 p|(5ZWW;ux"Z b\onqÒ,gW~@so:'V,`YUKrYe 21$=r+0u^K%T\pWtKf[W68&ˍ##KI8̫ Mn[Y +' }90Ѽ8Ʋd-aYZi`.N Ϛښ>hfN hm bbs8l2RR5!&`i w!˸SFH\!16VZfCuR~[TGb:_âCkRUpPRG I^[Gxy=C':WHn<~K(Y'F+|ޭcأ;d+稴Eoht# yU59u@R#ε4yj+Q>L߆wdcFl͖E[zy*ej(X:T9nI7x``Ï~v&ጌe J_<9ZBZb ~؁!PLMexf1֊dY׻ [Qx!ڼ&zMltMjyIcEt(ܞo2T4&e!nӝr7v.O:b7dJL7OcP^C26W6*ȶ,ÖFUP.Ҁxqae")Ѳdt1'`gz5PJ(I24&jٗJU'][_X&?Tf"DQ q<;D*81h7~O77gZY]@[&O!de9{M ݤAa3 1'{88x۫+ܙ[w/AT KXB65xo\N/.P+ P.pkq${Αя8bG1S 5B)da,a{qh7݁XUNa0rCRvO|2:)Y3XblVPM\+`w˜Bן\VyRY Qd@L7z<}bb9=CN[K=oΝu, {%l>`(p_Z^ N `09hAO@: DE kPDJ, B|POn(. $0%u|TCr AL |:_=ghd鷼X5{hCx)Da|r R#081N$c80< >z`̧݃C88( Eagxӫ&ĸ2T84L fl{IDATz #;eVlxɒ>R/i9(v5p@#ĺp0~NdR`_6)I.ci69^%e5S2e /o<&iܡI؎M%B)bj_0`nXLX]^- S^jsJ[^ENTky1 O߬0K Vkk!+ۜMMAnwzLe4Aps$rU7:谫l)xKNZc'Me$ L.ջPLJTAVQ*FtWT6PZ*]T Zy0 ش3#Թ.e5j}"3w+T KP\t+HBrX\ mFu3Ժ٨Ks-AYWnף!0 J i7V^n`9`dz!xb?'&6^}k3Ciٮ<^ٕ9'lH<  x`s C =.I8J?|XڐbJ 8, O]+MY]%(Z DhH\aNL<{XY]JK;#Q+Iunj&$ ?f|G.q\&p ^\m O/&fӟ`̟1`dz5 ! qxh]Qt٘ ]\RG4@'bV{HjNm>ƭ_ʢF] TCdBog(Xux/omZo#F"=oǍVʄگźU8^M+ZӔk4ĭk\Q'TI_~׶xYGzVJW:~ Y+;-c95$8V8 a׻%&RXy *tjSגf{k.S{x}CP7}c>F4T߁}%b–bH16!k0TްԄOElu\Di2cqh(7NHg<N2W?%c: p_4u:cu $Sg+{_kN](VW_jg%*r8Hxa敷 4aTwZm.)@ v\KG#^eǶz|;y%[:"U+@7QM`pDH|( m.PE ԞZUr'L ?nڸA ]ȧ&k|**ã(Vk9iL蒧;bHN.v_PҠDuG}ΎJa (k$瞭tb5);r&#܈0trVˎPugԀӎ@$F6.w@yJBK} OK@QQ(hGSZxd<[*3B"~9$S*t)pAηhR'sكT8HkTj&#)B6#$om&`Npik/нJ?Xofmq:-n.W/^lBNr GGqG>ӗ[hovf/|[Z]grr0?0 fMvقhD`K8j:mI5.NwS Rkb*"3R#\I1 Cc#9rq0BUK0M5j4ap!nbh~H6UGPQ2q< xXdmD1Fi?̎_."3ehG!N*{o#&Cn[l2gזϫ:+G9C|<|Ko囩| E `P}vL#{o.,ܽ} qXO<1p\A73<^-o)Ax &g 46+:Y+/N K\e[4EE˗qXtˣolFJW1Q"uw0C+o_2(7(4MnV ,RxDVkIۦDLcwsvJ0]$?(ɸm5_ұ銢kѭ0U NU~NKv^dC$! DN#iڒb C:\F;]R!?z7n,lrդQ0ɮ䀌_vKБGrBemِa mFKna"pt\xh%ծ8Iifմ Y|mzPͨ"箫EcISiSC/ϩ!dJ%S]K1͐)OHuב@TXzCv;T0\ҡ[<Ʈ2]bjܶOa0ׄMj'^\E1#YD|"2z`&#fp'>|R >>x+M./|ǾEd@#lfc@I0"#Ľrc"J8 ThzKnpz(揎,kVU,dëٽ6uJ&KyD蕢J%͸M\؝c3S0d"GJ[:g 9(ƻĊcXrzuQd6J:&ޯIdI^4$/އp &2|5 ~f,N.\O;4 k9\62sta߻8MCC캢/4ȸL@}Be$-Q1?1 msqT iJȬ'iY8YX((%D'M>Ng/8ͺl_y>QK5zL u/ma4,- q"oX$FcǁU%eqx{jK6 tksCNj}ۂet4nQ9Us'@"a4K\׬pSG#D}^hBl GW/ mzJ˽@h·cD4^ac6Y@Ɓvo{W7RgLmہe/jV]v4#Ta]laC5Q}ֈ+ ]]GE'F֣A'w wn7s07Wpꛗ]OVE/wjoiVP[{#<JP]m@>ڪ06سV>tg{5w d̗dtǥ_̧ۮ݉$ျͼ 61k ȦP[ tRi8Eb&QYل1ʻ`qzCoI0]>=Q?Pe Ց_hGA֒WUiN7XCR袾!Sin{XNO*NyC4iHˡ~ Ē9M8-Kj;doW O¤!1e 0˞NL# {~n^[^^~8>8j6B@̊BU)gpCCPxp:ݽ̬% ˱Ufe"$8ۇ3"&xvw#u(#P=Vo2chśT^x~8@ͳN $F2Yihsx=K0CKbbN `>HQVCn00Ar1R@H+!%[uN M9y( 5^"I,- nA gg@.7&6[wxM@}6ͩ4ґvsW% 'L7 GmPEq)bܮ.bs+J8X8=>ٟ8sb8;;c.N*I7I mɊҶmӣť8O tOw1m{$]RxAd|!2.G- OymPIEZU0ds[9)\<Y\`fbI$E "x鎏̙CmmՐ)/a(?[\_x@H`IjEyTYtcٓS.<^7dj{۶zDPRK"-鵭c ro4X(؀`ܹR]:F\$.iN]1kLD_gYMbE7 R{(!H3ݏe@ | ac?LrE .N&YdJPم-+1y 3@^@[)*&]PLBR%(S/jʱEѠC׿f%M(V}6ʯ4pg Ę؅#N4Gf>?C©Ws/AOE#*4#-*;,~Cݷ+l D2覱&~({h7B¨,yH@kP@XL>:{!2Bpv2A`Vl+L! ljimEY QsXJQKC'- ccM7E˵1Dpʃ .sqP/)-)TtKBr"3*vI]5_A;I XITe%h YC-ЗVl 4PPa~@8T_(0 ƭRC8 Qa xz2i)>M  ;&V:kRǨUgDܫ;^6+Xfs#LɪT4vppv:q/x _(HoQv]aB-X+ е$j2>&^ahW>GTPQ'uT,"ApE]᫅婢v[6Fޠ<𦽎@ҕNPKggøaLH\d`GWeJ.s9}.bp#V;Z%-@^1NBn8[K="y'ݱu* )dg0e TgEoѣhx NQJO2Ϊ}1>) tLPИ=vȰ(NP5>k~!Z'$h9~wvŤ<On'#mLesS5!0( vpAEyK?a\)s! V! H\WkO71~ب>?B658:FZdTH{x&^G%cmh0eH\cIPF![GU>CnA}H*iAi04bht4J^#hϵTAWNܐ(kT`Qf=<(:V2&$+1OU6X# eo&x†$\£n- ѭ3ĥc5ҝ%΋koCB7ƁhE8Z:{*Us6W8]W~&ض&,X(4Z B zt -Ceh#١|YbBfN0)1Eb gBƧs{ tY&bo2;9 6 (NFS[,)% =O2XR`Nf&&/xЭv<;[\^v-:rSB5`HtvSurUB x|o++CH{Ze16=Lj. im4Ih y,8<fড{bqXVNED*EO49F`v8{6Tob"_;DBD|3*LGHjK+!CC$5K* -iYLQ$wk%#Θq)yA;/|fA^֚@`^ &O=?8p1A6#SwI|<x I baG4r4#(Ђi}pq1nbpK閽Й[v |ZFWr«ݽ]1mĨGePr PlqT&kl 8[rpvr,THC! {$99ڽ  ;7$ pR3o(OQE0COl eT BIvu)~b4+8\I]TkI3L]( 1"G66(j( 71w AKNS W}P!o>x;9n(lE]TiBhiG=Ԑ;n^3H͂VQ X Kie"@";!Օ;]Qڲ= uܳ] E|ve}iM١vzm{V[DTJPQd"AlqRfGehd7VXno.eQBWjkȭӄ.|mGD^7Fj9()WѼ[ T!vD%2?+aDwa64mWC"AULcCZUL۰ 3yǴ,^tr^GTC Gn*Ĵm u]"\P*NY!w}T-gFٰlїH(`>>jִ%Q L ^M<"PI_,Foڰ_r̐MhA,%`,Wx6/8gyL&pwlxl:|BҡՊY@mA6S 8%P6OΨ&;Fr<ܾ}{vnlTȺS^8~mB! 6PȅFB]DM b}jo #9EeR @oq1LBd)҅$W@ses]Nb_f̶vCQmBKQì3.`?x aȁ'{uc{`f qc}48h6) ɂ0sanif05U"XC'q" p Lɳg/7/.˧<3X 7!@ ] &+E4 LU(lfz7>W@l!,^\?s HUƆ˃_m%#X}G2P4xā0ɖD!9xJpAE>Gt*LxdAI呬ɉe 'Jh;x&ZC6Y肃h&: RҢ2rVҍ@QT쒗,ROR85viz]z9bKu(ŭLg5偨Nol#`NJ4@8)W͋MVMhx5j8(_7+Mc1iY!f@P1'ncZ/a@Yfcvm;W¶P;wWfۍ[AM`'TEr}Tج BA_Ue!VGn1pOЌRYŰ"QbSǵ!@ -WSc\ ] I5jdX1tZgNu~̹_JGKE7VFkH׍j^TZ{󮽧;FPD|(=r=m*5U*A&iuȤȔmatmK]̇m C턡hI^Pwt&2"9ԓ\u7It;)1<^XKlW\\)M  rgpyy8|c<ΒBzFFu R9Pewqq&ܥr/Y= MTv#vJAZxT8\=~%Hl.HmהN H5Ð*[ BzNYoKʴPPuv tN{A"7mï//zzcoC6աkiaK'_nʱ0FT.Z/fg6ֱ~|upz:1~k}mm :Å[ V {]7$o>cB %B>&P;B9J`QTmд"8݌ HwN8XDЃ i0n")YH&PD {8FA:sVOh SxbƓb Z^o&A2cS{H=>38L#aϏIt7PlkX\!~LAsB7 {2@Tq*6(3&Y`4@ZLQ* Lݽoۧg na+Ktw)Ou4$;Ōyaa!xyMw0"Q1UӐC"@]`zAej@XP_Zb: Jc0kݶ+Nښ^dGН#cێ[3iެ&D8_nt3ᚺ*[WqW •Ftھ%kf8`] &+ 7zTgA)Y̑T'rv=2%F`)f(3cQ}62$"4}T4Qj+@a M󃃅Yv5;$eN›[}{uu=XGQBU`a!+v$%4^/ ^НxvcIgiPrmBs Cԡ9ϴp }J@WJCN(oAi(M|õS SEc9K\a)6+龡f{[Xk*gشP"]{bwQG5A? _ܐ:CrHM\PH8OUayqj~֡z)O2 ipoko};ځ8,Y<[eElpsax !"qc4ƠF<ѰV*{1ZCȼ* ӽc :+ 6e" fh;s LK> .Ӄ#N(| NBTr4`(32:ih888i2 8`,wB](rbaJ(>lnD[;;>}^@!HƇϟpLA~}OF;pePPP#9i"}8r ( %d/g4`0mr~eS3C ⇱:8bN0VTDB1NGx(i۵]k q5 S03?Z,2jHNz bi2Ӝ4^fsa%BK kw읲؂4<_}t3/M]]Tvs#7~KD`,U~+j:5PƘ3Y@{uv XL\~t?*K@r.V5@i!k<,Z4v# NZCڙֵ^+8LE-B*Iԝ-IAh _`5 nHb$|R޿Р)0Ô o kEYC8{ɒGTR(4'Bgu׹ R%px).TMN*ga`^)hd7.̢;KmC ? =`"*AhS);7 u04R*00?^INXumљPm!7 $q>&цV)e,'~WM5 !oCo:pWdcq\ FC.+\CKW;?`Q4p.iCugR2_dƄe=Jx:4Cچ$?Y/ee`4f2!g4Q lP1m"hL**ՆR4 (: OLGp9HegU@L", aJX&=.WY=X|gˣ %ܻ܊ǙWp88:amcHʂvo|ttJ 9RbZ' %QޓF`x/]HĔ*C3MڣAIn}.7086L `L7!{߮4@3{Q5Ǩlvv  WuV>ƨ ЙOز{z2N7Xxŵ%&` „ 90_8pJ K=s*AÇ5 iڞll .[U\f#="t`d,Wʚv\bGsb\ D8H@A9KҾFkcSn_a>H4>v|| #!iM#qL]ArPDO`h89D}uOIWg N}=y˗ϟ  x,-/- dF9n>+wڜJb +J^OA#.B׏$fF |ET!7BPnn#<*t%AK%!s*)h,^o]#r'wݎ)~^$%[aw <|;kp#~ۭe ?¦ťa (9q#i;\BlGIUL l;fi;VQ=@_9u|ܤ3<1ƉѢIDԧ^b)hI5AV7ST֥f>yWP "닽wfHRK=bjۇ Rah2ƨū= i4qaC̩4 N$ן^3Ly.+ewXoLfS$Ze=bN = Ŏoj9^MP&o-S8bKVW}ji!8Z}7S*jE;J^j"X7b?))/v FjhrF SYa": ǵNNOF̀>ɕjI\xqHˏ+r՝> 4†X6 FpgV`CђDLυ2u$<_\顣(H!l yvS3WNG*η\^MmpؼEf)枤.J0q xa'}ӧ01<{bg{d{ۻ8 uķ4tG_wQL#/0I>jم]S:[ lck C%AeҺ95Z&l51T#^>ɎLCВTӰfd+{$R8x G:yg\L0 X?eЩ ^7̲PX\Sl^GL1~=vvpITj/` K1eNϞOO=u텹=z7fg`A+sݫhq`}¬9MhXwC_B"8B3vvb#+gF^7#p=b"OztJi2Fˌl 0tA$h6P)S?N0YN\M\2;y. 'g{3s Ta*!$dnpVp<8o|ϑDXY^y{qA;wYZf:9R`lP$5M! Qr`}Ỵ2S!flnyAS"AZ!m8u/y\ CAa^c-y56&ǸN,fyLlR#$8kjU 6 1YQDDԵս wHxzUjO^s}5*DcnMxb&`F 5"徐RhGuE(a1ɏh鶼UZI2n(m~kH6DvDrKf. [exZ#lHz a!MO]m^eZqGޮiWĶ lft`cPGP~[ƤW`T!J2d9"eBH|w_)N\0tE3My^+EQ,[AaՅy\7_]P:u =#&bXDbL2,pڀ$:^(4x:|!؁svuJ>v5J̚^(!trw&)/XQY,$R 7I~ZijDK\d4dPul';ji01044{[ 7kL}؉)~ h!ϡ<݃rr|z697ʉQlln#!vȱ IA"$BgFOSm  jq05TH7doTr4|F56wBs_ й2`[GzJf$OSl_AR:ڒ桘/<)JkHL_*R6v`_9uK^NL!dWa97SmDt 险8 )rm(E "hem{8mbm|'^[XA$CAA,BE9(i m <J{ Jn EDB(n4 KZY8^&ŚdV85&A7mepbtp EJG ;B9/xE[J_$B]s9;7aVt98889OLoVH[h3Ja"͗[-, ?xp{7N*]%p aiee֭[]AF>nh\bۑl$20!l ;-K:ssrr~.<|.%-ATfC]EYm@xe\Ҙ}^Kd;6J몣z~XaJݼIlU#S%~ yo9"cTENlbyyr?u쩉 ) OMа{|16=-B}g%hpPӮxdnp|xdA,hz-Ӈ48ڿ:\'_`OC[>:;b?ɡc^b*ҳƻI&L6,C`p* VBweZBZX[ΎṄ K_Q3^0EN[0WA27*@kI82Ȱ.IO p@N'4S0Ё2B=6;ED'\ X*Pf1 (>%o& ӱT\:VC\R6661>z[pi=ILjYɹ 1>)3 Uh:4>`̸E4 |0;KJB9 `>g+'Qko]=~~~X2*E.aRP-` pTԧ|;]RHGu"K+Sj[u *H!o*taB1Y쫰6qߕJ?,"ͧ52(݆5X~ld!vJ W $V~ T,ҩFϑsU,ÞkPۥ*)tL5 H/&.z8yI_~wSkveN7m|kj 㻡n͓!dn\h_}m<Xܱ6C[JT : D}V w+9ǣΜ.s1Է\Ɔ+q!4ƛ =) hnz%V+C`CDASj]cEB 09eK&҈Im>h>! \ 5BP?8f Mz[܏\0(s#E<$"3++&0:6`?*1"TDp[|2PMAFlJ6z-"NZ^.c6R k^)/""x 9Nƀ3kj YpX(5¼D5$ehJh5i ܴ*l g<_?}Ϟomolmc{ M |݂(h`s@l+?1Bo{"pb%&]N|S,sKYus.B6̼7#!{$0 _ۂ1H~Q4Θ|cVO0d}(V*RC`/cCcVUV}[1xk:@4]٘P vK "t^s)*I&LN-30H$M ۩U\x:lj;F =V*<>xʢjzj5QH%~ef涀zP*b/'\ 4H#saUʸ[XpN&xxƥԀ {D%HYTc0pM\0qdyWz|fi, ɽ&E!>=bˈs?|P.kEVi `O[. ߜunb 01u%kwuw+hL*2BaɊWb9- qbW`䟿,dTuob<|_"Ʉp2/+v}_Oap-w*ز+B؀1.̌]R*| $# 3G{lA%b%[vmOg''wW8[}sm^> xN/Q6eUp8WnP' 0"12?.z^b s۱z `<[ Wn; 2..!H2Mdj@Pf\Iulb0j)5pG_-`BUhPV w/{D#]fZejieͧ%͓1/NNNϖVX89"~~G뛛H=(֗a"n.x`D2P K`1Y?c=(֞ Dm<za9{j1~J!c@̗"v+PȖ`;6Zt0ACxVpH,h-B$⑱ u2Փt0=#B~f,Y1ˈjZ0vעyxk J"QhoUl݌+sIױml1vgCj= :U7<rVM:@G76Sbo?|kC{KJx֗O]Q-) tUJCj}QDK@jt ,dWR-DE]W[ 2dZu5&oVD&^#QWcJw p\P#RRc0eh|@[['"v8 (ĵ!Zr'zL0OuQ'ѻ4 G>F4(aU\ZuѢ>:Vv ';;#ֺx:X0PB#йj Xh4Lq$60\?1s97T _HQ7߰YU!NG17zK 76Qr$eǺ|DFÑ+q(T])>},V7L8L%h@YlHl7e >󑨖LzƘ6fSb>{6g Pֺ:n=#8;sD<!ܰ`h"{{8d3 I 0w^Xwȼ{Cߝ|w=u k=6cSp_A,1{!1k},lh;-cJ.CYhjǓ$<-{lL<Z*PkP!:^t ԅf2$2YM̸??Ue,sRl-ADZ~n3g7߿;9W2I}ʲ9b0] %H%PdF=y[;Qc`2~v :<8LV7cϯp%mQH}0l-+ ؼ<ų40$fN-ɍ􌍧c}Q qG״J[Krn&m:@Z=y&!4Ư%욈 Im8j-1VH vdXfdk-fФ3kP7޵;v;Lss5\ ԓ'v*96m7cm]_ y>x u2 $6 !h>13aN.,~1uĮu2', v q m>Z0 ]|KіiTo:zNKִ !# 2 ޵#*#s[SXoԺfqi Evpʇm10qdHje~YZ^Fp4ˀ #BҮֈ/!{ Ԅ'#+-qBnnNk#l{Rio߀Um2.|cp`f1.\hEg+WF \w85nl>^NJ]EPis%}z$#sEo bѕ2))Qܦ$9 M6IqgxzbA.Ġ{kb~yWg}Y{W5u [>VhƝ@ES \4huTķ$lj$hnIPܢ&1j kzM8KUBd]zl)Jp[;w`#Bx8*@.ԍj磰Մ>MH& ti(DÛA6Ykww}r]_˕xRLFYCnPI®"NbZ5y,]@W <͔VճIaS g:m 4qIQ,P%:*H#ٟioɘ;1%zoUt K ŜS&cЇ,(c5@BumYj͉A4.*ӳf,`P=h8HƳK$B cVђ(U#6?y&5 f~c!0?(ګ>_ 4R gҺtvjV$>d*a%7#uY"N.n[s4l`9LMzD=tquk\_1V;ezа[!lnlҼ -u%jmߤ+Y ݁e+bvyg97Xb6X"vZlJ885`Kg;\-|A0`_s ˆMEt=p+>OA>7+DŠ mӕ ryI\D &s_pTT|ټfIwH=`l[L툖qOO dJ?vpxDXw?g^ү|DENbԡ@\g6Fs 4=YuG}OL8oLBGg'轲&+6lU L%ٛ zi%lZ&g͂|+xh}Byآ/@|r:Oh,uW5jن"C UT3ǦLJtN 7آ6Éմ&snXiJFUAI`coI Oo?# Ӻ IuVaA_u(7 'urZ]콕6[Y{:sv#=Cmrv8t)`p{@kv9"^<{E.uBɢREV {3 s"w.-rB+{捝Fhf{-ؔ&ubemE.l) mp4l;7}3o˩p^>X;o;uaVslƤ+Q$wR=K"nH Uk;ֹYHeR[ g%dާ$jɨrR OFtǂ` /C601 d]EƣK"i@p4ɟ᏿'+.$v!mt >4XBZȯ̲)q92hkB.szC mZ(eƂnV $kXP`//ox  2&''X]> [inV{ss{|c.ano (|<Ċ٬5h4WV]%;C ;mL`P::+K><^at4Pl:Ʀ.Mܼ,떂&p6!,gGˀ&!4 hJŚ Cpz Lڨ2hڐôl\½/dW<]ԣtZBlD"{qpi`hiý 1cNLD1Aoepn»W;~{$q ,A ީ`UỌ7,&";6BO+ A]` Z '’;#6ogw8go> `"Oig+fGqB&4Uei)-lXNDv78&oxݶ`g2W\[jpa\ m0%O~ LZDKL9&uRvLn,wzv4pIafQ%xf~&6~*  G~)0`gv?>ٰ`y p{ -J(egΉ8nnCRd"/~Ȥ2}R]g4Lf*a",ca2Vɘ2Npt6em ,љDMj4m^ꗿ܏h ;~k7MQ1L w)9R[gLe0!ެ&p,JRiG_"[Џ1SwMNI85t{͋/98 b|Rg9y0GJ +z#֑y3WǾNogJjv2E Tʅd0jԌb'v-g5FW] Gf6:VD?Zdyg9/85 ggR*A""A䉌:GG4!{ k I+]xB | Y=R0u ߉k)wC'B T8>։K޹AI h*׎!$D4@Ъ™%=_/Ǖ 6rR}g7ig)9'u_sZJ]֌p/qUkkj OD5{nJpexO-)안)9NW R ֘ȟgtȼ9'zo@|.'5]L&%ǵaogJ(oC*8۷5Ql3l۷X=m5r,BЬ+lұHmn֊*l!-=m·L 85,ӺL 4OOHp} !vw,>vô)W7 ֕a[a=C ݰ w[IP.am*ÆvHu1Geo}E%0G-hڴ\:!H|f3a߼#X0 4ɇΘ;[8"\+ B̬Fw"_?ٻ_w ث 2eةҳp/J;G ]FfDOGϳ`'Qr lqtGPnn߾}~yP %Pq ;qQ ̃`ܩ-8$ݐWĄɷ e'de8z;&$q1'2˼>t&y'fhk8-n d̛fb ai;g|jpݻјLˎAG G F+ylKqSCF Bm[f]8-Z m+bx04[87e!ag[z_."pJ"ڏDo@!P>\krH%3hcd?BS2m>TA/!YQΊ\]R\*wӴ!A`(p\zG|y7L9|["Kfn߇{/BVY b42׫nЧx;w5ˈt~Jc[hl $9sT_1$rIݍ6GMimjxT>%Jb::}u$+cztR7ɋoHݚK0)2`\dY`'X6IsB ~mڗ$8w0*x}$YO \uK%9 TV5YM$vҿ_Ԩ-:}(rDGDWdI)'Yՠ8q zqNU/YS1m2"zd`uk_suص!~EKSBy+2ngBsKল>89? I`cݶ։@ses g3xZ=}[0#ZS^kJ& Z獖#;$V|5؂ O޼ÒlZD|[[7[ mfvqvK[Hl #seY`1y>4gY𳽍[sg!H@L07vv-¢tͶd ٢ GЄUP:&|\}Ŝ`qt@<cr! 6f> <;M7ۉ4b^ {{+`{l2/CSsx)t-Wz3.k|G>?oE$!iae2~n8F(k`|O\`F"qآDY;ݨ0a-3F6`lcRw4P0QjF6 G ߈D@-% ~D}p_d(vw>r$L,OL"x^%D7l#QvXw`tJ dCxbC&3ЪĦޝN,ЬEw]Ĕ.?ìs߷8-Dr}b9̛"ԩPN|(&0eTeɘr_M}lL fX؁FSXYnS풍)\4r|p[iHex68؅9iI'D|?_-TX̡\IlMnq&(!9\dQ"#$  |s>L"ΙI(w+0{V\ny9I=E @'Bਫ਼ZIn JYkى_PYM0t( ,OѿyDm2Ut~•&*eaD8jgj ]J"}ȟN=*e#8={w^pW%{b!`(\vOupU;a&tH,.#dS7@pvT#G$PȮ vA39 &s&xT 7zQ۱c? 1Oz0EMقvW@R(:VspWuHދIg8'9̈`uUJ j~mu)MCh(|jP2S$?$я~/cVŸkGBAD3B ]2egOMpG} f$&5iYZ |s}''' 6 ϷpC&|__hge֪!˛ %M+X׍N=KZ+ERL^崼ރվձ핚vWZOL/9!nD#v,k'GӂPOӝ  j.1@kc {㰟T_ClV}YRGE(8JhΆ4-h_6pi`8B޺~Q(_Ĉ¯Nxħ+u:xmCł\i=}4Nɯ{^ݑ^pe-M>Ek-i(=/ ,ݺ{aKJ&q44uA fjE*(aJ4|8"yK$}8*?-ƥ<_zT,Ttt>a] WrDYHP= ⽱kD^WJKիP#L U")=k'*{sm,~}i(c_>%! .s}/z*~([{+3;꓂y<&dND#qDɆb1q4b3(4I&{}WۛХ'J; [fB*DלNuDd pyFވ=`>'$y#Q^< 3 &g0?a ԆŏNNQ!R%>l^qi@HE/=~_;'"JIofJ{iC)悉ٖ){/~.kP ^wW氼#Ö:7ƏwHgAayR-"l&+a}FFuiްsi |LTvSh;!|/;D0m*@9~ǟ;/ ͘}W=/R` ,Lj}X^[EmB3I*KgK%Yg1^EVs4XH|@ bnco|oߞ|u7/^~|i N\mkcQ1= Q]*+#WQˇ'Y nj. { ;8nkaw} (%/0Y3 [xˎ pˌKJ᩹{Dۓۇ%_zi) + QTc OU=|wu8zB&'(8:"s ;* @HW}szrp} p2AfdoGL*=1Lx?K6^w(g3~/6Ӿw|5a[Џ3w|΅I?z|*Wj?1G,BDqp4̯`E\tU*m EonMqPkbT1+ ?Х_|ngMwk4$inkV}ES'I9l(DwZջf[P,tg"+b}O߂ӘYQ"tz.͆#y:g[Ǣ̇ Ab!B^Z{agyhrHt,?X) V ڶ? (hb}xt~gkw^cwGBmf4{B8\D!a#/<>9l{ 8ɒv,2lxz7X;,gqe!KX6Gw Vkb; 2%2_U|ku:̌2as3XΉ #pat.µaĒ5Z~ic/&훷?:?~@ħ;C1&܀;BeZz $)ktbaKQ A ȁ`$ klgPώS(S4 m!VaF'/|ɧb8.iSPJpGsv~{fG] M& ;Ysޖ-\]\.٩ 3Wv&5SE~z~u{姟{2 yRԶtX3r<#''_3ij|'DkR3-&{O/޾ Bz@ە=q'ε;VJkr0P[ t22:XQqk-_`ԉ9Ҩ^P. b {\P2+mlR62&O|`Df@PHXYDWazKL(3Ϯ@!FD a+MJZ]яU jih̭ #ziZZeZh;=X[''h:(⢋ D}Jzs bF /Ӝh9c4#+*MR&kݮM0+ZlE5s9D ң5XuD[㶴ջ'ohrZYF?MT{RGƂ]7 J|4uȫ:eծXybbP.s<`W&B~Gpų9nKg’0o>CCR7}PU %쒗/_bi{$F@ Fs\4;K͌0~iz磴Ы'bf(Tx54FyvVUG=aEk {-']z93F52u? ԇ.³GMJs/"Y.n nR?1t'n UvoT-{x< * IS|'NqWY`ĵfJ",'$QpC_sIXpҊ.0:$ ]Fbg 8XHٹpVDgxqhľ&51^>CS9pQM6*#FdsT`U\EL%)}=4k#c¦d Fz$RN3dCEHj(FpjHت60!)$gh [EfшK/=}kDل< ZSQ(z\43ߒgqzWCRc%ӈlbx.{:7`a 98nܫMa'4_jR!E72L>;*QHh: 3`z<@";'2H86իwwx̽3v_|`}gϞq_s ,7k~Cr 0hµjTª2cjk+-ؾ /#W1{zwwKhs}x C6Nk9 `^?.!־8D625<h@@TtGǐΏ~lE3r(ikX#S>R `_]!Fȥu3U}6$`*TQLFSWnacg ؋dpIFГ*>,+}$/= RFl1౅w?}wk/>d ,GMXDXr$]&dH5 (N/ab) taW;YVmȋzP>4*u +n4Z|VM@Qv\e(wdﹴE5sYhwY7h\h= oEk@)ZXu|񈴎ɼlVKOo䴧cTm1qDMVIVm2w8J'5f 7=( w4L}PP+:hnGH`=囨(ѡv Ho%EoErjXr196ߘ% Xdhǥɫ!I^ N?2L\*<QhX]HtŠعX״3 1h쵞>7T'"f[faZ]Wwhh,+ No筚Ia=ɮjxax p)WjRZY[w<7fu3ӑZ#7^M|oh/?d@l1Wx>3) <1 T'_2^|7?,hi` ~N,,=fW =3ܜ=KЗX6m!#Nz+ ̕g;/^r.!A=Hx^^^ѐ:#-恻&,=NDFM8;Ҝ [9roE.rσ!-0aaZ؞ᕰ0/w t(YJ fTF[Ԉ8Rr,q@얔ʯlTpB}RѶo’yHtp}yww/|u7 ݰ):AGNo9`xBZĻ V2Rb(Q̖3D1sF < zRB-jXk|oسH`3"i "Ϸx([g샔ϻuhsTV25j)/'FGp Kꇮs ⬳5l:42̊+@q6.K,}*gSa^-kP #,"Ʊ/^^{Q&ܜ^ceV֗חiͽ=d%ΘHŃ "MHuD;mIy=+}zpuo]^^a/=0Y<\\-!TR̬ud;Gώ>w6֑`ouowegwe{ge{+[vW 9;<<8H'J|Be utT݇Xo71Nmi; n(ƕGB΅Xۊ^[NxM344̑u3EˤZ%]aE[ƾuVXtwz\FoThk JNG4 K<2Вd6@ؔ숔NJ{Q@N H5G[C?2/T;lnf"|uŠ7_ 0> s5S>Z$ %Xh 7?6fJ{toTQeq8j=,'OGa@ T= L¡nPnJOOI!Q2Q%a]Gۛbtuvvu~\i99 }R\4ALOCF-5۷o};C+ '>sSY456VwgV|1N=Z)'B\+-e34|7ty_TB *63'>]T@m%2[]v,b^ S"l2k`˙X:cGd`|^zMQ?k%$dx]x NAc|qGAAU?G߶h6kr|N,mw/ma@蚣@cW؅vU5eE\4| Su4LZBxbnu'y7;O*oւQJҬ%q%^4h*)vhͺGrrI\ _ZafU*'A֠(iY?Fo$Hm5pns@7dq3˅ͤܭ?.m]#mlڒ|y}(Y>'OD`BTg),>z .΅Rl>1irWDΩ7ȋ;)B.4טl s S&퀃_?__]#.-&}¬#2dQ 2 saGcŐQ-f'xe3q ('SZC YÕAT$t C˞'6z= ƛR@uww 0K_^XF @n_3],k䆀Ca."H'~?` -Bn'`ʉprrryq@D!*zEO|uP }q" iOUTHELŮ)Tۼ.aFm*AeIܑKFȧ%fIMHUU#\m\Qn^UQ\E )faY|M4n)T%jݳ䜖YwwA%hEFWCjlĐD}U&7CTq g@lo13HLD4|P#΋6x5]E;ć/L$GL"ܢ 2xA8PK}iۺ K7#in2|< 2Oo'J9Ȇr)&m`/bz=_;uD;PhY[Q'VuZ%ÊZZF=.[Z+-`yXӭ/=!A~3g ӿ8<@ <=0m۽݈ M!an.o>|sZ{_"!N@> ;7wcF&z\a1=$0xn'< o\M.YPv6E-p"׾ ?_Ŧkeȸӄ$ Ɬ{ :#֘ y`_X/$;-bp#MGN 1RO\j ?ˑy;4͠}鬄dJAR(3Tq :?9D?CXRM7g@rSD.<5CMخKVY[y^=zslynb:!{&!-G:P=~ȴEY J;|!> 8qqNVƂ^< 2QxHsi)#  D(_\) h3Q+LG[و,րs Xr։g E:$:#Iʘ 6 y"/K4}%d>t7(مH`@^ ꔁ]j~HeX,NԪ!yQ蕷Fr[˴ч?}DHv "k Q@_ јoFM80F@v%)^Sis#o~yzdR٥=\j?! RGٜ@Ћ8 *lr6Dc5Y>Q[Ma*ZA'EJbPաr$ƬԃugZwqⱲm,bhs 3^[nt޶<043x<'0В6 >IgV+5p6A`ac{kc(WoK0|bXcN7NG9hM${NB?#t(vKHV1\F.#Ar@,hYȡAU/T_rZГ,ߨQ81[vWB(3Nt|Fw;"}ݣc oin1WIAK77.ؓ%i{Ա-GM2P)끣;dx5jF`>6}W oWx"IgOa)h2,2.no)ke Nr 2&ŤtXD|gBTaRnK&}'|tTxss}s~p>sGH=ob}Y#7b6R'#'`(W>޲4zãݴ3)*/qlX>b&_!"K gZcmYp0R- !wI}Jabi'2(;GF4}m.E򼹹8=˯onvpNX;Wq|NVrі5B3{ y-eiBB؄aSjtf{h֒" X!-@H0+AE&CFa0pîej>?[WvI.D UD$ <_3 7ܨ67JPXIu;o /s.ВIҋƌI+UihWUqʒZ&rw$5BrA{aR6+ ?gnqF r"զhO`$  cG|D0-KLPeŀ߶eXº/nnחvwz OU-·_ي2Ⱥruaw-`;hyV2?" 0Ύ039yʽ.+D:GRO G_WyYj6Һ}`,.dm oG} tut3WKJ!B6H -$|qor[$V"")tG-EWݕv i?2G p40{\A;]־eb,ZXض ]?B&k}nf*a"^s܎u#F*<2\Q}tpE! Og [~>i+T:卨lsS%l=AآiR9νv G2X2J] 7DzKtRsGVߜGLzU?Wmq9Ju}3D2 ]~yH8I@Z ,]r.nt/WQNhq+D u*K5j2'ǂ&SEE`UU Qb i?p8; Ů#P]l% x(ಜ, ;s }oֿ&Lqn b^Q7Eeu7 Aejk&/R֪qUBEMTyJ=ڊatFE4ʾ)> }*/>9BlY;J5ԮjX Ahԝ(~xׇA)N&+qDТXUjV 3wTi6쇊'NY/ߋpUH4" RHh+>u 仺ߓQfaQom@.W>vyO0Z9DX@,KLAO޼;>>F¸#MVOSdvٻ1O;;[gAD-=:̧,x] tT/d65ZV)$BA|g-Yf SOYzmflڳj4~ψ_o~?/˟wvw?g8"s>)xv^`~<-"\pSg`:mn'qJV)0ؿ)υ Xj6Kp6gQ<䉧/SWJ]싴ϕi PW'S(:nA%gcBNig?X=Y\AJu^sZ򒹂 ~x  f^ϙZep\\nJJSȝZd*^zrke昊y{ԧG+ H?rB]& =눡2}J|5lNGwl)FK=g_h7Ptӵ`&K&YL2qoh@)xej= XFЬ3FPԥQCP_W [oc^JOM[8L XBf*çQDUY7>LJ-QiSa%&B~lh--@`H֨3E:;٠oI^tKD_}B6/Vsn5\ Ԇqvvnhv.(~vkR!z;1f(Av vγOd~q7|[S0s !o5RJ4OW'oT/@DfMp]Ѹ&0]mn7d! 2N]D4Z a` fcm+PƦAV퓞mFmj ^]_bqY3VAXw3|o޿>=]{xۃb y`[sE@'[VfY@K;kX(GkHHs[ ۀ.w|XuK{ hP#,!)?Eߎ$MGy[x^05| N%5@zrR>OfZb!i/qSvZb |vv||糏^\}р7pn6PiŚRYm! e_t%e}RJci|i(12PߜsKkꩬ ƭ"kMs6o_MΛKEZX]{٨)0 YafJMnS0'4m+>:L+tpndӘq5B4NM%#:„Ց @l0k]4v2tr6:iJ'fkJ&n(b5u⡹G6ށ6)'`>9(\kξPdl5< $T5 5wX/ a^D O߄$4Dx>]0)U^I0\!Lp8r6Pf` uT4LDUmjz*"v!.6or "M4_=}@`ڣqDVR|w&b&o|*NK5t Bk@*X 0sg)lJo9iɎUbQh>(Q=-<06iޫ;4\R3޴ϲ7&JIGm3_'1Uob͒aH8l=Pk]|m.n+ܣ\H}ޅ^t] `[" lj@,:,7l>?;;E16m?nbwo X-9 Y"Ϩ>h< ;[[u'ċݿA@mO? u4P;n )ێM: ;1ewK5uן03*Ăt3f-]{PG$8k9E- lCx=ߝ6Vww6wַt$# 8bpG.KhkGà _u1|`~7e@c#_GL\xLhq q" zr Z?( ؇b6!Cyǻ+vG0YZG:beV 망Soten=H:K Lja o ^ĩ18/?I^]&WFwq(+9ruGp &fgNx}[m\:t߆x |Z .5Te6OAn2<ВDNv|̹h5s%pnSFQb+*5+WXTXԽPT>X{m8Z Nj#PϊͼjLKa.7+Njdڭ_BQ]3 Hm=/\|4)wyC`T&>6.J-Rlh'rgEELoJ;g& S sh+7=K`$&([išM ][sx`rk A@&WsY|:Vio1HY7XֱRH`_p0 c9W铂k7Aڏy*^xb;j <1G."om4gqEH*-mbСA]GӘ mhLS#_c;טA-7f @b42~ g[=AcϏO-J:7} 5=+;Z5LrN`#]f3zQ+DwPVؘ6Wiǁ"T{X`z(bpN}8z9 ຟ KCު*[~ 6!~i ?WWXѠ:ȍ߀U 9 [T5*T)9? *% ?Q5SY I44V072['7VWN"Ib97F4t|j]ψJBrWb)XWoWa_A s CƵB~kXq`.~ā|1}C\FQ㪩Y poЕzxӇѓ)@[ՓY75I-{^Ye˨ֻ8, ďsj)8 dpl\1|6+nE9&O#4~hҀKX2]x)99!%Ykqƒ*~24 ڎg# ˸[:1--c{*4 PrU?2,nlnlbqa"n ecyigu |~x)67 ADvo6iB{ !tc[vm v &## 6l0%SDr;92q3,pP:L|  k*8CHu4qs<7Rb8RL5u(!, $>i~}qr)n"&խ-ئKkLG`$[R; 9h$h>DT]*FHu :IJ[[)M#Ie 67w>20@! ,y:(P: ~`nYC )p(mG- rr,p)#R]+@;|흭d꓏|]y3vʬiCA<hڇ.mh/r"&fvBפA,wԃ¬?ޘP{S(f~ &Z98-7,* dUȓԚPbQ:9N|( )x?5mNȱ dN- )MR^PHXl&Q Gؾc*AN?k vONlk7VQ&Hկ{%XLbyЙR#vXM^bQezqC֛+!Ztƙ.g QI|RH^dR-/dR-ӾnqZqȐ؁(P7Kow#a=V&8}~@Hpu5ʲ9DX 83M:3G JUf=UNTg4E'(ZKB UharxF"XjwjmjmM8 CNRG&9 ,F{#M姵AR2zpR"*`̷XEjFc= ɆJn AVTnJm|=s'kP@Z¢mLwl`Fs6P#TnDk'A 0D@~¶gmMU욖yWO4Lvp::^fKyfO4㋼!-k-fdQMWNLSb^Zq6``,׫K*ɻ2 ;\6U?7e.>֞wG49?<@Ƈ-tIؕ tp)wd!)"{$~!6Ww,d xUq|];=;&bD63/@\HAf)#yA, r*SImJh97Կ{KvIqt|ZP$ =A<|G9?\]G_ǯ߾Ƕzo~{y} (GNu<{*8fst`[]CnpdN/JGXlnOy!yPP y4{^$E 4 3s0~/mMd OF%ljX)Kel@dY_]ڃO’E2='A gNXb9dR &7PYbvlӲe_yx\88<1Mj6Uo RbT}hsE7F-f}qKlZ<ehWB]WQmT,4T8׆pM3U\s)'IAN/"ZU%؞zF#0AƾZHUU.1zHq=$fQuUe*E .*) Pjbio`nsMg#4|W ڝ)uz6zNzSoX#SxC_@JpflNqK*I{U W9QK*$u7N;+W0E4]3I~NL@2_BC~@I1%/W4:jJ&xӉL(X4rN*fm6OD鴸=h 3C9m|%LB;,䤄JډAڱTc7='P-I ->'HشыFᭈ'xmJJL՗3J%?В/cc5{MTkD+;_jEly3D;CT3!hFo@|X D]୘I՚6ᔑ'l1x[bjv፜,NO,_8izŵƤOuA;^ [x}Otthqϐ 8y:/}B7ૄGc6 )7Vz!!3Um.J|rzS=I7XK!FMDr#<5Xd9b!JStܚd#7_t/E1 7 7}`Aډyl@|7".nS})"Rx8"JU2W]/C$D;4;/:pU ^[ (Tg$|Zzu֪Eh"Xe@˓<+-x.FB`e1S 05vH8͙% r 7#ܬpY046\o-o0v=mq2?; JP&A"̧r + bc} -v ,,B"IgiBY)f$dA ޱV Hт1^a, Un>ˏ|&*!5jjk(uBqZQI 34H\MXd7?m/oOoq%N4iNqTs3K or2n|HX[9d)h)nydueܤ {ٰm18kᶮة|+a`XGPÍ$1# ?x5)ᣜ.0! yߒ=Жޡcm D7z@ n1h(c6cTJ2.U/]竈p"qhɀ``I0 @ȝs9,;rv lvS Fd刚' oNc\&QHw8͡Q_<0%Sk#:v sYfb' EJ/9kb|G/A5ip[8r3ёw cH3,/`!TӃ6bnu!Kuvp;uF\l[?=ˈg;\ߚ%E8JaA2B^E0]6d$w4U:\*qhp|ə(V! K,D핂@1~J I9ݐ`!D,&b +TUw.>p9:Zw3dD80y-$]Oxtqw?6 sYe!Gׁmcc8OKa.O7@_R#UERj§#S Jq0]HtHkRd .j UWHmi?)>1E\Y\'*r}6+ZAn6W1@V/{ׂmG1 $9)yNX_p-@* .Ė 6Ö^F}q[Qq,ӫTs lt!y B(#%øqX5:R8/STs XbW!uAhs OØ)tc "5(*f'^ym Ɣ_1H]~?ɗ;O˿77~ן?uz~ۥ[3Yytˎ/f=z=Aйv19P!w`ȏС+6‹5Xh©Z0~m5hu.I!K8t\Ax2t]OuoY uECg(@=(lgZ֐Ʊ!SHlm|!N#A@G:n!2@=Ts}1;8uFT=Qa^BQFX *vr7^d4*S;S͠U,%]W]4HcԚC^u9HIwi#`INF=8G|@MNsN5V1)ﴗ]L[ղ|(0T˚#L498ANn 4BW! K751 co{V^5 '={ԅ|Z&?wßF U .zBWOP#L̂1\z[ujuΧ =&3|3F Bp7dbNFLKn䱤=Q3VtW!wkݍkMǹ#uv1Bsڋ>u2R,\J2:8@7)Zn$'} "5]Ce;;RgkS ).s6^;O x0KW:vؾ(}C߫YOGkL^'Ew-}Q54\M@J\cS[aavY4ʼM q3kEOyPnm)&Wvo.Ȑ.zvDs[l/<!fU7A2/ >'qD:vS ~DŽ=Q_\D WV ȶ{sKYv73A[pCx*KiUЍɿfe˥(? ȿ@cMAKb ۳'kw<Μ8o#*/hXC5>le64䴰&P4Iؿ>pCl-/o.-m<-o><oL;gT(RW!xˀ4%mX04)dRF'0jh q)߳kx_N^̅@=)V2%3bE-ެmQxQߕ? g9 朩!UEEk!]wkaeJ7Fr) >㣁PBJz#ċg>7 f)foZR̫fUqtݮΑFxjHO 6ZerH"" XZ!;ghZM^aE [ܰ _S/ѷ u]P5he6|B3 2 jYPQށ'9@Û> d}N'ޒ. Gz/M_AŢ#`t%P|uQ@hzF-+C_?+`-Z>{MhO $IrFU]|]˳ ѕw23_lQ"FgJy֓R[HPd]5mr/^\`Z[izTKWވY :S]KĂRFMkxG?-&&BYXz9x8l}}WR v=p"p7~;,?-u_/?="giuiV6WWvbeau~_wm_z92޲;+[ۏk+8Y9Vlʮ9k / ÂDb "rȵO*-LAXT5l,-?'.·FT%IKF,|&|FI{`dЄ,(iL d 5_)bx7\,A4JY:MlWHVk s8 ֹzg0X?$Yihɾ7ʑUTnrMOKD4@ǂ6C,fM  ,&W=wIDAT=Zf;WDvF6u÷TR x7 Qjw(1 DdpnDyȯwu5&bGС)xhEJ.zٚljFf__a1ha/{s챤S1h ;ʐ|ɡ)N4a[֢4zh`NIcOh˦ FOP:G"J C7P% (82O%LڀHrEͼuwp:c)c6-bڱ  N/NOow}jICf Έgks ?| 3:j;ÒEˉPJ 9kWf:;N{yw|ӻwOo__<|}vW+xj7WKbo.p-x}zwhrv}uws}w{8@~fWvgT8kp4}2vffD7@i)͋}Ui [@H~šn>wV*U0*ϣP$IU'inRn$Uip.ر4Aa͟VZ V`-&y-yNQLt4^Z~h-`2oW,1MEROxd>=Hq]jEWFĔdxe'6o!8AjC=u*?Y/K $> :G/Jq 4z2W9aRk .|;+6_ _JNН86`ɮŲbW%q^iLԄh'iQaR/#FC0 P큊`|)q1na{.RDzQ:Gz0/&{#!`N9|<Th 0&&rns"_׊x)x!%'.hWh暚hH*roe4Zg~Эf,ݢb8]*F,~XHgW-qpb/ސ4 = ՕZ]~۪|FGLZ̑];t˷2E)ʌ0PfDZtBz0<A!իk,U9_c֪Q]=ZQCcv,/C83XL@ݕ˫ׯ_}g| ߰_i4M8ӳKz%X+V?cxL4= GW6MX&$ Hivg"17p&"L 892Wpyly>mWPLܻ;{kSŇn?h#wXB ړbm lQñCAJgk#`ǭ*Kfή]oWw/x!n={vsIRicdg@chQdN.A;w.dvCvDȌjIniv?dV֩?U4M]e+˂Pftx'0'ٍ}p67vr=N/۞;rxwAL#q.ha#0g\Ep8y0@5Q/x&m*n8Z2R2x|X"D.l>;:8 YkχKW/B1ucΫz4@Pj ]qDb]87a18\JFvM=isHW6T0jP1ap"RQx7 .Z]e q#F׼hV:^ ݿ$V9 0']'Xuz -"7UK-V[ -zgm`\4L@!9 xrQQ<p/z`/%aj%sԋlg"88;{SwAA D|1D_scSMu¥64YYAGr˄CdIXJ`dϊo٫qgDSEB4Q8gqIIW]]gORX>-dI+2'07:8:YjhdÇƤ0oǚ x6P׼ V0FSU"Vk٬C7aހD;!Tsl㻮t4bѻ  ݀E+e~s>H[7 iڪ61 dlB=xQpb6͈pXve7FyG|9m4_>&u^͸PXӳl~!,5f7 Efn\"m]b躒Lh{@Oϯ/hxutmM?ܡK_]vyqyu~`;d`~acmzqvn ^|㡆0b42SL^H'1rKRYq,Ј~XVE QfQ  XSqtȈb:,Ma׃$ܤgp!pHHh neHR8mRvp p4VM`m_A<ӜjVl'բba`oOQ(x[g;I$ur] W.{s^Mp_1T1"=ZzfSlۯY6\!U7EB5uȮ7.H>HO.Y/mSo/ j'qv[[3h;pV7}[9N= Ch)3y竩M8]Khh Db B I`0m #0(AfyLh SȂ}U::MLW"DB둹.u-ފ%"vS”~ <zӋG45w$i(*=8@rf ڑC;:-ބ+T@(j&4bxY3kM}:;2FnJ# 'Dz'|Od4} Եڐ^{?Q~wZ*!:lvU}  ('lZ cz*,ifⲝ:C; R39r==kaQ۬bI VRvc4 Es&h#pHtuH8f=¹͍ܰ8_Zm[FSXϞpisiD92mZZp~|vys `vn̴ZheQ| Y^S:_oomaݵ]Dv3Bl*֡oonnoV-}ݚyD7p_;B( P|ehqXJmL) ?}t4>bܦcX# h_XnU3 CCi,毬!G<;P Xe Ffe*Dt>n [x%lZŖ~8$5Mގqpss :;>>|_|{k}!-/^n _ [kΫW_zqxtQW67W6mG~`gjD؟]2-o tVXoe/QX|,|BCFgS>欢QjpB 7jdJ <Fl& pn3Lzp~sury?:G_a uL~d+^0zn۽͵ex2{7ة33{KBE}ERŻ"C7]\XS{J5-:X&jUϽV@ ]mmVOl(T[/5Ox$X*jOgզ)q4B@P{aؑޙ}WgrTkx( vi@NI{wE\3jBRF, Qǯi!\w?L4 y!GPE< TڑAtwwN ׁS rg) >ij+{ Flz(7 ??6Gn+D+̶8yTmiY[_M;lQْ۬\ս_ jwҪ! Eea֭ 2l h^,@4"Rсͥ+9 F|(If7Moxb6Y 01u`u=bmM/5;d)1)u;{t;Ut/B+ŭ +ؽZX%O_pHl%#~9S'mh]/G;Zc sH(- ިذ@Ɖ,YfNڂ5n>ITEuA5#"l'أ3lA{ 1}mK01qNrnn8T""G# 'òMDm!ve) l:r'H0Jج(K e냹$l[)aTZ6-HC+Bi98xÔJ̾k]4; Oh90&@v߆B^~DT0%?Y&ͅʊltmPٺf0"^)VQ(hE:*( vΤaa;G̎t? .է͌>8&ۛ닫ɻ7/WVowW>:@ݽgώvwwcM3"M`Z.'_MvOx`4I,ٙ킬|{3涑}`PMرv~ Lgh|XCL+k ?yD}ziI-WAgmĒi)@p| @g[ۛ ;eN׌+?6gܴpѳcbbxGB~R~CeD1?ԥ1+Hbu~@ ƠJ26F6? :˻+; A/3vHT,Geǵ#A1rE$ y%,S6P4AfQ3%cPl[.{= T0jNpZQT9@PxLnށc&ϰ7s.ndŒ&K&1)xys"*UEJ\NK  }# Idi&aY_V8/3cMUw\=u_& .mZs:*7_RcBZݫuxZ+*/Y(`YdŦ鏳]HhS[ODuN*5=-5Ex0ET> rh/|Ӻ7N_w@Qw V\B%ZDk@dɀLTԚvkV[s(Ze6"ӳomvK;zY~Ĕ~+GsDLOM;]`+ELԄ,:mEy|ጒ|,1n-YUd޲_'o2 E4jGAr;䊴F7ikEl6} ;rK/o8zaf[חrao˿xf <Β#Xʥ%$Y5W:aõ,uZػ2Y`4mE jpCʆͭՍ r d1hL W fksunYPk޷  8hH;/ TZ3r+ё>{Ĭ }dnA-|vֱĶ =mhP"=`ۛF~,㉥5v\N..Oӕvo/~phw;خ@* fqֽ%T~qrzyy n@N ԅ1"F\~Q:JƃەXM$!}r ߣ~2rKGpTY(aAwIbh!*ALJM `s`mfkY)@J@tpx˭M$FuX%O8yyu@]Ufw2P){{}>R-)$=tB(VR1'zşGc<_z]dž7W[,Vh $",J\:"0i W>Hb:28b0'UW'ZV$̬у1hFQ|񯺧 I|D6{^COV* <#$TA7H/7\ =->X]V oumhyMTpޑ({--`"}F4_ ЪaYo 5r>P@zBu,h0wK76p"&CE̞&쨚t^W0T ST/Iǯ@?Jƭ,j8ip{ =.ٗױo9iˆ[[o{kww 5ii( 24Ab? <y*{w}˯O.Ol{ G9"G`E5b bkOOyWHF?uRyIݝÃ-^]ðypr#P<3L\N0Ų\oZ/>?quu}=|,ƾzX6??8D?]#(=Ⱥ&{5"B&ezˢ,FXs\յ݃8BOO/nT(1m0V8ŋO>t}sØPw) ۻ cU\Zv~3 =/O'?'CfRy_"u s!7nFl{zz?w?}mRۇ/>ܦjQMjRG$“ˣBGj9jcݑT ŬQWtNɘwLctODeV7ܤU]6M2%5jTkt:Z$<7 hDwXC~p.}CYެ^(%zQ:MqV;imvನRONϩSQ"|oB>Y;.,hś^QeT{kӞ I/L:qE|F9zNd&L  2ZVMdUkySa "w=LL-Nl kjψ;bi,b4r !GE TsA}V(A71ܸxh]7'T)ݘ!&Y@CP@@[;Q,lSRUDb &S M^ CQ.&ńwj5Hg:T&GE)DtN ǭKUZ* , !}.R6F- WΪ/fpڋUvQdz1;1S|FF4WQ -y{,S3ȓ,%*E!uqv>Z6v 7f˯z?C}ǟ~MLì|P=llK%»@:Y]'_>ˣ ], '4!adbډά6} k 3gwdy٧Şn `s .N~> m~t? -;{bks3{o` 6aY[F=e7_8= rT4Bhۮb=֮~vzzzr;V zo|y9$Q d0:؆ ֬yY4I'O4 v-Ƚ  7`c#o Y 2Mp@OG{\]"7{vC-/oo{ەO~G߿_<+k=xI8ȅkxw 9qlhMXP)S@qp+\ x<` N/{@.\@'_y@X>zrlnmX癠8 w$yn7j(s(+D\%᡻X߽9;=¡$8ہa ΄Oc-ө4?# 6y:|xtpg$[&*+!ƅz@/W|-Ǚ<>AWJFplq2ȟ??яn}{+HjQ88gs|kP]&)FLk6e R/M10H},apHYE_ J9&`660h]j2<*z\e$\ ^wS} PN}㉨.ǪCImU Pr|^Rc⼢d~e%.=sӠ/9٩'g]VM3q4R]WBk2f^!P& cGZy"" U1Υu`@@~/DsTFuhֹ#1өv_E4TDJC߾r,"q|IbWrpf#٤8O s{WMeۤP|D2{Y',>sJE+b%Ee( g@W 2j~zţ LLAJUˍ,tU^V"¿ΐ{IdRăPA o_&^SɇDڤAMUA Ut/55* yٖ..u#PX(#+BˤthF:!_ԕ z7!~yͥYKg65{6M`c_lS9בrv0;U[نkc9 t( T]sA3=w:.Ƈ6 !dd^]}c'!facc:.EO?yˍ-K La@IW>7[̷=mg6X ?l>1ZxfV~%vgCziԲXOgl.!c]ߴ{ع)>D+q b5R?ƥO77'gpzqv|ۯz^w_ͻ/߿yuvw~q `B*=v<G G`C6X lf8=@=n9M dvTUb-Ԁ8 7Fsx:ٲ䐖X{8, 4Xdo/(;sB/f 30ޢO+kM_*F1Y^YN$y"Y(oIoNNO7tGO]?Qi3*{S.o"C|V"5^z{{@e h]à"DrAT2u'+V1w(OOW;[Mj% oRvvco3*fSWQΈʓ[k?4:iY[C),r#A(K'`}=ۚx;y٤I[ b8`ޕ6]?skchgdS.<&m>j˴m| . :y]w$ gU@d?c5b FGC0s4{MW?N \ϐ(!sPu;7 <8fKuTJ7i$yQ8‚1:t_Ѐ~98=CH=m8P8/oS.c^I%d߇:>υrdV%i,ZbtS_FDtWmCzcj6Ԟ˃s QeqB/'RQp_%}x_ŻϋGE'C`/Y>Qmݴ2$j". x\|E}-4(9:-[$ {u&ʦE枭pwm9uAX]zU,:wnnNv/S [zM#JbCZsޫTr,9nTSڞ~ҙ3@y,{w`)ĕ< ^$ܗr%ˀ1J:uOYe͓SK;8DEs\gprsG!0ԱZ*: gf(;Gm_A%-\]&>Flxq\ }}C-mOl+`SD?=2=N˶{ omw6A[Ih!Yԁ$̖te\2`]2ާ"\b,W@߸C~c NP ) Bgq%=&@iDe=L60_ 4~~D=?յ/`/i0"t;8 gxĀ n0P 4J?^^g߾}_?ww~?^՗oኸ>@;H,Y2hxpBr TzuIl;,]M zsʛ)Rk\FJK>uhs֒24bb1+W+T|fmrQ{-ܤ9jB5$=*bUxmٱ|7u674- oV8(⤢}$ˮd>HФCsB_ *75nQvA\_ ߊ6]{aL?[T) vS3hbQ#;WFD?6GDv s D^ #K=+YrQ&QLNeOY\P5缧fk$NAV8v27տTOeUP,8ߔ!+koJ޿8T Ea,$ԣ&L0~Z;,\WQ0de٭9b1Q`U6$$NGgco4EdlM+*GҾrR_S:FG(O/U7riY(WT0<(9kM1s~B`]_lH\,ث隆-SlFn3od1u94.vͦe а9Q%_z}oBj,Oq\\v˳g;r'xp?cŊ-3 ]N.O|x{x <%F*J a;&9E?X fz`[a6g7rZgIp@*Zz @k%AdAߨŰ |`ƮDry~3QG~1b,y`NDf~:L ̠aR uXaG8,Y;FQ1/˜p}56} _;?|[@wW8 99~p<{D(\lm`g<%T R<.Q# {IP0ClC{ :tR_%0IF5wyYM 4S7H_9b5Oa•9=i~:,r_ZGp.#~:JHHnhG H'qt̒3"^/\<5ٌ\wq~lR\RPVܳiN"{(1QrOSaMwъvn җ{vu 7XX`vCvLml!H TZRylPwT2\$(z ǏF0^qt.ɩ hF_i GmȐs28DJ]U(cq”蠑3rVcǧ 5F(:-NAi.z#v;@9iGW7oo 椛s&]]meHDiM,JO&w,U̩OVzH$D}cϛ|3D.u”9{A7w V+AYaNJGE n+~ q^oeEeR_pnDCP"༏YqYA-ZF4$*;B;CS V J >-Cu 6Ģ)cB (\~-F*J7X%*S N{-O*Cwx}@5]W?`x^hRJ,UUI4:mI)1ʎn };Huu7%n# MH5S>Gґ$?nI>-0B%;=NPZ)Sݰ6_o9;JuX(|lsHF׏BC? ~p(!Q Gn/avu\sM7 d9ؔo#4 |rKr`}"My'-Q\MNiED#])%>Edu`rPW2ZPr9Cp8ݤXEȁna^\Anh<,b JP8V~'۸=\|5p\mL]Ӵakx|)B̪}J= t WtkހPN&9> ae*moV(HΆ-xX4k;@Tֱq,|@uIsP@Ƅ?1d ڋ=~Т nPz<7}K.Wq-4vn4ٵ4։LRe\j\^ԡX1? 5fxnViX ۄTF|U!t81\QyŇ#nD+z<ͲX.A5zSVY A2:_uVjzǁ# N+ Y*0Eh^N<`Uǽ9O>0^0׵%zG}AQsR='aY-|&f)+bt=}i+@+S⩨iFydI;@';;0[bWe͕P>|vmViD/ P(bQ !Ѱj%`6 620E EXfhUe93v䪵U۲lG1 CS 왁1 .PuŎخ_[Ǧ =0\}o#3" pf&6ÿxޅ=^5~|{87'9>=^B=eqZSnlx ڰ,. HeJiK&N  Bf_<Һԏ]$D–c$ʃ5[tΪǾߠaq~iQ_@VH5cUBP*5eMQ/µqsun"nyCPSV X;M 'S]w6rXN8a#q5Mquisˑ2j"$PAtKysRaOH:pPl0=6h4ۤXtEnXS|eyu}`yu6)kgW-v\73Qk}J$}v W%/E\ykG݋%@ kYjywB⺇,p!Glwy]gdJؠ r89H+^( ,/Hi N,wƶ}-ҾZ5@A}5Zjgb{"b#frqGUYrYsy zGHQg_F7S1,ئ elr؟fkHdlw9¾/. nX)9q*+t[<`뛲Aۻ7O/O1o?:@oPlGWٶVv{$*8zfƹ EqHp~|~DOHPgIh{#Gd3di ,-< idI"dQo=ofmT'1CNʑUx:%}DKsOaH-KF`] v0BludZC4 д[Y7R3lnWæ,0#iš`HͥUd|{~{{壡qWv6,'my<99`kXFn.߾ o,!Lc;b <'Cdn#1d,=hHO6s6 ;h,*¸Mb5u`"ĞPxNwI%2FSrpմK)R\-5nmlz_S>4sRM(Ʋ~P|cLL AFxռ'`V-fLI.vOV 3[ }#O>Ɛjo`u˛ Og  ,$򳄱kT.[{;t /Om&(](BzV| #  amj#D V7CBM6N9P.xSJ'0݋ @o'Ea q%}t^aICDXE%ٔ3KA"~P9;Aލُ؄t65~'g'Qyަ.]}^DsM̖GAUERBsA\>s`d -2섽/p(Ly^ߨxS& IBLsGؘU;J-o:t 1y)/r4m'. I;N`&m/bc"B.{$ip߇N88ǎݝ}l*2F;8=6Uw^}_ k +kw+K[뇇{{Y+&"r9 6Alm_ڵsV\2~3n ^=l^ d!!ΌIfeN e 0lS2x -C,6ꡑLNc9"!Oq`ow1gY؞0ߪE8qmӯMzXã01y2;. !'?_ᇿ{܃e0V:ѯ!m*\cvI '%vJ`w'ؘ.xˏ>ޅiuڟ=Y$XfVfVY^w}Awnl7I&Hh6F=Lz? L$=HFj5 H4Ht7h޾֭rE="nxY|"<| wl%p x+M m&8cY؋*| ׎nPBN,s&%%#(G!!0AvzL4%29ccm+.t"=>H  h¬jJ m0$+wD!n-39"MSS_Ex_:2XãCJ1|z0iڮXH E$4  hBRvbUtgF꿞 9.a;$:խ$Ca"b!W>:Rm ѣI9:%2]D)Nq¯Ɔ+J9Qp9$RVcHc3QU>7 v5%IG(bS&ZVIˑgG%SGfƜI&pP ktN@ϬM\N֝yנ$PJ^3.5suDJ^7VH h( fD%eDݒ Kyu1u1X! FR++R Vdvaؾؤ$num:HߊK߰xO`XaxA>՞u埒Ѡ'6Uhl seuK5jP!ˀݎ=_9J:+t.սRղ O6z59YUrls@N 6S!IgHtt'O<aV †]Hʑ1  VsNÍo9|.}E\i eNgkb!Wo^ڹ+mPJMY's#2eq=Rx?ǽo">8RF+\$'+ethE94j0ٶ\p4WB4 2xgspeF>ug..Rr18+[7^KGFw*H,p/i23~IlCvxA MNX˱3{1ZqkNDށ2 T<Λ >% x;1KT@d;(O a`@0ɡɫrU-bWCq>܍ܼqp,Ⳙ!*Ep2j1`N.в#\}(rxr(xG%zb1<{[;^, d_r/\վxJ 8AD ㈣pN_/ X]e!5_bE+H3KCMP4+_`r08q@b$JP0"ʯ%Z#t.HR6eEŽC=( |Z e#Oaaqe%2(n"T_&+-Y(v*ELRs$cULA4ocm}eeE^=;972._Bڨjj&ʯLL#@j00l'GRJGGzpr:=?(^DH{"Q4x__$ ^8:\'9#(D\ :AAL>^oL`+RA+w hyiam-ק/ݟoeou#"Qn,cf'-[V;tm*c|ONڽ!~3ň$l,[P_C$-ߕ0wΠf~ozNypt-O ?l(/1hL,t)M -X#5j.]Dmg l&Zu4PT*!> @QՎq.{ _q։J`f8=Z)8z,krK]AAغ05~?KHƸpC[ 2\G=n+89]u|{{4#O=]Jz7VU0M WoלT8SJo]du)pZ4P ?3dUQ-C$cl$5;v^fRF}P]fݎm!gsOt2gZWvQVzM5'6ܙ9-لmF[ }_ff>A9ۨ oJ5AlpE?ta ~IfI? n$g̘̓l!uo&tla(VqAـx J#``ǚ,u.G=\lC$$<pu7w!+ X/聙As^3kxPFΙBo,-DFDTAq2qx8 ~k-4+қU脭FOȔ$:W $SF?; =-94d|)rG05uu@󨕠 ؀X^fO儸>mm~`k`wm K -xRN۷oߺ} k/@\ %*fl0?+|;y gqPNĐAj q GJ8E:qSgX د,mGuiuY8ۆ]H#"JA{DλdphbZa_ HU#䀵NƧ&NGnJ;"R1GBmc+++kGQ tnҍJkC^ Nä.БHNB~7BA"e{B@K.{y LkB(!=BUQ 8QfE) ".!#`&@o'Nd>:8bx=<D賌Id3BsGO@UMf E֗.wAIs;FKifftr M X> %. ~/=Yf3"9+z+&Z'ӊ1֓91iXCutFmqW^- f !ךKN;zi pq'm ?]XM.R7> ?}袅+7 > w1DG7<b5xgFvR9K/S0E?&j MuGR@ _Z0@%ؼ}:fʌ,^ݾJa>? ԕ~ 99&6&oE؎3FUv1lhY>cxy,]0tg2-G^nw9G0gbgtѢ"1w $Zt+θ 63 uzȫ6N|]rh-T].:Wl*^„(Af1zGvB\WK@S!.KTըçcvP~˂S'XB-ʮbn<-Y88`g.!`1i3)fϦ{zk]rM餍i "j=ž1ѵ# N@ Yttǁ8sgkde]<fK!1o웠K5jK&PR}fI_ߜp  {p^~@C zq5xA kc?jh r@#D*E i'_8zyhIDF.]W )e&Y*D,UjvB  O_-}~#'g F#xn%cp>ǧ,AZ\123;X(~8`;jd x˯g] 'U3H"4Q| صt^7j*|@6AX;pR"TV#,dѼ@GxKp!8 `wY+U\!PX z~:# w2{9G11d@f>)`[nfD(<?rG"t6qyOBi (0>8>dA"K:|=;`8ȟ9Bt >`b8~f~ j09w "$)dߓֺȞK [ƨ&խ]lErmT u"45NflR7@ PGo՟'ȎgD](Ahgqc~4%BQ@'⣉O#EIZ$LPocD6 )xEϖh>GrOGNyS_rFʫ7a-bC{%BS|RH$kG&bI_E`-}C7r =O{T8z/oHG R+w`[¯~gu25El\'N<>shqj-2ۥ.+ `P2[ma%ѫ@v!ɩ(σ=B 7@Նpv TGa!ݠKH 0P 77]}p(2olz]Ğpv:0&4Pjؼ Y:5SKCE7X#FxriRPxب6Mp!lY¼yȶ]at莖'\uH#=m oj.Gr¢s>=rЧPpdF'bL19@@L7,,1, D(þxׯ݀wy{UHs Ԯ.W43D#@P 贉8CW6?yMNL]vgVa̙'ƏX:MCf8`t{w~㭥: j#=FQ~_ xNƑtGPpq>{MT/ a2M@Jx[ 1r-# HJW} LAQ1ChW9d=s$?|)ܚtKp|8J8 h?9"Az/6P<]Q=exz 6A㈄ ƲD_'YJbj)K2m^yի(Ycc(tCBɛTȔo- < QQ;ԝ819`4lŚD`;Nj'Nu;G?h*ͣAGXEk![0-C+*Ĝrg.ea)KҔM {(-ٷVh9(n(?FwÁcG 2]X9hIjͅPe4xAkbtE_|O,{+/x_L>yGЃAC ?Z!͈Ӯkaց"LlnC4/qIPrz _vm.> ex[]@;FC8߁ Ga8 E08pH:$ɔ.Ft7#2 tGeJC2M KHk0S*!~[oh@Oiޞ"P aeHj ,ڇ̈́jRvACT:طEx{ m&L EEFːc84Dhצ P-ǀMA|H۱=v >.85[Gq>3CzڒaKFܰi/lE/)LҼc90K98S8)+WI# Gx+fvjH =d \(!W_V\)IjŠ 3ˋLW?I2L[ϰ6vg n?:Y]S,V<DZҪb,T|:nX|׋0o޸;Q1}z0:LbĮ,r)y<4q A/̢dm}HY .5G:>|RʎQ߈(0<;)"@Xb ++$r!D qK.XyQ]::(@1 ,>DR$8D! yn9:B@F8;a kXg` @76prDꄼ4U!r2hSG6rs㎄\Nv U!<:9y^/8YlXD =XUhE,fL+v_AǴlo#= 2T2D;r蚸VT'VOM-$ G+| >Wb?)ƣ$^sbDb<9j'O%{r?arДM (L2Z1} D#R$S`5r&$>sū⑔Wf\rW`l[u ?i.Qc`6t/!޾7i<t~C'U}ř4;pn5ij/%(̒ɠdwFlcDD7:P4PgJJ0$m[ڧ윸xX<.%RU}Y% 1;.DZhtai>MvJm qsXWhRy40L\Yx Clѷ 顉$Esw AE=^E96(<Įpɋ~3YZǶa(p؜An=!`CdӢeVʓUiD9jCJ g#$Є3*cz|ј~a,F*ZՃQ]Y!{˴vSj@\e4_-ͼOwEF=ױA|6sDc ! Es ;̏JШ*XhʸmZ!pgfS_(ܐv@J#`Bĕ3ɏc338<8zVՖV<q~z$\/^0ݸ}ރw^dƙid<(v86r oˎp$p_ EVPz쑠/e ݣ#$kc})@M_VA=9X>87zFQ8XŠe{tVy$:N`A zp0h`مET3'msN B&DH ?jc:x״N|XpP5;>zx֭܁eJчCdږ<B@7+Fv_/m.-1m#53Ka HdC`W|靷ݿwEz~}'>(ʀ.YpQ;Mu-'B|Rؒ*"&˜KGZQEt,6{g }saՇڀ9꺎cE prc鰔hl/}@BENs}K ,*3pjg^Cʚu @ f8J))"!%y38[T" VWזWVP]"a;0Hbw^v"AE7TaSo5*c2CiFTk: iN#'G8 o)b <$"眏#̳nu:Gƈ*œFC9V J[)e[K!iQP4Q'n?;Rݕi#A$#J$5J(I|$#$lL)B,bm`!y5e!RC 4}RB]%mB Bj^jxNQ;#_/hʈo b9>@i9X;90{ibl^GV0_ȦI-CǒaYaJ#)8|RgB7,wإ Uvr@'Dٶn_,Q=0FlPKphA27un vgh E$L`K[[GXW޵0z 2fƱu;OQg!?a4{U!ު=³c";!(ZY .3@eI1ЕI.\erָhR& M#0?:}i/_-}۷nb.,6c(p3x/X,:S2+WǮ~c@bxtdlG(Be/|G_g?yea**rN㠔KԳ555=??;5= HP&!gU$_9'U-o,&eZAf">rj/kC@dT QrɕXRV1IV 3gNiAC5?z n l>:D4ɊnTQG2(_H `}Cꔬ|?dYiJǕ;8,ah'kk[`+W]CJ8 9.B~YZ9/8}Xmnڷ=CALYR*?VwQT8EuC8(ފ#m98ZHR|ЈH8T FI 1)x/)ŕHHL'ܮ_A1 Cۯ/ s jҿa#ԇ>:pt=8;7U_<G: djh| 麢 S -19'#f"o}puX L3#'G<ߧP-N $ӿCJ593 >X D5Xڟ  @'7,oe֒v>p􎦴};*fbTpD {W\g:G* I"&3p\G\\ PxxǛ[;ex/ =9D#lo J!8B˘ W;KE`굝ݧ+`L`>/ c Tx@G`2Pt{{{`aa~2QDd:E7%:( א$z,eaIM-Q3s !h9nK8qp|en ؛>gg)D% _eC& h{musguU #V!8]fUny։#DC.*1,[ kaC))$"ʀ+sW^cqNO̚HS3'' ,Kq(ꄾ50xwogHfP= GGp`5`>!{ߓq-b eGyqve?9ipBm znLH &/77GLsI߻^rrnuc*(Qϛr|*SYL1+r:'YC`WLFuYFAko9-s"sJTHx tz+̫8",VgA]-]Fmq6~n}+?;;*`K]ڣBU0,.TͨzWRdyD[Σ~@EC aĢCѧ*  c7ग`ބHKao(nDὦSeٳK!P՟+@;| Ky$ϋ4feǬ@]н( |O tHB/f-\o`m|p<]LttrvѨр09~5pJ˓JVН F>VaXg7F` '^\ F؜6r]9Q_Ycd곔Җ}S4 $wƢ/A#^}s}+  g[^:qc?H>(b.JUQ{"j d$b01>p#pyjC]L]c#YZGr?PK2L,CQ,R =siy<? A; 6ˀ/gf~ŅEE0րƒ8E=\F+l#eFR7b ˧_/M1ހ@ØzoƯ} E%N8]EquεkgFY `l} X)p@dfH6Bܜt-"+ vBDnƸ>1@A;p~Kt",Թ?6&&|rּ<,#Nz}p4 u "mG ?$JOT(OG֧eboG8TyPug<\d2+m:E;,B]$b > $%w3I$8eg 2)V P'#677!zc$jG1QjOZ[W1!ĂF}0HCBڝZY:Hc1vsO%OF%/vge응%&HP'~1?'9,Vd|<>8VFB\&A| `4Psk&,ط؋A)l1KN~7RN51$l.fk&Mb.98.حiZߺ@ub-j) xo",0]Ϛ} }ru: e\?ΖjG Snk9a#NzyWgn޾}.$Տ(ά:, 7,w91piu1N8RQ:Hhl޻;S'&v!.&OE~K#Q[!J#N|߄Q5i3E.},pAX9xa IiDJ)y/BW@5Ul1wPnMaEK%ȶvQݝ>z =z* ƅxA`h~h^s[EzW!4jA8e<u+ }>WOZ6Pe8?2;¾ Ìr)"saw1&U2we^GzZj~V5 瑑G$J4'>!cˣ\vapMQתB2xzoVttV\IusGZ 4sijLGʩa2FY20t^^ѼP(e;Bh"yoЃcqML:k X˜QC) A)]]u]@E@T+A3Aĩ Wl9 C <1 пAn[yjU0KR^X`xRY}Ӡ4ef !et B2L3BEhF*\@K0t;6zڡl;|`]E)؁ڎ.)/ T ٹ/  tGpQW:m MC.G[5N3꾰vQ:Ufຒ?\4[crH}\fLJ}=ZNLveh[JεCQo1S'|.;(LJMoO%iC![؄!fG)Ă؊瘥Y8 KZipaAVDDŽVx,2'2vޔ>v, 3̛Y[8+lirɮsQ;^ȷ؏Œ|xI  صcr^_[;7Ye+6ejH ZL\<$Mc?b.Nyk,֪#XW-ǑjbDs9 Jȴk:R:PDՓL{^ف;(X+҆w&2\vL0gf}컧[xi >)5:5m,J,vR8L}xN$J;49}ybvזw7E>9;AG_[rH:%a%{r73 /O"|.`e{ϥrGS!#ցImHs9wa=q.쒐̳<*'¹=y3,o᝞LkRG^#{ؤbЯ2OS|l_є|CZm) ;llj(ybu"e$EP4LfUf{sJ=? 7NM}Ag$v`΅΋AL&@$ KEeaGgRJ#m Moq7Vu4IrCh_fnn*QP4)plҊy?Uʼn]YL7㸊2U6>J<`(Bω9}Ɣf#C2r#'Av) pP;X2x̊@YCCf|T)%0o*<'tmJ|s9t;BKJ*vܘ.}0L4?&qV -9/{#WP uw}&BXPZ؉TGQ”T}g =[X(hhvKTCP fuo%Oj!;~|oR}Hoo3~2' ū( oG\F.+!vQ90pW૥W|y )A'/\z4£FC([KpF,ϝEAŐ#mK^iaO_A5|,3O#&y@St)[l%bFJNS3:mVBd*Ҏo +*3OZ4|dcG^tjZBYb1U$EX[lGBTp+gخ2RgąbV֐:D,xfvfw*tW\̎s.77w ???(AE0X Gs׃_8Rxί2$GͦYU'@I%t\"#S$N 8{[ayFX6DmH5Agh4@\)Rj^ $hܲ@; #xW&+ .=V)OAݗ͍Vڻ [Į aN ct8 ,}gޕhk-+!{ ]ʤ}*hYKQa yǛ3 *]F7n1G$7K|+^AѶ7r~nJRBQ#ANCe0J~Um wFaU:ˇ8W,}<^|K[JpĒCHnYƮIz*4m,7zNH̕mF=hX \lوI*0suv[j. {C^DAOI0l+TU"6i)!s AXUmgAps=WPxCS55x 8![J ?u8mP}kg g.b$l{z;'A=>^ZYY]Y=ߟwwPO%2qaKe R<I(we9#CAqU{r >Ns(]H nmckpj(FMX$4QՂDhb&U]%dep4&W0G/PeBj8R|\]N6Â|{Q1;xE C$R*:."H2۹#d xݻw-_0V[@Jop HI@APiumsmu p >Y擧?x>_})J?89ɘKkkdމ؅^1WnߺzDP܆ nXgQP6F$9[^RC7`'[qRR0-NOC^Pm6j#T)id9H _@9l08=:@sMFʒ@%K `# FAC}ApSi5^E8Q-Q<"lHa%ŴEѲd ⋗''_ P0-[ zsG9aPWC-Y#gFv1\LZ^0, v)`.+^ώ(d$͡5퇨RN:Vdd\ PI Tྛ8o:!eY&Xƣ ZG1$pIP@f{x*5H|Z<Gpr^ڹ7THB]D$, uIԤM͘ |"Mc ݘ۞C$=4JKtK*],w[0+ZXAAo`0FVCiT2.D*|(m*\[ci|s;q\_p6Zm$ܓX-حÏ+IA(ua7ljyF`]Ϳh k2V;J2D\lXM ~g*'SEz<{"ῙcV쌱R/hPm?tU T<ѐ?طEιGmemȟAnxc=~iV* uQV}.[*8 @99(i^b젎N iB> @>SG􇉓 M6VcOXH_gIsg2E =557wa s]*h 0̪2[:;1Yo+{EѽGC 2ܝڔPptmzM^SZ=c)]G Z-&mh:(Ωty至12`g3Ee?7c,o.(xVa{7դ4.󱝽SԥDB?*$}X g>FG'q1A^ -*>!Qc:vD!'wLF d=4 3<`Kd4 U=,+H :4UZׄ+G;&PV.'ʖwp6`6 o(/r}pӯ!HH!|pw __?GΝ;\722w4/ol.Eܭݽُg{/?KzCe NgO?XچJH0E+c VA1H돲L־*'E̦4 *pK] fF +!e|C&qa:ǡc?Xg3SFRC9I2fx(G^ ݂̓`<5ƞ.+5!$׹IN7kɉ7{,^m=Yү LUpl9!_p1M# 1k_tхK B\ʐބ\prilV/=+enGde|۫lN*/.r,je O͞r&e(>?bξo5uz MrLAm ؒ3/p;x2(vh@rH* +kDqOݩJE#VXx7K5Cر"~]XiU$-t5Ob$ 3pS≻: /fS$Sm&8E$m㼲ȚrV?jb6!g. #'88OpEpoeeykk{"x A :FwnߞBeS֘`@ "̍Ql_FH_(",k.5|&/ҩX)*eJ}p=jkpz(rU|/v_"`-HO OF=;7RxX @yb23;`Z`6Gf9 |cs @bM*)^Ύ#uޞ}A 1O&&Qft&ՈFl|?%ыw4dxV_r3f ‰=0只V7h)WE,%U d6iUMPC٤‘#a s5۹) PUmΕ @& ɖ 2~IT6Qv% fi:5N[;b "ƕZtc 48LMy=/][̔FKҗ鋸Kk%U]7g$ $iP>3'YQr Vq+?WBu\1 9 }WuPAOď'o^Jur)r&]Ã;#(C 2uXOzU`AVsR|Ma3>A-)尥VG6545^36ՁooHd>7߭\pTN\!y3+HWRF/n|y%VZ~vbpWN&.M yϵ76$sl>E{xa~ a˔nءjp_/9Wn߽sM3zKh\6໪-DE\8H[UA 7h{`weM7P*顚M`M1mjaaic# .μ"FDLq)?MM|mT<{p?GІ ^ǝRdT2TM*ݍgy_\|LQ-~PYLh&8xdT#~5D2JK֪ c ܕˠvE3TGWW6ױa^.^E*B`W^o8!8h  1Gb屑If6O);==F @Eilu+Ksܼy@TDl XS!c OѪ™7) _C}8YKԃE *ӬHYy H8襸pHF-8 9& Lm9F Ƅ dF0xJ+(JصDD-B=]򓩕݀Q.@e2+ \87Ko!E^q,r_[mDPBTD@l9`Xa  M|A]fxwp8HP8g+s[D5WvRR8%C"[&6/#̚3H2l_bE]eyŵ*kQE|:޸ҙmҒLKDIP(+}0ڧ-]yotM><3FBfn&6ZJHƝ+X (;?.<&tptjSRYf?b7ࢄ m?ߚF4&Fy^;vßԅiuϩv*v*DZ1B)v&S.DMIGa4jo))1r< *ېdP #&RiCAY_? x)GQ &JewS&$!)d:xgtԍb|) |m6lW%ޭؽT 5:$\{ʷkIJ{nMZ -s;l vaej{(CAoLj%"Vt0<L#-!`'ž dE#d:/9fBғʕo@9FHҀ@R! 3TgtU1zJ*G a$/Gzw`sя 3ba ,&C N\ fh8H[\: s0x!&GۻZDL--`t nZXJv̔dx?g`d[ e 3`ȦWz(*j6wvV6=,9L# \Mt}گWW`#N0p;&ݼ9uyn!7țخi߾DgƶbK͑3,b xPHVx*2ұ[Y (|s% )Ѻ3u R?]$Ѣ ё~/^[D~BW`f uG-Z)>"ɛyֱG{ /E{E0}r;)ӣᯰ(/L@~CpB$ !~ 67Xpa<X2:YxTDۘC LJ hW+CFu'WAK+&񶆈PQEN9LOfMg+!oc>_-BAOD(vf0E OX~R2PVB d1| ZΦGv>Nv͉ILv2C1F7P <ÖZKhLaF*5xHoXh AhH^Zz:Q74=b$k$E\i̺p+M͏/vzT<5ظ骕V}Vaf5-‹hUNx13Y0pEP|UН^jvEt(C //?6+:(C҉Vz!` 88!uD<Y2-}q5Đ}Br p0 |1b.)0F+` n(18#(pe搒|xxug.]fQVdo}Q ΨNm:\mܫu+a=A/OمO̜ 3q ľIށ$XdD>"k۸>=REdVmR.E3>60Xl@j1|]x tЦzM,\} =`P=;ʫU+WxǓgO7"by^";pC8$W)WS'Ȥvk  req~ lfߜ**Vr'6D? ;7^ =s (a'ܐ~PFԧl;XKv6еzo'p xD gf8DY<!68ȵQb:!(J"J3"J%`̍Ƽ8^U"b,mMMv3ز"ߐçREDi4{-^(E-6u#wIu%.LI.d႕W/_!$Np3357^ !ai6TzNk?xt7h'Je.*_7׮٦ʍ1j dk6+ 1[k"EVm}y 8ZW$"N)MLpc촲HJh@UJS2PٞANBMtݶNbJxHb@Ww.?c: qi-xW)-B u8[f6Pz2b%Uh$t̢'fUwz o2XH$ L|QZa ̖r{Ry?#1lQ9UJKT˾Vϣi_ t_κ քht\qE! '^&z ҹ``?#PBOaQ!e a@/#P/۷oܱI!`+|xC`a[7tD }Ē\^D*uVRoZ ;r险ٹ fr$1RߟJʟste͸sM@'YigncUҡUMKZ@fh&d&C l5̴ihQ9jca"@lr .?`c%Zޥ]Pyƽw\ĉloG|-i[`A4(ES% v>y#Q*,剀`91:rm``@-L _tz#Ȟ fb:OԋfOeGu"O#Iv0\"Dr6[ lX5{9l}3 UftN\D+cEHvȠWo8׊9O9EZ1<5:phc2|T^!V~*ݻp.'?^~r{Qq `mp9"Mb:Rq `a&Qc -^~=?\zF`cq׾[nzt Hpv7vTshL39ג|lG Kg&N1͋$t&0w\7#Y/!蘧S VdpT]H,J^Lh-$گ >Kdռh_:^S_w7"&[J]Bbҥ}x8*q"<̢qsg|ak wǢ??E`Eu'W?w@?|K5(/K4AtnqݔH.,"ZQ"UҋK$##ݽqZqd B?}|*ɓL'bZ'ES0v(3.[5J-4 ˜c&CeDUﲐL8`esF`j5{†i2"WKt!GBꙙ9܁OG_`IkpY u !6plFҾn?|9|::/JB^y9Cei8Й> ߰F`0j{;ҁF3P Dᨣ5V;ĀPB,7>y^w:deT0IlRR  OxQzNrA9 #y8֕smZ$nP=TcAGq;1Smks 2ĊQ@VG:&Wqg}?p:&VWWXǙXh (ɵ=3.O_&;w}k|˥=F^?Ы,yid_W,a>M M@ax=JTF`hHܒX%~-1!zN_ȐT&GNlm#%̐l"Qi +/U'˝0 Ѝ  ' ̝ccc(O𣣃?9 6mNL߽死8?n`Hziy{wqPE=ԁ>5T[)JJ&!"a5yy5UzybUB :C/5nzb gMܿw4N1g85h{mŻ[UN*VC4Q3RFynvW+|.2AZYûR*w*p}Q*Dn5ě^~],yj̴0~nVf׆|2&ۥn_=S3 "ͩҘfCB"1e`/k6Rw \Bۊ)yaR`vDrk<զgI3g&đ}ljX~ rq2|" {AlJCV ;vS#iFTF]w([]`i6|y>Z M>zhb^y+k4m> ^hsa~+4.aK5@е~erL U]/̚ss3E،܏dٲ?{n{H]ԫ̓Fѱ}>~ccS3zBwkAz'Fa\9vmK𙅅p,߈\^]x53M(͛"wgm>@DŽ҃NW&~ԅ$EM6ƿwݚ}kSLz48K6 !!{ -4句qvfo[ `-y^A;RzcC n  (MHc$*(xpihcF*Ev" AKc;UAJ|_WGVy_B::}X>EUπ"zis792dskg9q?9.$`!wdp5eBeoe- LJaHC/_z̽ɩ9C(}S.#Y=#m5\k2ͻ*<@'VZGjc+BE?m,t 'xYI1{yzqa 8A'nAH H'!1vJmQՄJmS~}JeEƘ x"1D) tr9"g< +h p4>5=8WH3;p* j \:?z!gXG(" %{pIh6Db / 'k;kׯl 2"G'  CDˤrq2:5c!J a҂_#@:ͬڇBOy 6&aN;s70\EY`JJuw1z8FGg+ӋgfP vz">@ϻw,x@w&>CΊR#aDBhXtPu QHilF44#5ĴBZ'= >]*0yB0/QrOpܕ(Kwy.HNLΣFCxAR')UȢ<Ҙ,ahr #%dsIelTJZIlnoU9+_?leh6R"JhP%IcU&*?1԰YV? { e zBwXFVpX/+dW ԮNh b&}[PD|o4 Ƙٗ`bD%&PR(8)^ӷu%b 5%U4-9N[/E{'[/CYUDK 6|kVDb.Jn3tص?ނ2m;ёTP2C)^Ƞ"!nv}?dv Bj8stFlieLt!5PL{I">xpwpg#Ѐ?z,&&K^fI`YZ`KZa"E rO]9@Ï?o#0!IT"2h ޲J 5lBṪ* w^Cg⹽azzƍkO+nʬhGČC1А:?qM4d'lwůT͜($cR]PI[8ƤB2!4dض):"[X2h Sd@s@*2//xEݤ&E 1$N&[;8:HMNP Vg1;0i7 {[؇CpQvL!|O2!3`x.J5\cgR'T3's|455yn"ʚ,/dITϣC&-Kxv h y!CoM(L0^?ݽŭ#ԃ)-+D^+\鉠!'J*VaV霅ˌlIagB!(s o&QO"Ȓ'=bq:/ 4Y C;&koy@wVOq9qRxvg[PՊSQb) Z\vhã`N$ե;;.Ko` Fȍd)kSh@ o%`)7)DPE [$,crs~ ? 0_a/-8 HkAP2QwRk"(i>%R,~VFC0Kfΰ=7s.APu]{-u .4&w` _s"pyL_GenoxTf*PR5:"~r 6(d4|SnaDm-cIj05-.y8rII vcT-DHG'3Tq<]'?v_~ +|z\ru|P6 /7YՓl1␌͵d`(VVi!hw6[zt1|_=QԗP}!UIHLYQhu?Y0qWSΜʘ8]Y 5u,݉V֙B{o܀ⰽ{t(xūXGg ˛8{ߺ}ūtQn"., 5ZC7 c|V ˉrB+,G <|L8bwgK^2*rU/W `L596rE$9SS[ZcE@H=FAK*zr/'BvNa#V6p;ώg'f/_D U#gdKpt:r2Šo*GXشXHVm83H3)7'AҒ_CõUdʬ n@"2Lqq6.@7"0; G8'6G|oe]f̋aNAOHƑϩ-VUpڐƩsĪ1|OBo>pv&eCzOŅ+2E놰4[]9]z!J]LCx9]Evs!Q3fg}^?p=2_wLx?UnL= vIY҄J;6u.]. 2HH$ay H>C}YIvЋk-DIC7 ;*;r/}Ed}޿˗̤SU[rP*m3lW}E Qiggo^҆?z% 5F;_Z^YHʻW)U4aNUXK C<}Ɯ4`Y\W n '%{0h~ C` +E7]BH4EfdY;PvmRX\жFc%DɄZhvMJ&Y*&BjsGhz[er!u.:x*6,ӉӣWg]ÎpD0"".7!{71uP,/Ғ4'8:!$ Limh&,\Vcb㱕]n qetP?B+^V,'㸏ł[pL<38Vzwoߺ \\\Z@e8ih׆ #mXf͛R_Y.y2J+*ǜ7ԹQd 8Ge4Aj5bn|Q/` +NO?Լ;@BKj0t\]hBr;"c1/tw|t\3-, Ikf0$@!cJp# QF,| ؤĪ $DQdksS)R0ZSQDЈlN9V_޸bn`ccR8$' F,gXxI԰q! +ܰ~#t|t#LLAIm@L HװTUsu2W['.bi\HYW-Jݩ6)tE}.ŋMH;^?]Bc@8H3a&uH ~cV3+g}7dcnǪHOmi(󶢾r*[=J Kl{H˜& ^W"FD>16RZKΓg'Y(R 愂dvo6* F#Bݑ#Zq?OU6S.0fpU3#0[tԎ$ ߄ b exCaMTA߆M:m ׌k5XG~ӏ 6` c/̯ՑXu"^XS\wFs-fR7hkfzx? ?  p?M(ya<4ˌ 4'QOjzT<+p·G|WΟ{?y0+LL'p *0Yh1޸y}{GWAmT(Xw.OuԕXYZdv sF]Q$}z1Mʄ\Ƈk摒 KJu1+[[H:xz˥ׯXz5HMOzH9,!a8zq>}m;*N<6g ڟ[hSC_3Ct[L=<CI@ls^)x޾6qCM7S\ p-NNLMNaG2@||d99&) +LNz0έZ×{"0 Xv.0;u]D:|2B~p>@BBl"a :M{4=pv_] V:D(b@Y2:(WF,*N_~uw6uK5HGP޷UfUDp|(A =|[Rrnt&5:=Ot nM1A㡔}ASp#hlX4'q"CG,ꇈ  ggF'@嗯_,-$2`U%V@:`q(uV?9$);WP$}UCP*USy泽 '7:=Ne;sQpJ EAD-, 1F?U6-/"YxC%);C8:{ P^bU4pl{ F :q'89MOL#l6M?9B !~h;5xpL pFq'luV&% 1pCfpxil+8Ӛm_Z pD@@%5J%)^Bhv/5R HWzCf{C7&[ΖRFc`皦K yw).>#p4폟<Ns7*X'0L[;]O˯~(.%z 6(P lG8,kffB F7_z%~v63v:2 ű9r i6qR15U)x+SI~ZU.c: vS\+@V<$'SFv9V)LO'η77^"ʰG=Ž ,Oܮ3@S O)[`+3zat PX$aeQ` ( dkBt7_LB1)hi+Y,nLuwf9}H@w$'̓ffY#}?d ,#Ȍ#Ce4 ^FL԰񟠅YxDet|88aM)( Fe|@F >7.j,^Y Yi KVR4`wr@Hi c <:4ƭU謆jFsG:VJdDJHZ2CK0w\Owt]'I+Uzh|"QɃ73n]1 t΍1z|nW ez4.* }b#՞-S3o@OǢ!k-1S-E5"^ ꏈb/$ 5)EX _)w/s"zlboEx t `!Wl4N?V 8dasEM xZhzh-ƀ5Sg>hJmj$$G?_.o&{@s7 M":S҃. LpO=5‚+CnҌd;Lr)>4$*0'2SaQ+ e==U[BdIu)Q0/Ƽ3҉HbiV=%"xg e᭷h3;}0/ٟ%ny}0S|竿w~p:R#񞛝8xpD9__|7!}]f/~փߺ=m7_'ԧg>/|_~<8-T5!P1{n!`eip&|\$K?-9!1?j6ʏ!lcE AËK?G6 >;j3GZ +3s%Ju!|l O^zWv8O\9bdɄ^R:ģ>60 9,3.>7oQDUG—?{-}xvscӟMD܌|8_ ܆|c ՟{8 (h'>'>w>y=0 $^쭛CBo+Hba3`r 'cTQ ^a\,ujoU7&y8 = y4sC}t̓?EAJF^j~a>bT0 (A(s9|U4[xr!B 8QW yCN#\gj*N\ M: zi,nހo8^pn7^>}6=>(e~mBD|TWDTm!X]Bwka ,qtrl#XSN|sX0-,HO "tEEjh&~2fV1Q\vh( 1a$[vӡls&!}!7&0L z~clÈ N#b>}sx&"Tva I5 `SD nSW?O~s_[w̏() VԠ8zG}_@@F}{3vwx)0+T: Jz- -nIqcŌ.W519O1w~@;Fƽ@^2fOxtWLN50*,b42?3XR9.;HX\&4 ZK<:k፳**SBN_X WR5C BFD]U(OQ,Z0vۅ%)bIk9EK#SȶБ7ţ(!AP$L<&s¦iae~e'TPکg,#1z12Ts|؋wҤ. [B)&3.%czCܼW_v>O(in@QR)o\Gs+S L>s[6BItf_:c(z7l?qXFfd+fh}1=ւBklœL T2=S9x `=I-H\nͪmR~粷( 5#l WrCMwݢƔ!S4WU|ݢp9vkLd1S-˅n0A>V.-FCwn*%J`δnoS]{?Y$kCb9J`Wr՝8#- Pl X;}}O?ѷK&bǠe2 n-ژOW\- `} ޹s7 dՙX8T}Kgt_4\'wA^z DuơѽAoGXs2R^)8̧& *S[(Pb82U/^67?[]أ6.J̞aפ@㳴ZL*fH^ g)1/aA f2}J1P*%^kѺYbcg|D{:WKc::kG}ϕqHryk7zreeTC;9?9t,ˎNɑn'm3-TYD,{W^.dɵX^mR{rVҧOZYB,ϧ85nD!793395,·q 5J- i(5R?dM*HDz`F(eEL y zA+K:3a2KF}yQ9'RP8I9(Tr 8_oQRo'r|e`Pi 0A mMeu9Vٕ&G5[{0X,‰lđN@q|Pj˄>`xld!\cV2΂=IP^lY1Xfb;w89)7-HG3n0:ʫxR"44׋H Ue^Ow3aB a4s/s quP>vpV+|)*Gw oJ{2p8Ȼ#35u /4 FzA~qJej2]dDkq#5*rd$WD$G}!pX2!!j~J}l!\GAi =FN8*#j<虊FZy_ֈIPP(o?yP"R6?Tx艱*Մ78~1\zWC|h=)l*:>(8=Oē -$u|cj(JxN~"Q7!!9exWY '=Rx!dӻ]9''wv@ҳ[\K 땟⤒~ o>DS0@  a]+Y0Ìd8dhu! %hߋˆb MɪŠPQUi*@0ZL)Uz9#(!1DK,bFv45],YOziFJ4o/s!6^ 2u_|P.Pڱ%D71h"h<(^le{3ϵ 7]G|\pAcHC fW8-p ˏ?^} q,bM ȊjEX.SyL+7E(=& C,r9;xGww<S\SA& ]Ō:.~*9Yd"YHV1"AEPCQ'GhoČGe'&" 4H ^YIz^$F48bJtͨyݺK+ݓ+t-ĘeBe#!sS+E'nC6# Fa8 y‡CojSh™T$nntzU7i)9d [n tġ 4tc*g:9.Z:. êj@Aa%:`@aY녢++(C}$?mev z5|cck`s3^ {o ރ++lڇ:xû>.yO~ǃawж)rrN?8h :cM .hUA*0y ,bEnhk_/hwR}Y}J*r]tiΰ!?N,,eqas-za#6^_^^ BGae~~0?O3vi :O3S=COzljm܇bs{1ʠq7:C{I dQ6!mH%tܐ:' xyK^tc]4BNR+?y"j`Pիpፌ^>FɓQ\:&NۛqqtO^,tkeG޿7{QC PB˩ g| Oxi5q9đ1u&׺˺އgk )'f4Q'!em!mLW>^Mc d̗Ҽ$4gh) )9;ӛ@ Jр}-_*c$T?d1mPaD;[u$L% Q- zYIt j&NPɩ@ MȂCB( m̦O0uփci )c;$CݤPEkhK5oM3N*Ehl٤ orEꢴՕ*3#>^dVnPl俚+ 'b $=RW='*yũ&_Xdw_??w.}a*PPF~ٳ׽0MzS6Jyri 5aZ`:', 9p\?v^D$R:,;7fH`;fSN ikل5B+W ܪȆy}o#3?)\hTަ^)Hq;29W??p"ldvDp.vo>| 3i܏R#\M)BPB&Hqx.]!ؘJKJOa UsG%e6 @mC\uyd΅ΧU촚HV3A_Z!,iP:@Lh\))JT:JDɾ\pnnS"M&=&0ZHx,mw&[k# 1pD!Jݼ6֞}tQɑ1&yC$_:i#cpCF(ó[[ 33CbUg2`p]%1(ᰂRW) ?QYPv2puL0s4l T>:F=or1Z"4A"}E<s iTH 8j2E4^PR6}LDGw2:s<#@w5@iwx[(afnڵk J g0+H,SBV#hHAGc\zv rb5)kZ>)f;; С?jxVC@c0ŭw33_1!ȂxJyW鬈d yL@g2L{8>C3)D*8_"R稧(Ht HŅ㣃%6xbD (DC3lax9u-3-HdT=3ueM($eKvģeFI9Q[$N[|[ YžE1t3OX&iHX3=|{zQP:Fr̵YBv 1=/5iC+2b9Tܖc!c AdfCn'/X6<[I֐_Qu_e`UHNڛG MWuΡ5D 50$V_0> =MVX6ߪIHvO /Ͽ׿~^xSp&X;~1 صJA)7ܻ;0U Q!S00?O?Q|(nY9)aKEr̿fkZ(^0^Xϋt`iݹ?_᷾gH[g 2t~w? Em:0ٻAGaXp.ն6S]uwIK\6BʼSu&4%츈c/-˅\L˃i#qƎ]H)<4^uF2K2NKo4$*F'Gx3gruKQ yD3h2w Bo9aw8d|lb}uœ˯WP/D{y8p`cygXv>zdFLCM|5i qvUNFmtGsy5򨚏tԔ@e͜]|aDgK5`wbL 5{[fG72-xE_ X`-7$^<6h-Fxjaax)OC}"yQ9|O4EnIч[2\#M=J"(* .e80Xn܂ zԔy=2+c+w3\ay= ?)HӼñ]oJY2:ə6wHjF Ȃq%wP=y2AzcҪ{煪8 ī6D0`GMHXӢCڇ>I-O翃u{QMNT jriA9{#;/_mzyr Dql/Vq&1 Eۈ+I㤆.'$|шVsr!^CQ_M24Pȥ|_ Jص]8.P MD"\`sdcqlg˦ʗ].fs5.yI~nNTh+GB6zZtkDvB =J>(E|d Ia qOВi_CѩL9h43$ک7,bYhwo|?~_IXݑ3]|Oػ&[o=@B[xY`.9= xP0!,^K\n)Yz(}/evc\7\xTuz<} _~m /ŲXlRFi2-%CUa8NWFѝȱ~{Q"bNx }(#~zCn?z󶷶"P5&0V5Q[ūEpHVWVOo.\yÛn!<ҕC"R>V~Ƥw[/p}-r.؆#Kz(ƀ$ #i8FG2HU4L U"u A" E$v/{,{T*hGj7VD̚R`',]J[ei\Q).C"*^a@όng\&5?Np0[Nӂ90 ?VWYXgv- ѪZ;Nc a\EBJg6b څf-ԻWj iGʉrWvĔVb;pLQ>uqx,t ,lƄbQxZhXmмk jLPQ&{ųO77_Zz26vܸvέNL"؞J6 o-_G&2+Z2IJWe7HD*M,A`GiQRRh4^I16HQ)9 9f0sMF `PLx\Mv'|lU0؛ \ֆĘιXYkQ+0fh R9gm8uz D 䄇jDG{#W[CRKg-0U D'\,a(4?G`qUJyr tV Fh][/YQ?(xRt_ag[[4iab3xV}bu,5hc1pfL& \甭,l;n!S7nݼp;k/<]z|go)D"!6_oN2RiIS%,,v&22 QG9@ wC\*z+\z/ (O * ҥ}46&xGP )ZBւjYHisDŽ'Jn#/ԯpO.8{b2+Kn hzm"@h]M\A kv\FUK bO3:W>D)'OWWWp|%2>;GEq^pB "Y_k#Yat<7}a)38 e{& $? ڛp_/߻{#xâ4`62#kxٳ/s YB5,7`kB< MŌ$qPCPAY>t,]% ӌSĭȤhE^ݏR@OL}Bld%HԦgãt(! d'.!i־.[_yGxPOR-_{7{a?}.WN5gd\%q8]W4:18,UuF߿-}dpUQ#.]t;wn|˟+?,6-.>$q,&-21ykSEWa0ZO?=7t>r\Xy^zD(~6xO>1^`Yʮw6>n卽/_M\XwudEh'U +.j y"/C㗱Kjz|jcXe!aPLZ^fmXG x)u  DܮKm^=I'8Uy:g}r"Gn#:Kc 1 X3xHGR0O@m3营Mln ` ewo*&CA{S7w-^'d'R٩UlXStbo/W׾v# rkg[Oͱ$uz̪>|ӈzBX.K%$Hm~1iJ@͢ \SYen^I/>`:Tc(ax\ppj!-(z8%l@e&pK$<'vPVwt>ҌY}O7X\\|;,\CÜO %s7ڜ#!զ}`SH ܙc*9yUU!3 =)Ifwmbrʕ+cHHP _wP!w<*Hܪd1 ̨]O9=CȄM^!2Nx\zK(\"i3Fj[Ơ@aFy5VtAlZj+jBx⪅3>سͰPAdJZ $ L R7dAn#}u!@ iiV*r#nm 't1s7</yVaSsQn+̺ CX:{h>y3>9ݚ^YO<"P*w%P=,1\Cc8E%V0Yf W7tj2HURm?jVE#+I>:Ģ T _oMnL^N 8A !<*u vD#238ЭUp:AxAXIu,'A ;*B02>@'4hY9qp9Ҁ7U2[Xš8(SWܻ{i3yZ,%] @>$e4!6˿2s7*iux"'ݳl]x&ME$}_3=2(>ZkOcUoyW: d!~_-p$9, i34ڎ-^g #8=uD|=W@c/i^̦3 QHW&fv_b4.*"0ڴnm8 OypLT"ZW*]9b6ABk߶U͝JQPa9ƲwybY B<]UH#eٔ{<&x!$a Vߛ 05^aa @ϟ4;@ȹe dú_C#uXM~k jmMV4 yVyrN]łO pZ@M?3 =dqU !U2@DbJUi7c0(fm_5S.”w_ <㌎~'>-a߿W>X3w_7C,p}0 G`W'N/rM񊱄/[{bOF`cXS]el6NNgs쟵fg7( bP@L+Gg>C@:__|嗾%K÷y)\UKCKjͭ!Fl)l.Q }p4l`H<ƳB0-,藝TЦB}^({dd_c:nM3̃FsJgdԂtԏ^ꉹ-nZ?B:q)O{Dr9iI~-OIaYQ!/ff.~گ/ַbV %sdc~091fHnBfq,!3 & v.L{x{8uhsso}`g߸ѵ78Qi:j5%n5Zn}Rj& T7q:%q6!صjEy<B5lIB4oaR|dyvU<ҽ e^2tcD.bD͠4uSIXW8p;CI)KrSpׯ &3.45E;@뗯WVUlxl{sS'g(@?|:_x[V5zڸppg?O'BӳD`VD?KK(!JFL&Q S e39L^FXȵ B RDX|,}KbÙ"?:pT'zBrM$l빛 LOdc7 ]  $;OEl힐Fl 1!~JPUksP;Casw'>+C;j{-~Z B)Ik c0tAZ۫Y?@{rc(F3ϤvҰ:BQ2Xicr cڤ H+4F={hEk=*6ԘK AtHХ}/z,:P{ ĶՉb6/eQ r^Jtp-5Y(L,|<%)FŘ cMN=Y>1NCWu\]ķ$jL IQlZN81AM։[!TXUѤj&fF4DhsU4Vn$:cc5:=憴fLE冤Hjɂ}1UFL-[Ex-a_/_W_~W2uNJNB#pޏp%*k'suqHʃTHԵoyDwiy?n\_MioEn""V)aX ~<_7_]q] h͝]䃨]#>`n;Fi=G[EQTD!z2GOL-5t飏'e&Y  ů^j04 O_܁"M _^צx zV֘{APH=cVU dEFqϱZ$,0cٔ-xٞ}̛As$ح×Hu>BݨKt(j ~%:.z_O9 PHm ` @2"58dU55E i+kaU\an|Ņ~޽y T<{ۏ޾qfApd!Mݹ 4|z2`? y8bsBd.!ʀ}\{>FfsR7VUiu:iS83դ TPCe"d3B(`Z@ M@V±FO2C]YIJNRϒ-Ծ%VZJMpߠ@X;&#O+aHg}M5nab yL|C"k6h'z(]AC}Mv50JJ qx qncCEi2؂BP @D,['[R`FuI5Mtdwe\b}= Wkvlןl4c^⥲B88mhk8?OG?[~WLA}/$u \eLع*BsMǺLj͉.,NFPFW[8 ,1G\K:]y$Ԋ+0dBE{5|̃߿5;C%ܺpe.N~FcnU45 '1#vR:PFGH#yJRLO%\/'V5PdLirXOd\ |+՚֢۸=z 58qE{°̔B!PZd&W< 2:( jt4g{e^$ ,@3AwwavD\< =CHm/_BM lAB]~[ο?EЦXm&1=geq7᷾w>Qg?.[~VUYs<ޞ8vS4)Q"h@O~%e@φ1 R y-&iiҜVws|Nթ!2+ʬ*߷wֹ>Y{"V)Zb_0N/+si(B@bp؝^cHxղi"W)z&és8Y}pri1u\>Jl aqouzpt|m1Mzcgs:-*G:sL} ɼl/< ~Iȱi!W|e-)yG(`L*`Pe*C|9X!zi|[sc @hiiǞ|d&&]#"TQ<%kTWQzqY@g[,Pq'p8Ppxp}, {l:+VQxyup D!|)t$d'xwϞ>A6~ehKrQ6K) 8р?_/ſDE+ sJ)7 DOQgxoE :(·xΥX%Ur6!< m2:(F"MtMC ^I(h:5%?fe&Y2CZl8&HۙG.7J㤦K+ي~V>bC[J0Pz!^B%/š s%EECB`6eĮL,`I i<#ޜvVr1i&&<}1']͙iIW+nyfv" ΁p*A@_0}" )Q VTZa$:½&XA{nAD2 l6JĚRLBPa.C2А:eeY y]+VF=:e6vBtaX >pv F -oh C7½hhN#n>l$*+KǨGbM9%M:0Kð[Iaa%=;^e}0zkRWҚw_=4['zw҆r}0r:1@7g\wt:xvޏ{?o:6`e\'s"u/,SDsƝ}W6菾X ϱ9^i^yDGOރX$JK 5~ͷ zbO!휐ؔV3#duhLci44*S͒2 _|m"04`j/ᦿw"G;?RZ1Sst4Dk1df~.׎ܘ Ӈ4α$ғ@Eg{>Þ%Y* S*gp sg|h@7#qΨ\Ūߘ h`p*gƩgKe1޵<8PQ__UVD~GB9ϥ4e< .d)sB#mΗyCf2|#xm@!4770*PWީB#phʚ^JFഎrdĝفv B7`rEy\`۬POE+GhE>TlvJG?tZ\t6S3tL!VW6#Psu<=m"$ a!n_dLcp%5l% H LPM:J/0hG|RC(wQ H$0=A ̳GviXDKn,A`?<;[0X0A&[l \Z6&Abr(8Q%ݨ-#<c*XE??k$E>8#u8b %Mp$&SP.šCkU@qr~ WJ82E q`/ZR#‘\No`x|;'B Z,Ai9+@**kLe(eufM @jlv+<&گٵϵѭ5JT4qסF4}a!O,Z ZְG:̘2dJ3æ.JÕ 2"sa~5il3%Ug$qwE[W7}98L2*άK^O?ll @pa45LN5rkHU2z1QiQwv(0 H&\ c&zvv;ND1)dtF8 NڶD"~=Xܧ(zqI83%^/ΐG+V`AM_d4LxhKwRB?i Q9,9i!+͈A#B[%Y4RW)T\:-^}*娡G.~\ H4LDpZ4cdm| h%u-ov ?W쬹 MeVW87}4L\E' (=/V`B'2 *LWTʼnU'jIX1 `X"٨ :Ɂ"4B! FUL| [G;2gϟ>{r%8b (ۀ5y^.@RUJu1,E Ɲ vG KG8`-F2/"Q˳ЮXcc7&q*ԡ(\LE&i& #I'G N`֓"Jᨘ6I6Yl6D=ưfVTA`vCjž-~$PY0c)[D`JxBNE h$+beQ9ե$s˨=cJ;ˋl klejm+AN].D3{fUlӖUWAULe)^C(g+CCZke~p2M5̏jЌxqɷXH]{"`z {Z 2G!F?@6tz\*M~jov3 ojj,=vYD@p5.W j #U\qJYxcI]cFcd3%l[pZݏ2iT_g1^<jbMGE̮i9o3VVXfɗ5{i+]_3SMbM=2,ɣ >F$$PO&, 1EE f;Ql}' ځu/vA~BbW8Wwq忟=EI?oy|ȷwlȣq;kY\X% ql!{;SNwW%+XX]FW,}2 o՚"B? E?+O;pa! ܟNQU" O{@IAf]V%s-.|8}7~ӏ>|[[F&/?<:~]i^ca956V>s'=`(iܚfܱffxxu@/rp/|W==<ɧ~xui]0ȊGͿ_bX*RR.^Kos?%Lf'ǠEӏ1k (j,εeopk׈ -sorO>Çw g֯~ ˗]Y{?Cλ=zG?AIm7q!fcu#v͟Q,ۢGVV5Zok/aE퟇_5UOGaquhQ  92;7qC`1;.={v{+|^kk8N<8'E.ދfYG$(2W_=;o~[k͓\,u[ʛN$1E.! 7޼ɝX /գ7{G'g\uRm "A.9j]/_ņ COewћ] 4p hOĦ.r^0U7XPGNyTU>/CՂ_ Yh(C𻯾.^^_o~.;e,n<8@E4lml@ T|r@3»|jZgHjcoDK3mf~ɊhbXݿ~_k%/m#g`>Aw%KèOfihF+#=n1xfrkgT1N2+l:eb&42[y:jrZƸA MD T\ӗF!O/ xhbaQ:3sK} \y(_?;jAh;/ 8^GpSE}nZWG\wNCe >+AA:sʲYWsSB Q]6h4l+;XZ!nH a:uIie#u6Ƅ)L\}cu|#ؘo4hmJ7S(bkikkxkJԺ  MNy0 ml^aAy^qU)䕗3ݦp> /&#$Hb~eO  A! Q(,8O/Y[Ik ;Yf5< 2\9 |K2]66c`Jq`h n#bECY*wa =zi1qB1BdGs4@3[_GΏ&c \(;gw#Ѐ<8V/y~4|RUdH%r~l,+tJۻN?W{7ɥïwo'olzQ_k®PIQ#L+W?$JyZ>&\eyրYsYtҕː- ߱<*ӳgrٷw^A@jy$5l"Ixq¾l䫃C|)1|Őx}r;fpKU@Ygקƨ"Xf.o#AA-83QI䰕ObijDOl…;7~G>T;Woܼɒ*p?Z q;m :na1[*]#MyZexݛoqJ*.Ӄf>pp?vwL IfӌpYx_%sФ Q kH˝WqI+:G?o? CADO|"!h .6;LAU̐K:+A u)IS=`*:U++g2_Zqyk /3MD@?|BB0l.X9JA ܾ{}WO+?9`|A )XbuRQ # ^::<q:SЪCȶ`[һWٯヾ-X<:]5C_hIHTN#Y٢HY@B\HDcde+8 5mK<`_QPB\tO3>?z zoT2cl/giia0c^65Dui=ZnN {"$Ge]QLvbtsŲ/4̱n2ɔOs>W8Y-Ķd`ٕyWS,^ $۔ʗ .[%-:΋$&249F1Qu`+P m%5㶽^8J% NJ0 w?p)ZqRا?r Іx.a iFt$Iή1T͜9FƖ0 v"<~c1|hi6GDf^(G7)$QBHUD0Oy]6}cto]7Y^o\ QyֺZ˸TƟ~,ÐfK5my}fZ"jpz}}ʼnuE6{_tƍׯ]D$FP-6-8Egeޣ4FvϞ="՝7WT@X%R=j(\ ¿D?9mc!X"E XVeC19%jKBxе5ǠR3Q8+ΰ &k30H=0O Lj|ܣ_( ')[~E?y_CyJ䱾',)F򖬫cATWf4=?F* P2pBN୯ܰ[y>|p;<@=Mf-H\4Mg$Teͭ|__䓏oߺL&;iArL|,Ѧ3i\Gň:F"дŗx(b ᚊ4ehȈ t)940M 0&^VKrz먳%N \GaV 9EE]l@p?h$EVΌ@˽8'b)2vwgؤbp$$paJsPѠ`:;e`Oz:FMA"p(Ҧi]6gbَwvSn8VUzh;( i. =DhNϼ8+ڰ;OB͏ ɴS+ +ݦQؠđ7-$Wo!:? ^ CD)q._G}`p3^wk%Ҡgfa[ mW>t.Y?č$C]BaDx-0yp+ א gEgMb>Oi~Y (-rnԧ&CXbFPTZD=:*w Ѥ}Qi +Vk4l7U !l4a)IRv)N%M^% LC}]Iw9tɣaFc2#<ޖ26MCju+f(ZPo)a-J¿8,OKZ!|єSu(.e_Oh7XpTk 0ĪR0vWPih"0+5"zǝ4jWo< #cjN ǮP#d4! M/yHL_P9)eˋrYo[7h:qt%rѲbN^GG켴T f&- zMC:3giN51ZQaz1 rn ,1_RKCg' .15ISvX:cmVĒOȐ)bd!4F}GOO/^@N(TKAW6Ĭ};eig=|ӟonWL zsm jJ IQV2 4ғO_>׮mmý=]+@xn!".ImqwO4% SӶ7) rSʦp3PJެVt+Kp7jFF؆ɩ B1J4p E[2hը6$0%-׮T dHgݓ#\wM p"JQؠaeP ?g̟n2 #^>kWm p@SEJ `ʣ&*ʀ+Jڎ/n# 5:3 Jʠgl+Q*B, H@mkcB SbCőف?f~m= i(4&I5I9˿bP\^B2A[;;;[iU!SBYh>xMs'&W`$"YOPtDDw@6m[ץm7qY偶>K@G;߳γYdBהiRflHk_~^^.롸!J 3KNu'#sT>e{ SWeԟZldGy(jc2fXV!_SPIn\߆0nAw^~q8Vmf44f]o1XLh(67O da%FZ8Q3t֖5 06k79|Q͌ ֘FɅKL]ť]LBi8DsNVôn 4 a;5*_/'1 pӑȏ,뵄 ?c’ΉWΡC.}$2I9cw`DlOm /w=DyoE4\_k5p.dL!0yǥ(:܁6ŵAVOB?x N@|WBtϪeO U:`z< +L2p=Ѐx6bfkE7dq\V|-k=#b(jeٞhcBPYDXJM3ɸu6l'hFWƛ9e^ڭbcU~Cㅂ[>+a%Vo sE-+nA2iA9UDRѽ);{p-xAݳ,$kaASxIuhQJsj)H*ຊe"v>k&|-n"DYj(KO0Y^74) B"=LcM L 7$l*@\) }hY:P~ Z౭]{uplFa!$2,=&*^a\y3̖g_WhÜ-S󙱑0Fp9iXp<{?l.Nx[ p {_ۘ#^A&h=8HG+/H-^uH]a@UySTߋqe1UOXc\{{5 4% x" cRRHN"rIÒ"{/_}(7ߢabNaAn`1F$CWlnn\*9KML'kbw~1 pYy V Y- nݾ@%4_~(67lj[U p! 3zw?#Thp?ݻʂ"H M=,z VDrn`CؐRZW|¹Bgk1w͛QZ.d~u,0F8`2C)jw2֠8f -z ! $lct+FsXq>Rad\bzk&cΣ4#(TkhPzf22c#UtiędP}yQap b$L:漴wݒ&HhJKZ dHL<㧚X38ſkƲN&өr+%]~~,  䋸5keDPA~,e֛:NiP`e%Pq*ґ-k$48>j ~FlMp6WdM=LNM!lQqtz- #oxjrTC٨1/H"H;yr]W(Fp6HD{S;%Vccr@93IF<2 ?bS lQEFiċo:ŽvѤLw=ItJ!6hݛ`^:0\1yA 4@CKc_x[E# <_uHzcҒŀ ֢W )ё'Q sB(xEFE3)pGy3ݘ~l#VhrkYeRE-*K,ې,EUI( 䎉FJ.m0Wo|%eBZ^`h$wѳ0V9냕 h&{xm>ȯ#t-dUW]yzaR ք#y `[`!pBCNfIY͈]"FAN($XwHm`5Az M V`(MJ'XMH)lm։K+@W8<Θxq uTaJ:՜7񌌧6E/t*v H.NA]/>}Bj@Q!F BD`sbZbq ~`Ў x+9 7/j(r%/xǽ}(Oڹn_/_c`J*A|cT8zp@ƀͥi|AV[ye'=O>wކ %edi" hJEtL|n(C$3LtIM r(ZAyE&9;5z B~818M2CZЯT0gLpek60fYj{4.A aqfG]#X(, r$۷l ,U2ˍ7Y{ɸ.qLdS~ ;2c vbīg胙W0^Y(V{rҕwoBLd~QuqF&-`6dxf Ү$qt_ᆚRfm6lY:&5Vpys22-H3$LK5^ފ&EcD:d(BI}#@3{*؟oPP-g6b!P|gYƽϰ:@LƘh΀6Z¿<mѕ#|: nyDycc#מ \d~ 7L`/_k'> x0Q5k~>IUw:?S(2':6eSf${cΖٯ rpCx&0Z*K7ySM3,eph@І Z|y }i*ǻ8:1'Bb0H18"DV unIVa\՚a1Q6l֊a ҡp*658x[LLmE !8K"#Nb_μî0,*E%xd;ƛBïf{xC@A!r' %{GIxbpr zėJ[/E[ VJUD4AU$ WϔPS53*]whodv=Խt8xvs1I:aSees+díE8^!ܰœg(J/"\_|1p*!Fh%gī` 'ap2iuwxc?nEM"a-8+Pj=~Dž\@' 3:m,R}|dw8rflWɺxpO~޿{cr 6e0'.7c K3P=2QS0j'i XHTKa#et cf'gHZu0 p(Bl7nYCZS(ޓo`o+ KX95%H_s}zi!(+(5<|xHB:LygY :K VlHI|wՋa [;^W8 { ͚> 1@qJ~H1'YHӷeP38G%nJw(WFHٳO(3UI%at3}4+ws) 8&nl]+>y064>/&; a{^I#a쬘R,ONhhRe7 &HP,|/><ZO Vr`ji;+ -%|Vp9+# :-vFJOFЫKW,@mO S{3y`uL;h@lbEa9{³k,52 pC&hFO%4UYBjێTmP{/-1G)0Ռ <+C9M49 pG WIIN7^e$Lj" l]$7H"宵 ߡC~:2‰x<|mW6AM&ci`!a352O[>p-eOz Ś!F{qy=plCnI>Q/XT!ʏBOdBGugL*Z".R2`bmYR52s4|/b\ӧ{/^"7tڏi’""ᵾ:Ϟ>CeŭΊ9ȹ@ hy̞r[#q2'u }欱*.2+i|K[COssg|0:ppsahpbvhr8a)ݚrV RC7,>莡8fY`6nO='P#|s>fsE^%>'颮f+*UjS{zboC]\Q*h'A|I]v,7:s+x52')eDvfYsj5 ̔[(Q\"frhY9};6r؃9i(bGQpViD SQ7c~*XTQzT7Ck``IjxcB8d8[^Ñyg`QT)DrH~/)a҈Za_F5sM\`jW[ЅpQӪŇ< J$+G j6/K Q#/f5k'+yܻzeV|!n8X+@6lyBo|DNLa ,c^W/^}>ݸ~(EbTC?EhPZ9"5?_!oܻuÇ;׮p7ut[UL)p7} 7])W<Ɵ]BޫnwE$ MŠTJpu 76PJc}.&7PAǵHih0\ql\pDH,YMaQ|- ]*%ڛY׹=-)|{pewX'ӯûI|dM`a\ٿϟ U]9$D(]:(A}p8kT~x 5ulsF.8pjLPR.qRBPx TO6ɿ퓷H aHX (pj @S!2w_+/B );ۛd큿z t͕ׯ""#鞨8)/$+.4rIaY A&8Ru 8W0dpX+qj$+s ̲zݝ;U7aǍ8 g̀?֙R&WfոY&R1@ Ł&8tF yn.5$Pjړ;w{1u#-/K3ԧj7yF;d 90W]Xš ,Q 'E#8*P s% ^;Y*rK5qFb`Pc%%՚V>#r,zJJ]:AN] r-`ШHU|B%s],ӯpP\ 2)w>gh8~}@H *ȫ,GޅFh/=Ïhzzn{cu{ kWQ[`?޹ (GKwËBL^![z\?ډSZ5ODmՆ &Fgoq 877) ߜl*Cȃv5`O!kII7U;GcLw R#OK\MՇqSˣ4t YMuW~ ]v)KF@,y|iK <FoGthL9ҽ mz 6{̰\ w5bc xzG^K1{4Gjy6<(zoD腳 |'k.[M"6 &5Q1`=şQ"ژZoOl mzíI'+k~ciFnY`!]mOCTZlZ6EX $Z;]{lJqe;Z>F /Z#ire?-O^f9/^=g9v@R訸 $nmH- `͋2y :* \-lI|4`m5X?ǧbX %s;# ~@eh>}S ֚@OeHF\,n?)+rL9"8Cc__[Q WKQ ~7m6 KJ [6CPs=S\eh&u^=#ЍQڿt-Tvb3X^Q+&o{'>i5¢٩  Įd^_|rKGO={7XgHևtKFZ)cˈG` 9nB]^_]Fhc;ˎLfR;? MSrg6xgߢf+{"\D8Cq'kTJz;M:LY<$O02< G[6(- p%X8 –!M%]L ;> PA,-A]zUL(P]'6!'*ڼ, dEHgp2x":*ojLAbe_h+O'#081w=waQl)1O"Th&`CŅK8 V؂X(ٸsym^Ʊ)~eEnP]Qn ԢOcFddH-*\ t} z蓄˱9'b,61 ~TQjN< c't$$e>e;t7w}= SK']@wUx {dqg("՘TKD[ ,-?Liru1Awup'tf)3<4N'r)IB޺[,}fi }.V~DKǢ`4H K;^tU46r@j%@,̳F[:ƈ}Q3N;]!a.#xزh]2sHR.feT\&+w3N0y^imE./(Pƫ/S[g|,XVܣ}B[>fն:g~C„:tȱxU}R.-+}xYCw>IB9({=c!'nx俷wsJ:7dGG|vB5*&4g[o˳T&:'5Buk {̸*ZRƖ9`tn/*!'-y9?B}od"CoHp,&ZʁAɲ {VB=92rj0Zm8q9Ԇξ]-!I lvhjL#` 6 δJlaq\9`7Dp%7/%/BD`FAHi98t+0BKFI<+4w^ w'ք;iY${Ҩ bjҺiiFΡT.<whCk^R=]AafÈH*ۢz(Bd`N9Fo=ab wȀ*gPeAT>K dZ (kZ+pUM[c%$ZH?'H`jf e>js&џN*6oȰVc맳!"dui*mc.VTPhO'?6-p{a?Ul_1$֥rYBBz=rO$o[sw'~S<,lBsGw ~2߲d'ɵZ(D-j|taopjcw t4da(k}8UrUԷJu0~)4%{Km7N=]Bfd 縨[Q)6 3!jnx6JEy̏VzWU`b#$bZs7QlHfk苎հEТ'A)]J갎frZA?z#,q OPzިd)"NTC6@LpvU#S#D{Q-F=~'Y*D&ϰk 6KsN  X{+66`zG`}bOڍ7v]CQOA[Ť K燐j Ĕ\wr޽?{WVq E5EAp?nN|M85XYwD8ы۬ Td`r|!K4"7Ga=UF&2H u@'dU#osr̵g2p^&﹛NER*h҄I[1El vCk>bͩHbPF5ރwՐ<ƀ8OHr0QKUJDzU,W) /s3GOD@TeHi0%0@sw" bd] Yp*TT _22L

u2~;2v R#ijDJdՌp<4kHQ6A=D8 IGCaHDpk9B'TCr->rU\Pz `.>x:!מbFOt'^ ~ijnW=&`xg*'Ed(߄ #TsA1Ѐ2XW/ܣ.gljHZj%J"Ó5Qhw@ElǏxDgA"TD}ʛ jX*FHwa3l.b ;"G7qdBFz1V|46KGEjλrt-ѦQS#P bؓ[Smt9$5bz3OȡD c2ONZ(D,\>/uO/@HOH,Q),_܍of(M=}`ڸ"_&ޝ jB{qK-96}۞CLF7@f4F-鈘-qfb=Ad^iџqVhگ]HxySu@P̗IcJQ$S_\N: |qmtQՏDyB*>86d2% * 'k^G *HHr@C9hIÅuTutF+$TrYĬ.Ģ4&vldppusitɅq̝K@l ȔКHNmJAVꖎ+ ŀ[5v+K-Ɛ%ZL# Ӝ(.26,N.I8h*< PG\FPq!! *j%GRV{s6mkf8!RfEI)Ƹ=Bv[ j,5%vay U} GL{[`K͠WAn .8(dy<)u#,C ["stylTP.b2yzyT$ě3'r ZQ!xsV`1jJ#烳=.Ps  OzGUOtx&vAD^vBCj$;B'ҲY)s1O|/ҴzUpR> lo C4%#w؂3i<.qa8L(?(2USM@|E!aUdP}`>}_{l?:>FHhp l' ўRrt2l\&jqfZP$sfc<*I)^PND8Rtv Y8 8ClHR 4H+H9!R":J@]60oZwR!(5o H.v=EÎ QNa ]RqPۋHx }}#'  2^D+.m[_`7??ы/yko^>:9i):N XB?| EFB*x!Jfj G(?ɓ/ "A ⴁq;f`(,,\Ag"MN-7]%e/HG*yh%&:5@VKEq|XjE ٲ#[БdzR")Hԋa8k0OI9o7r nU0P1YoVawхvܸv-ݿ ً'O}_}e6n(u*D_'P+bF2"ERA2RXهUtt"/OZ)øAErj$u䬁ÔΎtٔ+X+) YS"D_E&+FןL֒E95O6lk:ȚզFu tS-IAiaw|6A 6&P>hZ/]Q %YʀKFWu{( Y8nMedhTSly,"mվU`pRc {@n) 1Z_4ԍQ0Xe[o6 ɇ}V:mdl@zG0@Ӥ'L3c1r2cl0}ҎK#AM>b|k /b1vLҽ votF6 aN⧊ T'z,!w=aT -Eɕ{AQF4[pFOxN3:,j;}6Tl:m'nu qc%=Sl9r4o .ܤ4ּ]+ܝ}& $jO~#tV+"'Zn'/jf2ۙ5అF*ȨʬHG[KB1Q.7yA?ֶ!tJEXK*fjW)lyu_e里U[ނ!u `P'm2b@nf2Pae1ob} $Cpc Y@,>Krx]1CP@ւ 8#:A!h ^gQO ʲVke*1_N,n4I| UKHnx^·bl3%zCT:r;5||9b/qիw޽wÇ|ǟ'O>?G}<~:ZxHp/v#&6w{^.-׋ꒉ|'|3.V~s|spPF}ǒ][tfBiΟ{ YW,6%)Ђ2)JT SyN"(\ I.I񞙩Y;S}~Gk? 58uq\͛HSMXt>[.wLO\~]7nQ4Gko9x w m2ElˡeGdH1i~c'o# %>vj02X ց2ݼ=8c%F~"e:-vd]ͶO@8Pjl~W՝ʊ_)Nch'^vZ/(j4օ=덀8Ep8WUڦ҄qݰㆰSVmv,=є |xBl0k;j^vQ#,SUb*dZʝqM#ϳ15 M+5 Dt EgҶ=uX;;b} -;$Vk <=ؔ%¶8EliF`j J@%}b \ҿ Kki!w o*_:)cDbZRT<%wyt?dsY:N }" L'B(n-]23lrI-Y',= $6wRsiڸe.r p?g /py?j"޷|>g_<~,+l ~Mbd}#i <2|ksw|L~GuܷNrd#9JIOr &ANdE9']B'g|thI.W1*_(V!Ʌ)FMB͌ppd|pO_>1jՇr7BmMns@4Vfay[YF\o㖂oQ[A^DMc>1j(LJ76n|RYWVdAE/54*Pƒ7q6Lh(LmO#3;5XKAL[Qg94BXM&60uw5ޙmyheγL [dJ8&BY w0'Q5C8|ڈn֭2D F $` )B'!a5 8=%?2 Zn[7~l8tdzvTԚMcE+/gՉPfW]7&lL,Z ޑ1gl2eQ-|4Ȱg>`-q!$;gu6aw7L8B]H6:^0_u)'cf.<ME7)Aˁ.(CS,&S~]*Gе%#0O7`JOXkeUƨZL_Q~DDѦPM"HA`?QY4)46l޸%k KӚq!=^ByQ:5H0%|`Le4IZ.S2cTsb4,FK^ F=0|e)KXƛ8Ѡ *"9$}:'mOY|PXblKZn%ãǂ[|x~ǟ~O>"l8:do&Ŕ^;'N̞t=Mo-"Uk&<ǎ@TOQj$kp%$^6S,N䳊]w p9Zc r@\(TT%f򳺄!̋hw@g%$W.T0Wj09oY B%Hp. 8PY5`KqI\@|a{c6w67xPS"}a L,ZQQ7nݻwƵ 1I`hW@!e +8${}t}!w et`ĵ*&po30"*atȢm!ҳ\[4̖(f43w8((bdW_.K8߷֔`ENa CfI~#pt腚F'LӶXUSXFd/'SxM ZjmZV(Vc(lbg>TwƬjڃ#yр9AmZ?[E'ߢ/C]٠Q(skqr GLҠ,޲ú( ]3-{rή-˼8n9>{7؂FBel545`Dtg:.Bȿky# GH~6rzXv %yl-`oL;2 ~qLyƃiH4dK;'Å4szq>D2#ZG3߹w 2颼M쫿&ӼX3Z} b>Ҩ~5v exzn ۱]P(,;2ySsg}h^`$5 98M:2Bk&+vutYWWXݣz%$hE^.:blTaQWnTP\|\UeptB| Ոt1-#e`ʄs'HbH|M(#!?g:`:?M">M|  ӆ~lq:ۼEؐ .C1q1B@e]R(jk2:MS-|_Etz(Oꫯo߼{?|᧟|ɇrέ[Xlw%y ph*Ńx kMʂȁ*L\9\pxd lM7W5Cp@Y޼.Q(H ڌG_pc1AH:7ƒr4z7ciWG@1c{{2򠲁4Yt]kR]<fU l"8>(49c4#hoQf]-}-$onn(%7P\_Gj&p@wous@87tbZk:@4~d샧O"y< X}' AubkʻL{̑"i/.@)ay0=-橙jX3v[0t4 LjSz0ŅʑZ!6PxUijBi8Qb'PSTU@t9}G歛x!{9Ě) Iia"vvud?|Ƿo~?<9>: h{PQq̬by 3Mh )&DlDun&X֤`4*|-`=:5 IF[vur,'uP4z-JbRQJfAS:^v3dQ蓝6aQǔ|$&dcsb.o(>S}FσiD75zZ d<TDݭ}4n hp=7#IWfQj(T kl'-RRL*F2\6} p;[T\ k\(J 8?1LS7 S}+ Qp ۚy;t#Ϥ%0O60! x 8{\ Ū_Sv.[Bjޗ0 PcD϶pIdZi?Ĝ܍?ٿ8@e" B'a˵>JhmS$."YAdї_Bm"Ge!F7hK(.U08`6&ґ5<°bq&u!i*Ą3P \q%3[B( Ѧ0O4sMFNV&e1vTȣ<F\6NM&~&W?(_\ʠ<1gdBD}.Vh~Mre2>{6:InHbHkanl dLp&}P,7 &',2!=wbUw %Cx6`\cbڥW|8zpyN3}<w4 [~''?yxk6ݮ\`tskDG֝X|b~ך,pY!- 3aCXa*CA?;Z+X_;*XFffvK€u#xT6GLw :}fIE9J\Y8Ǿ/AM|4dVKP։RlqǪ:sE*4fCm/Bi؂kI[.e%<ֻ QI*"0YfX}*^ lg ‘(X辄uL m+=?9Q Igx3|0=[W&fx,bHcQVFIJָ$YP泱9 o2?ۇ$d2wxjӒ冐 3Չ(-\k:ɲi (+ۨ_dȱhj&4YkPe=KvMi\>LhȄ?Pv g%{E#V5wXj +FOj"/_D3{?৿cK*N@);U<SxԀWڕ+W`Kwr|/^ Y2-OvE˺HsN?G;\ҖIV?D >R{UGo12qv4 (+R&:AQ7rԖ]}9r3E ӳj%Z?ч/8x~ XxCj#oi%^ XrpegH RrL,MԩhWPqcsc}u[3**zB'!K`4LdrcV4Qɿtjd"+ 慿%VfV8æ,XVj{?ITjAO9Rdw Q;5[c8X\6dS[Ԩ5/)5MJ1"I n,cv5IKW4O{RGٚ\Xzt|A &e*f*HhZ"Ƹckc>q?4ifkxl-J)Qs} -a^E5o~nS8*$ DԚЂ hLЂbӓv6خ+ݦW-&h5OtX<ӹ̼ 1?*92i‚DbVS0˽#lI{5f֗I8!.c:Qj]i tc+Iܙݻ˹Níys  8]ujq=aw \ˎ,V[slݎn'D%q?|X vji߆j{Cn)Υ痠GYFgCtk_i^èۀtr'ʻfQ3=6 ̤bņp, Et2_%=m%:@|TXiE׏<Dy sPh[b]YgTz szQ(('9Zȩ͘ |1OoYE>TtmE><о[٭^MIgk تSA"nRh> Yd 9AJFÎB|4%  ]\d.cAU qC0\vgB3*H|h3+Ϟll}`pb$ Dv;6`wq8!@B%pέ?xx9CT"d&!8ܹ{_@&JujH}A"Ƹ5SO3ŊBCx/_okFE//nS*u.Daem[ǰ sS:v˞;b~qc( Hk6L=k"T׸2gU9o"@[LT1լb?j ŲJp_6M`̒o4H}so|Oä%z!hm̍F6̨Q;*N[[/%u760k5[ɐ'Yz{0y&?mjGቫ $u^Yo1uB!ёҒb(JQ- f)tӄ3f̝dn`#V]լRy hpJ"RQKBZȯWXQXcp i?J McMOJYK oD~ I# Q&nԺRCY"0cnPi 떎V{h¿KYa66iqKƏ0 ۄ~3ʃ ;oivj +:kQ'tj]TU-£bHE[:jE z6𐳯Ƚ*/ lfeXhQEK4]pAaq,h=<|}Wǧ8ezB9Y'([qf|*!QxFmˉ{4N# X.2͐djd ݔ9Йj4cԣ5AFML5,ӗ%v %ɅhSSFZ\H uЦI_I"A^432gle"txR&̈́Lbգ-o˾[vi1fd<4sYBŮmipTnl2ӼNS1ezSM7U8h-Od <6d=ja9G{2@v9+^x">:9Z*\ @QGfeDE.PO~@>67`"kOx$P+82|aWwo޺^}MO12d ЂZ'zbyr(c>dS"%HnhsٔjqK\s`Zr&H7նq9{4FWډ"jLdw;pŅ1 3fNxǀ[SW\SZchU/E^!pB`'m@E4'oP Ry^9xŋݓcEpQA8q_ `ED6 ) o`WH|b U_E WA.I/ Ƃ$|wPNn ,i."$^enQO51%iT|+|#}q\A\~3XVP*Bp;k`ë͌v"ArA [H\_sbEp#~stthWո[T`itzO9CI-Dnࡃ9Nm.d̮%m8sW,(\ ݺ0?iJ,I:FDNhPb}OeWB`m>b9npcz0 D񛵓4`pX8(t\jЊ LC> ^=/Z|7V3_ +϶}e?O,JA:$c/v0KЉoxb"(c=ݷ1uYjdtڥ2뺒gƨsk`f@PLǫc%hf&. 6|AˈfQYzv Fu:?U3ýn&PrαYWڈ=hOnɯm`fgq,6Ag6Da ߔP?)^KO{Kfr *OD?Fo%Yml#8HH=HGEs_9C;(1KZ/OKdofq䓮j\c kOk٘ed)R\: MКkk3齫3D6^E$ rm֚MT)s)ϙBWi|8^4EnY`$ wp@$- ,*{1x8X]|N4,9ҧ rS/g_c#V70a+-$1Nz=\ $Xހs!|(Z󐼬% &A!'U/.^\ e9i(y^cPDծ 5e*;%{*Ck_jH/Ҧ7r1%v#I&?=ȟ/<(s**J=X|JNS7S( ڢ.Wpb8$!k‰$ǯOpc) Jy5eX eUYfOȕsL~rĐ*zD$%-ѝMཽWO_+)@]|ު F1 C\as5L^aV( qt-BZyw}3x cxjޒL6 <*J:c]݁wX;X[W @w _NjoqZ%~A(Hj@ *5X/eJ,¬A)415J8e (8NwSIfa\%%-A3ӆW#'U(@EWՙA]KU%v.O6b;٘󿧠>$ԣkW_4O<6X{G^+Fz׌< ҍ#gk5,e {d4}U$+S._HCa=@a봯LJfUaʻjqiUR9ꕁ1YIgFR[Oݗ܄dVpNm4Kݸ*m5pGR)r!5x%k;s=Hi$/7fƚ43ܶb2 lU%w4ר:02DX[ nMSnz`/<`]*f)H? /SMuJ*hKYtk ⦎+Aӑ?u^JK#rԐdhKVjEjɌ֐.n+Ġ^zj'5GP<߸}ra.hv~/Weu?at w , eCJg᱒!cCGUg-/ " &}>&N0Md3METN;Kt}4T8NguR0R@SO A ,31/8})3u:AzF|4={_| 9~} PA:j#*L/2GGpB gTº%7IDATGW9ʁS}j_-@ɻW/=}=l9 /DDbț ئhm#}7zOLE2}P,](#oF׎(=89=W6I sˆFnsĚؙNUS` 2qàBS1Z9EAČe4qIƵ !Tgz W<--:#Mϴ<0khpY7dfz6IWU,ϭj-uO.iy.)hA|T6R7փp%҆BP莄PJ?sLzR5K->ށ\n=E_ F{zxhFuY78GdOW 4*-/Vh8%I"^SX+hϱll:450o5RMM{1l*12k ZHm ]%TC}Y(rt/e8o 4lf~yJ ?8Qz9̫-+Q' ReZ[6DWziY Hڸd:R g$,QlY7aQG X{ph Yo.$ʠ5i>7YBJԳhf_&wAh $w n?l{0g6vq:luޫOPzsmsss}s h^qeu jD1%}(l_۹sױL+>!?GZl!{qTELp4Fpqk{ p۳ׯ N?X_*0=a,j%<.{9N[X]lownHXn*,-$k1쬈 9y^d8xqSDN@,O=Fho0?˯oo?W_|/,B g^|SO24  a@(nX_FTbׯ2AdTvRfP׃G|"Dp'#q`Z$5pbу@ͻp EpO SqWqʳ@&j!=:8 ,͓'?V?!Xj =;kҜ1!3.GJi~~Yf"L]<>Oiޡ !YrшIYu{4Q>gyW׏_o^x&׊Q+1ӳWGgǧoV7wvo8IWӂ%`$3fqt#lܺs%$e$V { lU W]8~} *ۢ"lINsH`#x%[Wxp%| s:KQkk<DZeŋ[ X.^U#VFH:Hq>1z rCʴ%N'E![n@ & I/$:\b05B[ʎq 't#r _ur`9]8bMGiR3SioЪIeDx'0q g}g|})@voܼ#'f2Uz /Ai^qWs,,~Ph7| XH J_ڜ.0CH&ӓ1(!*`GBxf+HC y8Hsg,&?•ݓbuE"Έm6`Ӑr!8@ 6! b^_xmg:uP5Q)ǧQY$30& #r1&,X3Eyٝ \?پxM҉|w>5wȩlm )S1b }88pz%uF!5dk6_&hS7y24[a@w-ars: შa!hFξIu,Gj*0?bڨk+$HEӾ4f03YD[EշlZ6F ZRX+0Ў&GĂ+^F̐R6XsF9qr|cܨ9sCd$F=$DJH%GL"D({i"N7cguipp`½ nUo}BGg|>ړBdޢ`0'm6HuX-1顛J"3=Gutlp㹁k`Ԅ]5 Z,#aZL7$͖ 1|Ya[4+m?_&0iüOɠ& \H:2$Z )mգ,8e}%KL69=K5JrM2>ц!p WQf۔GBL1̞P aӦDBA@gNyn'WkلO :{ɘKR1Xāǃ[^t` >\I>A 0;7n hE'+lHLT9𽕉Y%+ $xH_#.!.ۃ}n<9:-RtiĸDPXIj2Rt E @ggR$EVHb;8WJ@#\!EoXt=8:B˜߀<b%Z2uksuxZKhQE[ JxqYccUi3y.Jj {쫞au i^8TVķ*)TFZ _m)t<ĝrx=1wYrZXsP 0Gs_3 5_aCCEy/0 O5ه[t!a?<hSv7۸y}Ux2mu4e1 + EpBjm Ãei6q;-j83.u<%rnVE3?+GeZp}kWxdUe>ߠ v;b,t3T'UpYkX?*R GN#RxBac1{]!9OaSaj,KbD"851q>']e`Ǽkx;c l]YGŕ++8ƍ8D΃7\ჯH@3p8 1G,b{cA)n2H0xsҩqA{vcm-axHuן[7ݺqʥ s5WWAp!U-F@+}xM,CV5LPV5oH[6B5e6zMRj.Cҡdu)7d dgM$4 dB4\NbTO&4Cym6lKfRKلk\lIg=B/t&z(2LV786A:Y̢WgH͔ـ8߃qn+8n+GqiӝKxkfZV'P+shsZMáFlY.<ȁ8{-Cex;DN؁['ܞJF4iajZ=߰+b GXƢPam~'c\YkVɛSh7SX\. &whD gJ͵ͨr0xNrDP"nxEXHo'q}"PNj>2-$xd6o-_GLHP^a{{s}]z}ܺ}w?|'䓏?W?c5WA2\Xrm8Hc ~ c`#S\Xz 7m\dAH֘`;ug߮ml }ccGXI(k(rɠނ5vY'2Kt&~ @x͛>w 9o?ooW~W~W~?՟~|G};9<'~>򸱹)ťpZjuO*Zؔ:Z.k%{4_;`^(.2[[(<'hNܾ:+<4 Ð$[{q*OHu ^}JrQOxYk$%j)hDMe;SJx E$+P\ 8;]Ki%k~eή$`;ZQ‹M9֊> ulDLWRku?zmϧgX?SQSu?x92ql `ի7n ЉN0dF-2 MB SyǮ' H?G>x dWh;١sYK(9?sPU<א(쇬Ttvu߾u~xo={zx F>}vs.Q#E;ָyktL%0?qV)]<&[| .EdbaYR  ³| YqƣG4:W ;0dEb__Edh?si wo߾y֭7߽s nBi8+}7[LyBsE-I"JcIt慪D8xh͑QEKX#G),!i xݬ2NG`N;z"C\ -N8ǁ7 ? cTYa2hyh @綐ڠ O'rÊ"w^mr8@3=bf:^tk׮߽{y6e 'nmT Af_E5HrgV 黃78;W95</ >jskcRlyܷdm:o=ߦf1s30)_¶z8Ifarܱ̈́yxY>_gǂk,>Lwj[S%=x2˺)@4ϐW&iE g2??k-57F[wqzOͯdtZ5FnCas 燆2hMg` kl>9G*ZV#ȷF89)UՖ-PTo^|$isҧsA .G0jk&|V˙X0"]b4]XS'塬Q3A{t`*./,%M ia^LH'\֛ul8SC4I˿ٗ&mm-ca4ηL`<𦣛%|o˳!,f. dgC=h>vNq52c\w^ !^1--0Š#`οqfȧҏ\t/cVk'7 옞s݄Yr* 3r5]=] U2ü BS=/^VY0hn0L+w4y'$!Lzd65]!4Ұw6޻^o1AxiEx>؃JZץ GƊWא]$kL 8.>}xV:ϐ319HpP>ְq[Y*V+Y4W|#:yOyp yxo}Q Q8zpx#*j8{U+@aZ*kkti ȌlzaTORn09 AE0ak]KtiuH!mG[;CFu Fty;_T,!b^K~3"ayp]K /!|QLB?XёX&^[XCM+(Vpw}7]$N 7"8=>tʭ<-B:P^JJFpPk;(bLRyvkDn%E[cP?HGs1 M _`p #Qދ3Б (\0^rΝ?=0?#H\U4!وhxe%ւb/8=2⇚)Bf˦R^b:vUôEE:ZN b&éSW~?8ARGp!@)_qz |rn 5PPQ?QP[Nxh󄹪&.#ǙgIĎYeÅHSIT3"5H>^h0~쩉;ȈVlMaTu/0e@%08T1ee va"a&y8N07X(Àq59Ҵd]Q=3l!r.eK빰yD4jJt8}EXZ{dƟ>E YP]4TO*yhk7"c%}Xke @מ)pZB5iFq~H7tNQJF}Hh>K:T(eM6. CrX*+-Đ0GYP͛ZHm:V`F<{ L8 Wڧ ˇg{n߹k(pWn`yXDN2\# lqĒpHPH<teͩ`;h _.kWz -Hz67c> 'WebU6*F|x-uP+~DmdvV@dl8ܡ\2\zdS jsV>,8[;?]R.VJ|bu{&o\U{$qe~(a2`IhXS5pt/˓w`@݌wpgQS!PQ(x CȴcA^.=ލWqC,A X] @L :^}' 4@A(bԬ-IE _m(=if:k'U_,#}3[%%Ӂ'xQŋj|Q$`6f$&D5)o (̌(2jFԓVFR{e tߺCZLMFeg:!Ck"ÈrOul[ i!uw mQ@SqF&ѤRXx1{s_&94Y5ѰT5 Ri'pHA mm铭pCQ䳂rL3mM76H߻c֐''uW(ƛ"g3kQ{ynHi_"xGemu$CcdDRM:&=H=l:!U 'l4qTɨo4ČXFWm4ph[ ͽQI[^^<}k1=o<(3sEYsmǴ(u̝NJ{(҆Ni3LaUVo!^ YR7>zqb";lE/G%^ #WI&?fKa(8E^%VÀ֒(ų8tj4`, yCFûWH[]W^CϱxuW7X!G:}|:\eFe vU`xDUIQinx/?C?7UKج!<% ϰsc{ X&A:NHxyݥ+o\DFe :g@!t6 9G\}EuLЄ&1/(o3) albM[&j$aƬ 6K9/\dF5,U6 ¿L⿊0(.pd#?~z\ಳ"'DWFD6q@J?`Bey:@O׶֯l1ҥO>ut[Wm4ҵKPqe՜!q'[; ;0Aa#^kwJ $.m`Ԫ۷n}tދݧO 'n3n~$Ȏ`Pϟ>id'0 rƵ?pQ?Yq)dĚDYg?dp<ܤ 骒 >q p^S >o@LIOv_Ħ(Hb#VdHiH`RF4Ck2ŭue :o",3T[cJ> ~Xa-orX r&4!Q4_qR*~ܢ^*4 Gu?,;F?ϗs. 41aZxI Q,7%)PB1i;hj4˷6 w;eй0'qKLHښe2LUTazM^`isQiˀ|uc@imӬ"Vpwclh:a}q  5vn-W8ɀt?C@gz`RR2{ZgLa N^^9ʀ>D>@37'³C1fuHE(JXе9'U䂾/40})vtD5c䓅G'ǀql0bC(dVRWP bX {AHn]]]~so_5tpW$crŚLҖBw:2^#!rϴQBdWhJ*k Js7-3H ˳7ީ ittit=g"K\ J$YK/݄= qIivO,Q{5Ons9jf}r&˼@эvBx^^r4QS1lz^9 &49-d &Ea-[ij5Z٪~\EyaX-5m#1Oo+P̔zZZ=iwI/=Ն?pD=7ͧc2DM,'@ JmA}#iQ"z-iehm|@ҹ^b*-|^ifxeku5gFB-% Kxb·³<nml dM[xWhMJ%`.fdIFG#bVL++s¢"1‹0;nUމ^WHE I-fOKpw^Xky]SԷՕп|f"܌">;GS8EPUիM7Jt8⥬JD/Wzöo!:Z]5zh|՘14x=kyi:B&#[wݵL&*Fi<ҟmf#F ys:zZqJ lN?B;/[\u\h7~ j{ܞ/Oӫ(ɘUz׺ٚd P2iW&.CESh!5[/p Hۏ+@yl܌ p-\_ch| ^/Jq>Q$@Խ RK.ൡWw޼=]pG`f<{Qw7Tۉ&e$6!U!e ?ҋNڌ3- Q6uAevO*)%[BDIA +?$G=CwH4Gآ/+%ː֔ߊ#j#@ P*2)530 L uG*aay|f;nn/IvK4GAԵgVX -G+;sd7?yk<4 B^5R0kSO=j.ao$p%n3o[>GWCY^*BrnշML3 Q36yB/vf'̙)KqUǿO!ռV*R̡X@T8 ؙ>~Tiwq:a9VH5\}XcqN j9: 8ONqOϰM; 'Q0]~̽;y-A䈵k3J'gg\]>;=B/!լ{@i@"ná=oQ l{#a⠾Vfrz@ĉsQ$8l"#C^rrp~6.k2|f[+tVgůg?՗_obP~ĝD֐ӁIcڼfEV ?z#|W_bo~cdX z{wG:p"D r|{hs»C%)b-/^(t($gC?ӟ.'G =pY` ^Η;P-ptx#UQ\tJhzy)7Ȕf(;#_kvOpgd֣nC7U[3n:.yL¸!qtW5 7wOB [f+Vf2vnF\SH >Rf]0 ^] ?TRJW ?"(>pUo…^q!rKk$F`QT#5cnrPf 5Evz%`ͪYd;QKg&Fh]涞= VI*5̠X)M $/4wc!ߺߛ^ĊV)dBp@lkxi03^:dXscKCmĵ ln X6YЬjM0'Pzi6PXjA(#Yϖ:<79B2ä3'z,)s*D6溱_LcZ(]a"yPo͊=UUGLL. [nD˒ Lm ײ0].>Gezܨt{lAm6FSbօBJ64 :!1p|`4WFV9D8GYJ硚Z@:(bR95Ma]r JC-^X0c&a)p5P5Wp;=puJ\"=dݡ*{2&6Գo_2`b?)颸w )tf/x]1m5]o1D0/D(?{-UnW}%s݂w'e=6coZQpU)0< sC~]T]T 00%`”e[R5 w"n9;)6ELx`賓cYsxxqvqtx曗HpBώOi:Gt~_]9#HJ+qN/9;E׎w6=bczD{HrQ66݀0Rql{܄wBChC Tbqi#V%)JDzQʂJZnţw)Xg6A6~a]8Ps .\4i;^>BZE-H O)&LBi?^# 1bީ72 -rI{f|^g͆u 8o@0<6dl"st}C%4в 泼t{rʘ|,01쮺~44>ebaK!S2m6 tF[BI͇#zt;ZP?Zìĭ-;vJ6jg< j8`ƏYjAMܹY Õgr,j%AL?Y7ij&ĘFs3;/u]z=CEBvނZD7 vh-/5DB;3)=Jdnw1s \6p<HMoJ/_Uw,HCﱏQ{E0aKPߞYA.e8Qh'LY#+.f1jr~4g!sQȫUoSoFےh/-= 'Fe4tjMM*|bUbIs&{vU@MQrOm$2=0?1774'$&nӪb5덎Hh c\fI',xWSib{ QuUZ3Ɏ`q- 9Y?yE̡e(,Ocgyxb0j]U{)O1#fRpKQd֍ѨW0.f`s²]i~=},ђi3b̈uRYY"{ذ 5 ffp08C(Ͻ~믎޼> p+ Yods-xy5ve)xvw[y=.eDr)qtuwwjPZG3,a<7qEh.d/ r+Sz9N_C8"Ϫn/{'(i=`p4p! hu$cXyx~/oW?k?x9Fm3wnc+ +X3v;I -tΤ|}=f8*p۸'Up~b{|[ A5Xܧl0 "C6,%]!+P*%kY) Bnו߂M9Uށ0VBJFt_+IÏPC1L.zoȝ# Ƣ6uspefĊ`u%ƖD_I QGP7 T ۸9aLA;B>:9>ysmT4 @EyZW[~o+r&!<<8+ <8bqGt[Dml@P ^% X;B\q+Ǟ&'].ưzS[5xy6&&3?vo%汢*k~j (-=Yzb^D6PcV>!2YpjvUS?l}X.\z/| xCWjBX8Jfۙ#7:obV u%J=/&'&,jyPw>xpf"ctE*6ҐF3GsRX&6:4QK lY{&3$ahHp.]Ѿc$;o+uM"K׭tvfcH[zZʍh,S$Zɫ.mمUVr4*(B({(* vg͊@z5m[  %fKgJR,&4 (8g5<D^a&(qzΕ@l,lQ$L]%|&4 W1%WcU8*$kK4649S&IֻhPdor=QFc b]g)jFoh52vWQwJ\sY$ִh)_l3Rۈ3鋹ܪM8(i"''bj5/Wzi~[j4O 3 WASo&[u^2ฯj.O-"޳GH%2OX1օ&ypo=/7@]!ńC`Z(L14gOK4C Q 8tM-|'U&< p?̑+xSipp|CfSSz1b XPSm>EO*WNz?y?i :4}D'y{DlbkZ7 6 ]|al{3A[Ɩ)qs{ ./m䀀w 9]^j޸);)n34û=z}x pjQ 3i .\\΁ ~ `7`#8߃?#dD>zn+±p޽;wpc/g(f| &ONt. TL .C|W/_/fDB큦QT6XWRt6Ŋ0 eUMZZG%/qê>-F9Ax@1fi*T/co1 .=%] }2+'%HeHЃݣHLyrz΀A]h,Ƌ*1™=\rp<{ŠJ0-Ѿ4/>My鏙TSED.a $4{MX(G/@cT'ɑO@(/;a`DӪOUhS'^*Zc+,:-rjf hE(Eɷ{19r@KLx0հ$綦W"Y.3+uQ 7g RIL@-cf& ݲZ.`в&F% .U@azU[bY}=YgH6_dl%v1OZs9/fNBdV %}W\MoKVt34->@`kHgea|KKU*_(GJzVOZ"/y6 4*FVmfaTo"w_Ifh?,:T tM[+,P=y |y V,[2wuڂTr޵OSz+Y|z| {Z6 [ VϚr",CTv^Fbvw^pqZXJ̀ߠ6o1JMg tG|n R'=)(V dlXO`s, 30F?zXx+# 3# ^&evV7D<ћ7p`! D]iRL#hL;2\M% .moy¦dM%.lcώYd f}D7<1شCDGEQB?qN63Va8#W~:u_-K6%HӰq&WxhI$uQ5«yVβoO%CKD޸?ǧ+GLH` ? (ݽtpb+㏟>>A9SS2u^i(3nt"1d cTJ^19Gn d7@̝;_w?7_7 W_|?Oos8˱w +1ꫯx僇?sj rI1Pz Hf3FS+$::,RƴxЊqIÔ@gZ  i^ѾKA8Zb o,0< 5*3(^AM9# 23 o@ҍw8p Hw ɊHJM/%S7]\ 0Fܕwv/>윿syխHrkݽDC 3e6_lMYSCϠ5lԌ*naW?^CBdyuc R.=2w1E- jb!ݣZzi! .\^RsLDdWM6$?]M"c)h|5u5&z_._"pݶHOAh1sVxB2H!1L)"*<.Y.yK壟7`;8~{/__|ͫÿ~ӟ7pW+ =wgm\йA)F.֫dW!' W6b~9#u}Qx1QJ3X{[ GĨ_K^+y.@r&T)1 eR@Osd<ȑrwUA:ӄ+h#OFU$g Owd9)cgDpD<{[8dsukG?w.s}^{)[T,ijvHf_Yp!\P62LkOӹZ:Gh𚩵.Mw/]> OHS;pDM(XZ7Ԑ׵`:Rn1}ѹzNzDUI̫/|YbI1`p Q0bI\ TZV9z}6"Ileꗿޣ;HgIds8>PEGd9R{*.E\?'(-ttvzw>Ӄ]X ./1N<'{.< }r]!S(bJyCđU'(>2Jb4f)<{ɫ/|u׸(?~#ܷi_ׯ=~gm&ɜ*xmz$KdqPCe#27s x,3J3 J цW( VG.DwT \(מӠ "`h2nǞ{y68RbYҧP{z 2Ҡ bR\VX^MR.Ynj#5X-CjpV)W.|CDêc)Jѳ۪M`A0*k7!G.n-zM˦6yi^qxf9tMђdǶ1d%ic ,XHf-׬==P /m~t<,kys!֏Bwk.j`Iq`x>Xb˜Z-Ӭ_XX+~v3"Ҿ~0׻t` "VmqӹdA!qJmUy4Vx5qd!T!7scAI\3R贻&J2ErFK2`s/̻Y16z/`-M"dY'Xp\\ nrԆ*1a=n7|nh~ԚK𞘗[ LU{BL*7H2[Gdki-)ڥZTD\kC?)|A EZdZpѠ{m R 0΍z2=lzE"W 0CdQ+Ŧ N)UA2Mʖ2-ulww@xOVy hʁ#|*+!!\YD2*\ {Bǃ=YΓ~l*R1x}\ABsCS.U%xޜZO%;\ȁ Ns%r4 ( 0<i sA _׌\\~x"ρ yWMv5Slp7$ug?^}իw@}{?/SB3kRG JUeǣFJ#MrnNǤ e{st/SjƨImOK5?lD i$ql4ƵŴ=Iq^xDӢS72}5v!U5FhR沼F]c>Fz$mOLP׬(e,PDHGQC%Ơg4Xg1+[ BFtꮐ^C'l{Q9!K-@:X 367 \jw&ƜS\ Vp97:٣dTo] OsƪjjC&@|㠭™ܫڭe@+SDCe$ ֒ab7LoC@l9f1e|O; WQa'u5¶57nP!9o`C xT]X~e%T 3gpۼiekH|u!U%CjA*CeL,1x"(fPM1* +Dneђ悘m{mXtt434eًe"M7bT^֠ \^\ x곥K#҆mtKDBXk(\ ?ȴp vYA䠙Z`l$Cs,j̥?"5"Clt_^ڹNXkc<3KԎm^7yy12i N 0u 2$òup{ څCtӋImQŸM^XȱQ#)~%:`-W1RW෷slņظ\xn{LkqRB0in]#/&Bc.0l % }~2*"֚B31H(v3.*)^FQaK +Rysu((߄ks;l 4t o<-2SifsB ?CPڢ˚_̨+Wa,y?U10…nJ3'/Ϸ7zy&\.FXRKx%ּc3tp42iD+d0) -(MHa%2_my|e%k\KU5 8B\U1F=mXȋvi6<$ihZ[$Gaa3כ>[e_3Y~ޖ&-Z_VJŐ7z1 ߐLQMeAѴb0}Ŷ%&~GF#ڶup`ޤJj@KPzɺǍdzŬA;2"i/4r@CmeXFɪ$o9S3y+./ZQY5y7K-5=6hr[! PJJFsI8BZcypO?}{#_WH̀8wn+Y$/%68 ff`R b\j"#,rd( մ:%ڵsQ> qZUt 7-+#{5wed@Ee=UY挑щ3\G ,=t!r$N0NNSs`?O(-D <{OG?^<~ YWN0 xpF ])h;E`N֊Qndn}sŔ(;lAWȧ5Ɂ.'tF\^HRf$GY3BϒlK)DXÂ[ Fid"LŦ`q2K4Cb6B}H*d +>vZ$7 RwꚥsJ<z~nٳ/0okn`J JoB[  u@FKq ZɖR1S/q`0o6KK,6H`-Ť5QkmCoF?'|_|w/νEu.؅l48یhEv2e bmB\kɣyO{Ӎ Ռ)BFp_ӤG+kD;<`KK^i4j=MZ DSW̵yr|q̊ qS}6S:8$vI\.}4@0~k/y)L9F0;0[S\o| n!d# m3$J0SsIqsTٻ'Bt:d/~HVG(FgڒZzxѓwO~?zFJJPOK҂։[@zqXB$PLS'BLz2L*惤ĂaTq OXRG#,*v^Һ3ao|bbu@ev,8@%=f@L@ςWJ;hh{~ ^3}`-_t7(I$` LQj*8"⠲'א) :g'lfdr'4ƽ+RT>c=X{q&$a÷oqwL~6w=[]"#< 0:!/y5I0 *pTޝ5??O?'щϟJ<`~/_=zd:;;D*B&Sp >ex:#%Z}{b;R(4µT( =v5SȴV>oKQpXx<R J-Ba”A l}pbN=5~яrbqjE[UT e;ЂD,Y`XލYK!f= F e(]m%LZ%ը;єY J5)yg@2oGvTݜH &Y ǡF,ڴɨpK7wӞt_L=לZ\^Cm+2qV0\_.ȯ٤7/SqSZِ,p,}H,ˑjDl:Ӫt0z:˲]-BlsFztA\&ҩŹ*?cd%e$ g!j֊,24*SkhH"߹d!3VuCvb0nlZCCObŐim3-hxf:u= &fݹa.MV1Nob*\y-Uє-P&a6z`lbږ^:g)DK'+lVbB%s6 KU-ؙfAƂg-ʍTXe߸9%l=o/U_V+cטZ& gK$czm|*ά)ijgŮYrgOI'ux-4%ܹ.Q!h)|K Vl-6ezy_^uV1bĶ|o8 2Ģ\̋%QGx>'+WeP FVHucN5AeY7/yNs#5q6sܽŴp>m|叮u m W ;|`{ +܇|LE|oʪRp^ *C t\\LJoqbgֻwǯy GyYa1T=qUffWB\p_9|}1tN'Ii L x  sU.KCK}:n' q Hq$<;e Y,cD?\?cDQdұ4%~hc纁~?/]~?{{~w>2C X/|ͣ>9pnE ʩ0v1Ut5o(!,is&~uǧ0=+'9GHsK(V ce Xphb4呸O@d ͠ӊ40 i(s?\^ ]R<$",Jvlt"LJc?L_;tZXSL e$& Fdۄ{F_vJ]P<1^ 45,C8 bU5^u^>ibr'RSJY5A95T֝ z>|FMn]2߂m}WciBɣM \ W k0S nFҲ/qz(☂v7.%mPg Tp).W6c.Ck 3m 鐔ь4_n#kPuK~.kB^]qmYrq@l;OiQhj ZI9L=vP'+ǁ ܌1oC&Z=`Ͱ ۫RÅm-`n i#yCԳul3K9͍ yūXߧ A*闍RL/`-[9F+ n1ԫ3ZIb *S!KzUH6dIŵ7OBk/"AnT GXPgV_MEhZ^S$ gB~]HWʤkf ,F{B'Q|5^!뛘􂡑1Z˜X>ہb-)=s|G<oIS+ۇ{fV|6&fl,Rwaq}?N 6oyc?{cVJ/t˫9xElAδ҉)IhԆȅ\L@X"" &8Vn'E8# yě;ѐq^"u'c""bw YB*P +,2 %?< ig_(>vKiyn |4 4햃&q1|;8f $l@&#dꤹH,Eg g}`_OW qAk;1`\VKQR5# - vԕ;4ɦ(`"p v0oy 1w'|tݛ9(#~} \X:[=w*ex)#j̑D ?G?o~o~'yK@/\%Fkׯ_n>U?sj d]8HN" [ŕcJD̏Ch#d!2#Rt|$e cq! JSCqb˺Kg" {>2U* Zw1A ('Gd32;-x$dDL|/FmxsD$:aҕNkmOILsYN5i8894oL1ߖ%H/)׍!Kl8'eλwPJЊIEb7c+ s4lCtZ/*9+ Uh1J ucw^PwӓE(х'{>6PܜGT,U\ dZ0}:>\\A:D@pGR}j-)(e Ǜ$0M?ox+e\T#0̚"[6nuCݎD% w,ӈGUnBVťqE3L{5ԼoyZ+h 8F&e+soq MVc*fՏO*T<یjWIm<h{2"d/uPV2["b4~Z:4iOzk k$-~=ZIizJ<伌4QeMx 3feDNHX*@`.>).m}a~kfBY/J{>MÙUzGύPhA(ə8+'bt#sg @lH/lċZ9ʠX o\psG炓h5x MPtvwpa4'w@aN1@=;)s!o4ba'BA LZER)pI2mN5E;bQ;B>9P/^NŴ^^kuPkmZ>9o@|ilU wxo5pԫ*fE)yl)z;TIxYckJ1̣Cط* ũRf6rk-orW\D4.m!!.!IRS^~1ȹ!#tD+!`I8/O&Հf)Ϗc* b?a5p7zH5y|v '=Ux o܉=//OO./Nβ˓ N1(_>‚ R([en,|N6QL$z8tw#/>$`k첊M!磸,\(ĐҸnߣu!/ai;Zpn#EPZST֭"/. I^z9~*M×R`)EEhȣ/gh"s$dNJJ"=ɓ6A*J&QVT7Rʫp6(:!h.,0K+hchI}r%n5c+&R6SBVE=QW̞txfÚjܺJrp[/w`rIs;h\gAQO k_<&4 UlPk!2c;i3 W %Sw[QLnPxiȬ@,巻+pF[t@ּ MIi`xl:cM6 3h:oyB)iPX׊t,-F1cg˚:QC2o">G֗O:l*0#Z3'`w3ÉRn`h{~0FBJC,5 !Ku_TZ8'-_K{)lâ:`OKc6Di7r}s-pM-5smf3?+(ˢ jJ)Z /|xvkV =M11SF#]ғ%i,<ӊ>U#Vj25?K؋Uuݮ'=pH[Xݭ5|pEA8B6V|`x)uOhkPfG2zEǤlgctjWx([QW0! hYι,W-^xb!(ɊUKnӸR V6SRgQ23 kEpiۂ+pGP΋r,>cJNOf0/^A4 ^hS4C&99^[i+뼞`pƞkiv :ە% Ɩӹw`1$_j_)f_7jl;a@D a=b82 lQ`Pz)îBQk !bz;^d۷*wsJJ1 h>C@ %Dg +'|>\N(Pƍ~ځ QHD"ELOJঀ ޭH̀cwd8gjFp% ׀$ ~Ӳ4 `AW0vEs~qtSpvKGzl#u$bPƿd.~fΉ$A?ȁd@C?~ĬjMnAiHbr15)5p0w%s<3tB9(E0 >|j,LJm^ h JchM3 rIc۵F#ҖW4$8;r:XzmFA QƵ̯5x^2K);i;](){])botl}E%-u;.Wr&n U;JԐWIl} bS$Ua9EQs_ ΅( 3 5E0[-R1c1m;7i!f!Z&{T}4R1 GZ }hSN_z!Xݓ4 5,j;vMLQBN x݊cNh}MA.mI=ea-՝sxZ-1ܤVGRY] Th^F~hM, &|kH0cռjtbM*HFոڽn< lgtsq1?\"zF- 9G.fj6"LlkJi]+j 2'be/-<-\=JBS }C%ā0irne@KK`.=Ɔ`>ƃ"ޕbUj2d5 ч^3S{vr1'w"O9A)ż+lrɏ܂֥ <` ~FEDKA`'kАہuZjBOڗ7BgIN0|Yxy,*bdx2s<ɳ\%~fQBMYd[0eVkPIHQaMZD&drAn vs+X 5z=ϷȠtb"hˊUxdZ`rjB-;/7Nҷg-#q2X+q+aZcH͘PF蛎F:Q9=yě` UtK`E׺ ؚtB&W4&'Z: ZWZnG_ sRbeVV7'2t&i¯" -6(S xp!Pz7d@*yZ>U"F\92Rz1: NXxwibC k+V/UXZ76 [Vv!˗P럱֣Q1}z/4Zr+9* iԆ=FYCo3 9 ,$B j߈UDׄD3חgeq6 !K?z=d`= ܳB,yN`fG`?O2ta=۹!3 0DwV0b#be% Rmh5Y,">ljW^8pxpM)A#\%59B'@8lpvN m "B !QUAUAIE1j֏L 5v鈔f[-I$kE!w~#<#]mZgbDİG-7 CA(]%!_k=qm)w$c\>,i'r\[oȕvzv$J,36P޸,7P1uzyՓ^Ɍ S9>8TlyȠ8$|%165yirW1Wgn,9HjqnU(3:CrAEr')QhFi\.WT 9L@{.&wf!QNHص Qc.KgOͦS `l}UE-lZcUA2Kt|2^3TͭFCHLrIBƈX w1ۊl'SUl8 Ӻ.\"f,Nl$*1L4\AV ep?vLM46 ߉m(5'Lo#y7E9l-W2lXfkM*Qi&if,Ixh|E{יj.TINMNKCjʘXu6d -)d5ZZ?b8(bERoe4颋&4o}h^ڷjHZBcTGQkx%K,?c"KtWifA>Pڰ Hng{Ub)Yn4.SB%x?X2ITr1ek7O+ A U.,j6Zr`:mΝ^ݻ%(u@HFC#n[V PB&4P‚'\s6M4-!{GF" sihg>"qdd6=Z|v)r BeU MIʐ՟i_}@J`PjDYmC 4 jPA|6hw`7a;ڹ)r⃧B./M.3JCb>inh:74h s>~! h]7O .5K@hJl8>UAMN21Q[qY1YxJJCA2l\"yEqv S$Xќ*X<fLSդ3jv&^e-ל\<:1M"*fBMq fvɲ"M@Kx6jf^h'`EejvIE(:KN԰E4觖,7gי?!GŮ*2Vtl{h8s-{(w$0*)q|)02?SCM$ݎPKR$' |7O`RBc o}' K,% ޽bbՋo^y7{=Bp|<,J濅/:ZW=JXz}X.;}dd [=NkBK< vM/Z x u6uV[A?߹i+[pތi(E^*-ݝ&,7G(hxBo]cA`zNP|?lEhV@6/lΌbĔz΃ nbuBq}Znymo6jfLŢf+S;pN2h pݙĘ %RhZ7͕Z-ٌ248w=*L֮ޕ 2}2 Zj~m y$t;VVlHHB1gr-I̚aE̴bX7E~,d.&MR7r'fN B wg|XG7'N;Ӄ?-%Kj*>0:ZJp #e:P"l8vl;Ү#+AWSm^ coŝhLbmniͺ `i3Z>{cOS;/ج>E;{va%Mh:ak4Xef(kAq$?92ۃ]|fZ$6o,*bBHFǏGX6;縕/J.S9 JaF0C8@i^‰ذW x7oϣ'O>]/J9#o~/3O?̰8v lѼۋ"YsOjg-Hxg/_7_)S8=^\H<<X_(ցk'`ׅ/C 㭊H7ICW3n/-@r&"AHw}`Y(l5oW$&f2h8:z¾p2h{)-y8qڀDH;wiݻbCy//~ xm Dirq ܍hW":Y #8~{IEa+n}q%U;mH8F@{ yCGemQT!P>prV?;;OG(}ogw~̜\?>E<ӳG\\=x_8Cߠpѣ=xكI#<(PPOБo*P ̀̍Ao%?""/;|D :ж?OO5h~Q5YDpj'1Td<2&[yE"-hCD-񶲒R N0JD 6r2/zM@("'VvzdMe\KL! ++pxI 0ymۙ!y2LjAo5 a_è'h0GŚ:a[Xa0-p?f\,43yWu%VМ7Pˍ ,E+Mܒnqv/xe]c7W5 E ´ ޻].K*GؽTT.:  zdU9J]]HcdA1Dp5# Qya?tѠ n~)с&^쫠MM$hU갽&`=dw1;LVzHk\祁.,40W4cxðk͖- qU@.A?卌m-O{{ $I  8 ".x*;NZ la`>Hn?u {Wp`%toqJ lD\Rb[Bi[z t^LywgEwO1;JܲΔ{nXՏ&61s [d#x/[(/?wq 4MӃp ( }qG`ߓ82FM0rag uɳ'_l,=z`p`\ y0u9Y!PpΤYt8 Ncʒwr!66(o!KhmI. tBhB"aɟ7Lb=>`'Y|S8{?O|럽K_CnaBbX7vu4 ~%lV6&8L CHM hUXDZ N0 O(Rųܲƅ`O΄K% /+0tl`N;w={'Oͯ?}E .\ۣ×/_~~>|h> ?y-j·~ɧ}G}$>_-0b@V'иB |~A>Xc [@!!9gNBW,\]46,Q[ l(̔hoxwo?{'fNN.޼}cHIA)6:􃠈,42sC^/ ~̊}ʬʛ-m "',ve&  03H3+;  (G^Ԟ&]@-`KC$xlSu=c|ߦ&Yek @5/'] zYU:u+?fiڄjF} T45dIh"Tg9긮pJ{~3//)a1kT¾.o1i<9L:Bbr6*C%88[8]s(ܣ 8鵭 , g}7*N4[g VZxNrh1i=.܌\3~ #jv U@gĿ/8 nrڶXnI]5[Hf (;PoQyE-dx!V):^GB&Hs25X{kZĎR,+_)PԨ%j^ W,6Jd!ns/y5Ƙ,1Up+r.rP`bt& p u:']/UX`ã7GGW샊+}_n2ȾJ|(l9r,+!]#DbS ~aSW{0uٯ!0}_/m=nl}0# = {O 0a {!rO2 lcL.pǟ|[0Ǐ"M ©lo LW° {.$gB n z˻{|a+sE7?Œ|p!F'=;SnKxGx%yQszu׿Óӏ?zϾ_S0@K9wyP@Wr|4a*@W 2 ~?9?9cI~_O??}\cL޾~ӿ_?O>/ey1M T @xOzlL gUiy@p?; A; |:pqD^<G/B6ó/ovz|G\f?ml翎bq;L2EV/VW,D6]4Ũ,> p>؆O~JV;*4C,5|$? 6GD:o(ю2cHd:rX}x|tx<}tyڙL4\] k^N91B3G~`²UD5xA ȑ;0&E{&/~CHN* +B=v*A/H_ÛcoA9#|8|H$""tf1uE\m\qCd{ `Y>ymkƹuF1OGϸ89 >2v;<| Y+Wկ~ DKAxʘ"JoW=HBnhsNeK NR D `t Lȫ>`bN,ZV2<)Hfy |{0z/0RxSQr OJVp8&\-LrVF*B(g~v"؅vZFWz#9ްIpnHP+%O$)o%t n| Gk^[C;z:6Wkk!|!*V,0P,{E-|XnIfiuoO7[=MejWY~@,7QHdvK{ߟkHv #юFD^6 rY1x_B*Hiu:Z4v:rX9Sr-ϏE~ ϴz"W7Icې"% o|x۸q2I[w,0[nk6cngB//(ڋZ-u&|Q9{2۶̡ 6_%4fiQ{j\rPq猣.d 6r4N"P׼),\!,Ą־VPr@ 6PщWlf=IՒ4F2V̂wweG PiP]`P>|@7[Kf9-hDQH;r4[dDNHq0ZD >^I_G4{[ р:Aw::wt"L? J$pF6 آ){]?A$0-tXyCswF Ȃ;38=W89`1 q>>RQ(=!ɇu K댫7"ٻ?z gr!x|dG^|_;<&05pxl+")#Yvqp~/=ۻ՗)+m hP;hv6Iԡ!p {"aG$f0:lFEYx$HBw|(*Tƫb\¡qO܃EsX02OPc]4 =mPXW d ;?G0xN[=>Ǐ08 ."U<A.-7V\kHfh ^heF{^^;5 X<;x{;/|Gz;I`%ڤE,b&SƤ8Pk&.A,pT I%eh,'hT^KaI&Pe S dm넚}x\PڿlѦPd[~;x(ƳBT9>\b)R貄qH$|%ͷ='njV@bhRV `_@VkKPtaR%EpQs)8L#xU*Z7jW8^GՇ` ݶ&YxB^`7VkeV> P`9> FVI?EE*5`,moPuᴄ༩ߊKc `5W,ؠqYY4h/H6Yjo|U 3PF\Y!8w Y̗j9@*T7Xѐ"GÀkFX^=j46oa׈6Ԉ8v[^٨cjoz ˜Ȅ+&ڜvP =Q#>jdZ mա!\KKᎺezád]~ -qnfetAG*)(1 uriT[7mN1T-Cꅓ=UZ,-"], '$vsaƝDQ̮i̸Ay>jE$ُ5 ӜSӉs#C!SAh/`.v򒬅E#oeMN [1;pF.}h#OZE|s :2վ.Jx|~oJ``}+!sqX@?Z.sZg:j.Kը q&6 (`uD`n$ʝ1Y 3tc[օ4nAGmi۽g$Dh)GHulL6D4̌A0%S3C^q< VJu%lZ  ^Jt`Lg(yutxgmXnLp >_`5<8hJA;"+ 21\>=EpAЄF5Xߔ uN#BN reEcCd;4Ċ) Es#;z3+Wgt'ŘU$15ty;D^dx9$9`n&0&p|T2(؁&2ݽ'k[liVI:n0"d>^Hc'(Q<I-ytwϤUDiwp OR]qYY)W:ޥL;Q Nép.3 }T0;.< -X vG+iƂd*\S<[ܕeUUE~Ze^4lx%B%=Kxػbil{meW߅>' r8Ցo"K-&ʳF9RVxB# ŗ@VWT[`B 8l|NH?`Ų $Nh9<g˹f{>!ؐofn vEd?jZ$W kn1L>4{DoL\x#ؑeB~t~ N/R6W#f'@DdjA6OW+h^p3 (M0[(&f{Tn]BN&@ݘPyif0qOXjLJ1b v,D-yBմE`:i[:C<&Z7ƇIK՗3hIWY5AU[a] 7ƹL&^%1tC4 A0-k73rMy0M!UpgL33EuXQ|zd#51'8VWUJԁkW n&XLQ"ˊKGà&zڰ,'X6 q(yTjbӨJVUm,\[Dg\ Ѥs؈G[#ʹ5-D|^M1mCn/ X}yR> f!:K dmlz%f*U(+rjcj8È(t) 4|2C3s>7EN݂f ML*lXX1"DiQ# bY}K蒬 dC T "V&ik=]}9Cj\et v+׫q57!7cY` V_ :j縃 -i#8ݓxLpD# WQ1v@oD<[K@&'i|\lM'a1[p A8+d(RF: 6l!0KX48.+6;E!2U_ʺatPGv1=BܚRuӝůR`ĥwq! >͌S <5]J9͌)=C }lLI3<.QA$55MERC[`XkײE_kHr$zqt " ?Vb%,7zr5̢MDk]4yJ2  2RQ#kM0hFo+6 Wԕݞ5Z7jxK4YK&g$`V<D"Y2^E Lgm<Х?DtP0*YvZR׃պ jDj>ܔ̘<ߺ|`K C|%7QA =)i4͏ii¼Wrܙi `)#"[TSR[4Tf|?Ң!ohG}0\E[hzt0 Y|U cQíυȦ8 M">;hRӍJZz6DJXa (2A{'HBXNV1air;Vlz N}7Oe x{s] 0kEEhѐeqi"R\TY׶A?4Ź+MTXW6Ȑ#te1D9f^Yql u) $}|ȅj4t"S oB B #S3FP CbT 1d;ZHb 48ZZN< \y@Ae[4u"Ghp*`ҹ+:뺡gUIG! Ӡ$}*\io*JV V5+O Ak9"J|u`:7w;[+x5888~V>lJD05-9lHq`Ǘ\:7M *A~ɷ')T.Cphycq;kl롍|F*@ XX:92C9bq&}'V4R=z;:MnwOkzM=1xKAȩLPy F-g\ L8򜢣J+Fqxb"lhdZ0K\3B-:(9_9tJ Z-nۜ VTYyl66N,fiJ#{=Fen{1PG3ucy(CAYr1H@=p)Zt/9N}Zh|Vgj/J&T07)fX~;iהςQ l9U["LZ%(-UjwD%M1t5yغ-eu{M~lN}`̓,VBbCKGC"T>wŷV¹b\b%tضת-m&5\P]:c 3OSkUXѲT_(YOR|) U!i<+KEoԴ8 yqٵ|rP=Ius%7Q!R&ؾjgo_9ЬEXIKz/ҢqIJS4S/n4/+Zh;yԙ`l+.IM,)C_XEؒj5ZTj?! šB~M[h+l[&C,虪 (0se扇Qh/őU!0Q^9,) 0ƹ˦1bw1N,9,4W]UaBp`-;|xy6BJ~\*,S4f=yg'v9.qBdvL9ICZ л ֲvkә:RV6;G0ݻc8hmhh;LI!s:G`$Ř`H{o1mfPp0IÉpq Y"#Qϴ,/Z!l!bf_&` 3*15#?R8D_C~q.>*-//BG%Ovw56,!x\QwPαΏX𭔜dURxT;n*a9GxHD# |W~RNݢ$?' T޻~M@ώkDSPIw_~g}UA}jX\rOkx;Xϓh0!w& c.e牨 ,g"ϐR,σg/hX}XrJLQHcwCCW}Ru7Fнxbo[pG@P`o6!Om|!̠LKBOWYf E&CQk"i٪okMfTLe=(AP0 n" @A zCUw/G,֊([61BxHJhbn۲=iTү"ΫcDbPXK$D uL=--[x(P\ocBJjqb~&`o` 8<^AoIRƢ9%9Ë[1Z`EC}M$6˗3:.9C:NӣwJgM=?f:o0tH* ,Ai&Z-^d"qas^ibd?φn-j j1 WKÄ[x;6uZ|"D`]A 1CzF-a`#Pm8ۿTvL,   f{ Ǡ%v)FL,Ι@[U>F vb7f4i@KQx! PZ7@%&毖,HQn0.l {E7eur^L-ni/Ͽ+b5]')'(29V7ܭ2*7 .-6 ?nwp^S{p#3Mͺ{\6Ybh]׷@8S2(1qK w?0 _`*^<NkSRo5:j%/K,zhkunˆl Ed_q`j3d7AZV_۴Y,.:ip3X"*uZF_7JJnj!3Op": s9['.p)ST9J8XHkRet9D4XR'W'g4<9xwrG<LPpq,)˼x8p|</P/lWwX&5M8JZfsCaܸʀB> a^gzӌH`r[X5&-|xYՋj`azzx [t `8ʚղr$++sÁj9ݚoF^2_58Oc#4<(d"Ot |.YҖ;O &7 /u h)k';dB>4,5L11pݮYPW:9,k 2.qhtb\~ĀDN.Ĩ+\l wFBx0"&`:ouhʼZX)#/%W\ʃ{`#@1#bR'Je.&TUxwz"eL7JӠrƨ.qDX7zdCRFB~Vs7zɪ}Vf`k=1oRxr[e7%Rf^Ml7PXl6cX3q=e[\!sj#0* FVV6t]jTXpH^J4]lkx M$ӛ(,1,V.ַk뒛%J,T#wWh4M?fd)1q̒?6;perYt9c0BжbӶ@;9h ϩ{r񨡙f]e&vqIJr/,qRnī^jpzȢcx2d7FWZ1ًP7o{|yƏ8l1@_J֥lg*w}XsN 7>'㚔c샶e;707bV7.VO7^㣗5'YTz ȻcD[. +e Eq;Nz!]Ť69/cyc*`Mg63 jqgXԪyN|ų=@[u$'Y"nHM ܑXÉD#vϴNc%$W@qQطPf2?dO?xjI >;Gl 1(:1승p _#2yZ2h5NS\[u2! g?IA}8$}`dvy x?0ɂ 9°ӎnW0e(CpNg0_^ 0q\}ŲXֿ~%u!5>ɀGp%-h,My6hIDATMXؠZU#8~I0=`/2ܾ4"ՁrS9XPF@K tqt$%FF(!iXJ#PNE<H]iAJ!ASgV/tJ79|ȯGGXZPr6ZJ)G*mmdҚO,*f< S\ lIQ ֺ_$Rbb"noեZx3zK3TjkItu#|Ȓi+ K:EuBwTwfO05,T7!gP%lVhŝEQ}W@FDX3W jsCÑ7fwKgfj̦)5 dN'mR۰(ܭ[CE c"3+-/BoAm`SpsaG3dsxy .PoBֱ0d303hIqtnB{P"T0g9H:3>'2GՋ8:=Vrn/NhX!ZP|YP0%E*%Ww?-36Xp xߖjL)-7q=D|\_.SrV(ӷKXuwMB/sdWHYfwIa\E4TYl Byb }*ky$phfBarьT+mFK226TR)I=^7ntg}\ܝ%^K4 3A\yZ^ oEkK#qIa}6}vN'Y:"B(rƛ rmmLQVئnW ^W Q:Q/p]s|J-]&k" CS[6RU6,h\6 F4UK! B:jf\;ksZ[ک\!h;ĺǑf.^c uwf '),NFȧT9ޡ`0ѓ'zg¸/dCdtO eg4h[  Ë6o6y- Y;Л39 Ïq8 ;((pq+`^:~"8xB΀Pak0骠RDb%C@CC H1=s sXX!VϚT0l'z{2{ < CXERݹVp/UD'@eboj"IIKIU\E"2<V@@vK b"2Hӫ > G\"=b7Z֚-)(HJRWѠ a(w}=!=,A!o(E0 \ϡ zȋ׀]0,X Gcj+җ~kLU""JҺe^jf#afSѵ0(Elya7K6 hg\=bD.e QiY= Pnjs4q岟5#h*p=-6h֗<%a}nDVdLӚ:[PlltlDm +%EMTu) FAn?#[ˆLN/* - 07i:OD} !l2|O8O}X2Cm[r gč{X4[e#Cb-,C.KRiń4ITu<+<6xeA,܈^7O 8Ig Em *tI|2e;9Xyj!3fֲ.{7);|?¡DЖ?g[Q1[Uˆy]i1o-LDB1F/gĬb<or.8j妖JltR RSmMQy! gL2e͵fbȬ֒ZBieJT,ce٪">&TV0C8_;`GfjN(gKgyNXbGh@y"*4H)28 ]UV"ΔƅrhCD=9'Ŧ:(U1x0w^EU[I3W7k%L"9س YQFw&Zݮn(.2hs.,I5+8MuaIҬ"'|Ez91.)q|Լy(&XtZ^#N.;wp0B(3{ˆ+MR bd@p"|K| 9[ᚓOPwxHQxXr4nG`<s/Q1вUj/s1r@MfaAֵf)͡Ll=f5zS9,Ү1T 2WoJـ+HlUN2<1P_‚!k S0$rMlb X/{݁ %3<^i Bo]86ڀ$1R4&Px5ܡlG z';LS~/R?irE3 2'ee D`BuFRܳ@):̺LQcyRl!y EAHaj;' VX1:Bn!A|=B jpt-k*,ɋ"~ (p4RxXLw0Dr@.WU af*Ҡ{8O M<8\R:4+c]~[BG5FCb*]o VVL%/FP H?s nKLeqX `w"u(&]-A"I[Nm ԚMtW4Q\EV\ZD?f3nj賁SP'MQ</uf,Zz潩1Lqkuցˀ'ʹB(;3^R2ZNTKfmiM/=KBvaFAEM, ju}g#s&} OĠ7]M~lAAG;8sPÇ0A?p)HҎ*R65§}Cs 0]>G|3TV&BL\#Q8V# $y= w-<VOt#2~%etD<-R@7'Vu>WQ ![14~)[uEt#[R2iZG'^l-!lDYT(P Ǐ3C'#Ao^S`Aܻwpp+[\ nP0{%u#O=yD\eC0bQ<%3ā~tpn/RsH"_ 8Z C4VAU&oMxs +¿m;yyh-Җ!dqyhNe1m#Mxh,gWa@?)Ab&mGZ!p6oOj T /qA:}0o?[NJ)ѰZcڊ[b,tAr+Ь2JuC MxBֱ +LnѰEG}2="r)eϞvz+.YVPR I?vϩ8}L D{ې9\[P`lТ@w4'G (4o $oI/!&6#HAP(G)Zr1KZd+5s&MV~swS!p5qY6wް 7/_~lr4"d{4m_gmYh^\]`+!(ey(JƵ;-x. ZD)!L-#=zLپr8i1XW"~С ϯ~uͫ7";ЂVu^.h$jDeLiṗ;#88萸[NhsW ֈpֵœ|ª}: ->qۺu-6aQ:%Il+ڜ`z4Ni!3ǝϞ>pY* *FJ!Z"cۜi!x77_|yr2u0;Nx io( /M];~@w?<pϱ "!n]~`> 0jF

xo_oO޼x^^zu y qЭAyiE6e6WEuK U؎f^T}sptA\j4tH:|e= 3o( \P3(7._=~OO(BZ Ou}G߅ag0nv`Jth$ fL("q}r|d>|w~wN&I]j?$ux&1~e@]goAk"iC9tz&rWnw+GK|P:Pӫ E) s@H ft"81<'(%AmL?yx-lajN%$:&FauaўݙiwCjuW7S AYF }}cg./2ɲ D< C4XS:7y6d~a,=kXK-Q4ʘX]QAiAk-)J( tz%Yr7Yƒ[ͥUf' pZCFވ&0ҦZ+|^N̗̈́¶tU[XBdCb7nE یKL#ojlkD6fo[k)c!Vmg`)@ QO} %ޅRuLtR&Q(" fB}~?h7䎰Lj̪KCڛo7bC텐MaJv\ţk`jģ.hB.'y)@| vl~S#Ў&rK )݂EI0IWz&gvd`҈Ɇ>%HH8C^󠥟;W۷oqdU :Xc{'_loÄ%<t=W 2{{ [“۴^wBo W&HSIE}G]Ҟeȑի/ oǴbw2C]lVYD2*jbWYrhIJ0 SE%F|!9! GG8.s?S V ȈU;";n!q ;<>=;@g_`}ы>Çt0?mFA_~Z<}/$k.NA ,կx]FC@&.0/` 0M^`J@ 0 _{?-d{8q./]@nHp){/?˓W}?_/sB.!1`@F̒kHdrM eLQ:Y5hn>~wz~rx';!Z2b\G]BG%bܹc~din3/'W?ϿGO?{q9@NɢHpdŷ({C~_'0x\J ؄oq qhzƻW}3g`{qen}ԙ*mnPIc<$kE8Rq(©_<s<ٜYTkjǵЬ)|.1 *( 7[-ذA&F0DSL| 'f-L+B b0(]LmżBAWoDM)ޕP9ʭEsJ!ZҪ`i9sdqb{4յ]:dt+OKеqI-= 7:DHEH+hĈϜk'xzD#s"Obk\O>ŋ!g#_hȊŋ? "8 <1iED6î`>-w`=Ĺ ϟN>'?=w=OO<ǟ<~ɋO>ٳ'm#6gW??я~_1 P1I(~ RIgv*=&Yee%vux֤,;VFݐqD<|` w|A v*G ?s|@tbGl?A???=&4a0 \ .B~v}lƷKK8Në,X?7I^Ak$h8:< } ݽ'ȝ`?ƚvQatۣC#Ch'ǁ=2ܾu E{LtUΌc8Bh怴#{z%Am;BAbY_i)NΈ2X$SeK/3IW!yvq?o3$mi4&'c G&It'&^QC|\菠{LQa<`DT-连 K"GfRY-{2g1 x$E4XN3R򯈓J0j,R+6!uUN5!3& MϾ^ L8H+-P$(*kNEB{:Bs _WWJ*S4*90ۙڸohJ<$>*hCY h , LdeKo͇{9 ^sb>y^Wtl{}5MBO"ؐ_aT^z:aV2WG ̍!7f/T29a.=S ݑdp.g±'Eyr[ &fY֎=% jl|&/CzؖH }~5ktaX5휘P[i;k'e:.o}MؓlsQVK{]H=ө߫?V-xRa6oloihG Lf8gv,`ŊVE IC1 x S3 :N˷0E} "ytY@f9[c)W%[k}=G)L4C:eTZ"Z "a^o5f s:7Bz#YjG+YkYdbHHh'Է|4tjq@v@\򝨂gHC9U8@q'\=b ldMiSEӱٻ$Ŭ~/p)_W5gOi@cw[ڝ_sǰ?'YvQG0#a!_F@R`3! >B)BCf4Fì.LM(:Kk[#"hF>Y;Χo=;xǟ? dww~TNrѓݧ|>~O>y{~'Ͽ'xw?}|C0DD|_R ՌdGR-Pju! 6qEweTX|m#Ǣ&1MG~W f>:]2l7%^rD @Hjxߠ5 &5ᢾcYvYiWسm]T([gDOT"GCr!q.;Gݷ\56EfMzݢ&j5ndo)18դ4 7jVD^⻅5{q,)+9#mko!NomÒL5 U#&Scۜ;xO38WDVSJ6A8żIF"^[_gx롬ݼ #jyfD8$L l!UT`UtYS84@[w9 -g4!Y/ԪcQ,~I{hZeMR\ i-@lY rʅynq5cW:@xm4kS /'y 읫EeniYLq2c tԺZzZHhtSYڠ)ń WZZˈKfy " G˫ɂ|/HF(jf ׬OPyJ19h\%*ֽ'! LTAN8xS(w؇:RZz! -lHimsX.fٯojʐРklac{xqԧW8@w'y5IgH񜷲فx9هsp[wn]ppV{ѓ\/0i22I$F\+M'3~7߼|WyrgLD}:f$#$RruoL)R~p.?E\ #v ݻ0&PK>eb%iZ(DKB*UU5m1mrHtʭtD#5.)K}/;F0{XdT@q'y=HiLa]Kc~Xta8"<5ɭVB4153Ƨ`]dPdmM[ױ^9HMyֲGV;mf8^Q4ö8QGٟ=Y\`ndԾ/(@H&v7c3c4>/nja^F6c28=fj6 R RUY{ҿU͛7>܏9~ܽZjִٮbpɣ!emfaK'Ԃ5/d`,cƴ h2ZfȵM[Z<3>5ZU"z#s~.jJNL|yh݃f4ϲaʦ|?2} fcg[2͊WЧ.r`#fΔUڨ^ G,9w2aP9ֱ-:Ω?Ԣ =jJ5q5Cɓ޲Uefc| h.1~/v7OLYc(qՂ"Y\"-NR_Kxƃ.J<ᰥw$<| Nt{!]4D=دZPFF-v 5a^k 0 s8d%5 sz\i xz".LhtXU 슥"t'fM#v ׉!c(IV,j݄_Idyy^38o.HX]YCrWC=u8&&&=Irds+qʥׯbSwy w8'T3VYXW=XsqǑX,8$ܜRFrsWw$ԁUn/euMjEE;T!FcmPJ)"܊2քD}H,NJxѰp[.iEkK=.uk H.7Qqsֈ.L"R%t.ղ }4iUzv-R :LL3`{.hfؚ lΌӼ/L;vI3ypeDy#:] cq2k4KMz,DJN ;ݳkOXϺиKOln 4 xyH hbv[=1ΐ6"-m&r٢dX gz <Ȱϫj2D&|m/ oÍc[S]lpa^J]-0-_r_Vky U?QF+PxO B,b+([&W2KkZx\kx~B^?*+jf@$2H* 9q'z;w`'Tr9T6Y :n % i6s.v𢰌#`dT ,qTD|Ԭ.B@6}ZRO4 (X,NeӃZ 5$*jU/t;@t~ьRpL0/`E4"'\%%dQI8ŨRxhؚoVXS2ul=UfWG-KFO^wg2H`Z(6p>c!s{xh^$:Uf!NLÿ, rՆ+"t[H>sPx_7>xdE9D_lƴ\RfBCMXK"Cb|OE@)zɼ eq:MKD:aM,RÑ}e/b~1AIKU-i1Q '4dw1~*o^ӎL3冎n ܕuo$6ig .tl)ehb}EV=u C8nn+'?3 V=`r>"1d4Oxs[l,(/+Eўezb8Pa :ї ٌ\OqrzPVu ']ű٘FM S. W,HbXBB=(]*4Njx2Y&({.v 夁@JghvCrI6O1Xc&qjyV$/F#.G9]֕V3[T&.W,dIOhSzIةժl[QB_Q<6|m/es _ㄆ419f@#\id+5Ę nVd`1栥tQamz)e3~wj̞scS0mπT ޕ/xaPH/y@h",=3 JnvT4thQ>өHs Oy ,x|6qj,GcӤb kÂ˗/c=822ZF:;n)~xš t,}!4#BOZƒp# WVY=m+WN:G{?3=UX}w|xGrY=2!}pmn8W;TGhGF#ˍi*7KJ\IqSCGԁgcq8dx>FO@W`C\RbX[P!C;4a̝:ā5ܼ[9hI8-EH5c4pfmE58:[ucr375oJ۫Fb8hwp6} ր*ussxaet0(icGNN9FC RBrpCJQ _ˢP:W%=($]T.i#26Hi?RRlUx a 0b<^ 8T:k R zdrSW M E@U6N7Lc۰"^u6ŠD*7m#:vM4،p`J;LQѵ Ep.TZy7DޗcH2&7:L ?ddʤif~݋ 'Z-,i!#MxZZy5hj^2 =^<><".娗K$ӹޓ]3. 4Qpi{lJ}7VLT/0dA e)`-M5eYȘU}Kx -yWtBfdckC`jn16wL-/}mdtwx,*Fz}/c6'vRpD4)yWY-Ylwi- \0tU&Z׭m8hĕYjf𲰹S iCuXY8Z ި2pTje7`Af nMkHqGk9s >;E#Q֩7";VJVdT9B=ceuf6^/0.UPЈ:ĦI/uՆ0 9pNý`-T9v؏P` baC`IAhdi)S<e+%x# pxʖς 5êF )֕`&~XtSO?~ڕSWϜv箝:w']?}ƗN]?u;Gg.==+k+;ok;ZTzWO8{6\!}pͼMxr!).D(OOG!2W l"F, =maPz|Xx)S'!t޽ugs3{~=ZlEyՀME^l8 -FaP/W`wFgAs!>#/P l$H>4Ҡ XD׉芦`HIJ}N܂CT 0Xis1Vo~DsgtuKi)BHPQIh;2|P s< H-u*/n)VM9ɮiF1ĘN=p;0ܠq(9ɺp@&޽jN=s5ιJ* DZ-&!&Qz[M"ϰ fM% (`lCW8t[[Z@mf 5km0ۈT7!eNoO=sC +D\Az!qvjK'~'K۰7P/Q!>=_$)6/8mN+2<ލlg(2 hBLigOn<@ #Hj83̋y Vx!s7SJ4w/)踬ܑX)Hb}$f oQOR+k6GI7oy{s[uNE(( :W5loksks0TMh OuJm`*A'q;Aq{;?nyBo\H8Ghkp^r/x -pP6'.хV!hVI8 >UudyF.cl`ؼެ;ܼ9p\]ɂ寴@ª,"fcuX k ZJlDGr($qhj(ͳBٽ,,yDٳ*C?O46V ¡)FY Wogwe(j̅?]%}w4ZM5ep\ܑ۳Eey ؎%s4|c !bDZ_\ 1յcʧQ^wFt9{ZASGP}+ $m1e(w㿽"n!3.i)6ZFڼ;=[Q5>衢Vs1?wB R _Y&hJݣ*Ӱj2m$*Ig~q/]Q(c*΁'P-3SS慪Zx h_0'_2-D;&cS6(r2A9ޓߦ5.£种M ꁏe!Z MD* 5vA&نocM\_p&r9l^X0/ (ђl<՞ + )+gml 8%>ۛK7n\x)d$φ4{xb!pݻo#$7H/rwo~+篜ǁwٽsu}k{so[7o޺[7׾}-$5lҟџٻY!^TnpHf `์83*"tHRv#WÇP舎?|~$ɋ؉cogg}rwo7 > !=p #;;S3bwoA^vzMF0_ckKQɝEcumlHdY*2 A~nʔQ'Q A=#TlP>r\[AhiAi{9R$=.C?2EBT!N|LJRmP ~g1Nze{*R]N3f!ZR}ִ%(IPn-mp-΄԰E)1ⒾGXa6 '2uS(ZR'M}(1&"SE2ֆOLj$ 4U`:-iUkƈ ⛓GlGiFpZ#̡\ NoۤcM`!1BlszZr b˽@ v&i?:18".ȗpI`ˆ^Pb>;t2s,) ^Vω[j :*2ZVLZ Pm Eoӧ<#E7}&,2EEiRZSuyqi wd2@-)5FIC|Y!E ൷YAbqNf+h5li`eu*Y3qܘɈ~iT;wތV[K1- ;j[[{H[oHV2O܎3žZ!Lأj5m_W&]UҦ%ְ;`*quٍK/xyc,0W =:x;oܼm^ŕBId޼1Ah*h_ͥf<V){hsD$@.8mqVk䄳P E5޽W7~G3<<| x<M Z8.>wN8:q~[;>sO^}ۛb4 ;ؼu=rrpc){R|$nW^-bm WG#xߜa-N8B.OmLy@Gf6"|}lpb@$8x /]x Uu*6x#8ޒ7pmM`@n: +\!0ݽݍgy=:q<.E)bbHAjƙ됯 \׳]?@gr-0pspPQ'9w$tXڦmyKjgjSdpJH@PQh6RxZm쾎(,_~@׃:կ8NP-bvnwz+r s ~q :VKV0~S_ZzxʒkwT*=-EG8zhU7\ҴȼL:ګ Ha$P4P-{`F?3`˲"^ ɭp|g%Tn0(LVbd78plm.h l7DOOcVi &v̑}іzŶ^p8UKp%Kq59Gh9kE];#]oKr[Vղݤ29v 4Կt 9;YLwaw>( CA U]8~u jnJ'nPJ8 ]sW:?A>1 `k@lz*3t!OQ9>{x!}j_EN%kkUZ/x*m4.D ̧PPg:]tX`6cNR>xOaG?e(QLjS˼ZAnUtU+RP`7uZg]#1rk4Ei|ͯ0d% l{ӱ<5E^~*kVWfG7 BmVǿv bV+ȽkIar{ZX}Aιr ihC2i̢‚pD< y phl6:R ^Gp/,3٩3{Y8>37<6z 7\ J»?Pgt1g83,mA5bmߗT5^6[IHxxlb+A1/e^YM=Q[=2-aԍ، k 3bBςHuAk*-8nj:FF6 H7Wj $e+XM%Ai):mBS'ۚ*9Aӟa3g}{ i gS\@ߜDuo.jtL8xA%\T$zQK mmma7 hSBOuxjT[Aj`*MGXZ Sn)Hh58: vc(Uz۵ 0AVgWӆω[s ?:@aw{ނSϾ؋c cYy:I<[T-X;,5r30ɍ{Ol|o+_/гGy?O}gp_#(P"F`2b+ *֭4xQӦߦzT29 GpS3܀E8bYx5=~^};s?W[[ $ʡsg<ēϽҥUN}c7ߺ|ݧ^z՗>#ww AC6 xILNU3kg!8Љe:H^N 8UNBBWOaQ.p@tě,>סXM,nom;c*E2ڃxHOMlBa\?O G;wwE nA˯t!<'>p˰nfAr;Јw-%֮[T\oU"QT0hlZ`puߕJ/d4 -}bң5efkUUYwFB\p4`z"Hw҅W/"٤bjUC]ÔrNhl*$NG=d [#&TU6& V_R* N[r͆ 55譇UAts2 r/6Q sǓ4DՉ23]bFfLȎr+>6m?\̈́K}j@-bUX䆅'UhX^_/D` žGVbKjFLG8KMlW$MHTʶ9%Fct"^2 7HdIe`zxb&b}0 ou:nm8 s_vit͏Sl2'S4KXFآLX/]֊ߧ*DW3u=j5FU? OP^ln9Fbw @Xyt˽e;tDZ>{',{7,3D:œ,Qщ7"uV m_2-bBc:Yj0=Ōhh`aħ:ڂ3׊z㹂wpN(C.Xc(OiSS62p-v3Z]G,s=D68z ۻ?|w8\ 5aV~)Ѧ'}-2zzA nGKwUP\9z>8?[w0G`.XRŅ&/cSD98_ڣ>yO7 .zW 0!}<2 Wo.8Xl(s01h&@C;4S'x w@ )Χd$X ɯ1<>D<|˟?7n?`N߅_;?)_Woټ}owQ 'v UEzuC7`~umw 6`*\v+ \AnGO<N>oΟ~O_铫+t?/|yAӧwկ>!аq=E5WePE&kX~%#ZiPq-vv^;)ArsO=b 0b.ܼoɃ"I'.\أ`Νm,ARGZ`EEM쐭X=jEdiZXsLJ%au0Ҫ]K>68'E/R|w ߒæG|d|V&׃T.@V؞LG|19#-S6H`q{^sTwJ*h `0NR}oiFp?:V&5;4ח e•15zEU#cbJ靡⎻oQY@ީϐI:)J 'x2OQyU Hkje  bC"fF>EУcfQSRFFZœ8ql2t XXQ8 JRb CۭgE2J }&, >gӾ.CbE9.^XLUf4]76&ߋ=X̎|pȍKZ\,B4!Gx:2ղfc|9:FZ:”]1BFP\TςqBd񱕚 3A#G%/U{ \3` |my\A==;mQ>LZ9[F#Yf;G<˵j1hy]cP6޸Y, },α0"3 =gۮUޏz<ۈ5hƾeD(D P4i0,VbeJx R!Qkן|W` )>Xg&O= a xpQ/kemk#,ȱʧ%u7X|Is$1s/"q"};>;PU$DDS @N@2><8{[󕯾}+:Ab;N@^?O>U<}_x/W?3Ϡ'؞" )#@6Zy v{ 4uwN`;sfU2_`gױs< o?O]{fͷ~ k8?܇ΣZ+IXuWoi@"-*u[rws%f($0V->"{B{И^c2qCd4`IlfI*3h|hi 0hQR zl(KPsY=mV"Og +ˮ[W~%L`3uhJSUe@V@[([m5|PQ@xUo_b~bȋyDD5:V 8], m6J/iuvG6«$9$|ؚfuѡ̓6Woq^OvzQU=g4ޛ`hk|Z2 h4r좑UϤl0+zP7K Qq{_ژgia\"![ږ<)+#iSSg2)S-6D4/JK s >ϳ,>Cj; n^sXǼ6s3^o*HNQZ3]1. 3rtfVd"~ߗ-"풋Xm[\ @A7:e;K0x=<d&Lkj=P2A8[ۺVF>T!=*te,#LSC7I32rK&%w٠Q&$F @;HE,BOf}Hd&6~{e6]OՊfb(]%P6LZ>pbeq Rހ] ۍaSX;1^]R}YY&ZJr:WDuqEp`AK0zc"؄PC_.$ PvC9-1^?ӸW4F찛o4hGRhH3k΀ cbKe?k2wO|ʅL%h(lqX8Fԥci1%vI pg0JtdwWS g{1r>sϡ Դj4i14^cG-IиQ"IYg} @X*lC|T^(jQ<$'FlUḬULxsFW6#pv 5paRn_έ?ҮJ8ޡ}qD&R?z ~oxG?ݾepaڙ"ptOK`C@u>`ɃWNIN`3 dp LGS_~#/G8N88ubG?T T`,?:wn㥗_^$YxSN8Bn*?( ,9 B_3(~|3Ah@6ɚGN69T<K$N?&@)ɯSpH|pt`p9'AvDw+C$>"n*s>2A9@S81u.%QU*܎C1@ɿ` H)0D$~nh|jVeLɲ³ vX* m4@J$%,,1h ]!kZc @GEa,'g,)35yՂ/Rt.rsTiqǵ"r`r莶TQeלd!tTA@Dzީ y b+\ck05SjѐeDx40 ?"257=eNk\âTtɘ!ɰO?ǦZ$Yro/yNCI<  W-y WLSVjԂ:Y !FдNO~n]1= 2%sFP|NDh=k?[ @p|w\68K#bt[Ѳra جZ$L9z­_]7-9̣0t!/A0H M & LxsZHѿ(M{W'\,#:J;#]+%5W8a23Bb0&"0I v7mlye䈮m/ԀO`+Mܟ%L|Gx3s]ظwoP!;(.C:$çzx ΟyĄp*s!mklIME[+NP'~'.iid M0YZC8N4e3 ƂbP7,Ac0H*bۥphe'1^ W:Sܫ-L +%UHTc9fX)αzŃ2q=P=IٳzzNnC[)4Eї<:df  BZ.|ot,Bc 8?ܷn\O{p5)nPPӍimnC!@hj%KY"ABNtu BMBz Oc7pwmNԐ︓a{E簅gwʐGܗ9 q_CHF ‰"Sq`!RbPN쉇؈og>sU\іO>뗑C9}1sn?|]+ s?Ol`*t"Fs/?ܳcg{Q!M3©W.]y$<.[]pMÁvlHӹ֍xx=A{s^\J(:pm DJH7$d>sJt9]\Pj P Ƒfl3(76U$4uHqg'DH9å\R+П!JT{9>_BUVcѱxCd,)nGAT x>r;w)сyF&ت\fuq̫[Zq^{)SXk\*37"(F+sl,@vSD! x+裮agc7.|G~>goa\,QKrb}fA%9s~ao&ցHkk>ׯ_~'\w78AU8׾sgsK[];/|3\G~WlBgsGK QR_5 QC!]x_|C?K0s*`AS#q(w@D]R}/rn;'ݾsO>B-AHm!c[Ul:DU+"ҙ2OGe2(Xoo(ϛTC-UDo)9_eQ(+ g2%+Sy*O- nwQ4raif ʄCwpRy HI yO~i"}?B+ŐiWFHw܌4$_ e$ %nZ@VxBsɑTu/R:v7k.bBzIs_1/耼6Y{!OpT fp_:jκPH~UX(teFT3hVö!5*5hKyT`[v^eQ\ϔq؄fIɌܸ(D?l <% ! ]G!fA/tsNv|,lPDP><qj!5 nܠ~!@N^z>_Yt窲^ /!I\@hnAz!GE`gڈs'|LB5:R'ߩS!-y I5DUequ+j}nacM(SX3}yWi.GU2XdΟdzĒO^'y:?بYm'eQ7h1=݂a'TvX6P'3dIJ4胴7Vބa)%T#E( pR,Qe)GcAY:Z]¦북-FIqKeӻ]|q G̻İR>S]p^AAz 9=B1S-Rfb?drY.>[WF4)8l '~ˇ4,H0PԀ,'Ȅ .<#"w0UBMuQ^ON//a˜A~Rž+9xG{SۯCD{HR gb8%-V&eAJ,r*K٧`Dc9C I92bYPm(AtyRCbO\sglxM7wBᗐ5a0Sꘅ'EmH~*22h]%`%%A<Ķ-IxAz~&c=jR#iIoџŞfRw5Zt&* XFmѻ۴Gtmt7-˴?9m ,}R@EAWKVMw!vʖnدY>j@-'Ʀa0s>u뎅}!sԖ /|L0ytSFji`@uԯW[@uk4YhMqH%8N4 eQ6.AKspqTN{3|Zk)A)_vmD'X.qЬeSk;)>=:~jFsqAڅ@cDŽ5tO]|Éo~"" o?r4NQ w6YP#+:|G>__yW^?q.7 x<8rxfQ&c{ZWV Q#Jc(ߞz|h]}ìY7`Ҝf dæMA Ȥ9~Љ'QN(^:`~: pQ{4 Eɒ,Qε%Dtب4I3B%R1QftȳV7ۚ)Xڈ$LNAc,-khR?jX`K( 6ETRݨj[UBF)ѯqE`*:a> A—m j6"D)i˝UwP_@xr<$iE]HE- yאTl,xTYPUr:7[H&>]e8fA مa/ []xp 8I0Jutn`iE~4Yfٜq)[LF\V{ t22$3`Bj i3d kLOAo.z+s7acڅ'm$be4M *3]?Dmr SEP ]: 4CLĂ֤| tYË ̽]^:BEɵ^4me<֞\BM  zb0I G9ZP[YX*:.֫Jjz ; 7;ۅJ^8V0K=x>MPeD?K0~XAw4hHx%+{7EBUc9z*32)pOUHc"/Da~CL* ]zG4ڤbOwpĖ]*ZxYZ{h;CͯC1E]S-lO%$ (4Cd,_T-|>ao~dir.CÈ ,#cT[ۅ,/Ȗ3М'ܹ/]pX.m8'`!z$\r2@͙j1=`CG_!x ~@F6l]pK6`Ax!}̝;_?{'MF'Geͣ2%!FQ NBfٳk/^:dž܀*ߺ}?޽6O\!p{{{Yz1wi`K.XAkXGz]ry%zA∃M4z;`U>`&z }S\vegg~'>zyrx[+o"`|} $DKg$0FE"bq2"@ wFfsz* Nržh*%!bޞT `Wy˽}d=a"8s%&C^S#$pPj;:Z2/{^)m{讲ƒ klV\c**0 C|Fi]UfWXPլ1%[<>Iҝ܆cc*QhN] Ϋd6TPkVnx֧NZ՞ q2#+@x!!",BC|7D 82/}F=m;qCS_pI,ں%, rfg4^|칍j4Sy|]<:ŋbVI(OvLT*f:!hrXbC[[{{8a,Q33YAn7'x5gNS)  dj !3:tfOy  GoyVAl6~B0][j^_ā XpmO>R=1{=vҺfp`}c'xg??ɟ&/\ӮsP\|ouk/2|BZ^asܗX\R< \2M42C΀ Iġ#@pd% Y!ܹE=)C!H F9ę843uBa GAvyXI_*$[dpNΔ0*2k%;솗4.-#PE5U %dgDUJBN0sY$S 3)qGb3F' mLn'5QG#B{CDS20^Tx_H .և HNQ M ŝH`;Q?PgH.D| غ"d0hDSRXJ\žY]dt-F l+"s.)'cL$q7b =KrcGԐ J%/bEl6 ;;[0Ul_{ɠ#4un6zˍExq# 7atfqG3fNhfӼBp$6-؂ⶊXzfHٺGa0鍵׮Spj"us$/=ML x{S]/(60&Z+jss0޹{w I k:pF7m8l?ܓO<'RQ@"aԚ. Dj@][b">=[Z ;??~ƍ0=$Ze GɉO>3>w%"qA՘b̓yUĎf'ʬbKř{p?'zr:2P!\nV puXl>GF 'Kg,hG|8縮<|𹵅us'w}tuWسH8!}{H 5}@YB eUŞBb&p nȐÃk ғp]_P}FpC쎃߅vxp=i9G2@@T񌿳MQ%R$p|G 8zGF_d՗1_(>2TɂnJTGR5n(j!\sv.$zf'%?(ƨe{au-W5+:Fd/hqP L{|Vx,Y 3uRw3̩,1Svy;?mfejtL8q+uZU1!nFm8Ee'rSR.큃 sVM/[32w=:໨e:b+i{,j(*eAdqlw]VTi=1/y- $.- ?<єbj[6[@]2n.ōJaV}v3IvE@ÈjiR.Z_<9]XIH|Gm:n2ߚ*KXK,Di,U} K(aK1gayN^XdT}mzQ9YVkequ94z^v0 Oo~F["lC]mhgXY $ogɱ!Ȯ/KTz9O9>ӶY ѝrPB/. #%J42U:̕Pa \AL B[Wj9[n7.8}fIb'M&6-dVN5A$m#ވ:̟vJ"x?r34,! d} PX R5WOz8#nZ* ә׀:-˧I{.rCix^XXjGgY`[y__{*:KtR$UhC0y:E!]n6I Z7x뭷aǩd ) 2j-Wd$`m}{DdF8Ȁ'$Gm|]=X1 Ie zX90(!8C^0R U%3HZn{$q (`ANcWXy\ y$G4O o+ X'*e5ՋGc@ȧWHLK"{2Hv!Ǒi cv4c](|9"$KRcM<ih4 pO ɖ]Z? PsFV Xo )2[{{׵9#´#@B>޿}6NA]7/{xA;wؖ1?.'{%ᧇl8AxZ4,JGH_X .  j `A!.B0AL5D^)$ `7W]GwS2b|D3a̓F^"YQ]P˱8ݏ SmZ4 EpQڕڰH_u]G~=j+Vm+1y܅RzAr/14[W86V;@h{u¾r{ZaqK̰pFBp0^[ώ&l{*/br*A]bX%\R(n"i?0{_EaZ՜ %!{`.[3sV0)LTkG/˵ˊYuǐpdzP+ciPwv{br].sRvNlwZ˹'5hGn+ YBo}0_*ڳ@c၊Ky5e(!4/!UxR3,M;UqfWa xA4u~h5oYe ˬo'ا}ȭqLgb׵%㐇Ǻ+dҭb@]s«(XP\M9N]/0 8ˊfU͒M\߰HtRWay@٢ m)5דv0mxۤ#JBOjWijb˛NcHO HwKwkc4) ;l ~ = ԯEO`*}*8;5dz]mr Ga-<&t պY1cS=`a>W`}{n;`A MԶCDqz5oMCKd/8r@h$ O݁{ׯ!c_%<Ĭ% H^X9SO/~77>˟48v{)Ya~ޘքx1&xb)pgwwg" *IHQ5lOy *)  Yb1" 㸽BPM57-Z#qE^Ǡ*(CE$&9ױyX78%8jp9"8iRi։zvx(ªnBYicj\r'*/eD 6:WͺF&[vK~+' d= ]jej%%fwV2 tfH 2~~4t4IF`!zWI9ԂG J)|raJRu@8 +\< V1HrrH\lq9[kg7nM+gVmC8!+b<bu=z49Ují/cl5dz b$Gabj'pV}uqhr-j8xeAVڙ(~óilA\U`F!Ș8Yޓ3 F<%nR`W\a[3e@5׾]1_SJ"5Bf$rBS]V1RaDD%ǽS(M=8C[vEWjʴ.H-rdcw+e=u婺0iؓ:׳S:ڪASwS&Aۏ͂plTMх u, \0D)) z' Nh$BAz:֔QݕENYxa:ʧ#Z3l42 B6,ȉFx {u,y _~ WBSi2L @Dj NyV|_?ϼs|>민+;0vqùs71yԈ?rc֞^0[N“;s 6dDFO>uaղɵ10޵ZJ+vːEAdȁ 9.KtS ޝ=]I떈[ݻ5JV#t UOz $C]њ{pƸq4_h0e 6T]-ԠYbe1$B|GWjT sc]^xruAzOߺ{ygossw[' .OX㠵XU;;7wÅ XI}Cdvc ?JB܂Pq% F *Ĝ6 0 _zv"G{ATBAjߵBwGi=BveX4؁[\Mt=gr2(LF:@5hx{F\ցo҆J6"TtVؤ3h7vk\Ij?N5[qfuؼCS" lZKW UìTS\igDaAJ' ~R.[_ꕖ6Mf%9(ӥ0CA}rI DgV\P03S"SV1cNlaIXhJKdnˑJdx)m:4*R)BiY"mྨZ%GX$!&6l\W/\} }Qiy)R$8+EC7.gF>,` %piϟ۸tYDOtP01ô1Eiu@p}nj:rMflMA%i3PZzæ)2a_pkS-Я3/c8uvGH0A5gQ6l(0ɩB~m PĪ T@K/?s?|W#C4v|c1v?_gg´7Gݼ19xpcPƳm*Dr N~/^CwƴK1G_OgPO73+([ C؇ȁNi 9?x7/_‹/b9g x’r XIS|$\{ 9l#\§Q:"{D1,fvSh N"d;LΝojHSqsf2{j넽ww!;޵n9 ^+ϓvWyQX1>b>/OMEΪ$HW<9N8Us M(kɻ7W媓 \szcBl5~u+]c\zGqeQDC}CDortASlqn@ȥeK=ʜ&V~B/QLtHߟk^EJ-$Yhyq%bd4%q~L{*4/hV`n?VwFDJ)1`LOiDT`ddB|2RXguLIfC iD} cSL]+f0慆aP+e{߸Jx 4 MtXb jPyWZyYis4 ;زkc. ݗzTW&b?瓱h7\&htFp&:4Fm!?*d\W; u7P[Zd0|O+hh?LQ6ذ_tCIC!3˗&Ň7tH !Aos`ͣ|x!6M* Թ+^_I*1t_f]jS]\'6?~T;2"Vm\ dRok^46Px\ah Ӕg;qd-.+n--0K>qqZ. 7x-\NHa!6WоbU4$1asN*(;uqNzj磾OD(v1 kGK01{^"+^Y\$ 'Ŗ! $ l5e?9\ǒQ혞:mc~Ugrb?3ΫcAUO9yEK ] {jA U{>Z=fY/Ljض;t&p*S0ڼ _xG} Y|\]ǥ/=D̜3Wru4qxHIDy+_k_6 9vܾsiyG!f#FN?裟kKK~P^xs?ŏ}죯_y镗)!Δm(™:> Unu)6WjTgd:7~| /<@uyY2dQ7UAJr,hwz,g`"ϝ?!g=tEͰPsl({Οǹjyp-c=3n4χȂ8;B* 3sRqR28ʬ?1fT-PآAjU")Ptno3 &WMC!]( icFu5Y@)Iv(ȡ\`aY4t Xب%52ʀG`gl?YJ7P?"faIV*)U-qj02?C)|ÿX0 OjLU#8*$:L=?#뚂 pF<ȨdBA u ;H7(ўW@A m 1Sh'v#?JI( +ED(Z/5H>474~XR-`9".apsBJ>u4#&LhĥnhJzRvRpaRei!WQ,R >iϖ!`Lo\H 3(i;F;ӚHn9|(=g[|aX۰q\|Piue/T6i{y0j<-1Mр{bhSk QhQ<`yOegQzi|=Qe6̚xj0=Fx( Ի0=QzxS{Ell%-"ʎ#Cvrm ~%nD=5ϭ4["qB]Zp{,Hys0St+c?v-)ڪ-íHע+:xÌ5X {ĉ'`^t ˄ ttw8f} X+` mL@xV1k2e?xh-^sXqӧa{&UۀwxᜂRۛo"{G&:_FstL{Ksэ<=ӓό{Gf^'rE,LVʙsg>7o ]ck~U1YecSO>SlѴ'6tL8wxDEN vj[Ӷ_~W_'_}U-޼q}7^{}'?O+"V,CcR"Sp*&xʽѦ~[o&xŗ."]* Z g|G#3Ń 0"NZp y8&:6CRؚ{O<+\: ol ReO-Q[~=$L/'+@,?_)ʸcOM12zY[kRLd5Hyg[Xg9UBrX'1<\XM-(N~ T[0JՕe ,6#A'HWYWPj6R2ݻ{X$?ڃ/=PgPB>!Y))W4f$7R% L̳Z eqpm eBD vnd3zYt]]N?V4a fD_ơ* 4/\i(ITqBqOއVw% {f5Nf(n, ==sfTrG )=rD;*ң%UGUT$a,~u0êi*E@)o; mquX}aoͣ|2GPF-Vp{!6mGuUO\X#끪+x=@4`+dW m >Wb':N{*hXXutLg)EBDťa"Shjg̰~'X:":ӌCC;e!}(8m:nXOus s"$}*C󒅧R/x\ª#5ql3s<U H x?b}O>Z~=$^!w~yk.շ`X0Ԟ[#DM\ͱ=27i]}UZl}3Vg S ! bosإ֙wjhPTs|z U:fT-OkZ'"`51^q0q@/*e$M,)QExPMsT~YgTG9)EC((3)(=B4 1zY΂vKi1nzٶ2hiޤO0%@W])ײPʈ78 4 d ) G CMp-3"V a(F8Bl,>J;'<AaHsG=y7VN 7Y)N:*XƘjlXyM7jPi[ ]!Gh3/i/2`"O[N0i"6 J`tp9L` إ|euRA.J,>I'B9 5#NY jqTA,ŭ|*dO=u%޾} B8syX16*훈pZԿVh= 䈕6,$ܻSrʧ??B2?%U>C~|;|!AGrU,ˣLDj2&ٟͿ?}g~?s?{=勗Is㸬ql!X*[|a ҁyթЋbf]V]$C53KK WlCJ!*WI~ nXt Z'3NnQꚁYN(滱8LG%0ǚy͵B_w,cóa\1'5FLn*Rc\Jz~67fnD7.nVJRkz耋z>x28飝,#I"N(̀9v DI?9,i S\_]s",~Wߋ&1LU5 Wqpty1 1,+2 U9Z?Micy?%yq~ÏLoW4Rּ֦^am#nd߼ڛ9,Hǁ \1!wVRqqdM2F(852&l0F%9bBN q'hoyFh,\ck)4{bEO^gzx4RD]30E!wnjӣ2%ӯ[A9VHM}#50pUU"n2Ya@no9Fc"j!y[Y F?NuQ=y_RB J35e@QˌӦ۵K(AՅ.I !ӔǐJ꩕ԆJJMiȠxs8 ǒq1p jЙA*`NCBFPؿJ<\3r)k#SHYkE]hr>{6TmԢ s*Rv[ V;}z'v#\M X6%m A.F=)U*B/)ru!51PZ.'bdYȺb\1je5ܗN، ;|Sufˆ O}ZZdDu]#vᖈ}<8liǦ|6⋹s;rEHE@~)!Qĵ.tqㅇ\YW4?a,^6;EyeW@/'n?gVuz3hȟ\CԸTG9 ;X\`14:)<ófSQ O *<67Yt OEp)aXXy3ruMե~&e27ÚBrEQ4X(TKG}ܠج.v e k&ˢ1uZ$5/!$8Y: m&i$J! sE+D#!7b.NT1T{)]rDc9fXLόM[ڴ]@ԥEuc]ІUթV"Uq/cD1f-ՆY,$z>t aC/2g[ Lk-@DQꀄ, Dtd͋h*U,B~ zO\d+naD0a<(Y8aƽ %جDC-wyd M ]o}GԇU`f[w|߽y F~=쳘BVx7\4oD  ` M@ĵמ{^OO.x]xDGx^M0a}% dCw_d*qLHuCd>G}+f|<]gL0YcKi >$X)]O0冻w޽7 `pʱ|PmxE -/J: #ZzmtxĶ^lr"tKMeRlH6G[vef^Hi|n#OӨɑJ1fArE8{tWpQ",f n$_;rkԕ) 5+ bPrA!wEQU#iǝȇs%HE7)IXr6=NDل 5R<;LR*OMq[:K_u=vf"bͼ'%Rw|Y;lT6J;uUo=xc"&{²<rw]F3S{I `"f@XOB~.LkiG3|Hͷ*:-Ew61)n'4bQ2K w6PZj!ՖΚ1\3L<i UǠ9^n6qRtbˎӓ۸ i'S7rJ5g5HKGDv䓥̸gGexd[<`=;FzUSuz_*c;P3diZYJnK}5t2_]`{dȩIm%ě6! F8,*#|A7Ω~=Oۀ"o6o)8Gws2[?Izzr҈x!HpBWœꤸJ 2:-p?r)&'*@aa'um== /bKx Y"&7J §KKt fMIk$E Sk39QUQ,aŴ73x VH2uUYGD!T*Q4j.%XR0<(37 5Z9-SH1j'Ӗmdf"B ~ F@nfiVc[ku&E6[qy]{e**N;_%lIg^J%@`6,`wY˯) @WL%`$T^НYW[\5Ōh&7201g&.ϞϩQ1Pgn-p̔sæ}`Ali2KN5\XVbf8 5,R $ЖUϗ<;to&  W+M+Ѩ̠YϏJϤ-hHǠ1nX`"q lKMO;\%<^mId$?_9QaVvɓQ kbԕ6bbїPl_n& $ ZyPVF0(! wٵufX4-qNK][]gIL8=2j? ^U`0qf&UL0sSz4!zc˦RJaH&f8@Y֦Tq'=ྸ'Zef8Eba6d^t,fc`35&>X9!u,sQ b)5nWҮiݖ ՝OQ.{HFpQ a?m[Ie*ҍVc[fkoSׯ-`\V 0)ܚgŸty0o i`ZuEckd32{'RhtR hD !#co/j,!/AfcNV2rS{yD#3Lee6<#V&hti7>…<u^("K֕AAeFa;Z-g "E'\#w4=#Q0<g)[{ou6X[8m4l>a">U/ɟw9á8]qvk.m[X*ld|p/gy3exP{7_©]TvCͶ-m H%*j9b 5Q]`2赤p?#n -mWbQ{a _rfnWeJ,~!ri-JVPXjaXQG^*,AVt)R .::JAR܂l Ff`+1ljiŨF C2<^8q/R|ٍE;a.]B,lGu@h$RwvYS$, lu&=Ջp0k,?e,{^2:5NÓ5AP/I5f}?wuŞyLE#;? 840Ce`O)uQLk?;>Wv! ;7HTXL&K{%!)[UuaTp=p"Pc;Ѿ 4Oj!9fMnz-aV>w!A\d: d+ڈHtЕ槜䢉&4 klfoLD* &`RbӞ['6+F{<,؃GBSCAwTw7>@͝;;Y؄Sx7ܣ}ipރ#vp1p"Uq-g8!Ui:_@-Eq*]q-x;Ru⅋/}्1'Y""`O^ Jļ$$bRV=z@?0̌l ol ր)+`$c KN:@l,B܈F' 1\$" ѦB0|"yޞըwO)ҫبFD7siݐ"l3Rr8 MK<(} !ݥ\F{V{լ6-dNI=@(5waL,-FσhTXdıGw vPU >B wiuZw"|aZ+Y8ҒGT1uI@[ke/ؙC*w ,'V +X,W 0͵ń,M#wVۍƼW ۲rY?XEtl9L͎7N + > Et(.:i+<:R_]Sքz8ZRVs\G2SdJD0VPCZ7 ~ bTfՐ ҖҰTV[|ڌ:8lƕobݼ7ak7Ʌ /{>s #3H7& f[L10Ě魏xWުPF]alSt(D $h(̱1iDy#5d6j4tfNX7R fX],^RNF6S^X]w"] pk;عTdfl9g,Srq18GN`ӑ]OCy ,*>.CgQ@{p+9_tm4<8E2u5?:F3 f66u6G\!b=N+6`3РwچYVxͤYGȝUY;f`)AזstRtl4q '3ʖ%D?THhc{GkXakP4&GF>V-7q|}Rj/ ]]PjFГ)xz}t~=sS96T(AtP5QVPrY2iX_?{*b{< z<Iz=ɧ|Օ5xZ 9.uLhR)D3ژրׇE&d^بLtz7/gm Dj)BZW6X* @Ad;w#אIrv}]d9 DΦS`?逌kjPsS'ҵebΝqQwJvºdA"n{H=Qr.O_TF!& %A#P_:Jm9=ȫ [j.] RI NґU2lXҒ%iQDZ][e, !pULx'+=NB 6!/FPwfY4X nRH*lJz bH;ࠁauNJtՊiJWyQQ=!&k.R=}Bj/5R}&ߊ ~T9KKuԌ0)}bH5+NUj5UmSlsGwt[zaY5n*4a2Z{0 A`}='`/OapE#BT 㑴GnY0~ 0̅i`0|0_x;kgװo5o\]C2^ڃ]9iF ptTR>w$00IܤH"mL!ad[4drcii5*< GÓhdxiQ rTf5#=7mSG:eܱ 4ȭ6t fmG yƨ:Xߋj77W )]]CoUwˈBFPRzvcB= fT5]γFdmpW a$BTNf>o(*UǷ,HSuBv(=лƅj$q ݭsm=YE3a΋](4n d56ODwKV%Cd F!.A)y!GXib(p>؄޳ײ斔hK=KdJM1:%엹^Ped`;=, .ٵgnBx2ʘD Z8~@Xp;[oq7os Q]p2Nc6H"/~>rE1 3X4ci~Xd:x3j7j˃dz !h=Q #11ʰ׋rSpE,+/~XwX[YY,+vO91i@.8W# pLƴU˻L 'BJ{Q1Gb92>VXdIHj -,IFOm9R1fe,p*"E<|bsά edKݨ9MỉTN't+;=)>vuzHQZPcEDȄ؋A2 6yveҵ4BǤzi]"wDIm{B[gT6+;`9V󫪚6a%5Jd;SMtƛ?NHIDATP2h!Anc?O@)PN A\{;Z(Q&Z ! H/.mU)S.c6t6YFe v r>eHH't0(@,"л|_z㯽_~U(q &5}CPs>(_ŗ_~w1V.G~_~僯"W?/?B$0tf]( δ7zȚ6|HCklj }+((\WZs{$wH(+VV yrX J3]pTHA1̵"чq@EU!;jvZ#XQ!UMmI+v֩It1جum#1 Ⱦ؈u7SK&Y&I{oscbE UTUj` K]hy}N! t9D+bpJ[gByr8դcx1k7>}41e"3ag'͕ӈ"[zەLUF"i}0Uŧj9*Ŷ$2?iprzšz$X&`hgoᴯ[䍧D^AKAPP sv1v?GUe#0HCVG84:N1OrIܦN '`XŐ;c7GA!Z850\CBOt4*zИ\v̕1TNS`Hbpdr BQ̹-Vtܻ^ua 6dcYP^30%B r(:TRv-_gV2xLK[{eLe$3\Mo*"-dà(q6#ʊTYCpW~X 07)s~EJx@?8BqimBP'[Jbj '!2㛵|Lm#W=%`fz.Ȩਲ਼GpOqT U5u3 ppS#^J ul۲0QUuJF7[^o5`b^t=vܒ((hoy4}~h7YBܳ=/֕y?R!ƉJW<%*Yg0Id.vǘ)412q?QES]FlκaPDp5&U_ JpRh4)n@j1?7J :Z1E_Y@fWӤ|7ad}s,?H)[\W\(D(C~Β;z%6~QPmNVv&Ҥua (qx&\XyZPktx- VбΝ֏^nنoM[7oO3hOq'g3#sC;9%M%( \z2XlhDq̉ +hao8W4K΢)>lnDC,i28+d@"z<I>%pތ3DO'vТf9Q %MX"%,35>gJ/UZTBpzydJL@"l!zY,!K<!@+Wew9z`h:^5eYYJ\bP l=r|tAP#LZLA"hMtkbKdTT{CbAT hS0Tv֞xFQBF)gb@hiuMYc/4\l*Y>‹=ou꜂C#+ "NN!1w% 25Z8Hmri.CLFR d|Go%.`b[UI7rbSrY,>閭1m'νuWO %s/|c|1؛?h`= ǦtWjrzӀ*btőwU/=ЅַKov:̃z3sHkv2cDQs.ٲoTĬR,%vavPPK'AM$!AYoaG`%Kڸ)Y{zd"+n&;G6!4#j P1Bt62T/$68&l): D+F$)qP7Jc2XEE=Ċa+QƐ mth7]hgx+e8z{|9\hB94$vG'!~a}*vy_<刂LL)|諰!s58F ±-2zR(yHl- bY2Nľtց)MK]gĠ%d|!'Y_"Rڐ{l49ې@ (jtZ7/u:BR>T0iw_v'>W'*!.. ЗRb:_s|={ ~r̩5Ԅ!.b!أ?~5VsϾ>9ݭ// rȭ@i찃 sc7/ј/gIWǑ3*JoFh k])1Kod+)? PHqyqȐ؄,;K_\E'O;Y@A"H<25l!X/36=*#:N)ڐf&c$3Mſčd=c(u*Mh7~h:V6e|#U'}cnQ_b0Դ;BB1/ö&A|Mp2YO-{jzjկ J/zNh/'۷`%kz*J]KH l<Ļ[j(Y,El3D^hLD&[޿B rV c9F Q=@DLJ$5fEKa$$B- [VxT M he5?[oFV 9|G,9(`D,8Y,o)c茌Fy~if/)"xyq GI̓l3b3:;#%QaW-6tRJ5HBq5 u3nR?i_jGHPw$<{ô *EUkfpD]N;F΀xs_{S"fS}p ~yc10vuT{Y#gba TBo|wi׹Ub}uo KP=bd,I4?YkU3j*%.΃߭*QPTKͨV$D8vM8OD4aSV҇a #K tCnPB ; B XŐhzRטAD[`/h.T |:( b]q!6;2m2 dp1 3iO2r){zq {bj 9R]V|3+FLBţ\Yب:yT<8Ԣhxv%5v l$L)LHVG@^/2`˲B.;)B{ac}QR1L'H[`VF*VgkeLVV sa}]Pzp<TK&q\L(a"0G3j]w걸Je"ΗaCgQUM}e~yT8l@V L4!LeЂ9@Dո)Qo/.R>GACuA \RéT%_rnpkװ h_>;/~G✋Oԧ>Ww毿}^BEs{Y=SRjlm^%. ]K(:˭fwP]jM@Hyo+w"m{\M(̓\c_Gc&LAÀY]3VOGȫ1¼=8ڶ=2CtlMhVI35Hd<*3 DkpWt|5BRrj͖FH)r*!|94hE}Qx1:ޠ N&aѠ_Jσ'w~vT}S䵗܆*M;*Tb Mj45G aJc@vAĈ䳚pA4* ci?= 1v_ S4[ݳ c_9pº8ѵ2w\Tb>Z;L/A$&`LV}dm<Q" :t'KTX񈶎~ Ux URyDC `q"1B3,_tqzX qfBɃJgE\SJOHO=&}XyqNPnPw?HJC354޺vP%2T  JZYKHK38[HI8V$ćTjɦW <|O >51r6,%dbI$$ #MKAsxZĪkH"D4wٲ:bqcy$& Yk[fe]jYdd&M- YPf]U]bApD DN)2#Fܘ-wܛ'ook^˗1{!ucv]WS'WR\RXaZWf+ٞY4BH@?)A6)F}w[ȼ_0v0t&]hfO1HOBGlAW~K_׾7+[ͯ7ɗJ?ݿW_*w᧏?xV yTG\8H)ƾf)!F~s[SPM*tBA?U]yZ4̺ZyhGsQw hBJZ5/p(jP Pǎ {lyV7a-v zӁ$8+`j؇D`F2ϩbKԜyJ*&p<:l"a gMH٤{B ;^а1jI4Bh+'k~척Tflj&8m`vb2כ=kP܃qZe7jTveIA}qZg%M =񴵭I& pr(!NX1OߪGg:AK 6DWzChK'Kʿ#1*awh!A4Y 57MKuPuFa ba%j{eNc%vƭrxFL3Q"3;_ّ bdѲ(WRofЉ9U pՎd++ƣƚ3Lh7@v@Q-Zؒǔ% |Cؖw$@rmNa4d B2Z xW.+sЯl?W!i@ܩd6qUgh=i3g &!.OoC#҈u\MI*JHȳRpSuf#g7|@r'Bշ(;h޿A~T:/Q+OraR=ص*D1bx]O#»sL <sЉ޾}tc{/P ¥&b%aĠCRe tBV  s+ɏUXo}[س? /X&*M(.IPqx8ԙwoߺSO}zxSL=oZYߚO>Oş^z#Թ5^!ʥg~֝W^;s+<̕kW lL;-aQ"_`U k̨HUڽy7 ?gkG2\ji]P  Y8rЍ:c.VH%`;Suq|n|p#XȮ85nr2WG˜[-w퍄Q [/j~ _{-)mPZgKNak}aV lD})J!X 0@{Y`&rLN vZ}7ZH]ߩD-U$qMmoFM=^%Jb47lBbz*ꔖY:^fcI\ QW$"k:ٌ Pc;L~2<5rN!^:v3cnAoR1s=P;y WpP3zZm>VlԾ"C9,| LڱF`#bWS%l&gΕRyƍO|!X&nk5i"ژi T_шB X+~3 ~7?◿C6=bJM A?tO>կ~_|㻷ɼ/ǟ| 5}k~b/NEx_u{Gwn_Xe]j4:%X),d43&Ztz3{!o^;G=^?:*}n4*[! fE.ʂ>n5kͨR:>صڵtZ^nXH#6L*ھwz,S^E!T?:ѹlx̞%"aM>P>,7S0B.t"$ y->vVm"zU!d{Mt" Rs @jg l rT3fUX=uV hZv8aA%@%myL7o̻<3%?3T䀽u(0Yk|]`_ r 7<*K#BQ>&p =vǃ{#RnQ(6*BMnic``K8l B)3@MT)!edu]4=:7G!n } |@w'ߵf HCuu MtZBurx^$6ͤK_FHYvLW@hLt7=8S2m$$XgQ' "3Bv4*([+Zj ~^ɠO -0(Hla:ˠLk5:CtBrf4#aʦrn of4R;xCJGԣ^:h-ZaU_}5=!RF2 "hru[W-!-KXUl[EIK5/"t:ܿc7FcXAW݁Taz0D,lBMdv /}zKٵ>~fP'qPO}8VwM>“0è#f!bS<~fa/}X\K_U\szxtšs6ڸ_IR `3I 1u&E4Mαs%P:F?3Av\|OH/ryD!-;eVіWSeVOZ ZM2q+>AJȫkzXOW/oj^xw;_j؜s#Yl,TDmzOmxO|ŏ?o|><3?Ϳ_**Ut(M[g?'Op?_:D6= C*8'R0yvTIi)`KQ0G~fqiy}(&Յ6ݺUt5m+8[Xj{?*7_~]۷kw_5Ƣ[鈗8X}K;Y #%x֏yh#)!X Rf鍍EՏbF40#["{W p>nq$O z(܃u ` 8WuvfoD<0f*Y$&! o.6~fP)5 ;-f`kG@ށ/T}^%q_éFIx} ߭J嫘`=.C%BV&6Ws 9St8To%\ \᠅) PnF{ >)2he<j 14=V]*P͌ )\Xҷ O{6qY/ " )E2}C#8]$UDnˀv k(L,3LXXohކhZw٪]`;xwZȖmR&x +:QZE=86Y̷&>FVo}ҫUU41נ} '([b|`ڳ:sW`$3mǃ̼!ɸ<[f6pHrЇuS_Ybc> 0|wcC,fS\ja1b=qAqwV0f1й}*c"Y bB/;`HafQ6!ӛib?a#d`b V]+#^{N%/z԰6Jc2¨ CR[/3ɸ\>dep;I"` w>M\UXNk8FKTO" jH#ȷ "Ro;N@o9٭ק0gvsvxD_&VqDN_Gs`a2Q2"2qK.5 )'Q`ܫ_1޴[+Nj2&岡E!"\{5LtonQv6?+2?=| I.E)?P Mdk-GE& KگD2Op1oo~^|e+ Eļ+NtN:JwиBTz7;}+_מzuiG?PVqeΩ;xJ 3+QTg>/} /§>??~?O)5A1d׮_W>ϖ|?7'L8_ĥ% :334y`:F!jOSª,VMf-BaJBo VN?%|F҄aBfjpf J.f@#UOgy/ܳUȧkMC>6):Y'l;6~B!J5ޭ;M|eriKf [D~uG! R0蒊orYU mm6SƎ@orA5xy @MlB%0xk!u ^p~4N<;Ӻ"JdfcPttVd^ΙZeR؎t+(t״Allɥ#Θ5hW0ʹNl>nIu705K,G'#:'JV/ԿZa3 .gg{}s=HhvOB8^&kucevDaniNVV1~ UMϽ{ȥqa=Lڅ%DPs3̭|n€=XNnP ꁬ=N#cA嶟e2'Wf#MگhN Nڎ20/rQן-4?n'Ky'o w91W@Im Vz:51OVY4KJcmU .l*[Laݱb 6X, clOg}eKwιj`dxDVz12,ZeϷc"U$/zs-4'.,8ۚ βp9%_6kaunNitFi wX=23`bWҳG hrnV9ϧ3K.ZK,* ѮX e*Z6sTI' t<(w3X;~kZ&RbF0 tGrp &bsIL6חNYuI?sC(BI8zQ Ҙ: '$X 5jTZM"*RNz#aX85ij:9s 5LBg?P@nCb A0Cj5H}QIn=ڗ^#ZFߵ(O'>:n2tʂ!Dm<U\wys_~~?2(@ U!:tU=.^x|ǞzҹW_zsW]pQnXIa3g?_Wo>sw/_/s(9.jCMDҖYmpNUN5*MQD-KjAꡬN\ZOw(PJ.x1fI65ͼZu#[!UyJTmCq*0"ڑμԽ͞U+VGC:+NǠ,-!-G%qm/q1Amܖ ۿ^:+?ۿ:MçHzLlAs!L ?k;cf @P܅0 ɕ'FO ѐHIpk5$iy%SGQh˃[.iNPqu+cYiG"{4SY2}' ]*GٍBW{B'~dVIdE1!BLc3kYȏc6۲c|UPEnaIi Vfd@n8WhdNp6Ij8n+xTk\3\9{j#H W=+Z2`xG P%%IY0g9@ ؛|=Pc4 ;M%GKF rC>{"ln\I&k/GM0Z7-{/Jvn묅}[  ՂvՂjoCSX_aspz52 >GN4ڃiF@oDghP_5 wEhN˰Fl1H+8nn!E: ?JcޜpbHW&pN)zf|TD#綝|m;7'!in/>+4E*ksnؼ olVeɳȟz3z$mbwhE?Ln Vv#*v?jB0wD/C91 ,h1%|#nlk Nd7&9U> YyIG'\ܪ4e6 =1>T'qY>M,c CBiD00Wx"̺E8o8n5idl1aa= X @ȀgUBzyUҡ/[hYO dIL!,.4x/̤넆aEYBnZfEɑ,; T#ɞ`. Mciuc-Ut+: wi:&&+RЧi \7#m\G,#"1tVj88}H@[Լe'A3#ѐ #>Y{&[9GCn2@LtDsz1O?Pr:[BI8!D jF4Lí[G?'7F͏D(~yEyepU79e/~Zc$.Uo~rx _o~?W^j)^pv 9GjU< \|sg=/8Uf-)u0hjv]Lsѳ.i Jq Q.R@jq.>&p~G,TMG8͗>"T}&k]?88>,і/|1j\Žzv*%pXo5ߴ\BA7< ¸"3_(6>Wsq 3 %^g@ '3u#>8?Oz-egJ[ "M_rVJ5B B;Й. d]8ܨ26'~BPQ1V\#g f-_0v$[x.*HF?3 6:W"<ʳ\G Ney90\5L#\EDKa_ɇt^̉e4MrpF4#mb 0{Vfa@:IOM~Yڶ"OI.fK3ƢyNq'JTE G[5Qa0h7/-#{l MasgƘ`7['|NA`!Il ۇzE\qkeTKڏENAWScHݼ\ha)"<(x^h_q7+,ѭ+~0nr"xIcr" OܢScHl9e2)e`k (gֵMŮGP0Ήx Å@(N&H`1Qm#!ㄻ QC1g|kTsBƒrg@T(<+s|jC=[ȷ*6 =`ɬ[niRQ+&@!tkIAeTR^vz:GfۃXlUbsG5s CX椿|XO+2"F_LU.5r"A PQk &g3 YDf{J$.[zI 'G$/Gx-\A/gy,>74Ff0Fg!zeC|ܺ8cƜH6FYL!w&OWeq<VQX"m NbOB]5ZΉI HtyQqkVx7|3_?쳵AGPZ3Ԟ&(n: ׷ڒpBѤ\,2Tj_꺊UdomoN]˒Y.?9^yk_WgutG?߯*"K۫|&5t;B"!>"doIU!j:7 --wg$rm0ia£kȠSZxG%gs99:E[f ~{w9F1-Va^ QJݱX+C ~GJQ!pSHL& qQ`(u`_ Ա~OBʴ^vF.PaZRʂv,z9[ԧxx2!MȄDpeMjf\vԐfɍTŜOGCeٶ.̽Q -JT4sBeDy2zG{3StKH).ݚ.crk̃\]7uJ"`w 8Si2eFȜ+=/eL am0kòvxm,d*TiAC|Եf[)hN{.I <itҐN;W &ܯyuҘbq*6G`cStb#퉾Ob=2MUrc u&cHmt-L[lҸ9:e;3o2np%dvOZli_Yz$'sb&kȽ lˈ` 81$gqPQ뗫 ]QZG̱b k@!ދNIh:Ўx;Rb7؞$.5GYicQSnKOr< fèhwv Zk.4;ifi^ߓg'Tdq y:I1jۿrhj\[> OZYq3I  eЄ:U6`%dvIBXq'n" <Zp_|ڟ HNfۅL@%!(:wO{+ tSk|k$PϏcVEs;O:!bidfHqwik΍@PdIK+'yQ<$&a R^- ^LUryܷq~l ng\cf/` ٯ%tt& G 'Ua=/a Q;öq"8Vb m֙3jԌ y!aކQ+#hS0<>_1$ _1+~- l¹ebk߸j5Zn= st"K~#tM#5A8&7FFMt lz ^.loۏ:iJJܙXǶKxq{ Ks+ZNT4`+Ix1vvaMhsE vгkfmF+$ِݬO lQvt9dc/`j)iٖ&BnE,A *XhU7Fs1 @iNecE3[ӆp]l DgJtq9S\X۶ P V= l2ͣ"ǔ=>j*'04p]I}&oZ ,^`pohiN1LfgM gbjg93f2 >'=ūGЅ88{p7 xӯ-z^LI纛R1n[}8hIkB{mAq\)c%KƵfnܦ& oSV4JT%`Xs~RWa˛ZF4!ȣּ#>J xq !{ǰFs>\؇;Λn? 'j, yIiYc &u< ȏgp\w|nO<[o|k^+0IbxV)crmҙb5EK[g8UFwGT UE Ո:ӟ? _;y*xfbNġ pno{ܠ[O- |Mr+ܴh``;"L-8- h!gHu$JZ(E 5(ܧmJj^IkRI9 <[:KRG3)|Yfqbͺދ-)vSdhE1}2Bd)t[2Bh ͍Q8AGTl| /|kG .dIUE QB|g|}-8lr.ĥ1` | `[5w׹ ([%RzR * 9wۊʑ@zJ;[/L,FK =S,7_+pR|nΨ }L|"Ë5Sqj4+5.FN7{LRpM抧4)V]ϝ'>8825f)CV8 my (5Apw-j$`]>1¬Mpұ ܊߲5+X_=M*M!B f"D03aiX`Jb3ѽv;|oYkpϴr֕ iD[Dr.kI~3d{"t a3>&H.d=#cCNF3: j|,Ilw# {mP9 uOhaC,yjF݅k./X} 62NB#]YoZtoqcl"Lyd۷ʐx>~G2ӊghWk3̏Sr- >x[i)}Uk=Dz`C\ ?ؕF^6HF?@)h ҥRpxѥD2+B4Ŗ.'x[CDlzPk&z`C͵Yy]L>钕Y[ʌ7ew|1.@킴0 e4VTP3լbng1KyGGgz>NMןBIpZ@~w.TCHK-\H8+_vIU׬K5bvs,?2hS4\l*^Us`U3U hW̻N%z T; >2# 6 q0NyxOzo}xx>/~׮]| ]G4mkKE*h %H1Sb8f$DuDt k:aQ&V/o?[o{o`r95<4D"RQbY9ĝol|`hl$ \S&yN'ɢ5TP: z6DB(Vr?fע4ii2npo.]5_K}O[13nP_&I(I+ VGAZ(PmŨ8A+)#TRZD弬ƆI[$̉n[sQLۋwApHG9! A:h:j|̊C[g+E23Diҽ)lbuxhQDd8C?ۘb?ɭ NR!E!l0!A'Q/ Z|=s oWc|O )W5:b5)yF_f'7z`wtvjlW0lUeYnLdRHYmM9bF5xcr:Vb?6F̉$E텍1|<#-+=LF;Odj%qg00voѱ Լ_ 4I*k#y=y4a}}{fU$(V@fEÚkÆc8ec}h[ |Ӈߙ0 CY-([ܲգFtȂL1+$#;1+8W`=1͓1еr^gPܜJl8g'LSiNT T-i9 fY:D%ر[du#k^XH{ iAH\rzG>MPupb_`Y zL3*C-QOd  +@\1Ϋ>:o?OV1 x?kX} Eh1 d/^2F:O=z~~=”f,mPjO=^~տۿZNDՍ5/ c ,̽g m)ήn?m#dDIZYs+22d2<֯ Z;ol)YX*SG$JN`;tC& E9"pX|%yAO|PQ7(=,7 ָ܏ku6,laΝJ8dsZآN>,^Paa4&۔GEYE%bd I|G[m܅S!#õ* ZD]0x`{?c.Vpv]S̈M> 7b3s*D6)ZMCF\XGlv Q.I~hD‘[zr1ρNP` &|E2IU.UYVQ̝2DPVL˿4H\ِgDk0/@-~cƱ,ӳG[.l"CfwdԘsһQ)5Sn_qEl1'tC:dit%uC-]((grvCb .Ƞ6w_\2uo׆r;q/IQgxy yfJU$K6]Ȍ|ѻd8>;Vim捪2j[NO`ɐ50U'2oܼq' Ĵg3Lۇ+[v`aˆQ ^: Ã0"5IdQ}=UmE!1gۯ'iJuQ~}ƫzҮ#EZCO<򵧟qG7TX-ܿX8-kB[ _J9YkM+:BQDd'`P-צ8s92Iu)Q#I*3ubFţ)A|[ T+A&;POt~QbĶu|k|Tp#M6Ó LIԭfaZ.լ.TE_9iY:TB@&_2vR&L47)Ai.v;$\ W"vNiϬٞcx;[/l)d8 v<76v縷6~/{3 b1uX#ه vt:=VuV*;q {jePBaV$Fv4:l^U5 5aSHS]{-V ANT/DّHB^ _VA)?Eo2R Tq_ف)zUNFM'82M{Thg~筷.?O~ _>)6uq=$-zdq)oub"ʦg{U@ua7IT֐TZEW_}3t=>iEྵ1}rFbsV=깛Uitx@,,8߀/D!LPYH8d h:͌XISUGfh|ڛ*Sy4APL`ӿN\c?~ 5MSOvRzXJVGAU {&V'WBŤ"Y } 'FZ !l\  ٤}@b75C ;*YdRi}l)B)vm2 5cKR JGۜ̋^3z_Yr $p't/Vd'2G:nԉT,.!v؁.C4Bd–y#(FM9]X~2M6N 59冀R8*=A!q;0r"3As3Ҩ=} B;}8o'kP l cm#=&} "EmG?vVnhl $^ Qd@j0Y;]r)#yaWK*ӊ=B6L8P~J:xX姂'Q+^utH8>زeukK +~OTݼ+T"DUxJO%PZG>*WuAnlI^f>\`#J[ex1X&i0KdI$N2B@O<>o(lC50#!1gFEzq  .Wt>DP6g1H-I6u|l #h4nR7\s&ܼa|^"Uz<ʥoӤsn&U&휶C#n{by3 g[Hw6V| OPdW)L<0QG$E{o5p2f (Zя} ^hNtl%Lfh7\A88}0[[,fj4T^IUga`l2;zNV@Vtz& ) dEv@ FtI-i:6`RQ0( īKDšA|^7'-47tYҜII h 6MrOh `,,=g ]BuKKޘ 9b17v$0^a?Ԡ}%=ju'LZc kBEZwĝm,?gG$g@i-6'? aglg`j`?1d4u`P?J 1)3&_<~nnDb ]̇_5Ѱ,M7b̈́#N`A?|FoZUԚo"nGiWnYhO1G۰ [|XŬܨ YM`} u4vX)#YBP6uQ}xfwJUdFk24SjP)hZW*>DlcΠqN7s!#}鳍q5qa= +/MKn[7 Ep0ɸ+afZ,D&MlGsj. O+\]KL phrP|f !9H#6wyb$܃ס>oxRnAoC`[r"Z,aK:gql(TZ?+7/%c6/u4bڹ1֖}_Phuѱ3GR2  蠯뺢lVJYUI]ѻBµ.iYs{}|]G=_W }P׻"3uxz_꿀)BTxtv|hvi(v7*M +/ԧ};o_;Gܧ?;>]miKxB~J;`Msa ~:\ơVD`5VbZ } UxxPب>_ƭr*e@6Ojx˿|{?8{|%Qzj cR\Tt>Tj\,<:slxd|}8[QܹswU9Nh"fSO8+qř6}q."#ԿY%|7(fqɋ]qo* R<X0g<|zzE4P"Cƈ1=vNi)bc<158%[8|4RFt(Y+]35LT h ٖP6z8$:ܶP5f+^d3&jfTqsXrM\]MX{M⡬ۂZU%D 2:UwwVf͓c3lso@ ^OW3f.aB9|ej~ؤZV8?qZRŁiBAָ`V2p{Vz hTdQf -b7[Fj-" gߌv YɬbnPx" ae ?zv){B8ifŀ[ 2"ܑ?VfN!zL=?L=jk2N2)j/z $|ph$f3$ofXtA=ʗ"^vQo^ 8Ο2u3öi%N}2^K.]jE7)֖͚ME7s')2CMg5(,joB|{߿#dY%9} {KX8M%0^ ZːO(H*/`F1 .dpSsg.(^A2+pN2jC*}t[գf4[WP:hBOf5BNuBvVe>Smޫ}624XU;{+jQNV+-@*=tyVW>rriD_xHy*VX|SA l͒vc%ڼYfiΊ}E2޺u_W^)h_/]: u뀃8@ Ԟ6OٔѣwoW_{Ǯ_W} hx:g"?[fLsөNP@RyO,bG Ux P1HzwZmNX\XZk~?Eo9{KWuםݽst|{7{^yz\ūo+o|_~~g._6 )OA!S<49 >*bn$ *[G)%|֖{^<^}w~r2 UnwtSSl˻[IS O\rX{|T 8(CGaґR\L֕2Iat$A: K zH_ZbUĎ|EȜ 5c>wQ. @lsQ L~-`3 KGGu^YRbm{6C{xXQB 9|zhѧUY(r;>F/``TN^%4 :4Jz#1N'OƄtXFUu`=L~\m dJV:{}`TdtwEppc !`Gʹ&?T#h$8l&Un1;м6wN׮]|#sp\@*wSȈ7K+ɎV{؝2&WkT.4 3&dx,nksU`i Tftag`rI6T_aS9]Sxsn @APZpԤe#<`-? ._.kB2ˁsARDZY3E(qaHFaH of<;\OG瞒{׎L3얇д za: KE 6xV =3h ZT?:~/+TEx}¤tf(ѼtKs8`y縘:4MVQg-A+<]fL8i."õ|긫ia?D%*13̞7t:%G`[w򝛯;`Baoub紝 xboЉ `n@'j?Un=~+xDP3n+%x[{ۻ.L$"ѵCη-+P/{xxx֝7^Ν;L"G._y/"͜y":UX7ka~(hbĠ)eU3?9H o?Ï˷O?OYh U;qH ItGG&)tJ[>z]9&PqrJ?jӥR*(7lc T}q G TC8jt 8 ]Tz6IΠutm֥H0+ .]<\x>O^tjz"1(6CsWO_\MQ*S >s>IB!d~cqHz KaiE؀ObQ鍪Ta :pP='^\wDY3 SAW_׫QU?[c c\W2`o{&e"{s[v;OC vjsK:Q2z?&S2H;#ޥMLIXl nĩObִY[ݯ=iz!W.p Ywrwj!fQ`Q2y - ??+k ~cWz;.YqTLkٙ.'-rE)r2p8|me?crZaWN`],4C$OI$geFJܬ 6XQW?}_s!E#XH r1ley9:1Z{MIfǸGk5Zf* Q/]*kR>LYf,l3y/_k\e\dǰRKuI+h`Ow*T'kf&F ǣwS`VDd]|hGg˿x{V䧴 73=N$ ls!4 ]Xt\>C>!qZqFio.QpC`kV.2kMB?qX )=0Z?X?JT:']JCὒn`&]ɫ@eG.2gI& 4\Tb ) Qɏܩ³a\ @X$%AVUpPO`u!t-nn-^hG^[[[ywW kgkW/_kUU/>c_?}RR JZx, RjCjf, zQ1:X:u|@R.w @ɒkP/0tG5346Mɖ-ţ۷/~T3(5R KW E;$ypP;U*8s( ɱ*/\~Ja-kbcAvMJX/c 01a^òJ+vkJ]T՝yQg@5~"*T8juM0Jn&.{L0NN{F]A>|82Lf@ . 86Yػ|7eo9$F^imD2OZ֥LXN]YWhqdꕬ@!/Ng teΞTY QiqtY$oOOxEfLm@hh pu BU6OV BXlĝik;>ljB0֩ R$FR泯s˯k& ul,(ðp@(w5rMTlt7 ؙlRUkյӡwuxxIoLFYX,R,W0n `H8-٠@IZ=+WT0&R'oI`!qYA@3!PNF$7'~n;-#:yޙ77ny0NCE(};Y #-x-utq9^xԢ%%6U?w?^;ԕǵb? "ІpQm@8vh pϳX"urUWVd5_$7_|Z<tޣ޻W^;>:A4 k>"U=SePL껐YQ-^I@e:Env4] eqp! oP`VdntP"4VW2T ^iKCM}pJ(IQ ZW_Ĩ>N PV]s8Cܾs"K|jPw9DZXȶϊij/Rp~ 5m]:7NX^ SM%uge.#'sDTbSKʈ>Ugu\vg>/|(;}>IsS(\m̩[oh3TqBbQwt3*6DDL *)/#gޢ%,+0"Ek4 S)8{tW_?~EYhfҬ.*-UՅ*@d~*k3*Tkwǹ`+^~XdPE C3uʼnԈKmA(%S4'Pu{wȼpa'k?dzէZ?=*M#Q4ʡ^(U S6"·Y+qZ[S'EuH^8&Y&6:£GVJ ܀"t5Rm#P(P؛Xn?ɵQį.V* 'wK}ٕPe"Ax ڲƹpp*yg<{ η_2RnФ{ U_$!(nH{ -F20!*g|:O.@ "l@G54VJTMѷ$b%EȲ׮^yO=5!#bGBn?P+?j80]lB*ymB9>!R=l}b?;L_Y& |BJ\AUQ>h@DT&~E8(e uO 5ݕ [BXۯyT[߽q7o߾C2B:vY\<*h誗wNp&iC]Z׈ 28?g3}bSϭXWQz훨֪MBH8ZөLkוOh6Rqɹ^*#"z$ /$d]hpX EQ\xƍ|;O~\ĺ'=ӵ_UڤҾIH ig*US>TѝG7޻[k7y;_~_\>D'AI\"!*`hYC0t'L+˭٥  kBڅ5:Jz6<^̧~JԞB[Ѩ٢͛n=+x+sx:DXu'Sz#T]5 N ?G(-Q^%y_cք@aկF^SQRNXwWϠ3։0DΩ+90t`؉-0A̙PNZB>mP`sn@LDE9ĨXG^:DO?)fNFSbTQ%jzN((s!Ϳ7(y9epM:r9l-td#"̂͹%2e',H$nGrCV/NK^ F|8=Oyd'S杹(8+tLN'hmxŧ}4x! Ōn[pW7==o6ޚxR`˼wodnLQ߂~6W<7rDz&;7A֙iiGxWø'463w(ټ͝S`ek-S=ӎbx`c?uH)LsFN5 3deVh"#Dnnr{900<]G=+ _;Crk~8 Ӱ4ogz؆ͯt\l4>Д03ovn;;7~W X<6n"EM+6ytќUQAY͚73uDj?P e`j4zɩ09NMYՃ,"apXEE#u.=ᕪ ՙ*W2P-tط+RFkT@şV"ѝ~WO_֟ מzŗ^|'hY: Ւ`UQU)V5)'Ł]卣7nV\_'~LJI@2gvȆX˕-" 8^%ڄdALd.zgC $ɍgҎ9 I$+~ohT竜??_>O||OEħz(jG:3e#N@$NmXy/~6|]VPmlZFb+vj«FC++$?gUc(r.z|cXژTG9?’n׎k?}JK{V #X9YEIjUd@2}J8#vAZSOLP=?sBкkX:Ab8Jhp'4H&h-j5=Bliȃ:u Rp?*Fo6~_{>Uݽ O^VŅӬל6Ȧ)fo\ "2iS`{(n FrƼeT_ ee&5(]t溋T2ڬ`<ƮLWH;'*oȂFQ} ΣwIe |q{^0>Mr/Ñ061 Qef/Ji8u ]@CLG+N`~y㝷ܿ[I-(Eٻ#qXBc:Қ}^ &\w-;-g$O" 5oH4J|u&"uZ yz$ho/uC ޾7?19gU~^#>R8/3 ;9*ْ- %R6i(BIae3YZt;`2("3'}c;_ֵ;EQk K_z/_nqg9zk'q:.7ɏR'2ҋKrrTB޸A>P Lfxje!€xx,@_z巿}Q雿_O=Y, WH\*M@7G?mE?? ueP"${q!kH/VUKu%)Si#[nJ@SRC%} $Ƶd0miembت8S|W_?oo_S%hFQOYI.Vg<ƍӿoYiR[   }Yw<ٖg2|54~ \o<[ }[Og>SǮ]/ %g\VuehS}K_zُݺ]~xͷnJJ x2T"V+5&EY$Ӭ'l9 {Po8CιFir:8 d!)l'"dUҩ>(&HKjfcN!$D1a>. KouH~-~G +yƕ>lO>ܳOTb;&_4/7 ?(݇x0HݪII@/F]"cJ6t)cJ*e-hocVm ! Q9-%<õNAolL*MMi2r,=fo\liaT7l+`r˖JK|@ҟUO#'hӈ V P"Jap}6~K'[(kan\*!Xn# Cy x׉sUCno4 1æxCzþCْ>֓Di2 lAQc%1i9!\c tq ;9:l8-aÜ[r|P d!11Vĝك-5ڶ^DMx+* m&0f0YLH133]X{-;}owuɷ1o/.W4rW/ fyOE4N~GMvlyzxF~t^ڭX.f*q l" ~vj|f%1(uYg̊/1 c%h3-bY٥P ;ҝF0c[֮R4Zp mw8{<ND ]o5Wդ> 5f^ߴǏANyRx氢CQ&/g~/"`-y6H5S$L;=0^c+'b xE#-p[LT^JB5(%.Tj#5KRMY10ȯq܍־d9@AˉRGP֯NagDsu*ᵫW똇JWGiQ!bOcuĥڗ !0?@ ]pMZ&z4^ ٳO 5|s` u\O?ORtV<ǫ~K_ꗿ/} \|lFPπP|ZJ*WNP{H{%*T|_e= VD*}Vd*Ub_Lٓw*s~'ShyܥnkBܩWG/ 1ekܠ3/~g=Y\a*Y[:Zd]2LZ=sUTBǯԣ=*Opm4mDcnzН;ZI>Gh?GGuJ6IUY_@JhT ugY[mU]P5Rw#e$3"K/trgݻW?b-fHZddZ JUԮxagެ&QSNѽs^]${/ڒI5kb3OInYnG['! 5F%#oމ1J8&1tն}jۈ=mv۬ᢣuFTiLXքL*vl4 EC"l%kE*bYt]YL?rɭ"64ؖxKd,Xj62us3 VQ+!ˉX[|@Ia(O !tL:aTNQIDƇl_4-L?KG;5XnF7t#ƠS8]?)NpTA> 7SpllDzC\DHEZMĠeb͚- ZNuy K}Oo1qIf޳;}`xo}$\Og҄u( D "N=4G_7Vl|mXZܝ@֦;^?a5 Fм&,c vpg46CObp{e(< $Z ȑEL w^hu6x/۹٫}4Vb  0vj XZl!<7p8Eh mR Uzm56IGyƙ e7 %ڱZxjp 8hv7T_EdQ'9 =@K=TV9㻴wx(k?>)ݻt }Bf@;sk\ $㝌S/dun?{h]yg U&%.*%~{u_[bVAHZh_8RQy*?~x9ɢJꌃDf:Q,]okgÈr=yT^S>vw3.?P\\~zFmaqQ%wkUVpO>v$n*cT0)މ-4" p8p ݶPN3GC"mɀl()0na?JSc& -*P+U',H+/KIJbRr1hW -ϔ h eS"FzIիsδ#&h;aIsY+-=66N2jhԨR jC+7%/G5`@ fjP@RUX'imYDWH'I}KIwz$"7n[֕vu1TX@ :S @c\+ K[, `6iJ%S8^Ha cHZYx RMf]VMڵh<8$}91#3nA8n15S~Xm//ʹצ^l"kuLC a9.6í>zsyAHT^p?YpKaQXADEuLiL)[*MwPv-`lhG#6t&Ѱ?S9dvG^FZI9F|k=rtey{@9MjwfhNK:8ʅ= QM7\u 8^8k #v%77m;3;j:]bM9;Kr3JE ",xf`剝dGML<|-\q&&1!ǐPHe %K>OO{ӽa=NU{RY ?5|ᯕ7vŘ˾g{3Prڐ&3QY*yi6{X)m-WJmu=(*f lWnQg= *>OXvfUD:.֕+Ӣ <i{kfEjQvl^IJ.n(L=ٔP߅#37r,75 ~X]( nlQ*<@Ym7WγDCuz5Z}:R\aL '|OzUB@P"\T 9q-T}^zzeRͻNP>$*Lo_WH]zuj >ۚwA\|} >PU͏=w^k}tÛ}@ѭg}zfDZ?x\դz\_=6BJX[e W}BAG:r6zQ?Ss& Q+TP{:GL%+YQB:$&,i%:(W ^&٩Qq<MDcufbB97=JŤW (ȪGTV̅{g.9_{?AesWUswďBoPé{KLex IW$Ly\5b5z&oc-<(&W~=`(zD}әzn(,}tu)ƞ?=W^UuGw*hE(Ks)S.TǙ dԟK (Yj-65;gi7w:p Ya 'Q{4sUU۾ԩ7J> 6;,Mr?(tl2bS8AqxMuqcnwiNy(.yhj>0N gSM YEöjc,tRG/ֹT)86o&+h Uay^yctOORjPOt.3^X~ W#0mCD _q'2b\Yak.(s#bk>cPG=4 . ipͿ֘l6L'&5f޾E~M;/ffBJV޳3h2 s{s!<=  Zؕh;=cl-aGq V>!Ѱu#ce.8 )Vcvg0+HE,4a mS,HW/{eWĎHpH&V˪ P#X8NdSaGs7ϋD ahD;0$G*m  "Ej "2PET| DơEk[ 70uD擦;bGxMJ$ua/G%]3"*]s$ TymڬrTXS-0 dST\\=7hEc!5عNAZ9NwVj _TbT,@xH6lJmuafx)⦯0($n&SCd< 4P%xAuŐTP嚙Ľ =R)7TRqr! 6es,-~8*7 Z=FP~anRtGz}i}ZFt<:F#V.^RQX[!5q 65fUAkQ- 3klm6Լ2EDT46'S`[&*V\SM: o (G~hzQQvќ8 Mxy2B4C60N]1yIe })]f[ToCvbܡyҲ=, B0\%@O9Cɇ+0"WN!BE*|-ܧ1b~ˇBu%2~P({FFjl:Yd7^\^(k4!cj+`0̏懀cYqP21p-: &ߜaM8S,f`s*<ㆥcdyjt607{1ώefMƹX 1I1Nh.5|/1}m`[[4 B c+t`Un ukg"kׂu+֝F 50A?OSÊDm<ڼTAI9<9YOG5‚Snmв{ ȯ]Hzwb]?ڤA(bj%8yֶ -MqQ[ z*SˢcCu/EC]sQ0N@/%A tCh'I&+%_CLX%:hU0 G6;P­ȯCsWD_璾"2! F^򠤠h(xC=1idOjmmGЄ6`]RE$ M@la&}[ z8c3]P2,$=,=*L>Qf ŬA+FnMpPq#ejj !6j nx (EBNvwF3Ϡ-09[RF߼;YXY:'ld>4 >U y!VH?{嬸['Z-H IT'\hs JZcukz8ąA2X9CψqjӥΧyZz\Wg4&:"C3i`N@*u-PaRa-5+em9?$J/ @A1; G[>@g65JG0|xRI<!,e7 Ċn`Fh~0X,-nM`X9'Oq#syCdvbe`^5 ]ͦ({b# ZƄ;jF՗Wn+oD3dՊQrTHbyQu؈ $n,w'pیo r2?ĤD\d ; E0l;r;%eҥeDZylah;sߨZl Wظ[{6B]3j Ν|_£82 Pl4^+x_nn]x:i\$N1!=./>!L:>VaHp 8V6{rDG~-j` fF#9 yB?E\PjݡbZ9; ol}E+3];pc0ED>>0\O/4?%y=mr1%aOqmyՠ_W#8uU)LP"6;M?}/sTO\ x.5Xͺr͈4 'queԬo=y(9OƻiNNy=QF oU3 T,sZA֯@Q#bpS{JZ k$U4Zsa s 9i`2ےb! }04 V}u\C{PKNyDqwLE,`({)P`'I +ncJ'K,,&/Qޭ, NԨlGugN'& )f#G[p!bG؁h p`M^|Y?4lyA3,A( `IBqrKG; Կ4'_^b)dMufBY،n gVMFe/Z4rh-EY/R@r(I"\m*W'HsjViP'?VNAĨtvF'g$S!9WTr$90"fdxP-Q㚣i*F!mPE^Gl(hݹ2EϤ`?$ǠS!cz|MJ"HTOc F X5fJ>z(\#']fbcP 5 tTUd;Sb't&ȕRb <@D]"%a8lzX@3)+io{gKl\ l)Dܳ&Hc"y}!Fh/5]=6SyP#4$XK6M2辱D0Pb[,6"ER,mɹDy+f!Ht Ѵl?#9gĤ`ƶD`AsKM$!NJ ,=as#wl;4"4' +^n֠sq>ƻaVۿMf8^IOb} 1&a-y`z;4 xm Mx絁gbD & NZㆄg\-2B> hڷ[čXy#ƈ\}X( oqq#}ܑoʎ #28d+d>L[)U?U9[j Z/5Q`@iu# /9V>\ Eb}BS(B4;tI0eӽ(L<"Ik \2X0+شgu _5_˲6f͆F$7r|}:W0<0*aiKMB+ 1e6YJdE U*P?{8ԑ`N @(t`v<&?U%|M`=T %S:=nܚ<͑xhu$?^I%h2 Sq𺽇S<2ad VE&gF%u'G\WGu2eu_8:8l#y_Y߷^mk <>`w;~ aNd$PeOM>)i@L=RΗWH!OZh=!`zB&-&_)Z,8Qf]kéHSZ1xIa ]oUUc:15,O:Z] / ?pJp$=L#0&PRTu.dTz*tܣJԣ*QZMjWg:}" Ѯp+k 1{~؞әXPI(+Kʘhd(1B)$5~1Dy[n<^ UTtX0obs.L 0X+ OWzJ"z5}=V%fK9QWzl:D`ģs-;,d]5ޝع_:VA60c1fR DV6y JE_0"X=qrb}9_|v)ҕ~Ƈ4!DrxN+Ij g [њ9"Ŷ8?{R3] h󧧉&6{5]807zPm2CpWo㨞Aǀs@lDmȕ?{6rk._HyhvƘmG:vsf+ m^x㺕@xNb]if78CX1nDtw/iѻ 7o70+R,ՅJd3u6RiKt]eq-9eN{ټtS['v7,'b{ -KpzuTڢg!+QCl[`gLemNrd'S$Ldqo Z-ep$1t&Aߗ *_m|o'l}/+''Y͌3^8\cQ;}`oE=ܾ>)0:UM̌_Muu,X",zV?T5E8zʀgZ_AYldGOd Br8S7 FGuK0Yw< N. ft0}Ywh袩\{5 =rxSeȂk{vaꫩv]1U.T]D}e\GL$,ak1xL -FcEćPO*jȒFRaC+O& A~*ȸɬ-ݸCBᕺNE~`ߘi4$ ^;b][ \S6 JMgcB#6i%Ν;\ʜ\# Be8.恲 TɐҔyF ̳N+U.@?r Cqȍ(jj5T_c("Q>uQ]lvi|ECFaVi<t[hZgV6NfQf*`i0kp{?ZBd- Ѩ5SÈsm3Y6au;r:; кozuuRcz<`rA Lgbpg=j"RމmO?{L\0QVΨ<bC5yLk kwb4Zô|aluCÎ>3]bv_f-?LK^<G5v_.F~A> ba@j>ɰp`(0Y(@-6,D Y44>N5Nf /AcC$|?aNdL0 j4wؒXwԇdnd1 SJ~oFyvDlk**L Z͢I+k} ZW1&j ᥾юm' 늧HL~60bV;jR6~NQT]< '(^G%{W!W5G.|;_wA3^H¼~%ca xf-|'%zBxIF(NSn\gk: gU Ņ1wm1YKLHaNǢI{@p EwCuܛ+P1AG]i(iY5wZ;ݘ('YAE^/#Nr] Bх.Juui3?qYTiHWj4B+=7c$〽fFe)^Ɛ'[=7>^BƲhuhRbcw+QvEXt=/+,Zif*Wԫ  )C 5bZ4M") W͍&R. De+v &0 %6 Pwjq#B͌aQWֲナ3ύSFzXa4ݘF_1 '| Fkh=nw 28܍}-qPsw>ƒyDwV[;-Yei^ X:hD\/Pz n!=e\ݨ==Fma<Nf/F}fJJC c#u6 ~z:2`nQ߶R9y{;C}|6;__.Y k+(FyDhV)J:Q6𤶍OsCĩ@iu>ntJzI?(zM.ĺl;@Ø#\BEM4.x1+992$ļv-QCAcTUB0%k*/Yヾ*wG5اpֳN{@)o54)l:DøN a S}*yՁGw}p[1gen&V8KLUSPS8Ι'97LKJ o'ҹ1W.qR3FaLj)l=|q<wko U4*׹z_ёX`ujuf>ӢR2}u<Ti1'c NbWT5PR6CG )Ltzl:I)l@4 `[0woMC3#؊YT![Ly4bt-3% ݺ 0'YGnf s]Z?T~4ǎ)zT,h1鞜"~+V(,R+Mq\*ItjVfE_vG Ni|3 `u!(Dl1am5ԙC2;63k ސa*9Vol) ^.S 큧a6K?:,I~6Q(csފ}+hi['"&:0m{e ;ޖlȍ(}YO~4wIy%̖巿 6һ# QaPz*I'Ki׷kj hkv䔮!JgVdpW]slmRkJ] Q$5C2X܂)}#RV"{*XJpAy<۩Ō:2SElEo%Þ`5eCռ;'WO.zwZ[AHHV*gSLt43uZaӞxg wy%`5 h>TĶ/K '2hS껂3gj prMO[OB];-f:e+ͷ9޵v+j! XL$KI 7DqWo65a,g:ۢ?QX`e>Dzv׽~! zD~XϰTHWGLxv߻7{sաTm>xTXJj)MS*Z6GZ=nqH^ VB!4T8lSRIvL%iZXU/C=5O,gg&_&MªFGx޻'5NA}n21Q!/%zkOQFGr%(#5gsrk5d|h|szO.2m -Z1qpR=U<[d_RܯCOj *݀Q9ʃ,} ;gCgSTTl奔q'"BKT/E yKBF՘h!ϦsLͿNR4#}jM^H9Uۣ/S0aM *) = f+pŭW` CSbV0Bk2o(v{IL(Џn';x ;ڐcPi*! J,Zai+O-`aLyeIwU6/wiwiY]LE|`+D$_вɖ4Mp1+̚C"a [t"ɡ|ҖRL6[VqkwiôDkso*Th<YYЊ70`Zcn[Mяjz{8{ӝ~ hA >F=Gc53YLSCT`n|Wf_H惦:~.j]g~UkC &*0ڋtYJq5E;zɜĈ!fލnq꿖6˷ Ӄ ! wOB!g!;x \SԣtwInfyy3Ź3S?D8!X:,S!ԕIP:î&d` êh`uu~F(P-(⌤(xK(j`320h"omHK+i9Ad&ʽ)jQv>E@KO2 w.UMcEywDB&Aۄ:#Q:4Ktw6~Ƞxk'mD-^mMv2L9"eD1I|̽u n&3cr;{Yθ J|Kp0QN|3C9 YuO}d]~vx֋5ש zAǨAe`Կ2mb!fwՀ,hȴUod7vԉ{[Á~i^,{6H'  La <[>H@M6>cX.>|\3bhyĺd]'lOOn6Phb^Q,;hk';%t(Q61`vO6m&ȼ56]Ynh:5 JP=.gֶyMqX0Z67zqxPuW{68JQ +}@rv[[.7B$Yc!3I nU|7G̟91LVNy )gLhO ?%K@VhtlQkPNt&-"ȑ˃ AnD8&+r"lL%RM׎mхMl3@jϤ\? Ъ55Z,<mO:ߓrp\T[N0gEɳƌfyV2Z7\AӞDFMD6 :f~urgBV;f:oX28mdO0,a -c0rdm' vwCt,xO2n!F@)m9z1h7r3 niomqp4S Now/NB.њd00?JNV 'Xme Wwp9RkKdxjnՠ ,huuuzhؑ}Z h{0~ړP6AYBE@JkkަWQ|4V =Gxk,/`k(de V䧙)8;ƳbWVDpC"n5 \c79hW0p0!@^P[eA5c[×?ıev] c}5!8 l_˖-XQ{6Xw eMI3-]Ҫl[5^t{/=s׮]+?y?*qGƬd9p*0`IDAT~fvW ݌'0tKW++P*'ZwS^f>\c$\J!)xF=QApԁh,]<<5g-h㜋1zğߜ}ع̢ޯuы/gIV򫸷ٹR` U0]cWbIL0IyI!* @}d.zc=_?NN4G#2k FE҃ZAt6o* h*kkG wÙ(^-_Z;UsPS^8([ޯ",P8- \9IjTYzJDZ:`8JfTz+rm6ǚوH97AF%锁}xBAʓ鍊17X-qKDe41W^}ђr3fC&2r˩cpC) 4!Hͧ؄hXssLх. 񬢸EX"01$6h6X?3 *r #Ll!blp< vtiF4J-<ȣغ I6JlF6as/Iɰb۾Ynռ XF4^]o]hl4s%a{3= u2f5|)9} +d(GIbw` چޔ;`߀gi'7v!.}EލgcG;[N;˰h0 -Odfɳ'2w.FN`1C PatL v;v[&,O:l+I>$Ztޠsa(U[t7pgb Aq۴XDF˖y?'㗶NӚw'7`,o`Y}.vs@;$@=Pԅ@|pC&/!H["TX~3$X2B^rw ;BP$(qf]S!4`VO<PN1ia,j! _ q n21 ½F:!;d)P^0aZ(GŌN}XnǨA[ݫ!ƈ"i㙩ZZ9A 2y&Wa~|_;4l̮yVs(!sAKaN`]JNǼ0A(V8dЅfG4 5No3aڻw+>RjA/֔ZfOܿ|xʕ/◾~s|=O<~n~pHSK4Ǡ]C"=eHwg_aeC4*lUŇzT^j]i:n<8_=4[ mUPEt&6l [HJ6WqYtEe߻zCJ%1 $HJm71A"O-0\8Žl}iA6RPk F L,E0t.iTD6Lsbf_ph1 YFrQbV8`mD#UZ@4syp9X0fv{)T>^%.Cpsh0P&̲"4Pڑ~ CP xpڥbuMbK"vXeY#XҬ|$pFK%(-,]`ICQ,/CÀLlzfr=THz#8tZy6?G N隰.aJw3PԷ紂 z:[9FgڋHg" po߳3Aܽ l|mA972)݊O3Mk˂[[X ` LZ &30Fe1!73L&1n[xBAzec*[N!H(wCƠْk"O\k NZ%,nntN0~%\ c9ΌId! F C=uݬ͐]?͠ΆaaA}_8ͺ1[vZ*eDPqfV}LDWZ%z=d/-Tv.F"錌vwql]g*Nq],^ljTSkPbveiþVyٿI.jE%NRZшQlZɂE?Y"A(c?)l8Vf˩ܼj07w0>AX,Q` ё9,!r7cEq;!3$f6[O5pR&&W/¿Fd5I\wV,6Ǣ=*:NɆ*Y)ȧ gVu|+`U vҵמyO~_㷾կ߫S+,M4!ovƪCݞ c Mv a;9 "HWT /9 ϢvsfK|RUȅD*'2j:P!6 m1x_C9yt2g[)>FT5sD<'tTл#`tiٴFԌ <7mrWtEи- [Zc\vB }O LlJ"kĝJ"P{Tm W. &_0ٙ~)5!k@¿$ELnI ( ыʃ,A`ITeH% SQAm=OI.`)ň/!~ѤҐWDת9sA{f0k&ۊ36 Ez VI59f"˝=L z-WfZ$:Df%(R*sއqfm21(Y>5}JB\/j_NV=kݭwO<_?LɎw(@DbVW._zJޛ? ["`!3gPd@02D8jREP{ .\p v )\_Pb}w5X "UuQ#[%QN'Ri%-rKSuZ÷8PgfF qF5GgDJ*i_nY]Mkhh1U'SQ/pAuQTT|d:VTs9C^EwBf5xIWPYP'.'UWPDP@E T zSp,#W: Q/ΠQQKa%u]8JiW,G:)y$aؕGSۣYIb{%(>8VGI4,\$6X퀂HB .=3P\fiTV KƐƲ6=5sUnEIE+,$s| =Z`qap^,sfL{8^KA25f~yqf>F?a0nX(qY۷`lmBe(߲k;mƢqo.IDNm0t`nsocsV uF6) &WP`E^},wیCϱ.Ova=m~mnͼKm6ZT~O3񍡐dX2Xg2W) fE;/xt遺_z~Da{D4gs=o=灢wqx=!0ɺduLRDz]`d樏.d.1I;-qف0\q'+a'^;ܗ\wB9w8GwjOwOʃ:4C.^t?pJL. (Hnh"d2JPƤ2s&OGnù ǽHt`y U**7 n9PڣJk.Ds%_C58G%赠@.^ps^(=쌯hZ L '?(=-^.ѡy^C ĵ-0AX)5a%ÊbO6l?:G8>{laG/?'1A16j_7-?,10xM Ohnl"3Y㣦ƨ#a$)KH#7vV}5j#̛s.Xˋs>"#3H:P/tfY=Tnpq?Uob"$i E(MnuStfn]E!vN"oYc#7?hDGደQ,e(:ɷx9\TrI/;&IM0hʈr \sOV+2XDKh1ZE3wNz-\􂄠$\{9&߉Qg]Q'D"^a(?Yħ +AY3v *p!c S'{i?ʈT _tc>Z-珑/FE:г2#Y"yזk g8 '=](cl5gI 7gPEQZlOs;lors~.hdj [5ԳOV M58N!)brqbEb}XA O /cVfESE)E$4b۸-,UUJe"Ķƶ3#vD"Afnon/..O?}[\v?կ~/?ן^v[ݿ'/ff66R=D遾}[Q1dUXT?EA,8LӍ)Qh5q"Kc l"$ zl +jߡv52J ՝GBÚAR5@+Ozb}m'=z2l~aFa``Plll\&ӱ(1S жQ5 x-1,˦+UPBJHk%pj&GJњJ#xUWɏQzB/*Ev2U8Tz81XHY"B$qiY& R?+٪[TiApeX_O(U#j6l4AN1c"Sݾ(_SrīF{PtXܭkR'eN-e`@T\ =N.@Tv9MksnZ/]u9 iF $YwHFNMHLBAsN&QWf皶߼Jʏɱh8bBExG4bddWM{ϰP4dk+~11jo#ׯAzR%%\D1p{Ƹթx)F4sLl1F%B^$kpy@NlfvRg>W [5 $uCWq{t+؜kS'1'9V;n84JU0cYlHO6_G4GZ`YoE: I)K5 pu4R !0S]Ѓ%:p`#bhm0M?$UJAB#+U%_.V(C@DW2yG=3DGGU,:;!=.^=DP+B5ngcF0mqE@sxT4(c ev|k"E N HW!>B(t]-͢o4[SzL(;y.h8L c vOV?QwRqiֿcB Y`Ck'g4gjr4-3ja(Aٍ@E^Qdo'U+F4 )t6&WUXS\W.6fWM`[c&6,1y/f6o?`E pxxpp+'|OujB\)?t)>q$̶iY:br|f_|/?_OO/??___ݿ??ˏ>͛O?Y]Y(/,B|vDؖ4.*Q<)WR;d^~̝)Jgڴ me@$*8!.aV/qLM{0CyP$"E)iP]%ulp:2 Vpnt{#7R͚u͙ED} !M1ʥimz['!mDR`Tx$Z#ᏅomZbc/1)/pܓF %>}2-nVW1(~]8;U r1QْW k֕K^/n[ śR6r)\>5e li2OK|9O~Jԋtk(Ʀ 󍵣j!_մsmrSj V#DM IE1V٪O~8@U ^UDZ \|ԒĚo"Wa1\Am_sZDp#edݺ̖ؖ_[{ `N]}>&,nLAG*YcQ;(B~U{2*&Rp|fT}1T_-]~ٿ9(zQfD;Tdg閧tÃ4d\Ȁ' =cxAo_.n0T~$:84$tR8:_CM?[ZZN'N< C _tϜ()Ou|&I֋ \q߿wsgx#c6-`xǵ7CЮF`y{}SNeD1#++(Xw^O>ǿϮ^_[M ?y2ŋٙ/fNO߻w;o߾y ?]W_~3ALOOnlomnRz̟fPr LHBnm`~o ,(Bʝ*CS$`pw=|&.}o p ŖC[ńOh G233:tx uR>2ߢ:ϟcAh ۔?w$g} %3oW.R J x , +v@Hˣ I^ӂO(m*C˃J&]k^*МM6f&*mYIL2ڲ@BeiШR‘aT1n:. ,@BhŅelϲwRjBbu\E1q3~::nb#-bzRvB6Fdqd(^`i 7 zR"jn>^RBT4Q)f~v{H OlAu#ZTV9d 1#L;KM[(Z7닛f H-6%9D?#*x-^$ \Ȫ݁^B@w!L`^zs/lrUӣ4 Z"MiYY4`:Ь)°);Hx Er<{ѧpek-7(lˣї2{@xEBM漽8!pD T`dp0jzݤ+P~!bsC`Hn(#QR)xцPHqVkyc Of9ѯ Qc̗eZ?*X/˷ٴJ;J.k]sPK(RG h,!T>XNs&_# FTL2ďf5lh \d)q0 L/ĭ.L<ϯۖ'ēC#C˵FěEmXF 8iSY\|~ zR|\R Gl";<$y"T4К4w!$PIr8]SJTegUJ@3&OcqI H[sn X*Bdx},x4#@@ЇDA/ ؓ$rms9؁}& %x \pSD>2{2FV& ?F#e+.1dM>:Y +A>>K wukf.Q7"+ى|^I:}lF= 9Z IjjQVVęRRm>I?8*2mpMt7W „UHKZ|V#<|\CEZfmRZl gV{ZEPG6 7$zj~إ\5 7(@GEf?E#tSî\* *k;. GJK*zC(-F+0H?]yksUS18!$&Rn\Z F2۝B[ \2pEHHO=r~us{umSO.'fG e !@v&9] We Pu'ZQ馫DdS49[-=Ka+Al0GF 3!H|eRboEgϺp;*ohcX-t%":0tBSG0H5,7^v} "%Bhg?ܠ@'n&K= HXeQBEFmAİ,V/RU&+{۟O(P0y7 ׂE>nՋVhsNl5su(Fgj+[0C\4\fqyr|U h6y&xVC7Il4400֬RjE_3@A__ 3}Xw}zll|ee>O>ɓ3 ߿KyW;QޏD =y1:9v㓬yNZjhhTثݻw{z7ύ#IA2ULP\+IIa,6 uJ)+d! 69u!BrX2I ԙSC= | H I Ghe/}KrbA )>3v!#_kBI'gaBl$1딈>a4D|98Ǫ8 ?!#tXa_Lnt&-Mdn)NN"caV!2^O!raiԧp~xfKԵP"TtXY \ kVO)P֗hD5 LVj@e.wY${wj~7ͷSN1h.W{Jc{]CQ cI('&3e#^3011<:t)A$gT=-=4K+ԈP# LI9h+MYV^6 \F=֨FmA6)AL5-)Gfw!v&Aح-QTPSS7 UXI @atpGRU >U杚ʰBh&BČǺ;5kBeU A76 POt#L@*mz>-7c|kl3=Ы[SxZWI+a==4C#͟ÎqCֲSOc+1{kda !qa |y\Cz#*IY4|sMl65>44\!TKa,ƚu kYG r!ki`/H=69TY&"`OZs^Q;LmxDd2]tl7z(Z܄hˠZ[/,UVݹGjt ~>YhZZsKE_a+Hjd "Cޫ[elFJcO-*IEE4K/iN6>T v ü9JϗǓ0:5RhcN5+$~d}uOhRF!#9/)Z@MpA0;6\ϭ yZ%SJC&q^d~v-l/0mo RgŲU1*tJXk1?qf9ڕ\+~?'ޏ~[.А8 "U>#<6͸?g???o_<:6??~/G?3sHT%2Cp9Ƥbmsj2f1a,jyKEJFt|h M)f1ߡ^r0gXZeGԗʈYQ`hNJ6|mQ ]"1[yB4-ZFcI6+&d):T&WJ~+&|R*d">aT8SF*sHoKlV* ݡ'u}X Ĕ9 0 Ro`_3.$~B: 3ӱdFrncbk l쯯b'#2_*#bcOTޤERQ[&M]kծ4Yo֤@%<$/ߨ=(5zHob !*}4\vf<6D2%`ᦡn2l6Vr+ƴ9P|*$"fʆ V%XaoWŴ!I@\xu:d8"31)( N4cxz~/duJ;ց=4WDnO(zIfFkfKAD\E%>=0Ss\K9^wpzS -ٓCTaCWZ&Ӕ j^$e G"^n8d\02Qɢ:Acs,f qȁnpt2nR!"CDEX1;-kq6̍,5|/B 4 0 (9|m Sv64P^hT./=a%'P)u~ z F=ƷlGs&ՌP7 ÿ FK EljMPH=>ч&Õx-^<2BZbV@4TISʿЬ&q5 }# .Qj8QAfo#),O2h ,(zpsdNi`kq6ĬO( !KOhJ35-%ƚ0cFڑRSn+ALEJ xjF®}^j88tp?n ?r8#>dcc,*T܏B'¹مO?~qBÇ9C,8v(By&>kbnc?~1)Hp^]38jvC1O?ࣉ qO'}'Va@$~ $sG[^RIhݰo¼x)qBLh.xj',$թ82D 5~3l[B _!TRkZZ #tHԉ~|ع۰ AB]b{~rT0wǍbf<'ȇ@!K}xĩZ>UʀOXAic adWo2M>b4X.Aծ]JZc]L NMxg.x`(ޢܸyMTH[ibDx*^?23tRb{6O~R߾S~׏(%Kpt` ov ll$3'%n@ w"NX&r9hSģFG˨JJ a`M( ȏ%A%CQȫ-mRf'ڃj5 MRERRV+cOhWyl潢Fyrm B ju^(/7 Oa~H)&Pش27/4`wU[\ EJx_KFbk X.[h]8i5%!d#9޳ǯ^w|ta`ͼ#,-M aF$&]Y8aDq[M-O-ķ&7o.9١h:1QCo։z81^ƞaf &o/ZM.ݬ,eKFhX—Z~C'S$ƴ3 mTS03{X*g*ܔrqMmۨ Ӑ0zrn,_9Ԓg!FZE*{Mf`bHh˓ {} ZH-CȤ]9P0sYXLĞvb/F^tØZ9cBHGKus &Se(fԇ+ZBH@0fn_:hd>A}XƢ`@|S3xpH̓!б=,^f2W^g`?WؚSC<0wp`ľN7}/ 3װl `.ܼ9qgwin/Oo? ,Xo??G?߹p≓':ă 4gNZ;"Z|-qyF>R 54>u$3NBRSZ9/{|:"FQوQ$c܄E2?1f4U 8-f:fY­ : }t-¡1uG~ IAИZn' 9nrq0( a0nSkU̟D|qLx6  #Vc[0ÁN\kv9s߹borG죈6%8&71>zS$w/oɳBz졉iHPp *BI@mbM̨ ;'Č_14xFep oN<977~/>$!;Pw7щų hbM!DzQYl*ip",A?HTիNFaAl3,_ 679TDf$R"<'ĈaG/N感ºA I `k@<"N^7QنQL VKRTѺ۲L Kc[il0Ĺ*!Dh*&w*NLc 4'KؑaM|iCܷM-B1!J-?Iazf`LmjwL ^~ڷp ?`ctxLP`h5BX-b@b'!F rlM)+n}Vm`* 5ٳ|>^@k{ZaeDQY!7olKbU\[Z>Xb%OT7͛)F BקP*xcWi5(5@PK|يw,brepP:7ӓ9RQ%vtB= !&:f3CmvNDټ`qYB'\CHZ-{-ssVzKZ׌JTA!0E<p'|MHRD f4Sdxvk rN3FPTm-LœQAXCf0\oP>TG.V k-[*fO_D?dąޓfWKש#?XSŤ^oTʧӖS.R62Խ_iD-tH*$k`ℑg3MTq yJCDM'W9F Wr?9{,tҧjЦ:!ĞjՊ8bT'xD(WۻqrPL2vZ8(Cĕr m -I X)Orr9KMH`$Q(XN$h}%u:P9/x76 !5Kly_I2r .DC#gVVױqh#&?L[lA3ϸcv'[T'cDq]K Io+ ^p9,}[\\1 OҁE]#._y?엿7/`o}t䩡fUP"'6bmBz e$&z>"AAc>:䪱fO9u4 `~ie'ZoH kMO?vOC;mnmP{\g_u\_ZXs ڙ{#>Ϝ>['&'D!DNOX›50~4yR X6&`k@sCޙȠYRQ:@~iueua~vccu˾#jf"6M n%w^B$H7 WlYY橢#҈FbpBWkTԀ>iR_hL7~лIvB TMכI<:&VcgdO 8jm@y&s/J l QCM2arJjxߑww3r/觊131":Z4NV9a*1 ^Cړ^fPZy{M+0Gb=6LHzϲJc<{eQ]q7Va*2%ArL9~609ʔ@=kp Eۛn(ēX6;%87 6mPT|32"1.bPB >֣cŖP"b:B]Ò0hd;nf퀣zْCOw]~ kbxBdP 즒Be=;8Lqkٴu[{ .xBM UR%%Hhvhv G s K1XzyrcE;?zk˛Ŀ{Nt8gOtj"ԝl}{쁊pj§z}Ro+sKAK;yM 7DLXKwX[77as V`GpQvS& osjmD k_D# Qp~#M$ҁ_ WQ?Yܓ5Tk(&MQ,}pMT2{V znƆyeIvsQ/]0D]/J :@ʾi]vBG\'db6Z_!vx6D6|yaR`En%D%xV sJ/ɝ춬6YϏfM( hyӈD|'MH-;B:~ő`@> Hf%e08U@y+J{eGc!@pp83by C)䮒e=,M4'~#Đ[E՘Š RA_='7eQOm&:#=c2NOChgmmH/:cG/s<62D(sCC'O#]=@aIXp⾊fGK}8S`ҝf^ єlؿ<"7<q|i#c\gVq},u%$Ne _(<9r RbM^g†K_2ϢJ^|2qWM9;/Lkh|HXMGeclfCe9zd <@t8k _bR,rY|) zc_jPq\2ht["֜%I{o({湴FI"`bAKh>v+A{ lXŵyDB^D҇2ZL_F fEc:zOT)U )&5h)p"v,ɸ/мXnvs"Mc[:aXUsG\A佊J D[cU28{W"@Sf ~|@ZT Vv1Ur5H5PɄ[/ܐD}˝ (`Aޭ*#<'+PqV:oTx-,UO'=z6XpA6n 4tܤoiN ^o^ M>ۺ ImD'DݡIW#†y.@vQ;V$֫z jJHkJeIhjuSqIw#eGRxxK8izn_Kb[;9v[XZh0'>,H)aZCw2bhY pMc6run* jpцg(Xn(Pbh4A:F4|];gт>ֳ%?}6펥ï R LR[='\ıA @:M.5#@xx;)Uȗ5OV4F&׺FtXB \<-"͐]Lh 3pUaYWzB,\gHDV0.nxKLr&SP$&P)}<H,1Aז܍쏸M3">jaq?a H>@, gOhXD4DvWnv%ɦc$K9{ydV hӿ~$oݾ333D V,,-/ǙϞ--//"^ZXX}˫X Z\^B~>?%\VQ҄H~1D.~„9f*~!{@L'qFJXI@T3Kfkd (\4>0W#0Ν={1lhZ 0xo>{޽>|;E @9zEgqen~an٥E~d44)&8gxxR4wk( W)qTl#sMb>r:RC /XZb Pzksb<"e`e-O>نQP䙓'=y@ ;?6)xu^Pb  I؅ժdݔr];n8 K#hVƬJ/6MH cdd݋U[5j@~ 0ReFà!Z|ZEPM0(BFAG^V"mx1 },OdN6p/] mS(o N3 "- **l rb{'ؘo#$` ZZE"*8D M;qxěiu,LP@̩trHS @{B! )u(SؔqwGCh/&GJy@ hבF89߇@kfQ ;p#pFӆW4x 0狪?o<l!dpSQ]`ez5N@mr~&`~uuytxx|bc1ÇǏ:4?9z4ωcǎ=|:+#A&/`S߇|s  x Lj*D9~0lln G%Kyhr \18*i<u_"VeVN_ӪrPvs̀3a=γhX¨ť3Kc((-9,bvv(ZN866Ҙ.ɻ 甁樂Av=Eq3- w܌f R~"r&,N,G12B{M2֣1mݨd`q R:@og X˝[?oc6~ eЉzU,]eoQ5B5c~4li{5̔%lomhbW&0FHP [5(a PZd[6a]ޕIh|~K'ȵ{xѣ;wmPӸME~ ߦѲSl^ fݕL*e[[vq+NC2KP:")hU9$9+,w!=AekVތ[4(hqyL, DaóCWꋋK</uɄqic;=_E,vq4 C&=?)ݰոZY:z"NuyPDx `z/(zr@ȣ3"{o/T؂:ёgn`6h;p,8NP&Ko'0˙IOK2LǓ/N.$e%WDȝ$&k90>ֈZZl!-} wIh[[_[:$[&܋Pi%4{bB58n.]Hr ?-X}l_qΫ+.]xߗ;[Q4Ǐ}ٓk\mhC[Mg\L7a F%OP'~+oc[ w@[L2o܈ !G/ g_~͛7WVW@7ολ3gLSV{L"ք>,Z1=&p*P s*ΰlĆ|$b99  ⽝텥ׯzT(`x ')|QλEԲ0$K@mXǏW~Btǎ}|;q,HRfQ>ȡs"XN8,]w mlIOy1$Җ?ko<$ E+d'6׿#l߱ `F޺SGTKp\vc~'^jFPa%$r*rT_APq(IՂ&Q-꼴BJ8 $8( !VT>䎒Tv-6s< p%Ķq)S x)Q#s/ ]`F Nɕa't؉H [uX;-v)NزsjqyOb%I'hDiU0ѠSw"ۗ+05DC-=XT9PMځG&_'׸ b-l R<mHV<0֠vI6 =ej}oT';jZ _'9a02JG*=p`lxyzǵ!n$nSţOKhx֋5jy/ qF^ FPOϋ9+cR pp:t_+ŷu;=F0^G.Emʧn) j駃浒n/f-ëXPGB\X-@ }y#Z?hAn (A ǻ^b|:)My姠b]hO; ffiJDJP!&_RMxAE3y-Pn靐.V]% žxE[1Ũ \qWuTԳIaԸ6g"Q"a6{@Q:lK'J᷃Sͨ)7EB+X87Sx2q.SQCIm]Zaɺ-][^;[JvQ")LA`ַvgZXWK`/: !Fll{ -@ `4DҪocle6唚//I#jfS^8r K޷s^l$RΓk5tv4qQ`7씑~ X4#5*C_gL|CxpY3C.^̙^!Tk^=_H bj},8`TC]tẮou p&!#}SSGc4Z  zy[>x(G=M``ltͪ9ECH l+g " u+nξ/^jii.ο{._:thbcqdx_'㕱Q5>6911?99ٹ'Ollw.DJȎlT4䘰kFqCCt$ -AkTp1D$3[I"3w(N=,"T?^o|SYΌ_?V4 <̓CP+@8+ٷoy;n."ӁT LMN pʙjzۘfH%SJZeP`U[ŽBXO%e)Xs{X݃P왙fft)B :|#8#ه%Xvr& xܬ W\C;!=sup p*ep+"ï1sl=.ԧ(M fP 3;m)JKz\#i;aJ~}\KTlL gg ojLڑGbB;22*[6<J tfN,--SG^-l-$M /C~9f[$fԛցvR13EV4BV?~sDNx(T|@FEG&CPn!:vM*ݐ9$D@5LMbVuka(*IأiV |D2N`Bi |+&Nneʜ<=v@ƛeׯ!-K`-Hɣښn%2`: l(WH+T|=$Gܾ׍{-B4gkbkQ]UζӦ(`̆[ F&{F'h.4FXa-$kތ.mԏ04ˀF -# ԴkH9#lu} e|\Ox¢}4ۡ' FjH녝cJ JtƊَ$a@+hކR)Q.oLoH"雘b|V&D+[ȪD̙#:s_<]}0*`8ݠI(m K"$;u, ҤR!fD .P^xhI5uV*й 6:Ȕaa67 䛣Ye~8~4fKG,aH2`r) 1/f? B\c߳6'$-i<%N$׮_ö191~3g`r$VΫ;5jCۜvnvp5`uvLD.[o8N~dr<Q:9Rndș-b>h,$R-le1M4<tB֛oy#G`s3<1_bY6tZ)k[kۛ;ۨ Z][w$) CC#\yG?o4⠊GO߼qi@7Ez 2VO%4EQj/$ʨ)"Ž ~U1Gb  {Ε޺@IloF/49M)y),Ȫ X $k\衙`}O9*^;2ն/ImViA6UvJ?"Z,.i)ҖIqi5kUT>d41pj阾Wܩ*ɮ-1+"2sB^ݎ;VElF%n߅ 9Ud$LZ!_vDuP!-BY'NA1R:$lK(`2³Ktr%wf:]Ab"`Fu!o=a^QބRRt W[CZ KlH# lLݰ.t!j8Xw-4Fz+ۡRC(Өr?D22rrhf?~ \KrU!0 |ȿ9ȼUy9"v+XU?*_ -c^t܊ZG~Az*4ʤVLXÄ}"5.pa :er Gթc(۰2㸮q1]Hn/J55mYsDh3u#VGX*P)0%|kZ PQ UZ1\J2|ijxDXO1-f8QLMK:@=P 7v˗޹?0vmƍ՝mr-Tݭ͕W[ )q??v*ahQ6(r ?8lwFкa%<,#JV&|Ks6$i2c^WUǮRl0S;jC5 @W(E¦D~@=:@]/q'Ͼ\'rI7~}:s5QEGΑdᑍ scś~f6aF=p8#VҐ-0UIJV/\Cbw{ýHaȲIݤtsQQYA\Щ'\~,ηgO~Ha q?c#Il !݀c_Q/:\j/ZbͻR(:wNrSIė8H(7ceI%ReM(, T'-}"7+{@NysNP."2P;UX, S:P-,堉nqH[Ǘ*%{a\`dqODQǒS]cexT#x k;?g=߮, =Ss[GLӔǑ6_uFgj|O(n@R3cŽmMy8ZrHY2bU_ UtxYl'WhfC.,0[0z%:rOS2QSEcɉQ=R\M{[m9$yP^ Ӯ}X:}\b(jұO=H.i+5F9FoLb+`r1ehBJ`/Mlh4{ ~հOtTH yF+kEol.lВFfI7vTo|hpQ 41oɈMBWLNV}׼bqOmb# %l'w~c,I8qaSXЏDҫS 8IS ud5mo*=(-ih~ >Aphf"tmT\PBV&^c~MfR? gE:A C|A)3Q2~xc#%ێL0hnZm5eBŷz IeKυ{PIѵ7`+mtdz-{8WFX,lM=ɖJ<#DR*(~rKSk !nCN [D1&dCFg(~Z̓bAaf_@fZLj} 5ӉEK: ,a>Utϐ[@3(Zae'bB0.t=Iؒ 6VȎB2goԁ7CՕ;޽wmu /Nx3gb{QsD(ʩM,t< @w@;qg~׷^_|G@ ,1NrH|%H&؁I9_#/Rf<3+f_߼+~oͿ;߻z'OqXXS ~eA-bpv^̎|[S ;>x< O)e8Xp|5.[5#8BG"'PR  i {D$)*LF Yx (],tj)揉w*dpE5"*4|kAOC`vv5tKRkFdh}romߊY8Xr-m s[~ 4A[J*.Dd,G#2vL+l*)9q8]:( 2IIpTaB7߭<2*)d2GqQ߷r?L&"ӾY؁6lT?(b M~ 慛cg֫OZR`'-"gI^nXuJ (MV0t; . _>7ٰO0A;n!-AgGsu/ٻY`9U-<Ĥr,-qV5: ]9.7`PX!(JFpӬS1,p|GJ4 $CAhs)Jb썮l$mgPzQ(XߔFD`DPZȞAe$m^3DKd-y$V^S_by=o@7аOa[z) v*NC;͓%cZ, k.dJFڻDCr˻R!|*\}aYHͱ JJfĿ.1n]V`P5L(/qY_8) 5SB?.ȍΰ6$fj!SӟxF/4qXtC>[Vto<}r>oF>G CP»/=pFy-7H^ӿ1z \dݚvlOj?@/q|0~9a X6CWθ UCnI eɛȈ9#\?uؓ$r"/T,&G^@'zswyd)dYT Dv6oJ"Í~Jh.kդrKa6;11Pܺ}&=F;7zͷjj0f$3eeo_a8ׯ]pͿ7~чO?/o޼ "J* d(L;;skOT,clTH"~Z'Rawk(/ ľ/3hs% `~~ɭ[>ޏw}ﻫ[nܸ~3+bq"-xHe,0BC?yޕ+GGǷ6w>~Wܹ#B֥%p>?6Gg ሩ),+ƺJ:>஖8$Dтq>밗x Z \ؾbcQdeDRlocM+h+{ʠQiI3 (BsLTחŀ ey4(>LUUA}3%9 sTY%=Jj˔"w+ͩ=P<6c3#b J"(#Qѳ4D7[츓C3P(no>BEw* KbBI3)D0oWidcKRYAMx5JkW:!R?"%EѻHk(ɣbͧhZ s R>ʬ6e Ы4_x)W/%&B#U=\6|.UX)df7etS RZkϢ̿t|o˅S@&2AD"}<1#uU(V(yy5p`rTiu^gG";IJ󝆁=Z:9JoEn('ՙh( KɏG]5\۞)5iWB^E:aBЂr5FP zq fP%:1"k|魶پ>=t-u)l]R]jĊ-UZ!l))(BQiw]Eq~ӗY{W*`Ax@#t5%eᶐ_)dָ6N ǕX1*.H ,޿Kˠ#/^x!< O+7#q\'OSǏ>յOB3ٗx^sK|A_|͛7G4pR[xZ1z-2V˦kF͸}$2ľznXի׮ݘ;|l;u긗{ts/^ɓw--h kH8"c8 1<]dd.]:vv|13wn^\XCmA*U9j2Zh YWtgief!&UM9&9(s[N N,(W>bP,d@R"ɀdWaCAmn҇U$hAWEV}%pMdC`@ 2CeqGE":ߨ(ٚ 9rR,bwbŰfE+~2L&xx:E=H(]tUis /y{7r4QD[f/҂-mȴ,)]̯?cm_"c;O4Iτ+ްk)ҩc=wdVUPBHWC;)&jTSq'2 -mb|<$XS[-G /(SĊFبA{=^!֬[S)z KոyJQDE I]#E៚^;*,\d ?fKiA~H6@ {4,%Բabeq C+z6MI8 Ait]'^y+a RRUx"Ѣ?JC9>ȳ5ihjT@p/m|pYk*#SXfh@媱 fMK۴KkS+`kJ[[VWЭeUi >3xK˫kk\ZMcwpu ڋD- U?6鸧Ulb$%.T---Q^02xA?phbdhh /a=3s"\Ȥ'BJSi0MKؓmZg O#:=-rHܨsh9c. w@s4{VB~bahapSEDpP7tB$j;$7WCthx-Y3ixEJLK @˿ebMMБYปq:5 !Rjq,Tf눁,y=逵r~Ev[0tTa<؃(kLg(VR N2 ;pfE<Ďss~ȡ˗/},}G~#DxjVsV쇷EzrrKo?q)< m y YFLKqѓ{Wywb|9uOq"&Y\k!Yi0"IibH⇥8xWp۲yړjN?*I0 9uU_+SZVFϕY:~]"D.UsZnR;(/Z:?%%/!Zm#'DҐb!N/O[륜4R5Xo{>o_|){rI,-b„W'4:G,.nn`cӧN=rNFhĩ~, @n97nz:/Њd33_Of.K8">Jz_GR1`̋o|ʋOh|ktlh7QsisCCCH`X_YEA'Qٵdp{A 6vP FF| o]F  ~ZϠ/|et\. d;ik:93$p+jWUU[ 6$湹A8OqT؇J M>}i>@Us0s܈ i[ ;UKe٥ ,P2Lƌ/eQe_F˔|>!ه4aʡ5G߸Dh1a2R0?ŐJ~ިsAÿ 1ҕ\4Uz+DD-fWR9CD|gҥ 3 H엑A :gdd{=9W1%8DxYBubtIQ A1oW=/$&+{һbSrVFHȴAN; +#C5ְFU+\&l~>Wإ݀;7E wTB`5hJt9&kT[`?U~)[=2w"'^76zK( ^7mQ&WuiFF t:M&ޤe߄ջ9nٜdqi',< >Z`6|z晽І<4x[ h\7d.`^dk@>)EODAh׾xǐ5BIvq?lӉLBR EAڡ>zb<(ȇVki< `dMm ;t.!l)5-2R cmr"Hض|ʖ'p{|GEp 7֖6x(cy`k3cR7A٪9-:"BvQc~ƿmG60 h}Qn2%“`[P҆l\VNFhj nѩ`=t9Ѩ}ŧN>Tp={F;k%HB!Tܵ`6%B\d*T6"}AYc!ExFb\ HG3ܣ+/re#hWcLPkCY fCOv>N: Y| FN (za' \+р$ݫsZ #VW` mo<~&񎍌\t勓|ivh9E:Kٚ T[o,\}LqTc X4ä YLdSm% r+EANN&mRzPb&P{Ђn@u#T9g0~lA29G%}2cMۈS%B=o$ I5NAZEǗ.ZY/ $;J JՉEn)(7_S S*5}^)3^ɽŀi*G*T0-di0[J{eI]Y"ԏǴ UY1r 4fs+R6bV6`srw7zCS`%p'pJqhԼ{5Ho&Zr4}"!MfB-z;/tZ#n {dA ).F $Mp-qj- V(H~8o%-#LmbNCAFm#۳lU{D9U*κ ҖЫ LZLAFOHuFG"Z<&j{P{2^*LShEswҡӭo⁞Nr u4q7N_ewv R³-~\ݩ5/!Wȣ˘e'Qu)Ң+HDPOBDPn2f%w ׵zzXεK#}TX.o*bd^|LvsNɕ/9>'(E>AFs<ɐAQbGkJTߝdb #f¶(MX#nxY su&q I0 'QkvߓENgf[oWy:nS9d%Ys;oƬ)*l*-sBqANCDEײy:!e~nNw6fұ35cFEpC9|^[B9_1XzT}5D ;gppF\^>8GaF>ةT3<oo:V# }e%$-JoaJlO=u ]]0EcN<ƹw޽<11LGɉfeNqٙO?WWWW?O?/tzNŽ f={]Dh=w&&#姟~Ǐl0?~a!Ih|+Ԯ]] %DܧShK aƿ$ ?d".m$s3׀=rb/q-,fwTNA <zq~8zDžJn0VeA$JN  ]_ N%g+qDD"&̠֓ʾ񓊋kFrMEV_Fd!-l1. NZ'vhj&OTYn?:?2b}<#ލEh{pU ~'S}ɶj YPw٬Zp]!Vހt|½fo(P?+d{@wcLOs7,~ٰ%_p ݕ7$z D(AФ"Bl_b|#j=wנԱ.(p8>VtW< M'hV4H3(5-u|4>~=uXk/G;54\I+;$’C>%S6@f\w=y:f_O>}mgfqV__ٟ/1bos>r}N=>@ݻwk_]}t,)Y'&.C}"U1NQ1̜(+iS& A5V= 7#3P՗_!)s}ﻨe8vE ~807`2v]]C+###VnݿY/~ٗ_|'0Hu*enJ@2;61zKCS@ʃGݿ0RpoZ,C))|W6 ]gy)!E3FbW%T'䅲aO$p@M̯ aoA[i9 $*t `ܩS$ۤ(*)}:FyNj3Tご:2i>vP؊WSX* q,~e޷wk5l@V?f,]HZic"S[QTZeRnWӝG+x5m^H$$22Zy{Yl\ *W^L]ru4G,W1UK5arYGzF,@-#VJ#9?zSZľw )2L5KNhc+Y1 l|U1& y '_d%7'6=`iRZ Y}L {<&]$wCξ,B|)^b( цƓ1+׃WZSlC&lI"^70-[Zo Q?Vt Y+gqw>x]o 5oUlVX[uڈh(q\ 6ܻ= *Gm.b"~! .NFYTtet[`L-Bmj%&;ŌY~ /OaR~3 7ʨfPbm+{r:|C͆Ml,h@&4~qИH)ym\yfdM˘B T KV968_} q{Oc3ˈ;gJE@h38_Ks s K 2 ?08::~=yXpرAzZO!1;bnvSH S #'[Dˆ[Ȳn5D[<$kgT@DR=~`^ZZ⇾ՍG|}&%؞rmd6?A1?w{ԩS1 ,Aϒ5OSx7 HvԾ] PE@m?hz33g%EL_ApA{@eueգG8vdT>T@g,/<~hii ='GΞ>ua=2ɥ%agϑ#bqC5bc7pPzQ3eDx`S4x MyPlf,9V\Ȯ&)8v̋HXz`?TЇ f&G9Bim@Y$0mA$ .MTLq (jYx̟Nihox2Yzt?gOf Z { И`dvcFoeER:H􍣫Zh: aZ^$wrhDCJJ.ETLN,6m*ΑzZq.ՙ:Q= U'zR#-oqfݽԽ6ZAjtBk&No1EL*Ќ݀sZNœFl [ T(. 3¡ Kzh6,D$yyW>N&s /urZYCQc\7.ژ 7qpqq}Zu"Dq1vbm)}dսZs6/O \9 IS%8.2DK tF(NL&''nE )]t@Tcy\38@UfXi¹mV;g9!;ASV1N:s˰RW6VW1-bRȈKN/y 892x5@ {~DDSd't%{ X0"Va XF󱮯65LY`F v.&hl8 sa߾OGCi1xWV֟=FY'OON0'WB#k"82PŸQ)$Z4Lc[^zΝKxo=wA6q20 z c25I8u``mccnaq j!}KK=zс3֑LOLMMDϵ( BZl#ɳJEIMS֎9,a4,$HqV u-) [oӧ$w\>r04vMC{D>{ן~_Ƶ2ƈ޻bv;쇁\^Y[]:Ϟ9|⥋j# N7ebA7 6Tg؏~q E"FeadIs1=ކ (iS=; 74kTvZkL"k cL>[x%|TJDkLbd9=;/|,A6N5_搄AoPatkW*`-mD K!z!_J .a14[Qw)tY\ "[+މݦ m\g GȬ1#QX\fAq ( 4i,# ]sZ2LԬZfGB;0Դ,$W) =F Vf$Nu4O~#2F4gf6j㶪ʏuVu=,8=Xwh/o-Ԁ MpSF?m+"r#{v9bMJHiBE߬OY{uRݒU fLДqqP=فRh†5? ߤ.$e, ]$*8 ͯdԠwrk# BK4xi,)c5} *nl*eWPHZ{lLEfPzo[+DB8\1񲾾K D얲PLpM\О|-o5=F?'BQ +C⻧022#DYE2Kc8Ș䊉' ^։֤zi_h0HɄC6 &%&6wf1ˇ)9yawҩY!~Sa=Z*x.Y8&d $U֏DZ#(мȥB9b7ǘXUp{C I1>% b3L7r'nV+حL lzR* 4P_dno1Dw  6@6d~t:ߨaN:rыiA:~&%5 8=`u}<G=1zB[ KԙؑQ)tټ̥H.x @pݯ 0W 8{o\13sȑ|Wy5r.cdF+hp}hY.u69@z8/pW9?0{iy @+:ǡr8}hT{a*q?82:vK++آC#;^渴w[&R 427.؂W^bÅ?eysg?o_4>9N .Qr?֭ XsR Gp9]Ǘ6~{$DV,Rh(Xo "126sBdBv:mn^x T4,Ͱ j;,/eJ-Z mDtӌH|N66I*Mw+0O6(%Px4Uu4eΩ7=NҐBy'_/`nS6^`pܛyL%O\Úݔ=#!y5x_Ը|oqЩ6UD?p 僃wљXf\R}MM)V Kp˶ m8OޡXgӛp%{if9y 8|GĢ@C\;X:ŝlii=Iɹ=6 $tZ=LpqB\4\qw\XYS($؇;D{#9Y0UƝ-o 'ut ?Ly%,=q;ZE 0zϦGXa2"UTv ]2b>ZK`b"ӳc(r7+}'Μ>1yA(j]b>;}$?|Çgfpcrj Dd #m J3T;u2  UREAa` ,sa /-|"bieٳ =R$VC?~U'O:{W(V`cCam2ݦLD*<-^>}!!`ˇ$Bq.{6A A2 )eWVز+}新e~aiH.,/,<~pxp+"mM󱹅Pu1L4dp69 3<aY?dy7*PXlƊ xg{jA iPHc"SH:XaǴي%Q7FiףIiPSЬ\PVBYV1P "yŪ6 aT0¨*v-o(1y連RƚVLgI!,zpYTKǴ- `hth A<=UTf¥FEmZAr H03V]x8iVd9OT,QRDeup[~ÐGz[*DŞeLC;FF kH><߆b$]izX̒kFa`2+ 4ۦȜ5MH h.TJD2Mi3pHmF%V|_(C/}Eie kjzulېe^*T] E_Gǀ Ƽϧ}G"vV R0)fb`iq&'N&7!̈?Ϧ.}%r*kEbw^R{ |2F6{p":>~ƭP߾ځ /~ч.]fuHƣtxD$huxϼDd~?GP88DO/PX,/_uG}һGVA:ڀ_u?Oo[~Xv_n`7-MAŰXbl ;~FHooܢCbƁ!jρA)0gׯ~O~~7`!H^Q|fAl򗟬-~Lk0KP__rc#o};W9Qt0~8ˆڱ>R1LA/~;0Ͽ+D@-`k#MlddB!4(.;=;f!@iS\.,1<62 =~i!|0cJY?`fjyrb*CC#H]e `-!XC<(Vm Pe l܀/ 3Lʃ!OήtBzW>_ nΦt7QԠ3_7_J.3!ShEv OQCRfN|`=i8E>E^9K6" TaĉcG "G"LM^<4?+3`Eg?xJRmr$]֤9T*, яrCwn^ڱ8Ð,-?X̃;THm9;;l$:*]K bxIB5. 2!V YƉ WDl. 1 P"UUP0<bjr# G:<ޟƫmő&3wpm?yHӷ1XwQĻ%b -Lٺ"{lm+HN\O[s_y/ħF l/±|` bVrpgۛ-_OZFPV2ݟZOy #YNE7F[kHbkjD7K4b/ts/גGBޝkwȝXX[ y'gר &iR C{:SRb(7-F%5CN4qUG3^C56փ@J:X\^ ow#r@)FF`쉗ڳ.]ЋOfRe\m̍r;#On2#Ò Uێv $p*C=OrZp||SaqGdۄm'ZM08j*ϕqk{ӈ m!$㷉\'rr; [6M|xNLAyGSHGJJRS|5{L0p c#c# & FV5*C('Og^׼!e3]4cpYsMs_zs ?.'d>xPhTa&$q*=zD DM>J'4$k/f}bʓ'(H8ƛ8…7yy{cB.ff翾qݻ3/{OK~n@rG'& N8cK\X+#qQd6kN[\[V}A je%ɄEmTF'hQ3*T- ,P7(ΖM31Hr!ˌŅꃲXX~:.@pL TX"QˀWU$Rp ;_Nd{htKbB,܃IXePY<⇿|L.5 9'6%;UQJtrL%@Ņj"IL$8#Ѷփ1xź܅CR DQJC*N-Ki覿{?6yOTMJ l[REνGs?F `[k .K*،R#yAE+h\J%YXOYn^46lӥS&,rAgM կ蕨i~@dvJ"?k EPBƢ2pĘ932~(Sc e?lT8Wshߤ0\o);*;71~DIKK["PǞ-=e!v wڊm#Fr1=Ł(&UEW˅$cc3,ɇxSin g|>/^&;aLvGg+<Ǯ\481ϏI9tΰJ(x-BGšYE_F(Ax\8mQE'rMqZR3Hϋ"xxe,x)gs="&AP~t:61]aSQX)ý-j <)wd( !7Aѩɩg<@smPl/~"0$" Ga\iy ske<~=uw/<* [bP =ز 2fQk21qk;WVq: v[b6jon]:w~ѕ`8?Spen^O8ݻ87+|u'`M`,$d+D+Z@6%I) +!F'q([ۏ/>G{_ヒ .] )C`ys5Xv8ue[!c .xQj#3295Icl7,as}}kmPf&rciueq-sD%:v[]9wͱ1dW#YaPqt:߄P"2υTgvni=195~̙'NKy,BΧ&8۫K+*v}8zfv~yyq#ȇ < )b]`@XN+\%*>g`(jRXR0/ ooSLm;-pk y^j6K]i]@XvW+3Gһ!\%6#t4\AͩBP2MT!tELLN&1ik)Fݰ[x{?z4{c{ ; y{EӶDCOhxBP-aFG<"EFa6૬f򠸞 ^g8<eЊ>J"+Qh7i{k%"_He|3MԖ#0kH*^n($VWKSɱ9{. '0(M;qg1H^|?SSlm"a@qSZD|MCvn9 c"`=y:} N~q\% :P)m,6+v<~ٯ?} K(Kp$/=szuess/VV^jUn )hѝ$T\X@]BE 6F!CF@PͲp9.ch4TN\Y-D)w,JȖA`> lzٽTQ>F5L% ǻaH$_xX `Ui|GF6VfxQaQm!㾍͗޽ w&g[9gRdYĢ^:Qy *T#3[>HY ʈ ،k5"k=8"uׅIkHڔv*u)$G92y87ĘFEL$<B{Gz*񙾐*tg5̝yl;03ϙ&qfkJL c+1U BV#\3[|f 4֧׻=[rqw~:n iL4?k:&ԉkZa bTw>jb| N2*)LCH D(FtWHE2&PU9x*nl˗92IĜ5F.%vTO݈6{5y1_g0ӪϼQ@0؞>?GƐ`Mށז\Nh64ji"սaG˪a ūߜ2CI+ڤ{/tEH+hb2xF#bs֬4M;[H<\x %=ԐF{n.ϖryb )yl Hgkf" !d@A_L;"}rd77 h59=za&7y{@W J2>\Kh"ڻWd Z]  O,نp\q[084]?wФz*VcБ3 L -EU\ k1N7CdzK@Ci{prxGW !A1*ӿw7V ;¥KǎG?Q>1,p ]ZXX__=vo}sn5KA_~ƏMMN:wQ">91xէNG C>u؉'1a} O<|ʓNb>c>1fffxʕw޾|̙8avʒv#^ӄbHSc##ϝEOزt5N(ᑡQD){Obajrjx. ۰ʃGOnܾ?/>ڗWoߺ}޽;w#w8t޾w2_y_9vۨw.^x *$MRjY"fgfzh<bH>a.W9'O\zӧ ΂H|)9zdg"cCHO/Kx!B:c|A=`mR9v8:aF8 #hv`{.P#/;a ̣4БNv# 7W6Ch\r bS0#)  fpv𾣘bChQC?ׯ :W%'y\[Ft2N)N>i^R~t0ad"B٩ jDL+Kfd ee;Z9@j-5x-6U_!ke B5#û{r4> iΤ+%$P$CmBy>22D#{n6zJۘgiXIw `d/cQj#23z/S>AIDHƆ̔,4 F\c#tʺbk$_L4 '~7fL뿰j 9Y1W(G{OE(ƤSU9'O;>Om<߲+^+͎Ź(< I|]iOv'QKͱ2 +qsݥA>( 2 z.DTB໺K8~$5 1렗'8^YVmk#^42,~hnJƉ bs{>Wxۃܵ|u׺`򤿈]d115Cazkaja=uZ⤺[T,!$ތ"Zőj㹅 F:}=.PLsA~|3P&*[/2IBuDl9Z (@d-y9vu~_)рJoD ,)މDR;U#_tTdsQuV"&UIGV9>^U^؂E 4UZI CRq㳰8ޢÆKt0phER-J  NƼT $iX̓4M#=b]u|ĘBO9JxfaUB~_J$稁=LHqG6-XN*)Y~1Ć0D3Db ng$YtbI]x#b.˒0I554f4色!9$j4y͛7K@Y^,?288>6467:iqT#:oikk~uc~a7~ yw޽r1xnye;nP`oH:[\&3H-,gn~ɳ珞v؉C bHp?j8$*~6)*:xm^Ig6VְJx`+KGG'qε\A6?xK^d8BS* _0 8bNDX΂ƁҠR ,?q _6YӁ!dQ~o}mcEcH8! &c7j"T^.,NHdpۡ"AuET^j  G8LJlm?j+{|AhNVa-[6[=Y Lp\^56LStZUp66܏'8ckN)x7v-́8"ZWS,\Va[<|=Uiwь{N_L ?$4Êx]7ˌSse"G3abm{m #bn}m&̎[veXh X޼0x[:a/|ѥCe_uH'~/ ( `[jY{pΫ-E%>hF ?VŅJ("TDG<~\]AxS'YΪ6kFqiAZPPxFH[4 I_^ju.DP6 )>(T֊)FvGʷa.`j>4z+6'RniI*FՆhcE=$$o`;~c5g@Ҋ1)S>{-TJܐƞ\蓌3Į1V|?60G?wq$%, A+P);ht+D؃3}H N`X!!R2nr2 j" 򈃵 p.m5#!/<9d^B΄x Tj^2TڭƜNw_W6Z΄A2/-= )y69 16@lnϳOaƚT Gv ހ^[0 H =<ʆ"*.=ZZ'v [=])VʺdG;njř&9Y=FGksbN\:Y0pqid`8># :&58!mMPX|/ů.7h5Wrj8h`w)z 5\|wuR`%ߺ]Ge:^^Tbhw~u-7ϪƊhon+ U/^ԉ.d#=kbu)Y̖*N2ljfU1_e{qq<26zM:-FKSo4vA/ 4w8NB65MVͨCKM \d# ^ZQc- T,B蜊i񃻷ꗟ|iZLR;LT?W'kWahsՌ4L3<5RN.m9 rnHT Vu|>*jE:z5>އ88 9J.#PpBuV۽Tpү KCfy :gUo. 4j F`=:w7 Q+{j9)[ …"uX[k(^h|v*^pUZ 2TsOXD48k<ڥmrzlmWwx/2%| ~5x2jtnR0.lЌME,| 쭔!uPRS /r!#[Kfiou5ȣ`W'm~KEh6jbVA"͐}$}Ѓf {fY|gJ 4AjVlБ^Bo+h" &u0"PfLT=#S1?xaWQٕ-9>4v&uWҷnsGA]$Ӽ-b  >?}FJ9VTϷm,s.,DN!T63l *=aX~X5lhjc JaYz}f0Jwi؈+X)kʇ7Oh2/_umBX{ r\La9BՉ#A1 3,P*J|,U;6jo/׿U.gpt*1:LQ;!Z&n(xCϿo,։T|%{/VX"'h- QZ, _ZAd0OVK yJk UPMjج*$X0B:Ĕ֑bIF3|:Dhj&[KɕǕam?Q)%^ˋ# sQV.ԯTB,a}5Yy/o`@+8&b ({[nd*PZ&WYUlS)(PI!=56*Z -"Tb(' ȥ+Q)6UVoy>{ %R R)Rr "QRH$KFaP(z+-Z[|("jQBQ| E͊$A~m< V=-JhN06~xnW~NsKծЊ=XoK?9}:՛řRSe. |z?HRaH'q5k+ښ&JH'6RCm90tsipեY֝cK.,@}YnxRK0!gYԊbnQsk(͜z/LF *qA.2WЊv{NHSŴX:С2ꫲDԱ4XF}5ZͰ|~APSO:pŕ[QE%e6]>k-(i/VZ4>)yEuǏ֋7cE /M$Y$ ДO(OA(6 dW/^~j1@m?w}%QTĈ jhܬ58s n +ߕQ,ZR)rAQJj2 :U}rDնn@%9Ld7(P\\TPxڻJ[שSUp:"lTzVJdXTׅ.S"l-|dbB{:nCaD<: "wp<,?>Jcʼn{#\/ԌګA>DT3hHF?.G<䃥^q6NL- :@Z]9kҧYxIe0~ cFmi ^U)N xT %@ʍ%a+W*&P $wʀPVQBrpRHJk!hRf; u#TD<lԒǵxԹQi%/I1DB|,Ԝ孋lh9py2WuԹ YO%[Վ%UY߱m,@O:ѣ:CĦDt?h4mdL8VK!Mv;M-$'dSBJћ* #@1|i `ԲicUG:Rއ%llaJ*I L_*$󆵇s}-ޯ;堔ئ ixowPxGbO⋵.&ZŽeˊY$+ݶB٦Um~ͪ˖IFHvaWΟӍHFLt 6L@A}c|96"$)\sz , 5|%K<7yj?P[#L\.$]KCf~knvn=9=}( "BUБAQ2)v6"@t8>ݑJB9C p7R~K20D?ZݿSUVߴd= >ŹW<:cޣ=.FSuP4P|`Ɛ7.ס gǕ 2sVË3䍱c|GINd~CHf״l$r`nfZ_Vm[Zhfy^P7K 2dJb$ń2m.i++Vd~sdp3b_IY'{ueotST:^m[u2"&3F4F8w93H(h֖C"0bS_yb J.H)LT}4!&h>Cy>RtIeʋV"ɪy_m \G`j&_hj>  mcL:XM֦ j,ܺXjdobmCW/k‚OT_R0XO:ۡ:0G0Tآ~ך^br@SD o J!vYֺ /AWAEM=**$ Xڭn5<8V>dIzNr%']pQ=q|Ժ^gSB:abUI'y0^;!qtGE .@_A#-XP@d-fSz nXj ^f Dm\BjΧOORW=|aQǢVP[ɮTWXA1 V'8ZR@%8=ni){QO9zW. m`_-!Jp>IŬtʝTR)6( NJ ¥;pR(eɆArPZV.V$aîa&V+FP &:lhP|TSv˪:=T{@.l5Ad2ޓ7n B݃w|%c}џoƲ͞;5Tj'RS`FIXP6 tgd2m>$4m7l7D-{\xL5!Վn:jF`^\Áƈ (Mr @ڬ5+yTd3|F>lZx&T36P0N3:̓0M6n?m,a|ZsIn|#uUnm i"daT[{ QP]}Zh}w__730/_O`Clr^ ))ƞɶo*w8E h]qf=]*z\F6vJ0(]UUXpK-DTgAUD2qyO֏m1w{%GRr;(<V4IhW) ؽ'6͸<֫!q$u69 dT;C!vA8KbCTp죌BD4%iQ|2*U 1ԢZ" Pg֟ )Ck?#"壘![5ܧehItد >(k9;Jȉ:!{@AN<"l]A(㬆MݤtBqrl~l zPg"oojv6 m%N8 GGaL vj gT5mo'|b2_gl:C͉ӑƾoo_`u#dv z(١uaSl E_UN֓qEN۾~V<[Zrev'lCT3 \;VWAK+ 4-h!A,6%~HzQ嬭,^Bhwrb g@tHqnp>IJoK (M;fBwJtێJCx.n$bO\/w9iŎ-ʼۼni@eɋܲ#gZ .vQYе3(>;v+"E:a:&@hC*{~.\ѧK> 5̃WCͺLAځlC3cZ8zH0t{igA jg*NTm1Og^t0TDƙK#D!& ]-H~u;c:08"'?&ʠ\wb\A~I 3+ Hq=BZ=q NSݨm2kQ^}D߫*P+yDar_W[N^&eZRpl;D6EVsQSZTE"Ru3^DwDЅ N:>1lA%UQ|T㸎aދOj%}Z~ Th(gS`@UhDe4<)'Xcو U~ PiӍ@ qed]FY3ѱH0,]!Rl`tiۉih<O,KDg;c]"Ǧϝ!\S>MsYP?/OUZ!{;5$f2O| |ccg,#G)4a z[Ó:@S'޼:c^˿:2i+*8a/T ̘" 5gxBouV2l:av}mBS!Jڸ7?'vW:Dm- ŚϿo:]9ǽ7R7dh׹,7 $/e$kB TLULվ' z}ƱV+# mm9;P}d:D1x- j}J#Mt|kO_*Z󄎯h]W֓؁mD;Iqwû"A|kc+m?&^}_0Zjoڣ|2D&w]u(-6JIWhoڂ}k( ߏC ׯ8.:~H u-hh0sF<:! ._bc9,3CerBկgO vmLq1J|]tȸ!t WRg ; g[D$D"B-΀$wMnP0'ʕQ6_:*;$ pd0!:ؒ}Ϳ/SeaBat7^j8$B8 ɢwDT ЄYAd<Ѻ9v!:@fsן~G"b?B,8dy|FIZwCגsP]nX*a趢b1^>|ya}ALVpֹ>])YcPafC:5Uœk]h$}t[\r/aHCG4 {5v8Z 0gyӶύy]8޲xkw΃WPz֏~3 x CG!IZ̳D#=o5dPRm~3.Ծ26XY`PilnË7kfk7~JHհ'#v$`c:^8 yq Ql>96՘`eu=5j -[ㇷes\КqP:N; ( g LqU/a'PGe17ݩm.t '辷)jн0bs쒩8@?|1ecس_{cmQ,&c9nS gO#M*`G5Xw &s f 4z8d5%es]{Fj'/SR֤F겢 'ދfgl}y#YSohJ/?-@/CĦ\ 4T8 Nlw>o*.y՚28:)6p}SJ>")9xT|REwex:TՆap!"Üne)U[:r N~?!d؅e36PU_t=+ʫHz,3(k~צNsl`p- q7Ee8uBIƋU^n!Ld֝'DNȱ2 L @^P춧<#tpե39# n/@ mږ=.KR;ƛ37d{TLS7)[jǺ)g$) # *&DcsjN_>EG}XII QZ{|ʛdm>RC\SM(! _:&Bt"$}"I1 x˕qt .>FGo.4,4W!t@xVʉ 0f^=?r() V>@GۆPf~ 3r,i>(^MV4ןWvqM3l,𱌧UpwuCyX"dUIpYHc7Ѐy; !&N pR'X(@*Y]j,6}3PgFnd]JTĖcJmj&KKI8c,F  h:ܳ؊JuFՃ@)c})g 1$; 0awR=Nx6ژQf\E6.q܁#w`p5Ƴ=C[W3x>FRz "8LSzvԸ֍n cz}f6~"8p$YvwHV7ns0݋,^92V]V7m8?tЫ|Uigd07Mk3\_ۃssmxo;ږ֑q'04_0:.X wߨ/cb:sj Ưha8ֶh_}[a^jɞ&2֭3;.9䰥QZg_>S^6iõW- }%bFU+}+`R思 )0|>ɽ 1_Zxaܭ%å'tfe"g6 wlŁw^DӘ=yQjƟh2Od$Ycz0=AZ>3:\f0>ި2jkDģ*.T[tm0uY1ll%sk"Ϟ_h"8LQq H 4aLjD!YBWfZLq AAVW-"|{ xx5Cݻs'wo˯PktV4EkG5skb[Wys`Db d@*GއSogJ LfZ;n%j)mKyRe^ly'0aF [,6{vƸتb E@GU HYcڊŐ*Z. DD1/oO] [ >&k΂'.POED1Mw)oɣSKfSv1"#4.,AWAU r۷o+mUpۜvTUe \$R;̩UN`khVK-MAfs! Ҟ3nEν{ 0IMZ"C% Gjm*(KGfU[JU׿o~#;4j+f|v5Su}M[YxՈuCyCX:Ho}&. <|ANq`K+-!t.EC4eRi^5^f@$ 3\WH bوIxv*p쳮lETVy/9~W^ІY| Z#4mWL(ۜc "f4xƵZ4qCrjxlHץ;W#uz+ oo/^?ӿE˘6Q`V0*`Ŏ00ܬ[OKp EѲWUlN͖Q"RHB9nr*c,Mz%m^x[wn=:BN`ikCB|6C|~_FK9? a=Yi= CtJsм(Z{OU!p0U3 j&V[|/a98AQjɱ|P)Ќ?]j;n__҉>OLfKCkė_~+T?QC) :y -+fyFU_ x>?3fO/wdU:ŽgX{K]ѻyA%N5Emut}uZIp-U4d~/X-fovbu0Esj*kXVĆ3PlvNe|8kUvBw/t^m]3B&;PƖI=0B|o?DJla6m(2 Xo®fEgx6Wz;@K.,j-5,0"vcֲg{f_2#_-Y/21`zk?<*Go*BAlGl~;V[hՈh&>*Uys u2_Tԥ# Ĝ!e;Ώ-:ʂG=6Zqo>~>Cb+vi2$# "6NXIݡznEm+U7|Zt6T2cqWD\>H#=Vԫ~XQmBnB~ѓaObcZ$BW641 L"yd\ i tvax)%FE%cI3nx=v%/^.ŒzڞviqHd0P͒VGnk\sY𡌿S NU8鑭t4n)0b|Fr۰a@${ { @b-3Qţ~*!43{%6AЌAHв fǬfL:2&elV8C܈ b=gDsci4 飺ksG?+cvK|>Zm %-!#}4C+TG٭+x358~@sWӷnIB1~ 6C_~f e*'MꟅS]|u*9 ;]W_q~rPD'62xޯ^XߩbN4^xr%l14/5Ov J(aYUsشeJm5 i~ $k Iz*U EaܳB8ы־z"sAձ^p.MSi5pzP^?>ѩ~kBYlz d+2W!^RݚM5p sm(ԅ6Z ]VP{+-HC5=l& z@^gS,Ï+9jbc2zCpEbpV G463H@Xˡqm~^f 21(Ng:qͅ!:bobtq, 4/gLr?"ftŅ1e^ "a8J|NRғ=z34)B*鐱Y $5m;ʪ`y>)L"YB]vyϭDU3r{Pa=w&4'a/BTZ݀kNOBwXDlȣ&%CE 'Id0pLzc'{ 4IϟW,?{GOuByFyc&K(ZQ.+"XV$\.FI7:+jQAzS*PQZSTOٓw겂 M" u]-*/GzlV|}c # {';r"g~UwTp_])"Z7jyT!fU8ιV^oeN7QP93 J̽#ܩ9uTLIdF́PYWtx5KF125X c6p2;g|T8Ey ݮm-Jo_}s>W֛XãfsU3W3,!,gq1}"f^4[Є+as ]j@WF(65n ,?nJuzT, .mkj`!ڍ:Fccdh*qwƭ:XrkohvJP;9R6R7^̍w4ppK{z)v!sNa%}#]c ;$J"?!?[j]t8Ny1¹.`Uv6q ( 7PtO_+ksje__xU?= F65sb.²0Aaδ .ť S)Lʳw ?y Q 6ԧ2jwAS*i qޥqăZ*:k` Z'3Ac 78u.jGJ QՁ Tv7oݪ}$J߼en%TA@8K, xB] :G(q+$xkdD g[We(j SxzK_x߃5Vrʸ?o=xpvHߍRa)nlt+E2~2Dp3YddkU#֘!ĬKyRB0U?Ja0DP_ {#D{4 L8 H18rX c,!YG7ҏ xBb%O$rcQq:PmR{,ݽwbIH0ͤp[64&|^0_c}o&q^@Ҕ0oL 76 8&sMʭS<` 7쌆AA!l8jVP{x$N2\K%5cYv5Jnt,j^ç)B&#vْ`-O-WK n\!)_j9OfK}=-o\sTlXad"fUwo_ܾslbR5/&Uwau?BPe/:Wux#nr5=mȻz9(kpc{N02Z{5K0 ֳgkx+<> q-Ҏos?3aC/I ١Wk/p -`X# `R:$0p]T@(h+rIӸ#$4V=zgQhr_~! *`z,HލΌը)hx,ߵWK3Vj)DsV\ *۵jE*P[BEiD.s*}3kSLy^ ?ա XdS#-+xj4ΰnu%9F d0r[~rM]3p/j(;hxvn%S븂ֈhPLvʅv{l"H Q[a=e HTJʦ4-5ڃm`3}Wm{m9ziTc|8A&_ m1+şnfq(mۀKZKk>_ n=NC)Wp̻ k QְI. lWzC/NjJ`O-[.U܁w?Y Z<?~opӳ( ʆ CjRf@ܖ0:m+0ON'+0tg;=AWxdet(.~G-7. #&vg(l[ŒggWRyNœNoh;'?cáNjk+A;wz㽥2̾Zͣ'/Vx+{ՃXtZ7vq6]Вoc$Ui(b zCΊh)Q=pu}!кVHj{>;kajnԴ8gs͑UyS|y0/Cɋ+u 6y׫6Apj1E=B4Ɛ,*DmdՔ}\wW}dԌ6 ( fy"-}P^Iau,7Z9VfjR=ڄ< i&tŚ犚ڵ_@ȎQ!$p^@="P95e\a_|>}U2!Gڥos+ y2  }1TCW>pgCxݸoo~gĺmۭWJ0~!Qa5T– ^tLD֖ HI!IJoةhhP gʳuW#N2Q+b.q]QedС!U!jG!Ɍ irias75^{,hac} 0p `agV%߾Y,wRBE[Ov-sGjD@(4zfXN1>hqI:\TDB*J]00U`jx^h6t{6* EUݛ͛5\ 2_6E SʤNym3"`}0g쥗Ya{*xaX(FOz bb++w5Đ:xNV:ll0Fxfjwi1ԤC'`0@5P#HUluzҝ;6 bChߌ&0-?矹qH]xY٠W;ayDC?j[F[#2~~tJۗij<2kfk  hTv1d\⤭Bc?AtMjLG0~{:n9b{*j4KLv's鞆= `pڽ lB'kt1HC>ZQދK̵7z;^UO4tI-mbb1>-9z~¡=r=|fj/M+̥wpGUYiO푪QSƢH_˟MuG2R AOU`^Ma`l53XysyyeQt*36*w)|/_<RcmڪS/Ɩ/shw 밤-QՖG5cN%mi% ӱ=Ì pvG`ҙ=Dg|qKm_|edj{^ΈcR[@)lOAK [K'^g\կ~駟WSl̤OUk"ցUD`pLԕ V8.EgK-Yn`히 =(ծ.弸|m:m[KIo w5=>-8xzXޚu\:gr>@ҍ;~iǪPr@vz͛pz-'f頂ZOpш0ӥ3dDr ?WU]7ԑ<\YiLML4e(|\qJ=s7ˣwA-mం~6Vz&'OtD*D58kt2lvΌ!팗L ɻ'@zCѣ K0"FMEk-Q_wDNhᕵ<\ K>B08; ">0|HbܞPI=݌2׻HS6N5Ry6 DgfaM_qG~[ }rIB!m#l)Reɏ49(U;='r^Ql(!qU]{ooo_*G ,ܦl.GfOpq4+ӯrl6godhFN9ӯr~ks:ק\G>y/O?}P>VA]p[&E+Zw:2)o#C.$kj}/?ث0ZWCyWzMՕ%ɱ(;b'6<71(y[WD=/)Up[B5n̄h/Gx{ LTh i^Hw qƛpơ }O,touɰnHE-\wԭyJE0͡ -簌ICa\OJE[$` pZs F y#[gZ*[k൘ij6[KD3HhpҶLDkd=F xEdkI,9Apӭ0S5e+WU+SK8tGZIH2Z:Al֘bD:fbWT>vFR-RጪBQ+ _;5Zc3DIS<1s}U1;㷺sέ_7_|]nId[!Gro []ij7ue\&-\;׆kô,4("yGyRH q_R@+͡R >V,%~\̖7ʔb=O=8f077@w֜bD+>h$ux>Yk&G~9kGݪә4jZٵcܸ 8d,z%#u-iP; <T0 IaSfNmAZQʣg7uԉ)svJ@7Ef8Z A$DD,2tAK`(>bda,ʆXP9\ FmrN~8/pu@0׀ٓ4R[jN1[XBlk.,~4 G{ Mz*|utJq8 FZ2GQ 󁞭ݚۛL~xQ:t)@jgLzWx {%naD:^P`.o]ӿO=}R!jSwaS9U@ïv5U|߬ 5.XkXܠl$Ofr+?}E2B6b.ʊ3nꏧpư+-sk:Ǡw۴I{36H{ABTh({ XFQY' (?(PBfg/,LLG].ǝ>Z!wwDĸt~{&CzOj s.F&?DHmGp7Q:oئn5&`:W#IDCݭ .hAĻ0r \N㫦\Lf۪5Vr=8DvG]2Gμ<&|`~i! Or6/E Us%m%7LHWVKK39BYB3onZ ᣇBQrYtpӬxE ˊitJF.mGczJB(bO#$9T"GsQ?CSAIe!C("A=oqK|׵ +mYO]]R ]TD@Ίb=J=30 p$QܩҫKJ%E]]#"6RbOPMҢ_qƅVSX_ɐ`y >,.{T&Z_4B .(L`3ŏ9W=|³#wV1рI߶hG3;hD-_,nR5R j#m e3cP^C*4~bPvoZ7T OQȩEN9$eɭS5)niSF^z w2О#q׹?Ьb;P{24ƒ岵9 WvcL;UN[Gݴa+S=;E[l*k֥ݨ1rx2$:#F ; wF_y͍4܄jWrqtyO80Ptw},UutБONԯ\D-sZ]0\w[Y/G:1-l*;|`pA[Cˠ\l 2~cc2]MşGy!ؕHϔ5:#WklNmA!3dkLR̬|f^~ ckKO0 Nɛ2vGz e~a (F!F# rMUsQwED>0 yCt~z[9 ÿ8S^l;!.Lk4im6-cDZ ysHFfks˄rNՖ~0)ΚiM56-eBpCaBB-s>^aL#;m!/-\k,LHx ƶSBy֖! \B]G]c剽7u}SX4húy}qT@: q\դeeHT࣏U6A @p< "y57Q[<*6Q.QWV ]oT "1@XW'"~N$@*P,ɄxPf"s83Z{H[\8utgYJ% .Ĥz+SK-K([$5\/.kjKؤN q3k9=ݶ>6S]Pi:]Tޔ; Vąs7<GuGL =H'8,ʱ*/VC4h!!|!=j4@$dJ+?~][ x886IQH0ͨG!U&/{J#6uL$1u)y$G6 5=<ޣ,janFyu_:&!G85hC9q @cLXǎ/?D;h OT>޶̚l 3 7Tˉ5NIV ~+/g-fey[{Ȳ`R2M5fh1'>paoB5M >kL㙹W|T ;gB`'U2 C N09޽- h(=[GD? & sb>=Pj󲋦Rn|ȿCWb< aciLp_^.,.jGv;P@_C^IcκP2]. XsG$=?*qtP! xf%xyZm-:S+#]#(|LC:RlS* PiM,'8WlQQΐw\BDva=q_jYT3ԵفEjS_xm82`URAVԪ*pJ)~QFpG؍^v+fvNRkH}Q{GO=z~c V^r]n Y O~Lჹ̸9A$뽭)G(t&hџ-CǶf+?Ʋ+MjU"]|ȁPK%a$b|aq_SgRف5 U"699~EFj(y>|֝f o$&6RS|jU~&a7plS#PuH:&]5DY9-qp咽x3L&ע`*j[cU[}==1 0u);?&3&vn= /lc.rt^$[$TxR/RfFƦ[c)]|uD?-Ix]3 ?,6L:ۺ0raWa}QkLVA2zaZ^m]=; |=zzCeZWӢsXh PkV ;:16lE={ TЭG̀/#/i-&\4cKJR&X EOisj{mUpIV޸.dVnHE.W\uwsQԢ?X+`;E_ϱ$YÁ +!Hq;Ƣ k%ǥtƎ=qF{vLLM[}W{ 8U+M[[ADN}/"o]曕\#c1DN♄9 $|3ɒ0 GK/>t?OaU$^`t; (FV6À~'%&/xxd0'=[<ÊӌDlIU dYYHuM-BE絒׵MVׯ O_H{V%hT m $Jڑ辠J _z[g<"f䮨߈6l4qٯ6 O1jT 7l+yM3bS ˻G?~ĈE*H=Hv 2Gޠn/PP&22bXk$@ stPphIDBX1:Eg%ʴ(M<#Tj뛺|'zLSjFs!`~xdu4B QN6zyw^4477}b_:ww9f|spď)B-wF '7 _ #G#C{eՆ'1~&IPjlb4 .ъbi$QJYtp5x[-O?"5+kvAumSW| oZABB1=lu,S /3L# m( J讓l_=zӣCgz.';zԴfFY!YQbݰz-qFYeUyyPZ"Ksga7{2 "RXvrdU̓*76׼^p;^ ) ^эZoǞ0EY`):rXD#N,ZE9pU֬*_Q/Q0]EP;dV}u`BոU!]U{Q:YĿz_,{$¦q 3kZ2d^]FX"W+F[|G[d,Jo9T@̢= KsgZ}gHe s|ppFap6-Ԭjf (B u b>-]R$˲,* x@U׬T49 e⠆i´]8ptJ8821o]:Mt |0BC΃\D8];Β𦰣}g*@PZq{U[F_P'2&~WcW3v? COP~ LgNP;ApC\EYlg fN KFj{O3L/btߘ/Diˮs#p^ӊJV#! ]] %x+q`GA=;bX?t]Ni>M2.L JەQE21_La^(ޏZ̀1aT 7;\/ c6E1BRN GzuG,QnWRW@!Auq L)ʬ(Jw`ʈgZ4;oJ #"k_ ,춎%2qTm+D6`<İ+C@Ǟ3$}s(e9[7񵝑ޞ4Yevt xn?'u$c\,˨c.k݆vX]]}0m\=sF?B" pjm!T+28$񩝀fkUmƏS=^׃7P0Ed=w(p;Q#fUac"@@0dG ~ܩ |> YH,Iq!0޸qd3bPFZtB0V.SGmwּ- &t@7ѵJЧ~Z K K$]/W`,Qض7ytERpW"͐aYE  Pۉ{TbOV33ܞP5پC8g=*E3]Fr`=7<]Y7w_2Rns?3&tX ώ6Yynpuq,#Ҹ{,4EpjR:w%t$מwŢ*/:TZ#ΒqvpGa+(8?5k2%:ApMݦq4V9Ln6mN55,m@_n,AZ &¡&ݥ(U.W2n;gtD {\ &@&Ё|m=Q*4D͟"rVSMmAx?6d٨վ"ĭk/yl{` +Z87Mp<}<2lܿ0/VNm 3>\&xMxF6 Y*qRsq8UdtR[NkxcoJ%qT{3sf5N„̆>b^̸xGMX67ԉ/skeT wގec3۫C5Dz"x#6[hh3S&}Qi@r*T {ŀUS5OɡX=رٍiƜbTgwTV0V +.!KHd %bk&lCHҝOQ#(u\buDrC׍Z۬=0 O~wZyj Y).>''M鸶 :mYHָCRv '@!?AoՌWR"=w^]-WCS煳jG<>3qh' :;6.K:EuX( ˔ ɭ"T0z]YVڻC7\`9ۣKy4TkpۭX~L$`!op&0[ڇvkI̡RteJY Ϻ\t1 u܌ }S/Yq=J,&h666b-' PhVĎpLUIq&Ta?kcR)HOy1ʲ(N贎ZZQxw`BPq2'Xu \^[,^w"M8 7XN8Vl>X͙eaZC)BDp[`D%X֪A S4'rQaDS ^Ui 5Y!s!-cuP0J€-mk,{ 匂 N4b͘GI').ڊ@A 'I%VG[#hBXV|JsLJDMFK:qv>P֙@Aols}:6$X[\>07ֆ ܊ w0?7<9/7~o4tBq:хqdzݼd4,CrXmg %prߙ[M xjYתfY 6{+q~Jwg,~fXt,¹MU (Ņ;vW)"-=ak*R nF cN"XAjIN'X()*^8X60=M\'h؇#i:x{]!)'z?:ѡd%Y]:)T1s' Eg)TŽ&-qcK0fxl%3Tk׆2~ "D^$I1%YT܁~K4{z`)r58a8h]%ѿ+UL82wh5&>iCmL O`z)] 5bh68%@HlBԢId ԣ!L*#*5 !遆nGE:w *t;sߴ "㹇mꋛ mA((hT𞸡;L0c2ߕvd]6uryVﹹ"9PBS h0AL'\Հ_-OcO3s b1T|"1h%2n.-ӟ ^:Ciz~ݴhإYzlE,sU4E%AwNz~ZBdu'; #-EJcwDNXZvB /P CC_4Otӏ_ 'Z`@^=WG Al{1vv/!y 'Ru`ɟWdUP*d^mؤG*?j.*U2.bb[+Tj aܙ w"S>bb?VH؁Rګb })纮l֒jUH"bA}QP3BnKNŋV;{[Ǹ 3@7ftblNЗ(V`ZAv_2\oYB7(k"xaxbY~cQl9#L(&XKd].juHh"WcF:ae@}VdgM( ެ!25U9Deu h^w3ݽ˩(4իOQxX]@P!*ȃx?Пv<+`\>]܌p~ù^0ĉ&KjfZ凿V=IvW6Dsʣq#n1o8V~v#Rgw S~cB62N=4{*IVdJw8bPO2'&ȱs)mL4УBTk~)(_*+ZsgMU5o=5n^wN3(TĈ1@wO_ػU%ڝM[f?OZ]pUJZZ]4PDJ7v2dtoȽBp2+^~_H ]#l+=!٦3VX*c 'Uآ .AʵMWBTl7@G7}?" ?%<R~~[NmθUFS LVRK=[mSH\*"q^$2bXB6&G%t& XMGE؀Cu BOmn|r0j7Wp=Ƅ+ƻcCPalDq>N]{bfկ ̡`h@G:1-rr״n0B9Dj; M|0)QA12IPm?D ߆ :!"ebkVĠ6JBڡDqOkJ0qipݠKI*@>Qt7X%1Ê::_GIrm\~r_ʾe$N(ڼmc4nA+/^t߁ V$Zѓ8g2)p$^ĢgT46^<#fppL j0@@at]JOPґMN3ڇk=وvlU$4*O,򸪧t~.s)@4N}A+ .G U(྇FQ[Hȭc}Ѡn܊HIѪۨ?E.`y$FZ3Mn) v}l", T8mam[i(51 "׌WL`xw6iQYkh*a 3ZhGXA<2fblV]."6\G}+B' zE5-tAp.[-fLU[}e5#=WR S|\?`C!b&0no5Pv[/ BUǎ Ap66F"zTtxBTB73 \[O OgO!H_qӘ{!)xFƘD5?۞ZK,-b㬱nػU'R? Yaty`Ke fi0vV<_ 7XkQlnS1-&W蘟+Hؘ|pWybp4E+f%~ $kZcь[x!9GQeR8]ϓN=eLsSsYEv$Z趫M1ĶzKfbs"7qC,nE>'osQȿ]_ݻwVoIH  $d%k2ڐK,N`R*<5;y0!(q?ԫ_ՙށCe_cd_XHzowfI72VBl\e}ˀّ [8pEEb&045[jq R$<-+ Dp~eDAMg ˢ/d`QL&뷴=(}QuC`KyVĪyf~NX5:pV BMJXʈCI3-x̲.nՉ;}88] ; rFGٻqۯYGm) 2K7·qez b6'n4A'o>Naf]O9Y'BN6xEINA;JYC>G;KR7y!]$HL5ˌ Ai aIp,c4BlJ?xUTxoݺ]HyۡЍ[%Ecxۑ Y[6ܫ_=@9A89 ^}U`5B.!CRjchohV^*C'7PD҈G[2bJXݵapPffi>t#'}Ni1JZ+26J;7|}o;Fܵ"ƪQ{2ׂXpWC+`}Ft_{Uo85ͶN.A ;P_ H n8{K'EiQdoC>Y]mdnSlr_n7*R1KK0йے0ҐKy^mg1e %C5cJʆdX#c~( y)hOm5T)XC]&ߖ>Cjg -}rϸ5 Kefռk̏:L_-m3k׼Ǭ̢h1")Q '2/(%o~1f7=k'pVAw&1Z?>\trjϰq& ^x4m:a;8UZy\i I K;μ5% UPѴmYuJW- |(ZW={IR)i1$~Fm Rl'V_0--S UZ*ӞVqT\lvO,K qjq5Q*6F"U=u 6Q[P(b#[ 23C[JAxB;˅x5w^fCkXD:"Jf-'l ِېf>crhZU1kP%1T2oPmv7>;#BZڒJԮd0h9|2v0 7D t~R ЍmM6s8kS ְTUD5Iʼnxt& a}?Z1\+eůuiXB3$4j,'?**(D2 ]`\Q8h> #ZuYyYY!s^6 Щlp1+6[ح7 q47c:``! @WV[V4"NA"|F$\&ܽlc O1W~*#'` TX9BaOc lU-5ĶEh[t ߎh[}rIuQW'՝'?5e=IE3A=0HAq}FtetRV26]%T2KZRf{@" 9<-U O-t(l]33V=wcGVzm=~#ڟ,u9VǕo ^f0i%ܸE09g9svj]lUE|z QFH`{f-쫊Ľ+3J4 bխ];Vk[uZu)XAL5?xTA LipņX: =_._yÇ00_Z-/Q)5{i{{T`׻va%duEUy6S"w`yVph)# 2 WkŶ,㫛?>HK#ڌH$َDձk k=8?4U!p zPyފ7DVШynfy<ץ fXOR R٧5jCѦV CL#i:,ND2eT-UBa„\z>zE`AǪGE;hOQb0) x`{:JзW1(@TW^3U5o z0 Q+m: R#~lsڎJ@k&-"+f2:ꉚh^2GMH2dq#B6T{0CV-uOwSD}p4iKCѳlA|}]a}TOF/tkAhg1`{3+U3s0AUGnM"Gar$'=4Y T㦇~j\ [L1cAiF2kC/ F+0DP dQ{G#Whlyy/Xn.P8|ɐd}m>k^C`Vm5ڍEьyP7>IV[,gXr-en&`S?õS\,rSvRn (.d_Ŷ:*ueLem/Sq8!pTԤbe:Fv?q<r[ F,vdҞb| `=[wؼ.s1'еdc)lÝ3ۖ*ϥ; Û}6b-olWIוs3޳&f9F)'Ijl|[Ei8\Тxz2p|b~ D7 ؃I +-530RN\ǩmXdԺd҈iV߾z3w!SClƐشmf (4? $ء%"8:t tW|ATE)Cr)?m ;b-O>Os%Q_ot̎` į7|Ud> zS}@-?0)!䆱 ۩`ЫEgOXj\+X(m`C{C+Hy[^6 =y뗵`E["/ 6wOȾkV^ikL!QsC)9ZgTdYsmPݏlco{/zLa+.?EXxG\Vck(;2+4ji6lPa<ة )#2rm_jz t4g3Vv`eW}8$UL|wM u$3&HdN klS 4( 6IQĦq`x"cJ"S78kAI YEަ0]/x@J|F`V=WCQ7 ڠs`ar:tr+/ֳV^nEyU&goS>Bi9uȎ–){Nɭs7f>U4:^s{H7rVU # c((++kמx=&{%[z}YiMG6"L06Pk׼ 2=/kQpxOdhofEf,-*qc܎u56=sFKQM4tz"CnqQ_xqM׹•GŎ5p[ˉekpg m(bY!%Nך4 !v"',c%CJA-wѮUGtbxX*N7oak!pmJ۽>N| ?"A(XXΊKPgEZ$#EMŻ,I ݴD{dyȞT8gO?+ Y%/\ ?,{{ͪNաS A_kw-U?,l8A Ro@ta.y«)YQN8MQwi r5FFuJH_q'w|ru B˃%]>T>I={ѣϞ>-L1kF-,"@ƜL6磼2߼ TD +TH Iskq` WF,90^A~`Z;9I=Qp~b#t뺡KjTCf;oݻ{ݩL1vݦYJafu=Yo{T{*W8ò׍t ?8Kg]`g}#0~!Vq"zlT(*kJDsW?)GJq&sXluMmkppNp ugShw\A4Su5DMe؟V۽dž^T܃?'|ƠbB+4go(;JR\J(%9R1/)#ue|Ҥ4ffvGzx)܊_Nw᫡yZȡӪ'j-2B/P 9n8Fivr/M[LMjo e  ބ\p_G;Qއ2 [BTwӌڏWd&ڤ+Ӡʽt4˵4/`g.owUX`/E(.7J+fAi?@ (e'{awI/Oum֪֙8U7C@{P n1,ʌˈO?B-ཱྀlTwM0%iaf8#3{X+lrdqtg#IM'#?O[9r-N'?O 0K|0D$ JSE ")j©8FBW@Rm[žNT67D$Yg. RKՍcy`?0 rK6P=kq<2ČQaU*168SW $PíDFcg&\GX ]c(3~R臬<;\<;PR˵$dETl.H':_L#i(\ 9d+CXG  KME҆ I#f#%.$($[&]AvR(y:Uz)GN`An8. xj 2W>儠l<,q.5/`l]i[ַTG:+ t9[%_K>QV>8&MR/Z"rY]Ob73o\`7KmRa3Om!?[)@I*Х*ڎ3KmJYޭ,pGO21)#i\ĹtG.`m `mQ5EP)O|eW(B{Ge/PD X(־َ/W"y 8#B&ыːa@{I*ݱr̟Gt|d 8o1c $-aM8)O̘C 7$.mw961l奲 ^Y 7?UsmׄƵAWFCsVPdԵ-X3B]nq!!ɟ̘A\[W xv"A&!vp};={ {_".VD(*o1wZu sGzTA.mdڳO:|UmAA,!xE4қA!8PF\(U7p@WE*$CMLjZY;#K^EFy('/ N[P9R}Xۋ~3*ːkB'1FOWqġenPjZT ^yk#(A^LˢX@UB"D1q`8%H0cFh7esp׻hu3Y[\b° N֫9 O*e<SׯՎ 4cx茝Óa%Kvɸ /bmM(ֆ/& 8`[ghq\v9I-"Sp3Η52Ж FgUY/ u_,ۜx5؏`9y{o]yS+uV?7_ 壼>yE[G57㰞MC|zLBV6ܥJ[ޓ_,YyN%׸kvVkd>,#XշW0%ЩI @ ~ p`G >n'_--s$aLf_![ zCiz+ )$^C` ]6${wJԞӧqzC篳C#tb"p )td0jN{?4h 9X.6v 飀9?0.lzGQfYXZ_/ƿ.i›Jw{cXB yQ 5yQubq֨ R%v>aQ|}'8Rlt!Xkk'ɤfx.Ȥuބ;iEGBA:?ϊfi7:IfI`䐺65%Tɮo`J 1[{XD),[RަiP=,Gvq/Y+޽;yҦgTX3tWqp&| (Z~iKڑy+sG"PL+B`۫GV$Cg#[%cpI%NaՊ_i%wF4@#@R{b`b!elJW*<Q+7k@h/VP96i@Zܾ郻~z7n2 K]{]dxD?**H?8{i* ~`!MS@vtviHiRA˵Ȁ%R/&TI><9A -` U]H0\&b~\tsNUUcH۝ .r{ZlME`pǶ?.:rg(>|x;fhvavxo!~mB e{1> 贳6 49̬h{ees'T9Oh+k7 䀋`D`Kd..;  f5]Vgmj ;{JJǶH &@XU]P Ey:%JtAf̛E\lɳo?Kf&?ɪ[7k@.m6Afߪhxix,T4}䰱mgK\ 90?!Bs\p9 Cj9JA$f{$xgn9?l8b#tUJfsp,Glr35rmed|%jSBA#)B|,%DZJu30NU/tۚA\=6#<ojWH>3aԥ[ql%A}艾&޻ðzeۍ-Í8*>BIN[R-)1 GOQ}8ş kr6ÀJ>-(PºHu{کrJj/gϟ8fJP"dmY[kȈAwtZBJ;lq]\ki"H'+YWU U-]ġ􈑁<(hتZvrAdq-A$8O0ft𲂴ԉO>}!K Q#" [Q R/}j`uyYFB!Ut rA;2a;pM %ؽM}I @.(_K)7LT#'AuƃO/OܹZC0y7@z_+ͅzSKj .tkSەv%K4 <08S+-j X']?n%4{kEFSY>1̦~֙jGS٧NwJu1cw û s@.~ܻ}(j-BʹT!,?&zt=ڃ2 su;b*ޏҥZ[EukZNvSb&8c +QP+K! Ydn6Ij^[>w*:sgڶoO#K•KzU3wipeP҉%6%mz8-:`b6KЃ́]Xl?+;F^Q  ̗]\9)&cT @NeiuEO(e˞ؕچ'% G2rڄPӬ|odNQ& ' daCO&Kr7_u4d2%M0{/0-V-hl|^\zO8ۥOo N0P!%dak`?2#iE( lF^di(:x5O-oG w¿ oIƨ 3IXoX;`uFA_[Bk)EZ a6n'h xF.V\V~W$Щ%ra.ꖣS p9Y2A &ȊD\jBNsχ$Q}v-u=~$OxGvx:՞yu1~r@gB0 S1; ,j5 ,`1! ǀ>Ӗ<3By72qLhS TQa`x8 %, }VI>ɂ*VHΩS&)cKןQUR|?E3e~āNJ "h %, =9Ҳ޼`S fEg]:3c!8hRk1BN5}"1'%Fp1fD{,1(xS7чzCc"X@BQDdTKi\xZU& ܹqnm+<۸>82N?*DIۛyu,췶s)؃hXqN>j8`rx$wGF0\L w`U_E}qďܺ RdQJr oya\DkTl~ v5'I`ʽC3*<Ĝ S~CTL;eb\0,LefXv<43عdQ-g=arZ4IA%8Ycsck"̦Ex7UeASZqԔ?Uÿ {Mmo,x2ͤZ`>^,]&|.m)]a;s:&%7ndkv @ u}<gZܶClU(ȓx-8$j]kZ״E*NߎAc_qezBۮnz yȤkKLk$B1vi {aTM*_}1]| *2bR #Uju8cNpȻE &B8"G1c,׶%["~?/gm'fc3p|Z(-ǮզOQaaЬY(bM*zqmU, uv,"@9$f` YuG=X8ׂ8_fwuhI=*.o߾mo j h׆ZWdX5N HS)f7 c`Ă !BZoEhu5c 6^p;VM[%SEMy=׏Wj 65<[B#!}IUWyX=\xFԦ>h:Τ뛝.+{As‰-Ae-#ij!?hNO:G΂7zе堌9݊fޚHޑIX7j]g5Yq|yIa@So7E/_|o+"7oa.@|~cM[>f9Qlvbw\G ;Qj"2Zٓ ֪FJЫDǽbTL)uz}JGZ cyj#]G>Kh7h&;o~D\6=<}2WCmt"I< )4צOكq?1jg=;".}EgK3Ţ>#Ëkո֬bnqrk^a_`gĚ,dCQd8FBmTQ`2Kz=qtWZ1{s8v1GՒVHBv,. ]4$oh=W.Mez%q_n٣D*k3%,@b fTpMH3<8ftD _d0a6"@b;]vyU=ИvwnD}_]\ IGjALW 0R삉7n IvXǥElKO-L\?OaUĠIvz94S&Fcm- Yrh#𲣾-FxZl۾}ECHȢm ̥-Im#0?l6r>ޫ,._3N$jͥ^xH(P} G5^;yۂb$92PtaD1`k,GIkQc@D]8['탨ę aUk`Dq{u=tA&v4Ej+.PqPVbxOtJ5v@J5JΨTU:3bbǪ1s̨yBݨ&ZOiES]GQ1 =!2k Ί礛𪱏jWU>=(-dH(4V Ay8Li7 m2.ih䌖%hq\ڮxz!hfhǰp byޗ _順lތMU}w{C|3JLbo735z1C.oHLDNI3@(F[ukDLYc@ډ@uQ‡O.~Oݲgh7X ExsDuݝ,;6ep׬F{ur{Rq">nj0=ðYpa bE]bQMHn9NVRĻG#]P~U17ރ"H~7A(L5W}J@΀k:2\M|h6:T{ZAOusW]HU]86{^ fo˹/.'( ?A+`;"-e7>tJm}=x.S6^{4~GJr:݁l\ E-3|Kᰉ_G jĈI. ~Wh+_Gr8TۜV bzn#HLb *Ψig f1b7E1g-"ɍʻU3=;b247)œ Ce?|gv7'u(I~:<ɡ`,|CWU:sݨFf&]~ymXop}UBoP_E2K{+OŒk`Hɤ+d DRi+AUہk62F眳/"^!sWՖ˥.,,/C[pecޕT,pQ}e^},>{oo˗X$Kk $yx ھ~bzrN ȏ2zR`^y)[e!bCuKhFlQցFĀ&';"!2:ڽYܼ^KlkoPԍ8@>~mG此jmtS0fuխ~/x`lTsxb8aTɕ1<^.〉ltF(ߔ [FYO~&` ԞvIxnZ{s~p Zy˶a=LqqQZ0텂)3S/^ݗ}1 mM3w:1ttf"-1]7gL媶zܭޝwniQE /Y,|hXMriV"j.WCe[`0<;o 7ցণ6 b21fQ X[ x b aՋ_uOMC $o i2B RYCw uԄ+- $dKU9,eǡ(6TK2h"+l]df\LCʂ[-I=bbN$p !#ASF_gm|P;A/>[ϴ/95:Ik+/UQq"7T!eWY%I25%ȣ{wI Ԡ=W,Tgiʇ=/tT}Ŋ&eB`\E% cD;%TyXKБ4aIt2Yu:r\Y6sj[/fC6 "a6@WʢF>[ÚWG)|z%K'4Ppz跹\E{3{7&v "6#n wwST[Ac0+Bk4V&hfZ Q~l=Sh/o(نa#"3l}|-]!"GNP]rDQ/.p@xГ>o]գ =.ԃV#|:`\˸BH])vYpift]FW~/)2.3%6<3L ٛl!T'y+S^ #~[&O o^MkbV`*Λ+r'ԅP|͖ݓkfQ wOխ[ 4\{۷>8 4@%\MpD@TKc[1fQ??<L{i ,/%:@%~Q/濺$w(xn9uv҆DluXGzy;gv#{ܸ~޽;N F-Mx4ƪ vqً_O=!$0,:.ؓJX7\c^z45ZBrvӱVhߺuΝ;c{`v&Q TTHꠀdhzȾfw =HFFeY2mU9؂0MK':BW<֚os3lryd$c*E?YކhEd-X $C~( cut͵b zvmKFpL}4T oZjc <gw"\ [cki'W[|4m,^S?(T=wv~#Z3¦xY,O'ez ASf#ab_?͟#*JO&˧ʡ-IUⅽ^Ac=*z“/_Eݯc/?dI}NFX#Mu^b /sj3sEU?d+uPޫy}y3Flly 0h_}_|V!|zzX?W23bj$Ze})*<ґ~ҫ^V^/ X#TimZ9f@τ+Ȗ"=LYDN@82]B_k[!/]-S\5%FKhmkIDATP{h"g\Я}S~GD2~5iyCoչd@IE6d7̱_1N4{/[Upô]V֧Cw6'vȞ.&ycx ӐA΍Fl5>|`jk$J U# $7 #kV9rĻɛn˿ [-9<8 c[_~eac{;^Aǝ⮭Ch.A|rڶ-~(7m/ HG(=}nҜ{h).4n(C첎_ g Zm~"  {Qq܎T!"{ XX`ۦ96:;lkS a7ȩZ%*uQlRiGCN@i:ゞyk4MIԖU-OR66k5%` >Ha݉ݍ!qK pzSb m>Ois~) )[@-uOLP!.5NlqGpq[w]Pݾu?ybGrsⶋGgפw*&%hdžȃ6)R="hw/%F(ɠ^*|%msѲvxonHr CWLI}S'-*xbץ8H .OWHL>TԎꉟ` 7c #m\?-@z'BW\_43ό>EߊT4Y3Ho$IZf>DVD]_ ]D.h!FU&\\z}3AASL 'L\j6,3OjFF$QtZ,e}vGS]KuwXΣ'Y,7O)?-t?\D7OWn?v,f0II!]*&ř8S~qPҰǎ7(iHVv af*1Yd9` H?9*8z @UiAvW`F -aB 69>Ϧ-|O- ܿUnxrM-Ƙt7ښ!.W:pҿ2@` v+G f; `Cc<l115ۼ\Ը8efp!ԃ̣¬{J&QNp3Fg\d4}8 RFv.Rz3cZۛW> QXKC@-(} &%\/TcĄ_O7 h !>3IWۚM@}Eۤ-k13H|B:ҋH'+NppwW:~_>s1m ^,(*eֺ3IH^ޓ~BaƵ)C@}p3OSԢq;c6TOfVUi*.3ڊFh B.{^W-V]f7le7:Or0^[bkEw.^:]I`*Ux9+,0&̮ @J(suظ65+JQ9kpWG.W_1+uIx[49MղTB:Tӵ[Y-Ҭddۘb5XPJ2s@<]w\cVvAEo- 5|cr_єQUpoeE* F;Vrf'tz _ ɫ #Non6M8ؔXP¯}k{ (7T"~{ D #d4vqmH3DGWbߒg3ހD\NG~Vx*/ed}>}{r_#VԘRo6r.KTq&PV3QAwTigВQ(sU&ahDZ]O-FIsAhum[ 5S)t1iI% XG ŋ&S匚*Gl}E{HjWV2NRt ˋ7o؞P28EU~߻W3T%e>jD`ŔՂT6B& "/mXUA,P*µ0\%< - }?)$T)R kATG*Gϟ=)45[tBNL}|-։|zիܽ $"ݺf׺)J/Tb$Xx-e ;r{ɘcmV;V6ۏfbUiJ4Z)3̡F1*vTX[ksՏ8(bhL75Z0xhXVZۡѴXE*{}:ݝOj$y##Ԕ1hS.hiw0=Q6K3,w mNf 7lǖ:ǘBa?QE^puGnVoe&qcY:&! >WA݊ڐ$dYW/솱 _3sSp۠ifd\g c+X QCwbǠ!n*8~iA Ӆ{v[hMD?Ž 1~/3 ɵ_D(;VnXˣh?7a92;<`̈9I80M՛7 ±^X` ]7ՎƢxmI($&ּ-jZ=UXPRf͎[i\B^ҬgQyG(|%h˹eС?6o dk8msay<"!E>_M9Bٴe@ǹc*HUC6&UU 2oݾ/I[s]+RCJ{m>/҃wԉozzMHo߿%o0EĬQn6iq2N@Hm24+1ABj)ڐ '+[h5ft!emMuQyy/wC2,P\..|yDx8:cOkWw[5f: 6ۨ'j= :])隞Ustjf4Mjq`!anS"}^U;xx5VqK#)Mn]q0asЛ`e|>\8&̓EV|/iu` A+蠏6$@ ej^7@մ|$E=,!y OLq/9|Bρ(+< /QEJ}Ny^(MNPGY|'^S^諀d-auyЉQV׌m񩶇V)USUWz*nJ[R$jr!GdP1 u;bMYwf"R:kx*- ے+32,v-/+|wMnu ~e&L^φxEL[! A\-o!| cCkK Uof63h\ڙ>$6#;D1m2;U(8JCB;m<ʚ₧hGG`VƘvSa5!!h:1[m$x=X%RGoz}z];QE0eٶX+նt Ta=Я7=gOTV Eb'ެ5_v@gB]4D?#Ps6*Lvnu(K}Ūo*K;ٷtN4Zc\g1PF&Dzq^,'V>ce/+CEj]1'x w=$J4c_6g^}oq]~ix:|N k[0FQ$0t6k{= '_Zi|^;!#>B7j:a@AuVGNh,Ixzvu/nWQvtU[&Y>8uAU Q8_Dis{әL;p?2n\^M8 &lKYΨNDkS[U lBc@8v)I)ʿ'ga"'N7uW}R";V8wcvʨ{)`1Y.]3hwFxqF$UEx[gAGcПNl쑓"phv+IaiϠq:Y-}Cܞ=ԖC#Nx݀NÔȾcj+Pc?7p~Y1r{ Q#꤃/TZ#,LW ETUSߵsUQî@u4Vh*0Ǎ$2A| g#ңͳGqU'"iVVӾ&Z5P tIe4<\wrΨfWB7۟V;J^](%i.FH ِYXb[ޠ6SyRdگB4aUEGo2 -|S_ 748+]E+:y{a @ "H8 OTs7V PZ+ى#etwhLz t_ց0k$4XS73 3E.f? x?a_1(09m'A-7xjsvxs$6"AހP(Q֘YZjNGnπaSɆ>֮ks`«o|$ޔa0uO w Y^J̨7C#ݍMs7idqQ-`{ze#D n;Ytth~y#?Gw ϖ-l" 8*9zU`3ާL0eP,m'ѵnBBJJ~(z0 U{[v!%J9H18oJ_cj|(A@vMSn.#BG?;;Zp4lSbR@ăW?oĪ= o?UF˗yeӲ.ĆSW3EXKV-muUBWRwgeӣQ iJ2LIg,0uYc4̇—z{ڶm:t- +N'AwO l5' (5.jL& @NxV઴LZ8 eoZsB5]I‰NC_5ۚȇRH'&(O)B(h8k!-U u958T&i gmdf3{g2RB;rmF0awQ@Dže%G>ΧD ucxϷ7E mtxSVZ%}^qa ̶|Jd3lE5@sA DB,<G-O" iF|o+HICa-GgX>97U/ei3$nz36Y-Dj98C y6uTtEv~ysqv~~UVi7J=%j^%pY(”`½AӦc -HAL (]Z X#y$Vy+" lc0BŤp 2N-:og .zϞ=~:>ԕa<3)yO߰GCxYxxV,| s_*l|d>Ck-BBwPrM ^Nϒ3fAkD:aG LR}kpeG9Q ؚB$gH"NޑVT_}ѣGC7n #|CfBؼ1L&m"ߠYG&vmEuzb$8D6G-K?<04{d*뱧=dtmܦԳ>"[½5cpvG7 8'pApg'BMm=A5[+V\$!BOӣ9'0Ŧ Ѓ-9ʹ2Y?ր]_dר抛&xH7%Q!p\T0)~~Dm7M`lcn8̗z w}01~`!2c""""Dߜ0w]]^_aMA#>#li Ie4ԍ,cBGIPјS﯌Y= 5RK7KpqM^I`N9Ϝzcl^KCYT3{wY \ !, ġuS6[ujm̿} ,\:()q|uo^Y7W׿~wMyrP"3 g$hǺ2RKC&m\y-O?3CHRvJƶ]({_;>v/99yP_ jG 7-=tmpٲ3:nyswˏM2d5lg䁉w٩/j:q,N|2+r|뤉g?zRc{pRkq=ݫRl p콪tčw#JF/NoiЬrc[k۽NZܦԒ}$]D" O˕]&@C(v"pzz|WIחW BdEJ\5Xچ6xdԗRvXڑ$eѸ!QsǾ m7۱!8wOM:ٶLyeʮ=0u~V*Ba.!)L}6D]OjV6|Q{4Ա픒QA^#Xh6-6=z1E`ZݐRK'd+N4R<֞F'tk4ʴ徟7@YPYŝVvݻO/^Mf5C?|qڋC `ѓX5Vu*WwcՈ~~U ĿKk;a\!wN<8ПLuwÞUr@|t:{`*3*?aSIL |^::^DS^hyC(&.kxtg.'5U#gy٫A FkgrVr"!Y .[ `7L3U0`gnnREmQk޾z{)g<PbW;Vn~k^3}1'k]D]ײz'7tGú.V{w~TUkTT~j{֫Ui&=Oy뺪uu+$@'G]{~XpT;}d#6"#9Ϣ-MS2,B|V+Jp%;ͦ}{]4Tb G`7HԓP*9WSKT׊jhzTD޿[|uXubUVQHeUqbI8jaaBq^qՆEAFǪ{UGx;*g!"'zDXȂ2#d!iֶDқaMjCh8sVuFi~>VfPML!\$#Ln٘>-:nї*%G㽕ϥ#nzaIE͐%X#meN%*XYڨCeE[R+l 2U@R9{Wϟj!jR{4Bw&Ҷ.ʍ$lMQ!IOPڽͣeaVG|&G(pWH/2ugAC&>z1;POR!7} ,-PbU=f_mHI O3#xk6lg<َh/8RSMK٦ N$8pa E`hH;5,fZD]C~w`J ]L>^azsqrga>L4Vb:nt[ cP2D0Jׯ\^<-,ܸ [Β OW}>h%n_վXpx,ۇ `d",AԺZJ6{|e5$ߌ}j=w mCxW~t5ƃ2Mc8ݶ vJ>Lwin5k\͕?J>_|U.fG"|̛v \3ı> ` j=t6> ,4sĭ[ģ8ʽʟn%9Bw1e FY:@!%2yc;TU!8O!pphBi&6< gٞ4rd#0P;V AouɎl>)1D#%_XjHm{9| P npY+R.]7}QYah~QIvsOg;avG*'뫛n~8'qhܙ0 VCJ =y,pġ# (0\H@Bs5ru6^PUE mȚ 1MX3yHñ6vbBZC2KzkKlv`WJoNɻ> b[Qk%kWE\(~R.PTycV""4(XtTx$OLea/( dEKHᏀF aww%nARrf%Ow즭 F`ɖW%4mMQR¬:̸:u(R67?bWYh"HEd?ZaAd&(z5T.2#<#E,| .}b3VGk̆A&uF_l&=3ܝ!Ǻ +JT1BJ1ء$ZS`cd_s6?d mtFzmph~  hIJI1u ɵ˱ QNԹ}CdoPl4\݇thf(pUKg-d?o{;7&[l"π,6ahPͥyDm_@P;s4a%e_ ;l 7nۃ Ƈ: Ծ-7.x|,ݗ0I`ܢvdzk@oV#e1&̝coȱ* =tLa͑世&vȎ8LŁeĜdz m$1mfiY0``}uM1S'1՟ : Nc=CSҦ,)yYS<膑Qq֏6 {;†{j^Pg72/miXM)@~DTr=5fGSۣ4::MPi"E889C-DMHn|+%V9 >^~qG S6nVNN_/%Pd B5y5;㦰CB'y;Cd5Vrk/ܮh/m@`l\GTq*|\rbn ?߁A?|:!I7 &^'p:2v8tb>h@4!˺{lKtXGifY;ܣD+0X7 ׄ\hʰ:>R"UrEIz_ ?s^1ZZlČu^ $ḚG|(Ҍcfb賥bù;9gIRv.")UF]Y=7؊8pZIy.''Nt:çQdK>ReS$c'PDG 0SR6{_ה/["uT;0V)v2Z2?x]pz6. 4Thj(VȪF/kMD{) YY>ӡ[%Z4;muuZ(M9邗S&wdC M8'qIKaK 6 l ͊hnw(Ӧȧo.g|cyp7lLawxrmvІX ~aΫ 6 6 lX \fؕYq! =Wސ A@/y8@\7|R M.dxlPwC9N-71}}8"![v3ݳe$|CAYEn w$v~ np4bE!j7*Xd8RgIa7փ6r" 2cc*!>FҎx#9='x ۖu!c/ʴj 2YWH2h #(~ڧ%`5?E,ޜHGW}?.Ca( %TF|eH$>>sc$3%MrNGrdv~%ǻK PbMoҙ]N4/ pbAyL1GAS\!Q-z4]y" gJɯN_ o?#'ʳlW)୆c!At:`Q1o%**Q3b8PxU[@*Xj}!DP-X%-F3NX6نV 8]Q 'CB O# Q0b6ީ: 4dd[ϐ'ZcxS=~Vɝ}6і1 v։*% #`> uVi}3,@8C:0"hIG*B-&VTmWt2/lQн[zj,J!ʓ=7{js:+$rԛu֐~u(1< &ּ 3l̔qkY5&ѬuqDYWd4|>pDj$= \1hXQæ#6? #?|S MX-2D!D]ϰ$O PҪ|3Ja+;ZvuCs1Obǀ#Fֿ[:>* @sø*Y-v1#zYéjD"6C5JD?=WzBO0+]Fj4` =p>w}gU(weQWJSU$ э- !JeԂݑa 4``҂a Mӑlbʀyt_VI D B-j|{cBŏA` N/OׅgBk>~@҆h,$z6SJlnb0'8 낝F҆񍒹lal-VIo@R Ѳi$u 3yb8 x;g_qwZ1%n,]`\Le Ӽ&(;\RP/;<ĕk9 *WGxWˋ7秧:iyggguy*] mk oR]R:~UԲ汕wU|[X.zSP"Bj#LIXPdUlɎ^&MIX&@`zZV3b|2u#dɺ;~@ɾ&}݊L@3 C![qϓ'$` ~YXj gvqv.%HDR8T?ELi̤hOkJf,H % u`pՊz͖T'ꊀJ:bNdҶ9%` ِj' lEtiϋJQ xl%> ˵t B &Lוn M-Zdž0l$CdWWҪ_^4uYL57a( zw m0N׷u|;j'(  Dfہї7/&212O`/^ ݱ^87%ۖ 5Oe4snK.s>5D t0LGn^(oןQZC;@`y7 :_vr!D- t6xT_[Ks 8n!A&i337μ5TaDBaz0 vv\]՜/ޜ+B+W ,jЯ 麮{%-_׌'$~]?N7iݩ怜 k_q5l,`h %(Gߩ@'j4[ꆒLd/ !tÑK],P0X-}8>Xh!(lo'^Fg8]N 5cZ_G8zv¨,#y*j{ )3XAep.=T.fF_au֏GZm:iy7"&uke# kVof&&lS_IB $451KPH`11Mv$ڶ_[Mb#@0TA2םИCC,2=t(hH{r 0U4wB-.0G\sTBmL‹(!K)%|J_}E)Є_ı43%hfޞ43ft,U*"sw=B*H0ãJܟ @3|_6| ;@+O:n򆍑㡍!Fؠi}Km9v7MoZDo*w6F't]$  w[kH.;\g JHFc+l٥4Quqit#7m#4:,:hxp LCHE$8_l~H^]~jmY{4̗Ê{T%cē೥kV,# bĘ0a1̠ ٟW,r!BPwb6^ 8zMnwqMT XҤ.4d7 m=`L@{|n}Pu'~N(t;rbG.A- S>ty\GGzDLً,i 3̠V.3f^Nߔ=SO+ҋZ6y>ZG hsأI@IA.w.t37EgȈ3T݁ֈ30s*t'^{G,ƹtbGχ:cG8rLSj.-*s1(lR2/>8=W R:ǣ ۡ|@S9UQhɩ[rbWh|tV9hДkis-!df&+g(L]n)U:J8"6t8N֩ U΂v~+_UU5Ԏw^-;j?X)eYy;iq-'C2 q`5Gڤ%,{[#vC 0uggwfAͫTf"fGmۉkmCm\&RW:8Q`Wr6'K/Nq ?x sd4փ6am)ө [*w8 w;)<%& 0QcxCo2>-D^#_{S[)@b];wg8 칚=L-6l\kqޜ)Yx<xS[-췶oh#.CMxWjP ArPDwAcatnΡxpRɾ ;*1A@NJbyEk׆SJ8TEypW JrPT*[.eӘƇE) mAji=o"cFB%iF騊_ܿJN4i1FF6l<:{BB-f)LGa躰犊]4 =3ZIF/m+P&Rjc5U]}%:>+3pjR{ 2 A0BSysU3\ArlDävKXkU !D>9SVgF{2>W[vSe>L ^z 04X]:4k][FP\Mǰ*hmd: d`h (DWQ 4КDG\k~4е%7(eArxUf>]U-.׵Gx4&XCP$D#n!Ctᆾy7E8vՍɃ=;@FXi>< Q? Í6&d,Q֟1ɂj ҿYӐ"ĦA0k޸Df6lr\G0tų&:vz{}=py_ jMTYII1~ Ҵn.*Uk0*.mq*7LAcфl7rOl7!WD;8=؍ (10&-FL$ojhUە;Tl1?ݗn:ߤ_xqdƨ1C 2e#A=ڶC8oZ9`ЂBܴaI Ehx|ƭrPr[" 6AEu \Pv\ӡ6j)+o=sfǽKSPCv,ӰYb*b^?Ƅ´!3,"3g/Irݘ~Z̯9ßlg7aVxnc\AXpbߞ_W3iwy8ݚ3o08Ч:_{P&$ hp6s(7kוmfd+xy5ht;LJiԡH)q XmWp~+a 6!^+PхZ5_+py]zM D)`r *0)|A^![wkUVjڂ ypE/sA?~bu%Qz1I#Pb:B Dxjr~?~G:)R߄np9IgSAڜD7UX"WFÝ'k򺬧D6GA?mG١F#<ֹ^$E: ي}+;8(TI b[Qem=6Μw,K-Dۀ%IFahr:o]gYc(=av+9-j"kB6>&Z TyO>bZ M_3>G㦃Ǯ1(g+ d <1we.og ފR,<[ax:L,u^ b3\-A0aԭoJص̤_ O~,FNc᲻w1>BvKf]^isTfw_*όrt `5V5ŔCʫ$΍Ǫ胳.Pz4?BUtX|z3.V\pdں-ظ{IPM bN7 DeSqgK)QΫ QHɏ0 Є6czw@ 6me안A1Jٝl(m]kO>~VJ. rbr:z'!Zl4i2 p! ȗqsyBж&@kVI~}|aFI/_d7v/ ѸEƶ{o5J-:R-/_)y3bFf)qcSI= j=]7ߣrV:12Ӽa'뢁F3_ #k@Q*qhk[ld9M`+lX6ѷX93urDFEĬ)w n| !20~~S J/v4R+[xo{V3b6MKf@ &fc̠Lay)%>.6-Luzymo %ۖ#Zk{j^9]%x%'(<G2jE}ݭ4^ZƧ,;}q̫ĹB{׈Z7f!&nBbʱLMe?o?}Y 3:, xk14 DMOqɂP2 勥 ukēWu^)к fH8{{p"(rC/ɱD к~zO_f|h "kّpPB %^_00s7OBG4mPLNSRݪGP-XՈ,cwV U'=;3sl*~ԊCuOu3<|hhfayɃySJLl:BcPtФP:0 t|qh^ 0LѹPǛd-g,VP 1C \b, &5?' Z8L!f=TM9fʹ-|R.j}aD!ZLd*ߣ=ԃ [$(5!RCvndth !a~Tv~!L!Nµ6HJN2$D]}EQlP0ۈV 2MIT9N+x6.h0DKX'<l'"w8ޕ +&ޝ<*P*ɓ' k-*ދ_~֮|D 75ܡЧ\q:nÂ[ዋPm]0Qd1p0$ RfQ§M_=ghixNegvP<,s Aoή} =bW`ГΌZR>kNࠃ ִ[D9iz츢#72+DE`w+M!D&P-xs4JI", CKT'#}D] =Zx01װc{`;pPBݟA{W« "+ =kf4Ae~pI&pX:XL A':q]<#"S z__"2jּ݁K`_ofƝ':Bjc:_x%)1zЭRr@A-f5j48p?[h,h("i VwH?3䜏 -$rj@*v EYJx~oy\ZyyWo _WWHם5Du|_ւ ]̝.^R F1:Mlj!ɘ|v{]UB&v,#s"=$hꕫo!inȝʟv*YZL }՗ϟ?_ӧO$ "QES‹6Ya1ԉN(Q*? ?-E9t$y{Ӑ7Sl֞܍*$HZlXGZX8VSAF%N{NݳrC6 G& O()|di挆 t+P_?IȌ˂鉆ai& wϐFkIMM9 PtJ|ͅ6YX`31SѾ'i44gl HFt9hmt={2_ 2ĸ7W)J<II4VdFaqc4titX:k2=cCE:ZNF[q:|y`gMF{víVƛ AMJ4BԖ9=!@Dk~o-u%)DM.k =8jh?WM7ЂY:Q-QE~uյRli)HX[ZG G&Y] -5M_0:=$QWͦ=$BΰQ( 8Gkݮ)(˚g=j{OC*:v 嚄B$3C6izeh-4[O1]\{Nl+#]rq>dj͆A6l٤W{@UuR&: 7 Ԛa ,(݅wrQ=u{C (n&w2Uҫ5uw~~˗o//mu{Fu_vOϳ^ldylg am~xܯD:lnm )rg0<4{2@'FjJdKB" 8#jKSf&hs[`#Ifs2ClDhh]iTCEQrgpnBXC@47'[}4-!!u!vFQ3fV"c @ݖ BJQDn j,3_%CX[^lbS"8F_xw;Hf2s>Hx"C\~,4J tlPgD/ . jL 1qfNbB} cx` bbA ǏkħOg8CԦJ;y [+&Ĕ0,]/B6P^(?r{bܳқ\kF\6L^QHFZNTVh0ylr)H@=6~=8M*uY;3~ױτ;K>>=N ܛtXFfXp8%) \t*w,gQ`BoBVBzla\P?JgFoz8at/-}Rwd nwԸ{r~UuZ&w"=-h\P9vE`#0HYDH?6Oc~M?G2뮦Y=:1 -v߬ o,iR1Ke1w\<]D%T(Bn;iSuMZpDv&%+ĶBmhEA+%?~\WϾ2Ͽ|QE<+ ZtL1AەXgjT,񛞕(Q*fX2Ԏ0#!Щø3ڪ bJ$%K6^Q)r}R O=N>> aEcX/cA=oA٬J v/_n8F:Bǒ''Xat-k(KI&1H Q[ahgz2 kld5 %`c w4naL|fgl] BH>j6SGzg?yTN7Cż~.;vygߜJ-ڴ($BZ m!QLLZzsb~; 31_]z6lzj#8d5!m֑ZSX,&(^Yh!pTtu\_F5F---l3f uHi]t :NFM'&56<P[ImOM,PeT2Z*'F܀B 7waҗz@g\F|a^F1`ձCV*i%^R1qhx~H-6 ulYu~,&MgJ#I OmUmι2Uv7v z+@4!s7QAF~HӡGGXa`AaVoGwVMi}QrFk; se OLц>bdaLL*v|/S%NjrB j(`+S涬6f XfMl[[C<=S'jszCuBO6+suꤦTA[?~X55)WS \*PuP+lT]P}bޥl|/D~&DZNf:aQPDf/N 1{[qzƄyeV wiV7L6k@,YhIpceC(~ɪ%nUCFΌ`9cj3~۲K#0RDX+FυfUw1JwGG:rW IJA \XDEd%k^HuX%+B\fD$8 ך#%Bԝ"ڢ6$X9 W.//*NxSk}_}HբڋVP$#D)DWEI$_2ُYuX< (D==SU2*9*R¤~h{ ΀b `5`) (jBPJp,E^G~&"%֛CUm7瞩˵쇏uZQmh`@xb`ڶb- o~'2ݚK8Lii)IPuZ$c7jH{XT9 (f949eMKs$T1l0I&`vjQ|%P3ݩ!EW"΃X5]!v#cw!ES逈 o}WwhfInt{ Mr|sl[xxclM Z}n&63ހC၅#3n4 BF:+(|kͶQ Y#{4DK:Cu"4GMo.]N-B6@ In)G+n-v_t k@/<2C,MLI QO!bSXǧ#C3)h%THl ulJ@S:TyEpg^vtCAFula@߬Qgգ}bH'ݷm,WF9;&186UNe%T.ۑF/TyZųqSʄTei9{I~6PV:#lMMߑ;-)w-aM5SKyl){_n[)@a~$&&0N<0Y=ϙv =yBHvH`y$$Ǻ&S6R*+;3Rw?5d3k2{VW RFZk- JLލE.lQ3"kœ BWΨpR{Zf^3IOm)wu0ZpqZ ɚm$Zw^E NB{|E%aj.DqUR|\,|H9; 9E3缌k YCB`(dxUϞhg" 5_a Z^TKJhAAGv1A?nv( m ,4l$ " -FqYSD(Y o5]6Im N<~tbzVwxm7^OY_UNzsqvv.kyGġl$׏ag/۬I,KEBao!,k}lM.VU-^A˾g| UB~;H~DldBQ]~N|Vh^hB'Ǫ|>< KxpQjWCiH] h*6@iBHu&*i4lZTׯ}nmXkH6*nɰ2< @ ma4)Ш|vQ;DXq95͂s h#_&G\@;;39k_f9dhh=l>_0~hZ]Z&WeCG]ˊIq` ,94X^5!n:qe UGՍW̲L~Ba x}+Ȋdx"wTDMyĐn/hL1X`MͻKg֎ 8VI, u8)eh{G 6dj:DsRT:m1!k"-=Bɱ)20fc8Q1umzj pԖy>& tmfв. աXȩxPV}9lqK4/ŗ6ef ;eEdܽJ$fܭ|Wp6IUbp6aziHM1 LA:ڃP;hݾsjΌ\KǁD :َEmаf4^^ +T";#V[ky6^{\)[5KSΕP{QIƔME3^oZR&ȝPmj97 X -ƲƓ);VLL_m&r rSMi\5mu,HA}i[fMV^X*HRy{y/fr@l*d\P+y4 r.d/OUwH< C-V{`%{4tU$l" ;^."GWМ|zǚǑ:ԣkIfAǀ2y/o$8&5(?4N8E.pv8L/G2.QV3A Z-%LPp guvp #-s%*C֍UPsXhZ'~҈f1 }y.ʦ%$-i҂h?W2Y[#3T@ mrg ƠKD=hG-7j9衆sM%:IP[դ8FvW<\Q}#prbzf՛P?nJUj67j 44QX?1a2v7PHFMj{$m2tshm7{268HTې 15vq-LM˅A'#5l;!IK\Bbq6oi` ?~OÏ?ݵ彌AQ,{b90oCPAKCTfofBy. Wqh{iV('_Z(\mQ/Phjl:B}?z[4\wQNM/>}믿|IPB0M 9Xgo4eyԺ֡h רX iY!ZZI s\q,ڑ^=@o^'q4BG^2کŜ Nҥ:PS o$@?D T O$+h51ds[éb''*OU2<1 ,m?\dO`bUqdb1䩓_>s(8L2 5( {*b JʶK84u%tB}t{_[:jCͼDZ0Q+(Qǀ Ǚ*qM'k0̍+a7_%2@z/v#U ⭰'EX82XJb"uRhsf"Eϒt,tWtf| lt;響/^^]v5OB/>QbVH(d7Z}`:s7|h ZU0)UDR=Q%P+~?w2~+)@ fN!*\q:YO~m*T`6O(W҆< S_u0$'~u<%SC| h hZ–vR*` 8g`zLnN} ^@7LWJ8t재QwՀ26:<,= bF6.˧J7 t7ho-Pie!qĪ ĩ٭;5Z- "R4u715[W(P37iǢ.x$A +æ lĵnabwO2©zȆI1GQ{@*D0G7dV|QG\@a!DL`5mn]4Z:w6w_xg[{4YDMl#G+df{2g#f흆*6 G#ti s0#mK4O1V?x7cMhԻ~zÏ/j'׻dL,X TEJigFʔS[nKi78:k'yͧe&%F%ў^5)9h(f"/vd__mYLF'<1vVчc,9 ~  w-sʉH&Doe㕵\!':iKv* )) $!1EC״"gᰋ@ssXPw&zd"*a--m׭?~aYR0 Kh|F ad ^ Гp]4c)Z;Ni!*LVؑĸ\BNZ-?*KI,Oא9Fl:PC,4pi3DBEБ_W_KTj >j%Z%uNZRh_06!xȿLB8bsġ:ū:cmo}Q . ԵoYP+=PI+ Ze9Y`nBX\M3 /*ko@ᦜ֜62!~Ok߃[n_]_N)48qҘtD+G-&ă-måĻ5!X bKF]ɀn0L3-[DTQ10@XW%;zXvlQ=p6YLQpև- :i:ƌa#Bn D0UW@Ք^1q~,u>ү2՘6NjQ_Ct|u+ ?D:(ٞ{ZaMe蓴=v1C%ou'6 &$4 B9 $y&tˍjp¶XHg_Hs[G^_!JqWfbX, nMy!eJ[/MpcVIO=n ߽u۫: Y+ L:$rggo.u2:r<+ )˧= a2!XM ;o:Q*QKbx)#j3UI$*Dw-_kU-uc], X )߽wR?c}|77jr,ϩo`f7߁ :Yب  ҂'Qtz6︂sD9αo{K: d[;NӜy0k ;G?XL ,w5EB˷ޜ~U5^2'!t8d8a va!LيHFQ93,6)!>lΩ]YueLᏑI,ddLQ3"h%JvYڛG= ɂ,JebBt,'>dJtb/2~݉(~F\FCA j󇢽@cMo~0 oc__A6Ij^ڏ'̈3 hR˧m/C|x۴}=IPT?Sn ggv͘I(G\|ceGnIWmP/&p뾡#3]4F|: L[܀9AC3LnH%'gY4LfZM)z"=y2̰yc,[In?:+UW{JqJ+ss/_UtL+{8+S|V(` ?˝r&G~S/HHCŽ2䙾,x\hi[ vFϻ KӇI]F[N|֊d 8yvt1 {@w xٺׄwbV3GL+1K02дA** 2hqcY%_&(oΖI'1O 33F!Azxꚴ[ =u٩) ɷ1%D(=5QӝTcWd9sDz.aD@ XVB--8lXXQuhK z6X1'Zjr| æEJz*6[As\vqۡ)`^Y L7grszѳSDm}ŋu^tWT׵~˭.Z+;-zP'\UX GB:=T_~BkzYP: SsIF@$ ))B| Y&8@x]K}DlV M$VfV/V B !a*RiCVBXѝDhYIwm42?5U&c&; | _TTGQ $iB5( RG AJV-PՐ`&Hj#O6p|;d*./=|Tya:fnޟ4ۂa۷*Vۍj3HKs-yzI QbΉ$HVC %:S혀%:{hE $40n kڔBR 8uDM>Eyf􂵠A&Ȋ n+qU;Vus . `i$PG~VځGJrƗ.l &wh7kc)1I5]Ӽ7-BC@1wT!4׏I5RU4?>k}&<2vׁuE!XF f`f;gֈBUmƗ"l Х9ܳ;xMFϊ{j~~C }?$Vٚ&dWrRGaP`7r]כ~0 Io"^0!CYn2XfwQa˨*wFMܓ6;wzu~Wb07#.{#xp˥WD2ѓ7$xVCDaȼ=ur)M<ņpa232λy<`<[] (KM\X[-+pѰ 6 ZfGe7AƿXIDltUX&#n{fƤ|Ʀ9﷍J{dn|LcP3}р5 fL NpTK'JV6vX S;FdځLVڷ۫/߼zϟ>o8d̪AsS;4 ,S>23`"lX&ZgzKM`'@hO_2X}9+d.E8@2$yOQO2EdRŝooLar&phX-{Ul-CƓEM[4yFN \@?T9,'M*܉r(C5|\ɰF7;׵HqR8rM!8S"=5#{LP*zs;O/)drGc٫+f.:TaׯO_|u}5=|qy~yqն *mdA'LF)&g_zRNLFċ؛Q97vLP;>= Eu]{*TG ?{:B:*P۶ *q32SQP +:%]ƵtKvsݓ 걂42j¼,t _ ;@- A0X5gpuU m}-(ުNBYYE1N\V 11DMn_Жu6:`:$ EN[Z_7Oa[&A_^;P,N z(nG=3@VBh|z*>'R5hKxB٦׼0n ZjU#y 㞼(鮋aSwA'cgϊ_`&heвm&ޣAoKPmzj5eYaХbœ6`sB#40mKi;2h(XSI#Jwl5F;/٘ՙq7\ϧ9pӣDHsw"L2P6#Sx{-TG[$q׌= 7'/ mV:ڳl=ZTkS!0i1j!I&@6|v=c܍ڮő/H>qly}nTx,7jݡ#6ƆŒX9J r/Oks.U%Ȭ4Ұu.>l: s⭂tY[ W*'mɤ`{ӕTv>e٪kDJ2PQ~@루FT,ne~vP9zx`;=sKm77@5NƃS6a ZL}|HJO^}zQ.D-ƖIz@6ԩ (l)NJ,$KmAF3;7I΅ZT,o .` fܑ f=vE^-kw|7_ՁL\9=w}\do(@GVF6Hdϔ3dL4Ը!3s^3{cƴoƎٹ뮄oz,La ނPฒdb$1I^opB9ZNoE*&lBq `&vFqlOͱ 󑈁 0c݉{8wUbCHg=8gC+*P?+T:x Oue?Ó.@룈. T%kz9-|鯝^cFtBz,xq Z͆ꦎ۫B1p^4,@?`~+&o^.St9}ype@ 3(J}`:C) 6f!2'=)Z=x[./׮ҋ #v⦠Q,uD`ֻ2!%/u`>䌘- "-( 4hZOYXPWviZ3F=tmMZ[%ܤa I<]Yu63S0hQZw?ĕ*;&STq XhH/OVY`º2fhýKΓ 'SJ"uvq[jh,,2ythxzܤ7o+3˧o*36ЏYchlʎ$ؼ%km*~ͱΊ`>3)eU`{uw#lNf#;Ӥ 0*QC(*BSٳH>}N<ͅ3&r^" m}&ρ$$Åioq3/+.[dʘ"ZjU~a8ISOV:iWs'57ޏ#]廦Q{$鐴R'NVq::V*'VR6!$g*rjPG ꌄ[gPج:X"*pQ u_M: =5- `4AS| ) /BNd9))2jMdX⻣N5 }c}QM+ɳO+P / #0ۇ,!v!r"bm3TMp2Hp3 ,hG^Db vе8bp2 L鸍ҧ?%gyYUp;4ؠ=EoC`$cD5YPZw[v &"ToY 0Ed6-xZdRn :?Ԛ:ڹN2UKr$  x .2_r-CB&|(iD"T ٨HBQN ˧5Ѐ,2洍  0(Ae\K՚86@xe @0SW*Y߬-r'A+UJ|o7oޙ&B9J 0$y@ݬIFJyRXGtˤ6U`OsGm2 KZY{Vnl|2b !A8R77qL|oYĆ\Ukm{qZBX7 k:3[T#[ 4=ZCԵ8(RMПؑφ*?G n@;VK@q;|;BD*jj'ݸ9*o}rڕg9IGݣFbϾ!7,?,껸׿WU3%&Ո98rx-=hR؄0P5 Bٱ^%rL'fԕtbn l>XfX;5J'[Bڜ;"7>fɛ`etkҽ=q3XfPT ,g}@m m /z@6vR 0Uʉ2vqwT i'_̳>LjEKO(iwJT9RNdͳAÃlK䛎e(ra4H,>c,jBpNF>;2Zh 6CֽriXx/͐nn  (跦H;c` >I2Ϭf,TӮ X 7ȹ' *~}ӄ=4N@Uk0Uz(|'7rsDC3tT|u1`Y̥F#$[!Iڔq x :8%^<rD/~'#$NTx}zv\ 1hI<9CPP_]؍n:f4apPHZ1x!'_-,\Mj~Ç@—NXxb kKG*Jx}Ly, -"+RQEh!~aHKW+SFD!{$N}oEOmqV[4Wrn}H!I0vLEi  aTM@,XlzxZmT@7@.zo4P6 :gU'd-XGw-[# Np5W_k_R^i+M T&򯌯 EYM9J!xMfGDMs-zվP(b*GJ!-I4P[H$aܶwn Zٵ'֝qAՈǫ7n)O? +Fx=oZ0#=MC+xHm/}ua!9[ TZtmҝƠsr(n$ 'Bi g [, G34dTog>H}{k[ZA.h{N2{~,g|{@8"T:h1uuuejEŅU!W?#L&:ťLje Եe Ġ)C!Y-sx1;`n8l^^;m]mԾu.-؜<37,B:j""./j̆yZ^ W{Sx.XxƖ6V*weizbQ9kk/R_Ns 7 o"$xnt[84A)k(7N~kqi[9Nf*y:/eD XsNcTU'!8݉x5Z]4Q3swɏcN(cc~{˱k5 b q ȓE. e^zFS(\k. Cğp8qEx|7WB0TJ&L ` z>PBXg1V%"_$@vؕ-Q((v`0|(yߵ_KWgOoMw_??Ң.Sk)2rT{ v@ydぴ=5Tb.ͦ8.x)z)0ثAS{Xt=W,&LjLڐ*5# -0hUqU`͡HD84=yja ׌1F Qs;=so`oY~#='NuE}k53DhmCGbĦd7Q1^MAEHցvG"] wOk@ eO` )ee0%Mrofo&AVq xF3M7 5# ʬop,t2@& t@Ϭ )l%dOUUڎOgr1AWHEZz`.5܄Qn'k:g2917"X:][hi0Ё::M- 4hFVxyznD3 dkrKC-ŚәpdвVV {e;PݵCOt[^˫{yZJ0-U$C pʀ~Yx쭖VoԺۜ&)izm0=+Ov5 F/w ᧩V7dc-4$DH樸 rhA!i $ Ѱ䦝%ѠwjhL#00曍LK6$ (wF)ݕYD(Y;*TrV[+dG 6+]YuLM.";s~2*}ũX曢 ]Ϧ}ɹu&x!"8fsT:AeO )Rg sLj;n$<[v фLdV1%uXa;MrB\C&(`g&&H#T4mqk%LFN fC uчըl~*J閰AL 8|G|ǎoDV{5Tg쫭"In3&ׅj^[>d,`g!1C؀.%dj DA1}mVHs h7'L8N9?JВ>u`ZTQv6M?J T_}!M,D79'*y#V~[ۯ oEUCf'SWr"ukj쁊!W Qrkvۡ*:dcι\r)O1@,6CzVΰ*˵ԪN P5>{H >+P-~+V'Y*cReӶBgZ"pS'E;a"rlnXuv7".P\V^%=h-yVLEUpPgr;N胰U9L'rjTԻhư7h JA&C7[œ'?S!FϭvxO5j%nȯ^%okVcC@,-ߢ@[@Trf m0 =J:HC/]k D+utIy:1H'BkS#z&5F7F3%o MM <~GtNfAA1&sg﹃(ZΧkJ k.x*4~'݅q9oK3tO LWo3ul.%g[m6-Nj "|1lkźe[Vju WYsյ3ǛUx-,K*3]kCF H_˗FHcf}`]Xm͎ꎋn΀l/T/t 뽞ݔ!>_M zM5"5JO |<:gKwX^ݿǿ׫+mgހI5RbR¦dj0w&:V4~ke';ъY6-s@?Qrz9P&ƨ4wTk /'* [ͧ~eڡ۵L><`vqJCGuJ%!&&`?kd̨q*&fMe?w?]_=cdf{T3v/:S\RC$UǵGfJ=\sf>TAܮ{kR•C7[!þS;ay`MjQ>̳vxhD^&~xS+0m`/+QXn4;tË{kVڋ/9.^LJ jZp@A2a //"(GrGn?<P#2ʣ h|hJ3%aV5q+lζ%+|FBtp=\K:;̳{XBjޖ5l;zR d+آs/_t fIx2 Ki5 D@F0 1I=kRXpqY ItCq QZc5l26t!e0iGIX0JkM59O*vlm+MQ7NTZ ~7a1 [3ClT *pEt i>D,A6$C%+r@@ S֯__~SRjKlk=QkJIDLᄲ_]cB^8Ld5yvtJ0XBf&?g[G4 Q܌}c<_uX|?8w?%t+! ~CǂPPJ{|-Wʣ3ODYT[Umm"C⮺mS1r%1A۶J!}\ݨRdTЄmeQ΀NVOD(#M&Hc~V,+ m t' m%F1j0,WmxLؘֈ;I Vvitt 2D,`YuzLjgX X@cVܡw?C~+P=jSSsAC~3؊ZjFL6_ ׽r?K~{wFyMfY`2_ 5Ζȭэ֛>: /Ҝ#e{ -4n72r64޽Tu6Y8)oR{Gd<!69%14gZvd?@%2h/1ͼʠ1eFA=8.>}wpuw5b}{SBhB!oWMB: ,PŁf|P ><'kRT j|믿KZ>*.]A>Ae~| jIiQ '+!LZtF2L4H-WfqnkcҶ`!jЗ']3 $"L谍XLR9Eⵣimyq^m*T)%Q*i] @ϪrVqBj㛒3/?Sb5"x fAp} #Fk&#TRD +BѺNrt_!;k4 @C2\IՄrR*PokL{:%QP)N^#r7źs`'-j?. \RulKe4nj{+9؜_{x`Z&c5=LX֯tu2u˟eп@.;pQh cmS LkW-q߮SƁ˜w,]*xbȦCmfo."a"SsX߅ip1R\3gø;9k̎η2ֶ n -f @?FɰK fE8'Afm;^\)< -5riUٯޏe,:=,[tT4%/(n܇%ƒR($Rm.c助*fOW9Uo9Ձt2[~ih O XX ^P7 ))lQag 2`.^Low?Ou} 4+B|e*O*ډ4o8>p͢E;yxm*W35CSMSÙvzm=zO=uAz&5*qO &lUc@^aʂDw,@f٫]iL>]/ w.a){%Q_b7Q ߤ3Hj[{-O :,+Р:ӇI4r-āgxTZc}& s'}ZդSmfe:4(׀@Z=nUU|uͅh6u #%$c$qtWon29"˚P5b 3CĮՌ]!6K <"4 m^e06*#kv#)*|IGۢy5=|J=h(p$i}͂Pr), >+P.qoc>hVDtL5H4g`I Bػp+7duax&6#C%,2t(Ud  F_/?|OR`sPz\{yTVK^fDZTe XIBԯV턜he IR? D!U ֏=v}*bw^RĨME SY??iHY.R?ڪ>P\VTBV(%?fHd1g1@jU5%2N*ǂ"\#=fvhHeH :eniuH" "A.63vU2* 1'QqZ:;Z_xFDE=1ufgg})"3Vdypyl nol@2KV;omoD;ZLY DKDhoYXf]MҞMml̴78=;:>@ JiT[Ǘ7_2K]Ǚ>VLm֨叵\ մ|xxtrk~t a*r4 iPIr(KAg4hl+L+G/'xl؉dC:r{f0#6t4?RQ<3Ѐo5: lbMmܽ+jX6 kmq-.)" J-ƩP5ZHeq #42zuFL^4XV0=p7g '==qU;KM*g ./VJVi@r!Bat#kOtf:wS'0{爺&Zm?Xl;]zQ5BV?mlD9쳀_n#db8<9s|2iĢY*ѷ09 }[YY`9 ZfSlt7U}|uXޗ_Eli4K[ Tyju:iuGGT~qxV B Ж&_T^4 ~o~(Zݔ.rMAuR fʑmIJ h,.djX MxbC`BX0箔UsRF~& ^hlC *wv dT|Qk#A"2?-PnBt dH| ״:p9|qaE 7DM4$  {]G]mVDVQì6mp1L7ac) /cа)Ԥ@g* sJ.;즺6~]Y `Ġ:h|V;6--,t' L G͟ ~m9[&G bb}ڑʊM޾ϧ_xU~8#b+98*RohtB7$0__ϧhuhH8WS \gTw|]h+_ 3,,F3AFTaΎo\{{jd{7Ftd#HP{n֕`y*$ \n*㟾 Ofx):#%̠H$º./),(6HTB3 t֛|F->C8t@CFk{f]$*MՖ5#{Ws, 4L+>y =il5(9s'h=O; S˞ bC^l@ .؜fW[_󶒒l:gC6ǪK7}e,b8†e{8i27Mh5̴QGBUt]>K > ő%HI0)"#mr283Id}*{a2h!/I7- ; $+>2u&wL&F$2S$2ЫN rB5T;R{|;ACan?pן=sd(Nhng`\*)׃@W˦QM/yK:!dUNu̠3ڢ]Π]?7{#EX랄b{ɲҚcϓqpgtW4 S:?ۋҟh@ sIv0FA:TEZ 4Ԕ[C9 *z)pLMď%ԛ#]|}\pr=A t &7 Yȗ#V6$| Thփ>oxjce SW5a+Sj3 t )}M b钐-?iV Rcrᆰ@7_?%#(X,8zrn|U\o.jKQ0ӡ|Id-g[\lm~>ZPX䏪aQ0pe"?U{Jvޯ>୨% f0+c]$6[b:8 "txw |_qJU E"~IF\DʌO>_?V6Fcd҉8PB ܑlb \-ʰuE蹤\3wՃF?󟦁b s'tn"-_ox]c4Nfa3;n?G쐍1 o _HȦ"x I[K'hzŋC{̬{g˔{K]&I\{ ?MPrۥ1V莌R1mx!>ĦMi]EЄ9hBi%|8_10bSDߺDE_^ F &mQ ;=Q>- ka/e}K ԈMxMfh7\rIc{7*>C}E͚IԂM '(VTrIM64GLh|0ܛ\gybD HK`|P+eYĊ_u䨕2tB\7*cF7+ 8&r qn(vǖKꂶ330;*jgp_*[ٖOiu M4?%k ڋy[y;M\gy!M<=|P(l ˰</u%_v\+f§^sҮ)2{a붍&pUC0CL9=%ZvLgXm׵AҐtYc x Rp@}0G3k;vmJٕ (}2XvSN!(ztnj\@Br-=Z}>Z'N9 _~tjv 쪶v>&汽]2CToB)9TkYpL|]Mf@7(y}A;R_ҷt+'K'0Tқ1h{$LLqKk!sRW6  ۰ǎ#10д^ wO&&KdC_$fpʕ۵grZ^0;[\ZXZܬXS>Z\VUOK;C2KWi.}ӫ 4zõnxÞSY,D𱋢a4{zpW]U+\ #cvY=x :ឨohzAVl2d~ջ۷pDQj-F&m.{я|G2"Rc)Lgؠ\{t\;ZxUgk0 $a{rծWy[YdT.%EK -ާ 0axֆ"t.c8| 49b] kXĵXxD"ޚkX8*ו)4=bg>}b4 syӪM"B$BD'jU)qJ4M BӚ~78ڢNW!D'0I![%f:6x*yC 􂗑jb<)D?DhTyH v!U" M,, xQ^eȀ([5y/?Eܭ<$*Z)PZ׌2GXn ,-TK#'"+I~(U,@q[rYQ)|ڃw?o<[ﵳ.b6I\b|퇆jݷWV[~ch X #HIl f##j9RW3i$ aV԰3:>1d膤HXM}]-B ŎMD#j:ûS *m"f&9 Ch"tCd}ݪ7\bf@#6(P>|X}@G>!{$/A I j"|8ds˂cu.hCczw+r.Lv]OƣkѢzTk YXAHϖ> +d)?B1SC?kc6v2[{S&aX9fB k4@ gUZFn߻{mF%ozsGXo#pX#Lc`ؤw*B$FBv;z.WeKk#Da0[/B/ch oȏfV{ 9Tx*9{#R@8S+TX|rd:͐ʻyuݍK'*vW:S%2M8Wք1I& #~|F=Xd`9+q7B+JP+J> ~lhXrv 53et&h6Kx=c_lb:2sqLP3OWr4֦!%Gb/83=Lke[/&&,5@iw Vrv!ѡfZQX6# "-e@<OEh}=E*NnZ٠P#(y$ A֙Yk 7rЁD 4xQbaeFl1Nn1/ 4Z0!*"⿣'ɉ:GFZY˫ot˴P;!n;^]~;kѤHF;sC{@cIdPq{8ӳ`c4llNANS@U޼>ᇿ姿BSJΞP(GN|QWSQ`$pQf儣UgHp-V]e«[NmЈT2lZ1ló[ h !3֦!Ũ^Lr$`4) 4.y8n٣0Dͬ四J!.ofhP4̧۴_,q~O>XQCR̒?B1Q|lN.6,еv':(lz@eV*,^oS=/àŠR:1L NA$g#l| z^o[Z h`E?K⣒^vSEb!p4n]~_Zs6kXU,H 1[n kсl;eySkmnݪ E5̺s>8Қ v䱴a}狓G'5I؁hm%Ͳ^]}8Gu(fe42b> _ȓoXb  ڵBS|lei֐l10 [ B7uB{bx@ݠ)j Ha㜴a?zhX~nĵ_D< mhDعjc2m4yK Rm1duuA&v˝-JrK$S8Ճp^݄Flq8G1EP ?R29}.@f+"wJ%4`?Ht y3FA5JLGЙz TՎ$`9#}h7Q}!ZjYkD8ۯ;V9`&^:Fbښy[g :@FNpL' h)Sxt3xE:Vc6S&ܩ&eȻRm V.铊NԮ.,z=@d#uz,G-uV^Ekٰ xM9d鰝D1FPv(di:Ya|+p(րzqwL I}`Y.Q&P0!Ί*ۅ)so}Pd+_~SSoG!^Vœh5'%Bm4b-; i:`Ddx'@ƷńK$0LGH 4)8TÅk3FU V ʂט32Pps-LPb83?ϔyE\8B|`B/:" )'K3>D^G Wpyɴ>UU-Ea_ev5!^o-Whil^[Fݺ1+BTmr]u4jFu_SuvnU-E$374Ѐ'AkcM*Xh; 51x-H,5$$j{O=<٩VAbzlǾ "  e VpdXӯdd#NTrn)ՊSھPZ'O*lH<cIW2a=/Tb|Z˃h4fv_w G:6iUfSV`ny5 ik=p4Ơ]ZMoqfV0Hjאy1*mۺIoa="$=;wD aѥ_""33d} @@!qp֏0Ryd70 cM4Z8dyf|d CCP~ |b4) -ž㐸iPA"S[%C+! ?*L>ڔ2e]\e{[w.JT)h\Z{q dcj`b˦\ִVcmt} sJh "`jWUZpހ D0.!V-2緿C y[)J=xCbXDŕhxSlagpCmb80ә"8r>̿]4Ɏv"*m2*{$WQfY~*e'vyQCL}6VB3M <鵆;iޠ2g걼UØ>Udmt*ݯ3>M` O87/3aXg5W_4" ?Ʃ!7"~A# oyЋ  0_,;/csefɸ{FŐ?A[^ aYrȷ@0%na@8.s M*p}H}l^/[^SSzg/MzVSLL?o IdgY<f xݝˋgd$IQS37j++LMUT9I~n9 x&'8)I !k +I|:'E4#P{Bu}X^JDTf:ᐣu~\ϫ /QG[Y HF*/RQd7A6"ׅx]uJ'J1qҊr'caI2vPhW'Oێ8).k*Y|=|?FY"a]*i,+f`͇Uˋ=}Qܢ,ίJM?P80p>gyvYX՗d%P:y QGn(h _tWV3o?&K0Jcaޙt0A_k}[ᛉ\ZUBz&'0$`vVIYk*}ݱ2W=&`LG}(𲇡W}5+UŻi+l y"0%Ѐ @X{9%{vDk@EHDs%ZΚH*Q3,aD, lOÎpT4OwKF.<3]᳣i-ԏ;mѲ Ɯ' :S@AV-\0@/g<LH#U+mWIYlTmpKڥj0.dQb+SA%GRE\B-*u7שuzWeȍS(I% -,Զ4rF(`fuox5C^ޣ7mJD헹+h( =USO>~P`r:؄';-'??y8i60{+;uSCnFAF@V=W{CʞXAVrX)8y=g0B X'il .~mÁ%C4@x&Ejl}uN1DCa3^Lhav?CᛩF)5tfy\_~%ЀD5Hw>`8B]f#2ݳji ؋Ȋ -Km3Mi `+ 7,p.3ztGr UM錆!X=󊃶,A4V2WYw#rk>!GV~c֏uF,ڼs4:`Û ^{7ș0Htȍxz2+y9y(-B˧n E=F_&X62{M *L5;RGj\(@aD\5'9;H\nIԫDR9!qXv[0[໚iDͬs+\|}?oʥbUҕ]Uk>pZ/rWugӅ<[clbc\maZBlÑfVtsU 1p^VL/].ȰY+*2'+,c19%%l7NH'o_} K,Ybˎ7q7s*OyW^01@+; }Gq٨3:%Ԧm`< ;Y֔wxDDzj8By\tد#moz l׍PƬ7F VKS`N@o#(%c#O' 0{Ӟ)B=+5#1T*7?"h&r '>BX2pl=!9o(ISqj˴ Ӕ03cG<tf&W#6Cjy gU#nOepEڪ5~P5@Y7!jrV-+D X9B҆G'^]}$$8k -eCiS+9?8y믾'}_.^T:QԣT5{Ym雀Mj::RS.!qEYh(,=ݨ=xsu⑔b,sb1?@5D4Soߟ_֖*ť{1'Gh~p%8[D2 >b8@w!OʀtHSq{HzrZ^90%䣸$DlQ)ƤćN B#ISwfz[<.%DAcC>G_|Gu挆ALTRS&ϤjD -UQv@blHv?¨ G Dm{'mzXMG57,ӎ%\D.4xf6ps /LnX `39[;>'%b oWmx,cY[OM45 .֭&'E<%-Yl}^/TLߙ%PL(WءH73b3cI`HZ1b#'v2:M Bi6[eqIJNDO^n˃ EBCe$n o~NJ,E?X3nvXofaz]Y:mԆ35`[D߿oޞ(_/3Q.Qc٢\i\gXZ]mJ4P_XǞVTF o:{n-:ޡvś ?Ú5?09|uJB#wZ5]D(לFf_P05'UPj(P_Y^tVUX)2.9̊v`)OHjf4zi]E腏3vYp--Sogn~#ejMa6QU튯\:r%V.ZrL2xd~gP5l 6T ]!w)Ơ ŘUx$wOz D'5uD |*<!ᢧ< YM\hhz~X4kPo(=P',FP9E(械u͑)8y9b{8c w>f*$b(Pm4 |r])Pb8bBF88|b0da0tՏQZ)Ic\],tAuͨ$姾yn96>9)ii]MgI9YTԾy!/1S%`)Da27[R]lv QKcȀiN ,kA1ae#:?xx﷿DoCNKW/gX7:ߞ/_U} #YA] 赇`Mײxz(q=9ɫN 7絉1CrNQ30k=4 |G-WWI{Wفnr 1tDy0ojry&օ9Y:㇏o?O- $6&a3Lq?;ukJ9r=Tq 31NFLŃ@6i8Mg(h5nl 2B6 W)3~ 2¾SqkYȝiH N<&W##pWV21Ӊɐn[:x'Nlq0CIY/٣ _9|Vb׬Hq4l_Y}NѡfI -io9a98 -j[_ s{`3".1؀q Z[Ph9Mp^ʰI@%+Cy0AL{v 2"&9DڧS#4or2»q^MvނcO(QV.j!C:]ʆLΰG=8c4m =犆!zHCfڍ@ծd3{wskBD]ƾb#Vs牵p~,g4PDͰ15x [A ",w[~f V\-/~w$ڢI;D|Y-҄جeJ'p_fMI@w˞AXuv9=l]{gU/G.ނߧLEdQ:a(HϪ~A jy{*`n 1`alf 豸>gуK;¦5d&J4׿E0`Ϟ2fɠEWV\NBNYB/Tʓ= ÉC!ߋXD޻J4>!HARjQUG3ԩ"/臮TC¶L#"̏FW.xf֦H.bd]F4'M@W\qIrɚ΍ؑe0bK!iX ) : t=eWz2ƚǢtK`edZƓ1//&OPVb/M[!::c&/ddKuơ+z*&W~9z秹Zh&_0bXn|i(7"|432O:z5r @Л~Nw /(-?PRkwiݳ2ᅕV!+Ox~v^2T@Eo] ~qlE Bꭴ ki%ܛTmDF  fCB6M$ =?w<.qzlQxPn&j3ℜ?U {% j 9ӒQӫGy)I玊%3 )k$#.⢒zy!Ν{2:"g$TFfILVԏzoHT\ډuiu^y)['R/I$ ٕY'sMbرH`}(ܫwNܿ_@~O;е2PVӚ%&E.'xr( c~fel;gpcb9X>=M <`&Q.5SiST?П S?%gSl G5EfizhcL5pTgӿlg2PϯĦN@1 ?xbevX e0`͂=nMPWi~v5-i't۰20]q&V[.UoF#\Yk\YَɮDU/O^hf0Ʋ(S,@S).Bo%VG^mrñ+/!/9ăQh^)bv5Jw aGM=ֽOh5ʳM7,j()Tϕy㋗/^4(v-[xls1,uvXH˦M,T3 VSWC?=:9u+^\0TP9b6eɚs+QNUPKQ(\LYFas!hPc@Q62se=*aN43!yOڍPBrqˈ:1j`rO-UVW|Z癷tTF2vh\ћUn ?oa zžWPl|.pэ-֌#o  Y{!19Wf2c7,>;NhFlx8O4hS槧=h52$1h8#9c+%r<"^e 3[7VQj׳T 3C2P'h!%6T4:xM2WEOH%#5z|𧣼d)J(ű\ӰϚlQ܂F!(U䀴]8z#}p=Sؗ4St LNi8ެe4g"76fG8N0YuկuH D%Ԫ)=᧏R#ERt8?T4 4+2R^#+Aps iΈЅn U_m`|Qbxb~mr[ߏ[lt `nB0=3WE3 ٯAP if{TjUq~ |$sm3|FAbL-&CFL>ANq^S98}湼`OZۼ{9|ZW "y]HUO2$JBF"9 {SJ_P鍌zě'N ZYDrD:E 2}ts8ҩ$mV/,2#oҧ h^o#5XC'v8'6Y={)M픽IB͜hwEHm#Ѝ^doUl\%M98KEa7LhIw1NUi1`Pv2<aݚ\b̝q$ŝpYźPI>kW>~TN4ݣtwuAO\,fimY:=zjN MUsMѮBMhx cl: ̢gd2Θ7fia6N݌v,`8${&2l{̜ o~<6P x;i ydLC&u+g$c mEB\*4L,v`\v\m 0'uKN:WIZȆy CpGw{ld|d:6[:5;yAaYT>\\W?}_qS> sO"Pˆ\EjiF;b Ҝ X-e}syKI~=ߒwP2V_Nاɂ"URؓԉ>>~P-zK:-Mt6g B48N\_։~yKveqIA>aWm!IG(( u鷨Twxo\CQ,^L:hr?z| `N{vy6f ]=v\>eq@1_y5ՆC͟v+}?n #G|9@"Z&h*x&+a603*l`EMy.VEeȾ;.r; w?ԝQ`ٯ(Xk 5? ǯ"U V٪#Vts:cwB3+:)HN=X |X[.' biV46#ptjjs?VEZ>P i&eF'/ EZGb9MjD8yo'u@C?[}x넏sNmiI :VPBz 2dft*ueaqN|g3lO~v߭N։RkVpd~j_umWuB9U^ў>%$X":B,lWQG֥ 9i=q!\"[ԛNjO{?OHVS$+@eT*ؚ"@"Q(ѤPNyp M ue_)MW ?"pRx-O>1ZKgn߼_?MA%,? AVm-aCXvӦmGPECG=X,RM7Wl̨;| \X@# d^&Xò~(6ZcѦ[68DZ4W$Gz8S`Dc]]hh~ 6p9i^ ao[#^W@h u.Nm2cL|!ε.ѡS} !ޢ/oo'v~i2a[_G3%oշ?yVByΛkuU엄{h" AgҊoɄs< /⵼{[g4UZvM%X<+<4IķY/NjdZAWsv2Pʼ6 ᨪva͕3$xFVaX3;AvbחdDTA x~ Gm;X$89zbx]GzEuŎi= _\0ru "8P9k 4 =ms&_DlV-%8hfMʏ6^ơS?r01V'; $vs$<:L!>ZpoN^&{@C$\c 3G\na$'E!9B}җt*}6+kSl-E<{x(ꍖ[ugq)m!YF$sGФUDh/e^l L wG<`A&/5[Ě泺$h6 8eUv7S@ѧk %&ۮHGr$w0xrhjC_:-d_ p'̲:I!"Xu^ir E}Q잹^4xx;`3 ܏՚8cѶ-|`|l`2*@d`2xr-M^žꇭ*ns)nxϜuzo{av0*PRG?GKbX>1ckYs ϰ7Suyv;PMμa]cg'n`eeqY0D,3UiLlpMYM,LGW;.)IOvXt?%La:{Ҝ(1bm Ma˸num#",Q|bm.o+ፒWf5=u~V_glU;VFHdk7~W_}'?̊:H4Ht*Dmq,r39Je#ۺ0qc m1qR6O]2 f]rudŇ7=D+-)-=<k0K猆M/=qqZ\M8V̫7Oh I%fBH&5IH)^1`b:^^?E &/`FQoSe} q~xwo޿:gPᄐYG)?_#% j^U^M*Wj@p%2pꉏ|[ǣF$Qd.=YP9ћ<e@])Z(ؑ9mU[/V9SFJ̯j*PP PWhxיt%V#=2&a0H B>(:NLE\fI~&Ja@DENsïN`W\y{^[z? ,C@Yr0x{\UYS Lϩ= HEJ/ kKP$:P n&jNInIY+ڌ1f>˖udX+%SDgsIJJeeQIMݛd֘aB3ry#wr!g9P )+Bh;  bh msh>CMJeiSMPߙ)G} BIR1Κ_CP޽yx{!ZTqC1Va@)rGZ9BB5'@/~E6i D^X+&uI3MPL8Ć 3wIS FG:2i^|`׆/:wo/`)cZ[y|K:yYp-o呒+-EjLMy$$BXI؎KxU1(d!0=B" Ro A]Π?ۨdP3YPQgVV|?z\ĭY>zU#eX,<4$Be+0j#,?Ƹno#q#5 s3l)~V9G17w>/L/^#-Z~l&tgfzK&j~1SӗAo)6@6DÆ?d`:R.!Ӄ.]2kݻ.$sgyfEVZY M ,/E6ZcȬtJb?|Qخ^rx<8s|Lخ#6ΖɅzCym]ڭĂh9=N^ 6 UL9ʆNxW 0*ujݿ`0Rв]@ К͗LmFD~)AFC 5!l ZѨ= uG/HIv;*z|5 (%nW\w߫E* |g.홵^$ vc~;f7/',jxZ=Wد-pHRTDBy]P_ekT tITr35!!Y !Gto@GJ b~Hu``*$rT@1C(85`a9.:2 <*U~0/HAx |{lRʭxB&k@+ҁE1@?jߩqWM\*-8KWs-_o=gM0PD$^fhD9WȬ:GF&}IcF$JS'PxC(Q+ N5Nי_܅Y5,Q U^6V-bK^~kwՁ !^a\ ƀdDi9˂sE R VQH_zRb!q3Į$(_|7ߔu~^!k%j7*n7Y~pMsv_o0oܨwK _fVkKRx8!?}(,6k>|Q^oih0a")"sy?ۯ]zAqfQĴ5JKv ,!\E^=38jC :oY$`Qn*e9NG3[%c mh W9K̬!Z*Ϩzcխro[s?:FXiqR;w?9o>ꊆ!X!%?ҐO4ڪYHfv&SoqfPq ~lxŠ~4`бg]+uc]pT&"ך4`MiAf7[oFX (9a. zs #vFj<۽}vzZwv*,fC[Ic列c?:hUUÊъ.  mհ:ycU ]o{`j`G6 kv\Mq®%/3FIqDye͸t=9@#唷J>ٲ{W[[; =[a C⍻@jɪ\LA/3oqQ\7cF2;QᚤqKa ;XS#yf B LD`J`<@4Lh` QXTQ@K4JI6^;cf<5VWH>@F@I뤴 D$6nIx]tk+L2j=~c=-u=MA:su4]S2B-9(cif6%#8f5d&Y 43,ߘE,1O4҅$D7$*R;:~ILTJSsc1>E?%j/ۻI-VƱc.hԒT|OO~]KA5t5K#pR<%-R,rK ⫝̸!( PE)2 ohJa5+budw?A xS/TtDtʑ0Ӑo֬0?W_}C+RlX $IQE^W ~MLflmK4o%*_Ŝo޽)_.ly8LRZaɢHQVVsRS-N5ء4Mo SZ~ -WfgWY %]PK9w}gO? /mԊ`[Kݦ6 ։<҃Qvg-r:z9 _nf$  >.λ9GC;J{׏fYl ",:LiX1X- xv] m?=͘<#u ݇xph؏1% -IG9(Xyi tۅ{Av_P L+Qąw'9MDz0D(ȯ h~ӔhuA&ƀxR~E *v(XސZ˓86{+#W*sNԖk a>&u^/e5ߜ]UԎS^Qk7ڼ]eMr7 |p+Ujm#a¹?R?FRi\O R Țx6ko˯<ڪMN8HpHâД8˳Cjڸ$v;EoԠ DJS45RhZvWX-ֻNE̺yJov~~փ{=*$FG!&H`kR NH']O6! ^A&ZPI^lB #aᙇMO ga(u˾C J<>8Jl'qr&|2!T,@xx7~wweX-Mb}E*#ACs",hś.T!z5+qud e3XIMS8"(&x*w40UdǪt ¢>Y5Y+I35#qZ9WsZ'[s~`|e8a0dXW l#s 8$LNxqaj9Lc9ս &a2 LU S@ufJkD3%4A+G8/ ܣ:M$Lĉ(ϞͷÛz{/ Z'3RʵHTP龎-QJuC9d/ {Ls;-EX-#yYF0, yʸ=|D?ǜRwI< ,3UFx\a3ǁi0]{ :4N4X`ڊ7[+ m+CtxaaG~ bԯ ]ÎJ7UZ(xq՟ ,d Q,)LNY$6^_ȯ5O e e7dNN|k3ʎ.:jw s3a[M6Ev87dZ }d#=O0VYՠ+ < [;F(ͪZa0##p]|u+9)+ޝf>04˩/v';gbE]\]z f=%"btr[TM?N.͂o$i:}+r6q l4^]z$ ((4\s2H fNt a!_',54}HJO8`r] M 4$*5ͪe kl`Gn'1Th#AoOJTC,"-޻?yt=e{D^qL`3DwN)$M-m&MA[x=P4xYݬ \n‘O kֿ-}5$Ω t WE-_T 8f] H9 JDN=  !&`2Y%@e0l`93 'ĨO pBw$WWD(<]%,¨6a9"Ӄ3e`qd8w K6Q Vf"5"(yuQ)GM-/t%_+P#XրӃүbZ~{NGʽB(ۀla)~ԡuPk,-$VuP$/f+]d@M2`sg+8Nv@mLEcyoj=̐Vl^bcZaU=͸(UD ZDN mИNz$Ӑʱ}00N
6(y&p̛m-bZT@y`jǨK@bqupD̜ ϰw6t,[y/woVUi2FpL濣#g$ ΍\WYMf t,h%qgf[66ny~{`LR;aRwtp`!澒۳hx%O+++n?rؤ L&Ǽ%-WKC0qh7TQZ2q]͟+4NJxӳ+DQ/M 2w'vԓBOj'hg]o88^.G_`\/_]ww[xyWzFMrur ,u5/SFr`t6<&;"@q` NщaT`ֈajo 4,#6spV jʿDEl]-_O` 5u5h^'KZA&I:n3ZD5iѳjvf\o_sU @@ %a; >ar%qIXÚEX#AK&xFҿGiSd ˃wMM"62(vaDoယ4J&t{' 4Xj:g{th'DSe3Xvw uo[. ^R-Lا1 AvmK݈YHT[sKW!Y/;+&I7 8ƙ<#z,R F]p8 B&>/(.-'|m$X)Oj1i]. [dmDxx$ïhi \ !:Wmb$ԥO&H["C!VV|_v0QXX"Ps9CF ѻ/^|U~J4j !v\gC*Q{ϻVy]~Hx[A\{+?0[EF</SP8- y(#rE?[~DӀ"SlFc }qPKV\^)w_~Y3x8{"*jPNF袎W㎼w$I/P&  b=4;g*c^+wPyʝV. <1[Dθ«)=`DdEck%`L/7N.UUn^̣Ms%\\e˯֊:O?wzgWb/FLa/nj\YFnx&m7i|Hؔrv(zf0xK_EL=*:(|-i~gc{̪ lg}_AferǚzKĖ2376R2v F']3[K byk+ +uYdpeݶAiP|p\ q_BSeCT4пi01Vz&Ns i/xC p:EЭ6Yc-6dG:"; B;3`$W&Y!¸تhקws{HsYiivC.ȇgO/Z 8iv6PQs(WW&+khDi:VqdX .U1v+U8,Xzarh/O_Wi'tNC m`l?0OxM+O)ᖇ5纑z(H1+&A a$-Au]o/֢2ζ,!-BUpTLeDꖔ}S/,O|E[ERz00fE*ZHX_X)g-|moTM=\&n6҉h5nwFGcℋ7o~Vx]rp<6~Q-A К̑CT"EeM'^s Q X_Q-A$۵{=MGp 6ųŞ^huؿ7sGG5N08~* 1RK lӗßs 5dǾIW07pl.FRe N<>_ߴ1nbHߛF\`D^+~ [,<ȱt`e}P)[W(^H('6eӅ 53iG`Whn,wk[/;9 ݰ0Fwnv>tǀ>ԿVXKϢ(?BbvxHygxh"YO/^R,SFFTW>#X_>KoUVen@;5:_&Æh~bww\;ް+ Y b {c%̡݊t-Xwp?dyxhdr N2vYeӼE?/yYOy,\` Kh13*T2CdBj)5 l%$!I4> ^P;(p_Dճ:ϙ50\Yb` 㚘jls>XI\T})Z\ 4wj5W' oRh/j ~iJ#QEK('u:`nsI-yƒJ+r?MsD15ia9!H(Bߤ7jERkȠn޹sGGJ4T:IB:엑{}(ܾe Pa_M|*NF<kN:i*P.?%58>L/#9^:$P0R ί.+ Sُ ۝79lz%D"qp0nYT*/2F)X@TPRtoǎbc'; uVKY5WEܩFv9mq:tj1LH̫f]gLT}tYXrHKH58C3Bs/JЉToJ:_H^3%M9 SZz0ƽ "oV=\65I:m]5A&a803x = f@᳐I@!a %hlr8n <N 0+1,FA+HlȝE߹0V|l&:2uMun*rTm>B Aۥhz)jV\Y6 SGluyp1 @)g8CD7Pѓ7 -GZt1=]TJw?<}HVVGS\-bA(EZ/R,ժڇT:0BBEm`$AbƚV[̇K9( $;®uѝ[u@?7։iygMwsg1;#z ^y7C\`SL99N%|aM"Y һ\ijN/PC˵ġ` &^Buo-ʳ0`1 scLq]^e ^uj({ Aj4 izD8)PțQsŎ3vfPշybcn#Yoכ>?~'hccCh o4fÙZ]43726aGO*+T!"Ck-ZTA`S0P 8FYx3䓮t[paFk1/X+7+.)h**3Mt=FI9Tz{H9¤vԩܷܦ YbDmՈ Hl4N>fic32&ZhY1cr D UV;|ŐgM]sH3xK\TF|+ƈ!]mI62q戆XA!T`ܹ}M|NˤBјxSFz@hp Y\8yur+i]rhGDM#ˬ w9u ,@ńv,Hȴ/EnE T ta5"Vi*?cFn$Zt!L6ʔe\2ijcߵphBðt+'<:Q#!BTߣeQÃb얞&^&$>;3|/B $G|ǂi6e~p#rE$+|LUcfkboXJ[buu߶H5{[Y1ި5[V/HbBqPhf -[X R"{l:/_'TBF8B`D6:>wܮ9Qŷv+P qIn|^LVح=> B$''.cIVВRZrxJy }RVgZ8g^jMAs`XODyLOV^1fs&<8=VR L_s eFgq^LCl+d6qa 5k0iȀn͵.χχEyz-3EV&?Zt@a t Y7s}ݼOb_(aX33 i"g `D=∩qKf$?GsZ[za(3)}d^5g?:XIU 3i XC^0"10P]&ҧV,QpAW+0:r/V؀L13a@[O.׌R3%_uʧ P"n(5iV.S"JtzpS:# ?K`!H"^ @`wKPR\U`_'+߿mTf~ra|3Ի}qrҔ*3Kb*<6X!]';9G_:j*`jmԒl1A)UcsR*2܅$l1H ~},xd+)Hv, SjIKNܫ\ÝJ4Ԣ@40l$8innpLD~ugF1NxŻS?ah\ptՍ^ ,A>♃R |zdO_>%WM*aI@Lv]fY]Z`ۡ¤=&%@&v \܃>;v>e_3 y|dž{y,\dl,amްVlTMqx~]%]I"ɘ_ly6caYpg7(DbFѐK=P<9*!|&ާmvړS?" *e<꺏=Vo^ի-O.j>g\9MPʎzD6;A[I{^\@qV8RrA1DaPC#^ȴbX%ċ0N4rQZ6xG1O^3\'gmiXasQ/Ħ,0pQtt%SC~?g_\T3 r@&RyݥD#=54no21da&G |ք )g}VLތ`P6rɮ;5LO.1Ewgsq@vެ1fiy;JsxvP-Bɯ|CaåhKOij+eI?_{eBe c,JUt3n*@MܵMbmcjǑ]x (&MXB`)^k[hk;`+N|}ʸ*>Q{e{]`X:kƀiva8vզ; ̗rX4cA.͕[@cA:ao?v@O)+SB NmԑDRt7f@ҩ~vfi3>B+&}uu߇-ػ뫙ogD!uM31i)÷"b;$:t fVHert m'}4MFlL*(F,o޽35XRE9R:Q +,7׾mXPi]Ya6N:~Ԣg'3`c_=_0JA.?YuZp ݽjC'Jdފtp@zzЎƑ.δXM`"˓TU}@!:o`tDY.@glVTgB~V ẍ́=J)H`]/j.pūGβRz<4Oh s 3 ٔYGG"kw=9n2+lN*hsluLf;j"p9ޥ@KB<ć{-^pGV[ݜKpgYJ92"`pLvu͞3.e6:S-NIDFǐ tMa8}1Dz#ݓ23d(hshԒ s/$PPӁ%e~~b[cԤ*Xo$4%N:"1%՘f~"ƊDѡeઢ' [IƪE94J”/M L=o,QSݍjrC1K5b'^Q 67`7?,\oTEC%cjB \a[cPy-NzáF/Gs̑> ,g\C^ldRfTM7spCr#:ǠXz`PKS]bL[w9uĕ=͢2'oT A>lfޗAW_X :~,ShϚj\dn9e<|fGvLcf_MFZHB>^ ՛ 4ʥ3w(ds(sY?4}#d0`2dLk&C4I^ kLьm1HxX/U@jg~7G1; 悝oʠiJ!g;E V_o߾;yy?8y}V@}ΠC"/#FJ: t*[5Gj̚!f8եR8$_#P D?7=, 4o0Z{:Pzu=ǗInelNTR#/kXTRi2g ֶ+!f L-gܻNT~fyK`%Ю봙[tK\.T]QEj$`}a!VΐPeayqŴU]xGgn荏>^%|>ƜI CVh&`Q{X x@"q1Bwlgw8q}ٝJi"K\!p "?+'[쒧Mtq0F@Of]x &Х0'DMJr&Q !P'(W"69$nC* IZ ]i LqLymM;ۄLNV4IctyjLDٚgG­f~gB (/E$h9.#Tc@}ں_7ןPǎA*}n{̂fG|C 8 jDBV[;XK z"ea^I)VA^p$r^F}^O[}t{-ϰ]F^/ CrtBeT-9G-XW ??Y=f]doGd`82 XGoSķfԉ| VZ1ixK h D^o%Uyxpu~؃Dq..瞤41q"5 MjY2D%CofD9']"ΧzƋbhbvAɲ/>>뵗&X=U 1u Ʈ6%*T\GpƵnꙗM( Cܾ0`m cPY4D$e< N6L ~e7 Ps*qktC9Llu]OuZt,2i -kDƍ쓩^O%x965X=9/)nQkx Islm G[卤S/ UD")p ii39dg]tVR)V՘WHA {@DX >F?JgEn"|l{RRqܝs&bDa1i'ԐrXe;LP f8z2t0ra22@/b.(nO^OV0XIQu[8*fu'JFU}1\?鉁~ OokwF9x-,w/T֖*EqQg=E@NmI8S⼄MTotXS=!VEI;i!r΢A}e p=/pRHa=I>9eVzN%Huܲz'~ZGͣO>O6{qа.r0 e 2LO_"T:lR]\ȴ,+8BG'/5'.W0oа;|1ˆHYp]Ji\Ԫܠ{tvhw&4n]֋4Tm6Ю0n[7Sywj:Ah5Xl{Xcgc3,cZ~|6[5؏в6ój,@MqQ7ld p:11܊v8x=,Ri 8{c cDtiB 8$t d7뵍Io;m8uz^rT3'qvar@+K҅á|;wZjOg=e^t ?^#VȦnrizVgPW[zɩ V&y1)$96aħ+)b4J U{S -1^ЏsEZ E} R fGKI-/W$d<H8PVފh \$4%/sdBiMc 2]2L˞zaK߂NS1PH!o[j{8"Pkd 9h"qC9!M|Ru C)5RoW%b?7kCD>x胵;pQ pZ(dҚc3 ,aG~ݒ#t~m:_YjS ;w 7CD/Ar@9֯[X?Т`[9senS*m 5o2}ɰ z}_+Gˁy/L@U"~QoęAI_E-xVb)bx:S,e jAN&$͖[:T[ND6-Q5߳6-똆W^^s҄V*دsNя(5288ppWށ-{SNdR(lu`BVDN+xT W.k1L 7o?{JM %|\D1g ClW;-VfYp4(˰J50Z(`FLV1N֯E6WyC`nHi-,<aZY Υ-G}Ո=c)Jk1࣏>zܽlhϱ{{N4‰X8e77 02mF閕v._zRӐ-qCJC z^\.7h`YfiC֞e 4MiXf4!7@"kvl0zlZ- fsb9- Y}Z=QQ%.WHz{~GnA3bFgN ^`lm~o:Zz6k5#Co~[|3CӽWC\,}5}3 )zUBd3zvzvZW=|XuxiVԬ}=\' S'vTE+7Ԏ˅_K$:*QkǬ nWW4dqbqZ_ru1ʄqh7=fc˶orI_k"њíO Q'˽bzL[&4ɬJ]ME$@;:|8?Ѻ_3tD# Xz&bժx%Alur"/xRnOݤWu0s H;(M:^\OphYox.`_Q` ` u:Wo֠$H6|RwӾJb!'b[;!kz鏯;Ire ܫ@iK~L"'(Aq~Ď0pG Ը",rCwE8 PM^)p* OnDdrqݍ6J:Cr145R7u5PkW1l8B=g/8`TJy2~R݌8_&(KM޿DYքJ*(U}RGPʴ:ʁ 7F,z9fu%Yh?*Lʴ"wu`6(i hM頩/^?#"qeѪ OZ;2-ѰA{a EAW $U앳7ը2wo&%20IjmYb$i9fؑaIl̑%o%2j_U|V߭Y+WuO??MEqB\c}}bkY ],W-ذ%]*mP =?>  :Uس3dte)y?mW|b*`=R\.i2Hh-NB`Xˀ6̌ I~pDId3!Mb0Vq{%q+2CS J7sE `YvsFS1{~5D=ps:6j-NHZr`U_Ex: Jp.*,'?-u2ɽuʰBl}+tl[Jh,)#`n"4u}cmg.`f2G7Rsz%+^R:ƹ,)V/Er} F'S3&1lL핞T s ]Nc(?Y[/䗊ߟ<|ТTQmd]!U\5 fP?ĪYrv@Bh)t`1YIA1KQ&*aqmpG5pBj$ ?樿(ǑERz?sǶFZ 06T]VŎ'2U}Ea(?)(IS }p7Ѱ8.סa4i+zaDqB5r }fR{㋱e-1_ںA~(65w&=NS7a4Q U2Mtq~E w5hr ל \A -8jۋhŦ(3f2S"@B}mKc n2.2Q3 AF#6rՄZlqQUo=x56"EG>PԺ ~^0&`v+`P]Y&/޼ՋWk(%I 6};8;>/VpI&m3*HOgT4]|q}YM2Cn {g9=̈@AS4\!KD$顟6cC!Kz#"*/.Tߧ9{Tn ;6k6M7QyHJ!̪G>RD,D/:鐁oYf8?r[>ÓC3'r4k; jɻ8Ru;_]rj)v%Hl#a[sP;u+;I z[ L䩱yi!+!}"2Dz:lzj(6{*JO&q:S'GFDP&vZV* Ț-v#^*hO.T: i%BYBIh6"tI8[I xiبfA69A;E`q!mZϛ xc\pJvspӤl rg#_i^$h>Frj!%9Tؔ4ԭJ5+jauH,Z!#w8ʩW,hl_g+3P#vi}ـ0 6 |iR/?N_KQHrml^@D_# QZh]*1mIIE_x Iń!dmՖuSYq%!IlWP,ffM8|G^yP8 !Pڅcɣ{/Z 8oƝ?;p|`qz - ?7Q˭Ԓkcp?w?9>R7?2&\0i͙F%B@1QjS^Ϙ[ io ƞks$o1[2r-tJUߚi&vX`![@7"X^>2E/u16gǤm)Y4zva▱'̛ 6h&Pp#Kiq>&i;MD1cc]b>*TX]g'x_. !OsB&Ah@|t&dYV$kTeke0#3c]Gw)]{kl},d:V1=G`#d7e:6RPWW+gijrhO&ERLON '3+UZ~Agle8AڲRN'B "8Bth5pr;#i̼"-](*:0hVcrCd8ʀqZ[XdU# O`8]$)E7UF.D:)2OdwPT7 MCO5 u+)rPƔo]CrDc޾r&0Q9;wkNYnS sRH6j֛`8Z,EVgi&$KL赋RAB껜ǣdpjTyX%5PtDM<ٰW)!4VaV3̜-zu:% Du-yAwGF |76L-TI(#Om, ]*R2H8xEy0Z ֱ8HدШP*F%Jf`Ԛ|J B.b\ P3a/"MMVգ3ZUެ>ֵbv ;tt`dp(̇xлqFΊ^ G6@vgZq?dmJB;tu 6:-~#Ǭv<ӋIBI_`\Z#П5s/'{f, UW#F ,00|`i >Ѵ JT-VU*!i/J7k3a6$0>{['ƴ֏:~=[mt)52k[GƲGyhg!&Fo!r_<".|CŞu7fl/JNx Ykܖ}\M#\4!js_o[#?P724&NJ3W_\{N8w,S6%'-l{ sM;xAbJ@ܵg5FWxgnC1k_i]-w/hBA K l _Dma3W /QIoEk=7B0I}UMY m`,Sa3D' M0:sEL1r䆂0|̴W 4d=sBDGq2 I0$&!2QkNDF0LANAkr&3цY+UFQx_j+ ZB=MsK*iogý"}`þL^9owbҧ/6KDUZt%up{=/_c3G y_Éɛ^ЬAc{dűьGbf5O;/ȐEjoI5'ϚY&Fΐb:VNowj53cu"ꎷS?х$l9\P.҆e?';~l~.;?b)5 Fn*x8GޝA} k>I*Av !z,Z[NiPVS\[Msse90WMXU| a w4QO4 0Sglc3HsxѼMD2|[Ӭ%|i2bq bv2T=aKRaP1R-97'8?dd#ph꼞G|amZDC;ZWd?ooxOϋ57-zC\b^5!^Po0DyL10ρ:uµٳd[-FD\g6(O)ˇ,[aTC͚=4D-Jk.\jcc"(%ômv&"C=.v0rs8A#hsY&=mx䑶Si=[cs;8X b.<F2As#w }T RXU߲O4x\. N΢,S%":"(lh}%9en8 o 'yu!Ծ|^tK 1CC_Yq^wñqNzs31qOZR3u]ÌIwc 'WPTm+\WQYts7#vE>p@{hd`GG kӤ(jd Zס\ ! ʭ۞yMlJBliT1L&FRSgggguV_-w,r{u!D%. xx0 M*X؜cM%8BY{, (V (@۝mqo[=@R=>m!*08J0MހjR׈a= &%K}ՓOmF F^ ]'NxRk!骭qj<I9`'Č;FIlS ,ӝbF?IЬsB1nƄ{'4uKt5.4lkl uvsX\d3`C>ݑ Cmq ]y/_e~3;`5N۹aS<0K|kI} Ճuώ;^Ȳ'&Lf_I%TECd,>YY%4(ej;s0\ Zĥ =j68Y$]ITF]QFP=7XlRrظ+]O+z:/ W_G+t =e5Fd_ %XL+V66J[1yppHaP.! )4;}YoןBڈ0eᓑsh瞡 (Sԩpt[*h tcְu't{IK2J)v"ÑFxi <3Z'FG Q;,+q d(/)edȹP#8Q ,;PDwv09PAUӶ&ԬMRW87&xSS>1i $|Xi1![fTOҎbڑtL碿8r2oJ| ]:DSWZw$Y#SX"דH`3C!wt<9뛑{'$ܠ3L4l-|y՛?ǚ#4SLDM|ӡo'5/޳c2m^aq_Ք2XqYH0qX@w `A,T[3PYhsYX8kw3 =͓KT? |XW'=r ܳO0E8ѫi4U[Q+3!O$+ZәD朕4X` Whpfo]> LI|QC,YQgɪq)v 7YEA(ut#ر~E&mn~|AR޴p.-UTfOh(JvFd_w#\g(53o #rzIo#xsۙrb%PH`!\Μ 64Qԕ !)~ zmp|.4w}RCۅ{H0V4CY1rk<̂k\dGgvt;d*ONN5be,YX6r!'a{x &"S#$إ@!Tg=S#hhOeAAoi+7\NHUĢHH22{jL.e@5"{cFbHVzЕ2@lt BUw39i`}RӾ^5 nBt9dy.}?q/{j_Ǐ $jStvVkjn3qyV tqw;]1~5{s ?Ӹh d?~eiz g =7}ix,bl}) { 1m˔!:ukK.5-&P'+G⷟YOC*gnR#5|EGoZP"p7d]=b3χɳ7XMl5.>\AZ09Çf) 89ұnN56F㴙i[$ oK 8}y̓VfVPtt C d^VVW4ogŝ*"|D im g]~8N? ( Pmϸ26?tz8I۴EH_uV\Q{!0C4L,}B[!xY#/9-f.G4ǚaA_Ǿ/DA&Cܥ*pT&l:2'^\j0{@& Hɺ:a ;#GU|jQ:Q%s8Db(:lv}ze%^˟KD?Vhp37LKK?2af G䱐P>=xUEdg˜&O$nJVE(HT1~:ׁd5"(^ "Ή8̩uj%Eޞ)@쏥ѳRx/ӎڰjSEej '7v8 #(Azp BY"*)ԃ7zܥb^b騯v% ([Qn=zK0T$w'߄AimGD&ɫ'Z)]v8ֲgb53.oG!__v٤`>ܐO1VhN2,V>j.gl:kU E"vh>бظoUUmhYyltD_~FTI٥RG=K>)|XzX_eĩh1znB;ر<7}0_n "w0ت9VhdfaILU ۈhfagIhDC}&Q/5u+x*$gUajl*P_T𮎄CsKK95AM%f/KcNAmpy3tdb1ejc([fcvEѰ =*O1om#ǝI/$ĸٜ[G9Sùй: c+D<[!+FX7;\h(KթD }),8Q/X$څU˵#틙 ǛNg>Mח6sx^,$MR5Y=XnY=m--C g"$ l>hX:ܣ:_0zt| nM4^c .=\YٶA`Z3ږM.dLW_#s!끗5/d*|l=q۳#1iGO+dʖ6ixyTi dn4_AqHE]N\ڐ{\̊x.~Ux9{ߝfy^\mZ5L"_׌YGˏ:*Ë8_ oK愐zC>E 7uCLw =x)DDu *&@)9֮[jzar)MpoĆ"o I\Y;x- rjsE[fxT3$vĭmJ+ݙ]\p7_T"Ȏn[56ujcmn3jbr##8,`S{8#$cg Ib"/Ǿѹ&1qW4<I3 =-i6x)7F1ה%ql2RE\(E`:/Y/Mx^Z`my_Bq0{5%lƅIuoK\a"Q`k8 )@r)PwO,m'I#Tًg'%⅝R+W@S X bbX`q9^!)e2-*Pg4SyAcꯤ&h0*٫8fuDqZCW@n`0{SѤUA>>h]vtfŁ0H;7{Go, C\X~=a20Ӹ2Y"3ϱϺϰ㐈g;oxz2_' G^=$E7\=% 2#^=܀0!Z w{4+/9-:TwTLm Ii:DWf;_JzͯճNJ-3ˁ֡_\Kif,^!. CԌI{ `2! 7V9)CEg9&ihp ,k;]nLmx Bf|tXAV4;G沝>2YX+SI-b,j"[k|U2%Gey+ 6g sa"yݝ-E&Qa1_D9ָ~6CFk.w#YO)ZT/^vX'&h@@WjS{u].wN4ExU{xC)UEBf[<'38ֽivtԌ'4bh@ݎ󵱦/p.XrA58Iss,-|˲zgRLn-dAl'fF$b Mef&V)oJS\-!*V>F;z{zӼj쀋B M3 s A_ݭ l! Mr*:}'$WH oԠ D(D>a=1RR;h_hf԰oPLgKc,>ܳV DËx"~tӢ96;A18$"qZDqBGh~|y~Z x٘i~^i(1g+,ohZGO-P_u)|к꫶MiQA@fAHZJפ]}gueT#7lWt&:zꡜ53l1&JazDs}lGhmN*|?|'ʬ907ɽ=hVㇷ;oQ4:A<iviGEx2beh=>9:J~f3k_옮GXV|YRq5 Y>z0̹8;߭׋{L+~~wug\㕍̹~gFnYOaP6(uj+k~reFuM4<֡2BHWRn <Lv܂ ~!n|kr.ORpHVA_OONjLB >-見ћ8nXkDuGڋKb8d_y97*PJmWtIL5cP˅Lx=hP*:Dx Oq:J?H sq+BuC{o8Frs#bkdk1x_;1}Poɓ/^U'x ^J \.'肌5li^^VnoAQv%s,bu۵ެkWN \7oX_ |{¾2H@2BnlydI)E)ht&Ӎȭ'RfIw l֓PBA~%,ߑ^2d\j}9X^B(|\X-֫#%,Fs+WEꃡME5I"Q|k%·h©Y̑f;4%Tz%NW_..Ku\KWo!%aW;@QBp^ $Ծ= ]YIUUYT-ђ#BC3{j(f=5{gJAkEZpi*59eEGLnΆYc9M&@H؄0roZ0 }PݗtY@1s@D.}F[T^HBPT 2S)V)]kDU1p3h&xOB]/>[ z}OO1 žumaM|+aCQ 1,;NTtRi]fU 8ģ Yf6p(@f"0bZvzܲ,nz_|OXVi̬#h%oK ƍSwׯN_D\GTR"o+Nꁊ.MFҊ sJ88(Jݒd҅뵼4#t0^^fDCeG!^ 4s3dJ@^lQ,2lP`zW#/zŦ,o~-CpфEqraxG???|W$C˕4=߿y^)B6hߛrlH21c._XNOe |r..F\x~ҕQ򊪾OQ+NrYqsǻ@ 0uCN]6>ZiQ|qVVҌbÚqg2=I~~~/:}V|mg. @.(~v"'5JEjO~DÛrZ./8+ ppS#q#<FY#vpdI*YxZTR?^42sQ1W(8mg yh~ul )edNk*Ա?dtDT'w^?V@II4ʉ&l\,QE ".G^`u8/B6!HN7H_3;d(ްuփ/:Tŏt aRL䩘,#:&l#R3 Q; t :V@33RmˑFI8{̕n_(=KB5Q/^IfZfQDsI;sÑ{׿xzR=S[y?`*7N~vY%&WC!.; ҂NicFij.Ag5żN,֬"0%o592Qh{ %gvFyjӂJ`:mcjEb`9*0HґK ê2fF]>}Bͣgx*Dp`MaS]w$`T qBYYMZn;>Q Ak\e*sTEMP ++*0ܑKpKT]ϲ/2Wet́tIP* u|d$6& ȭZީgdcJjf^m6̦280ݻg??˯cXu U A}oTQ9WU;ŤE9QQLчme>yP!5_33;|donͳҷybvv=.:tG-yG#ju>)߬#́9215iuLZeY:9Gآ . [ü幆5wshwPZ®~Aqsf&x7i6agQR||¶vZ.g7:&G>&I^i(4ɳyRYF%~CA:aբ?my-m̰=P\E;#].ho1tpC<4yuWXSWEnW^F~d10+ y ԰_O대xJsTݛ:;y1:ff'Κ|f K4{AJ]bxAJJ4|/_և#|cPE,+t_iEXI8D) Q^$D}n9ݨ޽rNA]5Y>b9 V"ޤox_.HւߐͽvY%"h4Jo<˂K#| ȫ苃+('}P=Al#NE/rT+:\ Xٶ!H%OKty6W%#dg\`-1H! _YF>BA x9P%5EfCIEsP|aBk.^"jԬEhՏlqmQ!BK<ϧ.Vd'* sCCYg0192F"w·)a ff#B4ԚxPĩQ5X@2^VK JH)nQ&ѣGT:4{?'B.Eg+qY_߾ӟ:yN "$حGp^>[AzaZ>yiU|J=I6'[_o-P.ZR#U: \D ֈ)VN ZXg svɪO@"tY?/j:ӛe:G7U2|)@ʏ~  Bݺs C0WNo/q"[++me(:~ Zu_#CZ*#Mc?~.31(+gw"ZӽDϘ_Q0  a֟#CFXw"gN2_ kH1Jk'^8hLvtt>A; 6w$iүyi3QQ̽GwWH.I]&W!ˉyRYXTP7ۨ}!K܂]3{SJi+V["ʰV͛l8 mL˧WիzAm?de4BK *̩`&"$*. Z{qfc,lҲNX <{\R,PBуCg tpV5[d+`ETT.c#)0\ RLWdum'ͩAiy 0<"А=fRc~ᗹ;1뺗蝕iw%"ύ$:r./&.qAd-!Q$ad}gJxǠfXSPt$+rP֠іH0`z|;tȷ0IR cxoh.쫙qKnT;`ݏ?׿'W_aY'9ھT@oEӺSjp+3r]U8AE !ʕ-CY?%J4WjN)G2ZGH[\B:}_h K2y*>􌶎,a#),߿WϟVJD* ^oy v9 : 45:w4<:pj5_އ-grl|5D,.ڱ!I+zco2AmfG$ddVsnvi2co=bȫG2O>̃1ә| IFd(Td> Nv)RRZ~S%zJJKUe}uzAsq4O,d6s#i0y9K{96wh/O..nܸ@(mR?;t+,#fmx ~慦z#Q y]֛x2K]2a9$^v9k] J:23UzIkI#h [S)/t}Ð.k݌ dGs;0#YQ`8އ4g*dҹ_'CJ fΩu8Q!SMYgA{D(ABub'0Z{"M7yF% ^%1A>4my Q&`^- =+rUӇjYk3L 4n-k ׻ɖKl}^-PX;Lw]pjJ?҅dt=keiǃF2W(I;kb}2 V; ƴv]~,:YϾ9#oXO)6x+rIvdzt D*Xk45B|.2:flF?U3lưD>yqO+x ͂s%C,ׄLaX6]z%vWBZGzH53x1(\:Z`M̲)Zܽq^oYۻ҂y۶)gAX\'\Փ~>?;%,A*r9(~l=0 ڸ/$0,4~OY0M՟qR8-/"lzYdzw z<BP-H9&i(73GE ɩ2 G"9 M<wם7;xVƔda'1F3ˡ3OE6ЉPØFp 3XbJ.'|\Oҗ Py'7LXiY,{]h\i2FWD[Tf^7yipvk7.]' r9lP?UC`LXu×G uҎ櫆ygW؏fP $] ~6 FVtOI@Ån/.O_(/(Yua3 <l+ &ڠ>h*)'|R} _PGL wbaR&ǵ'nWofy 7#Qf`ә~gvRvt4iwF>=5 5qMdkxa -[^88 7Qs.AD<|(W >Dv/yqQYyXe]̰3h:Z{Cr.Y b =~J{pK]3Ș EڃF))bjhLwRM&Y@@h8u/upJeG:j -0,b<Ъ ]4fSqΛY`o%S**D:%A"\2+=ƢZe4rL5J d"e-#Fl:ؕTX&Tb np Mz!jnn7#5dnaCFS_8x&sHFAcB2o -hEVlvIY}$<8Y(Xz, 9Y:4ǘ&m !\zeXM7f­dai2,DvV Zkvzg/jI1n#m^]7Y8Uq$|B=q]?E_NY˩^ce j`P6w'څxޒBd/$ѰBqcnxJFSԉ3d;=Xо& ҬG[a #o8y}z3hDr|ޝ͈@|+k /vFN/$G]^U7cnAd*5VyUxoKr%I&8{D.USwf~4PM4DMCY{X6|sX\իjj`P "зl# rm$.'0Y@E"  8H|C]-5SiL<-;NY^cflTfI %%Pd>fE& bG5aA}ZWdf./:YHQv15]g\g}f2FȅDV5kM`1T]4HB}{u ~RXp95&lM(`@RΑMIr [X U&VF`h5N3Xi"cgV)EQK+iUB";**MBe{ҳgyv'<|ۃZ:YAIYBTNI/Q~ZK ($3UV#jQ=_IcHkc#0oo߾y7h䶫X+-!ew ѣٳ:],dt+ E,3nwgQmðҋAQ6u']% Z~Q,46=6J4&b+%Af\ V 7Ȑ} fAsr9w^#~/]&*R ,09F|4j TH1xx  #7'ER7F3..$\!.LͧMӋ?F2ؔ3GlHG,kE[s3Agw4X]$6o#WWM-=p:]nk Zi}0sʭC<^ɿ/?VΈ7-κD6Faswa6#jcpO|aіDF>GG"+^ЯLZDT]eӪ6Ro^WYX0b/ʨ|o$78"HAי[ a\<nc4{ /QE=Kf?sz_/^xFTt`g,@Ob{+PeՈ&.u bV|iLr#E_j+5 Vˆʴ=)L;!&uAO@+;I5/ބHd =ꛚ{dy r `ԖehA@uG/d#ggqڀXV[00^꬐SKrڍ{o3>_tF`dAyB9V;~ W香9H6Q94ӌFdbܿ·Ed;eNRB3u17=%烕`CPKbQ~'t1B +\!C@-aevcKiWQd SHƧz3g ~ߪK@JX)( 4.]XR[9#Bgl֨Zes0!7Aqg M,ڬ 78*[T=تD3osrL,FQDZ qr_Jf9&hb]w\Iq]?1|1S+A0M>kʮ+JЪ8Dr"%x[7>?:AJFȳk?$7,Q8W7 k/~r4xe_Qy>.:[ƑN1a @lM & !KW>Nں;ZB+*ifƼ5ٌG%w_4AaلAGV# 4FS66P2}%,Mr~rv *Ѡ}-N/rw}WUL^*e/Pg>F3Sitҽ<[Q]pyʍC!?4 5[.Ejؼ%׀4ݦ&-NwIDAT*%2G7/Y|O) ԜpnO=γ,6tQ^>/_mIVBHAS]ImyjHmbVvMh|D=AIjpW&& lyffkPQ8qD_klRg=A> q7cÎ[mk)5Cӹ?L}dYd=B*S1Fa;*\=̰,I4Ȁ{9dkj <%ۿ5 i\M0(!}!\3+ ѱpYc$0E&t R ^=\jaq*{֪z*T"X3,HCf\e &3ƥ)7:ǹkߩ]IE]Vn>Gs¾XF;7sk%.RYM{P(UHRvU&jhyti^Fr JW,Оt6#tA.H( ϸUOέ7?oa|,~(ڬ 12Ͷ&Qk舨 NO_|o_qWZ$Z%KJ*"`(qzPvPuվRFڠӓǧ/^.W zCGaBe4JgH(Ǽ!{.OE1v1 U< b3 TTM=#Fс(vmУGWݸv yvMaif~7r`h ^֝ӯM{@1Xpb j=n=}*qkJ ;f7GE:;Hn$G}> ߙm<;HZce&nL~Fᦡͯ*cPvEC-4z3x/1 <3 }EG,}뼾k0 M&6g"'_hX 7ÄMaLZ3NAd.Z O_ןeRa9av=:店BrhPj'd~6¨ٶDضOQnE;q&L._'f!Ca`T(7*uO;JUW<`;%0O\C Hh%4mW>Pf/GubKk4o8NDY>]&0 ?MG!{Ov:Zb)CX&E ?hJәYdotF3#asa- HeEi#O#)NaA(OuM2 嘯^6WȂՀwtMd5{79!>4%ΐ8! صۿ|3W^DKOYbXZӼqm\>Ң*V@! 1 +|#hJ֝YxggN׵-Cb4PΪơU!W_+E^pU}=&&hqz~) kyp48GU%khW:-QfEuC/ZqFP죌2պhX}Mz A˵ ʚ1"IOvRI#ױZ`WL J h!UVŵ형d-R904y珫ų*~Z.fcGBe^ķ A7m*(~Z/<XP$#'CT؉XR5w RriDz7nܼYgN\\ag]@;yߚ?V~/h:Q-4jx_"Hђ@d %w?M^=<VcQtQo^WYx \z|n[kM5 Dhx6CO݊)?}?E`ef t8 Z8ԕx9ekq{ly5`j % ,k|l%7ۅ)QPebؗ)\f_W+O5nd]5 dRyUp r:?rOc% MatZi=25Es2rM&L'+e~{oJ4Tsw2E9Ϩ ;IL\(Ze1/V::<~;:E{N8`:e$ AgƟ%՛F77l7P_VRp Hr 3: ylI  E KvS!if5e: 9qHX'",C8H᫏@ 4Kt'{1LᣋY@sC \#p ɉPN|wxr/O?i#Ҳ+0PҝGZ p߿Ajd8AA@e n 1x )J;T)W)橲zjH:a@VPJ;&kݘHCI0eNj $yDo,k.W^F e-4 hB?AVxroV@ĤXBgrd+bN@oA3nV([H"ae ^̊_<ϟ7m@vkiPqK r҈Nqx\z3ɌKmcXguF^cT8Jr ' Vޝ۷?O?N872~B8ƨsj^CL:˯27lUZP,R*'ia!󖽂T`S,3‰& kǧ={"/y ;r LsS|]<.SD)ZMҙn0Hpst:VAY;WݺY2TƥׯOk]̻Z:?$2Os ϳG5٨{,$ X5D|x nur͍WOBcduʌv84n-~\ :ZjzVk46B` ۅK+֑խ-ͮC Z0?c8Z8ƝjK6QK櫛w feiFMܡK$kԨ8}3=͂cxmw B F~ΝQX=Èv >>=!6,;`8q!HZb+G3\kۯt8m3 }WD(/!>a9sDuZQӉ P?tfi*[G kfLʫ*H944eͼZ!^ׯifWԞzf^SN!eDcI]Ӹg da5'x|Q_L/>hFMϴfd`2Kǜ3IMk6|vA)gF$_$ÕufPN90lnOٝ2>` V l]Wy!3Z=H]\VQ7g;A֛Z)0"eq8 JK|^ZB*P!Ǐ>>9yq?yz^xTQ=ZzS P jofdH ,""Ah5ZN sLƾN{HϩmHU ,3G*T2fPWQݻ7oެ-cH*wb̊?kb7H: ;ÇOa)h@*<,RЎs%Sn5Ƶz`BA&ƠR%`lX_:"'lDz0!>Zmn޿rfP`C&$}F_nM c- 6rq͛uD]&tBp;@.>J;W<\ K* Wvn!6kn yم1KeƂvbvne<:Fg>{u\όwPf q޷29oAQm=sȠ/Ark+V*fd-myB61Rg4>FM>aoa]ŒO\S~~v_k!fCZL#Q <~arE* `a3yexcn~,íxitfu^ԭ.VFwphLz݅]>zEՋ渉=k^=b .~zvDyo1z@|klb0c8B= 6ɏ1wfUS+_hLV^ n;>?k+}vBSk|P=!Dt죌к|ZYu\!e6qcx'ɩ%4,Rm_~[*yu1*QOwh jšg.YvIK`ܪ^Y7E,j:EM`:іgiʢR =Mx܃\@[8ոeK*^b%πG*A61!r{;7 g„l0b +̍5b/Fx`7S!d#: P؉U OJU@%@f1P(m ZvD*q)_V:2@6s u:Ҏ5KNGPIubZrjA9k=< I$$ĽV>%U Q~%˸(dkU/NzKު@۟|WEC . fdUS:9QEßUՇh2Ia&(.5y{Tà =p*  6A'ʾ_mmyRAnRʔb.I~PXnvU?fHC\O>R#Pgl2Uw8/^׵ڞd]uƇ͹a,No([_?5'$/s_=$a\ozOjc=p~ j4LM]C h{|"mX`E3?Woi=>`iwѝG~M? >;f돍^ y`~`>hc!MAd'Ż8˭:M[97y= x☁}>r;B5G잎?-xh+5ZqӬ~[i@4t@C7.ɳ^(_3i`JTיڄfۺGk'kW.9FSq3|u9 \`$w#Y6c&Q!.!,8,Z]?p9r dK[ {: Ēp.h:{]CX.d}"lV$]秼jv_5 \)wԅҡbY, v@[+?8Qn=}>UN\Q݋q ,'g=61|MHnRhZ˞C;:jQk{v!<|/`2~0h"P/W1 dE%}\l͙~6ffF+IB!R,Mvb iZ݊숶$\4IT*d1C# w IK N}G>>?4nA*%#Q^T;<(ɮ\]^*$ c(LB a`+Ҹ#h֔NR̭0]"LmjEk/߾u[7_*T]oW8faAm10f/^?9y^=xYsRnDdjq5e Y*.PR7$ЯmOQ3.d]N,d.( *;HweqcpbӶ+\m>7ӆ@} ` gSEVceeCg\l}} O@u+Y}+緢x%%ɷ6f.ӡH;Aӡ+H[iGgGؘkwށ&@E˧N \-[-ZL5lO0#d!Y9TCefOL'}=-꼜eQfqR` 蟜Dhzqp#$;9H3gN#b,6==ӈApGaatj- Cк<`4fb;DunEL tQABTET2̄gpR ld29 sxXUp}teDX["Qk\/!E+moi:O7if\tnT(ʢN%3|Z$(ě9KIHbpp8Vv''fi3KFŘ^!KT)VҨTSF ʢ'g/HlOi(o"u6XC*r*}Yrq +: *# gVií;7j]̍t)(Yqh"~_*{7ƻhqdɛkP=j+0V,EfXSZ B3hVÝSHX%m͹[dĀ&8~+ J5?@qV*˰KVƽP ZrRFwzm~|հwm4X>zxZ 콷w)v Z@Tk6n j_CEf6TKe65ak׋ UY5]a fmP)gT wVk Qxf.3 xLە&ER NUcQPwEK:v !t]uџ+,`U+:&hڣI"AWGEp˜wLYA*#Y Yưh5ӳҲp347BV@TҨWc<:2L[ J |x dK`y/ki0uBMa CyeHa:c1 ף>Qt|RmZ/Jmr]%g݁>,Eѡz=xXQ;1 0 p[:o{]'ON?y㇏=|XIˢ>{vR/kԎR, @>ኲ V;6h*>juA!=pV B jzQ\}B[3T({ǂv좰^]Ka8guwI>8 w$ڄ2eܮXvU4hzSK;3JQ, ]74hWk^|$] k3 rA ZFs;fYGLr`hM!]Yj ۖsY|M'sDr[-ica‰#)fG4_dI1-cUjYkׯwސY( #tg:r]FP K 3 mDt~8}6}4c@a=< &< ;h0#>~o|enlې$x!2)Vj2yF/ J¸\bt H[F)c-=WΣ# - eLHX͔3oN;Cߟ'>l;<@E$jo}>9"Ki8mFs-SN1Ԛ^voX uVɘ>~M*Qql:Wh2uvpb+ʡ䃙%r)3h#S;2fMQve3ڃ9H>/>'e܁Pjp w/yb8*u: ׁ0K$!fg^U;-БLcZw)Վ iBbfm- A7Sk@є|&p3Ibv)Dcó5A.^݀`iS%eeߴmBlH~ށ ;f.ܠ Ӱj+Ҩ^or~4BN(zku3^x]sQEedPk^pҗE#S,p E'fN(E(*8Z݊t*Cmc9*j AZxRk;!Nk$m[խ޹{C+'nh3HiW-`kha`UEhX."l%h#_~bֈi zq O jZ›(ڕQu83Cjr 4kBJ8boaAriv˗!nvk]NQf҉ 2DhnLʶb/T0"ɥ[n΋;n~Mϱ7{;wi57t'ms݅ڝn.>MˤDÏ>㢱?}wYK[oEc(lHK;{q~Y>͇tDúC p`%hkt}V;q6 =ɚxO<~*5XXh].-`/JH%-΋Z }* _+:z{PrZUR@̲%l#ђoOXJ6ڈ%1ike4Ę@*3~dl/W'dzbYo y=6rk\s MThȖ Dv~{+ׂ7߸yC9ڟv~Bwn\^8:O{P)y-u-)B}584z2vBq&3̚LoN[K1O=W; * k;.Mb"35 "K~›}*x!&mHC:ǃ-2 K3+*2c9%i \KbG0A![[wC Vs ,HRhȑ BzHEO#QB#1TDpdOP/%c wD7ceQ Fz;E&؁Kv(#I}Y`6퓎 ̌! q(l'CA{<Θ e+`-# hot'Ьd~Rk/<=y&:%VDxV /]@WSXHxC4}) PhjQ$4MY< _ wp>DL;PzU{e*p;oݻw>{N];>ѷrݻ{ww:j-pPX"`"p3F3p8aެ 0>k=KL&खQwr. dmrӘu6]kv+XCmʩ:0hXm }<.qn0%jQa vY^v\5FKrΕ\*ΨIX SGt|2wݗ4D88&|%:z0_-3x3,Ԋ h{]U|y 3%9u6q~oZ+tamE׍DC|&Dl!0ݙa[z+hcmjֲ0W:1aũs~WL#1H&–a`!=`Cfcb@C}pǷ8jm_BΚ!bb0Jx*G4סͺH p&TiW\l:r9LX`=9vvISdO@G]pZVu,s #8AYxht}QGC/ V̍ɡ8#;2wo_J"mZ7UMO ȤpXRViq-H>{Ym]vYaB:mwild]g!;TwzʝkvLU *Z|/v ]5TDn.9`+([xp-8B*Eî{V0 1&t C7^9-,2#鏱 U:$gFA(ME[ ̞© 8eࠎ= śƨv'ণ 1qѠpupЙ ) ![/W"5ޞ0Nm3ЩA E= !v Wp[sU$g!=RgIL?YC J,38Ax>}8uhXaҎ55;rL]z O&õ7q>۝ȝqJrNk-D,Y-Gjϭڛܹbۼ9VxuqQoqZhhHJ(^8`jnmVb׷4}o@bUa2[2N]T 'Og_ltc{Z&GXxA[HZcowPܖam6=dkoFnYK+P#D֦lJՎʞ1"'I0)Wũ$* Ҋ^T.q狗*Oٱj:r NXs/V9+P^okBhnL9ej}jھ|5X+WF-)vhK"?Șn@`M]k@9?ۿ?RN ^(LQ,tJZ[@fR y´/q#Ihuًj(d.$){*> Dީ$BmuViUpmYԶ\+t܆A-/W=l qa@Z8r.Y g⋾uO;֞gNѷqռE+R~*rdp6F`C(0)VЅ$\z; @Pfշ[ڡ$T#i33p.pmHCe0/T th+bAtMӐDadKbX& &FHiVLJjQLWV90Jg$v^uTY1d{Uw*׉Cb\s>LTI8VZIP(4W)S Ԇfcsfj$N>{oO?7MNwfHg luh6៓ U e~BE|G"NUUGoϔh W/ItS6<㦬DX:mIvQN)+W׹Z6h!笙AuKʩ+7_;޸~RԤ/75e\>S -e!S)R^{ܩ!xw3:z@Q-ԎTE]oP/_;F99&I髯/pFJ{x-odfx]7JF9Uq]TRU+L TquNTDM\V)Tjh@n7_44`Gxe$Ib]q8N$iohUJYs >ҧ!HIJ[Pz+y!rD.J j7Z"^t+fS &`N: B&QrKDPKT #'.} Rp4F2AtzJֳWr]`NDDL1'&ⴄ]ym aD yP!f_F͜ˆdZ̙ls8QI\֗A4BcU8!/bLQmtH:ZTB`kb|$M2('ي0؋}f[[rj*]ikתlJ~k:XK !&@ݕ|A_A_T] ̃|̞)kے*'_|k1`9RoB bs]X3p46ɪ6:DEWҫRFeo޼{Oг)3d^x5a #fZ —wfۃlcvAیfnT'`B.¿sttlmm[VoZ7{<(MmZN4 A&Xӫ[>W16#g780oĶ/6$Rh^UF1 1<.:?D ΗcM4[[j1`{Uc2(_|cU4\c$ s*F(&7fz/L"kWHHNC(Q^(ebN(>ʚ",T{ $,@wNUdo28qݶ30"DdM"F9>rY =E߇@J)֤{0ɴ4 D"5jIr b0\tHC)y/Gw|􂧥G ($KTpހy +t2f8q੨.:W|CY]9ίZ42 eBNkLJBOW[IQ%-G )-.@v_攁, Et8u76zBډ@){] `٬$'.j#Eh@Wed.J^Үo\[7*lGW[:RoFmqnȚ=>(7NBa5Rd~zUVF6A` j}jW,ϟק_}3 XU4R>Bs)牦yGWh[#mIN*tfTny'3~F1246NL\k_FijN_RȢMBW)OP *uL@>!SdI'[a4|Uro?hء ]__. VON_GG8v-8_36;~.HˇSVmq3|Q ̟~ŽbQ3354WqhmQ6e%s6uzQì; V1CfëMxyml`vU{gN ,MN#:^<<ƙ&e1G;Xq2£2do48S$l2S?oO*k%0 h`d`K|o嘲>3㺨erꤓ-K':r>Y QC-nݫIv9=g2nsI$A䠹^>|_=aq:c_]"WuϝDžָ 6Ѡka(WJ4WW&X9hW2Xnad.rh SjK&(㒒4PlP.Ak\.dGs.y>BjYqJ%Hk-G[3 XHVp"H8y̹Q,Q9'&:qg'gvڼJ󊡩[KXz8P9@Ʊc69EaΣqtaJ0Jx4̚O8L8- #Niв;Ï˞}`~BhԻsB]sC>3n@+4F;~ Ռ6(I0, yb!R ޔSN*d/\LلUc ġTdqj@H$zBg ; @/BBt&i#>}%1 q #5&Ve_tˊaIcQT:SBwM&hTTXeqgu$+ ^371/j>5C}XuMBnњsw}N+*KϘbȂ@5pg WI$O0E[3Hh8R]vQ%~6#"P" µ+Ҿ;ړ@Po,RYxqr*mWpDt`?dE,FsB}}o~_/(f%uUp%Y!k' =B\^؅#ܲdÄߡƨh8}r:DotDF12+"v}Sp+S!oՀNQ͊oE12|J30 kMʈ"r5t[w޻w>$Di^5l\jzVSh‰YFp>xHyDkLfwC}v4i36])#'k6=|<3ZTPѥ< c jP`sՔwftU⧚P%ؾJ΀uڊ"ZHk+Uεr@:NFwn3!v,PA旦|[MjC=ZPpS _~W5ڱlBiC$T;Ri/V@'^:Q.kx* ٢;忪^$[=! ȁ3F 8D!͉7'"iF(LD =5o7(l1Q۽"%򟽺־DXKBSNHD~]D|JپxsO@&u`%cvqeL[`5WV}!/f"𸡾cnM ErX+ԌZʠzaeBP6IPl`Wqn z!2: sZbVmL96)b\y#9j 3&6"=n1Ck/gfuƶYL,DfNˠ]`G yE֯g+942$*s@cr)ctТT$ԪggOx |̦nr4ex-4` 5R%yDNӂ k 8DŽ|z"ɜӌ_\%$m<u6Y`#$Ly2%\TB-#d$iaTB MI֪ˑf 7ȒŲ/?}vhBX(KDZèq }_}]m)AB91Kkj-MM#JsHj15x$%LSjZGԫ''OKeT f.XgS" M}Il:זYP"NqΊ`8D3V: ˲;KdeW=_~{gb3tf Ajznڪc| jx'aMh6E⻷2& -|~ ~/=ڝw[}LޅDR edN,5kܣoq J4<l'ִ$ށtg Lpɡ,-vʘlXjz2CibA׍~wg]=E=+#  oSxzHT}tm{3kQꯏO˿h?4}d.^h=)qL5#B;! ʝ12*PMWEkš$[m_jZ*v֪eK- ㏶"d{$js+ >U)+H\(3Y핋xI#,{ֲ(l7>= 3y8OOc҆z6aOmfМr*"*N,7GxVBKGPXe :t]"|9r8)&uQkμ@|1rA"B. ~}|gU9aKsn3qG蠡k}EM6{"<靳ԝvAd'voLg%4-D$J,&18PyN8S7EGªѯ1BO {7?߿\^tl 4N)*~-񴂔SD_oGc)yG'ϙu`I-] ZYfAySS,#^ឍNdž) U!)9\0tՌ=1S_fjJzi&P u{wݿ{ݻ7ٽ>SGCH"υ޺<غkY&oXoz >{mwm=2Li7c`}56n6:.,DѿYtF y{l6A= 3]V2;A?N4~9~BSk(k%v.fdڛ@eLBb՚QG6 z;>$rH⡢5wwAXXMi:\Mw>[jcnj( VYPČr_OϿ/u[d2Pze5G?_;d;BAx- .6J=TZ9Wh~WB+|kyQuVWj!X[U\*W+ ~VxG{!.޸"$Rh-vߊ)qYQفa0vxOϿpeZ@UWjMa5ys_2+JshIsf8@ѭh8[|`6/pq45t|si8&x }HvSG,>l4&LٹܲDS+0F,3s/^w'֕2_3$@H/.'oEfTD[,s-OȆ+q/" R:e"~e^U~ijȑ>9SDa)aeZyeЖ>/`T'!P {g,y쐵+nkّ!^SPi RIR4޻T Z!R1i*ѠrŷRblª"dL^.%#!.JjvѽQ;j?n 'ypd b&GQ'_&nKcghA-|Bj_O=vX _Npԝ9\XG 0L"Ѡ5 d!"nJwF2@JQ'Ȕ7H2 ;.nݠ dF^w= y gjSl 1c)p{S4VI?93Z3lI4YMVQskM8Q@E N:=*(8ުlVT]}JnU^+ <\Rj,SԈ/9J&*7C _@; 76 1 /FL^YT(ӓgN^~^6+pFg|l[Qj4qzz  UMQE"}L1{.w,ܴZM6RIsUgO+pzRj͊LEqGgނk[xEb)x/֤Hn v!$n@7i[1daA>dH939Nbw߻'~9`E`v~"z䧵YYmjQzcvFB3x ZFq-C„N|^__TC- zfĂ.&Re\" E9 $:XHwFݠ=Ohx嘗!zk K/ih{{h5G[/?遑xiMCߊVMX>-3mîȣ=~wzЃO*ajluV-Uq Zy_~e"_j'3^ tQ wJwM)32r0E[oI4lWjo9HUYNGTEëZfkaG Sl.>Jh/3O3eAr(2iNT\`Y•R["Kk5li {DCU4<;}VfK1 =!' +Q'Iܾq?O{~7E|&= լ  @Ք˅"=lx wMg vůKBaQ\8Ε7q^(Ե16 i tQrW vԆJC㝻2lWxŐ&"7㤆L' ZsCLN `*.k+$1j)`p, ujrbjI4#$¥ jkHjcDB3,B%LW`#ڳbspoY1 =rtcQ!Jf:Gr,i+XșMuc;wv!w$GDz>|?oԩFvSfؼ- 4Y=#ЀTyk/ #с>8 h%\ɳeӪNe}*kJ}. ;*֔q#A뱉j+;֑Ka]WOM.s&s +XyYB3h֦wxc{;6>/oh4ϧ&B<+4^ M!ݻwT.ydrck>gE7pqեT7s #l%@+dJIi_ š%Hh?px)S!'h`J(Gf&)AR{Bm]ə<\YexSu4`#聂B ɇ -Pc#v\CjUVj~a >?AeىA0 zs(vXd9(R_c&?srCVԀ/1e+m +Pr GaEpt6/uh#mJ''h> >bJrѕhk祈)%M z Ks$fHp%-ez[*菺Km3eTP ںN"8-.iu6P.G'B<ެV+xh@%|]\b:5L( V=O>JhAQ#0Bӆҝ#` & DȝԞDsO2f}dJi_pjm~+wQX!d+O +1&sn/:0omq6HaQ Xɼ<;}gx1;||}Q0s|UK'b+'F1YҸ4jQ!8c.$ 䍮+D2ҙ.p9CEV#JVRlvk4duG:Z7E[a%?ªc G=W` SaeUZY?|G~uH [}d뫇~4w#ƴm5^ܹa7#!h&mɺsO4!kj~F}Z!S _V!Aw9=-8G;Vk#04 -!yr%_6:1I!PLK屎' hn%;I 8M@Ubfȓ{h[2\M* Hr|HA t0VA5f0za(Q +jx˿ݘ&1a+ z f 7x\TL"Gg̛ -enXKX_A*qz9U]>NִhWV`*'qY:sϬHj3ikrДRxD ڸח.ߛ׵Z.#7KQâ !2KCMD[d"&n?`qhUP?Wi:/xB_ _w^" Iu>9X~iE@:rhfl{b1J,8 pj!j#tn_aCw Y2} ámg9HԢ!6ElEO=^W8YbcZfM:u*;wSy5DHҤLm)QPFдe bt5JwLpz ŸSB0l9.O>۷ko(Rˇzġysu07_EC:εXO`n7z}g2+5\.Α?;0,CYov\`mETb̺el`AF>QRyyj1᜵ TkP`]]pb۰r$ zݞMn ځ4nUk;x~#Q1vwV#3SWQ9t7B٩'Ͼj~}bYw';vh#Ȕr;T ZH:9 >UHܫNT_ 5eS](*Ug~qZQA ZdꁺyӼ}Y07^].ܸԁIOI*gXs L<]{~;թN?h}ΥD#4z-uR̔8'MY{_ի>5,R)kⓜV* ӿ~U[z.[5IXؕ7G̠׋x>vh+Qýڕ@t;3Z\͸7 =TZO ?:VkCt-_17QP %O$Wl: Ä0Dpӆ|%9hc %\֤HL eDp mWLt!Juno['/{?H@KphH23!pH_e2%^O< rAh3@$& X*vЃvw”[ 5 1f6= -%B?nH*  ~@ DE[]`6/:? cF ZK>FH*gkrUJ&[Xf;_2)H?JQ1lDNGZtWb e]Ǝ`2j،]uue-ܑ-L<:9&&.B-&^FXMJm̆v1S݂`6zf$v 2~1OX(yO>vRbaoz*şN|Ou Mb67$+Y@cδp;1i_bo9@YbWzWՓeFdBeB>yS„8W<)XU^$18++ Um KZba#+jk)~\BʄS{@nֆYnI 5U6iF?eT|\F"1?)ES}g `Á-^[{`[,ްûiF[8ZmbfY[Bkim3WcM\绛2ꘂZpZ5"Dۯy+͊\,⭦ m%6}_홓VԷ yhE%X ڱz;- .n@~sI #b eȃ d.,đ5A!=>:Wg//Uč/%oTQ;J*4|{P eRP dVe䀞__n u2Z̼,jh(gYXSqbNGuRE캭C'U'g;ԭW|$.2MT:M]ji-`[TC_ܜ]b5vTm|Y.<Ͽ=껟hu򂵭Z!/QCʌ}*|n;w?g֚iB t=x94!" md´&f\ư"<ؾ}84LUX該 Cz9@!3]iw>2)~-D9l'!ay й7b\s0a:_Mgvʽ#wIq4]Bm:ُޔjN2o̤\E*AX(8M3H &zeRîJ*+!eW`*%A`\/!)&%9*j} bN<}.<~"V@p把P:XqI6\r#ń!K|[i΂~2uR4g 3L/*TGI"dFcAƚ򵲙Y2`(#^EWk#GIYGE(PoGb%[`#BLh $P}_QH0k&r$/pΝSA/ʡ%#Yat٘LOJ oeH8L`nThDdHPD=l MbzˆJHos1RSϬe"vd9j= C,_7AUc(p7zfϡ#?b;=<~:`jfv#QΝPZJ8DϤab-/ECĹnvJb.*~MZ4 ,M}L sVcav.c7bu4UO^7s݁hxhhoCh*\<º-Y B㥫+ɓZU U`[ZVNWvFa:5/Š6fz؛y$+\%9ɡ]7hqB vZIOփQY)2"ӂ*t>DcelUǠLp{RkD)3k۪54re,&jRy˗# E,Y%FK4˻vT|W뜬rHTV#4̔DʚZLȽ2I{@hX$In&r$MEڡjR١7+׊u'}UpUd7[ʸ җӷr_ |g8w>[dB Í},3?[//y#'?XkA20ehjsk}98y8nFvqݜsv}-)* enܶVZ:ԅ+v㾥nv {܇ݦÚkImF רZ!ߊx6k5XQ$7%Λz˸ +5x*҉IŜhK r}m:]Tt=TO!]}ɼD_ǟkZ'J64-W;aU ޽OU6I~! 8Pp_ĀDFf$pJtM% (w^-g8x26bb hfȭ OcY d5И$l*k#2(KjF#'hC'( 13޻fthf Kccx.Рބ,6ѺV;2,@ ӱ_˛u"",-L{t`.qyu:.@yN` xoĠ<(R<gSA`;kN'4DV25ZIcƺ x6Ww) fQ :oIWYե*68JtEg*Xz%[/ݟ^2 chnH"yua#`n4 ) Jx4jlĹ7A̼~h8U#W+#Wkk?XlMho"2I hSȑb]p?آA5=րu |.bbip~xEۏ}9HaeiF4pBT^i%Bvq!<ف0,t03nO4 8d϶zܒ.mqX)0.b| fV&*gBx JA{%R=)RT=.{r:ވD*R6x?l5yFtj:/cڭɔNmūuC@Y4oCI{t7ylViMT/+g!L6]xuޣ!L,g¾ C[}&Uܬcak41 Vhpw?W/EwTmҨkZMB+߹{>??~GUO t<f80K)>aPlhhqc oP`w9qI4Ҁ`mF_Qgqo)LN|oȃd<g:st`[ r$OϕU]װ H0~ZdK'K(w|Ƈ cT?AAJc! \h,~dJJ'J[Efd(''GUK+HMa8 `n50ܨBzzNmc|B^Xm@b>05ͩS.LyEH ~Sf^#AO Jf93~³'U ^ %j޷BGbʊ-)p /" J"lK=[u!A4MО:@Zp֬Sv4+%4DB6RifZ!jeЮXV.WTPJ-TE[7jxet .(e_1Ģ{AwJeUû U>`eȨ[_'f̀?t5e")>yR V!V^A :AGXJI$EdĥWw΀kB{hfoݯՋOO_}}4CdRP\Ib"ޫ1WBF~Şv$W2w9Bgԕk7kAf2 j%y^F`ҨKڥ58>S%dC.k[飽4dD\d`ў:|ThWLb?|O?y׉ݡVY`n.~ Uڹ1~ӮCE1nX,Occ}6{=X?TPh`*<1w퓭i]fgf/ΐ%`t%V&ޭ1htLDx*>nJzchqf-j`icl}"i.*goA7z7ri?j*~==6Z!ؤZ} yk{b{a_ !ڣѺ! <Ar멵N4|GET6{#?|~-"% ziD`H5N-(⬺O]Ak}d2 kAׯ'yr؀ N[oJu"5Pެ"WZqZybfZ y̨ǭ~`α)-L8$xKErϿR>uhAfőҵz׮}'O͛7Y;6l9OM\fg\//¼kJ 8ha}@M1Yи7"O|%nqIJJ:d*YĀψ(UY 2 U9,zUϩ^iTO%kT K{JsG~P K>0 !<.ْ̂D-6K,R(Y TP])UPe,x} ftU(xNYe 1S-V]/wdG|9D<}j< KV ڣ,zP.JbqUZD($mQ%XDCn޸UIȰq" | $u#6{^f]eAn74Vr@6KgA hXK.Ö,=IX*|roV|JwKmV4ҭ TNwl[- Yf#X9o#dV(YQb36|-=t73;}l 4:AGX-f=t7*B[{4 ; _E~W"u~B;>k#Q^[M#m 4}m<ʴSgs3Ef0 C)ݡ&↾Bs.ʷ^kEO?h/tAK|RN4$2M^5N/l$`>U2ȕhx˙x_6 *I.WhdB]$x{k UR:x\:uBG]W3,N!gE9s[l1MH4ӣv~+ζ6id)t|G"=mRnPIx qI;<+$h&bߪ%"^;*3-:FTI$^ۦ} eLH@ ܞ/$NGQLAzčXVg~# UlϢ6C ]c X` D} (a8rE|c@x T8%Bf1ȦgUmjB&4=hkOTBM'VIBC9~3D=]ns%nQcv.\$7;CIKOQ& Skhftub[WgSfБaˡg)Co2()!z9 P9& 7S@Kqf P*;?ꫀN`|:ֿiTt\9T@KiP"9k*C\ UiZ;Y#tw~x^5\̷ [.bdL''g/UpJ1Y%8%uJ4`~kFe|j{"+H&$f]D * yO(NXG(t0@Ex ɼC[rLDMs=^j*YZ;?! /Y-'o]D>_ 7o]ˢn"KTL ֜Njȭ͎649-6b]7~`Ϗ-cc~p=g\1hgiٌ {-6{0V^_p.C=!q~&FȜhhܞ}Gv@#Z.\^>[!f̭zHS? ,X>Nb}fn#MB~-%<ż=zs"%y (J4|YV\bsc$qx] ;vryK]]$BpخՍJ*Pk/\ri9P*e+pƵZ+Ԁ"9]B͂hN5a) F9I{ıF[ڴjyV2*4?һtX~Zr]dGb/˯Vq!Ql5+s+yO>kđ-z+f@vP /wquGTQ0D*ZRj[pvjչ (urAb`Wf8׉]M# izYrd@Ҋ2$F@-Gi;L5G<~ΓƏP!v.l> s(%X!dwG\9AxEW9J qU^ɝ)1:2 *ήDӁ5 svC~,H( !`)0T%ancoй" ax˲4oJRxɔ5O<~X'G9yܭёfKCk=LSxM8\ fWGU<G!AOs"4JK3"!HM ef7)A7]QGFԉ fV`! [f9Gд늗F1.hª*7w=I)0 $Z( nd}6_lT)_<"ko,Yo p +`` R%%om2IQE`֩ Z%+U2ltX#+p,0Z(tZ9 ,wS4\6I~?;ѹsfbq18r \NJm֠DȰ&Βg?$ t%rhxUnܪ#6yZ:CLHRZ~*0T(J4`-nJXM>$N[&K4kFgk7h4 ~y645nt5t+|n !bTwڙ[-[^L QL0f^Yˎ;6{1~M]3E6! Ѻ Xl32crk84$ROv]K,( Qg:fQGJK})fM% Iz>ash Rnں'$E=G>@~$1߈B*R/tF+4sn@9IOWB)'E8.#mb^# =Ëdb( 9-`^I ?;=}GTCП)\_j{{( V5 [_?VC>l,2hBnmPҀF!$cn<v7'ЦL<'|fkσ)P >o .Omtx!=(;W`9OJ=`zb2 ۣ < 9U$"\D))R{C_mkIl\$kܣgL`L?k_mDßDC:'VpMZ&s޿wZ" #*OG P"1CAw*h!PN!p7p !oKrtTAhu+65RWn2I(7| -%`p7Ȧ&[S3[ I x4fQWKDOyD5kBNw|YC!3kgEzOѩ% ]&i,(RiLȋPMCv䓊"w=Ӗ"FL9✈TS ab !q!#fq1=Zف*2"os;[]@&eף\`Ӭ0M9xǨ6}6{Ƀi\a%v^4L~ZW6K,Fֹ &-V7J75 NBAIݣqUD {L8CYT Hb@] G3Q:6+E OY C;6QVl#HXuhI~__ P 5ƢZE,ի7.]Z5<~YŮynKg k{Km鳗_~QtILPY!:J' f h@B-v)$ZONOʻqV@,h-LqE&` .i/`AQUZ`U#Cʛh·DCHj8X+ $u ؤp>=jäb΂[r).OS29ƣjYPMlHH 6-5@b;TΠ>{YɍߘT2m-nAީ %]eptq0@Uձ]MRi @h9N N.r8ւG|{#XAPg}/_}}:1Nq(yP8в:>:\'T?N¼#j\(,eۧ7ƺLCS@ʭ\wh 2%,u8HzWȕPOϞWt-ikDri9:qa Fߡt3fW![x hvЧq\ CP"sB ! ?,*!tznP"W;46RIp0 xkfZe  @7CQ?> ҋ@ѳHlJ>9QK2є~ػ7՟%kK X=cSw丿j1Z*dxXɆZUFrf!#A\*AsO ` m8]bT#KElNkXDL5Q)fB!Je=up갢˵$z%Yؔ}zc'$Y=SǴ8bYXГћP;|:+1Yx;P_L@b3^VeJ*id~^FkTČI&ELWLO}a)[d@| фQ\pIYJ-FP:euӵ[O??{t+Wq%J<6 P}zo~߾9=ŷ9 V'i%WpG̋mBM^:^5j''O+SW[UPFhT6i`\ 4`~ e-_/KIxk`5RK 85i!O .G*r'0 ni Gx3` ?˯mj/z@$^97tt2jK߽w?~ɝ;w s7I1t޲]5hGfnEޔ_$.֔)CxtFF1A2W/Q-۔R+7~PaD\:T/qkx֡^$YXChVe$yLXTdbn+d_"' q̌cG0%5p]TjE*ѠzO}^*% Ljqxͫm>5d<7>3?Om A|"-= =P-cM9I7Mm{ۍr GS%Zk$_?`kǙqÉv  NK'7bzuWt{GX E>Pn&9"<7Sl;7#D1wDCL'THMڹҰ[/8rn?5FiRdQH4~Ãɩ:sݚ kAB6VY22yuZ>#G` q9lb1$=$P"rHkJ äIeCM4TL>TSLdʠ`,L^r=Hq(GؒgDwaY;մ (yg Lۛia8B/In ]͹%3hZ9Q#o3l#( zו3٢uutVɓax| C%{UNp"@̥" 0p G2ۆDJ`9((7pe jXvqR.iIWzج!@"\~m…T7u(eR'@/DZWq/E|h!*uƠ8T#TD8'?Rd>XJ#zĢmO[m2s(<(}"VP8 R LK0:;`_N^1LO㻁٫ӧ[/ѽ>uQ ;s (Y粆d?t@hP#(2zN9}vRK߼ ٣\NGûԸIqAYU=.xyP\O6),PSi(vFj%c|yN}G}JHNM;gG2XȰ՗9Xu?{tb?)+RDˣS&yaf/|cԅGCM иkrV9A``BzkÀb8ӻ5620{8~3VmN8n>,Zv8Lb]X M~ îB>hkפ8w@k,Ώ-i ms!o 3r V{,ذۧ25{nClxyREkMy[olgytZݙ5_yO>_|wuz!\-;i 7exL*dB[Ț*r3 0u>҉; &3㹔=־/U-Հ]W`Y2NM]YZΥr EU +Ԯ /VECꊽg1}mW {j /7Z=CLf{R_hg_گs&"NWi+sO>U͈y{kU NfvAo{v^:+ h3btQ$$7X}ȇ{"24U.p[0aXʭɹgq&ѸWc)e:}f AZk4x4?5Q)|Dɓ #Ci#:YZҪc-?y_~}i)Q7pg5b0 DAK -Q/BXZ%IP>ۙYHn?Lɗ U7E" ;Qn+WI3pMWAU$tpr 7*PN_ 씨9H{ ]aF?֊! D#:|,:&=MҐF^hA#+OkrDϤٰ˖nlTxTwx5RzI:rЃ lC)Kv 9U PjAjW{@c@t/zW;dTA(mUhkW ~hV<sVuInPAhc0 ̨ƭM#)AQd\JJ^u͇HT^Z앍$$D?pC%?Ww>6ՙ)Oݽ>ěA4q->~nh鍭k=+CpD2FwAcjib~˂m9y?umz'Mro,!_Gs35ֲjKmpO;p S U,fia P3sX 8oY+3aЁ=7Ek|ޮ7V mXv`:$u>]&΅mC0A UXIמ[)CesjOT66F[{R ˊwf=k`^L :prJX Q2YgL;O')ⳫKL^NpZ*;#jd:(1|d@5 8·&ۣ࢒vvn`bu_n ҙjъ;b^*+1=ѓn80@f*fOʋrv5w EW1m[#V.4uzq'}^b)1y˩LuNЮ06,Jbq̗w2@IzS)kVIݲ+ 'M>&]uޣN5y"M9]WE<sjFZ4'>dlNkQU*} yKő r}uԩnb;CyUN\%Rɸuܥ}byJPGfJWcFy@cZ^V"wY6!P~܄#B=?;B,Q'K<#+|N+t\ށ?rp,j c,ѽci*=i.$0JʝJ uEJ*N oݸs{w߻nsݚ}[ }Ӱh _)3d $($\;س&N6$g8OL/ #H/ ԅ s#¤2[+i*6ĺn#юNYei|=W2&8 DIJ^bFU#щ.U OQm`Vfr^0#$'LĬ&ܖsA6OX~S%: ]]e(U!yfjfBXod4PtMz<ԋlSi/%Qmti^/wj۷k;wJVV[a=瓷~zqzǟw|MBo/Ylgp}: 42Y,m~tX{#/x݉We;}z0_2 o;eXhyyWv>k5?N 6;aDhPiL3<8m pmrf(Y:&Ԙ12eGnފ76"hdCO;(Ml][3,5w*)i`'' 2&M~uǡfLK*# 0oOޔCzjaq%,q. `C݉+WvwydYhh6}j юCaàk"J7x+/Y2 4F3pz]έMX.A}΋LP kFXu?||>|,<^.u,ܑo}{/(H o9ދu\KQjxd=G~Z( RޞeA&.x^@"]FpE'B+dg=E<-iDGn` ]j1lQkHٙ$:U;+:IX /^?|_$Fr-.#o׍̓&4aЖ\mՈge6DNr 2 ":Ǭgd͚-hj &x5Ҹn4 f$"i[y4Ɉ 9cfӪU7ܮJY=>=fD}7[Z{)Z2qSZ ]39s ;W'-}yn|6+BH٤[[>>=|,y V3j$}eS7Vhn$c 3]! =DDlgYm21kVފM!Ӎ::L= L9H66>ka;cO̼-O/Zٷa_Diga(r[132*m,ӳ3;+\܉Wip%ԏ~_jem%waqR` ӂ-5]ChƏԶ6xL6l]f.gkZ[%߼vV٦zJ487bʁh9]^>\)# 0_9l2&Uh/~巧Oh PP+VݿSGU}G $3 .!6v3cF\kqrxJL!(gI)Sd.YYy ;mI""zС6?q~H3n@]k 0wi;!r>fR}3>M B 4PVsgDӃbѥ`tpN2yΌ[.<_{ ~>2QQEYlܽq?1w $ūHis8ZKKoĜNp6H54uhPW Thk~PB^գg]ԗH8%5  E(fxepW{Kct;ȓbfDHқi9's WPx R_;[TF<ά Ӓl&a)n~zǧ 3i4ih{Fv >^ " d1d2/CB#&AKZ iMX`RqAրq5hd =YW_8+Bg[WD./|Wܤ( Q{e vOH`-̜ɱX[b3Bʎ(a X](_#ͻ#rsӾ~B A^F"O>-zT]=6VdޜUN4XƩJEM/cv85W!2I5+PEHȨK(EP_~DL˪%Z3%BJ.?x~pe{dZFTEXVMο[98!6tMi~w߽DÍ:.̣,0h__o6{iccpijǑvW7;F&~w~uȘys2M?NCb:soeGZ=v ͚[x= U+?ou(DiWhf+_ޓ;ŵ(< s?4!'zHvMbG)[3}9<"|[=ڢ8nѺ1Հ-1pP TWUlX3xuY[~:Swrl։+kR4 ܘ$qňOHqp6Gf뚠K;W"nu-֑.dѥ)Mѕx"j[9Ğp+?83кJ1!%%w CXPh6`ƶD3J)ʎ]=!Ln:9~G(tGE͒ xڐdtQO1lFvdJ+ZAeB+vZ{[K$jGͲ5 IxZ4,ceV0jF>cX|{гI<_4*>_%MR-I%M4GU׶8GMs\l ;ݏydtT?ʁZ{wR >eZ`t|QuRV@+y B:roBbUz.݋#Qm^~v|ѓjE-`8A1)kqsOur'<3^E)f#G{( lQS6aF"3' 8&Dsf%4Ual)c?<0N5d{ ڇvztG޲vzp3^tRMU@ ؃<}e؏fu[v?̶F_.fM7K;mܶg _@n#4^gh!WzԇO an!t.l2 ##,=Cet;{o$!9I3)15$-{dfMx۴"e하3طpʺ?|_=.9lknQuk###+EjP:jJ"Nȅ}G)Vڛ+,_U+Xr\ $~zU.g.J-!ʿׯW?:yrR_tTn}U{#|KYnKc?~мo:nW_֐U2"AeVj߻޻Ua\XdKzW+J{5)Z@A%'Gn0ٸo7m5)6kﰿgj3D CR];Rl[aM\13֛VP+N ۑ-UPJ<޴"%)9!Z% lr&(ji4z$NC 2))g4 `R-dZzLee&tP@Sf_OK с~1j@[Mf .c# bLdK/"#E:&[[E}d?aYz2ذa6kKI'Ő5/B%R/KDȻs%a<=4jJfMa2O>nSʮN2 I'ivGx [lNtz~V>KGW>~&g>_|O?VXHI؈xQJqڋDb Ҳ CZ$)$/+pܪSoݪE[Vx860%k;':{#5aCXjTt57EEDۺr#~Iw$Tm &._Z;~}X F- e׸1?62_#˃BhA^^)K0X71!y;sϽ0u'vgk~0Xfnrn$w1n0QZEy^ -yX2rX.hwq6uY^;>b,{ݣGꢏHk`>j 5eWj nݭI;uCq 5|cf Dh{i4]Ĥ)M|7e7oN4q_ay!r! W7\O>޻u64avpg8ǎ.fVD72QKvNzDks͠d?{4X:O=)Uwrc 6zl=:T)3?09GRkxcW3)t0hd r +0Gsp ՓˮZ#%tɕ8V=`tN8,Obb"[Sbmd, mI zZʪ>\7J-jCk¸"ڙiT() D(gׯ#r]ZppjuM3 )<9ڪOqQi7ָ.BTKs=Lh `IG l}_|?9:x"teˇ~g%k)숎Tި4F=#}XFdbC$V8իgUpn֢6[V╜ x9od JS'TV,$UQ-Y/0nTJD8)H Bϐ@I׊޿WYZY#fǘ7j1>{1!Ys k`.nkӍ3p&kwyLCъ\֤jqZ DZM/4lP~o͏靁l m[o-HaN!J[7#YKr?hhEgMv 3d-;I 9r;~TN62wXR􋕹;&ݏ֖^8>`. K$e_ѿ黇=9*g .xO}bm<9BS@v-8֏lJJsEѬ*quI4`UѠzMK Q-Ct2>I^vުjj+j\8y~zrrZǝ[?;w)uU|z}д?Ie|=-&៵fn?}Y.d=$CN7SW^YUKkPZkUuK{s L>k}ω`V9{`n_?TB6gH*겉Gowe=j!fCs,=5c|= c!oRve *,@*N /abBf@! вimgmzq2 Fhh4G^i3v&-@BFD6,D0І9q 0 mgvG5Iz_cX.]iܭlh ڬʺgHQ#IߚWV tԷ`>:oW/$/?//}}e44] ?Ť_~O~^XŤm+r*C&∥K7ʲ;Ya%n̤'8DR */6}<{~Qi{ʹXC`0r B: D",*wWb5z<]}zbGBX%E똽ި__#SLO:YWmkKL}<ԻlFtfã!}G>A; "~UK>zdǁm,6c3.O}㋿~^HFzSIif+Z<5)7H:FRӹy1M[ ;x?[O`{RXL;0d[AȪ>\l )hƞcmQHQȈp0q%wӖl :K֮c[lio~?_r zW{1ls`>k/\yU]&{e) ܂9Kΰ2sxtm_FYUV /Xu 2j S:rPmyzN]xcFsmG5jm-P&ka͋ow=x4W/u{ 1l(2yfxJ_%61t0T*/ Cu>o_) ȜUo/~V&lrp21Gsq3JqlT GzYaﰨE ]I1!7+jFa`Ŧ0.nbE~H-7$q;u:C7F v+ )EHCwY&\ )(0mlPY8 d,w9X3,Ӧ 2o,{ i&w ۚ%Gcӛ/p} i@N5Z$\BS 9YoWB&&}iҿy,TXU(3D/JnÏ~!8B:mD@)H(!(`XVJL="KW5 Ӷƥ~Y/7 8P jYB\ x4P _!cP7#[&՜QMvlEt`NfF!jDr IOZ&ԡ' Q>Ll wFLħ@:&GQ+kaL L h># f|ی҃#C/saK'co/="RO 6e_͇ـ$7eSҴ0][=E Dz.6dia6 YjLX+Р52˿)]:THũNJ}dL)/^!}[mU.[:NMu0uMo]WȚq֙ߺ@A@cco֋RCU 3mJ1wcR0{Qr/_WQ۷;{l:J^ulK/-콱J+GJN/Z~YVOˈZ@[bɺ1XZrmLb\nh@(zͼ]s8e|U+d:ʔi\?% $k#qvWeHFV5lOtoNᛁJOނ8e $p仼!Vz&q k\u (IH-9G!v7nQvbP,|8[`k3?".q &S=t Nwnf 8Ox8J>i0Ҵ{ 3!]j^% ¤wߡ>"v54O (y JRrSfiQ<z`0W&Ղ 9R3YE%$i"a[Ċâ(W _SGi_'5Q٩<̆")I|J)@[o=3߯DM*U*i:@—zIKwӅ e5t!K-fjqvb Uvm*S/5w~ӻ@8xFk.}7).GQytĝE֗RYY/WUx--Ex_׎*j+vX2*5>UVCՈzP+gIDATT‚{s=S.Ж:N:|j^uS&勺kwEiA2ٞRkja-XaR)ʚEʢ|ٗ˿|/b-[;w*ZKHI+pW{! 9< rĵUz'wβ$Ev|"NttEb\ : hL#Z=SSocO^a&͖zi{PUux3Cf̆ hA.nuQ4NMT mGխΆ=t]x h $_g$&XZo5eLV'I-$QDK<üUOgYUYIUpH;> #,enC;Ј)?Қ"T :'%?@1e=f2@{Fc1.:! y(IDp} NQucA6$QWTԣ75k[9K %'mEE<9t4 /{zQ5קy0U_|_ mz:F{ *%RJJ̪4Jռ] TMB<<1*ыD틗O/.*Pu~Oa% _dyD:a!@C#3M+!?g-y,1il2M hHݻwWKFC5=XKu%~irY1Q򆁆A32=3 .ğxJby8 -(S{@zk&1)xH𔩙 ޖ2Ǡpњt053jxoeX)᥀ +e:|7]='O^s͖vO!DyVk*2ҲHm +k+S,;Lf%iY̋JvzMt# 󖁦|BU%,tHBYVq9{UҰn&zZ,r'?q}aԺF&,1{9s%Wnꆑ/O+Rc~կ~Oʙ|d( . pN gr=΅#X.$e9tC{5R[q?2 C|'4j(e2˙op"GvUJ =qt>û(oeyYu\h/ZwSƾ xy)|h`pOݭJPi@]kn[6|d&4e.a&e2|/|X a Fzk)h,鵰H]oq<PG=!2Y@  *d h uY*Il^Z3>G'7q1VhL v[h#Hlm~ ?Rۻ8擰F&x =e #Ȏr8qb2## 7AsY>!"Ȝ(>~cucV[;EP9J!e06R6rJ*8iA/Dug$DANW ʗBmBs_[Ua .$KEGz#N: %V 48͑%u(scKlӋ/y9G\T SD 'D6}]EPIolH!XiS$u]wϔ"q2Rts|brt?!܆'j%Bq[d05֜Gv˅<&(Yޓ<]i T޽okZ(A3f[CW0ת 2>@_Ȑ'MZ\4Ш/k[G8#_w=аB{##5!!QZ_V5(:n! 3shk՚eZn%ܐs ¾mAE X?ؼ^Qg!ecZ >U#5(iKu<~2{]{ތ/Ŭ-,CJÍx8{DA"Aza`4Xye`(.&Q^]\uez=#6|?I),3SMlglXW}nd`Ňjٖv^asz.N@/R3-Sӽ{K/uB&PѼz"Y,Bl ug44ױ]n(Pomvt A5Zy믿JN食7?ym(kR*C[\fb, Q`zJXhܨ0C$5dAʆ[fL}bХzo0"慁S06Z1ܮFl>ۿ/!H8[Xv/>:wWå*a".b#1Ct!21Gb8@ȡcil ѶeY/ѓS )C:mBc?LP"-;(l9%%>vFQBWp7(вN%`vGi$$-(&0 "*m -&1ڦT6h)`^ .ەѱ1QzGB3ԠpFCwC}5㧞o*3҉̝gvi&_W{|ꏤ\_p,d bm^ɊnG4Cnfr ]C _3ᆳ[1cZ2xGg4 5yxVIIR6#F?4.LGZ:D?XVG71߄*g/焟-,p5?۰UtJ>u\xLJAS꼫ߵMoZSxC&] mlqJ)!`T`#b_ѵxYuaSKfVFUP׺`k2gt\]8 ,@l(kEm#!z,;޺Fp2endΤ\ɨɺ;XtXzԜ rq_F>O?;gw X1}$'+U [cc@ )4A4,UYoeKJzgrEɾ^@'iYʦI fs^Xl8e|utQ8H,{$'BdEJGZu `D;GDuz O[rmb 0p`ض {[~! O$ugX 3 -.| 6L> T!A17ww4&1E婊cQSm;l_Cvrm`k9(\+0-R8^RjGr6qe+.e-`#=rܐk!D/ VVUH}8" %FFQУ*ҊD/M>$c=? A ju 6JK2zCǐ:Tkݨ~twZ&: ܔ^$Z!=M0i\>nN6:mճśWkwC0ӕW/o//jOϕA"Tmzh.F 3JӉԤy}Hrk)^aKxy: { 6CcR_}^Df.:ǁ l>l9Y4]Z31޿ /"'pE_K"oĦݧ2???wóe¢c~ǬkV',3Xxζ)7릛!T7|`H]RO2=V ΚP @-`(Fpdң apqpQlCC&Gzk( BmcAZf> ħaQJl Ll{S- dmbsPoXfV\b)m!f.DгyEtosSlhQ46fף"xRXwmI=!}k_#. |$gIs-6 pLHjC2T ^=!C^Mudc jrP#omٯⰦ,Ծ)%,'ARі>k\M-ܽ 2rnz{^H}\N[Đ3ɝsu_~W_|}[yo_9>6c|mU.ŷMi'-aQEE:hA:i=K&A,ɔƗ`[.IM?1n${ǶQ@P&<45E3ì ܑ^spb&1x> E{8mśJfO7#HI\G(w_Va "GQFN4X1aaHMwX!7sSf2t `Mg`xcPT /^(s@LՈ4@x!p?3PAZ$1g8Av]7YbCY@^2V8#$9~G/b2+YaTג^ E ^:,k7 G v" 55&4iHx.4<t/@ ӎB s'!%TDŽLRj?NҿQVvQ{gZ ;]脞;J`#m |3wKGc2}4??߬_-Ԫ'33 *m0cP.hˉBh )ŕ:ΊS̞PzUBu F~z?wy,.Eac |· 0b%USX 6aN~. D=5!WKUҹ r`P/ oNHА13úY4:T'E+&V}>(r]Yd_i,+V▕CxI9/%]UCT W \rɈ1]qxG0&`PFc['C1n /RZm :Q˘39w ) I r~ˊ.C/E˂rг#Ir3+ީ`VhpM vLo.,T$ mg)E٘z۴2_YYOG(TAb4iuQRj,~jB#7a5|qLOv ǢF58.1 (:6zNE3Ѕw'?5ܤ)R6KKEh 3S5?U0;Dc#57ݒr&#M_+&3=fLT}A߬g*@e4۞ovJZˢ`됕Z" 䐷&:/8~u?|tHqՊO~:%8V$:J]׼:J}al 4˺G7ԕM+ЪMiLI*ee4<&Y41c LOpID)ݽ[sOW!ց U1x;tX䓝_}]wC?حw= kDOBT浟S5Ӈ)_[#QF2?[{fq265R8̜DFS9uƌiר)>zs#X? D/-U; x@k?6K> tܑؖi?|65Duu!Dr _DvA./ٛ\RQ{i)-U_yz$@rbs` Sos b)w&:|ӺiZ|Uk5afV,zaHoXwuk, :MMdirVZzEa"l^(oh0beYfr{bnn?XFUv։Ǐ~_/~BpNtnNg"n#Pv  9SU-+ǝTRoq*4GY%nxeuv741lkJv|ʲx-6*Y F6Iv FV[':"b<8S_UFÇnкREMI&j$H혨*Q2e;wM 2xuг2KGؼUW4i@H(c>l(ex TLFMY2 +֊Qsډ07GpEpj$Go/h-+Ѐ” c& Ö1&}sR?et^Pm# Ư_>=N.R&U%2 Q/LBx5ssfJnMGnkpZ@Z ɛHRvf[%aTEwX∐z:~ɳ ޔ#6z]JQOo[d]x iM{uy3ck(!H@=dRFDRzꞘAltg ѷ2h7S. '$f+᧯rãw0T@]TNLRillT#sbCvoAQƗm[uT^e4‚T5j:Z3?f#GKX=ZDS$6QI Yslr76Uײᔧ'aj7͖WO_\>/*?};n߿wL*!XuJ99k<,:)\eD,Y1au4m86U9Ͻ *!k9D6%d?i6Dl6e,}(*XbTtdV م0L~L'{qVl+Lx`Y˺Jd h(B һA<0KhnQA#lЃK % 07&ĤB 2Dc}pml=١`ax~#:n=_CJkm Pؓ-"[4Բ5fi> OQ4̛5Ls;"CjFG9t,%Vi0A8*{v܆rC&l1Z] L<6?qP[ T- }}qٯ5< X5|sNuN A,d wM/J^<{vyuYO4F;)nԁ1W"+ 7C#K!(aL{FفNCgF6huxуRW Y_Dž$2 ɩ1rȀR =R 8b(n J)"Egݎda=WEGu]T(uFKym: 4bl*Ԡ?$uKvS|3|F$cjIͣA&UNJ@V W/j m6V"J $ʑ'+_ϣ?o)4B<͢IԃluP&I dRBr>Z!*{ /~+pI#E-+]Yt>بlE9h5Nf8*.H"Md6IGmjCLߥ,q:Аg`074$Lc-?X.i52ZqbQ r̕ʠd{N@FݑsELߍR÷FA1: &>Bi݅a)hڛwu^bXCcDi5,߮ޫ'z\G}Cl蝗1KmtDG-DK[e")'62L{X2tʴP](}A 5VڠQ=豞TNG(,hۯkN=0(E 5'!yv/mpjϲ.^|jZ_|y%N2ž]I/N?a̭/-n1fbV[6_9ژUH$y^*b3 /H)e?VEo ;l\o`q-͠a1!%r@% *¦ ;S`9PPQñEs H #w7nAG24r?0 U2 \h|vVj(XS\"HQ9C"(LT{X5@_%Ȓ5W'.jB-U|YڸUTdSF.]h ,ΜW[7Pk`̥!b2S m<Vؿ/P31Y7Z@ 9*-Hݑ0p"'9NV턓3%dR0Sǵ``l`Do|+ҋN/m*bIi(%٥䠞4<quuY^;X+\-ל\UA7$M T,FHU