PIDA-0.5.1/0002755000175000017500000000000010652671503010335 5ustar alialiPIDA-0.5.1/bin/0002755000175000017500000000000010652671501011103 5ustar alialiPIDA-0.5.1/bin/pida0000644000175000017500000000237110652670576011757 0ustar aliali#! /usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. if __name__ == '__main__': from pida.core.application import main main() # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/bin/pida-remote0000644000175000017500000000077310652670576013254 0ustar aliali#! /usr/bin/env python import os, sys from pida.utils.grpc import LocalClientDispatcher from pida.utils.testing import refresh_gui class PidaClientDispatcher(LocalClientDispatcher): def remote_ok(self, command): sys.exit(0) def main(): dispatcher = PidaClientDispatcher(9124) file_name = os.path.abspath(sys.argv[-1]) dispatcher.call_server('open', (file_name,)) refresh_gui() print 'Error: PIDA was not available' sys.exit(1) if __name__ == '__main__': main() PIDA-0.5.1/contrib/0002755000175000017500000000000010652671501011773 5ustar alialiPIDA-0.5.1/contrib/kiwi/0002755000175000017500000000000010652671501012736 5ustar alialiPIDA-0.5.1/contrib/kiwi/bin/0002755000175000017500000000000010652671501013506 5ustar alialiPIDA-0.5.1/contrib/kiwi/bin/kiwi-i18n0000755000175000017500000000564210652671043015162 0ustar aliali#!/usr/bin/env python # # Copyright (C) 2005 by Async Open Source # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import os import sys # Required version of Python REQUIRED_VERSION = (2, 1) # Directory name, defaults to name of binary, it is relative to .. # a, __init__.py and main.py is expected to be found there. DIRNAME = 'kiwi' # Application name, defaults to capitalized name of binary APPNAME = None # Do not modify code below this point dirname = DIRNAME or os.path.split(sys.argv[0])[1] appname = APPNAME or dirname.capitalize() if sys.hexversion < int('%02x%02x0000' % REQUIRED_VERSION, 16): raise SystemExit("ERROR: Python %s or higher is required to run %s, " "%s found" % ('.'.join(map(str, REQUIRED_VERSION)), appname, sys.version.split()[0])) # Figure out the directy which is the prefix # path-of-current-file/.. currentdir = os.path.dirname(os.path.abspath(sys.argv[0])) basedir = os.path.abspath(os.path.join(currentdir, '..')) # Add the base directory where the application is installed in to sys.path if os.path.exists(os.path.join(basedir, 'lib')): pythondir = os.path.join(basedir, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages') if not os.path.exists(pythondir): raise SystemExit("ERROR: Could not find required directory: %s" % pythondir) elif not os.path.exists(os.path.join(basedir, dirname)): raise SystemExit("ERROR: Could not find required directory: %s" % os.path.join(basedir, dirname)) else: pythondir = basedir sys.path.insert(0, pythondir) main_module = 'kiwi.i18n.i18n' try: module = __import__(main_module, globals(), locals(), 'main') except Exception, e: raise SystemExit("ERROR: Failed to import required module %s\n\n" "Exception raised during import:\n %s: %s\n" % (main_module, e.__class__.__name__, e)) main = getattr(module, 'main', None) if not main or not callable(main): raise SystemExit("ERROR: Could not find callable 'main' in module %s" % main_module) try: sys.exit(main(sys.argv)) except KeyboardInterrupt: raise SystemExit PIDA-0.5.1/contrib/kiwi/bin/kiwi-ui-test0000755000175000017500000000565410652671043016000 0ustar aliali#!/usr/bin/env python # # Copyright (C) 2005 by Async Open Source # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import os import sys # Required version of Python REQUIRED_VERSION = (2, 3) # Directory name, defaults to name of binary, it is relative to .. # a, __init__.py and main.py is expected to be found there. DIRNAME = 'kiwi' # Application name, defaults to capitalized name of binary APPNAME = None # Do not modify code below this point dirname = DIRNAME or os.path.split(sys.argv[0])[1] appname = APPNAME or dirname.capitalize() if sys.hexversion < int('%02x%02x0000' % REQUIRED_VERSION, 16): raise SystemExit("ERROR: Python %s or higher is required to run %s, " "%s found" % ('.'.join(map(str, REQUIRED_VERSION)), appname, sys.version.split(' ', 1)[0])) # Figure out the directy which is the prefix # path-of-current-file/.. currentdir = os.path.dirname(os.path.abspath(sys.argv[0])) basedir = os.path.abspath(os.path.join(currentdir, '..')) # Add the base directory where the application is installed in to sys.path if os.path.exists(os.path.join(basedir, 'lib')): pythondir = os.path.join(basedir, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages') if not os.path.exists(pythondir): raise SystemExit("ERROR: Could not find required directory: %s" % pythondir) elif not os.path.exists(os.path.join(basedir, dirname)): raise SystemExit("ERROR: Could not find required directory: %s" % os.path.join(basedir, dirname)) else: pythondir = basedir sys.path.insert(0, pythondir) main_module = 'kiwi.ui.test.main' try: module = __import__(main_module, globals(), locals(), 'main') except Exception, e: raise SystemExit("ERROR: Failed to import required module %s\n\n" "Exception raised during import:\n %s: %s\n" % (main_module, e.__class__.__name__, e)) main = getattr(module, 'main', None) if not main or not callable(main): raise SystemExit("ERROR: Could not find callable 'main' in module %s" % main_module) try: sys.exit(main(sys.argv)) except KeyboardInterrupt: raise SystemExit PIDA-0.5.1/contrib/kiwi/common/0002755000175000017500000000000010652671501014226 5ustar alialiPIDA-0.5.1/contrib/kiwi/common/async.mk0000644000175000017500000000564210652671075015707 0ustar aliali# # Copyright (C) 2007 Async Open Source # All rights reserved # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., or visit: http://www.gnu.org/. # # Author(s): Johan Dahlin # # # Common Makefile rules for all Async packages # DEBVERSION=$(shell dpkg-parsechangelog -ldebian/changelog|egrep ^Version|cut -d\ -f2) TARBALL=$(PACKAGE)-$(VERSION).tar.gz BUILDDIR=tmp DOWNLOADWEBDIR=/mondo/htdocs/stoq.com.br/download TARBALL_DIR=$(DOWNLOADWEBDIR)/sources TESTDLDIR=$(DOWNLOADWEBDIR)/test UPDATEAPTDIR=/mondo/local/bin/update-apt-directory deb: sdist rm -fr $(BUILDDIR) mkdir $(BUILDDIR) cd $(BUILDDIR) && tar xfz ../dist/$(TARBALL) cd $(BUILDDIR) && ln -s ../dist/$(TARBALL) $(PACKAGE)_$(VERSION).orig.tar.gz cd $(BUILDDIR)/$(PACKAGE)-$(VERSION) && debuild -S rm -fr $(BUILDDIR)/$(PACKAGE)-$(VERSION) mv $(BUILDDIR)/* dist rm -fr $(BUILDDIR) sdist: kiwi-i18n -p $(PACKAGE) -c python setup.py -q sdist rpm: sdist mkdir -p build rpmbuild --define="_sourcedir `pwd`/dist" \ --define="_srcrpmdir `pwd`/dist" \ --define="_rpmdir `pwd`/dist" \ --define="_builddir `pwd`/build" \ -ba $(PACKAGE).spec mv dist/noarch/* dist rm -fr dist/noarch upload: cp dist/$(TARBALL) $(TARBALL_DIR) for suffix in "gz" "dsc" "build" "changes" "deb"; do \ cp dist/$(PACKAGE)_$(DEBVERSION)*."$$suffix" $(DOWNLOADWEBDIR)/ubuntu; \ done $(UPDATEAPTDIR) $(DOWNLOADWEBDIR)/ubuntu test-upload: cp dist/$(PACKAGE)*_$(DEBVERSION)*.deb $(TESTDLDIR)/ubuntu cp dist/$(PACKAGE)-$(VERSION)*.rpm $(TESTDLDIR)/fedora for suffix in "gz" "dsc" "build" "changes"; do \ cp dist/$(PACKAGE)_$(DEBVERSION)*."$$suffix" $(TESTDLDIR)/ubuntu; \ done $(UPDATEAPTDIR) $(TESTDLDIR)/ubuntu release: clean sdist release-deb: debchange -v $(VERSION)-1 "New release" release-tag: svn cp -m "Tag $(VERSION)" . svn+ssh://async.com.br/pub/$(PACKAGE)/tags/$(PACKAGE)-$(VERSION) ubuntu-package: deb pbuilder-edgy build dist/$(PACKAGE)_$(DEBVERSION).dsc cp /mondo/pbuilder/edgy/result/$(PACKAGE)_$(DEBVERSION)_all.deb $(DOWNLOADWEBDIR)/ubuntu $(UPDATEAPTDIR) $(DOWNLOADWEBDIR)/ubuntu clean: rm -fr $(BUILDDIR) rm -f MANIFEST tags: find -name \*.py|xargs ctags TAGS: find -name \*.py|xargs etags nightly: /mondo/local/bin/build-svn-deb .PHONY: sdist deb upload tags TAGS nightly PIDA-0.5.1/contrib/kiwi/debian/0002755000175000017500000000000010652671501014160 5ustar alialiPIDA-0.5.1/contrib/kiwi/debian/patches/0002755000175000017500000000000010652671501015607 5ustar alialiPIDA-0.5.1/contrib/kiwi/debian/patches/00_eggify.diff0000644000175000017500000000076210652670656020227 0ustar aliali--- kiwi/dist.py~ 2006-04-25 12:08:54.000000000 -0300 +++ kiwi/dist.py 2006-05-06 19:29:41.000000000 -0300 @@ -25,7 +25,8 @@ from distutils.command.install_data import install_data from distutils.command.install_lib import install_lib -from distutils.core import setup as DS_setup +#from distutils.core import setup as DS_setup +from setuptools import setup as DS_setup from distutils.dep_util import newer from distutils.log import info, warn from distutils.sysconfig import get_python_lib PIDA-0.5.1/contrib/kiwi/debian/patches/01_avoid_building_howto.diff0000644000175000017500000000042610652670656023152 0ustar aliali--- doc/Makefile.old 2006-08-24 21:43:21.000000000 -0300 +++ doc/Makefile 2006-08-26 15:30:15.000000000 -0300 @@ -14,7 +14,7 @@ NAME=Kiwi URL=http://www.async.com.br/projects/kiwi/ -all: api howto +all: api # howto (not working for now) howto: howto.tex mkdir -p howto PIDA-0.5.1/contrib/kiwi/debian/patches/02_disable_pydoctor.diff0000644000175000017500000000061510652670656022302 0ustar alialiIndex: doc/Makefile =================================================================== --- doc/Makefile (revisão 6595) +++ doc/Makefile (cópia de trabalho) @@ -46,8 +46,7 @@ api: @echo Creating API documentation - @cd .. && pydoctor --project-name="Kiwi" --add-package=kiwi --make-html - @mv ../apidocs api + @mkdir api clean: rm -rf api/ howto/ *.log *.aux *.l2h *.dvi *.ps *.bak *~ PIDA-0.5.1/contrib/kiwi/debian/changelog0000644000175000017500000001317210652670657016047 0ustar alialikiwi (1.9.16-1) feisty; urgency=low * New release -- Johan Dahlin Mon, 16 Jul 2007 11:01:57 -0300 kiwi (1.9.15-6) feisty; urgency=low * New package with a few more bug fixes included -- Johan Dahlin Wed, 23 May 2007 13:26:29 -0300 kiwi (1.9.15-5) feisty; urgency=low * Save the prefix from the module and use it to find the externals when we're in uninstalled mode. -- Johan Dahlin Tue, 22 May 2007 16:25:06 -0300 kiwi (1.9.15-4) feisty; urgency=low * Redo for feisty -- Johan Dahlin Tue, 22 May 2007 09:49:28 -0300 kiwi (1.9.13-0ubuntu1) feisty; urgency=low * New upstream release: - Workaround GtkEntry bug when resizing the size of its GtkWindows. - Include _kiwi.c in tarball, fixes build. - Use pkg-config to find pygtk version. * debian/control: - added pkg-config to Build-Depends. * debian/patches/02_avoid_version_checking.diff: - updated. -- Daniel Holbach Tue, 6 Feb 2007 09:42:28 +0100 kiwi (1.9.12-0ubuntu1) feisty; urgency=low * New upstream release. * debian/patches/03_epyrun_use_local_modules.patch: - updated. * debian/control: - moved python-support from Build-Depends to Build-Depends-Indep. -- Daniel Holbach Tue, 30 Jan 2007 09:52:28 +0100 kiwi (1.9.9-2ubuntu1) feisty; urgency=low * debian/control: - Replaces: kiwi. -- Daniel Holbach Wed, 20 Dec 2006 13:00:09 +0100 kiwi (1.9.9-2) unstable; urgency=high * debian/control: - also need to build-dep-indep on python-glade2 (because the doc generation wants it) and xfonts-base (Xvfb needs this one because of the 'fixed' font); will need a way of import'ing gtk without a DISPLAY; anyone? (Closes: #393874) * urgency high because it fixes a RC bug -- Gustavo Noronha Silva Thu, 19 Oct 2006 00:12:34 -0300 kiwi (1.9.9-1) unstable; urgency=low * New upstream release * debian/watch: - updated watch file for the new download location * debian/control: - add XS-Python-Version, since vorlon convinced me of its importance * debian/control, debian/rules, debian/patches/01_avoid_building_howto.diff, debian/patches/03_epyrun_use_local_modules.diff, debian/python-kiwi.docs, debian/python-kiwi.examples: - enable building the API docs and instlaling the examples; the howto is not fully buildable from the tarball's contents plus tools (Closes: #384159) * debian/control, debian/rules: - make the API building process run inside a virtual X server, for it needs to import GTK+, which really wants a DISPLAY; - we need to build-depend on xvfb and xbase-clients for that to work * debian/python-kiwi.doc-base.api - register API docs in the doc-base system -- Gustavo Noronha Silva Sat, 26 Aug 2006 17:26:19 -0300 kiwi (1.9.8-7) unstable; urgency=high * debian/preinst: - really fix the upgrade problem (Closes: #379359) -- Gustavo Noronha Silva Mon, 31 Jul 2006 23:45:42 -0300 kiwi (1.9.8-6) unstable; urgency=medium * debian/preinst: - added to handle problems in upgrade caused by -4 and earlier being built with a python-support version which was buggy when handling "outside" modules (Closes: #379359) * debian/rules: - cleaning up; removing commented code that is not needed anymore and remove the code that sed's the python version, for that is done by the tools now * urgency medium to get the buggy version out of testing soon -- Gustavo Noronha Silva Mon, 31 Jul 2006 23:22:58 -0300 kiwi (1.9.8-5) unstable; urgency=low * debian/01_python2.3_install.diff: - removed; no longer needed * debian/control, debian/rules, debian/pyversions, debian/pycompat: - use python-support 0.3 and newer cdbs, that builds using the minor version required by the package; also make the build system more robust (Closes: #375270) -- Gustavo Noronha Silva Mon, 19 Jun 2006 23:37:54 -0300 kiwi (1.9.8-4) unstable; urgency=low * debian/patches/02_avoid_version_checking.diff: - don't try to parse the sys.version string; there's no need to check for versions inside the script; that's the packaging system's job, so simply disabling (Closes: #372660) * debian/control, debian/rules: - support the new Python Policy (Closes: #373560) - update build-deps and deps to have already new-policy-compliant packages for python-setuptools and python-gtk2 (Closes: #374505) * debian/python-kiwi.{postinst,prerm}, debian/python-support.version: - removed; will be created automatically from now on * debian/rules: - make sure the shebang is modified before the install target is run, so dh_python will have python:Depends right -- Gustavo Noronha Silva Thu, 15 Jun 2006 20:55:10 -0300 kiwi (1.9.8-3) unstable; urgency=low * debian/control: - added dependency on python2.4-setuptools * debian/rules: - rename the egg-info directory to remove the python version -- Gustavo Noronha Silva Sat, 10 Jun 2006 19:05:25 -0300 kiwi (1.9.8-2) unstable; urgency=low * debian/control: - added dependency on python2.4-gtk2 - updated Standards-Version to 3.7.2, no changes * debian/patches/00_eggify.diff: - updated; no need to include ez_setup * debian/watch: - adding new upstream versions monitoring -- Gustavo Noronha Silva Sun, 4 Jun 2006 23:57:18 -0300 kiwi (1.9.8-1) unstable; urgency=low * Initial Release (Closes: #171950) -- Gustavo Noronha Silva Sat, 6 May 2006 19:22:50 -0300 PIDA-0.5.1/contrib/kiwi/debian/compat0000644000175000017500000000000210652670657015367 0ustar aliali4 PIDA-0.5.1/contrib/kiwi/debian/control0000644000175000017500000000260110652670657015573 0ustar alialiSource: kiwi Section: python Priority: optional Maintainer: Gustavo Noronha Silva Uploaders: Debian Python Modules Team Build-Depends: cdbs (>= 0.4.43), debhelper (>= 5.0.37.2), python, python-setuptools (>= 0.6b3-1), python-support (>= 0.3) Build-Depends-Indep: python-all-dev (>= 2.3.5-10), python-glade2 (>= 2.8.6-1), python-gtk2 (>= 2.8.2-3.1), python-epydoc (>= 2.1-11), python-twisted-core (>= 2.4.0-1), xvfb, xbase-clients, xfonts-base, pkg-config XS-Python-Version: >= 2.4 Standards-Version: 3.7.2 Package: python-kiwi Architecture: all Depends: ${python:Depends}, python-gtk2 (>= 2.8.2-3.1), python-setuptools (>= 0.6b3-1) Enhances: gazpacho Replaces: kiwi XB-Python-Version: ${python:Versions} Description: a graphical framework to construct simple UI Kiwi is a framework and a set of enhanced PyGTK widgets designed to make building programs with graphical interfaces both easy to write and easy to maintain. . Kiwi consists of a set of classes and wrappers for PyGTK that were developed to provide a sort of framework for applications. Fully object-oriented, and roughly Smalltalk's MVC, Kiwi provides a simple, practical way to build forms, windows and widgets that transparently access and display your object data. . Kiwi is inspired by Allen Holub's Visual Proxy. . Homepage: http://www.async.com.br/projects/kiwi/ PIDA-0.5.1/contrib/kiwi/debian/copyright0000644000175000017500000000202510652670657016123 0ustar alialiThis package was debianized by Gustavo Noronha Silva on Sat, 6 May 2006 19:22:50 -0300. It was downloaded from http://www.async.com.br/projects/kiwi/ Copyright: Copyright (C) 2005-2006 Async Open Source License: This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian systems, the LGPL version 2 license can be found at /usr/share/common-licenses/LGPL-2. PIDA-0.5.1/contrib/kiwi/debian/kiwi-doc.install0000644000175000017500000000010610652670657017264 0ustar alialidebian/tmp/usr/share/doc/kiwi/howto debian/tmp/usr/share/doc/kiwi/api PIDA-0.5.1/contrib/kiwi/debian/kiwi-examples.examples0000644000175000017500000000001310652670657020502 0ustar alialiexamples/* PIDA-0.5.1/contrib/kiwi/debian/kiwi.docs0000644000175000017500000000003210652670657016001 0ustar alialidoc/base-model-design.txt PIDA-0.5.1/contrib/kiwi/debian/kiwi.install0000644000175000017500000000016210652670657016523 0ustar alialivar/lib/python-support usr/bin/ usr/share/kiwi/glade/ usr/share/gazpacho usr/share/kiwi/pixmaps/ usr/share/locale PIDA-0.5.1/contrib/kiwi/debian/pycompat0000644000175000017500000000000210652670657015740 0ustar aliali2 PIDA-0.5.1/contrib/kiwi/debian/pyversions0000644000175000017500000000000510652670657016330 0ustar aliali2.3- PIDA-0.5.1/contrib/kiwi/debian/rules0000755000175000017500000000132410652670657015251 0ustar aliali#!/usr/bin/make -f DEB_PYTHON_SYSTEM := pysupport include /usr/share/cdbs/1/rules/buildcore.mk include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk include /usr/share/cdbs/1/rules/simple-patchsys.mk DEB_COMPRESS_EXCLUDE += .py .glade .gladep DEB_PYTHON_INSTALL_ARGS_ALL += --single-version-externally-managed binary-post-install/python-kiwi:: mv debian/python-kiwi/usr/share/python-support/python-kiwi/kiwi-${DEB_UPSTREAM_VERSION}-py*.egg-info \ debian/python-kiwi/usr/share/python-support/python-kiwi/kiwi-${DEB_UPSTREAM_VERSION}.egg-info build/python-kiwi:: cd doc && $(MAKE) clean:: cd doc && $(MAKE) clean -rm -rf kiwi.egg-info -find . -name \*.mo -exec rm {} \; PIDA-0.5.1/contrib/kiwi/debian/watch0000644000175000017500000000011510652670657015217 0ustar alialiversion=3 http://ftp.gnome.org/pub/GNOME/sources/kiwi/1.9/kiwi-(.*)\.tar\.gz PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/0002755000175000017500000000000010652671501016026 5ustar alialiPIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/0002755000175000017500000000000010652671501020040 5ustar alialiPIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/0002755000175000017500000000000010652671501022372 5ustar alialiPIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/hyperlink.png0000644000175000017500000000152610652670670025115 0ustar alialiPNG  IHDRw=bKGD pHYs  tIME' - %IDATxՕMHaMh1nQH=HzP+/x)Z=䢂 '-^T*^,f fhZk@Ӎhkxp`.<3 p1ƚ]Q~Ľ'--- A~TmTYu^Ko^o_&GY SaQ;-)ЩP%;%2|.[^V.f:%8~Z6/?VwmNws }28x:;;a0֩>s0~5~$d9l ڴ̗ v\i(Z97Cf7t$pX/ &Ctwwc{{?'Yv?G 0̏9BYL&-[d(_Y"UUQ[[I\.8_M`Q,#G>biF{{{FT__O#i@H$hiil6`Q*x8` 9(IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwibutton.png0000644000175000017500000000137610652670670025312 0ustar alialiPNG  IHDRw=bKGD pHYs  tIME !M?IDATHkA?]Hv A<i4E܃h)! xA,"A"Q[B+4(PHu$YOfއy7lD@h7߁Z$8xo׳gΚ*ʷ(\3i|$~^߅)hvMfjk޲QJY[/nMErK|JH !o[I @9*wQ >+.B q(?֊5eᄑ$]5IDVVCJǧXLv{xzBX>m LcSdeLxrU, qzzzk D)U_i8t6 V(PJidd2Y䪸V+C  ) 'J%ZZZ~NLOO_B8sMmjCD |IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwicheckbutton.png0000644000175000017500000000141510652670670026302 0ustar alialiPNG  IHDRw=bKGD pHYs  tIMEgzzIDATxMHAߙ5Ӥ$[V( RDT4-= <氠c{bJ)h^,bvB!=xbkI_$z}> 6G?ǶmpzpIbX[eMܯ%p RJԃr`= d) TM=t!pΛkZsI1WbPu\x@jazm}! ۜ v6ƽΚϚBz3!$###% \ۅƀ-߀Cpـ>aPR4Mhvd @;pƒVW0Euuu-˲R^|sZ&c]{x4 8[-Q_n ]DIC|HQdY(R^W(*p~?Ð%ivl6{0wMDEdkk?r5vppK,Fx,//#IRum]ö{d!Ia;' +aFFFFB#m>}^DFGG%rl+ ;0M-HW%Kb7|>Ah<WG'nA8h-r) K%>|8p[E6~pkoW9^F^` b`062F@<-DG}2xoKWsP|>_LӴ;(/q\^z,,,KLƓ nMWWW`nn)uM5IƆH$(JEL$j0 2X;EQ$izTrtH$lJ땶8677!IR-h֜d2PᵅX,֜Ɇ9uсU@;ա"OLL8*tږ"4kLQ}Qs*֔kd25 [E}YOn r6]o\%oZIENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwicomboboxentry.png0000644000175000017500000000156410652670670026670 0ustar alialiPNG  IHDRw=bKGD pHYs  tIME#%QstmIDATxڽKa?3&EP8!v:XtRA$*MM(vn!RAEPhV0ah<]riK)_8ޗ{(x(xIDATxTKcW/OHfLxZR?j@X2n wC]t]qajAC3(…B#J)-*M|I=pPEf`s;_;7wu]@hGcVUESݿ*7Ţe5RU/hOjZua*sLE39K=ˢ^~}YTc.0~RUh@il|}DN׶j0 )@ `ض}< &[?U{Vؿ凜۶mP3 ?: R)9AYw``]]]D"㟏#J K[[[PPT*!LQo7ђN0 ,"1mooS0$4qz=M"MT5MCgg'~?666`Yh4 T*(NNN<|c bffu]LOO#ޒsr $B&s""=j Q,#"Qo!ѝ2Mx. "j5r91900??0oxM&ځ(IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwientry.png0000644000175000017500000000160710652670670025135 0ustar alialiPNG  IHDRw=bKGD pHYs  ~tIME)>IDATxTKcW/OHfLxZR?j@X2n wC]t]qajAC3(…B#J)-*M|I=pPEf`s;_;7wu]@hGcVUESݿ*7Ţe5RU/hOjZua*sLE39K=ˢ^~}YTc.0~RUh@il|}DN׶j0 )@ `ض}< &[?U{Vؿ凜۶mP3 ?: R)9AYw``]]]D"㟏#J K[[[PPT*!LQo7ђN0 ,"1mooS0$4qz=M"MT5MCgg'~?666`Yh4 T*(NNN<|c bffu]LOO#ޒsr $B&s""=j Q,#"Qo!ѝ2Mx. "j5r91900??0oxM&ځ(IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwilabel.png0000644000175000017500000000152610652670670025053 0ustar alialiPNG  IHDRw=bKGD pHYs  tIME' - %IDATxՕMHaMh1nQH=HzP+/x)Z=䢂 '-^T*^,f fhZk@Ӎhkxp`.<3 p1ƚ]Q~Ľ'--- A~TmTYu^Ko^o_&GY SaQ;-)ЩP%;%2|.[^V.f:%8~Z6/?VwmNws }28x:;;a0֩>s0~5~$d9l ڴ̗ v\i(Z97Cf7t$pX/ &Ctwwc{{?'Yv?G 0̏9BYL&-[d(_Y"UUQ[[I\.8_M`Q,#G>biF{{{FT__O#i@H$hiil6`Q*x8` 9(IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwiradiobutton.png0000644000175000017500000000162710652670670026330 0ustar alialiPNG  IHDRw=bKGD pHYs  tIME  -$IDATxAhe7tvm dMrla0[͵HZŠ!4sR"B9C`Y"B6L5 &I;e23yxnSOBߑ'X}_}mE{-QHJ/KW#=y=L #2qV23Jls~C$H Bǯ?/'>[߷~am2ʲ,Vp߇7z |YGc2 {qE(=88U__߭Nvߪ) ܯ.633z"J @Ris|~k)'9AG>J*ER$%μ{fc~~!lRݦ8IADDem׻΀I ]Ժ7NY[P.199\. ,..FGGyއ8jĞ⑸'}p՗VO칉ڗ5Ca,P(`xx\pC?z{{155ulnnR@$dYE@V6usiR"AbJ?wi^QGl۾nYu<4MVA@jL욜=kǹTa/z ;?SH)IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/kiwispinbutton.png0000644000175000017500000000154110652670670026176 0ustar alialiPNG  IHDRw=bKGD pHYs  ~tIME8!,${IDATxMhG]ֶ+g*!E:PZ 9℀CEkn!G@!R6 #0.A86]01RvN6%"ao}Xd*'0 @k6q_WC1jƟn٬Te|j()GCζҼe6!~RhE/@RhFE#}V2~yz< ~?QE@o$r7="tB A0c)wQ=q]T/%}롞)$H}>JZAoM7oBȨ {uH$BKKSKf<k+33x<Kr D"8|H3оi~:ui"#+Z?Bl6[=|Zf2W?z8KlPTA6Vu:Y$  ````B vLzLH`ll^E) 0`6撉(72 555%g188X@ F;E>}fȲg*0S&fDT}fjS\鹂(u9U ү94<`"MtvvŅߥ`<GAuu5}x<,,, :d2I4e}}!^YbssEQpeOUUNKn4NaP !@ Rp? RPA"tt:_NUUE)`nnϵ51IdrG~JX[[EhiiV]] _G,ZN..a-ZlV H@TXdnUp8vDQˊCFY  輘IENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/objectlist.png0000644000175000017500000000161710652670670025253 0ustar alialiPNG  IHDRw=bKGD pHYs  ~tIMEQXIDATxڵk#e?d餐&iR@RH xţE.{APmˮЃ?.m4 Z-*i%b5$&!dfxfy3QSOo|}U=P6y䵈ymʒ.5fDE[ҥ͑DayN K&M[nX:7"'"<uvl& τIn?3"v[D2 |Z0 Tg \כȯ_enn˲899q@fIV Fkb6Z8_:xy4n40LLL`6"2vo.@BG#(KuJyw\@=<# |r<W ֕]lAc4ILx]u]UTxL)9u4f.ܦ}eXZ^ S"$hL&Ýp{$g-.KoTD,--!"ض8r9(x\.c#ta0 0h 0, 0?&.5ibQlJBx>B!ly) VWWwbyPQWZ__@"J ,ݥX,*z|f0;<怗7)^g+3ZJu u?h'?@KΌ 񺠺eU^sˁͩYIENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/resources/kiwiwidgets/objecttree.png0000644000175000017500000000161710652670670025237 0ustar alialiPNG  IHDRw=bKGD pHYs  ~tIMEQXIDATxڵk#e?d餐&iR@RH xţE.{APmˮЃ?.m4 Z-*i%b5$&!dfxfy3QSOo|}U=P6y䵈ymʒ.5fDE[ҥ͑DayN K&M[nX:7"'"<uvl& τIn?3"v[D2 |Z0 Tg \כȯ_enn˲899q@fIV Fkb6Z8_:xy4n40LLL`6"2vo.@BG#(KuJyw\@=<# |r<W ֕]lAc4ILx]u]UTxL)9u4f.ܦ}eXZ^ S"$hL&Ýp{$g-.KoTD,--!"ض8r9(x\.c#ta0 0h 0, 0?&.5ibQlJBx>B!ly) VWWwbyPQWZ__@"J ,ݥX,*z|f0;<怗7)^g+3ZJu u?h'?@KΌ 񺠺eU^sˁͩYIENDB`PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/AUTHORS0000644000175000017500000000004410652670670017100 0ustar alialiLorenzo Gil Sanchez PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/COPYING0000644000175000017500000006347610652670670017105 0ustar aliali GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. GNU LESSER GENERAL PUBLIC LICENSE 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. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/kiwiwidgets.py0000644000175000017500000002260610652670670020744 0ustar aliali# Copyright (C) 2005 by Async Open Source # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from gettext import gettext as _ import gtk from gazpacho.util import get_bool_from_string_with_default # Register adapters from kiwi.ui.gazpacholoader import HyperLinkAdaptor, ComboEntryAdaptor root_library = 'kiwi.ui.widgets' widget_prefix = 'Kiwi' # pyflakes HyperLinkAdaptor ComboEntryAdaptor class ColumnDefinitionsAdaptor(object): def __init__(self): self._editor = ListColumnDefinitionsEditor() def set(self, context, kiwilist, value): kiwilist.set_property('column-definitions', value) def create_editor(self, context): button = gtk.Button(_('Edit...')) button.set_data('connection-id', -1) return button def update_editor(self, context, button, kiwilist, proxy): connection_id = button.get_data('connection-id') if connection_id != -1: button.disconnect(connection_id) connection_id = button.connect('clicked', self._editor_edit, kiwilist, proxy, context) button.set_data('connection-id', connection_id) def _editor_edit(self, button, kiwilist, proxy, context): application_window = context.get_application_window() self._editor.set_transient_for(application_window) self._editor.set_widget(kiwilist, proxy) self._editor.present() (ATTRIBUTE, TITLE, DATA_TYPE, VISIBLE, JUSTIFY, TOOLTIP, FORMAT, WIDTH, SORTED, ORDER) = range(10) class ListColumnDefinitionsEditor(object): """This dialog is used to edit the column definitions of a Kiwi List""" def set_widget(self, widget, proxy): super(ListColumnDefinitionsEditor, self).set_widget(widget, proxy) self.set_title((_('Editing columns of list %s') % self.gwidget.name)) self._load_columns() def _create_widgets(self): h_button_box = gtk.HButtonBox() h_button_box.set_layout(gtk.BUTTONBOX_START) self.add = gtk.Button(stock=gtk.STOCK_ADD) self.add.connect('clicked', self._on_add_clicked) h_button_box.pack_start(self.add) self.remove = gtk.Button(stock=gtk.STOCK_REMOVE) self.remove.connect('clicked', self._on_remove_clicked) h_button_box.pack_start(self.remove) self.up = gtk.Button(stock=gtk.STOCK_GO_UP) self.up.connect('clicked', self._on_up_clicked) h_button_box.pack_start(self.up) self.down = gtk.Button(stock=gtk.STOCK_GO_DOWN) self.down.connect('clicked', self._on_down_clicked) h_button_box.pack_start(self.down) self.vbox.pack_start(h_button_box, False, False) self.model = gtk.ListStore(str, str, str, bool, str, str, str, int, bool, str) self.treeview = gtk.TreeView(self.model) self.treeview.set_size_request(580, 300) selection = self.treeview.get_selection() selection.connect('changed', self._on_selection__changed) for i, title in enumerate(('Attribute', 'Title', 'Data type')): self.treeview.append_column(self._create_text_column(title, i)) self.treeview.append_column(self._create_bool_column('Visible', 3)) for i, title in enumerate(('Justify', 'Tooltip', 'Format')): self.treeview.append_column(self._create_text_column(title, (i + 4))) self.treeview.append_column(self._create_int_column('Width', 7)) self.treeview.append_column(self._create_bool_column('Sorted', 8)) self.treeview.append_column(self._create_text_column('Order', 9)) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self.treeview) self.vbox.pack_start(sw, True, True) label = gtk.Label() instructions = """Valid values: Data type: str, int, float or date Justify: left, center or right Order: ascending or descending""" label.set_alignment(0.0, 0.0) label.set_markup(instructions) self.vbox.pack_start(label, False, False) def _create_text_column(self, title, index): renderer = gtk.CellRendererText() renderer.set_property('editable', True) renderer.connect('edited', self._on_text_renderer__edited, index) col = gtk.TreeViewColumn(title, renderer, text=index) return col def _create_bool_column(self, title, index): renderer = gtk.CellRendererToggle() renderer.set_property('activatable', True) renderer.connect('toggled', self._on_toggle_renderer__toggled, index) col = gtk.TreeViewColumn(title, renderer, active=index) return col def _create_int_column(self, title, index): col = self._create_text_column(title, index) return col def _get_default_values(self): return ('unnamed', '', 'str', True, 'left', '', '', 1, False, '') def _update_proxy(self): cols = [] for column in self.model: cols.append('|'.join(map(str, tuple(column)))) data = '^'.join(cols) self.proxy.set_value(data) def _load_columns(self): self.model.clear() cd = self.gwidget.get_glade_property('column-definitions').value if not cd: return for col in cd.split('^'): (attr, title, data_type, visible, justify, tooltip, format, width, sorted, order) = col.split('|') visible = get_bool_from_string_with_default(visible, True) width = int(width) sorted = get_bool_from_string_with_default(sorted, False) self.model.append((attr, title, data_type, visible, justify, tooltip, format, width, sorted, order)) def _on_add_clicked(self, button): row_iter = self.model.append(self._get_default_values()) path = self.model.get_path(row_iter) col = self.treeview.get_column(0) self.treeview.set_cursor(path, col, True) self._update_proxy() def _on_remove_clicked(self, button): model, selected_iter = self.treeview.get_selection().get_selected() if selected_iter: model.remove(selected_iter) self._update_proxy() def _on_up_clicked(self, button): model, selected_iter = self.treeview.get_selection().get_selected() if not selected_iter: return path = self.model.get_path(selected_iter) prev_iter = self.model.get_iter(((path[0] - 1))) if not prev_iter: return model.swap(prev_iter, selected_iter) self._update_proxy() self._update_buttons() def _on_down_clicked(self, button): model, selected_iter = self.treeview.get_selection().get_selected() if not selected_iter: return next_iter = model.iter_next(selected_iter) if not next_iter: return model.swap(selected_iter, next_iter) self._update_proxy() self._update_buttons() def _on_text_renderer__edited(self, renderer, path, new_text, col_index): value = new_text if col_index == WIDTH: try: value = int(new_text) except ValueError: return row = self.model[path[0]] row[col_index] = value if col_index == ATTRIBUTE: title = row[TITLE] if not title: row[TITLE] = value.capitalize() self._update_proxy() def _on_toggle_renderer__toggled(self, renderer, path, col_index): row = self.model[path[0]] row[col_index] = not row[col_index] self._update_proxy() def _on_selection__changed(self, selection): self._update_buttons() def _update_buttons(self): selection = self.treeview.get_selection() model, selected_iter = selection.get_selected() if not selected_iter: self.remove.set_sensitive(False) self.down.set_sensitive(False) self.up.set_sensitive(False) return self.remove.set_sensitive(True) path = model.get_path(selected_iter)[0] size = len(model) if path == 0: self.up.set_sensitive(False) if size > 1: self.down.set_sensitive(True) if path == size - 1: self.down.set_sensitive(False) if size > 1: self.up.set_sensitive(True) if path > 0 and path < (size - 1): self.up.set_sensitive(True) self.down.set_sensitive(True) PIDA-0.5.1/contrib/kiwi/gazpacho-plugin/kiwiwidgets.xml0000644000175000017500000000604410652670670021112 0ustar aliali PIDA-0.5.1/contrib/kiwi/glade/0002755000175000017500000000000010652671501014012 5ustar alialiPIDA-0.5.1/contrib/kiwi/glade/PluggableWizard.glade0000644000175000017500000001563410652671044020104 0ustar aliali 260 320 False normal 2 True True str False True 1 5 5 True True True gtk-cancel True True 90 True 1 True gtk-go-back True True 90 2 True gtk-go-forward True True 90 3 gtk-ok True 4 False end 2 str False 3 1 PIDA-0.5.1/contrib/kiwi/glade3-plugin/0002755000175000017500000000000010652671501015371 5ustar alialiPIDA-0.5.1/contrib/kiwi/glade3-plugin/kiwiwidgets.py0000644000175000017500000000132410652670670020301 0ustar aliali from kiwi.ui.hyperlink import HyperLink from kiwi.ui.objectlist import ObjectList, ObjectTree from kiwi.ui.widgets.label import ProxyLabel from kiwi.ui.widgets.combo import ProxyComboEntry, ProxyComboBox from kiwi.ui.widgets.checkbutton import ProxyCheckButton from kiwi.ui.widgets.radiobutton import ProxyRadioButton from kiwi.ui.widgets.entry import ProxyEntry, ProxyDateEntry from kiwi.ui.widgets.spinbutton import ProxySpinButton from kiwi.ui.widgets.textview import ProxyTextView from kiwi.ui.widgets.button import ProxyButton # pyflakes HyperLink ObjectList ObjectTree ProxyLabel ProxyComboEntry ProxyComboBox ProxyCheckButton ProxyRadioButton ProxyEntry ProxyDateEntry ProxySpinButton ProxyTextView ProxyButton PIDA-0.5.1/contrib/kiwi/glade3-plugin/kiwiwidgets.xml0000644000175000017500000000701210652670670020451 0ustar aliali PIDA-0.5.1/contrib/kiwi/glade3-plugin/setup.py0000644000175000017500000000205110652670670017105 0ustar alialiimport commands import os from kiwi.dist import listfiles, setup GLADELIB = 'gladeui-1.0' def glade3_exists(): status, output = commands.getstatusoutput('pkg-config --exists %s' % GLADELIB) return not status def get_glade3_variable(variablename): return commands.getoutput('pkg-config --variable=%s %s' % (variablename, GLADELIB)) if glade3_exists(): catalogdir = get_glade3_variable('catalogdir') moduledir = get_glade3_variable('moduledir') pixmapdir = get_glade3_variable('pixmapdir') pixmaps = listfiles('..', 'gazpacho-plugin', 'resources', 'kiwiwidgets', '*.png') setup( data_files=[ (catalogdir, ['kiwiwidgets.xml']), (moduledir, ['kiwiwidgets.py']), (os.path.join(pixmapdir, '16x16'), pixmaps), (os.path.join(pixmapdir, '22x22'), pixmaps), ] ) else: print 'Glade 3 is not installed, neither will be this plugin.' PIDA-0.5.1/contrib/kiwi/kiwi/0002755000175000017500000000000010652671501013701 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/db/0002755000175000017500000000000010652671501014266 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/db/__init__.py0000644000175000017500000000152310652670702016400 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # """Database integration""" PIDA-0.5.1/contrib/kiwi/kiwi/db/query.py0000644000175000017500000000671210652670702016013 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # from kiwi.interfaces import ISearchFilter # # Query building # class QueryState(object): def __init__(self, search_filter): """ @param search_filter: search filter this query state is associated with @type search_filter: L{SearchFilter} """ self.filter = search_filter class NumberQueryState(QueryState): """ @cvar value: number """ def __init__(self, filter, value): QueryState.__init__(self, filter) self.value = value def __repr__(self): return '' % (self.value,) class StringQueryState(QueryState): """ @cvar text: string """ def __init__(self, filter, text): QueryState.__init__(self, filter) self.text = text def __repr__(self): return '' % (self.text,) class DateQueryState(QueryState): """ @cvar date: date """ def __init__(self, filter, date): QueryState.__init__(self, filter) self.date = date def __repr__(self): return '' % (self.date,) class DateIntervalQueryState(QueryState): """ @cvar start: start of interval @cvar end: end of interval """ def __init__(self, filter, start, end): QueryState.__init__(self, filter) self.start = start self.end = end def __repr__(self): return '' % ( self.start, self.end) class QueryExecuter(object): """ A QueryExecuter is responsible for taking the state (as in QueryState) objects from search filters and construct a query. How the query is constructed is ORM/DB-layer dependent @cvar default_search_limit: The default search limit """ default_search_limit = 1000 def __init__(self): self._columns = {} self._limit = self.default_search_limit # # Public API # def set_filter_columns(self, search_filter, columns): if not ISearchFilter.providedBy(search_filter): pass #raise TypeError("search_filter must implement ISearchFilter") assert not search_filter in self._columns self._columns[search_filter] = columns # # Overridable # def search(self, states): """ @param states: @type states: list of L{QueryStates} @returns: list of objects matching query """ raise NotImplementedError def set_limit(self, limit): """ @param limit: """ self._limit = limit def get_limit(self): return self._limit PIDA-0.5.1/contrib/kiwi/kiwi/db/sqlobj.py0000644000175000017500000001701210652670702016133 0ustar aliali# -*- coding: utf-8 -*- # vi:si:et:sw=4:sts=4:ts=4 ## ## Copyright (C) 2007 Async Open Source ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU Lesser General Public License ## as published by the Free Software Foundation; either version 2 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU Lesser General Public License for more details. ## ## You should have received a copy of the GNU Lesser General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., or visit: http://www.gnu.org/. ## ## ## Author(s): Johan Dahlin ## """ SQLObject integration for Kiwi """ from sqlobject.sqlbuilder import func, AND, OR, LIKE, SQLExpression from kiwi.db.query import NumberQueryState, StringQueryState, \ DateQueryState, DateIntervalQueryState, QueryExecuter from kiwi.interfaces import ISearchFilter class _FTI(SQLExpression): def __init__(self, q): self.q = q def __sqlrepr__(self, db): return self.q class SQLObjectQueryExecuter(QueryExecuter): def __init__(self, conn=None): QueryExecuter.__init__(self) self.conn = conn self.table = None self._query_callbacks = [] self._filter_query_callbacks = {} self._query = self._default_query self._full_text_indexes = {} # # Public API # def set_table(self, table): """ Sets the SQLObject table/object for this executer @param table: a SQLObject subclass """ self.table = table def add_query_callback(self, callback): """ Adds a generic query callback @param callback: a callable """ if not callable(callback): raise TypeError self._query_callbacks.append(callback) def add_filter_query_callback(self, search_filter, callback): """ Adds a query callback for the filter search_filter @param search_filter: a search filter @param callback: a callable """ if not ISearchFilter.providedBy(search_filter): raise TypeError if not callable(callback): raise TypeError l = self._filter_query_callbacks.setdefault(search_filter, []) l.append(callback) def set_query(self, callback): """ Overrides the default query mechanism. @param callback: a callable which till take two arguments: (query, connection) """ if callback is None: callback = self._default_query elif not callable(callback): raise TypeError self._query = callback # # QueryBuilder # def search(self, states): """ @param states: """ if self.table is None: raise ValueError("table cannot be None") table = self.table queries = [] for state in states: search_filter = state.filter assert state.filter # Column query if search_filter in self._columns: query = self._construct_state_query( table, state, self._columns[search_filter]) if query: queries.append(query) # Custom per filter/state query. elif search_filter in self._filter_query_callbacks: for callback in self._filter_query_callbacks[search_filter]: query = callback(state) if query: queries.append(query) else: if (self._query == self._default_query and not self._query_callbacks): raise ValueError( "You need to add a search column or a query callback " "for filter %s" % (search_filter)) for callback in self._query_callbacks: query = callback(states) if query: queries.append(query) query = AND(*queries) result = self._query(query, self.conn) return result.limit(self.get_limit()) # # Private # def _default_query(self, query, conn): return self.table.select(query, connection=conn) def _construct_state_query(self, table, state, columns): queries = [] for column in columns: query = None table_field = getattr(table.q, column) if isinstance(state, NumberQueryState): query = self._parse_number_state(state, table_field) elif isinstance(state, StringQueryState): query = self._parse_string_state(state, table_field) elif isinstance(state, DateQueryState): query = self._parse_date_state(state, table_field) elif isinstance(state, DateIntervalQueryState): query = self._parse_date_interval_state(state, table_field) else: raise NotImplementedError(state.__class__.__name__) if query: queries.append(query) return OR(*queries) def _postgres_has_fti_index(self, table_name, column_name): # Assume that the PostgreSQL full text index columns are # named xxx_fti where xxx is the name of the column res = self.conn.queryOne( """SELECT 1 FROM information_schema.columns WHERE table_name = %s AND column_name = %s AND udt_name = 'tsvector';""" % ( self.conn.sqlrepr(table_name), self.conn.sqlrepr(column_name))) return bool(res) def _check_has_fulltext_index(self, table_name, field_name): fullname = table_name + field_name if fullname in self._full_text_indexes: return self._full_text_indexes[fullname] else: value = False if 'postgres' in self.conn.__class__.__module__: value = self._postgres_has_fti_index(table_name, field_name + '_fti') self._full_text_indexes[fullname] = value return value def _parse_number_state(self, state, table_field): if state.value is not None: return table_field == state.value def _parse_string_state(self, state, table_field): if not state.text: return if self._check_has_fulltext_index(table_field.tableName, table_field.fieldName): value = state.text.lower() # FTI operators: # & = AND # | = OR value = value.replace(' ', ' & ') return _FTI("%s.%s_fti @@ %s::tsquery" % ( table_field.tableName, table_field.fieldName, self.conn.sqlrepr(value))) else: text = '%%%s%%' % state.text.lower() return LIKE(func.LOWER(table_field), text) def _parse_date_state(self, state, table_field): if state.date: return func.DATE(table_field) == state.date def _parse_date_interval_state(self, state, table_field): queries = [] if state.start: queries.append(table_field >= state.start) if state.end: queries.append(func.DATE(table_field) <= state.end) return AND(*queries) PIDA-0.5.1/contrib/kiwi/kiwi/i18n/0002755000175000017500000000000010652671501014460 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/i18n/__init__.py0000644000175000017500000000153310652670701016572 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # """Internationalization helpers""" PIDA-0.5.1/contrib/kiwi/kiwi/i18n/i18n.py0000644000175000017500000001540210652670701015612 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # """Internationalization utilities. Requires intltool and gettext""" from distutils.dep_util import newer from distutils.filelist import FileList, findall from optparse import OptionParser import os from shutil import copyfile from kiwi.dist import listfiles # This is a template, used to generate a list of translatable file # which intltool can understand. # It reuses the syntax from distutils MANIFEST files, a POTFILES.in # will be generate from it, see update_po() POTFILES = 'POTFILES.list' def check_directory(root): po_dir = os.path.join(root, 'po') if not os.path.exists(po_dir): raise SystemExit("A 'po` directory must exist") if not os.path.isdir(po_dir): raise SystemExit("po must be a directory") locale_dir = os.path.join(root, 'locale') if os.path.exists(locale_dir): if not os.path.isdir(po_dir): raise SystemExit("locale must be a directory") def check_pot_file(root, package): pot_file = os.path.join(root, 'po', '%s.pot' % package) if not os.path.exists(pot_file): raise SystemExit("Need a pot file, run --update first") return pot_file def get_translatable_files(root): pofiles = os.path.join(root, 'po', POTFILES) if not os.path.exists(pofiles): print 'Warning: Could not find %s' % pofiles return [] filelist = FileList() fp = open(pofiles) for line in fp.readlines(): filelist.process_template_line(line) return filelist.files def list_languages(root): return [os.path.basename(po_file[:-3]) for po_file in listfiles(root, 'po', '*.po')] def update_po(root, package): files = get_translatable_files(root) potfiles_in = os.path.join(root, 'po', 'POTFILES.in') if os.path.exists(potfiles_in): os.unlink(potfiles_in) rml_files = _extract_rml_files(root) fd = open(potfiles_in, 'w') for filename in files + rml_files: fd.write(filename + '\n') fd.close() old = os.getcwd() os.chdir(os.path.join(root, 'po')) if os.system('intltool-update 2> /dev/null > /dev/null') != 0: raise SystemExit('ERROR: intltool-update could not be found') # POT file first res = os.system('intltool-update --pot --gettext-package=%s' % package) if res != 0: raise SystemExit("ERROR: failed to generate pot file") for lang in list_languages(root): new = lang + '.new.po' cmd = ('intltool-update --dist --gettext-package=%s ' '-o %s %s > /dev/null' % (package, new, lang)) os.system(cmd) if not os.path.exists(new): raise SystemExit("ERROR: intltool failed, see above") os.rename(new, lang + '.po') os.chdir(old) for f in rml_files: os.unlink(f) os.unlink(potfiles_in) def _clean_rml(data): fixed = '' while data: start_pos = data.find('<%') if start_pos == -1: break fixed += data[:start_pos] data = data[start_pos:] end_pos = data.find('%>') if end_pos == -1: end_pos = data.find('/>') assert end_pos != -1 data = data[end_pos+2:] return fixed def _extract_rml_files(root): files = [] for rml_file in findall(root): if not rml_file.endswith('.rml'): continue data = open(rml_file).read() data = _clean_rml(data) if not len(data): continue extracted = rml_file + '.extracted' if os.path.exists(extracted): os.unlink(extracted) open(extracted, 'w').write(data) extracted = extracted[len(root)+1:] cmd = 'intltool-extract --type=gettext/xml %s' % extracted res = os.system(cmd) if res != 0: raise SystemExit("ERROR: failed to generate pot file") os.unlink(extracted) files.append(extracted + '.h') return files def compile_po_files(root, package): if os.system('msgfmt 2> /dev/null') != 256: print 'msgfmt could not be found, disabling translations' return mo_file = package + '.mo' for po_file in listfiles(root, 'po', '*.po'): lang = os.path.basename(po_file[:-3]) mo = os.path.join(root, 'locale', lang, 'LC_MESSAGES', mo_file) if not os.path.exists(mo) or newer(po_file, mo): directory = os.path.dirname(mo) if not os.path.exists(directory): os.makedirs(directory) os.system('msgfmt %s -o %s' % (po_file, mo)) def main(args): parser = OptionParser() parser.add_option('-a', '--add', action="store", type="string", dest="lang", help="Add a new language") parser.add_option('-l', '--list', action="store_true", dest="list", help="List all supported languages") parser.add_option('-u', '--update', action="store_true", dest="update", help="Update pot file and all po files") parser.add_option('-c', '--compile', action="store_true", dest="compile", help="Compile all .po files into .mo") parser.add_option('-p', '--package', action="store", type="string", dest="package", help="Package name") options, args = parser.parse_args(args) root = os.getcwd() check_directory(root) if options.package: package = options.package else: package = os.path.split(root)[1] if options.lang: pot_file = check_pot_file(root, package) copyfile(pot_file, os.path.join(root, 'po', options.lang + '.po')) return elif options.list: for lang in list_languages(root): print lang return if options.update: update_po(root, package) if options.compile: check_pot_file(root, package) compile_po_files(root, package) PIDA-0.5.1/contrib/kiwi/kiwi/i18n/msgfmt.py0000644000175000017500000001263210652670701016332 0ustar aliali#! /usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Lwis doc = """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. Usage: msgfmt.py [OPTIONS] filename.po Options: -o file --output-file=file Specify the output file to write to. If omitted, output will go to a file named filename.mo (based off the input file name). -h --help Print this message and exit. -V --version Display version information and exit. """ import sys import os import getopt import struct import array __version__ = "1.1" MESSAGES = {} def usage(code, msg=''): print >> sys.stderr, doc if msg: print >> sys.stderr, msg sys.exit(code) def add(id, str, fuzzy): "Add a non-fuzzy translation to the dictionary." global MESSAGES if not fuzzy and str: MESSAGES[id] = str def generate(): "Return the generated output." global MESSAGES keys = MESSAGES.keys() # the keys are sorted in the .mo file keys.sort() offsets = [] ids = strs = '' for id in keys: # For each string, we need size and file offset. Each string is NUL # terminated; the NUL does not count into the size. offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) ids += id + '\0' strs += MESSAGES[id] + '\0' output = '' # The header is 7 32-bit unsigned integers. We don't use hash tables, so # the keys start right after the index tables. # translated string. keystart = 7*4+16*len(keys) # and the values start after the keys valuestart = keystart + len(ids) koffsets = [] voffsets = [] # The string table first has the list of keys, then the list of values. # Each entry has first the size of the string, then the file offset. for o1, l1, o2, l2 in offsets: koffsets += [l1, o1+keystart] voffsets += [l2, o2+valuestart] offsets = koffsets + voffsets output = struct.pack("Iiiiiii", 0x950412deL, # Magic 0, # Version len(keys), # # of entries 7*4, # start of key index 7*4+len(keys)*8, # start of value index 0, 0) # size and offset of hash table output += array.array("i", offsets).tostring() output += ids output += strs return output def make(filename, outfile): ID = 1 STR = 2 # Compute .mo name from .po name and arguments if filename.endswith('.po'): infile = filename else: infile = filename + '.po' if outfile is None: outfile = os.path.splitext(infile)[0] + '.mo' try: lines = open(infile).readlines() except IOError, msg: print >> sys.stderr, msg sys.exit(1) section = None fuzzy = 0 # Parse the catalog lno = 0 msgid = msgstr = '' for l in lines: lno += 1 # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: add(msgid, msgstr, fuzzy) section = None fuzzy = 0 # Record a fuzzy mark if l[:2] == '#,' and l.find('fuzzy'): fuzzy = 1 # Skip comments if l[0] == '#': continue # Now we are in a msgid section, output previous section if l.startswith('msgid'): if section == STR: add(msgid, msgstr, fuzzy) section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Skip empty lines l = l.strip() if not l: continue # XXX: Does this always follow Python escape semantics? l = eval(l) if section == ID: msgid += l elif section == STR: msgstr += l else: print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ 'before:' print >> sys.stderr, l sys.exit(1) # Add last entry if section == STR: add(msgid, msgstr, fuzzy) # Compute output output = generate() try: open(outfile,"wb").write(output) except IOError,msg: print >> sys.stderr, msg def main(): try: opts, args = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version', 'output-file=']) except getopt.error, msg: usage(1, msg) outfile = None # parse options for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-V', '--version'): print >> sys.stderr, "msgfmt.py", __version__ sys.exit(0) elif opt in ('-o', '--output-file'): outfile = arg # do it if not args: print >> sys.stderr, 'No input file given' print >> sys.stderr, "Try `msgfmt --help' for more information." return for filename in args: make(filename, outfile) if __name__ == '__main__': main() PIDA-0.5.1/contrib/kiwi/kiwi/ui/0002755000175000017500000000000010652671501014316 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/ui/test/0002755000175000017500000000000010652671501015275 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/ui/test/__init__.py0000644000175000017500000000255210652670717017420 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin } and the L{Recorder}. The recorder listens to certain events happening inside the application and writes a script which later on can be played back by the player. To record a test:: kiwi-ui-test --record=script.py application [arguments] To play back a recorded test:: kiwi-ui-test script.py """ PIDA-0.5.1/contrib/kiwi/kiwi/ui/test/common.py0000644000175000017500000001612010652670717017145 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin GtkWindow self._windows = {} # toplevels ? def _event_handler(self, event): # Separate method so we can use return inside self._check_event(event) gtk.main_do_event(event) def _check_event(self, event): if not event.window: return window = event.window event_type = event.type window_type = window.get_window_type() try: widget = window.get_user_data() except ValueError: widget = self._id_to_obj.get(window) if not isinstance(widget, gtk.Window): return widget_name = widget.get_name() if event_type == gdk.MAP: if window_type != gdk.WINDOW_TOPLEVEL: # For non toplevels we only care about those which has a menu # as the child child = widget.child if not child or not isinstance(child, gtk.Menu): return # Hack to get all the children of a popup menu in # the same namespace as the window they were launched in. parent_menu = child.get_data('parent-menu') if parent_menu: main = parent_menu.get_toplevel() widget_name = main.get_name() else: self._window_added(widget, widget_name) self._id_to_obj[window] = widget elif (event_type == gdk.DELETE or (event_type == gdk.WINDOW_STATE and event.new_window_state == gdk.WINDOW_STATE_WITHDRAWN)): self._window_removed(widget, widget_name) def _window_added(self, window, name): if name in self._windows: return self._windows[name] = window # Toplevel self.parse_one(window, window) ns = self._objects[name] self.emit('window-added', window, name, ns) def _window_removed(self, window, name): if not name in self._windows: # Error? return del self._windows[name] self.emit('window-removed', window, name) def _add_widget(self, toplevel, widget, name): toplevel_widgets = self._objects.setdefault(toplevel.get_name(), {}) if name in toplevel_widgets: return toplevel_widgets[name] = widget # Listen to when the widget is removed from the interface, eg when # ::parent changes to None. At that time remove the widget and all # the children from the namespace. def on_widget__notify_parent(widget, pspec, name, widgets, signal_container): # Only take action when the widget is removed from a parent if widget.parent is not None: return for child_name, child in widgets.items(): if child.is_ancestor(widget): del widgets[child_name] widget.disconnect(signal_container.pop()) signal_container = [] sig_id = widget.connect('notify::parent', on_widget__notify_parent, name, toplevel_widgets, signal_container) signal_container.append(sig_id) # Public API def register_event_handler(self): if not event_handler_set: raise NotImplementedError event_handler_set(self._event_handler) def parse_one(self, toplevel, gobj): """ @param toplevel: @param gobj: """ if not isinstance(gobj, gobject.GObject): raise TypeError gtype = gobj while True: name = gobject.type_name(gtype) func = getattr(self, name, None) if func: if func(toplevel, gobj): break if gtype == gobject.GObject.__gtype__: break gtype = gobject.type_parent(gtype) # # Special widget handling # def ignore(self, toplevel, gobj): pass GtkSeparatorMenuItem = GtkTearoffMenuItem = ignore def GtkWidget(self, toplevel, widget): """ Called when a GtkWidget is about to be traversed """ self._add_widget(toplevel, widget, widget.get_name()) def GtkContainer(self, toplevel, container): """ Called when a GtkContainer is about to be traversed Parsers all the children and listens for new children, which may be added at a later point. """ for child in container.get_children(): self.parse_one(toplevel, child) def _on_container_add(container, widget): self.parse_one(toplevel, widget) container.connect('add', _on_container_add) def GtkDialog(self, toplevel, dialog): """ Called when a GtkDialog is about to be traversed Just parses the widgets embedded in the dialogs. """ self.parse_one(toplevel, dialog.action_area) self.parse_one(toplevel, dialog.vbox) def GtkMenuItem(self, toplevel, item): """ Called when a GtkMenuItem is about to be traversed It does some magic to tie a stronger connection between toplevel menuitems and submenus, which later will be used. """ submenu = item.get_submenu() if submenu: submenu.set_data('parent-menu', item) for child_item in submenu.get_children(): child_item.set_data('parent-menu', item) self.parse_one(toplevel, submenu) def GtkToolButton(self, toplevel, item): item.child.set_name(item.get_name()) gobject.type_register(WidgetIntrospecter) PIDA-0.5.1/contrib/kiwi/kiwi/ui/test/main.py0000644000175000017500000000404410652670717016603 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # """ Kiwi UI Test: command line interface """ import optparse from kiwi.log import set_log_level def _play(options, filename, args): from kiwi.ui.test.runner import play_file play_file(filename, options.command, args) def _record(options, filename, args): from kiwi.ui.test.recorder import Recorder recorder = Recorder(filename) recorder.execute(args) def main(args): parser = optparse.OptionParser() parser.add_option('', '--command', action="store", dest="command") parser.add_option('', '--record', action="store", dest="record") parser.add_option('-v', '--verbose', action="store_true", dest="verbose") options, args = parser.parse_args(args) if options.record and options.command: raise SystemExit( "You can't specify a command and recording at the same time") if options.record: if options.verbose: set_log_level('recorder', 5) _record(options, options.record, args[1:]) else: if len(args) < 2: raise SystemExit("Error: needs a filename to play") if options.verbose: set_log_level('player', 5) _play(options, args[1], args[2:]) PIDA-0.5.1/contrib/kiwi/kiwi/ui/test/recorder.py0000644000175000017500000004041510652670717017466 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin >> def serialize(self): >>> ... return '%s.clicked' % self.name @returns: string to reproduce event Override this in a subclass. """ pass class SignalEvent(Event): """ A SignalEvent is an L{Event} which is tied to a GObject signal, L{Recorder} uses this to automatically attach itself to a signal at which point this object will be instantiated. @cvar signal_name: signal to listen to """ signal_name = None def __init__(self, object, name, args): """ @param object: @param name: @param args: """ Event.__init__(self, object, name) self.args = args def connect(cls, object, signal_name, cb): """ Calls connect on I{object} for signal I{signal_name}. @param object: object to connect on @param signal_name: signal name to listen to @param cb: callback """ object.connect(signal_name, cb, cls, object) connect = classmethod(connect) # # Special Events # class WindowDeleteEvent(Event): """ This event represents a user click on the close button in the window manager. """ # # Signal Events # class MenuItemActivateEvent(SignalEvent): """ This event represents a user click on a menu item. It could be a toplevel or a normal entry in a submenu. """ signal_name = 'activate' object_type = gtk.MenuItem def serialize(self): return '%s.activate()' % self.name register_event_type(MenuItemActivateEvent) class ImageMenuItemButtonReleaseEvent(SignalEvent): """ This event represents a click on a normal menu entry It's sort of a hack to use button-press-event, instea of listening to activate, but we'll get the active callback after the user specified callbacks are called, at which point it is already too late. """ signal_name = 'button-release-event' object_type = gtk.ImageMenuItem def get_toplevel(self, widget): parent = widget while True: widget = parent.get_data('parent-menu') if not widget: break parent = widget toplevel = parent.get_toplevel() return toplevel def serialize(self): return '%s.activate()' % self.name register_event_type(ImageMenuItemButtonReleaseEvent) class ToolButtonReleaseEvent(SignalEvent): """ This event represents a click on a normal toolbar button Hackish, see L{ImageMenuItemButtonReleaseEvent} for more details. """ signal_name = 'button-release-event' object_type = gtk.Button def serialize(self): return '%s.activate()' % self.name register_event_type(ToolButtonReleaseEvent) class EntrySetTextEvent(SignalEvent): """ This event represents a content modification of a GtkEntry. When the user deletes, clears, adds, modifies the text this event will be created. """ signal_name = 'notify::text' object_type = gtk.Entry def __init__(self, object, name, args): SignalEvent.__init__(self, object, name, args) self.text = self.object.get_text() def serialize(self): return '%s.set_text("%s")' % (self.name, self.text) register_event_type(EntrySetTextEvent) class EntryActivateEvent(SignalEvent): """ This event represents an activate event for a GtkEntry, eg when the user presses enter in a GtkEntry. """ signal_name = 'activate' object_type = gtk.Entry def serialize(self): return '%s.activate()' % (self.name) register_event_type(EntryActivateEvent) # Also works for Toggle, Radio and Check class ButtonClickedEvent(SignalEvent): """ This event represents a button click. Note that this will also work for GtkToggleButton, GtkRadioButton and GtkCheckButton. """ signal_name = 'clicked' object_type = gtk.Button def serialize(self): return '%s.clicked()' % self.name register_event_type(ButtonClickedEvent) # Kiwi widget support class ObjectListSelectionChanged(SignalEvent): """ This event represents a selection change on a L{kiwi.ui.objectlist.ObjectList}, eg when the user selects or unselects a row. It is actually tied to the signal changed on GtkTreeSelection object. """ object_type = ObjectList signal_name = 'changed' def __init__(self, objectlist, name, args): self._objectlist = objectlist SignalEvent.__init__(self, objectlist, name=objectlist.get_name(), args=args) self.rows = self._get_rows() def _get_rows(self): selection = self._objectlist.get_treeview().get_selection() if selection.get_mode() == gtk.SELECTION_MULTIPLE: # get_selected_rows() returns a list of paths iters = selection.get_selected_rows()[1] if iters: return iters else: # while get_selected returns an iter, yay. model, iter = selection.get_selected() if iter is not None: # so convert it to a path and put it in an empty list return [model.get_string_from_iter(iter)] return [] def connect(cls, orig, signal_name, cb): object = orig.get_treeview().get_selection() object.connect(signal_name, cb, cls, orig) connect = classmethod(connect) def get_toplevel(self, widget): return self._objectlist.get_toplevel() def serialize(self): return '%s.select_paths(%s)' % (self.name, self.rows) register_event_type(ObjectListSelectionChanged) class ObjectListDoubleClick(SignalEvent): """ This event represents a double click on a row in objectlist """ signal_name = 'button-press-event' object_type = ObjectList def __init__(self, objectlist, name, args): event, = args if event.type != gdk._2BUTTON_PRESS: raise SkipEvent SignalEvent.__init__(self, objectlist, name, args) self.row = objectlist.get_selected_row_number() def connect(cls, orig, signal_name, cb): object = orig.get_treeview() object.connect(signal_name, cb, cls, orig) connect = classmethod(connect) def serialize(self): return '%s.double_click(%s)' % (self.name, self.row) register_event_type(ObjectListDoubleClick) # XXX: ComboMixin -> ??? # class KiwiComboBoxChangedEvent(SignalEvent): # """ # This event represents a a selection of an item # in a L{kiwi.ui.widgets.combobox.ComboBoxEntry} or # L{kiwi.ui.widgets.combobox.ComboBox}. # """ # signal_name = 'changed' # object_type = ComboMixin # def __init__(self, combo, name, args): # SignalEvent.__init__(self, combo, name, args) # self.label = combo.get_selected_label() # def serialize(self): # return '%s.select_item_by_label("%s")' % (self.name, self.label) # register_event_type(KiwiComboBoxChangedEvent) class Recorder(WidgetIntrospecter): """ Recorder takes care of attaching events to widgets, when the appear, and creates the events when the user is interacting with some widgets. When the tracked program is closed the events are serialized into a script which can be played back with help of L{kiwi.ui.test.player.Player}. """ def __init__(self, filename): """ @param filename: name of the script """ WidgetIntrospecter.__init__(self) self.register_event_handler() self.connect('window-removed', self.window_removed) self._filename = filename self._events = [] self._listened_objects = [] self._event_types = self._configure_event_types() self._args = None # This is sort of a hack, but there are no other realiable ways # of actually having something executed after the application # is finished atexit.register(self.save) # Register a hook that is called before normal delete-events # because if it's connected using a normal callback it will not # be called if the application returns True in it's signal handler. if add_emission_hook: add_emission_hook(gtk.Window, 'delete-event', self._emission_window__delete_event) def execute(self, args): self._start_timestamp = time.time() self._args = args # Run the script sys.argv = args execfile(sys.argv[0], globals(), globals()) def _emission_window__delete_event(self, window, event, *args): self._add_event(WindowDeleteEvent(window)) # Yes, please call us again return True def _configure_event_types(self): event_types = {} for event_type in get_event_types(): if event_type.object_type is None: raise AssertionError elist = event_types.setdefault(event_type.object_type, []) elist.append(event_type) return event_types def _add_event(self, event): log("Added event %s" % event.serialize()) self._events.append((event, time.time())) def _listen_event(self, object, event_type): if not issubclass(event_type, SignalEvent): raise TypeError("Can only listen to SignalEvents, not %r" % event_type) if event_type.signal_name is None: raise ValueError("signal_name cannot be None") # This is horrible, but there's no good way of passing in # more than one variable to the script and we really want to be # able to connect it to any kind of signal, regardless of # the number of parameters the signal has def on_signal(object, *args): event_type, orig = args[-2:] try: self._add_event(event_type(orig, None, args[:-2])) except SkipEvent: pass event_type.connect(object, event_type.signal_name, on_signal) def window_removed(self, wi, window, name): # It'll already be trapped if we can use an emission hook # skip it here to avoid duplicates if not add_emission_hook: return self._add_event(WindowDeleteEvent(window)) def parse_one(self, toplevel, gobj): WidgetIntrospecter.parse_one(self, toplevel, gobj) # mark the object as "listened" to ensure we'll always # receive unique objects if gobj in self._listened_objects: return self._listened_objects.append(gobj) for object_type, event_types in self._event_types.items(): if not isinstance(gobj, object_type): continue for event_type in event_types: # These 3 hacks should move into the event class itself if event_type == MenuItemActivateEvent: if not isinstance(gobj.get_parent(), gtk.MenuBar): continue elif event_type == ToolButtonReleaseEvent: if not isinstance(gobj.get_parent(), gtk.ToolButton): continue elif event_type == ButtonClickedEvent: if isinstance(gobj.get_parent(), gtk.ToolButton): continue if issubclass(event_type, SignalEvent): self._listen_event(gobj, event_type) def save(self): """ Collect events and serialize them into a script and save the script. This should be called when the tracked program has finished executing. """ if not self._events: return try: fd = open(self._filename, 'w') except IOError: raise SystemExit("Could not write: %s" % self._filename) fd.write("... -*- Mode: doctest -*-\n") fd.write("... run: %s\n" % ' '.join(self._args)) fd.write(">>> from kiwi.ui.test.runner import runner\n") fd.write(">>> runner.start()\n") windows = {} last = self._events[0][1] fd.write('>>> runner.sleep(%2.1f)\n' % (last - self._start_timestamp,)) for event, timestamp in self._events: toplevel = event.toplevel_name if not toplevel in windows: fd.write('>>> %s = runner.waitopen("%s")\n' % (toplevel, toplevel)) windows[toplevel] = True if isinstance(event, WindowDeleteEvent): fd.write(">>> %s.delete()\n" % (event.name,)) fd.write(">>> runner.waitclose('%s')\n" % (event.name,)) if not event.name in windows: # Actually a bug continue del windows[event.name] else: fd.write(">>> %s.%s\n" % (toplevel, event.serialize())) delta = timestamp - last if delta > 0.05: fd.write('>>> runner.sleep(%2.1f)\n' % (delta,)) last = timestamp fd.write('>>> runner.quit()\n') fd.close() PIDA-0.5.1/contrib/kiwi/kiwi/ui/test/runner.py0000644000175000017500000001660010652670717017171 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """ Runner - executes recorded scripts """ import doctest import os import sys import time from StringIO import StringIO import gobject from gtk import gdk from kiwi.log import Logger from kiwi.ui.test.common import WidgetIntrospecter log = Logger('kiwi.ui.test.player') class NotReadyYet(Exception): pass class MissingWidget(KeyError): pass class MagicWindowWrapper(object): def __init__(self, window, ns): self.window = window self.ns = ns def delete(self): self.window.emit('delete-event', gdk.Event(gdk.DELETE)) def __getattr__(self, attr): if not attr in self.ns: raise MissingWidget(attr) return self.ns[attr] # Override some StringIO methods. class _SpoofOut(StringIO): def getvalue(self): result = StringIO.getvalue(self) # If anything at all was written, make sure there's a trailing # newline. There's no way for the expected output to indicate # that a trailing newline is missing. if result and not result.endswith("\n"): result += "\n" # Prevent softspace from screwing up the next test case, in # case they used print with a trailing comma in an example. if hasattr(self, "softspace"): del self.softspace return result def truncate(self, size=None): StringIO.truncate(self, size) if hasattr(self, "softspace"): del self.softspace class Runner(object): """ @ivar parser: """ def __init__(self, filename): self.parser = doctest.DocTestParser() self.retval = 0 self._filename = filename self._pos = 0 self._windows = {} self._ns = {} self._source_id = -1 self._stmts = self.parser.get_examples(open(filename).read()) self._checker = doctest.OutputChecker() # Create a fake output target for capturing doctest output. self._fakeout = _SpoofOut() self._options = doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE wi = WidgetIntrospecter() wi.register_event_handler() wi.connect('window-added', self._on_wi__window_added) wi.connect('window-removed', self._on_wi__window_removed) # Callbacks def _on_wi__window_added(self, wi, window, name, ns): log.info('Window added: %s' % (name,)) self._windows[name] = MagicWindowWrapper(window, ns) self._iterate() def _on_wi__window_removed(self, wi, window, name): log.info('Window removed: %s' % (name,)) del self._windows[name] self._iterate() # Private def _run(self, ex): save_stdout = sys.stdout sys.stdout = self._fakeout try: exec compile(ex.source, self._filename, 'single', 0, 1) in self._ns finally: sys.stdout = save_stdout if ex.want: got = self._fakeout.getvalue() self._fakeout.truncate(0) if not self._checker.check_output(ex.want, got, self._options): print >> sys.stderr, ( "\nERROR at %s:%d\n" " >>> %s\n" "Expected %s, but got %s" % (self._filename, ex.lineno, ex.source, ex.want[:-1], got[:-1])) self.error() def _iterate(self): stmts = self._stmts while True: if self._pos == len(stmts): self.quit() break ex = stmts[self._pos] self._pos += 1 log.info('will now execute %r' % (ex.source[:-1],)) try: self._run(ex) except NotReadyYet: self._pos -= 1 break except (SystemExit, KeyboardInterrupt): raise SystemExit except MissingWidget, e: raise SystemExit( "ERROR: Could not find widget: %s" % str(e)) except Exception, e: import traceback traceback.print_exc() log.info('Executed %r' % (ex.source[:-1],)) self._last = time.time() # Public API def quit(self): print '* Executed successfully' sys.exit(0) def error(self): os._exit(1) def start(self): self._last = time.time() def sleep(self, duration): """ @param duration: """ # We don't want to block the interface here which means that # we cannot use time.sleep. # Instead we schedule another execute iteration in the future # and raises NotReadyYet which stops the interpreter until # iterate is called again. def _iter(): # Turn ourselves off and allow future calls to wait() to # queue new waits. self._source_id = -1 # Iterate, which will call us again self._iterate() return False if self._source_id != -1: raise NotReadyYet # The delta is the last time we executed a statement minus delta = (self._last + duration) - time.time() if delta > 0: ms = int(delta * 1000) self._source_id = gobject.timeout_add(ms, _iter) raise NotReadyYet # Okay, we've waited enough, let's go back to business def waitopen(self, window_name): """ @param window_name: """ if not window_name in self._windows: raise NotReadyYet(window_name) return self._windows[window_name] def waitclose(self, window_name): """ @param window_name: """ if window_name in self._windows: raise NotReadyYet(window_name) runner = None def play_file(script, filename=None, args=None): """ @param script: @param filename: @param args: """ global runner log.info('Running script %s' % script) runner = Runner(script) if filename is None: fd = open(script) data = fd.readline()[:-1] + fd.readline()[:-1] # Check for run: lines in the doctests # run: .... pos = data.find('run:') if pos != -1: rest = data[pos+5:] # run: foo --arg if ' ' in rest: filename, args = rest.split(' ', 1) args = [args] # run: foo else: filename = rest else: if args is None: args = [] sys.argv = [filename] + args[:] execfile(sys.argv[0], globals(), globals()) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/0002755000175000017500000000000010652671501015764 5ustar alialiPIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/__init__.py0000644000175000017500000000146710652670737020115 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/button.py0000644000175000017500000000560110652670737017663 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """GtkButton support for the Kiwi Framework""" import datetime import gtk from gtk import gdk from kiwi import ValueUnset from kiwi.datatypes import number from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject class ProxyButton(PropertyObject, gtk.Button, ProxyWidgetMixin): """ A ProxyButton is a Button subclass which is implementing the features required to be used inside the kiwi framework. It has a specific feature not found in other implementations. If the datatype is set to pixbuf a gtk.Image will be constructed from the pixbuf and be set as a child for the Button """ allowed_data_types = (basestring, datetime.date, datetime.datetime, datetime.time, gdk.Pixbuf) + number __gtype_name__ = 'ProxyButton' def __init__(self): ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=str) gtk.Button.__init__(self) def read(self): if issubclass(self.data_type, gdk.Pixbuf): image = self.get_image() if not image: return storage_type = image.get_storage_type() if storage_type != gtk.IMAGE_PIXBUF: raise ValueError( "the image of a ProxyButton must be loaded " "from a pixbuf, not %s" % storage_type) return image.get_pixbuf() else: return self._from_string(self.get_label()) def update(self, data): if issubclass(self.data_type, gdk.Pixbuf): if data == ValueUnset: data = None if not data: image = None else: image = gtk.Image() image.set_from_pixbuf(data) image.show() self.set_property('image', image) else: if data is None: text = "" else: text = self._as_string(data) self.set_label(text) self.emit('content-changed') PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/checkbutton.py0000644000175000017500000000456510652670737020671 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Gustavo Rahal # Johan Dahlin # Lorenzo Gil Sanchez # """GtkCheckButton support for the Kiwi Framework""" import gtk from kiwi import ValueUnset from kiwi.python import deprecationwarn from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject, gsignal, type_register class ProxyCheckButton(PropertyObject, gtk.CheckButton, ProxyWidgetMixin): __gtype_name__ = 'ProxyCheckButton' # changed allowed data types because checkbuttons can only # accept bool values allowed_data_types = bool, def __init__(self, label=None, use_underline=True): ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=bool) gtk.CheckButton.__init__(self, label=label, use_underline=use_underline) gsignal('toggled', 'override') def do_toggled(self): self.emit('content-changed') self.chain() def read(self): return self.get_active() def update(self, data): if data is None or data is ValueUnset: self.set_active(False); return # No conversion to string needed, we only accept bool self.set_active(data) class CheckButton(ProxyCheckButton): def __init__(self): deprecationwarn( 'CheckButton is deprecated, use ProxyCheckButton instead', stacklevel=3) ProxyCheckButton.__init__(self) type_register(CheckButton) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/colorbutton.py0000644000175000017500000000253510652670737020725 0ustar aliali# version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Ali Afshar # """ColorButton proxy for the kiwi framework""" import gtk from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject, gsignal, type_register class ProxyColorButton(PropertyObject, gtk.ColorButton, ProxyWidgetMixin): __gtype_name__ = 'ProxyColorButton' allowed_data_types = object, def __init__(self, color=gtk.gdk.Color(0, 0, 0)): ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=object) gtk.ColorButton.__init__(self, color) gsignal('color-set', 'override') def do_color_set(self): self.emit('content-changed') self.chain() def read(self): return self.get_color() def update(self, data): self.set_color(data) type_register(ProxyColorButton) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/combo.py0000644000175000017500000005012010652670737017443 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Johan Dahlin # Gustavo Rahal # Daniel Saran R. da Cunha # Evandro Vale Miquelito # """GtkComboBox and GtkComboBoxEntry support for the Kiwi Framework. The GtkComboBox and GtkComboBoxEntry classes here are also slightly extended they contain methods to easily insert and retrieve data from combos. """ import gobject import gtk from gtk import keysyms from kiwi import ValueUnset from kiwi.component import implements from kiwi.datatypes import number from kiwi.enums import ComboColumn, ComboMode from kiwi.interfaces import IEasyCombo from kiwi.python import deprecationwarn from kiwi.ui.comboboxentry import BaseComboBoxEntry from kiwi.ui.comboentry import ComboEntry from kiwi.ui.proxywidget import ProxyWidgetMixin, ValidatableProxyWidgetMixin from kiwi.ui.widgets.entry import ProxyEntry from kiwi.utils import PropertyObject, gproperty class _EasyComboBoxHelper(object): implements(IEasyCombo) def __init__(self, combobox): """Call this constructor after the Combo one""" if not isinstance(combobox, (gtk.ComboBox, ComboEntry)): raise TypeError( "combo needs to be a gtk.ComboBox or ComboEntry instance") self._combobox = combobox model = gtk.ListStore(str, object) self._combobox.set_model(model) self.mode = ComboMode.UNKNOWN def get_mode(self): return self.mode def set_mode(self, mode): if self.mode != ComboMode.UNKNOWN: raise AssertionError self.mode = mode def clear(self): """Removes all items from list""" model = self._combobox.get_model() model.clear() def prefill(self, itemdata, sort=False): if not isinstance(itemdata, (list, tuple)): raise TypeError("'data' parameter must be a list or tuple of item " "descriptions, found %s" % (type(itemdata),)) self.clear() if len(itemdata) == 0: return if self.mode == ComboMode.UNKNOWN: first = itemdata[0] if isinstance(first, basestring): self.set_mode(ComboMode.STRING) elif isinstance(first, (tuple, list)): self.set_mode(ComboMode.DATA) else: raise TypeError("Could not determine type, items must " "be strings or tuple/list") mode = self.mode if mode not in (ComboMode.STRING, ComboMode.DATA): raise TypeError("Incorrect format for itemdata; see " "docstring for more information") model = self._combobox.get_model() values = {} if mode == ComboMode.STRING: if sort: itemdata.sort() for item in itemdata: if item in values: raise KeyError("Tried to insert duplicate value " "%s into Combo!" % (item,)) else: values[item] = None model.append((item, None)) elif mode == ComboMode.DATA: if sort: itemdata.sort(lambda x, y: cmp(x[0], y[0])) for item in itemdata: text, data = item if text in values: raise KeyError("Tried to insert duplicate value " "%s into Combo!" % (item,)) else: values[text] = None model.append((text, data)) def append_item(self, label, data=None): """ Adds a single item to the Combo. Takes: - label: a string with the text to be added - data: the data to be associated with that item """ if not isinstance(label, basestring): raise TypeError("label must be string, found %s" % (label,)) if self.mode == ComboMode.UNKNOWN: if data is not None: self.set_mode(ComboMode.DATA) else: self.set_mode(ComboMode.STRING) model = self._combobox.get_model() if self.mode == ComboMode.STRING: if data is not None: raise TypeError("data can not be specified in string mode") model.append((label, None)) elif self.mode == ComboMode.DATA: if data is None: raise TypeError("data must be specified in string mode") model.append((label, data)) else: raise AssertionError def insert_item(self, position, label, data=None): """ Inserts a single item at a position to the Combo. @param position: position to insert the item at @param label: a string with the text to be added @param data: the data to be associated with that item """ if not isinstance(label, basestring): raise TypeError("label must be string, found %s" % (label,)) if self.mode == ComboMode.UNKNOWN: if data is not None: self.set_mode(ComboMode.DATA) else: self.set_mode(ComboMode.STRING) model = self._combobox.get_model() if self.mode == ComboMode.STRING: if data is not None: raise TypeError("data can not be specified in string mode") model.insert(position, (label, None)) elif self.mode == ComboMode.DATA: if data is None: raise TypeError("data must be specified in string mode") model.insert(position, (label, data)) else: raise AssertionError def select(self, data): mode = self.mode if self.mode == ComboMode.STRING: self.select_item_by_label(data) elif self.mode == ComboMode.DATA: self.select_item_by_data(data) else: # XXX: When setting the datatype to non string, automatically go to # data mode raise TypeError("unknown ComboBox mode. Did you call prefill?") def select_item_by_position(self, pos): self._combobox.set_active(pos) def select_item_by_label(self, label): model = self._combobox.get_model() for row in model: if row[ComboColumn.LABEL] == label: self._combobox.set_active_iter(row.iter) break else: raise KeyError("No item correspond to label %r in the combo %s" % (label, self._combobox.name)) def select_item_by_data(self, data): if self.mode != ComboMode.DATA: raise TypeError("select_item_by_data can only be used in data mode") model = self._combobox.get_model() for row in model: if row[ComboColumn.DATA] == data: self._combobox.set_active_iter(row.iter) break else: raise KeyError("No item correspond to data %r in the combo %s" % (data, self._combobox.name)) def get_model_strings(self): return [row[ComboColumn.LABEL] for row in self._combobox.get_model()] def get_model_items(self): if self.mode != ComboMode.DATA: raise TypeError("get_model_items can only be used in data mode") model = self._combobox.get_model() items = {} for row in model: items[row[ComboColumn.LABEL]] = row[ComboColumn.DATA] return items def get_selected_label(self): iter = self._combobox.get_active_iter() if not iter: return model = self._combobox.get_model() return model[iter][ComboColumn.LABEL] def get_selected_data(self): if self.mode != ComboMode.DATA: raise TypeError("get_selected_data can only be used in data mode") iter = self._combobox.get_active_iter() if not iter: return model = self._combobox.get_model() return model[iter][ComboColumn.DATA] def get_selected(self): mode = self.mode if mode == ComboMode.STRING: return self.get_selected_label() elif mode == ComboMode.DATA: return self.get_selected_data() else: raise AssertionError("No mode selected") class ProxyComboBox(PropertyObject, gtk.ComboBox, ProxyWidgetMixin): __gtype_name__ = 'ProxyComboBox' allowed_data_types = (basestring, object) + number def __init__(self): gtk.ComboBox.__init__(self) ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self) self._helper = _EasyComboBoxHelper(self) self.connect('changed', self._on__changed) renderer = gtk.CellRendererText() self.pack_start(renderer) self.add_attribute(renderer, 'text', ComboColumn.LABEL) def __len__(self): # GtkComboBox is a GtkContainer subclass which implements __len__ in # PyGTK in 2.8 and higher. Therefor we need to provide our own # implementation to be backwards compatible and override the new # behavior in 2.8 return len(self.get_model()) def __nonzero__(self): return True # Callbacks def _on__changed(self, combo): self.emit('content-changed') # IProxyWidget def read(self): if self._helper.get_mode() == ComboMode.UNKNOWN: return ValueUnset data = self.get_selected() if self._helper.get_mode() == ComboMode.STRING: data = self._from_string(data) return data def update(self, data): # We dont need validation because the user always # choose a valid value if data is None or data is ValueUnset: return if self._helper.get_mode() == ComboMode.STRING: data = self._as_string(data) self.select(data) # IEasyCombo def prefill(self, itemdata, sort=False): """ See L{kiwi.interfaces.IEasyCombo.prefill} """ self._helper.prefill(itemdata, sort) # we always have something selected, by default the first item self.set_active(0) self.emit('content-changed') def clear(self): """ See L{kiwi.interfaces.IEasyCombo.clear} """ self._helper.clear() self.emit('content-changed') def append_item(self, label, data=None): """ See L{kiwi.interfaces.IEasyCombo.append_item} """ self._helper.append_item(label, data) def insert_item(self, position, label, data=None): """ See L{kiwi.interfaces.IEasyCombo.insert_item} """ self._helper.insert_item(position, label, data) def select(self, data): """ See L{kiwi.interfaces.IEasyCombo.select} """ self._helper.select(data) def select_item_by_position(self, pos): """ See L{kiwi.interfaces.IEasyCombo.select} """ self._helper.select_item_by_position(pos) def select_item_by_label(self, label): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_position} """ self._helper.select_item_by_label(label) def select_item_by_data(self, data): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_label} """ self._helper.select_item_by_data(data) def get_model_strings(self): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_data} """ return self._helper.get_model_strings() def get_model_items(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_strings} """ return self._helper.get_model_items() def get_selected_label(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_items} """ return self._helper.get_selected_label() def get_selected_data(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_label} """ return self._helper.get_selected_data() def get_selected(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_data} """ return self._helper.get_selected() class ProxyComboBoxEntry(PropertyObject, BaseComboBoxEntry, ValidatableProxyWidgetMixin): allowed_data_types = (basestring, object) + number __gtype_name__ = 'ProxyComboBoxEntry' # it doesn't make sense to connect to this signal # because we want to monitor the entry of the combo # not the combo box itself. gproperty("list-editable", bool, True, "Editable") def __init__(self, **kwargs): deprecationwarn( 'ProxyComboBoxEntry is deprecated, use ProxyComboEntry instead', stacklevel=3) BaseComboBoxEntry.__init__(self) ValidatableProxyWidgetMixin.__init__(self, widget=self.entry) # We need to create the helper before PropertyObject, since we # need to access the helper in prop_set_list_editable, which # PropertyObject might call self._helper = _EasyComboBoxHelper(self) PropertyObject.__init__(self, **kwargs) self.set_text_column(ComboColumn.LABEL) # here we connect the expose-event signal directly to the entry self.child.connect('changed', self._on_child_entry__changed) # HACK! we force a queue_draw because when the window is first # displayed the icon is not drawn. gobject.idle_add(self.queue_draw) self.set_events(gtk.gdk.KEY_RELEASE_MASK) self.connect("key-release-event", self._on__key_release_event) def __nonzero__(self): return True def __len__(self): return len(self.get_model()) # Properties def prop_set_list_editable(self, value): if self._helper.get_mode() == ComboMode.DATA: return self.entry.set_editable(value) return value # Private def _update_selection(self, text=None): if text is None: text = self.entry.get_text() self.select_item_by_label(text) def _add_text_to_combo_list(self): text = self.entry.get_text() if not text.strip(): return if text in self.get_model_strings(): return self.entry.set_text('') self.append_item(text) self._update_selection(text) # Callbacks def _on__key_release_event(self, widget, event): """Checks for "Enter" key presses and add the entry text to the combo list if the combo list is set as editable. """ if not self.list_editable: return if event.keyval in (keysyms.KP_Enter, keysyms.Return): self._add_text_to_combo_list() def _on_child_entry__changed(self, widget): """Called when something on the entry changes""" if not widget.get_text(): return self.emit('content-changed') # IProxyWidget def read(self): if self._helper.get_mode() == ComboMode.UNKNOWN: return ValueUnset return self.get_selected() def update(self, data): if data is ValueUnset or data is None: self.entry.set_text("") else: self.select(data) # IEasyCombo def prefill(self, itemdata, sort=False, clear_entry=True): """ See L{kiwi.interfaces.IEasyCombo.prefill} """ self._helper.prefill(itemdata, sort) if clear_entry: self.entry.set_text("") # setup the autocompletion auto = gtk.EntryCompletion() auto.set_model(self.get_model()) auto.set_text_column(ComboColumn.LABEL) self.entry.set_completion(auto) # we always have something selected, by default the first item self.set_active(0) self.emit('content-changed') def clear(self): """ See L{kiwi.interfaces.IEasyCombo.clear} """ self._helper.clear() self.entry.set_text("") def append_item(self, label, data=None): """ See L{kiwi.interfaces.IEasyCombo.append_item} """ self._helper.append_item(label, data) def insert_item(self, position, label, data=None): """ See L{kiwi.interfaces.IEasyCombo.insert_item} """ self._helper.insert_item(position, label, data) def select(self, data): """ See L{kiwi.interfaces.IEasyCombo.select} """ self._helper.select(data) def select_item_by_position(self, pos): """ See L{kiwi.interfaces.IEasyCombo.select} """ self._helper.select_item_by_position(pos) def select_item_by_label(self, label): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_position} """ self._helper.select_item_by_label(label) def select_item_by_data(self, data): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_label} """ self._helper.select_item_by_data(data) def get_model_strings(self): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_data} """ return self._helper.get_model_strings() def get_model_items(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_strings} """ return self._helper.get_model_items() def get_selected_label(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_items} """ return self._helper.get_selected_label() def get_selected_data(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_label} """ return self._helper.get_selected_data() def get_selected(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_data} """ return self._helper.get_selected() # Public API def set_mode(self, mode): # If we're in the transition to go from # unknown->label set editable to False if (self._helper.get_mode() == ComboMode.UNKNOWN and mode == ComboMode.DATA): self.entry.set_editable(False) self._helper.set_mode(self, mode) class ProxyComboEntry(PropertyObject, ComboEntry, ValidatableProxyWidgetMixin): __gtype_name__ = 'ProxyComboEntry' allowed_data_types = (basestring, object) + number gproperty("list-editable", bool, True, "Editable") def __init__(self): entry = ProxyEntry() ComboEntry.__init__(self, entry=entry) ValidatableProxyWidgetMixin.__init__(self) PropertyObject.__init__(self) entry.connect('content-changed', self._on_entry__content_changed) def __nonzero__(self): return True def __len__(self): return len(self.get_model()) # Properties def prop_set_list_editable(self, value): self.entry.set_editable(value) return value # Callbacks def _on_entry__content_changed(self, entry): # We only need to listen for changes in the entry, it's updated # even if you select something in the popup list self.emit('content-changed') # IconEntry def set_tooltip(self, text): self.entry.set_tooltip(text) # IProxyWidget def read(self): return self.get_selected() def update(self, data): if data is ValueUnset or data is None: self.entry.set_text("") else: self.select(data) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/combobox.py0000644000175000017500000000245210652670737020161 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2001-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Gustavo Rahal # Johan Dahlin # """GtkComboBox and GtkComboBoxEntry support for the Kiwi Framework. backwards compatibility layer""" from kiwi.ui.widgets.combo import ProxyComboBox from kiwi.ui.widgets.combo import ProxyComboBoxEntry class ComboBox(ProxyComboBox): pass class ComboBoxEntry(ProxyComboBoxEntry): pass PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/entry.py0000644000175000017500000002145310652670737017514 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006-2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # Ronaldo Maia # """GtkEntry support for the Kiwi Framework""" import datetime import pango from kiwi.datatypes import converter, number, ValueUnset, currency from kiwi.decorators import deprecated from kiwi.enums import Alignment from kiwi.python import deprecationwarn from kiwi.ui.entry import MaskError, KiwiEntry, ENTRY_MODE_TEXT, \ ENTRY_MODE_DATA from kiwi.ui.dateentry import DateEntry from kiwi.ui.proxywidget import ValidatableProxyWidgetMixin, \ VALIDATION_ICON_WIDTH from kiwi.utils import PropertyObject, gsignal, type_register class ProxyEntry(KiwiEntry, ValidatableProxyWidgetMixin): """The Kiwi Entry widget has many special features that extend the basic gtk entry. First of all, as every Kiwi Widget, it implements the Proxy protocol. As the users types the entry can interact with the application model automatically. Kiwi Entry also implements interesting UI additions. If the input data does not match the data type of the entry the background nicely fades to a light red color. As the background changes an information icon appears. When the user passes the mouse over the information icon a tooltip is displayed informing the user how to correctly fill the entry. When dealing with date and float data-type the information on how to fill these entries is displayed according to the current locale. """ allowed_data_types = (basestring, datetime.date, datetime.time, datetime.datetime, object) + number __gtype_name__ = 'ProxyEntry' def __init__(self, data_type=None): self._block_changed = False self._has_been_updated = False KiwiEntry.__init__(self) ValidatableProxyWidgetMixin.__init__(self) self._data_type = data_type # Hide currency symbol from the entry. self.set_options_for_datatype(currency, symbol=False) def __post_init__(self): self.set_property('data_type', self._data_type) # Virtual methods gsignal('changed', 'override') def do_changed(self): if self._block_changed: self.emit_stop_by_name('changed') return self._update_current_object(self.get_text()) self.emit('content-changed') def prop_set_data_type(self, data_type): data_type = super(ProxyEntry, self).prop_set_data_type(data_type) # When there is no datatype, nothing needs to be done. if not data_type: return # Numbers and dates should be right aligned conv = converter.get_converter(data_type) if conv.align == Alignment.RIGHT: self.set_property('xalign', 1.0) # Apply a mask for the data types, some types like # dates has a default mask try: self.set_mask_for_data_type(data_type) except MaskError: pass return data_type # Public API def set_mask_for_data_type(self, data_type): """ @param data_type: """ conv = converter.get_converter(data_type) mask = conv.get_mask() self.set_mask(mask) #@deprecated('prefill') def set_completion_strings(self, strings=[], values=[]): """ Set strings used for entry completion. If values are provided, each string will have an additional data type. @param strings: @type strings: list of strings @param values: @type values: list of values """ completion = self._get_completion() model = completion.get_model() model.clear() if values: self._mode = ENTRY_MODE_DATA self.prefill(zip(strings, values)) else: self._mode = ENTRY_MODE_TEXT self.prefill(strings) set_completion_strings = deprecated('prefill')(set_completion_strings) def set_text(self, text): """ Sets the text of the entry @param text: """ self._has_been_updated = True self._update_current_object(text) # If content isn't empty set_text emitts changed twice. # Protect content-changed from being updated and issue # a manual emission afterwards self._block_changed = True KiwiEntry.set_text(self, text) self._block_changed = False self.emit('content-changed') self.set_position(-1) # ProxyWidgetMixin implementation def read(self): mode = self._mode if mode == ENTRY_MODE_TEXT: # Do not consider masks which only displays static # characters invalid, instead return None if not self._has_been_updated: return ValueUnset text = self.get_text() return self._from_string(text) elif mode == ENTRY_MODE_DATA: return self._current_object else: raise AssertionError def update(self, data): if data is ValueUnset: self.set_text("") self._has_been_updated = False elif data is None: self.set_text("") else: mode = self._mode if mode == ENTRY_MODE_DATA: new = self._get_text_from_object(data) if new is None: raise TypeError("%r is not a data object" % data) text = new elif mode == ENTRY_MODE_TEXT: text = self._as_string(data) self.set_text(text) type_register(ProxyEntry) class Entry(ProxyEntry): def __init__(self, data_type=None): deprecationwarn('Entry is deprecated, use ProxyEntry instead', stacklevel=3) ProxyEntry.__init__(self, data_type) type_register(Entry) class ProxyDateEntry(PropertyObject, DateEntry, ValidatableProxyWidgetMixin): __gtype_name__ = 'ProxyDateEntry' # changed allowed data types because checkbuttons can only # accept bool values allowed_data_types = datetime.date, def __init__(self): DateEntry.__init__(self) ValidatableProxyWidgetMixin.__init__(self) PropertyObject.__init__(self) # Add some space to the entry, so it has rom for the icon, in case # of a validation error. # # Since we set the widget's width based on the number of characters, # get the width of a single char, so we can calculate how many # 'chars' the icon takes. layout = self.entry.get_layout() context = layout.get_context() metrics = context.get_metrics(context.get_font_description()) char_width = metrics.get_approximate_char_width() / pango.SCALE current_width = self.entry.get_width_chars() # We add 4 pixels to the width, because of the icon borders icon_width = VALIDATION_ICON_WIDTH + 4 self.entry.set_width_chars(current_width + int(icon_width / char_width)) gsignal('changed', 'override') def do_changed(self): self.chain() self.emit('content-changed') # ProxyWidgetMixin implementation def read(self): return self.get_date() def update(self, data): if data is None: self.entry.set_text("") else: self.set_date(data) def prop_set_mandatory(self, value): self.entry.set_property('mandatory', value) return value # ValidatableProxyWidgetMixin # Since the widget that should be marked as valid/invalid is the entry, # we also call those methods for self.entry def set_valid(self): ValidatableProxyWidgetMixin.set_valid(self) self.entry.set_valid() def set_invalid(self, text=None, fade=True): ValidatableProxyWidgetMixin.set_invalid(self, text, fade) self.entry.set_invalid(text, fade) def set_blank(self): ValidatableProxyWidgetMixin.set_blank(self) self.entry.set_blank() def get_background(self): return self.entry.get_background() type_register(ProxyDateEntry) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/filechooser.py0000644000175000017500000000506210652670737020653 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Ali Afshar # """Filechooser widgets for the kiwi framework""" import gtk from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject, gsignal class _FileChooserMixin(object): """Mixin to use common methods of the FileChooser interface""" allowed_data_types = basestring, gsignal('selection_changed', 'override') def do_selection_changed(self): self.emit('content-changed') self.chain() def read(self): return self.get_filename() def update(self, data): if data is None: return self.set_filename(data) class ProxyFileChooserWidget(_FileChooserMixin, PropertyObject, gtk.FileChooserWidget, ProxyWidgetMixin): __gtype_name__ = 'ProxyFileChooserWidget' def __init__(self, action=gtk.FILE_CHOOSER_ACTION_OPEN, backend=None): """ @param action: @param backend: """ ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=str) gtk.FileChooserWidget.__init__(self, action=action, backend=backend) class ProxyFileChooserButton(_FileChooserMixin, PropertyObject, gtk.FileChooserButton, ProxyWidgetMixin): __gtype_name__ = 'ProxyFileChooserButton' def __init__(self, title=None, backend=None, dialog=None): """ @param title: @param backend: @param dialog: """ ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=str) # Broken, Broken PyGTK if isinstance(title, str): gtk.FileChooserButton.__init__(self, title, backend) else: gtk.FileChooserButton.__init__(self, dialog or title) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/fontbutton.py0000644000175000017500000000252310652670737020552 0ustar aliali# version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Ali Afshar # """FontButton proxy for the kiwi framework""" import gtk from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject, gsignal, type_register class ProxyFontButton(PropertyObject, gtk.FontButton, ProxyWidgetMixin): __gtype_name__ = 'ProxyFontButton' allowed_data_types = basestring, def __init__(self, fontname=None): ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self, data_type=str) gtk.FontButton.__init__(self, fontname) gsignal('font-set', 'override') def do_font_set(self): self.emit('content-changed') self.chain() def read(self): return self.get_font_name() def update(self, data): self.set_font_name(data) type_register(ProxyFontButton) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/label.py0000644000175000017500000001477510652670737017443 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Gustavo Rahal # """GtkLabel support for the Kiwi Framework The L{Label} is also extended to support some basic markup like L{Label.set_bold}""" import datetime import gtk from kiwi.datatypes import number, ValueUnset, converter from kiwi.enums import Alignment from kiwi.python import deprecationwarn from kiwi.ui.gadgets import set_foreground from kiwi.ui.proxywidget import ProxyWidgetMixin from kiwi.utils import PropertyObject, type_register class ProxyLabel(PropertyObject, gtk.Label, ProxyWidgetMixin): __gtype_name__ = 'ProxyLabel' allowed_data_types = (basestring, datetime.date, datetime.datetime, datetime.time) + number _label_replacements = {} def __init__(self, label='', data_type=None): """ @param label: initial text @param data_type: data type of label """ gtk.Label.__init__(self, label) PropertyObject.__init__(self, data_type=data_type) ProxyWidgetMixin.__init__(self) self.set_use_markup(True) self._attr_dic = { "style": None, "weight": None, "size": None, "underline": None} self._size_list = ('xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large') self.connect("notify::label", self._on_label_changed) self._block_notify_label = False def prop_set_data_type(self, data_type): data_type = super(ProxyLabel, self).prop_set_data_type(data_type) if not data_type: return conv = converter.get_converter(data_type) if conv.align == Alignment.RIGHT: self.set_property('xalign', 1.0) return data_type #@classmethod def replace(cls, markup, value): cls._label_replacements[markup] = value replace = classmethod(replace) def _on_label_changed(self, label, param): if self._block_notify_label: self._block_notify_label = False return text = self.get_property('label') for rep in self._label_replacements.keys(): if rep in text: text = text.replace(rep, self._label_replacements[rep]) self._block_notify_label = True self.set_property('label', text) # Since most of the time labels do not have a model attached to it # we should just emit a signal if a model is defined if self.model_attribute: self.emit('content-changed') def read(self): return self._from_string(self.get_text()) def update(self, data): if data is None or data is ValueUnset: text = "" else: text = self._as_string(data) self.set_text(text) def _apply_attributes(self): # sorting is been done so we can be sure of the order of the # attributes. Helps writing tests cases attrs = self._attr_dic keys = attrs.keys() keys.sort() attr_pairs = ['%s="%s"' % (key, attrs[key]) for key in keys if attrs[key]] self.set_markup('%s' % (' '.join(attr_pairs), self.get_text())) def _set_text_attribute(self, attribute_name, attr, value): if value: if self._attr_dic[attribute_name] is None: self._attr_dic[attribute_name] = attr self._apply_attributes() else: if self._attr_dic[attribute_name] is not None: self._attr_dic[attribute_name] = None self._apply_attributes() def set_bold(self, value): """ If True set the text to bold. False sets the text to normal """ self._set_text_attribute("weight", "bold", value) def set_italic(self, value): """ Enable or disable italic text @param value: Allowed values: - True: enable Italic attribute - False: disable Italic attribute """ self._set_text_attribute("style", "italic", value) def set_underline(self, value): """ Enable or disable underlined text @param value: Allowed values: - True: enable Underline attribute - Fase: disable Underline attribute """ self._set_text_attribute("underline", "single", value) def set_size(self, size=None): """ Set the size of the label. If size is empty the label will be set to the default size. @param size: Allowed values: - xx-small - x-small - small - medium, - large - x-large - xx-large @type size: string """ if (size is not None and size not in self._size_list): raise ValueError('Size of "%s" label is not valid' % self.get_text()) self._attr_dic["size"] = size self._apply_attributes() def set_text(self, text): """ Overrides gtk.Label set_text method. Sets the new text of the label but keeps the formating @param text: label @type text: string """ gtk.Label.set_text(self, text) self._apply_attributes() def set_color(self, color): set_foreground(self, color) type_register(ProxyLabel) class Label(ProxyLabel): def __init__(self, label='', data_type=None): deprecationwarn( 'Label is deprecated, use ProxyLabel instead', stacklevel=3) ProxyLabel.__init__(self, label=label, data_type=data_type) type_register(Label) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/list.py0000644000175000017500000000427110652670737017325 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2001-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """High level wrapper for GtkTreeView: backwards compatibility layer""" import gtk from kiwi.decorators import deprecated from kiwi.python import deprecationwarn from kiwi.ui.objectlist import Column, SequentialColumn, ColoredColumn, \ ListLabel, SummaryLabel from kiwi.ui.objectlist import ObjectList, log # pyflakes Column, SequentialColumn, ColoredColumn, ListLabel, SummaryLabel class List(ObjectList): def __init__(self, columns=[], instance_list=None, mode=gtk.SELECTION_BROWSE): deprecationwarn( 'List is deprecated, use ObjectList instead', stacklevel=3) ObjectList.__init__(self, columns, instance_list, mode) # Backwards compat def add_instance(self, *args, **kwargs): return self.append(*args, **kwargs) add_instance = deprecated('append', log)(add_instance) def remove_instance(self, *args, **kwargs): return self.remove(*args, **kwargs) remove_instance = deprecated('remove', log)(remove_instance) def update_instance(self, *args, **kwargs): return self.update(*args, **kwargs) update_instance = deprecated('update', log)(update_instance) def select_instance(self, *args, **kwargs): return self.select(*args, **kwargs) select_instance = deprecated('select', log)(select_instance) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/radiobutton.py0000644000175000017500000000636710652670737020714 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Daniel Saran R. da Cunha # Lorenzo Gil Sanchez # Gustavo Rahal # """GtkRadioButton support for the Kiwi Framework""" import gtk from kiwi import ValueUnset from kiwi.python import deprecationwarn from kiwi.utils import PropertyObject, gproperty, type_register from kiwi.ui.proxywidget import ProxyWidgetMixin class ProxyRadioButton(PropertyObject, gtk.RadioButton, ProxyWidgetMixin): __gtype_name__ = 'ProxyRadioButton' allowed_data_types = object, gproperty('data-value', str, nick='Data Value') def __init__(self, group=None, label=None, use_underline=True): gtk.RadioButton.__init__(self, None, label, use_underline) if group: self.set_group(group) ProxyWidgetMixin.__init__(self) PropertyObject.__init__(self) self.connect('group-changed', self._on_group_changed) def _on_radio__toggled(self, radio): self.emit('content-changed') def _on_group_changed(self, radio): for radio in radio.get_group(): radio.connect('toggled', self._on_radio__toggled) def get_selected(self): """ Get the currently selected radiobutton. @returns: The selected L{RadioButton} or None if there are no selected radiobuttons. """ for button in self.get_group(): if button.get_active(): return button def read(self): button = self.get_selected() if button is None: return ValueUnset return self._from_string(button.data_value) def update(self, data): if data is None or data is ValueUnset: # In a group of radiobuttons, the only widget which is in # the proxy is ourself, the other buttons do not get their # update() method called, so the default value is activate # ourselves when the model is empty self.set_active(True) return data = self._as_string(data) for rb in self.get_group(): if rb.get_property('data-value') == data: rb.set_active(True) class RadioButton(ProxyRadioButton): def __init__(self): deprecationwarn( 'RadioButton is deprecated, use ProxyRadioButton instead', stacklevel=3) ProxyRadioButton.__init__(self) type_register(RadioButton) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/spinbutton.py0000644000175000017500000001014310652670737020552 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Gustavo Rahal # Lorenzo Gil Sanchez # Evandro Vale Miquelito # """GtkSpinButton support for the Kiwi Framework L{SpinButton} is also enhanced to display an icon using L{kiwi.ui.icon.IconEntry} """ import gtk from kiwi.datatypes import number, ValueUnset from kiwi.python import deprecationwarn from kiwi.ui.icon import IconEntry from kiwi.ui.proxywidget import ValidatableProxyWidgetMixin from kiwi.utils import PropertyObject, gsignal, type_register class ProxySpinButton(PropertyObject, gtk.SpinButton, ValidatableProxyWidgetMixin): """ A SpinButton subclass which adds supports for the Kiwi Framework. This widget supports validation The only allowed types for spinbutton are int and float. """ __gtype_name__ = 'ProxySpinButton' allowed_data_types = number def __init__(self, data_type=int): # since the default data_type is str we need to set it to int # or float for spinbuttons gtk.SpinButton.__init__(self) PropertyObject.__init__(self, data_type=data_type) ValidatableProxyWidgetMixin.__init__(self) self._icon = IconEntry(self) self.set_property('xalign', 1.0) gsignal('changed', 'override') def do_changed(self): """Called when the content of the spinbutton changes. """ # This is a work around, because GtkEditable.changed is called too # often, as reported here: http://bugzilla.gnome.org/show_bug.cgi?id=64998 if self.get_text() != '': self.emit('content-changed') self.chain() def read(self): return self._from_string(self.get_text()) def update(self, data): if data is None or data is ValueUnset: self.set_text("") else: # set_value accepts a float or int, no as_string conversion needed, # and since we accept only int and float just send it in. self.set_value(data) def do_expose_event(self, event): # This gets called when any of our three windows needs to be redrawn gtk.SpinButton.do_expose_event(self, event) if event.window == self.window: self._icon.draw_pixbuf() gsignal('size-allocate', 'override') def do_size_allocate(self, allocation): self.chain(allocation) if self.flags() & gtk.REALIZED: self._icon.resize_windows() def do_realize(self): gtk.SpinButton.do_realize(self) self._icon.construct() def do_unrealize(self): self._icon.deconstruct() gtk.SpinButton.do_unrealize(self) # IconEntry def set_tooltip(self, text): self._icon.set_tooltip(text) def set_pixbuf(self, pixbuf): self._icon.set_pixbuf(pixbuf) def update_background(self, color): self._icon.update_background(color) def get_background(self): return self._icon.get_background() def get_icon_window(self): return self._icon.get_icon_window() class SpinButton(ProxySpinButton): def __init__(self): deprecationwarn( 'SpinButton is deprecated, use ProxySpinButton instead', stacklevel=3) ProxySpinButton.__init__(self) type_register(SpinButton) PIDA-0.5.1/contrib/kiwi/kiwi/ui/widgets/textview.py0000644000175000017500000000550510652670737020232 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Gustavo Rahal # Evandro Vale Miquelito # Johan Dahlin """GtkTextView support for the Kiwi Framework""" import datetime import gtk from kiwi import ValueUnset from kiwi.datatypes import number from kiwi.python import deprecationwarn from kiwi.ui.proxywidget import ValidatableProxyWidgetMixin from kiwi.utils import PropertyObject, type_register class ProxyTextView(PropertyObject, gtk.TextView, ValidatableProxyWidgetMixin): __gtype_name__ = 'ProxyTextView' allowed_data_types = (basestring, datetime.date) + number def __init__(self): self._is_unset = True gtk.TextView.__init__(self) PropertyObject.__init__(self, data_type=str) ValidatableProxyWidgetMixin.__init__(self) self._textbuffer = gtk.TextBuffer() self._textbuffer.connect('changed', self._on_textbuffer__changed) self.set_buffer(self._textbuffer) def _on_textbuffer__changed(self, textbuffer): self._is_unset = False self.emit('content-changed') self.read() def read(self): if self._is_unset: return ValueUnset textbuffer = self._textbuffer data = textbuffer.get_text(textbuffer.get_start_iter(), textbuffer.get_end_iter()) return self._from_string(data) def update(self, data): if data is ValueUnset: self._textbuffer.set_text("") self._is_unset = True return elif data is None: text = "" else: self.is_unset = False text = self._as_string(data) self._textbuffer.set_text(text) class TextView(ProxyTextView): def __init__(self): deprecationwarn( 'TextView is deprecated, use ProxyTextView instead', stacklevel=3) ProxyTextView.__init__(self) type_register(TextView) PIDA-0.5.1/contrib/kiwi/kiwi/ui/__init__.py0000644000175000017500000000226710652670746016446 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # """User interface: Framework and Widget support""" try: import gtk gtk # pyflakes except ImportError, e: try: import pygtk pygtk.require('2.0') except: pass try: import gtk gtk # pyflakes except: raise SystemExit( "PyGTK 2.6.0 or higher is required by kiwi.ui\n" "Error was: %s" % e) PIDA-0.5.1/contrib/kiwi/kiwi/ui/comboboxentry.py0000644000175000017500000001012410652670746017570 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin """Reimplementation of GtkComboBoxEntry in Python. The main difference between the L{BaseComboBoxEntry} and GtkComboBoxEntry is that a {kiwi.ui.widgets.entry.Entry} is used instead of a GtkEntry.""" import gobject import gtk from kiwi.python import deprecationwarn from kiwi.ui.entry import KiwiEntry class BaseComboBoxEntry(gtk.ComboBox): def __init__(self, model=None, text_column=-1): deprecationwarn( 'ComboBoxEntry is deprecated, use ComboEntry instead', stacklevel=3) gtk.ComboBox.__init__(self) self.entry = KiwiEntry() # HACK: We need to set a private variable, this seems to # be the only way of doing so self.entry.start_editing(gtk.gdk.Event(gtk.gdk.BUTTON_PRESS)) self.add(self.entry) self.entry.show() self._text_renderer = gtk.CellRendererText() self.pack_start(self._text_renderer, True) self.set_active(-1) self.entry_changed_id = self.entry.connect('changed', self._on_entry__changed) self._active_changed_id = self.connect("changed", self._on_entry__active_changed) self._has_frame_changed(None) self.connect("notify::has-frame", self._has_frame_changed) if not model: model = gtk.ListStore(str) text_column = 0 self.set_model(model) self.set_text_column(text_column) # Virtual methods def do_mnemnoic_activate(self, group_cycling): self.entry.grab_focus() return True def do_grab_focus(self): self.entry.grab_focus() # Signal handlers def _on_entry__active_changed(self, combobox): iter = combobox.get_active_iter() if not iter: return self.entry.handler_block(self.entry_changed_id) model = self.get_model() self.entry.set_text(model[iter][self._text_column]) self.entry.handler_unblock(self.entry_changed_id) def _has_frame_changed(self, pspec): has_frame = self.get_property("has-frame") self.entry.set_has_frame(has_frame) def _on_entry__changed(self, entry): self.handler_block(self._active_changed_id) self.set_active(-1) self.handler_unblock(self._active_changed_id) # Public API def set_text_column(self, text_column): self._text_column = text_column if text_column != -1: self.set_attributes(self._text_renderer, text=text_column) def get_text_column(self): return self._text_column # IconEntry def set_pixbuf(self, pixbuf): self.entry.set_pixbuf(pixbuf) def update_background(self, color): self.entry.update_background(color) def get_background(self): return self.entry.get_background() def get_icon_window(self): return self.entry.get_icon_window() gobject.type_register(BaseComboBoxEntry) def test(): win = gtk.Window() win.connect('delete-event', gtk.main_quit) e = BaseComboBoxEntry() win.add(e) m = gtk.ListStore(str) m.append(['foo']) m.append(['bar']) m.append(['baz']) e.set_model(m) win.show_all() gtk.main() if __name__ == '__main__': test() PIDA-0.5.1/contrib/kiwi/kiwi/ui/comboentry.py0000644000175000017500000005252010652670746017065 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Widget for displaying a list of objects""" import gtk from gtk import gdk, keysyms from kiwi.component import implements from kiwi.interfaces import IEasyCombo from kiwi.enums import ComboColumn, ComboMode from kiwi.log import Logger from kiwi.ui.entry import KiwiEntry from kiwi.ui.entrycompletion import KiwiEntryCompletion from kiwi.utils import gsignal, type_register log = Logger('kiwi.ui.combo') class _ComboEntryPopup(gtk.Window): gsignal('text-selected', str) def __init__(self, comboentry): gtk.Window.__init__(self, gtk.WINDOW_POPUP) self.add_events(gdk.BUTTON_PRESS_MASK) self.connect('key-press-event', self._on__key_press_event) self.connect('button-press-event', self._on__button_press_event) self._comboentry = comboentry # Number of visible rows in the popup window, sensible # default value from other toolkits self._visible_rows = 10 self._initial_text = None self._popping_up = False self._filter_model = None frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_OUT) self.add(frame) frame.show() vbox = gtk.VBox() frame.add(vbox) vbox.show() self._sw = gtk.ScrolledWindow() self._sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER) vbox.pack_start(self._sw) self._sw.show() self._model = gtk.ListStore(str) self._treeview = gtk.TreeView(self._model) self._treeview.set_enable_search(False) self._treeview.connect('motion-notify-event', self._on_treeview__motion_notify_event) self._treeview.connect('button-release-event', self._on_treeview__button_release_event) self._treeview.add_events(gdk.BUTTON_PRESS_MASK) self._selection = self._treeview.get_selection() self._selection.set_mode(gtk.SELECTION_BROWSE) self._treeview.append_column( gtk.TreeViewColumn('Foo', gtk.CellRendererText(), text=0)) self._treeview.set_headers_visible(False) self._sw.add(self._treeview) self._treeview.show() self._label = gtk.Label() vbox.pack_start(self._label, False, False) self.set_resizable(False) self.set_screen(comboentry.get_screen()) def popup(self, text=None, filter=False): """ Shows the list of options. And optionally selects an item @param text: text to select @param filter: filter the list of options. A filter_model must be set using L{set_model}() """ combo = self._comboentry if not (combo.flags() & gtk.REALIZED): return treeview = self._treeview if filter and self._filter_model: model = self._filter_model else: model = self._model if not len(model): return treeview.set_model(model) toplevel = combo.get_toplevel() if isinstance(toplevel, gtk.Window) and toplevel.group: toplevel.group.add_window(self) # width is meant for the popup window # height is meant for the treeview, since it calculates using # the height of the cells on the rows x, y, width, height = self._get_position() self.set_size_request(width, -1) treeview.set_size_request(-1, height) self.move(x, y) self.show() treeview.set_hover_expand(True) selection = treeview.get_selection() selection.unselect_all() if text: for row in model: if text in row: selection.select_iter(row.iter) treeview.scroll_to_cell(row.path, use_align=True, row_align=0.5) treeview.set_cursor(row.path) break self._popping_up = True if filter: # do not grab if its a completion return # Grab window self.grab_focus() if not (self._treeview.flags() & gtk.HAS_FOCUS): self._treeview.grab_focus() if not self._popup_grab_window(): self.hide() return self.grab_add() def popdown(self): combo = self._comboentry if not (combo.flags() & gtk.REALIZED): return self.grab_remove() self.hide() def set_label_text(self, text): if text is None: text = '' self._label.hide() else: self._label.show() self._label.set_text(text) def set_model(self, model): if isinstance(model, gtk.TreeModelFilter): self._filter_model = model model = model.get_model() self._treeview.set_model(model) self._model = model # Callbacks def _on__key_press_event(self, window, event): """ Mimics Combobox behavior Escape or Alt+Up: Close Enter, Return or Space: Select """ keyval = event.keyval state = event.state & gtk.accelerator_get_default_mod_mask() if (keyval == keysyms.Escape or ((keyval == keysyms.Up or keyval == keysyms.KP_Up) and state == gdk.MOD1_MASK)): self.popdown() return True elif keyval == keysyms.Tab: self.popdown() # XXX: private member of comboentry self._comboentry._button.grab_focus() return True elif (keyval == keysyms.Return or keyval == keysyms.space or keyval == keysyms.KP_Enter or keyval == keysyms.KP_Space): model, treeiter = self._selection.get_selected() self.emit('text-selected', model[treeiter][0]) return True return False def _on__button_press_event(self, window, event): # If we're clicking outside of the window # close the popup if (event.window != self.window or (tuple(self.allocation.intersect( gdk.Rectangle(x=int(event.x), y=int(event.y), width=1, height=1)))) == (0, 0, 0, 0)): self.popdown() def _on_treeview__motion_notify_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval self._selection.select_path(path) self._treeview.set_cursor(path) def _on_treeview__button_release_event(self, treeview, event): retval = treeview.get_path_at_pos(int(event.x), int(event.y)) if not retval: return path, column, x, y = retval model = treeview.get_model() self.emit('text-selected', model[path][0]) def _popup_grab_window(self): activate_time = 0L if gdk.pointer_grab(self.window, True, (gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK), None, None, activate_time) == 0: if gdk.keyboard_grab(self.window, True, activate_time) == 0: return True else: self.window.get_display().pointer_ungrab(activate_time); return False return False def _get_position(self): treeview = self._treeview treeview.realize() sample = self._comboentry # We need to fetch the coordinates of the entry window # since comboentry itself does not have a window x, y = sample.entry.window.get_origin() width = sample.allocation.width hpolicy = vpolicy = gtk.POLICY_NEVER self._sw.set_policy(hpolicy, vpolicy) pwidth = self.size_request()[0] if pwidth > width: self._sw.set_policy(gtk.POLICY_ALWAYS, vpolicy) pwidth, pheight = self.size_request() rows = len(self._treeview.get_model()) if rows > self._visible_rows: rows = self._visible_rows self._sw.set_policy(hpolicy, gtk.POLICY_ALWAYS) focus_padding = treeview.style_get_property('focus-line-width') * 2 cell_height = treeview.get_column(0).cell_get_size()[4] height = (cell_height + focus_padding) * rows screen = self._comboentry.get_screen() monitor_num = screen.get_monitor_at_window(sample.window) monitor = screen.get_monitor_geometry(monitor_num) if x < monitor.x: x = monitor.x elif x + width > monitor.x + monitor.width: x = monitor.x + monitor.width - width if y + sample.allocation.height + height <= monitor.y + monitor.height: y += sample.allocation.height elif y - height >= monitor.y: y -= height elif (monitor.y + monitor.height - (y + sample.allocation.height) > y - monitor.y): y += sample.allocation.height height = monitor.y + monitor.height - y else : height = y - monitor.y y = monitor.y # Use half of the available screen space max_height = monitor.height / 2 if height > max_height: height = int(max_height) elif height < 0: height = 0 return x, y, width, height def get_selected_iter(self): model, treeiter = self._selection.get_selected() # if the model currently being used is a TreeModelFilter, convert # the iter to be a TreeModel iter (witch is what the user expects) if isinstance(model, gtk.TreeModelFilter) and treeiter: treeiter = model.convert_iter_to_child_iter(treeiter) return treeiter def set_selected_iter(self, treeiter): """ Selects an item in the comboentry given a treeiter @param treeiter: the tree iter to select """ model = self._treeview.get_model() # Since the user passed a TreeModel iter, if the model currently # being used is a TreeModelFilter, convert it to be a TreeModelFilter # iter if isinstance(model, gtk.TreeModelFilter): # See #3099 for an explanation why this is needed and a # testcase tmodel = model.get_model() if tmodel.iter_is_valid(treeiter): # revert back to the unfiltered model so we can select # the right object self._treeview.set_model(tmodel) self._selection = self._treeview.get_selection() else: treeiter = model.convert_child_iter_to_iter(treeiter) self._selection.select_iter(treeiter) type_register(_ComboEntryPopup) class ComboEntry(gtk.HBox): implements(IEasyCombo) gsignal('changed') gsignal('activate') def __init__(self, entry=None): """ @param entry: a gtk.Entry subclass to use """ gtk.HBox.__init__(self) self._popping_down = False if not entry: entry = KiwiEntry() self.mode = ComboMode.UNKNOWN self.entry = entry self.entry.connect('activate', self._on_entry__activate) self.entry.connect('changed', self._on_entry__changed) self.entry.connect('scroll-event', self._on_entry__scroll_event) self.entry.connect('key-press-event', self._on_entry__key_press_event) self.pack_start(self.entry, True, True) self.entry.show() self._button = gtk.ToggleButton() self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_end(self._button, False, False) self._button.show() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) self._button.add(arrow) arrow.show() self._popup = _ComboEntryPopup(self) self._popup.connect('text-selected', self._on_popup__text_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) completion = KiwiEntryCompletion() completion.set_popup_window(self._popup) completion.set_treeview(self._popup._treeview) self.entry.set_completion(completion) self.set_model(completion.get_model()) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry_completion__match_selected(self, completion, model, iter): # the iter we receive is specific to the tree model filter used # In the entry completion, convert it to an iter in the real model if isinstance(model, gtk.TreeModelFilter): iter = model.convert_iter_to_child_iter(iter) self.set_active_iter(iter) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__changed(self, entry): self.emit('changed') def _on_entry__scroll_event(self, entry, event): model = self.get_model() if not len(model): return treeiter = self._popup.get_selected_iter() # If nothing is selected, select the first one if not treeiter: self.set_active_iter(model[0].iter) return curr = model[treeiter].path[0] # Scroll up, select the previous item if event.direction == gdk.SCROLL_UP: curr -= 1 if curr >= 0: self.set_active_iter(model[curr].iter) # Scroll down, select the next item elif event.direction == gdk.SCROLL_DOWN: curr += 1 if curr < len(model): self.set_active_iter(model[curr].iter) def _on_entry__key_press_event(self, entry, event): """ Mimics Combobox behavior Alt+Down: Open popup """ keyval, state = event.keyval, event.state state &= gtk.accelerator_get_default_mod_mask() if ((keyval == keysyms.Down or keyval == keysyms.KP_Down) and state == gdk.MOD1_MASK): self.popup() return True def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__text_selected(self, popup, text): self.set_text(text) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self.emit('changed') def _on_button__toggled(self, button): if self._popping_down: return self.popup() # Private def _update(self): model = self._model if not len(model): return iter = self._popup.get_selected_iter() if not iter: iter = model[0].iter self._popup.set_selected_iter(iter) # Public API def clicked(self): pass def popup(self): """ Show the popup window """ self._popup.popup(self.entry.get_text()) def popdown(self): """ Hide the popup window """ self._popup.popdown() def set_text(self, text): """ @param text: """ self.entry.set_text(text) def get_text(self): """ @returns: current text """ return self.entry.get_text() def set_model(self, model): """ Set the tree model to model @param model: new model @type model: gtk.TreeModel """ self._model = model self._popup.set_model(model) completion = self.entry.get_completion() completion.connect('match-selected', self._on_entry_completion__match_selected) completion.set_model(model) self._update() def get_model(self): """ @returns: our model @rtype: gtk.TreeModel """ return self._model def set_active_iter(self, iter): """ @param iter: iter to select @type iter: gtk.TreeIter """ self._popup.set_selected_iter(iter) text = self._model[iter][0] if text is not None: self.set_text(text) def get_active_iter(self): """ @returns: the selected iter @rtype: gtk.TreeIter """ return self._popup.get_selected_iter() def get_mode(self): return self.mode def set_label_text(self, text): self._popup.set_label_text(text) def set_active(self, rowno): self.set_active_iter(self._model[rowno].iter) # IEasyCombo interface def clear(self): """Removes all items from list""" self._model.clear() self.entry.set_text("") def prefill(self, itemdata, sort=False): """ See L{kiwi.interfaces.IEasyCombo.prefill} """ self._model.clear() self.entry.prefill(itemdata, sort) self.mode = self.entry.get_mode() def select_item_by_data(self, data): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_data} """ treeiter = self.entry.get_iter_by_data(data) self.set_active_iter(treeiter) def select_item_by_label(self, text): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_label} """ treeiter = self.entry.get_iter_by_label(text) self.set_active_iter(treeiter) def select_item_by_position(self, position): """ See L{kiwi.interfaces.IEasyCombo.select_item_by_position} """ row = self._model[position] self.set_active_iter(row.iter) def get_selected(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected} """ treeiter = self.get_active_iter() if treeiter: return self.entry.get_selected_by_iter(treeiter) def get_selected_label(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_label} """ treeiter = self.get_active_iter() if treeiter: return self.entry.get_selected_label(treeiter) def get_selected_data(self): """ See L{kiwi.interfaces.IEasyCombo.get_selected_data} """ treeiter = self.get_active_iter() if treeiter: return self.entry.get_selected_data(treeiter) def select(self, obj): """ See L{kiwi.interfaces.IEasyCombo.select} """ try: treeiter = self.entry.get_iter_from_obj(obj) except KeyError: log.warn("%s does not contain a %r object" % ( self.__class__.__name__, obj)) return self.set_active_iter(treeiter) def append_item(self, label, data=None): """ See L{kiwi.interfaces.IEasyCombo.append_item} """ if not isinstance(label, basestring): raise TypeError("label must be string, found %s" % label) if self.mode == ComboMode.UNKNOWN: if data is not None: self.mode = ComboMode.DATA else: self.mode = ComboMode.STRING model = self._model if self.mode == ComboMode.STRING: if data is not None: raise TypeError("data can not be specified in string mode") model.append((label, None)) elif self.mode == ComboMode.DATA: if data is None: raise TypeError("data must be specified in string mode") model.append((label, data)) else: raise AssertionError def get_model_strings(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_strings} """ return [row[ComboColumn.LABEL] for row in self._model] def get_model_items(self): """ See L{kiwi.interfaces.IEasyCombo.get_model_items} """ if self.mode != ComboMode.DATA: raise TypeError("get_model_items can only be used in data mode") model = self._model items = {} for row in model: items[row[ComboColumn.LABEL]] = row[ComboColumn.DATA] return items # IconEntry def set_pixbuf(self, pixbuf): self.entry.set_pixbuf(pixbuf) def update_background(self, color): self.entry.update_background(color) def get_background(self): return self.entry.get_background() def get_icon_window(self): return self.entry.get_icon_window() type_register(ComboEntry) PIDA-0.5.1/contrib/kiwi/kiwi/ui/dateentry.py0000644000175000017500000002774710652670746016720 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # # # Based on date cell renderer in Planner written by Richard Hult # and Mikael Hallendal # import gettext import datetime import gtk from gtk import gdk, keysyms from kiwi.datatypes import converter, ValueUnset, ValidationError from kiwi.utils import gsignal, type_register _ = lambda m: gettext.dgettext('kiwi', m) date_converter = converter.get_converter(datetime.date) class _DateEntryPopup(gtk.Window): gsignal('date-selected', object) def __init__(self, dateentry): gtk.Window.__init__(self, gtk.WINDOW_POPUP) self.add_events(gdk.BUTTON_PRESS_MASK) self.connect('key-press-event', self._on__key_press_event) self.connect('button-press-event', self._on__button_press_event) self._dateentry = dateentry frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) self.add(frame) frame.show() vbox = gtk.VBox() vbox.set_border_width(6) frame.add(vbox) vbox.show() self._vbox = vbox self.calendar = gtk.Calendar() self.calendar.connect('day-selected-double-click', self._on_calendar__day_selected_double_click) vbox.pack_start(self.calendar, False, False) self.calendar.show() buttonbox = gtk.HButtonBox() buttonbox.set_border_width(6) buttonbox.set_layout(gtk.BUTTONBOX_SPREAD) vbox.pack_start(buttonbox, False, False) buttonbox.show() for label, callback in [(_('_Today'), self._on_today__clicked), (_('_Cancel'), self._on_cancel__clicked), (_('_Select'), self._on_select__clicked)]: button = gtk.Button(label, use_underline=True) button.connect('clicked', callback) buttonbox.pack_start(button) button.show() self.set_resizable(False) self.set_screen(dateentry.get_screen()) self.realize() self.height = self._vbox.size_request()[1] def _on_calendar__day_selected_double_click(self, calendar): self.emit('date-selected', self.get_date()) def _on__button_press_event(self, window, event): # If we're clicking outside of the window close the popup hide = False # Also if the intersection of self and the event is empty, hide # the calendar if (tuple(self.allocation.intersect( gdk.Rectangle(x=int(event.x), y=int(event.y), width=1, height=1))) == (0, 0, 0, 0)): hide = True # Toplevel is the window that received the event, and parent is the # calendar window. If they are not the same, means the popup should # be hidden. This is necessary for when the event happens on another # widget toplevel = event.window.get_toplevel() parent = self.calendar.get_parent_window() if toplevel != parent: hide = True if hide: self.popdown() def _on__key_press_event(self, window, event): """ Mimics Combobox behavior Escape or Alt+Up: Close Enter, Return or Space: Select """ keyval = event.keyval state = event.state & gtk.accelerator_get_default_mod_mask() if (keyval == keysyms.Escape or ((keyval == keysyms.Up or keyval == keysyms.KP_Up) and state == gdk.MOD1_MASK)): self.popdown() return True elif keyval == keysyms.Tab: self.popdown() # XXX: private member of dateentry self._comboentry._button.grab_focus() return True elif (keyval == keysyms.Return or keyval == keysyms.space or keyval == keysyms.KP_Enter or keyval == keysyms.KP_Space): self.emit('date-selected', self.get_date()) return True return False def _on_select__clicked(self, button): self.emit('date-selected', self.get_date()) def _on_cancel__clicked(self, button): self.popdown() def _on_today__clicked(self, button): self.set_date(datetime.date.today()) def _popup_grab_window(self): activate_time = 0L if gdk.pointer_grab(self.window, True, (gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK), None, None, activate_time) == 0: if gdk.keyboard_grab(self.window, True, activate_time) == 0: return True else: self.window.get_display().pointer_ungrab(activate_time); return False return False def _get_position(self): self.realize() calendar = self sample = self._dateentry # We need to fetch the coordinates of the entry window # since comboentry itself does not have a window x, y = sample.entry.window.get_origin() width, height = calendar.size_request() height = self.height screen = sample.get_screen() monitor_num = screen.get_monitor_at_window(sample.window) monitor = screen.get_monitor_geometry(monitor_num) if x < monitor.x: x = monitor.x elif x + width > monitor.x + monitor.width: x = monitor.x + monitor.width - width if y + sample.allocation.height + height <= monitor.y + monitor.height: y += sample.allocation.height elif y - height >= monitor.y: y -= height elif (monitor.y + monitor.height - (y + sample.allocation.height) > y - monitor.y): y += sample.allocation.height height = monitor.y + monitor.height - y else : height = y - monitor.y y = monitor.y return x, y, width, height def popup(self, date): """ Shows the list of options. And optionally selects an item @param date: date to select """ combo = self._dateentry if not (combo.flags() & gtk.REALIZED): return treeview = self.calendar if treeview.flags() & gtk.MAPPED: return toplevel = combo.get_toplevel() if isinstance(toplevel, gtk.Window) and toplevel.group: toplevel.group.add_window(self) x, y, width, height = self._get_position() self.set_size_request(width, height) self.move(x, y) self.show_all() if (date is not None and date is not ValueUnset): self.set_date(date) self.grab_focus() if not (self.calendar.flags() & gtk.HAS_FOCUS): self.calendar.grab_focus() if not self._popup_grab_window(): self.hide() return self.grab_add() def popdown(self): combo = self._dateentry if not (combo.flags() & gtk.REALIZED): return self.grab_remove() self.hide_all() # month in gtk.Calendar is zero-based (i.e the allowed values are 0-11) # datetime one-based (i.e. the allowed values are 1-12) # So convert between them def get_date(self): y, m, d = self.calendar.get_date() return datetime.date(y, m + 1, d) def set_date(self, date): self.calendar.select_month(date.month - 1, date.year) self.calendar.select_day(date.day) # FIXME: Only mark the day in the current month? self.calendar.clear_marks() self.calendar.mark_day(date.day) class DateEntry(gtk.HBox): gsignal('changed') gsignal('activate') def __init__(self): gtk.HBox.__init__(self) self._popping_down = False self._old_date = None # bootstrap problems, kiwi.ui.widgets.entry imports dateentry # we need to use a proxy entry because we want the mask from kiwi.ui.widgets.entry import ProxyEntry self.entry = ProxyEntry() self.entry.connect('changed', self._on_entry__changed) self.entry.set_property('data-type', datetime.date) mask = self.entry.get_mask() if mask: self.entry.set_width_chars(len(mask)) self.pack_start(self.entry, False, False) self.entry.show() self._button = gtk.ToggleButton() self._button.connect('scroll-event', self._on_entry__scroll_event) self._button.connect('toggled', self._on_button__toggled) self._button.set_focus_on_click(False) self.pack_start(self._button, False, False) self._button.show() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) self._button.add(arrow) arrow.show() self._popup = _DateEntryPopup(self) self._popup.connect('date-selected', self._on_popup__date_selected) self._popup.connect('hide', self._on_popup__hide) self._popup.set_size_request(-1, 24) # Virtual methods def do_grab_focus(self): self.entry.grab_focus() # Callbacks def _on_entry__changed(self, entry): try: date = self.get_date() except ValidationError: date = None self._changed(date) def _on_entry__activate(self, entry): self.emit('activate') def _on_entry__scroll_event(self, entry, event): if event.direction == gdk.SCROLL_UP: days = 1 elif event.direction == gdk.SCROLL_DOWN: days = -1 else: return try: date = self.get_date() except ValidationError: date = None if not date: newdate = datetime.date.today() else: newdate = date + datetime.timedelta(days=days) self.set_date(newdate) def _on_button__toggled(self, button): if self._popping_down: return try: date = self.get_date() except ValidationError: date = None self._popup.popup(date) def _on_popup__hide(self, popup): self._popping_down = True self._button.set_active(False) self._popping_down = False def _on_popup__date_selected(self, popup, date): self.set_date(date) popup.popdown() self.entry.grab_focus() self.entry.set_position(len(self.entry.get_text())) self._changed(date) def _changed(self, date): if self._old_date != date: self.emit('changed') self._old_date = date # Public API def set_date(self, date): """ @param date: a datetime.date instance or None """ if not isinstance(date, datetime.date) and date is not None: raise TypeError( "date must be a datetime.date instance or None, not %r" % ( date,)) if date is None: value = '' else: value = date_converter.as_string(date) self.entry.set_text(value) def get_date(self): """ @returns: the currently selected day """ try: date = self.entry.read() except ValidationError: date = None if date == ValueUnset: date = None return date type_register(DateEntry) PIDA-0.5.1/contrib/kiwi/kiwi/ui/delegates.py0000644000175000017500000001410010652670746016631 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2002, 2003 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Johan Dahlin # """Defines the Delegate classes that are included in the Kiwi Framework.""" from kiwi.ui.views import SlaveView, BaseView from kiwi.controllers import BaseController from kiwi.python import deprecationwarn class Delegate(BaseView, BaseController): """A class that combines view and controller functionality into a single package. The Delegate class possesses a top-level window. """ def __init__(self, toplevel=None, widgets=(), gladefile=None, toplevel_name=None, delete_handler=None, keyactions=None): """Creates a new Delegate. The keyactions parameter is sent to L{kiwi.controllers.BaseController}, the rest are sent to L{kiwi.ui.views.BaseView} """ if gladefile: deprecationwarn( 'gladefile is deprecated in Delegate, ' 'use GladeDelegate instead', stacklevel=3) BaseView.__init__(self, toplevel=toplevel, widgets=widgets, gladefile=gladefile, toplevel_name=toplevel_name, delete_handler=delete_handler) BaseController.__init__(self, view=self, keyactions=keyactions) class GladeDelegate(BaseView, BaseController): """A class that combines view and controller functionality into a single package. The Delegate class possesses a top-level window. """ def __init__(self, gladefile=None, toplevel_name=None, domain=None, delete_handler=None, keyactions=None): """Creates a new GladeDelegate. The keyactions parameter is sent to L{kiwi.controllers.BaseController}, the rest are sent to L{kiwi.ui.views.BaseView} """ BaseView.__init__(self, gladefile=gladefile, toplevel_name=toplevel_name, domain=domain, delete_handler=delete_handler) BaseController.__init__(self, view=self, keyactions=keyactions) class SlaveDelegate(SlaveView, BaseController): """A class that combines view and controller functionality into a single package. It does not possess a top-level window, but is instead intended to be plugged in to a View or Delegate using attach_slave(). """ def __init__(self, toplevel=None, widgets=(), gladefile=None, toplevel_name=None, keyactions=None): """ The keyactions parameter is sent to L{kiwi.controllers.BaseController}, the rest are sent to L{kiwi.ui.views.SlaveView} """ if gladefile: deprecationwarn( 'gladefile is deprecated in Delegate, ' 'use GladeSlaveDelegate instead', stacklevel=3) SlaveView.__init__(self, toplevel, widgets, gladefile, toplevel_name) BaseController.__init__(self, view=self, keyactions=keyactions) class GladeSlaveDelegate(SlaveView, BaseController): """A class that combines view and controller functionality into a single package. It does not possess a top-level window, but is instead intended to be plugged in to a View or Delegate using attach_slave(). """ def __init__(self, gladefile=None, toplevel_name=None, domain=None, keyactions=None): """ The keyactions parameter is sent to L{kiwi.controllers.BaseController}, the rest are sent to L{kiwi.ui.views.SlavseView} """ SlaveView.__init__(self, gladefile=gladefile, toplevel_name=toplevel_name, domain=domain) BaseController.__init__(self, view=self, keyactions=keyactions) class ProxyDelegate(Delegate): """A class that combines view, controller and proxy functionality into a single package. The Delegate class possesses a top-level window. @ivar model: the model @ivar proxy: the proxy """ def __init__(self, model, proxy_widgets=None, gladefile=None, toplevel=None, widgets=(), toplevel_name=None, domain=None, delete_handler=None, keyactions=None): """Creates a new Delegate. @param model: instance to be attached @param proxy_widgets: The keyactions parameter is sent to L{kiwi.controllers.BaseController}, the rest are sent to L{kiwi.ui.views.BaseView} """ BaseView.__init__(self, toplevel, widgets, gladefile, toplevel_name, domain, delete_handler) self.model = model self.proxy = self.add_proxy(model, proxy_widgets) self.proxy.proxy_updated = self.proxy_updated BaseController.__init__(self, view=self, keyactions=keyactions) def set_model(self, model): """ @param model: """ self.proxy.set_model(model) self.model = model def proxy_updated(self, widget, attribute, value): # Can be overriden in subclasses pass def update(self, attribute): self.proxy.update(attribute) PIDA-0.5.1/contrib/kiwi/kiwi/ui/dialogs.py0000644000175000017500000003620610652670746016331 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Author(s): Johan Dahlin # import os import gettext import atk import gtk __all__ = ['error', 'info', 'messagedialog', 'warning', 'yesno', 'save', 'open', 'HIGAlertDialog', 'BaseDialog'] _ = lambda m: gettext.dgettext('kiwi', m) _IMAGE_TYPES = { gtk.MESSAGE_INFO: gtk.STOCK_DIALOG_INFO, gtk.MESSAGE_WARNING : gtk.STOCK_DIALOG_WARNING, gtk.MESSAGE_QUESTION : gtk.STOCK_DIALOG_QUESTION, gtk.MESSAGE_ERROR : gtk.STOCK_DIALOG_ERROR, } _BUTTON_TYPES = { gtk.BUTTONS_NONE: (), gtk.BUTTONS_OK: (gtk.STOCK_OK, gtk.RESPONSE_OK,), gtk.BUTTONS_CLOSE: (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,), gtk.BUTTONS_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,), gtk.BUTTONS_YES_NO: (gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES), gtk.BUTTONS_OK_CANCEL: (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) } class HIGAlertDialog(gtk.Dialog): def __init__(self, parent, flags, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_NONE): if not type in _IMAGE_TYPES: raise TypeError( "type must be one of: %s", ', '.join(_IMAGE_TYPES.keys())) if not buttons in _BUTTON_TYPES: raise TypeError( "buttons be one of: %s", ', '.join(_BUTTON_TYPES.keys())) gtk.Dialog.__init__(self, '', parent, flags) self.set_border_width(5) self.set_resizable(False) self.set_has_separator(False) # Some window managers (ION) displays a default title (???) if # the specified one is empty, workaround this by setting it # to a single space instead self.set_title(" ") self.set_skip_taskbar_hint(True) self.vbox.set_spacing(14) # It seems like get_accessible is not available on windows, go figure if hasattr(self, 'get_accessible'): self.get_accessible().set_role(atk.ROLE_ALERT) self._primary_label = gtk.Label() self._secondary_label = gtk.Label() self._details_label = gtk.Label() self._image = gtk.image_new_from_stock(_IMAGE_TYPES[type], gtk.ICON_SIZE_DIALOG) self._image.set_alignment(0.5, 0.0) self._primary_label.set_use_markup(True) for label in (self._primary_label, self._secondary_label, self._details_label): label.set_line_wrap(True) label.set_selectable(True) label.set_alignment(0.0, 0.5) hbox = gtk.HBox(False, 12) hbox.set_border_width(5) hbox.pack_start(self._image, False, False) vbox = gtk.VBox(False, 0) hbox.pack_start(vbox, False, False) vbox.pack_start(self._primary_label, False, False) vbox.pack_start(self._secondary_label, False, False) self._expander = gtk.expander_new_with_mnemonic( _("Show more _details")) self._expander.set_spacing(6) self._expander.add(self._details_label) vbox.pack_start(self._expander, False, False) self.vbox.pack_start(hbox, False, False) hbox.show_all() self._expander.hide() self.add_buttons(*_BUTTON_TYPES[buttons]) self.label_vbox = vbox def set_primary(self, text): self._primary_label.set_markup( "%s" % text) def set_secondary(self, text): self._secondary_label.set_markup(text) def set_details(self, text): self._details_label.set_text(text) self._expander.show() def set_details_widget(self, widget): self._expander.remove(self._details_label) self._expander.add(widget) widget.show() self._expander.show() class BaseDialog(gtk.Dialog): def __init__(self, parent=None, title='', flags=0, buttons=()): if parent and not isinstance(parent, gtk.Window): raise TypeError("parent needs to be None or a gtk.Window subclass") if not flags and parent: flags &= (gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) gtk.Dialog.__init__(self, title=title, parent=parent, flags=flags, buttons=buttons) self.set_border_width(6) self.set_has_separator(False) self.vbox.set_spacing(6) def messagedialog(dialog_type, short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1): """Create and show a MessageDialog. @param dialog_type: one of constants - gtk.MESSAGE_INFO - gtk.MESSAGE_WARNING - gtk.MESSAGE_QUESTION - gtk.MESSAGE_ERROR @param short: A header text to be inserted in the dialog. @param long: A long description of message. @param parent: The parent widget of this dialog @type parent: a gtk.Window subclass @param buttons: The button type that the dialog will be display, one of the constants: - gtk.BUTTONS_NONE - gtk.BUTTONS_OK - gtk.BUTTONS_CLOSE - gtk.BUTTONS_CANCEL - gtk.BUTTONS_YES_NO - gtk.BUTTONS_OK_CANCEL or a tuple or 2-sized tuples representing label and response. If label is a stock-id a stock icon will be displayed. @param default: optional default response id """ if buttons in (gtk.BUTTONS_NONE, gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL): dialog_buttons = buttons buttons = [] else: if buttons is not None and type(buttons) != tuple: raise TypeError( "buttons must be a GtkButtonsTypes constant or a tuple") dialog_buttons = gtk.BUTTONS_NONE if parent and not isinstance(parent, gtk.Window): raise TypeError("parent must be a gtk.Window subclass") d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL, type=dialog_type, buttons=dialog_buttons) if buttons: for text, response in buttons: d.add_buttons(text, response) d.set_primary(short) if long: if isinstance(long, gtk.Widget): d.set_details_widget(long) elif isinstance(long, basestring): d.set_details(long) else: raise TypeError( "long must be a gtk.Widget or a string, not %r" % long) if default != -1: d.set_default_response(default) if parent: d.set_transient_for(parent) d.set_modal(True) response = d.run() d.destroy() return response def _simple(type, short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1): if buttons == gtk.BUTTONS_OK: default = gtk.RESPONSE_OK return messagedialog(type, short, long, parent=parent, buttons=buttons, default=default) def error(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1): return _simple(gtk.MESSAGE_ERROR, short, long, parent=parent, buttons=buttons, default=default) def info(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1): return _simple(gtk.MESSAGE_INFO, short, long, parent=parent, buttons=buttons, default=default) def warning(short, long=None, parent=None, buttons=gtk.BUTTONS_OK, default=-1): return _simple(gtk.MESSAGE_WARNING, short, long, parent=parent, buttons=buttons, default=default) def yesno(text, parent=None, default=gtk.RESPONSE_YES, buttons=gtk.BUTTONS_YES_NO): return messagedialog(gtk.MESSAGE_WARNING, text, None, parent, buttons=buttons, default=default) def open(title='', parent=None, patterns=None, folder=None, filter=None): """Displays an open dialog. @param title: the title of the folder, defaults to 'Select folder' @param parent: parent gtk.Window or None @param patterns: a list of pattern strings ['*.py', '*.pl'] or None @param folder: initial folder or None @param filter: a filter to use or None, is incompatible with patterns """ ffilter = filter if patterns and ffilter: raise TypeError("Can't use patterns and filter at the same time") filechooser = gtk.FileChooserDialog(title or _('Open'), parent, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) if patterns or ffilter: if not ffilter: ffilter = gtk.FileFilter() for pattern in patterns: ffilter.add_pattern(pattern) filechooser.set_filter(ffilter) filechooser.set_default_response(gtk.RESPONSE_OK) if folder: filechooser.set_current_folder(folder) response = filechooser.run() if response != gtk.RESPONSE_OK: filechooser.destroy() return path = filechooser.get_filename() if path and os.access(path, os.R_OK): filechooser.destroy() return path abspath = os.path.abspath(path) error(_('Could not open file "%s"') % abspath, _('The file "%s" could not be opened. ' 'Permission denied.') % abspath) filechooser.destroy() return def selectfolder(title='', parent=None, folder=None): """Displays a select folder dialog. @param title: the title of the folder, defaults to 'Select folder' @param parent: parent gtk.Window or None @param folder: initial folder or None """ filechooser = gtk.FileChooserDialog( title or _('Select folder'), parent, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) if folder: filechooser.set_current_folder(folder) filechooser.set_default_response(gtk.RESPONSE_OK) response = filechooser.run() if response != gtk.RESPONSE_OK: filechooser.destroy() return path = filechooser.get_filename() if path and os.access(path, os.R_OK | os.X_OK): filechooser.destroy() return path abspath = os.path.abspath(path) error(_('Could not select folder "%s"') % abspath, _('The folder "%s" could not be selected. ' 'Permission denied.') % abspath) filechooser.destroy() return def ask_overwrite(filename, parent=None): submsg1 = _('A file named "%s" already exists') % os.path.abspath(filename) submsg2 = _('Do you wish to replace it with the current one?') text = ('%s\n\n%s\n' % (submsg1, submsg2)) result = messagedialog(gtk.MESSAGE_ERROR, text, parent=parent, buttons=((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (_("Replace"), gtk.RESPONSE_YES))) return result == gtk.RESPONSE_YES def save(title='', parent=None, current_name='', folder=None): """Displays a save dialog.""" filechooser = gtk.FileChooserDialog(title or _('Save'), parent, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) if current_name: filechooser.set_current_name(current_name) filechooser.set_default_response(gtk.RESPONSE_OK) if folder: filechooser.set_current_folder(folder) path = None while True: response = filechooser.run() if response != gtk.RESPONSE_OK: path = None break path = filechooser.get_filename() if not os.path.exists(path): break if ask_overwrite(path, parent): break filechooser.destroy() return path def password(primary='', secondary='', parent=None): """ Shows a password dialog and returns the password entered in the dialog @param primary: primary text @param secondary: secondary text @param parent: a gtk.Window subclass or None @returns: the password or None if none specified @rtype: string or None """ if not primary: raise ValueError("primary cannot be empty") d = HIGAlertDialog(parent=parent, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_OK_CANCEL) d.set_default_response(gtk.RESPONSE_OK) d.set_primary(primary + '\n') if secondary: secondary += '\n' d.set_secondary(secondary) hbox = gtk.HBox() hbox.set_border_width(6) hbox.show() d.label_vbox.pack_start(hbox) label = gtk.Label(_('Password:')) label.show() hbox.pack_start(label, False, False) entry = gtk.Entry() entry.set_invisible_char(u'\u2022') entry.set_visibility(False) entry.show() d.add_action_widget(entry, gtk.RESPONSE_OK) # FIXME: Is there another way of connecting widget::activate to a response? d.action_area.remove(entry) hbox.pack_start(entry, True, True, 12) response = d.run() if response == gtk.RESPONSE_OK: password = entry.get_text() else: password = None d.destroy() return password def _test(): yesno('Kill?', default=gtk.RESPONSE_NO) info('Some information displayed not too long\nbut not too short', long=('foobar ba asdjaiosjd oiadjoisjaoi aksjdasdasd kajsdhakjsdh\n' 'askdjhaskjdha skjdhasdasdjkasldj alksdjalksjda lksdjalksdj\n' 'asdjaslkdj alksdj lkasjdlkjasldkj alksjdlkasjd jklsdjakls\n' 'ask;ldjaklsjdlkasjd alksdj laksjdlkasjd lkajs kjaslk jkl\n'), default=gtk.RESPONSE_OK, ) error('An error occurred', gtk.Button('Woho')) error('Unable to mount the selected volume.', 'mount: can\'t find /media/cdrom0 in /etc/fstab or /etc/mtab') print open(title='Open a file', patterns=['*.py']) print save(title='Save a file', current_name='foobar.py') print password('Administrator password', 'To be able to continue the wizard you need to enter the ' 'administrator password for the database on host anthem') print selectfolder() if __name__ == '__main__': _test() PIDA-0.5.1/contrib/kiwi/kiwi/ui/entry.py0000644000175000017500000010304310652670746016042 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # Ronaldo Maia # # # Design notes: # # When inserting new text, supose, the entry, at some time is like this, # ahd the user presses '0', for instance: # -------------------------------- # | ( 1 2 ) 3 4 5 - 6 7 8 9 | # -------------------------------- # ^ ^ ^ # S P E # # S - start of the field (start) # E - end of the field (end) # P - pos - where the new text is being inserted. (pos) # # So, the new text will be: # # the old text, from 0 until P # + the new text # + the old text, from P until the end of the field, shifted to the # right # + the old text, from the end of the field, to the end of the text. # # After inserting, the text will be this: # -------------------------------- # | ( 1 2 ) 3 0 4 5 - 6 7 8 9 | # -------------------------------- # ^ ^ ^ # S P E # # # When deleting some text, supose, the entry, at some time is like this: # -------------------------------- # | ( 1 2 ) 3 4 5 6 - 7 8 9 0 | # -------------------------------- # ^ ^ ^ ^ # S s e E # # S - start of the field (_start) # E - end of the field (_end) # s - start of the text being deleted (start) # e - end of the text being deleted (end) # # end - start -> the number of characters being deleted. # # So, the new text will be: # # the old text, from 0 until the start of the text being deleted. # + the old text, from the start of where the text is being deleted, until # the end of the field, shifted to the left, end-start positions # + the old text, from the end of the field, to the end of the text. # # So, after the text is deleted, the entry will look like this: # # -------------------------------- # | ( 1 2 ) 3 5 6 - 7 8 9 0 | # -------------------------------- # ^ # P # # P = the position of the cursor after the deletion, witch is equal to # start (s at the previous illustration) """ An enchanced version of GtkEntry that supports icons and masks """ import gettext import string try: set except AttributeError: from sets import Set as set import gobject import pango import gtk from kiwi.enums import Direction from kiwi.environ import environ from kiwi.ui.icon import IconEntry from kiwi.ui.entrycompletion import KiwiEntryCompletion from kiwi.utils import PropertyObject, gsignal, gproperty, type_register if not environ.epydoc: HAVE_2_6 = gtk.pygtk_version[:2] <= (2, 6) else: HAVE_2_6 = True class MaskError(Exception): pass (INPUT_ASCII_LETTER, INPUT_ALPHA, INPUT_ALPHANUMERIC, INPUT_DIGIT) = range(4) INPUT_FORMATS = { '0': INPUT_DIGIT, 'L': INPUT_ASCII_LETTER, 'A': INPUT_ALPHANUMERIC, 'a': INPUT_ALPHANUMERIC, '&': INPUT_ALPHA, } # Todo list: Other usefull Masks # 9 - Digit, optional # ? - Ascii letter, optional # C - Alpha, optional INPUT_CHAR_MAP = { INPUT_ASCII_LETTER: lambda text: text in string.ascii_letters, INPUT_ALPHA: unicode.isalpha, INPUT_ALPHANUMERIC: unicode.isalnum, INPUT_DIGIT: unicode.isdigit, } (COL_TEXT, COL_OBJECT) = range(2) (ENTRY_MODE_UNKNOWN, ENTRY_MODE_TEXT, ENTRY_MODE_DATA) = range(3) _ = lambda msg: gettext.dgettext('kiwi', msg) class KiwiEntry(PropertyObject, gtk.Entry): """ The KiwiEntry is a Entry subclass with the following additions: - IconEntry, allows you to have an icon inside the entry - Mask, force the input to meet certain requirements - IComboMixin: Allows you work with objects instead of strings Adds a number of convenience methods such as L{prefill}(). """ __gtype_name__ = 'KiwiEntry' gproperty("completion", bool, False) gproperty('exact-completion', bool, default=False) gproperty("mask", str, default='') def __init__(self): self._completion = None gtk.Entry.__init__(self) PropertyObject.__init__(self) self.connect('insert-text', self._on_insert_text) self.connect('delete-text', self._on_delete_text) self.connect_after('grab-focus', self._after_grab_focus) self.connect('changed', self._on_changed) self.connect('focus', self._on_focus) self.connect('focus-out-event', self._on_focus_out_event) self.connect('move-cursor', self._on_move_cursor) # Ideally, this should be connected to notify::cursor-position, but # there seems to be a bug in gtk that the notification is not emited # when it should. # TODO: investigate that and report a bug. self.connect('notify::selection-bound', self._on_notify_selection_bound) self._block_changed = False self._current_object = None self._mode = ENTRY_MODE_TEXT self._icon = IconEntry(self) # List of validators # str -> static characters # int -> dynamic, according to constants above self._mask_validators = [] self._mask = None # Fields defined by mask # each item is a tuble, containing the begining and the end of the # field in the text self._mask_fields = [] self._current_field = -1 self._pos = 0 self._selecting = False self._block_insert = False self._block_delete = False # Virtual methods # PyGTK 2.6 does not support the virtual method do_size_allocate so # we have to use the signal instead # PyGTK 2.9.0 and later (bug #327715) does not work using the old code, # so we have to make this conditionally if HAVE_2_6: gsignal('size-allocate', 'override') def do_size_allocate(self, allocation): self.chain(allocation) if self.flags() & gtk.REALIZED: self._icon.resize_windows() else: def do_size_allocate(self, allocation): gtk.Entry.do_size_allocate(self, allocation) if self.flags() & gtk.REALIZED: self._icon.resize_windows() def do_expose_event(self, event): gtk.Entry.do_expose_event(self, event) if event.window == self.window: self._icon.draw_pixbuf() def do_realize(self): gtk.Entry.do_realize(self) self._icon.construct() def do_unrealize(self): self._icon.deconstruct() gtk.Entry.do_unrealize(self) # Properties def prop_set_exact_completion(self, value): self.set_exact_completion(value) return value def prop_set_completion(self, value): if not self.get_completion(): self.set_completion(gtk.EntryCompletion()) return value def prop_set_mask(self, value): try: self.set_mask(value) return self.get_mask() except MaskError, e: pass return '' # Public API def set_text(self, text): completion = self.get_completion() if isinstance(completion, KiwiEntryCompletion): self.handler_block(completion.changed_id) gtk.Entry.set_text(self, text) if isinstance(completion, KiwiEntryCompletion): self.handler_unblock(completion.changed_id) # Mask & Fields def set_mask(self, mask): """ Sets the mask of the Entry. Supported format characters are: - '0' digit - 'L' ascii letter (a-z and A-Z) - '&' alphabet, honors the locale - 'a' alphanumeric, honors the locale - 'A' alphanumeric, honors the locale This is similar to MaskedTextBox: U{http://msdn2.microsoft.com/en-us/library/system.windows.forms.maskedtextbox.mask(VS.80).aspx} Example mask for a ISO-8601 date >>> entry.set_mask('0000-00-00') @param mask: the mask to set """ if not mask: self.modify_font(pango.FontDescription("sans")) self._mask = mask return # First, reset self._mask_validators = [] self._mask_fields = [] self._current_field = -1 mask = unicode(mask) input_length = len(mask) lenght = 0 pos = 0 field_begin = 0 field_end = 0 while True: if pos >= input_length: break if mask[pos] in INPUT_FORMATS: self._mask_validators += [INPUT_FORMATS[mask[pos]]] field_end += 1 else: self._mask_validators.append(mask[pos]) if field_begin != field_end: self._mask_fields.append((field_begin, field_end)) field_end += 1 field_begin = field_end pos += 1 self._mask_fields.append((field_begin, field_end)) self.modify_font(pango.FontDescription("monospace")) self._really_delete_text(0, -1) self._insert_mask(0, input_length) self._mask = mask def get_mask(self): """ @returns: the mask """ return self._mask def get_field_text(self, field): if not self._mask: raise MaskError("a mask must be set before calling get_field_text") text = self.get_text() start, end = self._mask_fields[field] return text[start: end].strip() def get_fields(self): """ Get the fields assosiated with the entry. A field is dynamic content separated by static. For example, the format string 000-000 has two fields separated by a dash. if a field is empty it'll return an empty string otherwise it'll include the content @returns: fields @rtype: list of strings """ if not self._mask: raise MaskError("a mask must be set before calling get_fields") fields = [] text = unicode(self.get_text()) for start, end in self._mask_fields: fields.append(text[start:end].strip()) return fields def get_empty_mask(self, start=None, end=None): """ Gets the empty mask between start and end @param start: @param end: @returns: mask @rtype: string """ if start is None: start = 0 if end is None: end = len(self._mask_validators) s = '' for validator in self._mask_validators[start:end]: if isinstance(validator, int): s += ' ' elif isinstance(validator, unicode): s += validator else: raise AssertionError return s def get_field_pos(self, field): """ Get the position at the specified field. """ if field >= len(self._mask_fields): return None start, end = self._mask_fields[field] return start def _get_field_ideal_pos(self, field): start, end = self._mask_fields[field] text = self.get_field_text(field) pos = start+len(text) return pos def get_field(self): if self._current_field >= 0: return self._current_field else: return None def set_field(self, field, select=False): if field >= len(self._mask_fields): return pos = self._get_field_ideal_pos(field) self.set_position(pos) if select: field_text = self.get_field_text(field) start, end = self._mask_fields[field] self.select_region(start, pos) self._current_field = field def get_field_length(self, field): if 0 <= field < len(self._mask_fields): start, end = self._mask_fields[field] return end - start def _shift_text(self, start, end, direction=Direction.LEFT, positions=1): """ Shift the text, to the right or left, n positions. Note that this does not change the entry text. It returns the shifted text. @param start: @param end: @param direction: see L{kiwi.enums.Direction} @param positions: the number of positions to shift. @return: returns the text between start and end, shifted to the direction provided. """ text = self.get_text() new_text = '' validators = self._mask_validators if direction == Direction.LEFT: i = start else: i = end - 1 # When shifting a text, we wanna keep the static chars where they # are, and move the non-static chars to the right position. while start <= i < end: if isinstance(validators[i], int): # Non-static char shoud be here. Get the next one (depending # on the direction, and the number of positions to skip.) # # When shifting left, the next char will be on the right, # so, it will be appended, to the new text. # Otherwise, when shifting right, the char will be # prepended. next_pos = self._get_next_non_static_char_pos(i, direction, positions-1) # If its outside the bounds of the region, ignore it. if not start <= next_pos <= end: next_pos = None if next_pos is not None: if direction == Direction.LEFT: new_text = new_text + text[next_pos] else: new_text = text[next_pos] + new_text else: if direction == Direction.LEFT: new_text = new_text + ' ' else: new_text = ' ' + new_text else: # Keep the static char where it is. if direction == Direction.LEFT: new_text = new_text + text[i] else: new_text = text[i] + new_text i += direction return new_text def _get_next_non_static_char_pos(self, pos, direction=Direction.LEFT, skip=0): """ Get next non-static char position, skiping some chars, if necessary. @param skip: skip first n chars @param direction: direction of the search. """ text = self.get_text() validators = self._mask_validators i = pos+direction+skip while 0 <= i < len(text): if isinstance(validators[i], int): return i i += direction return None def _get_field_at_pos(self, pos, dir=None): """ Return the field index at position pos. """ for p in self._mask_fields: if p[0] <= pos <= p[1]: return self._mask_fields.index(p) return None def set_exact_completion(self, value): """ Enable exact entry completion. Exact means it needs to start with the value typed and the case needs to be correct. @param value: enable exact completion @type value: boolean """ if value: match_func = self._completion_exact_match_func else: match_func = self._completion_normal_match_func completion = self._get_completion() completion.set_match_func(match_func) def is_empty(self): text = self.get_text() if self._mask: empty = self.get_empty_mask() else: empty = '' return text == empty # Private def _really_delete_text(self, start, end): # A variant of delete_text() that never is blocked by us self._block_delete = True self.delete_text(start, end) self._block_delete = False def _really_insert_text(self, text, position): # A variant of insert_text() that never is blocked by us self._block_insert = True self.insert_text(text, position) self._block_insert = False def _insert_mask(self, start, end): text = self.get_empty_mask(start, end) self._really_insert_text(text, position=start) def _confirms_to_mask(self, position, text): validators = self._mask_validators if position < 0 or position >= len(validators): return False validator = validators[position] if isinstance(validator, int): if not INPUT_CHAR_MAP[validator](text): return False if isinstance(validator, unicode): if validator == text: return True return False return True def _update_current_object(self, text): if self._mode != ENTRY_MODE_DATA: return for row in self.get_completion().get_model(): if row[COL_TEXT] == text: self._current_object = row[COL_OBJECT] break else: # Customized validation if text: self.set_invalid(_("'%s' is not a valid object" % text)) elif self.mandatory: self.set_blank() else: self.set_valid() self._current_object = None def _get_text_from_object(self, obj): if self._mode != ENTRY_MODE_DATA: return for row in self.get_completion().get_model(): if row[COL_OBJECT] == obj: return row[COL_TEXT] def _get_completion(self): # Check so we have completion enabled, not this does not # depend on the property, the user can manually override it, # as long as there is a completion object set completion = self.get_completion() if completion: return completion completion = gtk.EntryCompletion() self.set_completion(completion) return completion def get_completion(self): return self._completion def set_completion(self, completion): if not isinstance(completion, KiwiEntryCompletion): gtk.Entry.set_completion(self, completion) completion.set_model(gtk.ListStore(str, object)) completion.set_text_column(0) self._completion = gtk.Entry.get_completion(self) return old = self.get_completion() if old == completion: return completion if old and isinstance(old, KiwiEntryCompletion): if old.completion_timeout: gobject.source_remove(old.completion_timeout) old.completion_timeout = 0 old._disconnect_completion_signals() self._completion = completion # First, tell the completion what entry it will complete completion.set_entry(self) completion.set_model(gtk.ListStore(str, object)) completion.set_text_column(0) self.set_exact_completion(False) completion.connect("match-selected", self._on_completion__match_selected) self._current_object = None return completion def _completion_exact_match_func(self, completion, key, iter): model = completion.get_model() if not len(model): return content = model[iter][COL_TEXT] return key.startswith(content) def _completion_normal_match_func(self, completion, key, iter): model = completion.get_model() if not len(model): return raw_content = model[iter][COL_TEXT] if raw_content is not None: return key.lower() in raw_content.lower() else: return False def _on_completion__match_selected(self, completion, model, iter): if not len(model): return # this updates current_object and triggers content-changed self.set_text(model[iter][COL_TEXT]) self.set_position(-1) # FIXME: Enable this at some point #self.activate() def _appers_later(self, char, start): """ Check if a char appers later on the mask. If it does, return the field it appers at. returns False otherwise. """ validators = self._mask_validators i = start while i < len(validators): if self._mask_validators[i] == char: field = self._get_field_at_pos(i) if field is None: return False return field i += 1 return False def _can_insert_at_pos(self, new, pos): """ Check if a chararcter can be inserted at some position @param new: The char that wants to be inserted. @param pos: The position where it wants to be inserted. @return: Returns None if it can be inserted. If it cannot be, return the next position where it can be successfuly inserted. """ validators = self._mask_validators # Do not let insert if the field is full field = self._get_field_at_pos(pos) if field is not None: text = self.get_field_text(field) length = self.get_field_length(field) if len(text) == length: gtk.gdk.beep() return pos # If the char confirms to the mask, but is a static char, return the # position after that static char. if (self._confirms_to_mask(pos, new) and not isinstance(validators[pos], int)): return pos+1 # If does not confirms to mask: # - Check if the char the user just tried to enter appers later. # - If it does, Jump to the start of the field after that if not self._confirms_to_mask(pos, new): field = self._appers_later(new, pos) if field is not False: pos = self.get_field_pos(field+1) if pos is not None: gobject.idle_add(self.set_position, pos) return pos return None def _insert_at_pos(self, text, new, pos): """ Inserts the character at the give position in text. Note that the insertion won't be applied to the entry, but to the text provided. @param text: Text that it will be inserted into. @param new: New text to insert. @param pos: Positon to insert at @return: Returns a tuple, with the position after the insetion and the new text. """ field = self._get_field_at_pos(pos) length = len(new) new_pos = pos start, end = self._mask_fields[field] # Shift Right new_text = (text[:pos] + new + self._shift_text(pos, end, Direction.RIGHT)[1:] + text[end:]) # Overwrite Right # new_text = (text[:pos] + new + # text[pos+length:end]+ # text[end:]) new_pos = pos+1 gobject.idle_add(self.set_position, new_pos) # If the field is full, jump to the next field if len(self.get_field_text(field)) == self.get_field_length(field)-1: gobject.idle_add(self.set_field, field+1, True) self.set_field(field+1) return new_pos, new_text # Callbacks def _on_insert_text(self, editable, new, length, position): if not self._mask or self._block_insert: return new = unicode(new) pos = self.get_position() self.stop_emission('insert-text') text = self.get_text() # Insert one char at a time for c in new: _pos = self._can_insert_at_pos(c, pos) if _pos is None: pos, text = self._insert_at_pos(text, c, pos) else: pos = _pos # Change the text with the new text. self._block_changed = True self._really_delete_text(0, -1) self._block_changed = False self._really_insert_text(text, 0) def _on_delete_text(self, editable, start, end): if not self._mask or self._block_delete: return self.stop_emission('delete-text') pos = self.get_position() # Trying to delete an static char. Delete the char before that if (0 < start < len(self._mask_validators) and not isinstance(self._mask_validators[start], int) and pos != start): self._on_delete_text(editable, start-1, start) return # we just tried to delete, stop the selection. self._selecting = False field = self._get_field_at_pos(end-1) # Outside a field. Cannot delete. if field is None: self.set_position(end-1) return _start, _end = self._mask_fields[field] # Deleting from outside the bounds of the field. if start < _start or end > _end: _start, _end = start, end # Change the text text = self.get_text() # Shift Left new_text = (text[:start] + self._shift_text(start, _end, Direction.LEFT, end-start) + text[_end:]) # Overwrite Left # empty_mask = self.get_empty_mask() # new_text = (text[:_start] + # text[_start:start] + # empty_mask[start:start+(end-start)] + # text[start+(end-start):_end] + # text[_end:]) new_pos = start self._block_changed = True self._really_delete_text(0, -1) self._block_changed = False self._really_insert_text(new_text, 0) # Position the cursor on the right place. self.set_position(new_pos) if pos == new_pos: self._handle_position_change() def _after_grab_focus(self, widget): # The text is selectet in grab-focus, so this needs to be done after # that: if self.is_empty(): if self._mask: self.set_field(0) else: self.set_position(0) def _on_focus(self, widget, direction): if not self._mask: return if (direction == gtk.DIR_TAB_FORWARD or direction == gtk.DIR_DOWN): inc = 1 if (direction == gtk.DIR_TAB_BACKWARD or direction == gtk.DIR_UP): inc = -1 field = self._current_field field += inc # Leaving the entry if field == len(self._mask_fields) or field == -1: self.select_region(0, 0) self._current_field = -1 return False if field < 0: field = len(self._mask_fields)-1 # grab_focus changes the selection, so we need to grab_focus before # making the selection. self.grab_focus() self.set_field(field, select=True) return True def _on_notify_selection_bound(self, widget, pspec): if not self._mask: return if not self.is_focus(): return if self._selecting: return self._handle_position_change() def _handle_position_change(self): pos = self.get_position() field = self._get_field_at_pos(pos) # Humm, the pos is not inside any field. Get the next pos inside # some field, depending on the direction that the cursor is # moving diff = pos - self._pos # but move only one position at a time. if diff: diff /= abs(diff) _field = field if diff: while _field is None and pos >= 0: pos += diff _field = self._get_field_at_pos(pos) self._pos = pos if pos < 0: self._pos = self.get_field_pos(0) if field is None: self.set_position(self._pos) else: self._current_field = field self._pos = pos def _on_changed(self, widget): if self._block_changed: self.stop_emission('changed') def _on_focus_out_event(self, widget, event): if not self._mask: return self._current_field = -1 def _on_move_cursor(self, entry, step, count, extend_selection): self._selecting = extend_selection # IconEntry def set_tooltip(self, text): self._icon.set_tooltip(text) def set_pixbuf(self, pixbuf): self._icon.set_pixbuf(pixbuf) def update_background(self, color): self._icon.update_background(color) def get_background(self): return self._icon.get_background() def get_icon_window(self): return self._icon.get_icon_window() # IComboMixin def prefill(self, itemdata, sort=False): """ See L{kiwi.interfaces.IEasyCombo.prefill} """ if not isinstance(itemdata, (list, tuple)): raise TypeError("'data' parameter must be a list or tuple of item " "descriptions, found %s") % type(itemdata) completion = self._get_completion() model = completion.get_model() if len(itemdata) == 0: model.clear() return if (len(itemdata) > 0 and type(itemdata[0]) in (tuple, list) and len(itemdata[0]) == 2): mode = self._mode = ENTRY_MODE_DATA else: mode = self._mode values = set() if mode == ENTRY_MODE_TEXT: if sort: itemdata.sort() for item in itemdata: if item in values: raise KeyError("Tried to insert duplicate value " "%r into the entry" % item) else: values.add(item) model.append((item, None)) elif mode == ENTRY_MODE_DATA: if sort: itemdata.sort(lambda x, y: cmp(x[0], y[0])) for item in itemdata: text, data = item # Add (n) to the end in case of duplicates count = 1 orig = text while text in values: text = orig + ' (%d)' % count count += 1 values.add(text) model.append((text, data)) else: raise TypeError("Incorrect format for itemdata; see " "docstring for more information") def get_iter_by_data(self, data): if self._mode != ENTRY_MODE_DATA: raise TypeError( "select_item_by_data can only be used in data mode") completion = self._get_completion() model = completion.get_model() for row in model: if row[COL_OBJECT] == data: return row.iter break else: raise KeyError("No item correspond to data %r in the combo %s" % (data, self.name)) def get_iter_by_label(self, label): completion = self._get_completion() model = completion.get_model() for row in model: if row[COL_TEXT] == label: return row.iter else: raise KeyError("No item correspond to label %r in the combo %s" % (label, self.name)) def get_selected_by_iter(self, treeiter): completion = self._get_completion() model = completion.get_model() mode = self._mode text = model[treeiter][COL_TEXT] if text != self.get_text(): return if mode == ENTRY_MODE_TEXT: return text elif mode == ENTRY_MODE_DATA: return model[treeiter][COL_OBJECT] else: raise AssertionError def get_selected_label(self, treeiter): completion = self._get_completion() model = completion.get_model() return model[treeiter][COL_TEXT] def get_selected_data(self, treeiter): completion = self._get_completion() model = completion.get_model() return model[treeiter][COL_OBJECT] def get_iter_from_obj(self, obj): mode = self._mode if mode == ENTRY_MODE_TEXT: return self.get_iter_by_label(obj) elif mode == ENTRY_MODE_DATA: return self.get_iter_by_data(obj) else: # XXX: When setting the datatype to non string, automatically go to # data mode raise TypeError("unknown Entry mode. Did you call prefill?") def get_mode(self): return self._mode type_register(KiwiEntry) def main(args): win = gtk.Window() win.set_title('gtk.Entry subclass') def cb(window, event): print 'fields', widget.get_field_text() gtk.main_quit() win.connect('delete-event', cb) widget = KiwiEntry() widget.set_mask('000.000.000.000') win.add(widget) win.show_all() widget.select_region(0, 0) gtk.main() if __name__ == '__main__': import sys sys.exit(main(sys.argv)) PIDA-0.5.1/contrib/kiwi/kiwi/ui/entrycompletion.py0000644000175000017500000001654210652670746020143 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Ronaldo Maia # import gobject import gtk from gtk import keysyms from kiwi.utils import gsignal, type_register COMPLETION_TIMEOUT = 300 PAGE_INCREMENT = 14 class KiwiEntryCompletion(gtk.EntryCompletion): def __init__(self): gtk.EntryCompletion.__init__(self) self._inline_completion = False self._popup_completion = True self._entry = None self._completion_timeout = 0 self._match_function = None self._match_function_data = None self._key = None self.changed_id = 0 self._filter_model = None self._treeview = None self._popup_window = None self._selected_index = -1 gsignal('match-selected', 'override') def do_match_selected(self, model, iter): self._entry.set_text(model[iter][0]) return True def _visible_function(self, model, iter, data=None): if not self._entry: return False if not self._key: return False if self._match_function: return self._match_function(self, self._key, iter) value = model[iter][0] if not value: return False entry_text = self._entry.get_text() return value.lower().startswith(entry_text.lower()) def _connect_completion_signals(self): if self._popup_completion: self.changed_id = self._entry.connect('changed', self._on_completion_changed) self._entry.connect('key-press-event', self._on_completion_key_press) def _on_completion_timeout(self): if self._completion_timeout: gobject.source_remove(self._completion_timeout) self._completion_timeout = 0 minimum_key_length = self.get_property('minimum-key-length') if (self._filter_model and len(self._entry.get_text()) >= minimum_key_length and self._entry.is_focus()): self.complete() matches = self._filter_model.iter_n_children(None) if matches: self.popup() return False def _on_completion_changed(self, entry): if (self.get_property('minimum_key_length') > 0 and not self._entry.get_text()): self.popdown() return self._selected_index = -1 timeout = gobject.timeout_add(COMPLETION_TIMEOUT, self._on_completion_timeout) self._completion_timeout = timeout return True def _select_item(self, index): # Make the selection matches = self._filter_model.iter_n_children(None) if 0 <= index < matches: self._treeview.set_cursor((index,)) else: selection = self._treeview.get_selection() selection.unselect_all() self._selected_index = index def _on_completion_key_press(self, entry, event): window = self._popup_window if window and not window.flags() & gtk.VISIBLE: return False if not self._treeview: return False matches = self._filter_model.iter_n_children(None) keyval = event.keyval index = self._selected_index if keyval == keysyms.Up or keyval == keysyms.KP_Up: index -= 1 if index < -1: index = matches -1 self._select_item(index) return True elif keyval == keysyms.Down or keyval == keysyms.KP_Down: index += 1 if index > matches-1: index = -1 self._select_item(index) return True elif keyval == keysyms.Page_Up: if index < 0: index = matches-1 elif index > 0 and index - PAGE_INCREMENT < 0: index = 0 else: index -= PAGE_INCREMENT if index < 0: index = -1 self._select_item(index) return True elif keyval == keysyms.Page_Down: if index < 0: index = 0 elif index < matches-1 and index + PAGE_INCREMENT > matches - 1: index = matches -1 else: index += PAGE_INCREMENT if index > matches: index = -1 self._select_item(index) return True elif keyval == keysyms.Escape: self.popdown() return True elif (keyval == keysyms.Return or keyval == keysyms.KP_Enter): self.popdown() selection = self._treeview.get_selection() model, titer = selection.get_selected() if not titer: return False self._entry.handler_block(self.changed_id) self.emit('match-selected', model, titer) self._entry.handler_unblock(self.changed_id) selection.unselect_all() return True return False # Public API def complete(self): if not self._filter_model: return self._key = self._entry.get_text() self._filter_model.refilter() self._treeview.set_model(self._filter_model) if self._treeview.flags() & gtk.REALIZED: self._treeview.scroll_to_point(0,0) def set_entry(self, entry): self._entry = entry self._connect_completion_signals() def get_entry(self): return self._entry def set_popup_window(self, window): self._popup_window = window def set_treeview(self, treeview): self._treeview = treeview def popup(self): if not self._popup_window: return self._popup_window.popup(text=None, filter=True) def popdown(self): if not self._popup_window: return self._popup_window.popdown() def set_model(self, model): if not model: if self._popup_window: self._popup_window.set_model(None) self.popdown() self._model = None self._filter_model = None return self._model = model self._filter_model = model.filter_new() self._filter_model.set_visible_func(self._visible_function) if self._popup_window: self._popup_window.set_model(self._filter_model) def get_model(self): return self._model def set_match_func(self, function, data=None): self._match_function = function self._match_function_data = data type_register(KiwiEntryCompletion) PIDA-0.5.1/contrib/kiwi/kiwi/ui/gadgets.py0000644000175000017500000001440010652670746016315 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Johan Dahlin # """Graphical utilities: color management and eyecandy""" import gobject import gtk from gtk import gdk from kiwi.log import Logger from kiwi.utils import gsignal, type_register def gdk_color_to_string(color): """Convert a color to a #AABBCC string""" return "#%02X%02X%02X" % (int(color.red) >> 8, int(color.green) >> 8, int(color.blue) >> 8) def set_foreground(widget, color, state=gtk.STATE_NORMAL): """ Set the foreground color of a widget: - widget: the widget we are changing the color - color: a hexadecimal code or a well known color name - state: the state we are afecting, see gtk.STATE_* """ widget.modify_fg(state, gdk.color_parse(color)) def get_foreground(widget, state=gtk.STATE_NORMAL): """Return the foreground color of the widget as a string""" style = widget.get_style() color = style.fg[state] return gdk_color_to_string(color) def set_background(widget, color, state=gtk.STATE_NORMAL): """ Set the background color of a widget: - widget: the widget we are changing the color - color: a hexadecimal code or a well known color name - state: the state we are afecting, see gtk.STATE_* """ if isinstance(widget, gtk.Entry): widget.modify_base(state, gdk.color_parse(color)) else: widget.modify_bg(state, gdk.color_parse(color)) def get_background(widget, state=gtk.STATE_NORMAL): """Return the background color of the widget as a string""" style = widget.get_style() color = style.bg[state] return gdk_color_to_string(color) def quit_if_last(*args): windows = [toplevel for toplevel in gtk.window_list_toplevels() if toplevel.get_property('type') == gtk.WINDOW_TOPLEVEL] if len(windows) == 1: gtk.main_quit() class FadeOut(gobject.GObject): """I am a helper class to draw the fading effect of the background Call my methods start() and stop() to control the fading. """ gsignal('done') gsignal('color-changed', gdk.Color) # How long time it'll take before we start (in ms) COMPLAIN_DELAY = 500 MERGE_COLORS_DELAY = 100 ERROR_COLOR = "#ffd5d5" def __init__(self, widget): gobject.GObject.__init__(self) self._widget = widget self._start_color = None self._background_timeout_id = -1 self._countdown_timeout_id = -1 self._log = Logger('fade') self._done = False def _merge_colors(self, src_color, dst_color, steps=10): """ Change the background of widget from src_color to dst_color in the number of steps specified """ self._log.debug('_merge_colors: %s -> %s' % (src_color, dst_color)) if not src_color: yield False rs, gs, bs = src_color.red, src_color.green, src_color.blue rd, gd, bd = dst_color.red, dst_color.green, dst_color.blue rinc = (rd - rs) / float(steps) ginc = (gd - gs) / float(steps) binc = (bd - bs) / float(steps) for dummy in xrange(steps): rs += rinc gs += ginc bs += binc col = gdk.color_parse("#%02X%02X%02X" % (int(rs) >> 8, int(gs) >> 8, int(bs) >> 8)) self.emit('color-changed', col) yield True self.emit('done') self._background_timeout_id = -1 self._done = True yield False def _start_merging(self): # If we changed during the delay if self._background_timeout_id != -1: self._log.debug('_start_merging: Already running') return self._log.debug('_start_merging: Starting') func = self._merge_colors(self._start_color, gdk.color_parse(FadeOut.ERROR_COLOR)).next self._background_timeout_id = ( gobject.timeout_add(FadeOut.MERGE_COLORS_DELAY, func)) self._countdown_timeout_id = -1 def start(self, color): """Schedules a start of the countdown. @param color: initial background color @returns: True if we could start, False if was already in progress """ if self._background_timeout_id != -1: self._log.debug('start: Background change already running') return False if self._countdown_timeout_id != -1: self._log.debug('start: Countdown already running') return False if self._done: self._log.debug('start: Not running, already set') return False self._start_color = color self._log.debug('start: Scheduling') self._countdown_timeout_id = gobject.timeout_add( FadeOut.COMPLAIN_DELAY, self._start_merging) return True def stop(self): """Stops the fadeout and restores the background color""" self._log.debug('Stopping') if self._background_timeout_id != -1: gobject.source_remove(self._background_timeout_id) self._background_timeout_id = -1 if self._countdown_timeout_id != -1: gobject.source_remove(self._countdown_timeout_id) self._countdown_timeout_id = -1 self._widget.update_background(self._start_color) self._done = False type_register(FadeOut) PIDA-0.5.1/contrib/kiwi/kiwi/ui/gaxmlloader.py0000644000175000017500000000441710652670746017205 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # from gaxml.parser import GAXParser from kiwi.log import Logger log = Logger('gaxmlparser') class GAXMLWidgetTree(object): def __init__(self, view, gladefile, domain=None): self._view = view self._gladefile = gladefile self._parser = GAXParser() self._parser.parse_file(gladefile) self._widgets = [o.get_name() for o in self._parser.get_objects()] self._attach_widgets() def _attach_widgets(self): # Attach widgets in the widgetlist to the view specified, so # widgets = [label1, button1] -> view.label1, view.button1 for w in self._widgets: widget = self._parser.get_object(name=w) if widget is not None: setattr(self._view, w, widget) else: log.warn("Widget %s was not found in glade widget tree." % w) def get_widget(self, name): """Retrieves the named widget from the View (or glade tree)""" if name.endswith('_ui'): name = name[:-3] name = name.replace('.', '_') widget = self._parser.get_object(name=name) if widget is None: raise AttributeError( "Widget %s not found in view %s" % (name, self._view)) return widget def get_widgets(self): return self._parser.get_objects() def get_sizegroups(self): return [] def signal_autoconnect(self, signals): pass PIDA-0.5.1/contrib/kiwi/kiwi/ui/gazpacholoader.py0000644000175000017500000002741710652670746017676 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Johan Dahlin # """Gazpacho integration: loader and extensions""" import gettext import warnings import gobject import gtk try: from gazpacho.propertyeditor import PropertyCustomEditor PropertyCustomEditor # pyflakes except ImportError: from gazpacho.editor import PropertyCustomEditor from gazpacho.loader.loader import ObjectBuilder from gazpacho.loader.custom import Adapter, ComboBoxAdapter, \ PythonWidgetAdapter, adapter_registry from gazpacho.properties import prop_registry, CustomProperty, StringType from gazpacho.widgets.base.base import ContainerAdaptor from gazpacho.widgets.base.box import BoxAdaptor from kiwi.datatypes import converter from kiwi.environ import environ from kiwi.log import Logger from kiwi.python import disabledeprecationcall from kiwi.ui.hyperlink import HyperLink from kiwi.ui.objectlist import Column, ObjectList, ObjectTree from kiwi.ui.widgets.button import ProxyButton from kiwi.ui.widgets.checkbutton import ProxyCheckButton from kiwi.ui.widgets.combo import ProxyComboEntry, ProxyComboBox, \ ProxyComboBoxEntry from kiwi.ui.widgets.entry import ProxyDateEntry, ProxyEntry from kiwi.ui.widgets.label import ProxyLabel from kiwi.ui.widgets.radiobutton import ProxyRadioButton from kiwi.ui.widgets.spinbutton import ProxySpinButton from kiwi.ui.widgets.textview import ProxyTextView # Backwards compatibility + pyflakes from kiwi.ui.widgets.combobox import ComboBox, ComboBoxEntry from kiwi.ui.widgets.list import List HyperLink _ = lambda m: gettext.dgettext('kiwi', m) log = Logger('gazpacholoader') class Builder(ObjectBuilder): def find_resource(self, filename): return environ.find_resource("pixmaps", filename) class GazpachoWidgetTree: """Example class of GladeAdaptor that uses Gazpacho loader to load the glade files """ def __init__(self, view, gladefile, domain=None): self._view = view self._gladefile = gladefile self._showwarning = warnings.showwarning warnings.showwarning = self._on_load_warning self._tree = Builder(gladefile, domain=domain) warnings.showwarning = self._showwarning self._widgets = [w.get_data("gazpacho::object-id") for w in self._tree.get_widgets()] self._attach_widgets() def _on_load_warning(self, warning, category, file, line): self._showwarning('while loading glade file: %s' % warning, category, self._gladefile, '???') def _attach_widgets(self): # Attach widgets in the widgetlist to the view specified, so # widgets = [label1, button1] -> view.label1, view.button1 for w in self._widgets: widget = self._tree.get_widget(w) if widget is not None: setattr(self._view, w, widget) else: log.warn("Widget %s was not found in glade widget tree." % w) def get_widget(self, name): """Retrieves the named widget from the View (or glade tree)""" name = name.replace('.', '_') name = name.replace('-', '_') widget = self._tree.get_widget(name) if widget is None: raise AttributeError( "Widget %s not found in view %s" % (name, self._view)) return widget def get_widgets(self): return self._tree.get_widgets() def signal_autoconnect(self, dic): self._tree.signal_autoconnect(dic) def get_sizegroups(self): return self._tree.sizegroups # Normal widgets for prop in ('normal-color', 'normal-underline', 'normal-bold', 'hover-color', 'hover-underline', 'hover-bold', 'active-color', 'active-underline', 'active-bold'): prop_registry.override_simple('HyperLink::%s' % prop, editable=False) class HyperLinkAdaptor(ContainerAdaptor): def fill_empty(self, context, widget): pass def post_create(self, context, widget, interactive): widget.set_text(widget.get_name()) class ComboEntryAdaptor(BoxAdaptor): def get_children(self, context, comboentry): return [] class DateEntryAdaptor(BoxAdaptor): def get_children(self, context, comboentry): return [] class KiwiColumnAdapter(Adapter): object_type = Column def construct(self, name, gtype, properties): return Column(name) adapter_registry.register_adapter(KiwiColumnAdapter) class ObjectListAdapter(PythonWidgetAdapter): object_type = ObjectList def construct(self, name, gtype, properties): if gtype == List: gtype == ObjectList return super(ObjectListAdapter, self).construct(name, gtype, properties) adapter_registry.register_adapter(ObjectListAdapter) class ObjectTreeAdapter(PythonWidgetAdapter): object_type = ObjectTree adapter_registry.register_adapter(ObjectTreeAdapter) # Framework widgets class DataTypeAdaptor(PropertyCustomEditor): widget_type = None default = str def __init__(self): super(DataTypeAdaptor, self).__init__() self._model = None self._input = self.create_editor() def get_editor_widget(self): return self._input def _get_converters(self): if not self.widget_type: AssertionError("%r should define a widget_type" % self) allowed = self.widget_type.allowed_data_types return converter.get_converters(allowed) def create_editor(self): self._model = gtk.ListStore(str, object) combo = gtk.ComboBox(self._model) renderer = gtk.CellRendererText() combo.pack_start(renderer) combo.add_attribute(renderer, 'text', 0) combo.set_active(0) combo.set_data('connection-id', -1) return combo def update(self, context, kiwiwidget, proxy): combo = self._input model = self._model model.clear() for converter in self._get_converters(): model.append((converter.name, converter.type)) connection_id = combo.get_data('connection-id') if (connection_id != -1): combo.disconnect(connection_id) model = combo.get_model() connection_id = combo.connect('changed', self._editor_edit, proxy, model) combo.set_data('connection-id', connection_id) value = kiwiwidget.get_property('data-type') if not value: value = self.default for row in model: if row[1] == value: combo.set_active_iter(row.iter) break def _editor_edit(self, combo, proxy, model): active_iter = combo.get_active_iter() proxy.set_value(model[active_iter][1]) class SpinBtnDataType(DataTypeAdaptor): widget_type = ProxySpinButton default = int class EntryDataType(DataTypeAdaptor): widget_type = ProxyEntry default = str class TextViewDataType(DataTypeAdaptor): widget_type = ProxyTextView default = str class ComboBoxDataType(DataTypeAdaptor): widget_type = ProxyComboBox default = object class ComboBoxEntryDataType(DataTypeAdaptor): widget_type = ProxyComboBoxEntry default = object class ComboEntryDataType(DataTypeAdaptor): widget_type = ProxyComboEntry default = object class LabelDataType(DataTypeAdaptor): widget_type = ProxyLabel default = str class ButtonDataType(DataTypeAdaptor): widget_type = ProxyButton default = str class DataType(CustomProperty, StringType): translatable = False def save(self): value = self.get() if value is not None: return value.__name__ class BoolOnlyDataType(CustomProperty, StringType): translatable = False editable = False def save(self): return 'bool' class DateOnlyDataType(CustomProperty, StringType): translatable = False editable = False def save(self): return 'date' class ModelProperty(CustomProperty, StringType): translatable = False has_custom_default = True def __init__(self, gadget): super(ModelProperty, self).__init__(gadget) gadget.widget.connect('notify::name', self._on_widget__notify) def _on_widget__notify(self, widget, pspec): self.set(widget.get_name()) self.notify() def default(self): return self.gadget.widget.get_name() default = property(default) class DataValueProperty(CustomProperty, StringType): translatable = False # Register widgets which have data-type and model-attributes # ComboBox is a special case, it needs to inherit from another # adapter and need to support two types. class KiwiComboBoxAdapter(ComboBoxAdapter): object_type = ProxyComboBox, ProxyComboBoxEntry def construct(self, name, gtype, properties): if gtype in (ProxyComboBox.__gtype__, ComboBox.__gtype__): object_type = ProxyComboBox elif gtype in (ProxyComboBoxEntry.__gtype__, ComboBoxEntry.__gtype__): object_type = ProxyComboBoxEntry else: raise AssertionError("Unknown ComboBox GType: %r" % gtype) obj = disabledeprecationcall(object_type) obj.set_name(name) return obj adapter_registry.register_adapter(KiwiComboBoxAdapter) def register_widgets(): for gobj, editor, data_type in [ (ProxyEntry, EntryDataType, DataType), (ProxyDateEntry, None, DateOnlyDataType), (ProxyButton, ButtonDataType, DataType), (ProxyCheckButton, None, BoolOnlyDataType), (ProxyLabel, LabelDataType, DataType), (ProxyComboBox, ComboBoxDataType, DataType), (ProxyComboBoxEntry, ComboBoxEntryDataType, DataType), (ProxyComboEntry, ComboEntryDataType, DataType), (ProxySpinButton, SpinBtnDataType, DataType), (ProxyRadioButton, None, BoolOnlyDataType), (ProxyTextView, TextViewDataType, DataType) ]: # Property overrides, used in the editor type_name = gobject.type_name(gobj) data_name = type_name + '::data-type' if editor: prop_registry.override_simple(data_name, data_type, custom_editor=editor) else: prop_registry.override_simple(data_name, data_type) prop_registry.override_simple(type_name + '::model-attribute', ModelProperty) if issubclass(gobj, ProxyRadioButton): prop_registry.override_simple(type_name + '::data-value', DataValueProperty) # Register custom adapters, since gobject.new is broken in 2.6 # Used by loader, eg in gazpacho and in applications # ComboBox is registered above if gobj == ProxyComboBox: continue adapter_name = 'Kiwi%sAdapter' % gobj.__name__ klass = type(adapter_name, (PythonWidgetAdapter,), dict(object_type=gobj, __name__=adapter_name)) adapter_registry.register_adapter(klass) if not environ.epydoc: register_widgets() PIDA-0.5.1/contrib/kiwi/kiwi/ui/hyperlink.py0000644000175000017500000001761510652670746016717 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): (C) Ali Afshar # # Contact Ali if you require release under a different license. """A hyper link widget.""" from cgi import escape import gtk from kiwi.utils import gsignal, gproperty, PropertyObject, type_register class HyperLink(PropertyObject, gtk.EventBox): __gtype_name__ = 'HyperLink' """ A hyperlink widget. This widget behaves much like a hyperlink from a browser. The markup that will be displayed is contained in the properties normal-markup hover-markup and active-markup. There is a clicked signal which is fired when hyperlink is clicked with the left mouse button. Additionally, the user may set a menu that will be popped up when the user right clicks the hyperlink. """ gproperty('text', str, '') gproperty('normal-color', str, '#0000c0') gproperty('normal-underline', bool, False) gproperty('normal-bold', bool, False) gproperty('hover-color', str, '#0000c0') gproperty('hover-underline', bool, True) gproperty('hover-bold', bool, False) gproperty('active-color', str, '#c00000') gproperty('active-underline', bool, True) gproperty('active-bold', bool, False) gsignal('clicked') gsignal('right-clicked') def __init__(self, text=None, menu=None): """ Create a new hyperlink. @param text: The text of the hyperlink. @type text: str """ gtk.EventBox.__init__(self) self.set_above_child(False) self.set_visible_window(False) PropertyObject.__init__(self) self._gproperties = {} if text is not None: self.set_property('text', text) self._is_active = False self._is_hover = False self._menu = menu self._label = gtk.Label() self.add(self._label) self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK) self.connect('button-press-event', self._on_button_press_event) self.connect('button-release-event', self._on_button_release_event) self.connect('enter-notify-event', self._on_hover_changed, True) self.connect('leave-notify-event', self._on_hover_changed, False) self.connect('map-event', self._on_map_event) self.connect('notify', self._on_notify) self.set_text(text) # public API def get_text(self): """ Return the hyperlink text. """ return self.text def set_text(self, text): """ Set the text of the hyperlink. @param text: The text to set the hyperlink to. @type text: str """ self.text = text self._update_look() def set_menu(self, menu): """ Set the menu to be used for popups. @param menu: the gtk.Menu to be used. @type menu: gtk.Menu """ self._menu = menu def has_menu(self): """ Return whether the widget has a menu set. @return: a boolean value indicating whether the internal menu has been set. """ return self._menu is not None def popup(self, menu=None, button=3, etime=0L): """ Popup the menu and emit the popup signal. @param menu: The gtk.Menu to be popped up. This menu will be used instead of the internally set menu. If this parameter is not passed or None, the internal menu will be used. @type menu: gtk.Menu @param button: An integer representing the button number pressed to cause the popup action. @type button: int @param etime: The time that the popup event was initiated. @type etime: long """ if menu is None: menu = self._menu if menu is not None: menu.popup(None, None, None, button, etime) self.emit('right-clicked') def clicked(self): """ Fire a clicked signal. """ self.emit('clicked') def get_label(self): """ Get the internally stored widget. """ return self._label # private API def _update_look(self): """ Update the look of the hyperlink depending on state. """ if self._is_active: state = 'active' elif self._is_hover: state = 'hover' else: state = 'normal' color = self.get_property('%s-color' % state) underline = self.get_property('%s-underline' % state) bold = self.get_property('%s-bold' % state) markup_string = self._build_markup(self.get_text() or '', color, underline, bold) self._label.set_markup(markup_string) def _build_markup(self, text, color, underline, bold): """ Build a marked up string depending on parameters. """ out = '%s' % (color, escape(text)) if underline: out = '%s' % out if bold: out = '%s' % out return out # signal callbacks def _on_button_press_event(self, eventbox, event): """ Called on mouse down. Behaves in 2 ways. 1. if left-button, register the start of a click and grab the mouse. 1. if right-button, emit a right-clicked signal +/- popup the menu. """ if event.button == 1: self.grab_add() self._is_active = True self._update_look() elif event.button == 3: if event.type == gtk.gdk.BUTTON_PRESS: self.popup(button=event.button, etime=event.time) def _on_button_release_event(self, eventbox, event): """ Called on mouse up. If the left-button is released and the widget was earlier activated by a mouse down event a clicked signal is fired. """ if event.button == 1: self.grab_remove() if self._is_active: self.clicked() self._is_active = False self._update_look() def _on_hover_changed(self, eb, event, hover): """ Called when the mouse pinter enters or leaves the widget. @param hover: Whether the mouse has entered the widget. @type hover: boolean """ self._is_hover = hover self._update_look() def _on_notify(self, eventbox, param): """ Called on property notification. Ensure that the look is up to date with the properties """ if (param.name == 'text' or param.name.endswith('-color') or param.name.endswith('-underline') or param.name.endswith('-bold')): self._update_look() def _on_map_event(self, eventbox, event): """ Called on initially mapping the widget. Used here to set the cursor type. """ cursor = gtk.gdk.Cursor(gtk.gdk.HAND1) self.window.set_cursor(cursor) type_register(HyperLink) PIDA-0.5.1/contrib/kiwi/kiwi/ui/icon.py0000644000175000017500000002431110652670746015631 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # # # This is tricky and contains quite a few hacks: # An entry contains 2 GdkWindows, one for the background and one for # the text area. The normal one, on which the (normally white) background # is drawn can be accessed through entry.window (after realization) # The other window is the one where the cursor and the text is drawn upon, # it's refered to as "text area" inside the GtkEntry code and it is called # the same here. It can only be accessed through window.get_children()[0], # since it's considered private to the entry. # # +-------------------------------------+ # | (1) | (1) parent widget (grey) # |+----------------(2)----------------+| # || |-- /-\ | || (2) entry.window (white) # || |- | | |(4) (3) || # || | \-/ | || (3) text area (transparent) # |+-----------------------------------+| # |-------------------------------------| (4) cursor, black # | | # +-------------------------------------| # # So, now we want to put an icon in the edge: # An earlier approached by Lorzeno drew the icon directly on the text area, # which is not desired since if the text is using the whole width of the # entry the icon will be drawn on top of the text. # Now what we want to do is to resize the text area and create a # new window upon which we can draw the icon. # # +-------------------------------------+ # | | (5) icon window # |+----------------------------++-----+| # || |-- /-\ | || || # || |- | | | || (5) || # || | \-/ | || || # |+----------------------------++-----+| # |-------------------------------------| # | | # +-------------------------------------+ # # When resizing the text area the cursor and text is not moved into the # correct position, it'll still be off by the width of the icon window # To fix this we need to call a private function, gtk_entry_recompute, # a workaround is to call set_visiblity() which calls recompute() # internally. # """Provides a helper classes for displaying icons in widgets. Currently only a L{kiwi.ui.widgets.entry.Entry} and widgets which embed them, L{kiwi.ui.widgets.spinbutton.SpinButton} and L{kiwi.ui.comboboxentry.BaseComboBoxEntry}. """ import gtk from gtk import gdk from kiwi.ui.tooltip import Tooltip class IconEntry(object): """ Helper object for rendering an icon in a GtkEntry """ def __init__(self, entry): if not isinstance(entry, gtk.Entry): raise TypeError("entry must be a gtk.Entry") self._constructed = False self._pixbuf = None self._pixw = 1 self._pixh = 1 self._text_area = None self._text_area_pos = (0, 0) self._icon_win = None self._entry = entry self._tooltip = Tooltip(self) self._locked = False entry.connect('enter-notify-event', self._on_entry__enter_notify_event) entry.connect('leave-notify-event', self._on_entry__leave_notify_event) entry.connect('notify::xalign', self._on_entry__notify_xalign) self._update_position() def _on_entry__notify_xalign(self, entry, pspec): self._update_position() def _on_entry__enter_notify_event(self, entry, event): icon_win = self.get_icon_window() if event.window != icon_win: return self._tooltip.display(entry) def _on_entry__leave_notify_event(self, entry, event): if event.window != self.get_icon_window(): return self._tooltip.hide() def set_tooltip(self, text): self._tooltip.set_text(text) def get_icon_window(self): return self._icon_win def set_pixbuf(self, pixbuf): """ @param pixbuf: a gdk.Pixbuf or None """ entry = self._entry if not isinstance(entry.get_toplevel(), gtk.Window): # For widgets in SlaveViews, wait until they're attached # to something visible, then set the pixbuf entry.connect_object('realize', self.set_pixbuf, pixbuf) return if pixbuf: if not isinstance(pixbuf, gdk.Pixbuf): raise TypeError("pixbuf must be a GdkPixbuf") else: # Turning of the icon should also restore the background entry.modify_base(gtk.STATE_NORMAL, None) if not self._pixbuf: return self._pixbuf = pixbuf if pixbuf: self._pixw = pixbuf.get_width() self._pixh = pixbuf.get_height() else: self._pixw = self._pixh = 0 win = self._icon_win if not win: self.construct() win = self._icon_win self.resize_windows() # XXX: Why? if win: if not pixbuf: win.hide() else: win.show() self._recompute() entry.queue_draw() def construct(self): if self._constructed: return entry = self._entry if not entry.flags() & gtk.REALIZED: entry.realize() # Hack: Save a reference to the text area, now when its created self._text_area = entry.window.get_children()[0] self._text_area_pos = self._text_area.get_position() # PyGTK should allow default values for most of the values here. win = gtk.gdk.Window(entry.window, self._pixw, self._pixh, gtk.gdk.WINDOW_CHILD, (gtk.gdk.ENTER_NOTIFY_MASK | gtk.gdk.LEAVE_NOTIFY_MASK), gtk.gdk.INPUT_OUTPUT, 'icon window', 0, 0, entry.get_visual(), entry.get_colormap(), gtk.gdk.Cursor(entry.get_display(), gdk.LEFT_PTR), '', '', True) self._icon_win = win win.set_user_data(entry) win.set_background(entry.style.base[entry.state]) self._constructed = True def deconstruct(self): if self._icon_win: # This is broken on PyGTK 2.6.x try: self._icon_win.set_user_data(None) except: pass # Destroy not needed, called by the GC. self._icon_win = None def update_background(self, color): if self._locked: return if not self._icon_win: return self._entry.modify_base(gtk.STATE_NORMAL, color) self.draw_pixbuf() def get_background(self): return self._entry.style.base[gtk.STATE_NORMAL] def resize_windows(self): if not self._pixbuf: return icony = iconx = 4 # Make space for the icon, both windows winw = self._entry.window.get_size()[0] textw, texth = self._text_area.get_size() textw = winw - self._pixw - (iconx + icony) if self._pos == gtk.POS_LEFT: textx, texty = self._text_area_pos textx += iconx + self._pixw # FIXME: Why is this needed. Focus padding? # The text jumps without this textw -= 2 self._text_area.move_resize(textx, texty, textw, texth) self._recompute() elif self._pos == gtk.POS_RIGHT: self._text_area.resize(textw, texth) iconx += textw icon_win = self._icon_win # XXX: Why? if not icon_win: return # If the size of the window is large enough, resize and move it # Otherwise just move it to the right side of the entry if icon_win.get_size() != (self._pixw, self._pixh): icon_win.move_resize(iconx, icony, self._pixw, self._pixh) else: icon_win.move(iconx, icony) def draw_pixbuf(self): if not self._pixbuf: return win = self._icon_win # XXX: Why? if not win: return # Draw background first color = self._entry.style.base_gc[self._entry.state] win.draw_rectangle(color, True, 0, 0, self._pixw, self._pixh) # If sensitive draw the icon, regardless of the window emitting the # event since makes it a bit smoother on resize if self._entry.flags() & gtk.SENSITIVE: win.draw_pixbuf(None, self._pixbuf, 0, 0, 0, 0, self._pixw, self._pixh) def _update_position(self): if self._entry.get_property('xalign') > 0.5: self._pos = gtk.POS_LEFT else: self._pos = gtk.POS_RIGHT def _recompute(self): # Protect against re-entrancy when inserting text, happens in DateEntry if self._locked: return self._locked = True # Hack: This triggers a .recompute() which is private visibility = self._entry.get_visibility() self._entry.set_visibility(not visibility) self._entry.set_visibility(visibility) # Another option would be to call insert_text, however it # emits the signal ::changed which is not desirable. #self._entry.insert_text('') self._locked = False PIDA-0.5.1/contrib/kiwi/kiwi/ui/libgladeloader.py0000644000175000017500000000415010652670746017632 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # from gtk.glade import XML from kiwi.log import Logger log = Logger('libgladeloader') class LibgladeWidgetTree(XML): def __init__(self, view, gladefile, domain=None): self._view = view self._gladefile = gladefile XML.__init__(self, gladefile, domain) self._widgets = [w.get_name() for w in self.get_widget_prefix('')] self._attach_widgets() def _attach_widgets(self): # Attach widgets in the widgetlist to the view specified, so # widgets = [label1, button1] -> view.label1, view.button1 for w in self._widgets: widget = XML.get_widget(self, w) if widget is not None: setattr(self._view, w, widget) else: log.warn("Widget %s was not found in glade widget tree." % w) def get_widget(self, name): """Retrieves the named widget from the View (or glade tree)""" name = name.replace('.', '_') widget = XML.get_widget(self, name) if widget is None: raise AttributeError( "Widget %s not found in view %s" % (name, self._view)) return widget def get_widgets(self): return self.get_widget_prefix('') def get_sizegroups(self): return [] PIDA-0.5.1/contrib/kiwi/kiwi/ui/listdialog.py0000644000175000017500000002635210652670746017043 0ustar aliali# # Copyright (C) 2007 by Async Open Source # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # Author(s): Johan Dahlin # """ A dialog to manipulate a sequence of objects """ import gettext import gobject import gtk from kiwi.enums import ListType from kiwi.ui.dialogs import yesno from kiwi.ui.objectlist import ObjectList from kiwi.utils import gsignal, quote _ = lambda m: gettext.dgettext('kiwi', m) class ListContainer(gtk.HBox): """ A ListContainer is an L{ObjectList} with buttons to be able to modify the content of the list. Depending on the list_mode, @see L{set_list_mode} you will have add, remove and edit buttons. Signals ======= - B{add-item} (returns item): - emitted when the add button is clicked, you're expected to return an object here - B{remove-item} (item, returns bool): - emitted when removing an item, you can block the removal from the list by returning False - B{edit-item} (item): - emitted when editing an item you can block the update afterwards by returning False @ivar add_button: add button @type add_button: L{gtk.Button} @ivar remove_button: remove button @type remove_button: L{gtk.Button} @ivar edit_button: edit button @type edit_button: L{gtk.Button} """ gsignal('add-item', retval=object) gsignal('remove-item', object, retval=bool) gsignal('edit-item', object, retval=bool) gsignal('selection-changed', object) def __init__(self, columns): """ @param columns: columns for the L{kiwi.ui.objectlist.ObjectList} @type columns: a list of L{kiwi.ui.objectlist.Columns} """ self._list_type = None gtk.HBox.__init__(self) self._create_ui(columns) self.set_list_type(ListType.NORMAL) # Private API def _create_ui(self, columns): self.list = ObjectList(columns) self.list.connect('selection-changed', self._on_list__selection_changed) self.pack_start(self.list) self.list.show() vbox = gtk.VBox(spacing=6) self.pack_start(vbox, expand=False, padding=6) vbox.show() self._vbox = vbox add_button = gtk.Button(stock=gtk.STOCK_ADD) add_button.connect('clicked', self._on_add_button__clicked) vbox.pack_start(add_button, expand=False) self.add_button = add_button remove_button = gtk.Button(stock=gtk.STOCK_REMOVE) remove_button.set_sensitive(False) remove_button.connect('clicked', self._on_remove_button__clicked) vbox.pack_start(remove_button, expand=False) self.remove_button = remove_button edit_button = gtk.Button(stock=gtk.STOCK_EDIT) edit_button.set_sensitive(False) edit_button.connect('clicked', self._on_edit_button__clicked) vbox.pack_start(edit_button, expand=False) self.edit_button = edit_button def _add_item(self): retval = self.emit('add-item') if retval is None: return elif isinstance(retval, NotImplementedError): raise retval self.list.append(retval) def _remove_item(self, item): retval = self.emit('remove-item', item) if retval: self.list.remove(item) def _edit_item(self, item): retval = self.emit('edit-item', item) if retval: self.list.update(item) # Public API def add_item(self, item): """ Appends an item to the list @param item: item to append """ self.list.append(item) def add_items(self, items): """ Appends a list of items to the list @param items: items to add @type items: a sequence of items """ self.list.extend(items) def remove_item(self, item): """ Removes an item from the list @param item: item to remove """ self.list.remove(item) def update_item(self, item): """ Updates an item in the list. You should call this if you change the object @param item: item to update """ self.list.update(item) def set_list_type(self, list_type): """ @param list_type: """ if not isinstance(list_type, ListType): raise TypeError("list_type must be a ListType enum") self.add_button.set_property( 'visible', list_type != ListType.READONLY) self.remove_button.set_property( 'visible', (list_type != ListType.READONLY and list_type != ListType.UNREMOVABLE)) self.edit_button.set_property( 'visible', (list_type != ListType.READONLY and list_type != ListType.UNEDITABLE)) if list_type == ListType.READONLY: padding = 0 else: padding = 6 self.set_child_packing(self._vbox, False, False, padding, gtk.PACK_START) self._list_type = list_type # Callbacks def _on_list__selection_changed(self, list, selection): object_selected = selection is not None self.remove_button.set_sensitive(object_selected) self.edit_button.set_sensitive(object_selected) self.emit('selection-changed', selection) def _on_add_button__clicked(self, button): self._add_item() def _on_remove_button__clicked(self, button): self._remove_item(self.list.get_selected()) def _on_edit_button__clicked(self, button): self._edit_item(self.list.get_selected()) gobject.type_register(ListContainer) class ListDialog(gtk.Dialog): """ A ListDialog implements a L{ListContainer} in a L{gtk.Dialog} with a close button. It's a simple Base class which needs to be subclassed to provide interesting functionality. Example: >>> class MyListDialog(ListDialog): ... ... columns = [Column('name')] ... list_type = ListType.UNEDITABLE ... ... def populate(self): ... return [Settable(name='test')] ... ... def add_item(self): ... return Settable(name="added") >>> dialog = MyListDialog() >>> dialog.run() """ columns = None list_type = ListType.NORMAL def __init__(self, columns=None): columns = columns or self.columns if not columns: raise ValueError("columns cannot be empty") gtk.Dialog.__init__(self) self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) self.listcontainer = ListContainer(columns) self.listcontainer.connect( 'add-item', self._on_listcontainer__add_item) self.listcontainer.connect( 'remove-item', self._on_listcontainer__remove_item) self.listcontainer.connect( 'edit-item', self._on_listcontainer__edit_item) self.listcontainer.connect( 'selection-changed', self._on_listcontainer__selection_changed) self.listcontainer.set_border_width(6) self.vbox.pack_start(self.listcontainer) self.listcontainer.show() self.listcontainer.add_items(self.populate()) def _on_listcontainer__add_item(self, listcontainer): try: return self.add_item() # Don't look, PyGObject workaround. except NotImplementedError, e: return e def _on_listcontainer__remove_item(self, listcontainer, item): retval = self.remove_item(item) if type(retval) is not bool: raise ValueError("remove-item must return a bool") return retval def _on_listcontainer__edit_item(self, listcontainer, item): retval = self.edit_item(item) if type(retval) is not bool: raise ValueError("edit-item must return a bool") return retval def _on_listcontainer__selection_changed(self, listcontainer, selection): self.selection_changed(selection) # Public API def set_list_type(self, list_type): """ @see: L{Listcontainer.set_list_type} """ self.listcontainer.set_list_type(list_type) def add_list_item(self, item): """ @see: L{Listcontainer.add_item} """ self.listcontainer.add_item(item) def add_list_items(self, item): """ @see: L{Listcontainer.add_items} """ self.listcontainer.add_items(item) def remove_list_item(self, item): """ @see: L{Listcontainer.remove_item} """ self.listcontainer.remove_item(item) def update_list_item(self, item): """ @see: L{Listcontainer.edit_item} """ self.listcontainer.update_item(item) def default_remove(self, item): response = yesno(_('Do you want to remove %s ?') % (quote(str(item)),), parent=self, default=gtk.RESPONSE_OK, buttons=((gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), (gtk.STOCK_REMOVE, gtk.RESPONSE_OK))) return response == gtk.RESPONSE_OK def add_item(self): """ This must be implemented in a subclass if you want to be able to add items. It should return the model you want to add to the list or None if you don't want anything to be added, eg the user cancelled creation of the model """ raise NotImplementedError( "You need to implement add_item in %s" % (type(self).__name__)) def remove_item(self, item): """ A subclass can implement this to get a notification after an item is removed. If it's not implemented L{default_remove} will be called @returns: False if the item should not be removed """ return self.default_remove(item) def edit_item(self, item): """ A subclass must implement this if you want to support editing of objects. @returns: False if the item should not be removed """ raise NotImplementedError( "You need to implement edit_item in %s" % (type(self).__name__)) def selection_changed(self, selection): """ This will be called when the selection changes in the ListDialog @param selection: selected object or None if nothing is selected """ def populate(self): """ This will be called once after the user interface construction is done. It should return a list of objects which will initially be inserted @returns: object to insert @rtype: sequence of objects """ return [] PIDA-0.5.1/contrib/kiwi/kiwi/ui/objectlist.py0000644000175000017500000021121010652670746017037 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2001-2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Gustavo Rahal # Johan Dahlin # """High level wrapper for GtkTreeView""" import datetime import gettext import gobject import pango import gtk from gtk import gdk from kiwi.accessor import kgetattr from kiwi.datatypes import converter, number, Decimal from kiwi.currency import currency # after datatypes from kiwi.enums import Alignment from kiwi.log import Logger from kiwi.python import enum, slicerange from kiwi.utils import PropertyObject, gsignal, gproperty, type_register _ = lambda m: gettext.dgettext('kiwi', m) log = Logger('objectlist') str2type = converter.str_to_type def str2enum(value_name, enum_class): "converts a string to a enum" for _, enum in enum_class.__enum_values__.items(): if value_name in (enum.value_name, enum.value_nick): return enum def str2bool(value, from_string=converter.from_string): "converts a boolean to a enum" return from_string(bool, value) class Column(PropertyObject, gobject.GObject): """ Specifies a column for an L{ObjectList}, see the ObjectList documentation for a simple example. Properties ========== - B{title}: string I{mandatory} - the title of the column, defaulting to the capitalized form of the attribute - B{data-type}: object I{str} - the type of the attribute that will be inserted into the column. Supported data types: bool, int, float, str, unicode, decimal.Decimal, datetime.date, datetime.time, datetime.datetime, gtk.gdk.Pixbuf, L{kiwi.currency.currency}, L{kiwi.python.enum}. - B{visible}: bool I{True} - specifying if it is initially hidden or shown. - B{justify}: gtk.Justification I{None} - one of gtk.JUSTIFY_LEFT, gtk.JUSTIFY_RIGHT or gtk.JUSTIFY_CENTER or None. If None, the justification will be determined by the type of the attribute value of the first instance to be inserted in the ObjectList (for instance numbers will be right-aligned). - B{format}: string I{""} - a format string to be applied to the attribute value upon insertion in the list. - B{width}: integer I{65535} - the width in pixels of the column, if not set, uses the default to ObjectList. If no Column specifies a width, columns_autosize() will be called on the ObjectList upon append() or the first add_list(). - B{sorted}: bool I{False} - whether or not the ObjectList is to be sorted by this column. If no Columns are sorted, the ObjectList will be created unsorted. - B{order}: GtkSortType I{-1} - one of gtk.SORT_ASCENDING, gtk.SORT_DESCENDING or -1 The value -1 is mean that the column is not sorted. - B{expand}: bool I{False} - if set column will expand. Note: this space is shared equally amongst all columns that have the expand set to True. - B{tooltip}: string I{""} - a string which will be used as a tooltip for the column header - B{format_func}: object I{None} - a callable which will be used to format the output of a column. The function will take one argument which is the value to convert and is expected to return a string. I{Note}: that you cannot use format and format_func at the same time, if you provide a format function you'll be responsible for converting the value to a string. - B{editable}: bool I{False} - if true the field is editable and when you modify the contents of the cell the model will be updated. - B{searchable}: bool I{False} - if true the attribute values of the column can be searched using type ahead search. Only string attributes are currently supported. - B{radio}: bool I{False} - If true render the column as a radio instead of toggle. Only applicable for columns with boolean data types. - B{cache}: bool I{False} - If true, the value will only be fetched once, and the same value will be reused for futher access. - B{use_stock}: bool I{False} - If true, this will be rendered as pixbuf from the value which should be a stock id. - B{icon_size}: gtk.IconSize I{gtk.ICON_SIZE_MENU} - B{editable_attribute}: string I{""} - a string which is the attribute which should decide if the cell is editable or not. - B{use_markup}: bool I{False} - If true, the text will be rendered with markup - B{expander}: bool I{False} - If True, this column will be used as the tree expander column - B{ellipsize}: pango.EllipsizeMode I{pango.ELLIPSIZE_NONE} - One of pango.ELLIPSIZE_{NONE, START, MIDDLE_END}, it describes where characters should be removed in case ellipsization (where to put the ...) is needed. - B{font-desc}: str I{""} - A string passed to pango.FontDescription, for instance "Sans" or "Monospace 28". """ __gtype_name__ = 'Column' gproperty('attribute', str, flags=(gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY)) gproperty('title', str) gproperty('data-type', object) gproperty('visible', bool, default=True) gproperty('justify', gtk.Justification, default=gtk.JUSTIFY_LEFT) gproperty('format', str) gproperty('width', int, maximum=2**16) gproperty('sorted', bool, default=False) gproperty('order', gtk.SortType, default=gtk.SORT_ASCENDING) gproperty('expand', bool, default=False) gproperty('tooltip', str) gproperty('format_func', object) gproperty('editable', bool, default=False) gproperty('searchable', bool, default=False) gproperty('radio', bool, default=False) gproperty('cache', bool, default=False) gproperty('use-stock', bool, default=False) gproperty('use-markup', bool, default=False) gproperty('icon-size', gtk.IconSize, default=gtk.ICON_SIZE_MENU) gproperty('editable-attribute', str) gproperty('expander', bool, False) gproperty('ellipsize', pango.EllipsizeMode, default=pango.ELLIPSIZE_NONE) gproperty('font-desc', str) #gproperty('title_pixmap', str) # This can be set in subclasses, to be able to allow custom # cell_data_functions, used by SequentialColumn cell_data_func = None # This is called after the renderer property is set, to allow # us to set custom rendering properties renderer_func = None # This is called when the renderer is created, so we can set/fetch # initial properties on_attach_renderer = None def __init__(self, attribute='', title=None, data_type=None, **kwargs): """ Creates a new Column, which describes how a column in a ObjectList should be rendered. @param attribute: a string with the name of the instance attribute the column represents. @param title: the title of the column, defaulting to the capitalized form of the attribute. @param data_type: the type of the attribute that will be inserted into the column. @keyword title_pixmap: (TODO) if set to a filename a pixmap will be used *instead* of the title set. The title string will still be used to identify the column in the column selection and in a tooltip, if a tooltip is not set. """ # XXX: filter function? if ' ' in attribute: msg = ("The attribute can not contain spaces, otherwise I can" " not find the value in the instances: %s" % attribute) raise AttributeError(msg) self.compare = None self.from_string = None kwargs['attribute'] = attribute kwargs['title'] = title or attribute.capitalize() if not data_type: data_type = str kwargs['data_type'] = data_type # If we don't specify a justification, right align it for int/float # center for bools and left align it for everything else. if "justify" not in kwargs: if data_type: conv = converter.get_converter(data_type) if issubclass(data_type, bool): kwargs['justify'] = gtk.JUSTIFY_CENTER elif conv.align == Alignment.RIGHT: kwargs['justify'] = gtk.JUSTIFY_RIGHT format_func = kwargs.get('format_func') if format_func: if not callable(format_func): raise TypeError("format_func must be callable") if 'format' in kwargs: raise TypeError( "format and format_func can not be used at the same time") # editable_attribute always turns on editable if 'editable_attribute' in kwargs: if not kwargs.get('editable', True): raise TypeError( "editable cannot be disabled when using editable_attribute") kwargs['editable'] = True PropertyObject.__init__(self, **kwargs) gobject.GObject.__init__(self, attribute=attribute) # This is meant to be subclassable, we're using kgetattr, as # a staticmethod as an optimization, so we can avoid a function call. get_attribute = staticmethod(kgetattr) def prop_set_data_type(self, data): if data is not None: conv = converter.get_converter(data) self.compare = conv.get_compare_function() self.from_string = conv.from_string return data def __repr__(self): namespace = self.__dict__.copy() return "<%s: %s>" % (self.__class__.__name__, namespace) def as_string(self, data): data_type = self.data_type if data is None: text = '' elif self.format_func: text = self.format_func(data) elif (self.format or data_type == float or data_type == Decimal or data_type == currency or data_type == datetime.date or data_type == datetime.datetime or data_type == datetime.time or issubclass(data_type, enum)): conv = converter.get_converter(data_type) text = conv.as_string(data, format=self.format or None) else: text = data return text class SequentialColumn(Column): """I am a column which will display a sequence of numbers, which represent the row number. The value is independent of the data in the other columns, so no matter what I will always display 1 in the first column, unless you reverse it by clicking on the column header. If you don't give me any argument I'll have the title of a hash (#) and right justify the sequences.""" def __init__(self, title='#', justify=gtk.JUSTIFY_RIGHT, **kwargs): Column.__init__(self, '_kiwi_sequence_id', title=title, justify=justify, data_type=int, **kwargs) def cell_data_func(self, tree_column, renderer, model, treeiter, (column, renderer_prop)): reversed = tree_column.get_sort_order() == gtk.SORT_DESCENDING row = model[treeiter] if reversed: sequence_id = len(model) - row.path[0] else: sequence_id = row.path[0] + 1 row[COL_MODEL]._kiwi_sequence_id = sequence_id try: renderer.set_property(renderer_prop, sequence_id) except TypeError: raise TypeError("%r does not support parameter %s" % (renderer, renderer_prop)) class ColoredColumn(Column): """ I am a column which can colorize the text of columns under certain circumstances. I take a color and an extra function which will be called for each row Example, to colorize negative values to red: >>> def colorize(value): ... return value < 0 ... ... ColoredColumn('age', data_type=int, color='red', ... data_func=colorize), """ def __init__(self, attribute, title=None, data_type=None, color=None, data_func=None, **kwargs): if not issubclass(data_type, number): raise TypeError("data type must be a number") if not callable(data_func): raise TypeError("data func must be callable") self._color = gdk.color_parse(color) self._color_normal = None self._data_func = data_func Column.__init__(self, attribute, title, data_type, **kwargs) def on_attach_renderer(self, renderer): renderer.set_property('foreground-set', True) self._color_normal = renderer.get_property('foreground-gdk') def renderer_func(self, renderer, data): if self._data_func(data): color = self._color else: color = self._color_normal renderer.set_property('foreground-gdk', color) class _ContextMenu(gtk.Menu): """ ContextMenu is a wrapper for the menu that's displayed when right clicking on a column header. It monitors the treeview and rebuilds when columns are added, removed or moved. """ def __init__(self, treeview): gtk.Menu.__init__(self) self._dirty = True self._signal_ids = [] self._treeview = treeview self._treeview.connect('columns-changed', self._on_treeview__columns_changed) self._create() def clean(self): for child in self.get_children(): self.remove(child) for menuitem, signal_id in self._signal_ids: menuitem.disconnect(signal_id) self._signal_ids = [] def popup(self, event): self._create() gtk.Menu.popup(self, None, None, None, event.button, event.time) def _create(self): if not self._dirty: return self.clean() for column in self._treeview.get_columns(): header_widget = column.get_widget() if not header_widget: continue title = header_widget.get_text() menuitem = gtk.CheckMenuItem(title) menuitem.set_active(column.get_visible()) signal_id = menuitem.connect("activate", self._on_menuitem__activate, column) self._signal_ids.append((menuitem, signal_id)) menuitem.show() self.append(menuitem) self._dirty = False def _on_treeview__columns_changed(self, treeview): self._dirty = True def _on_menuitem__activate(self, menuitem, column): active = menuitem.get_active() column.set_visible(active) # The width or height of some of the rows might have # changed after changing the visibility of the column, # so we have to re-measure all the rows, this can be done # using row_changed. model = self._treeview.get_model() for row in model: model.row_changed(row.path, row.iter) children = self.get_children() if active: # Make sure all items are selectable for child in children: child.set_sensitive(True) else: # Protect so we can't hide all the menu items # If there's only one menuitem less to select, set # it to insensitive active_children = [child for child in children if child.get_active()] if len(active_children) == 1: active_children[0].set_sensitive(False) COL_MODEL = 0 _marker = object() class ObjectList(PropertyObject, gtk.ScrolledWindow): """ An enhanced version of GtkTreeView, which provides pythonic wrappers for accessing rows, and optional facilities for column sorting (with types) and column selection. Items in an ObjectList is stored in objects. Each row represents an object and each column represents an attribute in the object. The column description object must be a subclass of L{Column}. Simple example >>> class Fruit: >>> pass >>> apple = Fruit() >>> apple.name = 'Apple' >>> apple.description = 'Worm house' >>> banana = Fruit() >>> banana.name = 'Banana' >>> banana.description = 'Monkey food' >>> fruits = ObjectList([Column('name'), >>> Column('description')]) >>> fruits.append(apple) >>> fruits.append(banana) Signals ======= - B{row-activated} (list, object): - Emitted when a row is "activated", eg double clicked or pressing enter. See the GtkTreeView documentation for more information - B{selection-changed} (list, object): - Emitted when the selection changes for the ObjectList enter. See the documentation on GtkTreeSelection::changed for more information - B{double-click} (list, object): - Emitted when a row is double-clicked, mostly you want to use the row-activated signal instead to be able catch keyboard events. - B{right-click} (list, object): - Emitted when a row is clicked with the right mouse button. - B{cell-edited} (list, object, attribute): - Emitted when a cell is edited. - B{has-rows} (list, bool): - Emitted when the objectlist goes from an empty to a non-empty state or vice verse. Properties ========== - B{selection-mode}: gtk.SelectionMode I{gtk.SELECTION_BROWSE} - Represents the selection-mode of a GtkTreeSelection of a GtkTreeView. """ __gtype_name__ = 'ObjectList' # row activated gsignal('row-activated', object) # selected row(s) gsignal('selection-changed', object) # row double-clicked gsignal('double-click', object) # row right-clicked gsignal('right-click', object, gtk.gdk.Event) # row middle-clicked gsignal('middle-click', object, gtk.gdk.Event) # edited object, attribute name gsignal('cell-edited', object, str) # emitted when empty or non-empty status changes gsignal('has-rows', bool) gproperty('selection-mode', gtk.SelectionMode, default=gtk.SELECTION_BROWSE, nick="SelectionMode") def __init__(self, columns=None, objects=None, mode=gtk.SELECTION_BROWSE, sortable=False, model=None): """ @param columns: a list of L{Column}s @param objects: a list of objects to be inserted or None @param mode: selection mode @param sortable: whether the user can sort the list @param model: gtk.TreeModel to use or None to create one """ if columns is None: columns = [] # allow to specify only one column if isinstance(columns, Column): columns = [columns] elif not isinstance(columns, list): raise TypeError("columns must be a list or a Column") if not isinstance(mode, gtk.SelectionMode): raise TypeError("mode must be an gtk.SelectionMode enum") # gtk.SELECTION_EXTENDED & gtk.SELECTION_MULTIPLE are both 3. # so we can't do this check. #elif mode == gtk.SELECTION_EXTENDED: # raise TypeError("gtk.SELECTION_EXTENDED is deprecated") self._sortable = sortable self._columns = [] # Mapping of instance id -> treeiter self._iters = {} self._cell_data_caches = {} self._autosize = True self._vscrollbar = None gtk.ScrolledWindow.__init__(self) # we always want a vertical scrollbar. Otherwise the button on top # of it doesn't make sense. This button is used to display the popup # menu self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) self.set_shadow_type(gtk.SHADOW_ETCHED_IN) # This is required for gobject.new to work, since scrolledwindow.add # requires valid adjustments and they are for some reason not # properly set when using gobject.new. self.set_hadjustment(gtk.Adjustment()) self.set_vadjustment(gtk.Adjustment()) if not model: model = gtk.ListStore(object) self._model = model self._model.connect('row-inserted', self._on_model__row_inserted) self._model.connect('row-deleted', self._on_model__row_deleted) self._treeview = gtk.TreeView(self._model) self._treeview.connect('button-press-event', self._on_treeview__button_press_event) self._treeview.connect_after('row-activated', self._after_treeview__row_activated) self._treeview.set_rules_hint(True) self._treeview.show() self.add(self._treeview) # these tooltips are used for the columns self._tooltips = gtk.Tooltips() # create a popup menu for showing or hiding columns self._popup = _ContextMenu(self._treeview) # when setting the column definition the columns are created self.set_columns(columns) if objects: self.add_list(objects, clear=True) # Set selection mode last to avoid spurious events selection = self._treeview.get_selection() selection.connect("changed", self._on_selection__changed) # Select the first item if no items are selected if mode != gtk.SELECTION_NONE and objects: selection.select_iter(self._model[COL_MODEL].iter) # Depends on treeview and selection being set up PropertyObject.__init__(self) self.set_selection_mode(mode) # Python list object implementation # These methods makes the kiwi list behave more or less # like a normal python list # # TODO: # operators # __add__, __eq__, __ge__, __gt__, __iadd__, # __imul__, __le__, __lt__, __mul__, __ne__, # __rmul__ # # misc # __delitem__, __hash__, __reduce__, __reduce_ex__ # __reversed__ def __len__(self): "len(list)" return len(self._model) def __nonzero__(self): "if list" return True def __contains__(self, instance): "item in list" return bool(self._iters.get(id(instance), False)) def __iter__(self): "for item in list" class ModelIterator: def __init__(self): self._index = -1 def __iter__(self): return self def next(self, model=self._model): try: self._index += 1 return model[self._index][COL_MODEL] except IndexError: raise StopIteration return ModelIterator() def __getitem__(self, arg): "list[n]" if isinstance(arg, (int, gtk.TreeIter, str)): item = self._model[arg][COL_MODEL] elif isinstance(arg, slice): model = self._model return [model[item][COL_MODEL] for item in slicerange(arg, len(self._model))] else: raise TypeError("argument arg must be int, gtk.Treeiter or " "slice, not %s" % type(arg)) return item def __setitem__(self, arg, item): "list[n] = m" if isinstance(arg, (int, gtk.TreeIter, str)): model = self._model olditem = model[arg][COL_MODEL] model[arg] = (item,) # Update iterator cache iters = self._iters iters[id(item)] = model[arg].iter del iters[id(olditem)] elif isinstance(arg, slice): raise NotImplementedError("slices for list are not implemented") else: raise TypeError("argument arg must be int or gtk.Treeiter," " not %s" % type(arg)) # append and remove are below def extend(self, iterable): """ Extend list by appending elements from the iterable @param iterable: """ return self.add_list(iterable, clear=False) def index(self, item, start=None, stop=None): """ Return first index of value @param item: @param start: @param stop """ if start is not None or stop is not None: raise NotImplementedError("start and stop") treeiter = self._iters.get(id(item), _marker) if treeiter is _marker: raise ValueError("item %r is not in the list" % item) return self._model[treeiter].path[0] def count(self, item): "L.count(item) -> integer -- return number of occurrences of value" count = 0 for row in self._model: if row[COL_MODEL] == item: count += 1 return count def insert(self, index, instance, select=False): """Inserts an instance to the list @param index: position to insert the instance at @param instance: the instance to be added (according to the columns spec) @param select: whether or not the new item should appear selected. """ self._treeview.freeze_notify() row_iter = self._model.insert(index, (instance,)) self._iters[id(instance)] = row_iter if self._autosize: self._treeview.columns_autosize() if select: self._select_and_focus_row(row_iter) self._treeview.thaw_notify() def pop(self, index): """ Remove and return item at index (default last) @param index: """ raise NotImplementedError def reverse(self, pos, item): "L.reverse() -- reverse *IN PLACE*" raise NotImplementedError def sort(self, pos, item): """L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*; cmp(x, y) -> -1, 0, 1""" raise NotImplementedError def sort_by_attribute(self, attribute, order=gtk.SORT_ASCENDING): """ Sort by an attribute in the object model. @param attribute: attribute to sort on @type attribute: string @param order: one of gtk.SORT_ASCENDING, gtk.SORT_DESCENDING @type order: gtk.SortType """ def _sort_func(model, iter1, iter2): return cmp( getattr(model[iter1][0], attribute, None), getattr(model[iter2][0], attribute, None)) unused_sort_col_id = len(self._columns) self._model.set_sort_func(unused_sort_col_id, _sort_func) self._model.set_sort_column_id(unused_sort_col_id, order) # Properties def prop_set_selection_mode(self, mode): self.set_selection_mode(mode) def prop_get_selection_mode(self): return self.get_selection_mode() # Columns handling def _load(self, instances, clear): # do nothing if empty list or None provided model = self._model if clear: if not instances: self.unselect_all() self.clear() return model = self._model iters = self._iters old_instances = [row[COL_MODEL] for row in model] # Save selection selected_instances = [] if old_instances: selection = self._treeview.get_selection() _, paths = selection.get_selected_rows() if paths: selected_instances = [model[path][COL_MODEL] for (path,) in paths] iters = self._iters prev = None # Do not always just clear the list, check if we have the same # instances in the list we want to insert and merge in the new # items if clear: for instance in iter(instances): objid = id(instance) # If the instance is not in the list insert it after # the previous inserted object if not objid in iters: if prev is None: prev = model.append((instance,)) else: prev = model.insert_after(prev, (instance,)) iters[objid] = prev else: prev = iters[objid] # Optimization when we were empty, we wont need to remove anything # nor restore selection if old_instances: # Remove objids = [id(instance) for instance in instances] for instance in old_instances: objid = id(instance) if objid in objids: continue self._remove(objid) else: for instance in iter(instances): iters[id(instance)] = model.append((instance,)) # Restore selection for instance in selected_instances: objid = id(instance) if objid in iters: selection.select_iter(iters[objid]) # As soon as we have data for that list, we can autosize it, and # we don't want to autosize again, or we may cancel user # modifications. if self._autosize: self._treeview.columns_autosize() self._autosize = False def _setup_columns(self, columns): searchable = None sorted = None expand = False for column in columns: if column.sorted: if sorted: raise ValueError("Can't make column %s sorted, column" " %s is already set as sortable" % ( column.attribute, sorted.attribute)) sorted = column.sorted if column.expand: expand = True self._sortable = self._sortable or sorted for column in columns: self._setup_column(column) if not expand: column = gtk.TreeViewColumn() self._treeview.append_column(column) def _setup_column(self, column): # You can't subclass bool, so this is okay if (column.data_type is bool and column.format): raise TypeError("format is not supported for boolean columns") index = self._columns.index(column) treeview_column = self._treeview.get_column(index) if treeview_column is None: treeview_column = self._create_column(column) if self._sortable: self._model.set_sort_func(index, self._model_sort_func, (column, column.attribute)) treeview_column.set_sort_column_id(index) if column.sorted: self._model.set_sort_column_id(index, column.order) renderer, renderer_prop = self._guess_renderer_for_type(column) if column.on_attach_renderer: column.on_attach_renderer(renderer) justify = column.justify if justify == gtk.JUSTIFY_RIGHT: xalign = 1.0 elif justify == gtk.JUSTIFY_CENTER: xalign = 0.5 elif justify in (gtk.JUSTIFY_LEFT, gtk.JUSTIFY_FILL): xalign = 0.0 else: raise AssertionError renderer.set_property("xalign", xalign) treeview_column.set_property("alignment", xalign) if column.ellipsize: renderer.set_property('ellipsize', column.ellipsize) if column.font_desc: renderer.set_property('font-desc', pango.FontDescription(column.font_desc)) if column.use_stock: cell_data_func = self._cell_data_pixbuf_func elif issubclass(column.data_type, enum): cell_data_func = self._cell_data_combo_func else: cell_data_func = self._cell_data_text_func if column.cell_data_func: cell_data_func = column.cell_data_func elif column.cache: self._cell_data_caches[column.attribute] = {} treeview_column.pack_start(renderer) treeview_column.set_cell_data_func(renderer, cell_data_func, (column, renderer_prop)) treeview_column.set_visible(column.visible) if column.width: treeview_column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) treeview_column.set_fixed_width(column.width) if column.tooltip: widget = self._get_column_button(treeview_column) if widget is not None: self._tooltips.set_tip(widget, column.tooltip) if column.expand: # Default is False treeview_column.set_expand(True) if column.sorted: treeview_column.set_sort_indicator(True) if column.width: self._autosize = False if column.searchable: if not issubclass(column.data_type, basestring): raise TypeError("Unsupported data type for " "searchable column: %s" % column.data_type) self._treeview.set_search_column(index) self._treeview.set_search_equal_func(self._treeview_search_equal_func, column) if column.radio: if not issubclass(column.data_type, bool): raise TypeError("You can only use radio for boolean columns") if column.expander: self._treeview.set_expander_column(treeview_column) # typelist here may be none. It's okay; justify_columns will try # and use the specified justifications and if not present will # not touch the column. When typelist is not set, # append/add_list have a chance to fix up the remaining # justification by looking at the first instance's data. # self._justify_columns(columns, typelist) def _create_column(self, column): treeview_column = gtk.TreeViewColumn() # we need to set our own widget because otherwise # __get_column_button won't work label = gtk.Label(column.title) label.show() treeview_column.set_widget(label) treeview_column.set_resizable(True) treeview_column.set_clickable(True) treeview_column.set_reorderable(True) self._treeview.append_column(treeview_column) # setup the button to show the popup menu button = self._get_column_button(treeview_column) button.connect('button-release-event', self._on_treeview_header__button_release_event) return treeview_column def _guess_renderer_for_type(self, column): """Gusses which CellRenderer we should use for a given type. It also set the property of the renderer that depends on the model, in the renderer. """ # TODO: Move to column data_type = column.data_type if data_type is bool: renderer = gtk.CellRendererToggle() if column.editable: renderer.set_property('activatable', True) # Boolean can be either a radio or a checkbox. # Changes are handled by the toggled callback, which # should only be connected if the column is editable. if column.radio: renderer.set_radio(True) cb = self._on_renderer_toggle_radio__toggled else: cb = self._on_renderer_toggle_check__toggled renderer.connect('toggled', cb, self._model, column.attribute) prop = 'active' elif column.use_stock or data_type == gdk.Pixbuf: renderer = gtk.CellRendererPixbuf() prop = 'pixbuf' if column.editable: raise TypeError("use-stock columns cannot be editable") elif issubclass(data_type, enum): if data_type is enum: raise TypeError("data_type must be a subclass of enum") model = gtk.ListStore(str, object) items = data_type.names.items() items.sort() for key, value in items: model.append((key.lower().capitalize(), value)) renderer = gtk.CellRendererCombo() renderer.set_property('model', model) renderer.set_property('text-column', 0) renderer.set_property('has-entry', True) if column.editable: renderer.set_property('editable', True) renderer.connect('edited', self._on_renderer_combo__edited, self._model, column.attribute, column) prop = 'model' elif issubclass(data_type, (datetime.date, datetime.time, basestring, number, currency)): renderer = gtk.CellRendererText() if column.use_markup: prop = 'markup' else: prop = 'text' if column.editable: renderer.set_property('editable', True) renderer.connect('edited', self._on_renderer_text__edited, self._model, column.attribute, column, column.from_string) else: raise ValueError("the type %s is not supported yet" % data_type) return renderer, prop # selection methods def _select_and_focus_row(self, row_iter): self._treeview.set_cursor(self._model[row_iter].path) # handlers & callbacks # Model def _on_model__row_inserted(self, model, path, iter): if len(model) == 1: self.emit('has-rows', True) def _on_model__row_deleted(self, model, path): if not len(model): self.emit('has-rows', False) def _model_sort_func(self, model, iter1, iter2, (column, attr)): "This method is used to sort the GtkTreeModel" return column.compare( column.get_attribute(model[iter1][COL_MODEL], attr), column.get_attribute(model[iter2][COL_MODEL], attr)) # Selection def _on_selection__changed(self, selection): "This method is used to proxy selection::changed to selection-changed" mode = selection.get_mode() if mode == gtk.SELECTION_MULTIPLE: item = self.get_selected_rows() elif mode in (gtk.SELECTION_SINGLE, gtk.SELECTION_BROWSE): item = self.get_selected() else: raise AssertionError self.emit('selection-changed', item) # ScrolledWindow def _on_scrolled_window__realize(self, widget): toplevel = widget.get_toplevel() self._popup_window.set_transient_for(toplevel) self._popup_window.set_destroy_with_parent(True) def _on_scrolled_window__size_allocate(self, widget, allocation): """Resize the Vertical Scrollbar to make it smaller and let space for the popup button. Also put that button there. """ old_alloc = self._vscrollbar.get_allocation() height = self._get_header_height() new_alloc = gtk.gdk.Rectangle(old_alloc.x, old_alloc.y + height, old_alloc.width, old_alloc.height - height) self._vscrollbar.size_allocate(new_alloc) # put the popup_window in its position gdk_window = self.window if gdk_window: winx, winy = gdk_window.get_origin() self._popup_window.move(winx + old_alloc.x, winy + old_alloc.y) # TreeView def _treeview_search_equal_func(self, model, tree_column, key, treeiter, column): "for searching inside the treeview, case-insensitive by default" data = column.get_attribute(model[treeiter][COL_MODEL], column.attribute, None) if data.lower().startswith(key.lower()): return False return True def _on_treeview_header__button_release_event(self, button, event): if event.button == 3: self._popup.popup(event) return False def _after_treeview__row_activated(self, treeview, path, view_column): "After activated (double clicked or pressed enter) on a row" try: row = self._model[path] except IndexError: print 'path %s was not found in model: %s' % ( path, map(list, self._model)) return item = row[COL_MODEL] self.emit('row-activated', item) def _get_selection_or_selected_rows(self): selection = self._treeview.get_selection() mode = selection.get_mode() if mode == gtk.SELECTION_MULTIPLE: item = self.get_selected_rows() elif mode == gtk.SELECTION_NONE: return else: item = self.get_selected() return item def _emit_button_press_signal(self, signal_name, event): item = self._get_selection_or_selected_rows() if item: self.emit(signal_name, item, event) def _on_treeview__button_press_event(self, treeview, event): "Generic button-press-event handler to be able to catch double clicks" # Right and Middle click if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 3: signal_name = 'right-click' elif event.button == 2: signal_name = 'middle-click' else: return gobject.idle_add(self._emit_button_press_signal, signal_name, event.copy()) # Double left click elif event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1: item = self._get_selection_or_selected_rows() if item: self.emit('double-click', item) # CellRenderers def _cell_data_text_func(self, tree_column, renderer, model, treeiter, (column, renderer_prop)): "To render the data of a cell renderer text" row = model[treeiter] if column.editable_attribute: data = column.get_attribute(row[COL_MODEL], column.editable_attribute, None) data_type = column.data_type if isinstance(renderer, gtk.CellRendererToggle): renderer.set_property('activatable', data) elif isinstance(renderer, gtk.CellRendererText): renderer.set_property('editable', data) else: raise AssertionError if column.cache: cache = self._cell_data_caches[column.attribute] path = row.path[0] if path in cache: data = cache[path] else: data = column.get_attribute(row[COL_MODEL], column.attribute, None) cache[path] = data else: data = column.get_attribute(row[COL_MODEL], column.attribute, None) text = column.as_string(data) renderer.set_property(renderer_prop, text) if column.renderer_func: column.renderer_func(renderer, data) def _cell_data_pixbuf_func(self, tree_column, renderer, model, treeiter, (column, renderer_prop)): "To render the data of a cell renderer pixbuf" row = model[treeiter] data = column.get_attribute(row[COL_MODEL], column.attribute, None) if data is not None: pixbuf = self.render_icon(data, column.icon_size) renderer.set_property(renderer_prop, pixbuf) def _cell_data_combo_func(self, tree_column, renderer, model, treeiter, (column, renderer_prop)): row = model[treeiter] data = column.get_attribute(row[COL_MODEL], column.attribute, None) text = column.as_string(data) renderer.set_property('text', text.lower().capitalize()) def _on_renderer__toggled(self, renderer, path, column): setattr(self._model[path][COL_MODEL], column.attribute, not renderer.get_active()) def _on_renderer_toggle_check__toggled(self, renderer, path, model, attr): obj = model[path][COL_MODEL] value = not getattr(obj, attr, None) setattr(obj, attr, value) self.emit('cell-edited', obj, attr) def _on_renderer_toggle_radio__toggled(self, renderer, path, model, attr): # Deactive old one old = renderer.get_data('kiwilist::radio-active') # If we don't have the radio-active set it means we're doing # This for the first time, so scan and see which one is currently # active, so we can deselect it if not old: # XXX: Handle multiple values set to True, this # algorithm just takes the first one it finds for row in self._model: obj = row[COL_MODEL] value = getattr(obj, attr) if value == True: old = obj break if old: setattr(old, attr, False) # Active new and save a reference to the object of the # previously selected row new = model[path][COL_MODEL] setattr(new, attr, True) renderer.set_data('kiwilist::radio-active', new) self.emit('cell-edited', new, attr) def _on_renderer_text__edited(self, renderer, path, text, model, attr, column, from_string): obj = model[path][COL_MODEL] value = from_string(text) setattr(obj, attr, value) self.emit('cell-edited', obj, attr) def _on_renderer_combo__edited(self, renderer, path, text, model, attr, column): obj = model[path][COL_MODEL] if not text: return value_model = renderer.get_property('model') for row in value_model: if row[0] == text: value = row[1] break else: raise AssertionError setattr(obj, attr, value) self.emit('cell-edited', obj, attr) def _on_renderer__edited(self, renderer, path, value, column): data_type = column.data_type if data_type in number: value = data_type(value) # XXX convert new_text to the proper data type setattr(self._model[path][COL_MODEL], column.attribute, value) # hacks def _get_column_button(self, column): """Return the button widget of a particular TreeViewColumn. This hack is needed since that widget is private of the TreeView but we need access to them for Tooltips, right click menus, ... Use this function at your own risk """ button = column.get_widget() assert button is not None, ("You must call column.set_widget() " "before calling _get_column_button") while not isinstance(button, gtk.Button): button = button.get_parent() return button # start of the hack to put a button on top of the vertical scrollbar def _setup_popup_button(self): """Put a button on top of the vertical scrollbar to show the popup menu. Internally it uses a POPUP window so you can tell how *Evil* is this. """ self._popup_window = gtk.Window(gtk.WINDOW_POPUP) self._popup_button = gtk.Button('*') self._popup_window.add(self._popup_button) self._popup_window.show_all() self.forall(self._find_vertical_scrollbar) self.connect('size-allocate', self._on_scrolled_window__size_allocate) self.connect('realize', self._on_scrolled_window__realize) def _find_vertical_scrollbar(self, widget): """This method is called from a .forall() method in the ScrolledWindow. It just save a reference to the vertical scrollbar for doing evil things later. """ if isinstance(widget, gtk.VScrollbar): self._vscrollbar = widget def _get_header_height(self): treeview_column = self._treeview.get_column(0) button = self._get_column_button(treeview_column) alloc = button.get_allocation() return alloc.height # end of the popup button hack # # Public API # def get_model(self): "Return treemodel of the current list" return self._model def get_treeview(self): "Return treeview of the current list" return self._treeview def get_columns(self): return self._columns def get_column_by_name(self, name): """Returns the name of a column""" for column in self._columns: if column.attribute == name: return column raise LookupError("There is no column called %s" % name) def get_treeview_column(self, column): """ @param column: a @Column """ if not isinstance(column, Column): raise TypeError if not column in self._columns: raise ValueError index = self._columns.index(column) tree_columns = self._treeview.get_columns() return tree_columns[index] def grab_focus(self): """ Grabs the focus of the ObjectList """ self._treeview.grab_focus() def _clear_columns(self): # Reset the sort function for all model columns model = self._model for i, column in enumerate(self._columns): # Bug in PyGTK, it should be possible to remove a sort func. model.set_sort_func(i, lambda m, i1, i2: -1) # Remove all columns treeview = self._treeview while treeview.get_columns(): treeview.remove_column(treeview.get_column(0)) self._popup.clean() self._columns = [] def set_columns(self, columns): """ @param columns: a sequence of L{Column} objects. """ if not isinstance(columns, (list, tuple)): raise ValueError("columns must be a list or a tuple") self._clear_columns() self._columns = columns self._setup_columns(columns) def append(self, instance, select=False): """Adds an instance to the list. @param instance: the instance to be added (according to the columns spec) @param select: whether or not the new item should appear selected. """ # Freeze and save original selection mode to avoid blinking self._treeview.freeze_notify() row_iter = self._model.append((instance,)) self._iters[id(instance)] = row_iter if self._autosize: self._treeview.columns_autosize() if select: self._select_and_focus_row(row_iter) self._treeview.thaw_notify() def _remove(self, objid): # linear search for the instance to remove treeiter = self._iters.pop(objid) if not treeiter: return False # Remove any references to this path self._clear_cache_for_iter(treeiter) # All references to the iter gone, now it can be removed self._model.remove(treeiter) return True def _clear_cache_for_iter(self, treeiter): # Not as inefficent as it looks path = self._model[treeiter].path[0] for cache in self._cell_data_caches.values(): if path in cache: del cache[path] def remove(self, instance, select=False): """Remove an instance from the list. If the instance is not in the list it returns False. Otherwise it returns True. @param instance: @param select: if true, the previous item will be selected if there is one. """ objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) if select: prev = self.get_previous(instance) rv = self._remove(objid) if prev != instance: self.select(prev) else: rv = self._remove(objid) return rv def update(self, instance): objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) treeiter = self._iters[objid] self._clear_cache_for_iter(treeiter) self._model.row_changed(self._model[treeiter].path, treeiter) def refresh(self, view_only=False): """ Reloads the values from all objects. @param view_only: if True, only force a refresh of the visible part of this objectlist's Treeview. """ if view_only: self._treeview.queue_draw() else: self._model.foreach(gtk.TreeModel.row_changed) def set_column_visibility(self, column_index, visibility): treeview_column = self._treeview.get_column(column_index) treeview_column.set_visible(visibility) def get_selection_mode(self): selection = self._treeview.get_selection() if selection: return selection.get_mode() def set_selection_mode(self, mode): selection = self._treeview.get_selection() if selection: self.notify('selection-mode') return selection.set_mode(mode) def unselect_all(self): selection = self._treeview.get_selection() if selection: selection.unselect_all() def select_paths(self, paths): """ Selects a number of rows corresponding to paths @param paths: rows to be selected """ selection = self._treeview.get_selection() if selection.get_mode() == gtk.SELECTION_NONE: raise TypeError("Selection not allowed") selection.unselect_all() for path in paths: selection.select_path(path) def select(self, instance, scroll=True): selection = self._treeview.get_selection() if selection.get_mode() == gtk.SELECTION_NONE: raise TypeError("Selection not allowed") objid = id(instance) if not objid in self._iters: raise ValueError("instance %s is not in the list" % repr(instance)) treeiter = self._iters[objid] selection.select_iter(treeiter) if scroll: self._treeview.scroll_to_cell(self._model[treeiter].path, None, True, 0.5, 0) def get_selected(self): """Returns the currently selected object If an object is not selected, None is returned """ selection = self._treeview.get_selection() if not selection: # AssertionError ? return mode = selection.get_mode() if mode == gtk.SELECTION_NONE: raise TypeError("Selection not allowed in %r mode" % mode) elif mode not in (gtk.SELECTION_SINGLE, gtk.SELECTION_BROWSE): log.warn('get_selected() called when multiple rows ' 'can be selected') model, treeiter = selection.get_selected() if treeiter: return model[treeiter][COL_MODEL] def get_selected_rows(self): """Returns a list of currently selected objects If no objects are selected an empty list is returned """ selection = self._treeview.get_selection() if not selection: # AssertionError ? return mode = selection.get_mode() if mode == gtk.SELECTION_NONE: raise TypeError("Selection not allowed in %r mode" % mode) elif mode in (gtk.SELECTION_SINGLE, gtk.SELECTION_BROWSE): log.warn('get_selected_rows() called when only a single row ' 'can be selected') model, paths = selection.get_selected_rows() if paths: return [model[path][COL_MODEL] for (path,) in paths] return [] def add_list(self, instances, clear=True): """ Allows a list to be loaded, by default clearing it first. freeze() and thaw() are called internally to avoid flashing. @param instances: a list to be added @param clear: a boolean that specifies whether or not to clear the list """ self._treeview.freeze_notify() ret = self._load(instances, clear) self._treeview.thaw_notify() return ret def clear(self): """Removes all the instances of the list""" self._model.clear() self._iters = {} # Don't clear the whole cache, just the # individual column caches for key in self._cell_data_caches: self._cell_data_caches[key] = {} def get_next(self, instance): """ Returns the item after instance in the list. Note that the instance must be inserted before this can be called If there are no instances after, the first item will be returned. @param instance: the instance """ objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) treeiter = self._iters[objid] model = self._model pos = model[treeiter].path[0] if pos >= len(model) - 1: pos = 0 else: pos += 1 return model[pos][COL_MODEL] def get_previous(self, instance): """ Returns the item before instance in the list. Note that the instance must be inserted before this can be called If there are no instances before, the last item will be returned. @param instance: the instance """ objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) treeiter = self._iters[objid] model = self._model pos = model[treeiter].path[0] if pos == 0: pos = len(model) - 1 else: pos -= 1 return model[pos][COL_MODEL] def get_selected_row_number(self): """ @returns: the selected row number or None if no rows were selected """ selection = self._treeview.get_selection() if selection.get_mode() == gtk.SELECTION_MULTIPLE: model, paths = selection.get_selected_rows() if paths: return paths[0][0] else: model, iter = selection.get_selected() if iter: return model[iter].path[0] def double_click(self, rowno): """ Same as double clicking on the row rowno @param rowno: integer """ columns = self._treeview.get_columns() if not columns: raise AssertionError( "%s has no columns" % self.get_name()) self._treeview.row_activated(rowno, columns[0]) def set_headers_visible(self, value): """ @param value: if true, shows the headers, if false hide then """ self._treeview.set_headers_visible(value) def set_visible_rows(self, rows): """ Sets the number of visible rows of the treeview. This is useful to use instead of set_size_request() directly, since you can avoid using raw pixel sizes. @param rows: number of rows to show """ treeview = self._treeview if treeview.get_headers_visible(): treeview.realize() header_h = self._get_header_height() else: header_h = 0 column = treeview.get_columns()[0] h = column.cell_get_size()[-1] focus_padding = treeview.style_get_property('focus-line-width') * 2 treeview.set_size_request(-1, header_h + (rows * (h + focus_padding))) type_register(ObjectList) class ObjectTree(ObjectList): """ Signals ======= - B{row-expanded} (list, object): - Emitted when a row is "expanded", eg the littler arrow to the left is opened. See the GtkTreeView documentation for more information. """ __gtype_name__ = 'ObjectTree' gsignal('row-expanded', object) def __init__(self, columns=[], objects=None, mode=gtk.SELECTION_BROWSE, sortable=False, model=None): if not model: model = gtk.TreeStore(object) ObjectList.__init__(self, columns, objects, mode, sortable, model) self.get_treeview().connect('row-expanded', self._on_treeview__row_expanded) def _append_internal(self, parent, instance, select, prepend): iters = self._iters parent_id = id(parent) if parent_id in iters: parent_iter = iters[parent_id] elif parent is None: parent_iter = None else: raise TypeError("parent must be an Object, ObjectRow or None") # Freeze and save original selection mode to avoid blinking self._treeview.freeze_notify() if prepend: row_iter = self._model.prepend(parent_iter, (instance,)) else: row_iter = self._model.append(parent_iter, (instance,)) self._iters[id(instance)] = row_iter if self._autosize: self._treeview.columns_autosize() if select: self._select_and_focus_row(row_iter) self._treeview.thaw_notify() return instance def append(self, parent, instance, select=False): """ @param parent: Object or None, representing the parent @param instance: the instance to be added @param select: select the row @returns: the appended object """ return self._append_internal(parent, instance, select, prepend=False) def prepend(self, parent, instance, select=False): """ @param parent: Object or None, representing the parent @param instance: the instance to be added @param select: select the row @returns: the prepended object """ return self._append_internal(parent, instance, select, prepend=True) def expand(self, instance, open_all=True): """ This method opens the row specified by path so its children are visible. @param instance: an instance to expand at @param open_all: If True, expand all rows, otherwise just the immediate children """ objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) treeiter = self._iters[objid] self.get_treeview().expand_row( self._model[treeiter].path, open_all) def collapse(self, instance): """ This method collapses the row specified by path (hides its child rows, if they exist). @param instance: an instance to collapse """ objid = id(instance) if not objid in self._iters: raise ValueError("instance %r is not in the list" % instance) treeiter = self._iters[objid] self.get_treeview().collapse_row( self._model[treeiter].path) def _on_treeview__row_expanded(self, treeview, treeiter, treepath): self.emit('row-expanded', self.get_model()[treeiter][COL_MODEL]) type_register(ObjectTree) class ListLabel(gtk.HBox): """I am a subclass of a GtkHBox which you can use if you want to vertically align a label with a column """ def __init__(self, klist, column, label='', value_format='%s', font_desc=None): """ @param klist: list to follow @type klist: kiwi.ui.objectlist.ObjectList @param column: name of a column in a klist @type column: string @param label: label @type label: string @param value_format: format string used to format value @type value_format: string """ self._label = label self._label_width = -1 if not isinstance(klist, ObjectList): raise TypeError("list must be a kiwi list and not %r" % type(klist).__name__) self._klist = klist if not isinstance(column, str): raise TypeError("column must be a string and not %r" % type(column).__name__) self._column = klist.get_column_by_name(column) self._value_format = value_format gtk.HBox.__init__(self) self._create_ui() if font_desc: self._value_widget.modify_font(font_desc) self._label_widget.modify_font(font_desc) # Public API def set_value(self, value): """Sets the value of the label. Note that it needs to be of the same type as you specified in value_format in the constructor. I also support the GMarkup syntax, so you can use "%d" if you want.""" self._value_widget.set_markup(self._value_format % value) def get_value_widget(self): return self._value_widget def get_label_widget(self): return self._label_widget # Private def _create_ui(self): # When tracking the position/size of a column, we need to pay # attention to the following two things: # * treeview_column::width # * size-allocate of treeview_columns header widget # tree_column = self._klist.get_treeview_column(self._column) tree_column.connect('notify::width', self._on_treeview_column__notify_width) button = self._klist._get_column_button(tree_column) button.connect('size-allocate', self._on_treeview_column_button__size_allocate) self._label_widget = gtk.Label() self._label_widget.set_markup(self._label) layout = self._label_widget.get_layout() self._label_width = layout.get_pixel_size()[0] self._label_widget.set_alignment(1.0, 0.5) self.pack_start(self._label_widget, False, False, padding=6) self._label_widget.show() self._value_widget = gtk.Label() xalign = tree_column.get_property('alignment') self._value_widget.set_alignment(xalign, 0.5) self.pack_start(self._value_widget, False, False) self._value_widget.show() def _resize(self, position=-1, width=-1): if position != -1: if position != 0: if self._label_width > position: self._label_widget.set_text('') else: self._label_widget.set_markup(self._label) # XXX: Replace 12 with a constant if position >= 12: self._label_widget.set_size_request(position - 12, -1) if width != -1: self._value_widget.set_size_request(width, -1) # Callbacks def _on_treeview_column_button__size_allocate(self, label, rect): self._resize(position=rect[0]) def _on_treeview_column__notify_width(self, treeview, pspec): value = treeview.get_property(pspec.name) self._resize(width=value) def _on_list__size_allocate(self, list, rect): self._resize(position=rect[0], width=rect[2]) class SummaryLabel(ListLabel): """I am a subclass of ListLabel which you can use if you want to summarize all the values of a specific column. Please note that I only know how to handle number column data types and I will complain if you give me something else.""" def __init__(self, klist, column, label=_('Total:'), value_format='%s', font_desc=None): ListLabel.__init__(self, klist, column, label, value_format, font_desc) if not issubclass(self._column.data_type, number): raise TypeError("data_type of column must be a number, not %r", self._column.data_type) klist.connect('cell-edited', self._on_klist__cell_edited) self.update_total() # Public API def update_total(self): """Recalculate the total value of all columns""" column = self._column attr = column.attribute get_attribute = column.get_attribute value = sum([get_attribute(obj, attr, 0) for obj in self._klist], column.data_type('0')) self.set_value(column.as_string(value)) # Callbacks def _on_klist__cell_edited(self, klist, object, attribute): self.update_total() PIDA-0.5.1/contrib/kiwi/kiwi/ui/proxy.py0000644000175000017500000003520710652670746016070 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2002-2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Gustavo Rahal # Johan Dahlin # """This module defines the Proxy class, which is a facility that can be used to keep the state of a model object synchronized with a View. """ import gobject import gtk from kiwi import ValueUnset from kiwi.accessor import kgetattr, ksetattr, clear_attr_cache from kiwi.decorators import deprecated from kiwi.interfaces import IProxyWidget, IValidatableProxyWidget from kiwi.log import Logger class ProxyError(Exception): pass log = Logger('proxy') def block_widget(widget): """Blocks the signal handler of the 'content-changed' signal on widget""" connection_id = widget.get_data('content-changed-id') if connection_id: widget.handler_block(connection_id) def unblock_widget(widget): """Unblocks the signal handler of the 'content-changed' signal on widget""" connection_id = widget.get_data('content-changed-id') if connection_id: widget.handler_unblock(connection_id) class Proxy: """ A Proxy is a class that 'attaches' an instance to an interface's widgets, and transparently manipulates that instance's attributes as the user alters the content of the widgets. The Proxy takes the widget list and detects what widgets are to be attached to the model by looking if it is a KiwiWidget and if it has the model-attribute set. """ def __init__(self, view, model=None, widgets=()): """ @param view: view attched to the slave @type view: a L{kiwi.ui.views.BaseView} subclass @param model: model attached to proxy @param widgets: the widget names @type widgets: list of strings """ self._view = view self._model = model self._model_attributes = {} for widget_name in widgets: widget = getattr(self._view, widget_name, None) if widget is None: raise AttributeError("The widget %s was not found in the " "view %s" % ( widget_name, self._view.__class__.__name__)) self._setup_widget(widget_name, widget) # Private API def _reset_widget(self, attribute, widget): if self._model is None: # if we have no model, leave value unset so we pick up # the widget default below. value = ValueUnset else: # if we have a model, grab its value to update the widgets self._register_proxy_in_model(attribute) value = kgetattr(self._model, attribute, ValueUnset) self.update(attribute, value, block=True) # The initial value of the model is set, at this point # do a read, it'll trigger a validation for widgets who # supports it. if not IValidatableProxyWidget.providedBy(widget): return widget.validate(force=True) def _setup_widget(self, widget_name, widget): if not IProxyWidget.providedBy(widget): raise ProxyError("The widget %s (%r), in view %s is not " "a kiwi widget and cannot be added to a proxy" % (widget_name, widget, self._view.__class__.__name__)) data_type = widget.get_property('data-type') if data_type is None: raise ProxyError("The kiwi widget %s (%r) in view %s should " "have a data type set" % ( widget_name, widget, self._view.__class__.__name__)) attribute = widget.get_property('model-attribute') if not attribute: attribute = widget_name widget.set_property('model-attribute', widget_name) # Do a isinstance here instead of in the callback, # as an optimization, it'll never change in runtime anyway connection_id = widget.connect( 'content-changed', self._on_widget__content_changed, attribute, IValidatableProxyWidget.providedBy(widget)) widget.set_data('content-changed-id', connection_id) if IValidatableProxyWidget.providedBy(widget): connection_id = widget.connect( 'notify::visible', self._on_widget__notify) widget.set_data('notify-visible-id', connection_id) connection_id = widget.connect( 'notify::sensitive', self._on_widget__notify) widget.set_data('notify-sensitive-id', connection_id) model_attributes = self._model_attributes # save this widget in our map if (attribute in model_attributes and # RadioButtons are allowed several times not gobject.type_is_a(widget, 'GtkRadioButton')): old_widget = model_attributes[attribute] raise KeyError("The widget %s (%r) in view %s is already in " "the proxy, defined by widget %s (%r)" % ( widget_name, widget, self._view.__class__.__name__, old_widget.name, old_widget)) model_attributes[attribute] = widget self._reset_widget(attribute, widget) def _register_proxy_in_model(self, attribute): model = self._model if not hasattr(model, "register_proxy_for_attribute"): return try: model.register_proxy_for_attribute(attribute, self) except AttributeError: msg = ("Failed to run register_proxy() on Model %s " "(that was supplied to %s. \n" "(Hint: if this model also inherits from ZODB's " "Persistent class, this problem occurs if you haven't " "set __setstate__() up correctly. __setstate__() " "should call Model.__init__() (and " "Persistent.__setstate__() of course) to rereset " "things properly.)") raise TypeError(msg % (model, self)) def _unregister_proxy_in_model(self): if self._model and hasattr(self._model, "unregister_proxy"): self._model.unregister_proxy(self) # Callbacks def _on_widget__content_changed(self, widget, attribute, validate): """This is called as soon as the content of one of the widget changes, the widgets tries fairly hard to not emit when it's not neccessary""" # skip updates for model if there is none, right? if self._model is None: return if validate: value = widget.validate() else: value = widget.read() log('%s.%s = %r' % (self._model.__class__.__name__, attribute, value)) # only update the model if the data is correct if value is ValueUnset: return model = self._model # XXX: one day we might want to queue and unique updates? if hasattr(model, "block_proxy"): model.block_proxy(self) ksetattr(model, attribute, value) model.unblock_proxy(self) else: ksetattr(model, attribute, value) # Call global update hook self.proxy_updated(widget, attribute, value) # notify::sensitive and notify::visible are connected here def _on_widget__notify(self, widget, pspec): widget.emit('validation-changed', widget.is_valid()) # Properties def _get_model(self): return self._model model = property(_get_model) # Public API def proxy_updated(self, widget, attribute, value): """ This is a hook that is called whenever the proxy updates the model. Implement it in the inherited class to perform actions that should be done each time the user changes something in the interface. This hook by default does nothing. @param widget: @param attribute: @param value: """ def update_many(self, attributes, value=ValueUnset, block=False): """ Like L{update} but takes a sequence of attributes @param attributes: sequence of attributes to update @param value: see L{update} @param block: see L{update} """ for attribute in attributes: self.update(attribute, value, block) def update(self, attribute, value=ValueUnset, block=False): """ Generic frontend function to update the contentss of a widget based on its model attribute name using the internal update functions. @param attribute: the name of the attribute whose widget we wish to updated. If accessing a radiobutton, specify its group name. @param value: specifies the value to set in the widget. If unspecified, it defaults to the current model's value (through an accessor, if it exists, or getattr). @param block: defines if we are to block cascading proxy updates triggered by this update. You should use block if you are calling update on *the same attribute that is currently being updated*. This means if you have hooked to a signal of the widget associated to that attribute, and you call update() for the *same attribute*, use block=True. And pray. 8). If block is set to False, the normal update mechanism will occur (the model being updated in the end, hopefully). """ if value is ValueUnset: # We want to obtain a value from our model if self._model is None: # We really want to avoid trying to update our UI if our # model doesn't exist yet and no value was provided. # update() is also called by user code, but it should be # safe to return here since you shouldn't need to code # around the lack of a model in your callbacks if you # can help it. value = ValueUnset else: value = kgetattr(self._model, attribute, ValueUnset) widget = self._model_attributes.get(attribute, None) if widget is None: raise AttributeError("Called update for `%s', which isn't " "attached to the proxy %s. Valid " "attributes are: %s (you may have " "forgetten to add `:' to the name in " "the widgets list)" % (attribute, self, self._model_attributes.keys())) # The type of value should match the data-type property. The two # exceptions to this rule are ValueUnset and None if not (value is ValueUnset or value is None): data_type = widget.get_property('data-type') value_type = type(value) if not isinstance(value, data_type): raise TypeError( "attribute %s of model %r requires a value of " "type %s, not %s" % ( attribute, self._model, data_type.__name__, value_type.__name__)) if block: block_widget(widget) self._view.handler_block(widget) widget.update(value) self._view.handler_unblock(widget) unblock_widget(widget) else: widget.update(value) return True def set_model(self, model, relax_type=False): """ Updates the model instance of the proxy. Allows a proxy interface to change model without the need to destroy and recreate the UI (which would cause flashing, at least) @param model: @param relax_type: """ if self._model is not None and model is not None: if (not relax_type and type(model) != type(self._model) and not isinstance(model, self._model.__class__)): raise TypeError("model has wrong type %s, expected %s" % (type(model), type(self._model))) # the following isn't strictly necessary, but it currently works # around a bug with reused ids in the attribute cache and also # makes a lot of sense for most applications (don't want a huge # eternal cache pointing to models that you're not using anyway) clear_attr_cache() # unregister previous proxy self._unregister_proxy_in_model() self._model = model for attribute, widget in self._model_attributes.items(): self._reset_widget(attribute, widget) def add_widget(self, name, widget): """ Adds a new widget to the proxy @param name: name of the widget @param widget: widget, must be a gtk.Widget subclass """ if name in self._model_attributes: raise TypeError("there is already a widget called %s" % name) if not isinstance(widget, gtk.Widget): raise TypeError("%r must be a gtk.Widget subclass" % widget) self._setup_widget(name, widget) def remove_widget(self, name): """ Removes a widget from the proxy @param name: the name of the widget to remove """ if not name in self._model_attributes: raise TypeError("there is no widget called %s" % name) widget = self._model_attributes.pop(name) widget.disconnect(widget.get_data('content-changed-id')) if IValidatableProxyWidget.providedBy(widget): for data_name in ('notify-visible-id', 'notify-sensitive-id'): widget.disconnect(widget.get_data(data_name)) # Backwards compatibility def new_model(self, model, relax_type=False): self.set_model(model) new_model = deprecated('set_model', log)(new_model) PIDA-0.5.1/contrib/kiwi/kiwi/ui/proxywidget.py0000644000175000017500000002674010652670746017276 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Gustavo Rahal # Johan Dahlin # Daniel Saran R. da Cunha # """Basic classes for widget support for the Kiwi Framework""" import gettext import gobject import gtk from gtk import gdk from kiwi import ValueUnset from kiwi.component import implements from kiwi.datatypes import ValidationError, converter, BaseConverter from kiwi.environ import environ from kiwi.interfaces import IProxyWidget, IValidatableProxyWidget from kiwi.log import Logger from kiwi.ui.gadgets import FadeOut from kiwi.utils import gsignal, gproperty log = Logger('widget proxy') _ = lambda m: gettext.dgettext('kiwi', m) class _PixbufConverter(BaseConverter): type = gdk.Pixbuf name = 'Pixbuf' def as_string(self, value, format='png'): buffer = [] value.save_to_callback(buffer.append, format) string = ''.join(buffer) return string def from_string(self, value, format='png'): loader = gdk.PixbufLoader(format) try: loader.write(value) loader.close() except gobject.GError, e: raise ValidationError(_("Could not load image: %s") % e) pixbuf = loader.get_pixbuf() return pixbuf converter.add(_PixbufConverter) class ProxyWidgetMixin(object): """This class is a mixin that provide a common interface for KiwiWidgets. Usually the Proxy class need to set and get data from the widgets. It also need a validation framework. @cvar allowed_data_types: A list of types which we are allowed to use in this class. """ implements(IProxyWidget) gsignal('content-changed') gsignal('validation-changed', bool) gsignal('validate', object, retval=object) gproperty('data-type', object, blurb='Data Type') gproperty('model-attribute', str, blurb='Model attribute') allowed_data_types = () # To be able to call the as/from_string without setting the data_type # property and still receiving a good warning. _converter = None def __init__(self): if not type(self.allowed_data_types) == tuple: raise TypeError("%s.allowed_data_types must be a tuple" % ( self.allowed_data_types)) self._data_format = None self._converter_options = {} # Properties def prop_set_data_type(self, data_type): """Set the data type for the widget @param data_type: can be None, a type object or a string with the name of the type object, so None, "" or 'str' """ if data_type is None: return data_type # This may convert from string to type, # A type object will always be returned data_type = converter.check_supported(data_type) if not issubclass(data_type, self.allowed_data_types): raise TypeError( "%s only accept %s types, not %r" % (self, ' or '.join([t.__name__ for t in self.allowed_data_types]), data_type)) self._converter = converter.get_converter(data_type) return data_type # Public API def set_data_format(self, format): self._data_format = format def set_options_for_datatype(self, datatype, **options): """Set some options to be passed to the datatype converter. Any additional parameter will be passed the the converter when converting an object to a string, for displaying in the widget. Note that the converter.as_string method should be able to handle such parameters. @param datatype: the datatype. """ if not options: raise ValueError self._converter_options[datatype] = options def read(self): """Get the content of the widget. The type of the return value @returns: None if the user input a invalid value @rtype: Must matche the data-type property. """ raise NotImplementedError def update(self, value): """ @param value: """ raise NotImplementedError # Private def _as_string(self, data): """Convert a value to a string @param data: data to convert """ conv = self._converter if conv is None: conv = converter.get_converter(str) return conv.as_string(data, format=self._data_format, **self._converter_options.get(conv.type,{})) def _from_string(self, data): """Convert a string to the data type of the widget This may raise a L{kiwi.datatypes.ValidationError} if conversion failed @param data: data to convert """ conv = self._converter if conv is None: conv = converter.get_converter(str) return conv.from_string(data) VALIDATION_ICON_WIDTH = 16 MANDATORY_ICON = gtk.STOCK_EDIT ERROR_ICON = gdk.pixbuf_new_from_file( environ.find_resource('pixmap', 'validation-error-16.png')) class ValidatableProxyWidgetMixin(ProxyWidgetMixin): """Class used by some Kiwi Widgets that need to support mandatory input and validation features such as custom validation and data-type validation. Mandatory support provides a way to warn the user when input is necessary. The validatation feature provides a way to check the data entered and to display information about what is wrong. """ implements(IValidatableProxyWidget) gproperty('mandatory', bool, default=False) def __init__(self, widget=None): ProxyWidgetMixin.__init__(self) self._valid = True self._fade = FadeOut(self) self._fade.connect('color-changed', self._on_fadeout__color_changed) # Override in subclass def update_background(self, color): "Implement in subclass" def get_background(self): "Implement in subclass" def set_pixbuf(self, pixbuf): "Implement in subclass" def get_icon_window(self): "Implement in subclass" def set_tooltip(self, text): "Implement in subclass" # Public API def is_valid(self): """ @returns: True if the widget is in validated state """ return self._valid def validate(self, force=False): """Checks if the data is valid. Validates data-type and custom validation. @param force: if True, force validation @returns: validated data or ValueUnset if it failed """ # If we're not visible or sensitive return a blank value, except # when forcing the validation if not force and (not self.get_property('visible') or not self.get_property('sensitive')): return ValueUnset try: data = self.read() log.debug('Read %r for %s' % (data, self.model_attribute)) # check if we should draw the mandatory icon # this need to be done before any data conversion because we # we don't want to end drawing two icons if self.mandatory and (data == None or data == '' or data == ValueUnset): self.set_blank() return ValueUnset else: # The widgets themselves have now valid the data # Next step is to call the application specificed # checks, which are found in the view. if data is not None and data is not ValueUnset: # this signal calls the on_widgetname__validate method # of the view class and gets the exception (if any). error = self.emit("validate", data) if error: raise error self.set_valid() return data except ValidationError, e: self.set_invalid(str(e)) return ValueUnset def set_valid(self): """Changes the validation state to valid, which will remove icons and reset the background color """ log.debug('Setting state for %s to VALID' % self.model_attribute) self._set_valid_state(True) self._fade.stop() self.set_pixbuf(None) def set_invalid(self, text=None, fade=True): """Changes the validation state to invalid. @param text: text of tooltip of None @param fade: if we should fade the background """ log.debug('Setting state for %s to INVALID' % self.model_attribute) self._set_valid_state(False) # If there is no error text, set a generic one so the error icon # still have a tooltip if not text: text = _("'%s' is not a valid value for this field") % self.read() self.set_tooltip(text) if not fade: self.set_pixbuf(ERROR_ICON) self.update_background(gtk.gdk.color_parse(self._fade.ERROR_COLOR)) return # When the fading animation is finished, set the error icon # We don't need to check if the state is valid, since stop() # (which removes this timeout) is called as soon as the user # types valid data. def done(fadeout, c): self.set_pixbuf(ERROR_ICON) self.queue_draw() fadeout.disconnect(c.signal_id) class SignalContainer: pass c = SignalContainer() c.signal_id = self._fade.connect('done', done, c) if self._fade.start(self.get_background()): self.set_pixbuf(None) def set_blank(self): """Changes the validation state to blank state, this only applies for mandatory widgets, draw an icon and set a tooltip""" log.debug('Setting state for %s to BLANK' % self.model_attribute) if self.mandatory: self._draw_stock_icon(MANDATORY_ICON) self.set_tooltip(_('This field is mandatory')) self._fade.stop() valid = False else: valid = True self._set_valid_state(valid) # Private def _set_valid_state(self, state): """Updates the validation state and emits a signal iff it changed""" if self._valid == state: return self.emit('validation-changed', state) self._valid = state def _draw_stock_icon(self, stock_id): icon = self.render_icon(stock_id, gtk.ICON_SIZE_MENU) self.set_pixbuf(icon) self.queue_draw() # Callbacks def _on_fadeout__color_changed(self, fadeout, color): self.update_background(color) PIDA-0.5.1/contrib/kiwi/kiwi/ui/search.py0000644000175000017500000006600710652670746016156 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """ Search related widgets """ import datetime import gettext import gobject import gtk from kiwi.component import implements from kiwi.db.query import (NumberQueryState, StringQueryState, DateQueryState, DateIntervalQueryState, QueryExecuter) from kiwi.enums import SearchFilterPosition from kiwi.interfaces import ISearchFilter from kiwi.python import enum from kiwi.ui.dateentry import DateEntry from kiwi.ui.delegates import SlaveDelegate from kiwi.ui.objectlist import ObjectList, SummaryLabel from kiwi.ui.widgets.combo import ProxyComboBox from kiwi.utils import gsignal, gproperty _ = lambda m: gettext.dgettext('kiwi', m) class DateSearchOption(object): """ Base class for Date search options A date search option is an interval of dates @cvar name: name of the search option """ name = None def get_interval(self): """ @returns: start date, end date @rtype: datetime.date tuple """ class Any(DateSearchOption): name = _('Any') def get_interval(self): return None, None class Today(DateSearchOption): name = _('Today') def get_interval(self): today = datetime.date.today() return today, today class Yesterday(DateSearchOption): name = _('Yesterday') def get_interval(self): yesterday = datetime.date.today() - datetime.timedelta(days=1) return yesterday, yesterday class LastWeek(DateSearchOption): name = _('Last week') def get_interval(self): today = datetime.date.today() return (today - datetime.timedelta(days=7), today) class LastMonth(DateSearchOption): name = _('Last month') def get_interval(self): today = datetime.date.today() year = today.year month = today.month - 1 if not month: month = 12 year -= 1 # Try 31 first then remove one until date() does not complain. day = today.day while True: try: start_date = datetime.date(year, month, day) break except ValueError: day -= 1 return start_date, datetime.date.today() class FixedIntervalSearchOption(DateSearchOption): start = None end = None def get_interval(self): return self.start, self.end class FixedDateSearchOption(DateSearchOption): date = None def get_interval(self): return self.date, self.date class SearchFilter(gtk.HBox): """ A base classed used by common search filters """ gproperty('label', str, flags=(gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT_ONLY)) gsignal('changed') implements(ISearchFilter) def __init__(self, label=''): self.__gobject_init__(label=label) self._label = label def do_set_property(self, pspec, value): if pspec.name == 'label': self._label = value else: raise AssertionError(pspec.name) def do_get_property(self, child, property_id, pspec): if pspec.name == 'label': return self._label else: raise AssertionError(pspec.name) def set_label(self, label): self._label = label def get_state(self): """ Implement this in a subclass """ raise NotImplementedError class DateSearchFilter(SearchFilter): """ A filter which helps you to search by a date interval. Can be customized through add_option. """ __gtype_name__ = 'DateSearchFilter' class Type(enum): (USER_DAY, USER_INTERVAL) = range(100, 102) def __init__(self, label=''): """ @param label: name of the search filter """ self._options = {} SearchFilter.__init__(self, label=label) self.set_border_width(6) label = gtk.Label(label) self.pack_start(label, False, False) label.show() self.mode = ProxyComboBox() self.mode.connect( 'content-changed', self._on_mode__content_changed) self.pack_start(self.mode, False, False, 6) self.mode.show() self.from_label = gtk.Label(_("From:")) self.pack_start(self.from_label, False, False) self.from_label.show() self.start_date = DateEntry() self._start_changed_id = self.start_date.connect( 'changed', self._on_start_date__changed) self.pack_start(self.start_date, False, False, 6) self.start_date.show() self.to_label = gtk.Label(_("To:")) self.pack_start(self.to_label, False, False) self.to_label.show() self.end_date = DateEntry() self._end_changed_id = self.end_date.connect( 'changed', self._on_end_date__changed) self.pack_start(self.end_date, False, False, 6) self.end_date.show() self.mode.prefill([ (_('Custom day'), DateSearchFilter.Type.USER_DAY), (_('Custom interval'), DateSearchFilter.Type.USER_INTERVAL), ]) for option in (Any, Today, Yesterday, LastWeek, LastMonth): self.add_option(option) self.mode.select_item_by_position(0) # # SearchFilter # def get_state(self): start = self.start_date.get_date() end = self.end_date.get_date() if start == end: return DateQueryState(filter=self, date=start) return DateIntervalQueryState(filter=self, start=start, end=end) # # Public API # def clear_options(self): """ Removes all previously added options """ self._options = {} self.mode.clear() def add_option(self, option_type, position=-2): """ Adds a date option @param option_type: option to add @type option_type: a L{DateSearchOption} subclass """ option = option_type() num = len(self.mode) + position self.mode.insert_item(num, option.name, option_type) self._options[option_type] = option def add_option_fixed(self, name, date, position=-2): """ Adds a fixed option, eg one for which date is not possible to modify. @param name: name of the option @param date: fixed data @param position: position to add the option at """ option_type = type('', (FixedDateSearchOption,), dict(name=name, date=date)) self.add_option(option_type, position=position) def add_option_fixed_interval(self, name, start, end, position=-2): """ Adds a fixed option interval, eg one for which the dates are not possible to modify. @param name: name of the option @param start: start of the fixed interval @param end: end of the fixed interval @param position: position to add the option at """ option_type = type('', (FixedIntervalSearchOption,), dict(name=name, start=start, end=end)) self.add_option(option_type, position=position) def get_start_date(self): """ @returns: start date @rtype: datetime.date or None """ return self.start_date.get_date() def get_end_date(self): """ @returns: end date @rtype: datetime.date or None """ return self.end_date.get_date() def set_use_date_entries(self, use_date_entries): """ Toggles the visibility of the user selectable date entries @param use_date_entries: """ self.from_label.props.visible = use_date_entries self.to_label.props.visible = use_date_entries self.start_date.props.visible = use_date_entries self.end_date.props.visible = use_date_entries def select(self, data=None, position=None): """ selects an item in the combo Data or position can be sent in. If nothing is sent in the first item will be selected, if any @param data: data to select @param position: position of data to select """ if data is not None and position is not None: raise TypeError("You can't send in both data and position") if data is None and position is None: position = 0 if position is not None: if len(self.mode): self.mode.select_item_by_position(position) elif data: self.mode.select(data) # # Private # def _update_dates(self): # This is called when we change mode date_type = self.mode.get_selected_data() if date_type is None: return # If we switch to a user selectable day, make sure that # both dates are set to today if date_type == DateSearchFilter.Type.USER_DAY: today = datetime.date.today() self.start_date.set_date(today) self.end_date.set_date(today) # And for user interval, set start to today and to tomorrow elif date_type == DateSearchFilter.Type.USER_INTERVAL: today = datetime.date.today() self.start_date.set_date(today) self.end_date.set_date(today + datetime.timedelta(days=1)) # Finally for pre-defined ones let the DateSearchOption decide what the # values are going to be, these dates are not user editable so # we don't need to do any checking. else: option = self._options.get(date_type) assert option, (date_type, self._options) start_date, end_date = option.get_interval() self.start_date.set_date(start_date) self.end_date.set_date(end_date) def _update_sensitivity(self): date_type = self.mode.get_selected_data() enabled = date_type == DateSearchFilter.Type.USER_INTERVAL self.to_label.set_sensitive(enabled) self.end_date.set_sensitive(enabled) enabled = (date_type == DateSearchFilter.Type.USER_INTERVAL or date_type == DateSearchFilter.Type.USER_DAY) self.from_label.set_sensitive(enabled) self.start_date.set_sensitive(enabled) def _internal_set_start_date(self, date): self.start_date.handler_block(self._start_changed_id) self.start_date.set_date(date) self.start_date.handler_unblock(self._start_changed_id) def _internal_set_end_date(self, date): self.end_date.handler_block(self._end_changed_id) self.end_date.set_date(date) self.end_date.handler_unblock(self._end_changed_id) # # Callbacks # def _on_mode__content_changed(self, mode): self._update_dates() self._update_sensitivity() self.emit('changed') def _on_start_date__changed(self, start_date): date_type = self.mode.get_selected_data() start = start_date.get_date() # For user days, just make sure that the date entries # always are in sync if date_type == DateSearchFilter.Type.USER_DAY: self._internal_set_end_date(start) # Make sure that we cannot select a start date after # the end date, be nice and increase the end date if # the start date happen to be the same elif date_type == DateSearchFilter.Type.USER_INTERVAL: end = self.end_date.get_date() if not start or not end: return if start >= end: self._internal_set_end_date(start + datetime.timedelta(days=1)) def _on_end_date__changed(self, end_date): date_type = self.mode.get_selected_data() # We don't need to do anything for user day, since # this the end date widget is disabled if date_type == DateSearchFilter.Type.USER_DAY: pass # Make sure that we cannot select an end date before # the start date, be nice and decrease the start date if # the end date happen to be the same elif date_type == DateSearchFilter.Type.USER_INTERVAL: start = self.start_date.get_date() end = end_date.get_date() if not start or not end: return if end <= start: self._internal_set_start_date(end - datetime.timedelta(days=1)) class ComboSearchFilter(SearchFilter): """ - a label - a combo with a set of predefined item to select from """ __gtype_name__ = 'ComboSearchFilter' def __init__(self, label='', values=None): """ @param name: name of the search filter @param values: items to put in the combo, see L{kiwi.ui.widgets.combo.ProxyComboBox.prefill} """ SearchFilter.__init__(self, label=label) label = gtk.Label(label) self.pack_start(label, False, False) label.show() self.combo = ProxyComboBox() if values: self.combo.prefill(values) self.combo.connect( 'content-changed', self._on_combo__content_changed) self.pack_start(self.combo, False, False, 6) self.combo.show() # # SearchFilter # def get_state(self): return NumberQueryState(filter=self, value=self.combo.get_selected_data()) # # Public API # def select(self, data): """ selects an item in the combo @param data: what to select """ self.combo.select(data) # # Callbacks # def _on_combo__content_changed(self, mode): self.emit('changed') class StringSearchFilter(SearchFilter): """ - a label - an entry @ivar entry: the entry @ivar label: the label """ def __init__(self, label, chars=0): """ @param label: label of the search filter @param chars: maximum number of chars used by the search entry """ SearchFilter.__init__(self, label=label) self.label = gtk.Label(label) self.pack_start(self.label, False, False) self.label.show() self.entry = gtk.Entry() if chars: self.entry.set_width_chars(chars) self.pack_start(self.entry, False, False, 6) self.entry.show() # # SearchFilter # def get_state(self): return StringQueryState(filter=self, text=self.entry.get_text()) # # Public API # def set_label(self, label): self.label.set_text(label) # # Other UI pieces # class SearchResults(ObjectList): def __init__(self, columns): ObjectList.__init__(self, columns) class SearchContainer(gtk.VBox): """ A search container is a widget which consists of: - search entry (w/ a label) (L{StringSearchFilter}) - search button - objectlist result (L{SearchResult}) - a query executer (L{kiwi.db.query.QueryExecuter}) Additionally you can add a number of search filters to the SearchContainer. You can chose if you want to add the filter in the top-left corner of bottom, see L{SearchFilterPosition} """ __gtype_name__ = 'SearchContainer' gproperty('filter-label', str) def __init__(self, columns=None, chars=25): """ @param columns: a list of L{kiwi.ui.objectlist.Column} @param chars: maximum number of chars used by the search entry """ gtk.VBox.__init__(self) self._columns = columns self._search_filters = [] self._query_executer = None self._auto_search = True self._summary_label = None search_filter = StringSearchFilter(_('Search:'), chars=chars) search_filter.connect('changed', self._on_search_filter__changed) self._search_filters.append(search_filter) self._primary_filter = search_filter self._create_ui() # # GObject # def do_set_property(self, pspec, value): if pspec.name == 'filter-label': self._primary_filter.set_label(value) else: raise AssertionError(pspec.name) def do_get_property(self, pspec): if pspec.name == 'filter-label': return self._primary_filter.get_label() else: raise AssertionError(pspec.name) # # GtkContainer # def do_set_child_property(self, child, property_id, value, pspec): if pspec.name == 'filter-position': if value == 'top': pos = SearchFilterPosition.TOP elif value == 'bottom': pos = SearchFilterPosition.BOTTOM else: raise Exception(pos) self.set_filter_position(child, pos) else: raise AssertionError(pspec.name) def do_get_child_property(self, child, property_id, pspec): if pspec.name == 'filter-position': return self.get_filter_position(child) else: raise AssertionError(pspec.name) # # Public API # def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM, columns=None, callback=None): """ Adds a search filter @param search_filter: the search filter @param postition: a L{SearchFilterPosition} enum @param columns: @param callback: """ if not isinstance(search_filter, SearchFilter): raise TypeError("search_filter must be a SearchFilter subclass, " "not %r" % (search_filter,)) if columns and callback: raise TypeError("Cannot specify both column and callback") executer = self.get_query_executer() if executer: if columns: executer.set_filter_columns(search_filter, columns) if callback: if not callable(callback): raise TypeError("callback must be callable") executer.add_filter_query_callback(search_filter, callback) else: if columns or callback: raise TypeError( "You need to set an executor before calling set_filters " "with columns or callback set") assert not search_filter.parent self.set_filter_position(search_filter, position) search_filter.connect('changed', self._on_search_filter__changed) self._search_filters.append(search_filter) def set_filter_position(self, search_filter, position): """ @param search_filter: @param position: """ if search_filter.parent: search_filter.parent.remove(search_filter) if position == SearchFilterPosition.TOP: self.hbox.pack_start(search_filter, False, False) self.hbox.reorder_child(search_filter, 0) elif position == SearchFilterPosition.BOTTOM: self.pack_start(search_filter, False, False) search_filter.show() def get_filter_position(self, search_filter): """ @param search_filter: """ if search_filter.parent == self.hbox: return SearchFilterPosition.TOP elif search_filter.parent == self: return SearchFilterPosition.BOTTOM else: raise AssertionError(search_filter) def set_query_executer(self, querty_executer): """ Ties a QueryExecuter instance to the SearchContainer class @param querty_executer: a querty executer @type querty_executer: a L{QueryExecuter} subclass """ if not isinstance(querty_executer, QueryExecuter): raise TypeError("querty_executer must be a QueryExecuter instance") self._query_executer = querty_executer def get_query_executer(self): """ Fetchs the QueryExecuter for the SearchContainer @returns: a querty executer @rtype: a L{QueryExecuter} subclass """ return self._query_executer def get_primary_filter(self): """ Fetches the primary filter for the SearchContainer. The primary filter is the filter attached to the standard entry normally used to do free text searching @returns: the primary filter """ return self._primary_filter def search(self): """ Starts a search. Fetches the states of all filters and send it to a query executer and finally puts the result in the result class """ if not self._query_executer: raise ValueError("A query executer needs to be set at this point") states = [(sf.get_state()) for sf in self._search_filters] results = self._query_executer.search(states) self.results.clear() self.results.extend(results) if self._summary_label: self._summary_label.update_total() def set_auto_search(self, auto_search): """ Enables/Disables auto search which means that the search result box is automatically populated when a filter changes @param auto_search: True to enable, False to disable """ self._auto_search = auto_search def set_text_field_columns(self, columns): """ @param columns: """ if self._primary_filter is None: raise ValueError("The primary filter is disabled") if not self._query_executer: raise ValueError("A query executer needs to be set at this point") self._query_executer.set_filter_columns(self._primary_filter, columns) def disable_search_entry(self): """ Disables the search entry """ self.search_entry.hide() self._primary_filter.hide() self._search_filters.remove(self._primary_filter) self._primary_filter = None def set_summary_label(self, column, label='Total:', format='%s'): """ Adds a summary label to the result set @param column: the column to sum from @param label: the label to use, defaults to 'Total:' @param format: the format, defaults to '%%s', must include '%%s' """ if not '%s' in format: raise ValueError("format must contain %s") try: self.results.get_column_by_name(column) except LookupError: raise ValueError("%s is not a valid column" % (column,)) if self._summary_label: self._summary_label.parent.remove(self._summary_label) self._summary_label = SummaryLabel(klist=self.results, column=column, label=label, value_format=format) self.pack_end(self._summary_label, False, False) self.reorder_child(self._summary_label, 1) self._summary_label.show() # # Callbacks # def _on_search_button__clicked(self, button): self.search() def _on_search_entry__activate(self, button): self.search() def _on_search_filter__changed(self, search_filter): if self._auto_search: self.search() # # Private # def _create_ui(self): hbox = gtk.HBox() hbox.set_border_width(6) self.pack_start(hbox, False, False) hbox.show() self.hbox = hbox widget = self._primary_filter self.hbox.pack_start(widget, False, False) widget.show() self.search_entry = self._primary_filter.entry self.search_entry.connect('activate', self._on_search_entry__activate) button = gtk.Button(stock=gtk.STOCK_FIND) button.connect('clicked', self._on_search_button__clicked) hbox.pack_start(button, False, False) button.show() self.results = SearchResults(self._columns) self.pack_end(self.results, True, True, 6) self.results.show() # This is not quite a requirement at this point, only # do it if install_child_property is available, eg pygtk >= 2.10 if hasattr(gtk.Container, 'install_child_property'): SearchContainer.install_child_property( 1, ('filter-position', str, 'Search Filter Position', 'The search filter position in the container', '', gobject.PARAM_READWRITE)) class SearchSlaveDelegate(SlaveDelegate): """ @ivar results: the results list of the container @ivar search: the L{SearchContainer} """ def __init__(self, columns): self.search = SearchContainer(columns) SlaveDelegate.__init__(self, toplevel=self.search) self.results = self.search.results self.search.show() # # Public API # def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM, columns=None, callback=None): """ See L{SearchSlaveDelegate.add_filter} """ self.search.add_filter(search_filter, position, columns, callback) def set_query_executer(self, querty_executer): """ See L{SearchSlaveDelegate.set_query_executer} """ self.search.set_query_executer(querty_executer) def set_text_field_columns(self, columns): """ See L{SearchSlaveDelegate.set_text_field_columns} """ self.search.set_text_field_columns(columns) def get_primary_filter(self): """ Fetches the primary filter of the SearchSlaveDelegate @returns: primary filter """ return self.search.get_primary_filter() def focus_search_entry(self): """ Grabs the focus of the search entry """ self.search.search_entry.grab_focus() def refresh(self): """ Triggers a search again with the currently selected inputs """ self.search.search() def clear(self): """ Clears the result list """ self.search.results.clear() def disable_search_entry(self): """ Disables the search entry """ self.search.disable_search_entry() def set_summary_label(self, column, label='Total:', format='%s'): """ See L{SearchContainer.set_summary_label} """ self.search.set_summary_label(column, label, format) # # Overridable # def get_columns(self): """ This needs to be implemented in a subclass @returns: columns @rtype: list of L{kiwi.ui.objectlist.Column} """ raise NotImplementedError PIDA-0.5.1/contrib/kiwi/kiwi/ui/selectablebox.py0000644000175000017500000001411710652670746017520 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """ A box which you can select and will have a border around it when you click on any widgets in it """ import gtk from gtk import gdk class SelectableBox(object): def __init__(self, width=4): self._selected = None self._draw_gc = None self._selection_width = width self.unset_flags(gtk.NO_WINDOW) self.set_redraw_on_allocate(True) self.set_spacing(width) self.set_border_width(width) # Public API def get_selected(self): """ @returns: the currently selected widget """ return self._selected def set_selected(self, widget): """ @param widget: widget to select, must be a children of self """ if not widget in self.get_children(): raise ValueError("widget must be a child of %r" % self) old_selected = self._selected self._selected = widget if old_selected != widget: self.queue_draw() def pack_start(self, child, expand=True, fill=True, padding=0): """ Identical to gtk.Box.pack_start """ super(SelectableBox, self).pack_start(child, expand=expand, fill=fill, padding=padding) self._child_added(child) def pack_end(self, child, expand=True, fill=True, padding=0): """ Identical to gtk.Box.pack_end """ super(SelectableBox, self).pack_end(child, expand=expand, fill=fill, padding=padding) self._child_added(child) def add(self, child): """ Identical to gtk.Container.add """ super(SelectableBox, self).add(child) self._child_added(child) def update_selection(self): selected = self._selected if not selected: return border = self._selection_width x, y, w, h = selected.allocation self.window.draw_rectangle(self._draw_gc, False, x - (border / 2), y - (border / 2), w + border, h + border) # GtkWidget def do_realize(self): assert not (self.flags() & gtk.NO_WINDOW) self.set_flags(self.flags() | gtk.REALIZED) self.window = gdk.Window(self.get_parent_window(), width=self.allocation.width, height=self.allocation.height, window_type=gdk.WINDOW_CHILD, wclass=gdk.INPUT_OUTPUT, event_mask=(self.get_events() | gdk.EXPOSURE_MASK | gdk.BUTTON_PRESS_MASK)) self.window.set_user_data(self) self.style.attach(self.window) self.style.set_background(self.window, gtk.STATE_NORMAL) self._draw_gc = gdk.GC(self.window, line_width=self._selection_width, line_style=gdk.SOLID, foreground=self.style.bg[gtk.STATE_SELECTED]) def do_button_press_event(self, event): selected = self._get_child_at_pos(int(event.x), int(event.y)) if selected: self.set_selected(selected) # Private def _get_child_at_pos(self, x, y): """ @param x: x coordinate @type x: integer @param y: y coordinate @type y: integer """ toplevel = self.get_toplevel() for child in self.get_children(): coords = toplevel.translate_coordinates(child, x, y) if not coords: continue child_x, child_y = coords if (0 <= child_x < child.allocation.width and 0 <= child_y < child.allocation.height and child.flags() & (gtk.MAPPED | gtk.VISIBLE)): return child def _child_added(self, child): child.connect('button-press-event', lambda child, e: self.set_selected(child)) class SelectableHBox(SelectableBox, gtk.HBox): __gtype_name__ = 'SelectableHBox' def __init__(self, width=4): gtk.HBox.__init__(self) SelectableBox.__init__(self, width=width) do_realize = SelectableBox.do_realize do_button_press_event = SelectableBox.do_button_press_event def do_size_allocate(self, allocation): gtk.HBox.do_size_allocate(self, allocation) if self.flags() & gtk.REALIZED: self.window.move_resize(*allocation) def do_expose_event(self, event): gtk.HBox.do_expose_event(self, event) self.update_selection() class SelectableVBox(SelectableBox, gtk.VBox): __gtype_name__ = 'SelectableVBox' def __init__(self, width=4): gtk.VBox.__init__(self) SelectableBox.__init__(self, width=width) do_realize = SelectableBox.do_realize do_button_press_event = SelectableBox.do_button_press_event def do_size_allocate(self, allocation): gtk.VBox.do_size_allocate(self, allocation) if self.flags() & gtk.REALIZED: self.window.move_resize(*allocation) def do_expose_event(self, event): gtk.VBox.do_expose_event(self, event) self.update_selection() PIDA-0.5.1/contrib/kiwi/kiwi/ui/tooltip.py0000644000175000017500000000720410652670746016375 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """A tooltip popup window which only pop ups on demand, which makes it possible for us to tie it to a specific gtk.gdk.Window """ import gobject import gtk DEFAULT_DELAY = 500 BORDER_WIDTH = 4 class Tooltip(gtk.Window): def __init__(self, widget): gtk.Window.__init__(self, gtk.WINDOW_POPUP) # from gtktooltips.c:gtk_tooltips_force_window self.set_app_paintable(True) self.set_resizable(False) self.set_name("gtk-tooltips") self.set_border_width(BORDER_WIDTH) self.connect('expose-event', self._on__expose_event) self._label = gtk.Label() self.add(self._label) self._show_timeout_id = -1 # from gtktooltips.c:gtk_tooltips_draw_tips def _calculate_pos(self, widget): screen = widget.get_screen() w, h = self.size_request() x, y = widget.window.get_origin() if widget.flags() & gtk.NO_WINDOW: x += widget.allocation.x y += widget.allocation.y x = screen.get_root_window().get_pointer()[0] x -= (w / 2 + BORDER_WIDTH) pointer_screen, px, py, _ = screen.get_display().get_pointer() if pointer_screen != screen: px = x py = y monitor_num = screen.get_monitor_at_point(px, py) monitor = screen.get_monitor_geometry(monitor_num) if (x + w) > monitor.x + monitor.width: x -= (x + w) - (monitor.x + monitor.width); elif x < monitor.x: x = monitor.x if ((y + h + widget.allocation.height + BORDER_WIDTH) > monitor.y + monitor.height): y = y - h - BORDER_WIDTH else: y = y + widget.allocation.height + BORDER_WIDTH return x, y # from gtktooltips.c:gtk_tooltips_paint_window def _on__expose_event(self, window, event): w, h = window.size_request() window.style.paint_flat_box(window.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, window, "tooltip", 0, 0, w, h) return False def _real_display(self, widget): x, y = self._calculate_pos(widget) self.move(x, y) self.show_all() # Public API def set_text(self, text): self._label.set_text(text) def hide(self): gtk.Window.hide(self) gobject.source_remove(self._show_timeout_id) self._show_timeout_id = -1 def display(self, widget): if not self._label.get_text(): return if self._show_timeout_id != -1: return self._show_timeout_id = gobject.timeout_add(DEFAULT_DELAY, self._real_display, widget) PIDA-0.5.1/contrib/kiwi/kiwi/ui/views.py0000644000175000017500000010630710652670746016044 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2001-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Jon Nelson # Lorenzo Gil Sanchez # Johan Dahlin # Henrique Romano # """ Defines the View classes that are included in the Kiwi Framework, which are the base of Delegates and Proxies. """ import os import re import string import gobject import gtk from gtk import gdk from kiwi.environ import environ from kiwi.interfaces import IValidatableProxyWidget from kiwi.log import Logger from kiwi.python import namedAny from kiwi.utils import gsignal, type_register from kiwi.ui.gadgets import quit_if_last from kiwi.ui.proxy import Proxy log = Logger('kiwi.view') _non_interactive = ( gtk.Label, gtk.Alignment, gtk.AccelLabel, gtk.Arrow, gtk.EventBox, gtk.Fixed, gtk.Frame, gtk.HBox, gtk.HButtonBox, gtk.HPaned, gtk.HSeparator, gtk.Layout, gtk.Progress, gtk.ProgressBar, gtk.ScrolledWindow, gtk.Table, gtk.VBox, gtk.VButtonBox, gtk.VPaned, gtk.VSeparator, gtk.Window, ) color_red = gdk.color_parse('red') color_black = gdk.color_parse('black') # # Signal brokers # method_regex = re.compile(r'^(on|after)_(\w+)__(\w+)$') class SignalBroker(object): def __init__(self, view, controller): methods = controller._get_all_methods() self._do_connections(view, methods) def _do_connections(self, view, methods): """This method allows subclasses to add more connection mechanism""" self._autoconnect_by_method_name(view, methods) def _autoconnect_by_method_name(self, view, methods): """ Offers autoconnection of widget signals based on function names. You simply need to define your controller method in the format:: def on_widget_name__signal_name(self, widget): In other words, start the method by "on_", followed by the widget name, followed by two underscores ("__"), followed by the signal name. Note: If more than one double underscore sequences are in the string, the last one is assumed to separate the signal name. """ self._autoconnected = {} for fname in methods.keys(): # `on_x__y' has 7 chars and is the smallest possible handler if len(fname) < 7: continue match = method_regex.match(fname) if match is None: continue after, w_name, signal = match.groups() widget = getattr(view, w_name, None) if widget is None: raise AttributeError("couldn't find widget %s in %s" % (w_name, view)) if not isinstance(widget, gobject.GObject): raise AttributeError("%s (%s) is not a widget or an action " "and can't be connected to" % (w_name, widget)) # Must use getattr; using the class method ends up with it # being called unbound and lacking, thus, "self". try: if after: signal_id = widget.connect_after(signal, methods[fname]) else: signal_id = widget.connect(signal, methods[fname]) except TypeError: raise TypeError("Widget %s doesn't provide a signal %s" % ( widget.__class__, signal)) self._autoconnected.setdefault(widget, []).append(( signal, signal_id)) def handler_block(self, widget, signal_name): signals = self._autoconnected if not widget in signals: return for signal, signal_id in signals[widget]: if signal_name is None or signal == signal_name: widget.handler_block(signal_id) def handler_unblock(self, widget, signal_name): signals = self._autoconnected if not widget in signals: return for signal, signal_id in signals[widget]: if signal_name is None or signal == signal_name: widget.handler_unblock(signal_id) def disconnect_autoconnected(self): for widget, signals in self._autoconnected.items(): for signal in signals: widget.disconnect(signal[1]) class GladeSignalBroker(SignalBroker): def _do_connections(self, view, methods): super(GladeSignalBroker, self)._do_connections(view, methods) self._connect_glade_signals(view, methods) def _connect_glade_signals(self, view, methods): # mainly because the two classes cannot have a common base # class. studying the class layout carefully or using # composition may be necessary. # called by framework.basecontroller. takes a controller, and # creates the dictionary to attach to the signals in the tree. if not methods: raise AssertionError("controller must be provided") dict = {} for name, method in methods.items(): if callable(method): dict[name] = method view._glade_adaptor.signal_autoconnect(dict) class SlaveView(gobject.GObject): """ Base class for all View classes. Defines the essential class attributes (controller, toplevel, widgets) and handles initialization of toplevel and widgets. Once AbstractView.__init__() has been called, you can be sure self.toplevel and self.widgets are sane and processed. When a controller is associated with a View (the view should be passed in to its constructor) it will try and call a hook in the View called _attach_callbacks. See AbstractGladeView for an example of this method. """ controller = None toplevel = None widgets = [] toplevel_name = None gladefile = None domain = None # This signal is emited when the view wants to return a result value gsignal("result", object) # This is emitted when validation changed for a view # Used by parents views to know when child slaves changes gsignal('validation-changed', bool) def __init__(self, toplevel=None, widgets=None, gladefile=None, toplevel_name=None, domain=None): """ Creates a new SlaveView. Sets up self.toplevel and self.widgets and checks for reserved names. """ gobject.GObject.__init__(self) self._broker = None self.slaves = {} self._proxies = [] self._valid = True # slave/widget name -> validation status self._validation = {} # stores the function that will be called when widgets # validity is checked self._validate_function = None # setup the initial state with the value of the arguments or the # class variables klass = type(self) self.toplevel = toplevel or klass.toplevel self.widgets = widgets or klass.widgets self.gladefile = gladefile or klass.gladefile self.toplevel_name = (toplevel_name or klass.toplevel_name or self.gladefile) self.domain = domain or klass.domain self._check_reserved() self._glade_adaptor = self.get_glade_adaptor() self.toplevel = self._get_toplevel() # grab the accel groups self._accel_groups = gtk.accel_groups_from_object(self.toplevel) # XXX: support normal widgets # notebook page label widget -> # dict (slave name -> validation status) self._notebook_validation = {} self._notebooks = self._get_notebooks() def _get_notebooks(self): if not self._glade_adaptor: return [] return [widget for widget in self._glade_adaptor.get_widgets() if isinstance(widget, gtk.Notebook)] def _check_reserved(self): for reserved in ["widgets", "toplevel", "gladefile", "tree", "model", "controller"]: # XXX: take into account widget constructor? if reserved in self.widgets: raise ValueError( "The widgets list for %s contains a widget named `%s', " "which is a reserved. name""" % (self, reserved)) def _get_toplevel(self): toplevel = self.toplevel if not toplevel and self.toplevel_name: toplevel = self.get_widget(self.toplevel_name) if not toplevel: raise TypeError("A View requires an instance variable " "called toplevel that specifies the " "toplevel widget in it") if isinstance(toplevel, gtk.Window): if toplevel.flags() & gtk.VISIBLE: log.warn("Toplevel widget %s (%s) is visible; that's probably " "wrong" % (toplevel, toplevel.get_name())) return toplevel def get_glade_adaptor(self): """Special init code that subclasses may want to override.""" if not self.gladefile: return glade_adaptor = _open_glade(self, self.gladefile, self.domain) container_name = self.toplevel_name if not container_name: raise ValueError( "You provided a gladefile %s to grab the widgets from " "but you didn't give me a toplevel/container name!" % self.gladefile) # a SlaveView inside a glade file needs to come inside a toplevel # window, so we pull our slave out from it, grab its groups and # muerder it later shell = glade_adaptor.get_widget(container_name) if not isinstance(shell, gtk.Window): raise TypeError("Container %s should be a Window, found %s" % ( container_name, type(shell))) self.toplevel = shell.get_child() shell.remove(self.toplevel) shell.destroy() return glade_adaptor # # Hooks # def on_attach(self, parent): """ Hook function called when attach_slave is performed on slave views. """ pass def on_startup(self): """ This is a virtual method that can be customized by classes that want to perform additional initalization after a controller has been set for it. If you need this, add this method to your View subclass and BaseController will call it when the controller is set to the proxy.""" pass # # Accessors # def get_toplevel(self): """Returns the toplevel widget in the view""" return self.toplevel def get_widget(self, name): """Retrieves the named widget from the View""" name = string.replace(name, '.', '_') if self._glade_adaptor: widget = self._glade_adaptor.get_widget(name) else: widget = getattr(self, name, None) if widget is None: raise AttributeError("Widget %s not found in view %s" % (name, self)) if not isinstance(widget, gtk.Widget): raise TypeError("%s in view %s is not a Widget" % (name, self)) return widget def set_controller(self, controller): """ Sets the view's controller, checking to see if one has already been set before.""" # Only one controller per view, please if self.controller: raise AssertionError("This view already has a controller: %s" % self.controller) self.controller = controller # # GTK+ proxies and convenience functions # def show_and_loop(self, parent=None): """ Runs show() and runs the GTK+ event loop. If the parent argument is supplied and is a valid view, this view is set as a transient for the parent view @param parent: """ self.show() if parent: self.set_transient_for(parent) gtk.main() def show(self, *args): """Shows the toplevel widget""" self.toplevel.show() def show_all(self, *args): """Shows all widgets attached to the toplevel widget""" if self._glade_adaptor is not None: raise AssertionError("You don't want to call show_all on a " "SlaveView. Use show() instead.") self.toplevel.show_all() def focus_toplevel(self): """Focuses the toplevel widget in the view""" # XXX: warn if there is no GdkWindow if self.toplevel and self.toplevel.window is not None: self.toplevel.grab_focus() def focus_topmost(self, widgets=None): """ Looks through widgets specified (if no widgets are specified, look through all widgets attached to the view and sets focus to the widget that is rendered in the position closest to the view window's top and left - widgets: a list of widget names to be searched through """ widget = self.get_topmost_widget(widgets, can_focus=True) if widget is not None: widget.grab_focus() # So it can be idle_added safely return False def get_topmost_widget(self, widgets=None, can_focus=False): """ A real hack; returns the widget that is most to the left and top of the window. - widgets: a list of widget names. If widgets is supplied, it only checks in the widgets in the list; otherwise, it looks at the widgets named in self.widgets, or, if self.widgets is None, looks through all widgets attached to the view. - can_focus: boolean, if set only searches through widget that can be focused """ # XXX: recurse through containers from toplevel widget, better # idea and will work. widgets = widgets or self.widgets or self.__dict__.keys() top_widget = None for widget_name in widgets: widget = getattr(self, widget_name) if not isinstance(widget, gtk.Widget): continue if not widget.flags() & gtk.REALIZED: # If widget isn't realized but we have a toplevel # window, it's safe to realize it. If this check isn't # performed, we get a crash as per # http://bugzilla.gnome.org/show_bug.cgi?id=107872 if isinstance(widget.get_toplevel(), gtk.Window): widget.realize() else: log.warn("get_topmost_widget: widget %s was not realized" % widget_name) continue if can_focus: # Combos don't focus, but their entries do if isinstance(widget, gtk.Combo): widget = widget.entry if not widget.flags() & gtk.CAN_FOCUS or \ isinstance(widget, (gtk.Label, gtk.HSeparator, gtk.VSeparator, gtk.Window)): continue if top_widget: allocation = widget.allocation top_allocation = getattr(top_widget, 'allocation', None) assert top_allocation != None if (top_allocation[0] + top_allocation[1] > allocation[0] + allocation[1]): top_widget = widget else: top_widget = widget return top_widget # # Callback handling # def _attach_callbacks(self, controller): if self._glade_adaptor is None: brokerclass = SignalBroker else: brokerclass = GladeSignalBroker self._broker = brokerclass(self, controller) if self.toplevel: self.toplevel.connect("key-press-event", controller.on_key_press) # # Slave handling # def attach_slave(self, name, slave): """Attaches a slaveview to the current view, substituting the widget specified by name. the widget specified *must* be a eventbox; its child widget will be removed and substituted for the specified slaveview's toplevel widget:: .-----------------------. the widget that is indicated in the diagram |window/view (self.view)| as placeholder will be substituted for the | .----------------. | slaveview's toplevel. | | eventbox (name)| | .-----------------. | |.--------------.| |slaveview (slave)| | || placeholder <----. |.---------------.| | |'--------------'| \___ toplevel || | '----------------' | ''---------------'| '-----------------------' '-----------------' the original way of attachment (naming the *child* widget instead of the eventbox) is still supported for compatibility reasons but will print a warning. """ log('%s: Attaching slave %s of type %s' % (self.__class__.__name__, name, slave.__class__.__name__)) if name in self.slaves: # XXX: TypeError log.warn("A slave with name %s is already attached to %r" % ( name, self)) self.slaves[name] = slave if not isinstance(slave, SlaveView): raise TypeError("slave must be a SlaveView, not a %s" % type(slave)) shell = slave.get_toplevel() if isinstance(shell, gtk.Window): # view with toplevel window new_widget = shell.get_child() shell.remove(new_widget) # remove from window to allow reparent else: # slaveview new_widget = shell placeholder = self.get_widget(name) placeholder.set_data('kiwi::slave', self) if not placeholder: raise AttributeError( "slave container widget `%s' not found" % name) parent = placeholder.get_parent() if slave._accel_groups: # take care of accelerator groups; attach to parent window if we # have one; if embedding a slave into another slave, store its # accel groups; otherwise complain if we're dropping the # accelerators win = parent.get_toplevel() if isinstance(win, gtk.Window): # use idle_add to be sure we attach the groups as late # as possible and avoid reattaching groups -- see # comment in _attach_groups. gtk.idle_add(self._attach_groups, win, slave._accel_groups) elif isinstance(self, SlaveView): self._accel_groups.extend(slave._accel_groups) else: log.warn("attached slave %s to parent %s, but parent lacked " "a window and was not a slave view" % (slave, self)) slave._accel_groups = [] # Merge the sizegroups of the slave that is being attached with the # sizegroups of where it is being attached to. Only the sizegroups # with the same name will be merged. for sizegroup in slave.get_sizegroups(): self._merge_sizegroup(sizegroup) if isinstance(placeholder, gtk.EventBox): # standard mechanism child = placeholder.get_child() if child is not None: placeholder.remove(child) placeholder.set_visible_window(False) placeholder.add(new_widget) elif isinstance(parent, gtk.EventBox): # backwards compatibility log.warn("attach_slave's api has changed: read docs, update code!") parent.remove(placeholder) parent.add(new_widget) else: raise TypeError( "widget to be replaced must be wrapped in eventbox") # when attaching a slave we usually want it visible parent.show() # call slave's callback slave.on_attach(self) slave.connect('validation-changed', self._on_child__validation_changed, name) for notebook in self._notebooks: for child in notebook.get_children(): if not shell.is_ancestor(child): continue label = notebook.get_tab_label(child) slave.connect('validation-changed', self._on_notebook_slave__validation_changed, name, label) self._notebook_validation[label] = {} # Fire of an initial notification slave.check_and_notify_validity(force=True) # return placeholder we just removed return placeholder def get_sizegroups(self): """ @returns: a list of sizegroups for the current view. """ if not self._glade_adaptor: return [] return self._glade_adaptor.get_sizegroups() def _merge_sizegroup(self, other_sizegroup): # Merge sizegroup from other with self that have the same name. # Actually, no merging is being done, since the old group is preserved name = other_sizegroup.get_data('gazpacho::object-id') sizegroup = getattr(self, name, None) if not sizegroup: return widgets = other_sizegroup.get_data('gazpacho::sizegroup-widgets') if not widgets: return for widget in widgets: sizegroup.add_widget(widget) def detach_slave(self, name): """ Detatch a slave called name from view """ if not name in self.slaves: raise LookupError("There is no slaved called %s attached to %r" % (name, self)) del self.slaves[name] def _attach_groups(self, win, accel_groups): # get groups currently attached to the window; we use them # to avoid reattaching an accelerator to the same window, which # generates messages like: # # gtk-critical **: file gtkaccelgroup.c: line 188 # (gtk_accel_group_attach): assertion `g_slist_find # (accel_group->attach_objects, object) == null' failed. # # interestingly, this happens many times with notebook, # because libglade creates and attaches groups in runtime to # its toplevel window. current_groups = gtk.accel_groups_from_object(win) for group in accel_groups: if group in current_groups: # skip group already attached continue win.add_accel_group(group) def get_slave(self, holder): return self.slaves.get(holder) # # Signal connection # def connect_multiple(self, widgets, signal, handler, after=False): """ Connect the same handler to the specified signal for a number of widgets. - widgets: a list of GtkWidgets - signal: a string specifying the signals - handler: a callback method - after: a boolean; if TRUE, we use connect_after(), otherwise, connect() """ if not isinstance(widgets, (list, tuple)): raise TypeError("widgets must be a list, found %s" % widgets) for widget in widgets: if not isinstance(widget, gtk.Widget): raise TypeError( "Only Gtk widgets may be passed in list, found\n%s" % widget) if after: widget.connect_after(signal, handler) else: widget.connect(signal, handler) def disconnect_autoconnected(self): """ Disconnect handlers previously connected with autoconnect_signals()""" self._broker.disconnect_autoconnected() def handler_block(self, widget, signal_name=None): # XXX: Warning, or bail out? if not self._broker: return self._broker.handler_block(widget, signal_name) def handler_unblock(self, widget, signal_name=None): if not self._broker: return self._broker.handler_unblock(widget, signal_name) # # Proxies # def add_proxy(self, model=None, widgets=None): """ Add a proxy to this view that automatically update a model when the view changes. Arguments: - model. the object we are proxing. It can be None if we don't have a model yet and we want to display the interface and set it up with future models. - widgets. the list of widgets that contains model attributes to be proxied. If it is None (or not specified) it will be the whole list of widgets this View has. This method return a Proxy object that you may want to use to force updates or setting new models. Keep a reference to it since there is no way to get that proxy later on. You have been warned (tm) """ log('%s: adding proxy for %s' % ( self.__class__.__name__, model and model.__class__.__name__)) widgets = widgets or self.widgets for widget_name in widgets: widget = getattr(self, widget_name, None) if widget is None: continue if not IValidatableProxyWidget.providedBy(widget): continue try: widget.connect('validation-changed', self._on_child__validation_changed, widget_name) except TypeError: raise AssertionError("%r does not have a validation-changed " "signal." % widget) proxy = Proxy(self, model, widgets) self._proxies.append(proxy) return proxy # # Validation # def _on_child__validation_changed(self, child, value, name): # Children of the view, eg slaves or widgets are connected to # this signal. When validation changes of a validatable child # this callback is called if isinstance(child, gtk.Widget): # Force invisible and insensitive widgets to be valid if (not child.get_property('visible') or not child.get_property('sensitive')): value = True self._validation[name] = value self.check_and_notify_validity() def _on_notebook_slave__validation_changed(self, slave, value, name, label): validation = self._notebook_validation[label] validation[name] = value is_valid = True if False in validation.values(): is_valid = False if is_valid: color = color_black else: color = color_red # Only modify active state, since that's the (somewhat badly named) # state used for the pages which are not selected. label.modify_fg(gtk.STATE_ACTIVE, color) label.modify_fg(gtk.STATE_NORMAL, color) def check_and_notify_validity(self, force=False): # Current view is only valid if we have no invalid children # their status are stored as values in the dictionary is_valid = True if False in self._validation.values(): is_valid = False # Check if validation really changed if self._valid == is_valid and force == False: return self._valid = is_valid self.emit('validation-changed', is_valid) # FIXME: Remove and update all callsites to use validation-changed if self._validate_function: self._validate_function(is_valid) def force_validation(self): self.check_and_notify_validity(force=True) def register_validate_function(self, function): """The signature of the validate function is: def function(is_valid): or, if it is a method: def function(self, is_valid): where the 'is_valid' parameter is True if all the widgets have valid data or False otherwise. """ self._validate_function = function type_register(SlaveView) class BaseView(SlaveView): """A view with a toplevel window.""" def __init__(self, toplevel=None, widgets=None, gladefile=None, toplevel_name=None, domain=None, delete_handler=None): SlaveView.__init__(self, toplevel, widgets, gladefile, toplevel_name, domain) if not isinstance(self.toplevel, gtk.Window): raise TypeError("toplevel widget must be a Window " "(or inherit from it),\nfound `%s' %s" % (toplevel, self.toplevel)) self.toplevel.set_name(self.__class__.__name__) if delete_handler: id = self.toplevel.connect("delete-event", delete_handler) if not id: raise ValueError( "Invalid delete handler provided: %s" % delete_handler) def get_glade_adaptor(self): if not self.gladefile: return return _open_glade(self, self.gladefile, self.domain) # # Hook for keypress handling # def _attach_callbacks(self, controller): super(BaseView, self)._attach_callbacks(controller) self._setup_keypress_handler(controller.on_key_press) def _setup_keypress_handler(self, keypress_handler): self.toplevel.connect_after("key_press_event", keypress_handler) # # Proxying for self.toplevel # def set_transient_for(self, view): """Makes the view a transient for another view; this is commonly done for dialogs, so the dialog window is managed differently than a top-level one. """ if hasattr(view, 'toplevel') and isinstance(view.toplevel, gtk.Window): self.toplevel.set_transient_for(view.toplevel) # In certain cases, it is more convenient to send in a window; # for instance, in a deep slaveview hierarchy, getting the # top view is difficult. We used to print a warning here, I # removed it for convenience; we might want to put it back when # http://bugs.async.com.br/show_bug.cgi?id=682 is fixed elif isinstance(view, gtk.Window): self.toplevel.set_transient_for(view) else: raise TypeError("Parameter to set_transient_for should " "be View (found %s)" % view) def set_title(self, title): """Sets the view's window title""" self.toplevel.set_title(title) # # Focus handling # def get_focus_widget(self): """Returns the currently focused widget in the window""" return self.toplevel.focus_widget def check_focus(self): """ Tests the focus in the window and prints a warning if no widget is focused. """ focus = self.toplevel.focus_widget if focus: return values = self.__dict__.values() interactive = None # Check if any of the widgets is interactive for v in values: if (isinstance(v, gtk.Widget) and not isinstance(v, _non_interactive)): interactive = v if interactive: log.warn("No widget is focused in view %s but you have an " "interactive widget in it: %s""" % (self, interactive)) # # Window show/hide and mainloop manipulation # def hide(self, *args): """Hide the view's window""" self.toplevel.hide() def show_all(self, parent=None, *args): self.toplevel.show_all() self.show(parent, *args) def show(self, parent=None, *args): """Show the view's window. If the parent argument is supplied and is a valid view, this view is set as a transient for the parent view. """ # Uniconize window if minimized self.toplevel.present() # this call win.show() for us self.check_focus() if parent is not None: self.set_transient_for(parent) def quit_if_last(self, *args): quit_if_last(*args) def hide_and_quit(self, *args): """Hides the current window and breaks the GTK+ event loop if this is the last window. Its method signature allows it to be used as a signal handler. """ self.toplevel.hide() self.quit_if_last() def _get_gazpacho(): try: from kiwi.ui.gazpacholoader import GazpachoWidgetTree except ImportError: return return GazpachoWidgetTree def _get_libglade(): try: from kiwi.ui.libgladeloader import LibgladeWidgetTree except ImportError: return return LibgladeWidgetTree def _get_gaxml(): try: from kiwi.ui.gaxmlloader import GAXMLWidgetTree except ImportError: return return GAXMLWidgetTree def _open_glade(view, gladefile, domain): if not gladefile: raise ValueError("A gladefile wasn't provided.") elif not isinstance(gladefile, basestring): raise TypeError( "gladefile should be a string, found %s" % type(gladefile)) if gladefile.endswith('.ui'): directory = os.path.dirname(namedAny(view.__module__).__file__) gladefile = os.path.join(directory, gladefile) else: filename = os.path.splitext(os.path.basename(gladefile))[0] gladefile = environ.find_resource("glade", filename + '.glade') fp = open(gladefile) sniff = fp.read(200) fp.close() # glade-2 # # # glade-3 # # if 'glade-2.0.dtd' in sniff: WidgetTree = _get_libglade() loader_name = 'libglade' elif 'gaxml-0.1.dtd' in sniff: WidgetTree = _get_gaxml() loader_name = 'gaxml' else: # gazpacho: # # if not 'gazpacho-0.1.dtd' in sniff: log.warning("Could not determine type/dtd of gladefile %s" % gladefile) WidgetTree = _get_gazpacho() loader_name = 'gazpacho.loader' # None means, failed to import if WidgetTree is None: raise RuntimeError( "Could not find %s, it needs to be installed to " "load the gladefile %s" % (loader_name, gladefile)) return WidgetTree(view, gladefile, domain) PIDA-0.5.1/contrib/kiwi/kiwi/ui/wizard.py0000644000175000017500000001545110652670746016206 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Gustavo Rahal # Evandro Vale Miquelito # Johan Dahlin # import gettext import gtk from kiwi.ui.delegates import GladeDelegate _ = lambda m: gettext.dgettext('kiwi', m) class WizardStep: """ This class must be inherited by the steps """ def __init__(self, previous=None, header=None): self.previous = previous self.header = header def next_step(self): # This is a virtual method, which must be redefined on children # classes. It should not be called by the last step (in this case, # has_next_step should return 0). raise NotImplementedError def post_init(self): """A virtual method that must be defined on child when it's necessary. This method will be called right after the change_step method on PluggableWizard is concluded for the current step. """ def has_next_step(self): # This method should return False on last step classes return True def has_previous_step(self): # This method should return False on first step classes; since # self.previous is normally None for them, we can get away with # this simplified check. Redefine as necessary. return self.previous is not None def previous_step(self): return self.previous def validate_step(self): """A hook called always when changing steps. If it returns False we can not go forward. """ return True class PluggableWizard(GladeDelegate): """ Wizard controller and view class """ gladefile = 'PluggableWizard' retval = None def __init__(self, title, first_step, size=None, edit_mode=False): """ @param title: @param first_step: @param size: @param edit_mode: """ GladeDelegate.__init__(self, delete_handler=self.quit_if_last, gladefile=self.gladefile) if not isinstance(first_step, WizardStep): raise TypeError("first_step must be a WizardStep instance") self.set_title(title) self._current = None self._first_step = first_step self.edit_mode = edit_mode if size: self.get_toplevel().set_default_size(size[0], size[1]) self._change_step(first_step) if not self.edit_mode: self.ok_button.hide() # Callbacks def on_next_button__clicked(self, button): if not self._current.validate_step(): return if not self._current.has_next_step(): # This is the last step self._change_step() return self._change_step(self._current.next_step()) def on_ok_button__clicked(self, button): self._change_step() def on_previous_button__clicked(self, button): self._change_step(self._current.previous_step()) def on_cancel_button__clicked(self, button): self.cancel() # Private API def _change_step(self, step=None): if step is None: # This is the last step and we can finish the job here self.finish() return step.show() self._current = step self._refresh_slave() if step.header: self.header_lbl.show() self.header_lbl.set_text(step.header) else: self.header_lbl.hide() self.update_view() self._current.post_init() def _refresh_slave(self): holder_name = 'slave_area' if self.get_slave(holder_name): self.detach_slave(holder_name) self.attach_slave(holder_name, self._current) def _show_first_page(self): self.enable_next() self.disable_back() self.disable_finish() self.notification_lbl.hide() def _show_page(self): self.enable_back() self.enable_next() self.disable_finish() self.notification_lbl.hide() def _show_last_page(self): self.enable_back() self.notification_lbl.show() if self.edit_mode: self.disable_next() else: self.enable_next() self.enable_finish() # Public API def update_view(self): if self.edit_mode: self.ok_button.set_sensitive(True) if not self._current.has_previous_step(): self._show_first_page() elif self._current.has_next_step(): self._show_page() else: self._show_last_page() def enable_next(self): """ Enables the next button in the wizard. """ self.next_button.set_sensitive(True) def disable_next(self): """ Disables the next button in the wizard. """ self.next_button.set_sensitive(False) def enable_back(self): """ Enables the back button in the wizard. """ self.previous_button.set_sensitive(True) def disable_back(self): """ Disables the back button in the wizard. """ self.previous_button.set_sensitive(False) def enable_finish(self): """ Enables the finish button in the wizard. """ if self.edit_mode: button = self.ok_button else: button = self.next_button button.set_label(_('Finish')) def disable_finish(self): """ Disables the finish button in the wizard. """ if self.edit_mode: self.ok_button.set_label(gtk.STOCK_OK) else: self.next_button.set_label(gtk.STOCK_GO_FORWARD) def set_message(self, message): """ @param message: """ self.notification_lbl.set_text(message) def cancel(self, *args): # Redefine this method if you want something done when cancelling the # wizard. self.retval = None def finish(self): # Redefine this method if you want something done when finishing the # wizard. pass PIDA-0.5.1/contrib/kiwi/kiwi/__init__.py0000644000175000017500000000573110652670747016031 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2003-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # Johan Dahlin # """Kiwi is a library designed to make developing graphical applications as easy as possible. It offers both a framework and a set of enhanced widgets, and is based on Python and GTK+. Kiwi borrows concepts from MVC, Java Swing and Microsoft MFC, but implements a set of unique classes that take advantage of the flexibility and simplicity of Python to make real-world application creation much easier. Kiwi includes a Framework and a set of enhanced widgets - Authors: - Christian Reis - Johan Dahlin - Website: U{http://www.async.com.br/projects/kiwi/} - Organization: Async Open Source """ try: import gobject gobject # pyflakes except ImportError, e: try: import pygtk pygtk.require('2.0') except: pass try: import gobject gobject # pyflakes except: raise SystemExit( "PyGTK 2.8 or PyGObject 2.9.0 or higher is required by kiwi\n" "Error was: %s" % e) # if gobject.pygtk_version[:2] < (2, 8, 0): # raise ImportError("Your PyGTK/PyGObject version is too old, found %s, " # "but 2.8.0 or higher is required by kiwi" % ( # ('.'.join(map(str, gobject.pygtk_version))),)) from kiwi.__version__ import version as kiwi_version from kiwi.environ import Library assert kiwi_version # pyflakes lib = Library('kiwi') if lib.uninstalled: lib.add_global_resource('glade', 'glade') lib.add_global_resource('pixmap', 'pixmaps') lib.enable_translation() # Be careful to not export too much del Library, lib, gobject class ValueUnset: """To differentiate from places where None is a valid default. Used mainly in the Kiwi Proxy""" pass __all__ = ['ValueUnset', 'kiwi_version'] # by default locale uses the C locale but our date conversions use the user # locale so we need to set the locale to that one import locale try: locale.setlocale(locale.LC_ALL, '') # this set the user locale ( $LANG ) except locale.Error: pass del locale PIDA-0.5.1/contrib/kiwi/kiwi/__version__.py0000644000175000017500000000152710652670747016552 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006,2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # version = (1, 9, 16) PIDA-0.5.1/contrib/kiwi/kiwi/_kiwi.c0000644000175000017500000001513210652670747015162 0ustar aliali/* -*- Mode: C; c-basic-offset: 4 -*- * Kiwi: a Framework and Enhanced Widgets for Python * * Copyright (C) 2006 Async Open Source * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Author(s): Johan Dahlin */ /* This module contains backports of pygobject/pygtk functions, * so we don't have to require the latest versions of them */ #include #include #include #if PY_VERSION_HEX < 0x02050000 typedef int Py_ssize_t; #define PY_SSIZE_T_MAX INT_MAX #define PY_SSIZE_T_MIN INT_MIN typedef inquiry lenfunc; #endif typedef struct { PyObject *func, *data; } PyGtkCustomNotify; static void pygtk_custom_destroy_notify(gpointer user_data) { PyGtkCustomNotify *cunote = user_data; PyGILState_STATE state; g_return_if_fail(user_data); state = pyg_gil_state_ensure(); Py_XDECREF(cunote->func); Py_XDECREF(cunote->data); pyg_gil_state_release(state); g_free(cunote); } static gboolean marshal_emission_hook(GSignalInvocationHint *ihint, guint n_param_values, const GValue *param_values, gpointer user_data) { PyGILState_STATE state; gboolean retval = FALSE; PyObject *func, *args; PyObject *retobj; PyObject *params; guint i; state = pyg_gil_state_ensure(); /* construct Python tuple for the parameter values */ params = PyTuple_New(n_param_values); for (i = 0; i < n_param_values; i++) { PyObject *item = pyg_value_as_pyobject(¶m_values[i], FALSE); /* error condition */ if (!item) { goto out; } PyTuple_SetItem(params, i, item); } args = (PyObject *)user_data; func = PyTuple_GetItem(args, 0); args = PySequence_Concat(params, PyTuple_GetItem(args, 1)); Py_DECREF(params); /* params passed to function may have extra arguments */ retobj = PyObject_CallObject(func, args); Py_DECREF(args); if (retobj == NULL) { PyErr_Print(); } retval = (retobj == Py_True ? TRUE : FALSE); Py_XDECREF(retobj); out: pyg_gil_state_release(state); return retval; } static PyObject * pyg_add_emission_hook(PyGObject *self, PyObject *args) { PyObject *first, *callback, *extra_args, *data; gchar *name; gulong hook_id; guint sigid; Py_ssize_t len; GQuark detail = 0; GType gtype; PyObject *pygtype; len = PyTuple_Size(args); if (len < 3) { PyErr_SetString(PyExc_TypeError, "gobject.add_emission_hook requires at least 3 arguments"); return NULL; } first = PySequence_GetSlice(args, 0, 3); if (!PyArg_ParseTuple(first, "OsO:add_emission_hook", &pygtype, &name, &callback)) { Py_DECREF(first); return NULL; } Py_DECREF(first); if ((gtype = pyg_type_from_object(pygtype)) == 0) { return NULL; } if (!PyCallable_Check(callback)) { PyErr_SetString(PyExc_TypeError, "third argument must be callable"); return NULL; } if (!g_signal_parse_name(name, gtype, &sigid, &detail, TRUE)) { PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s", PyString_AsString(PyObject_Repr((PyObject*)self)), name); return NULL; } extra_args = PySequence_GetSlice(args, 3, len); if (extra_args == NULL) return NULL; data = Py_BuildValue("(ON)", callback, extra_args); if (data == NULL) return NULL; hook_id = g_signal_add_emission_hook(sigid, detail, marshal_emission_hook, data, (GDestroyNotify)pyg_destroy_notify); return PyLong_FromUnsignedLong(hook_id); } static PyObject * pyg_remove_emission_hook(PyGObject *self, PyObject *args) { PyObject *pygtype; char *name; guint signal_id; gulong hook_id; GType gtype; if (!PyArg_ParseTuple(args, "Osk:gobject.remove_emission_hook", &pygtype, &name, &hook_id)) return NULL; if ((gtype = pyg_type_from_object(pygtype)) == 0) { return NULL; } if (!g_signal_parse_name(name, gtype, &signal_id, NULL, TRUE)) { PyErr_Format(PyExc_TypeError, "%s: unknown signal name: %s", PyString_AsString(PyObject_Repr((PyObject*)self)), name); return NULL; } g_signal_remove_emission_hook(signal_id, hook_id); Py_INCREF(Py_None); return Py_None; } static void pygdk_event_handler_marshal(GdkEvent *event, gpointer data) { PyGILState_STATE state; PyGtkCustomNotify *cunote = data; PyObject *retobj; PyObject *pyevent; g_assert (cunote->func); state = pyg_gil_state_ensure(); pyevent = pyg_boxed_new(GDK_TYPE_EVENT, event, TRUE, TRUE); if (cunote->data) retobj = PyEval_CallFunction(cunote->func, "(NO)", pyevent, cunote->data); else retobj = PyEval_CallFunction(cunote->func, "(N)", pyevent); if (retobj == NULL) { PyErr_Print(); } else Py_DECREF(retobj); pyg_gil_state_release(state); } static PyObject * _wrap_gdk_event_handler_set(PyObject *self, PyObject *args, PyObject *kwargs) { PyObject *pyfunc, *pyarg = NULL; PyGtkCustomNotify *cunote; if (!PyArg_ParseTuple(args, "O|O:event_handler_set", &pyfunc, &pyarg)) return NULL; if (pyfunc == Py_None) { gdk_event_handler_set(NULL, NULL, NULL); } else { cunote = g_new0(PyGtkCustomNotify, 1); cunote->func = pyfunc; cunote->data = pyarg; Py_INCREF(cunote->func); Py_XINCREF(cunote->data); gdk_event_handler_set(pygdk_event_handler_marshal, cunote, pygtk_custom_destroy_notify); } Py_INCREF(Py_None); return Py_None; } static PyMethodDef _kiwi_functions[] = { { "add_emission_hook", (PyCFunction)pyg_add_emission_hook, METH_VARARGS }, { "remove_emission_hook", (PyCFunction)pyg_remove_emission_hook, METH_VARARGS }, { "event_handler_set", (PyCFunction)_wrap_gdk_event_handler_set, METH_VARARGS }, { NULL, NULL, 0 } }; DL_EXPORT(void) init_kiwi(void) { init_pygobject(); init_pygtk(); Py_InitModule("kiwi._kiwi", _kiwi_functions); } PIDA-0.5.1/contrib/kiwi/kiwi/accessor.py0000644000175000017500000004343010652670747016072 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2002-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Andreas Kostyrka # Christian Reis # Johan Dahlin # """The accessor module offers two important front-end functions: kgetattr and ksetattr. These functions allow retrieving attribute values from objects much in the same way as getattr/setattr allow, but with two important differences: - They follow a dot hierarchy to retrieve or modify any value reachable from the object. - They cache the method used to access a certain attribute and reuse it the next time the value is retrieved. """ import string import types from kiwi.log import Logger log = Logger('kiwi.accessor') def get_default_getter(model, attr_name, cache): """Obtains from model a callable through which attr_name can be retrieved. This callable is an accessor named get_foo, where foo is the value of attr_name, or getattr(model, foo) if the accessor does not exist. If the callable exists, it is returned; if getattr() is to be used a tuple in the format (model, attr_name) is returned.""" func = getattr(model, "get_%s" % attr_name, None) if callable(func): log.info('kgetattr based get_%s method is deprecated, ' 'replace it with a property' % attr_name) return func else: return (model, attr_name) def get_default_setter(model, attr_name, cache): """Obtains from model a callable through which attr_name can be set. This callable is an accessor named set_foo, where foo is the value of attr_name, or setattr(model, foo, value) if the accessor does not exist. If the callable exists, it is returned; if setattr() is to be used a tuple in the format (model, attr_name) is returned.""" func = getattr(model, "set_%s" % attr_name, None) if callable(func): log.info('ksetattr based set_%s method is deprecated, ' 'replace it with a property' % attr_name) return func else: return (model, attr_name) # The _*_cache dictionaries cache the objects, attributes and callables # (called `accessor tuples' here) we retrieve values from. If possible, # we use weakrefs to avoid holding hard references to objects, allowing # them to be garbage collected. Certain objects (ZODB.Persistent for # one) cannot be weakref()ed and *will* leak - be sure to call # clear_attr_cache() if you need them released. # # Key structure: # (objref_or_weakref, attrname) # # Value structure (accessor tuples): # # kgetattr: (access_code, data1, data2) # ksetattr: (access_code, data1, data2, value_mode) # # Access codes: # # 0: data1() unbound methods and functions (data2 is None) # 1: data2(data1()) bound methods and weakref # 2: getattr(data1(), data2) using straight getattr and weakref # 3: data2(data1) bound methods (no weakref) # 4: getattr(data1, data2) using straight getattr (no weakref) import weakref _kgetattr_cache = {} _kgetattr_wref = {} _ksetattr_cache = {} _ksetattr_wref = {} class CacheControl(object): __slots__ = ['key', 'cacheable'] def __init__(self, key): self.key = key self.cacheable = 1 def disable(self): self.cacheable = 0 def invalidate(self): key = self.key if _kgetattr_cache.has_key(key): del _kgetattr_cache[key] if _ksetattr_cache.has_key(key): del _ksetattr_cache[key] class _AttrUnset: # indicates an unset value since None needs to be used pass class DefaultValue(Exception): """ This can be raised in kgetattr accessors to indicate that the default value should be used """ def kgetattr_guard(wref): try: key = _kgetattr_wref[id(wref)][0] del _kgetattr_wref[id(wref)] del _kgetattr_cache[key] except KeyError: # This path is used only when the program terminates. pass def ksetattr_guard(wref): try: key = _ksetattr_wref[id(wref)][0] del _ksetattr_wref[id(wref)] del _ksetattr_cache[key] except KeyError: # This path is used only when the program terminates. pass # 1. Break up attr_name into parts # 2. Loop around main lookup code for each part: # 2.1. Try and get accessor tuple out of cache # 2.2. If not there, generate tuple from callable and store it # 2.3. Use accessor tuple to grab value # 2.4. Value wasn't found, return default or raise ValueError # Use value as obj in next iteration # 3. Return value def kgetattr(model, attr_name, default=_AttrUnset, flat=0, # bind to local variables for speed: ref=weakref.ref, TupleType=types.TupleType, MethodType=types.MethodType, split=string.split, kgetattr_guard=kgetattr_guard, getattr=getattr, dummycache=CacheControl((None,None)), # constants: # access opcodes: LAMBDA_ACCESS = 0, METHOD_ACCESS = 1, TUPLE_ACCESS = 2, NWR_METHOD_ACCESS = 3, NWR_TUPLE_ACCESS = 4, # FAST tuples do not store the object, as the input object # is also the accesses object. FAST_METHOD_ACCESS = 5, FAST_TUPLE_ACCESS = 6, ): """Returns the value associated with the attribute in model named by attr_name. If default is provided and model does not have an attribute called attr_name, the default value is returned. If flat=1 is specified, no dot path parsing will be done.""" # 1. Break up attr_name into parts if flat or "." not in attr_name: names = [attr_name, ] else: try: names = attr_name.split(".") except AttributeError: names = split(attr_name, ".") # 2. Loop around main lookup code for each part: obj = model for name in names: key = (id(obj), name) # First time round, obj is the model. Every subsequent loop, obj # is the subattribute value indicated by the current part in # [names]. The last loop grabs the target value and returns it. try: # 2.1 Fetch the opcode tuple from the cache. objref, icode, data1, data2 = _kgetattr_cache[key] except KeyError: # 2.2. If not there, generate tuple from callable and store it try: get_getter = obj.__class__.get_getter cache = CacheControl(key) except AttributeError: # This is needed so that the check below if the result is # cacheable can be done. The inbuilt get_getter always # allows caching. cache = dummycache get_getter = None func = getattr(obj, "get_%s" % name, None) if callable(func): log.info('kgetattr based get_%s method is deprecated, ' 'replace it with a property' % name) icode = FAST_METHOD_ACCESS data1 = func.im_func data2 = None else: icode = FAST_TUPLE_ACCESS data1 = None data2 = name if get_getter is not None: try: func = get_getter(obj, name, cache) except DefaultValue: if default == _AttrUnset: raise return default if isinstance(func, TupleType): data1, data2 = func if data1 == obj: data1 = None icode = FAST_TUPLE_ACCESS else: try: data1 = ref(data1, kgetattr_guard) _kgetattr_wref[id(data1)] = (key, data1) icode = TUPLE_ACCESS except TypeError: icode = NWR_TUPLE_ACCESS elif isinstance(func, MethodType): data1 = func.im_func data2 = func.im_self if data2 == obj: data2 = None icode = FAST_METHOD_ACCESS else: try: data2 = ref(func.im_self, kgetattr_guard) _kgetattr_wref[id(data2)] = (key, data2) icode = METHOD_ACCESS except TypeError: data2 = func.im_self icode = NWR_METHOD_ACCESS else: icode = LAMBDA_ACCESS data1 = func data2 = None if cache.cacheable: # Store access opcode: # objref or obj are used as a protection against id-aliasing # as we use just a plain id(obj) in the cache entry key. # # We either have to use a weakref, so we get to know when the # object dies. We just remove the cache entry containing the # weakref, _kgetattr_wref is used to associate which key has # to be killed for a given weakref. try: objref = ref(obj, kgetattr_guard) _kgetattr_wref[id(objref)] = (key, objref) _kgetattr_cache[key] = (objref, icode, data1, data2) except TypeError: # it's not weakrefable (probably ZODB!) # store a hard reference. _kgetattr_cache[key] = (obj, icode, data1, data2) else: if _kgetattr_cache.has_key(key): del _kgetattr_cache[key] # 2.3. Use accessor tuple to grab value try: if icode == FAST_METHOD_ACCESS: obj = data1(obj) elif icode == FAST_TUPLE_ACCESS: obj = getattr(obj, data2, default) if obj is _AttrUnset: raise AttributeError( "%r object has no attribute %r" % (obj, data2)) elif icode == TUPLE_ACCESS: o = data1() obj = getattr(o, data2, default) if obj is _AttrUnset: raise AttributeError( "%r object has no attribute %r" % (o, data2)) elif icode == NWR_TUPLE_ACCESS: obj = getattr(data1, data2) elif icode == NWR_METHOD_ACCESS: obj = data1(data2) elif icode == METHOD_ACCESS: obj = data1(data2()) elif icode == LAMBDA_ACCESS: obj = data1() else: raise AssertionError("Unknown tuple type in _kgetattr_cache") # 2.4. Value wasn't found, return default or raise ValueError except DefaultValue: if default == _AttrUnset: raise return default # At the end of the iteration, the value retrieved becomes the new obj # 3. Return value return obj # A general algo for ksetattr: # # 1. Use attr_name to kgetattr the target object, and get the real attribute # 2. Try and get accessor tuple from cache # 3. If not there, generate accessor tuple and store it # 4. Set value to target object's attribute def ksetattr(model, attr_name, value, flat=0, # bind to local variables for speed: ref=weakref.ref, TupleType=types.TupleType, MethodType=types.MethodType, ksetattr_guard=ksetattr_guard, getattr=getattr, dummycache=CacheControl((None,None)), # constants: LAMBDA_ACCESS = 0, METHOD_ACCESS = 1, TUPLE_ACCESS = 2, NWR_METHOD_ACCESS = 3, NWR_TUPLE_ACCESS = 4, FAST_METHOD_ACCESS = 5, FAST_TUPLE_ACCESS = 6, ): """Set the value associated with the attribute in model named by attr_name. If flat=1 is specified, no dot path parsing will be done.""" # 1. kgetattr the target object, and get the real attribute # This is the only section which is special about ksetattr. When you # set foo.bar.baz to "x", what you really want to do is get hold of # foo.bar and use an accessor (set_baz/setattr) on it. This bit gets # the attribute name and the model we want. if not flat: lastdot = string.rfind(attr_name, ".") if lastdot != -1: model = kgetattr(model, attr_name[:lastdot]) attr_name = attr_name[lastdot+1:] # At this point we only have a flat attribute and the right model. key = (id(model), attr_name) try: # 2. Try and get accessor tuple from cache objref, icode, data1, data2 = _ksetattr_cache[key] except KeyError: # 3. If not there, generate accessor tuple and store it # cache = CacheControl(key) try: get_setter = model.__class__.get_setter cache = CacheControl(key) except AttributeError: # No get_setter found: get_setter = None # This is needed so the entry storing code can check if it's ok # to cache. cache = dummycache func = getattr(model, "set_%s" % attr_name, None) if callable(func): log.info('ksetattr based set_%s method is deprecated, ' 'replace it with a property' % attr_name) icode = FAST_METHOD_ACCESS data1 = func.im_func data2 = None else: icode = FAST_TUPLE_ACCESS data1 = None data2 = attr_name if get_setter is not None: func = get_setter(model, attr_name, cache) if isinstance(func, TupleType): data1, data2 = func if data1 == model: data1 = None icode = FAST_TUPLE_ACCESS else: try: data1 = ref(data1, ksetattr_guard) _ksetattr_wref[id(data1)] = (key, data1) icode = TUPLE_ACCESS except TypeError: icode = NWR_TUPLE_ACCESS elif isinstance(func, MethodType): data1 = func.im_func data2 = func.im_self if data2 == model: data2 = None icode = FAST_METHOD_ACCESS else: try: data2 = ref(data2, ksetattr_guard) _ksetattr_wref[id(data2)] = (key, data2) icode = METHOD_ACCESS except TypeError: data2 = func.im_self icode = NWR_METHOD_ACCESS else: icode = LAMBDA_ACCESS data1 = func data2 = None if cache.cacheable: # store the access opcode. # for the use of model/objref as first value in the opcode tuple # see the kgetattr comments. try: objref = ref(model, ksetattr_guard) _ksetattr_wref[id(objref)] = (key, objref) _ksetattr_cache[key] = (objref, icode, data1, data2) except TypeError: # it's not weakref-able, store a hard reference. _ksetattr_cache[key] = (model, icode, data1, data2) else: if _ksetattr_cache.has_key(key): del _ksetattr_cache.has_key[key] if icode == FAST_TUPLE_ACCESS: setattr(model, data2, value) elif icode == FAST_METHOD_ACCESS: data1(model, value) elif icode == TUPLE_ACCESS: setattr(data1(), data2, value) elif icode == NWR_TUPLE_ACCESS: setattr(data1, data2, value) elif icode == NWR_METHOD_ACCESS: data1(data2, value) elif icode == METHOD_ACCESS: data1(data2(), value) elif icode == LAMBDA_ACCESS: data1(value) else: raise AssertionError("Unknown tuple type in _ksetattr_cache") def enable_attr_cache(): """Enables the use of the kgetattr cache when using Python versions that do not support weakrefs (1.5.x and earlier). Be warned, using the cache in these versions causes leaked references to accessor methods and models!""" global _kgetattr_cache, _ksetattr_cache, _kgetattr_wref, _ksetattr_wref _kgetattr_cache = {} _ksetattr_cache = {} _kgetattr_wref = {} _ksetattr_wref = {} def clear_attr_cache(): """Clears the kgetattr cache. It must be called repeatedly to avoid memory leaks in Python 2.0 and earlier.""" global _kgetattr_cache, _ksetattr_cache, _kgetattr_wref, _ksetattr_wref _kgetattr_cache = {} _ksetattr_cache = {} _kgetattr_wref = {} _ksetattr_wref = {} PIDA-0.5.1/contrib/kiwi/kiwi/argcheck.py0000644000175000017500000001537710652670747016050 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """ Argument checking decorator and support """ import inspect from types import ClassType from kiwi.datatypes import number as number_type class CustomType(type): def value_check(mcs, name, value): pass value_check = classmethod(value_check) class number(CustomType): """ Custom type that verifies that the type is a number (eg float or int) """ type = number_type class percent(CustomType): """ Custom type that verifies that the value is a percentage """ type = number_type def value_check(mcs, name, value): if (value < 0) or (value > 100): raise ValueError("%s must be between 0 and 100" % name) value_check = classmethod(value_check) _NoValue = object() class argcheck(object): """ Decorator to check type and value of arguments. Usage: >>> @argcheck(types...) ... def function(args..) or >>> class Class: ... @argcheck(types..) ... def method(self, args) You can customize the checks by subclassing your type from CustomType, there are two builtin types: number which is a float/int combined check and a percent which verifis that the value is a percentage """ __enabled__ = True def __init__(self, *types): for argtype in types: if not isinstance(argtype, (type, ClassType)): raise TypeError("must be a type or class instance, not %r" % argtype) self.types = types def enable(cls): """ Enable argcheck globally """ cls.__enabled__ = True enable = classmethod(enable) def disable(cls): """ Disable argcheck globally """ cls.__enabled__ = False disable = classmethod(disable) def __call__(self, func): if not callable(func): raise TypeError("%r must be callable" % func) # Useful for optimized runs if not self.__enabled__: return func spec = inspect.getargspec(func) arg_names, is_varargs, is_kwargs, default_values = spec if not default_values: default_values = [] else: default_values = list(default_values) # Set all the remaining default values to _NoValue default_values = ([_NoValue] * (len(arg_names) - len(default_values)) + default_values) # TODO: Is there another way of doing this? # Not trivial since func is not attached to the class at # this point. Nor is the class attached to the namespace. if arg_names and arg_names[0] in ('self', 'cls'): arg_names = arg_names[1:] default_values = default_values[1:] is_method = True else: is_method = False types = self.types if is_kwargs and not is_varargs and self.types: raise TypeError("argcheck cannot be used with only keywords") elif not is_varargs: if len(types) != len(arg_names): raise TypeError("%s has wrong number of arguments, " "%d specified in decorator, " "but function has %d" % (func.__name__, len(types), len(arg_names))) kwarg_types = {} kwarg_defaults = {} for i, arg_name in enumerate(arg_names): kwarg_types[arg_name] = types[i] value = default_values[i] kwarg_defaults[arg_name] = value if value is None or value is _NoValue: continue arg_type = types[i] try: self._type_check(value, arg_type, arg_name) except TypeError: raise TypeError("default value for %s must be of type %s " "and not %s" % (arg_name, arg_type.__name__, type(value).__name__)) kwarg_defaults[arg_name] = value def wrapper(*args, **kwargs): if self.__enabled__: cargs = args if is_method: cargs = cargs[1:] # Positional arguments for arg, type, name, default in zip(cargs, types, arg_names, default_values): self._type_check(arg, type, name, default) # Keyword arguments for name, arg in kwargs.items(): if not name in kwarg_types: raise TypeError( "%s() got an unexpected keyword argument '%s'" % (func.__name__, name)) self._type_check(arg, kwarg_types[name], name, kwarg_defaults[name]) self.extra_check(arg_names, types, args, kwargs) return func(*args, **kwargs) # Python 2.3 does not support assignments to __name__ try: wrapper.__name__ = func.__name__ except TypeError: pass return wrapper def extra_check(self, names, types, args, kwargs): pass def _type_check(self, value, argument_type, name, default=_NoValue): if default is not _NoValue and value == default: return if issubclass(argument_type, CustomType): custom = True check_type = argument_type.type else: custom = False check_type = argument_type type_name = argument_type.__name__ if not isinstance(value, check_type): raise TypeError( "%s must be %s, not %s" % (name, type_name, type(value).__name__)) if custom: argument_type.value_check(name, value) PIDA-0.5.1/contrib/kiwi/kiwi/component.py0000644000175000017500000001032510652670747016267 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # Ali Afshar import sys from kiwi import ValueUnset try: from zope.interface import implements, Attribute, Interface # pyflakes implements, Attribute, Interface except ImportError: class Interface(object): def providedBy(cls, impl): candidates = (impl,) + impl.__class__.__bases__ for candidate in candidates: for iface in getattr(candidate, '__interfaces__', []): if issubclass(iface, cls): return True return False providedBy = classmethod(providedBy) class Attribute(object): def __init__(self, __name__, __doc__=''): self.__name__=__name__ self.__doc__=__doc__ def implements(iface): frame = sys._getframe(1) try: frame.f_locals.setdefault('__interfaces__', []).append(iface) finally: del frame class AlreadyImplementedError(Exception): """Called when a utility already exists.""" class _UtilityHandler(object): def __init__(self): self._utilities = {} # FIXME: How to replace a utility properly def provide(self, iface, obj, replace=False): if not issubclass(iface, Interface): raise TypeError( "iface must be an Interface subclass and not %r" % iface) if not replace: if iface in self._utilities: raise AlreadyImplementedError("%s is already implemented" % iface) self._utilities[iface] = obj def get(self, iface, default): if not issubclass(iface, Interface): raise TypeError( "iface must be an Interface subclass and not %r" % iface) if not iface in self._utilities: if default is ValueUnset: raise NotImplementedError("No utility provided for %r" % iface) else: return default return self._utilities[iface] def remove(self, iface): if not issubclass(iface, Interface): raise TypeError( "iface must be an Interface subclass and not %r" % iface) if not iface in self._utilities: raise NotImplementedError("No utility provided for %r" % iface) return self._utilities.pop(iface) def clean(self): self._utilities = {} def provide_utility(iface, utility, replace=False): """ Set the utility for the named interface. If the utility is already set, an {AlreadyImplementedError} is raised. @param iface: interface to set the utility for. @param utility: utility providing the interface. """ utilities.provide(iface, utility, replace) def get_utility(iface, default=ValueUnset): """ Get the utility for the named interface. If the utility is not available (has not been set) a {NotImplementedError} is raised unless default is set. @param iface: an interface @param default: optional, if set return if a utility is not found @returns: the utility """ return utilities.get(iface, default) def remove_utility(iface): """ Remove the utility provided for an interface If the utility is not available (has not been set) {NotImplementedError} is raised. @param iface: the interface @returns: the removed utility """ return utilities.remove(iface) utilities = _UtilityHandler() PIDA-0.5.1/contrib/kiwi/kiwi/controllers.py0000644000175000017500000001200610652670747016631 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2001-2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Lorenzo Gil Sanchez # from gtk import gdk """Holds the base controller class for the Kiwi Framework""" class BaseController: """ A generic controller that can be attached to any View BaseController defines one public variable: - view: corresponds to a the associated View instance, which holds the UI implementation itself (widgets, layout, etc.) """ view = None def __init__(self, view=None, keyactions=None): """ Creates a new controller, and attaches itself to a view. The constructor triggers a view.set_constructor(self) call, so the view is also attached to it. The arguments are identical to the view and keyactions class variables. - view: the correspondent view for the controller - keyactions: a mapping from GDK key symbol (GDK.A, etc.) to a method. The method will be called when any relevant keypress is generated for that view. The handler definition should look like: >>> def my_A_handler(self, widget, event, args): """ if not view and not self.view: raise AssertionError( "Need a view to create controller, found None" ) else: self.set_view(view) # Copy just to be on the safe side, avoiding problems with # mutable class variables self._keyactions = keyactions or {} self.view._attach_callbacks(self) # Call finalization hook self.view.on_startup() def on_key_press(self, widget, event): """ The keypress handler, which dispatches keypresses to the functions mapped to in self.keyactions""" keyval = gdk.keyval_name(event.keyval) if keyval is None: return # Order is important, we want control_shift_alt_XXX method_name = 'key_' if event.state & gdk.CONTROL_MASK: method_name += 'control_' if event.state & gdk.SHIFT_MASK: method_name += 'shift_' if event.state & gdk.MOD1_MASK: method_name += 'alt_' method_name += keyval func = getattr(self, method_name, None) if not func and event.keyval in self._keyactions: func = self._keyactions[event.keyval] if func: return func() # # Accessors # def get_parent(self): """parent: the correspondent parent for the controller""" return self.parent def set_parent(self, parent): """parent: the correspondent parent for the controller""" self.parent = parent def get_view(self): """view: the correspondent view for the controller""" return self.view def set_view(self, view): """view: the correspondent view for the controller""" if self.view: msg = "This controller already has a view: %s" raise AssertionError(msg % self.view) self.view = view view.set_controller(self) def set_keyactions(self, keyactions): """ Sets the keyactions mapping. See the constructor documentation for a description of it.""" self._keyactions = keyactions def update_keyactions(self, new_actions): """ XXX """ self._keyactions.update(new_actions) # # # def _get_all_methods(self, klass=None): klass = klass or self.__class__ # Very poor simulation of inheritance, but WFM(tm) classes = [klass] # Collect bases for class, using a pretty evil recursion for klass in classes: map(classes.append, klass.__bases__) # Order bases so that the class itself is the last one referred to # in the loop. This guarantees that the inheritance ordering for the # methods is preserved. classes.reverse() methods = {} for c in classes: for name in c.__dict__.keys(): # Need to use getattr() to ensure we get bound methods try: methods[name] = getattr(self, name) except AttributeError: continue return methods PIDA-0.5.1/contrib/kiwi/kiwi/currency.py0000644000175000017500000001522310652670747016121 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Currency and datatype converter""" import gettext from kiwi.datatypes import HAVE_DECIMAL, Decimal, InvalidOperation from kiwi.datatypes import ValidationError, ValueUnset from kiwi.datatypes import converter, get_localeconv, filter_locale from kiwi.enums import Alignment _ = lambda m: gettext.dgettext('kiwi', m) class currency(Decimal): """ A datatype representing currency, used together with the list and the framework """ _converter = converter.get_converter(Decimal) def __new__(cls, value): """ @param value: value to convert @type value: string or number """ if isinstance(value, str): conv = get_localeconv() currency_symbol = conv.get('currency_symbol') text = value.strip(currency_symbol) # if we cannot convert it using locale information, still try to # create try: text = filter_locale(text, monetary=True) value = currency._converter.from_string(text) except ValidationError: # Decimal does not accept leading and trailing spaces. See # bug 1516613 value = text.strip() if value == ValueUnset: raise InvalidOperation elif HAVE_DECIMAL and isinstance(value, float): print ('Warning: losing precision converting float %r to currency' % value) value = str(value) elif not isinstance(value, (int, long, Decimal)): raise TypeError( "cannot convert %r of type %s to a currency" % ( value, type(value))) return Decimal.__new__(cls, value) def format(self, symbol=True, precision=None): value = Decimal(self) conv = get_localeconv() # Grouping (eg thousand separator) of integer part groups = conv.get('mon_grouping', [])[:] groups.reverse() if groups: group = groups.pop() else: group = 3 intparts = [] # We're iterating over every character in the integer part # make sure to remove the negative sign, it'll be added later intpart = str(int(abs(value))) while True: if not intpart: break s = intpart[-group:] intparts.insert(0, s) intpart = intpart[:-group] if not groups: continue last = groups.pop() # if 0 reuse last one, see struct lconv in locale.h if last != 0: group = last # Add the sign, and the list of decmial parts, which now are grouped # properly and can be joined by mon_thousand_sep if value > 0: sign = conv.get('positive_sign', '') elif value < 0: sign = conv.get('negative_sign', '-') else: sign = '' currency = sign + conv.get('mon_thousands_sep', '.').join(intparts) # Only add decimal part if it has one, is this correct? if precision is not None or value % 1 != 0: # Pythons string formatting can't handle %.127f # 127 is the default value from glibc/python if precision: frac_digits = precision else: frac_digits = conv.get('frac_digits', 2) if frac_digits == 127: frac_digits = 2 format = '%%.%sf' % frac_digits dec_part = (format % value)[-frac_digits:] mon_decimal_point = conv.get('mon_decimal_point', '.') currency += mon_decimal_point + dec_part # If requested include currency symbol currency_symbol = conv.get('currency_symbol', '') if currency_symbol and symbol: if value > 0: cs_precedes = conv.get('p_cs_precedes', 1) sep_by_space = conv.get('p_sep_by_space', 1) else: cs_precedes = conv.get('n_cs_precedes', 1) sep_by_space = conv.get('n_sep_by_space', 1) if sep_by_space: space = ' ' else: space = '' if cs_precedes: currency = currency_symbol + space + currency else: currency = currency + space + currency_symbol return currency def __repr__(self): return '' % self.format() _DecimalConverter = type(converter.get_converter(Decimal)) class _CurrencyConverter(_DecimalConverter): type = currency name = _('Currency') align = Alignment.RIGHT def __init__(self): self.symbol = True self.precision = 2 def as_string(self, value, format=None, symbol=None, precision=None): if value == ValueUnset: return '' if not isinstance(value, currency): try: value = currency(value) except ValueError: raise ValidationError( _("%s can not be converted to a currency") % value) if symbol is None: symbol = self.symbol if precision is None: precision = self.precision return value.format(symbol, precision) def from_string(self, value): if value == '': return ValueUnset try: return currency(value) except (ValueError, InvalidOperation): raise ValidationError( _("%s can not be converted to a currency") % value) converter.add(_CurrencyConverter) def format_price(value, symbol=True, precision=None): """ Formats a price according to the current locales monetary settings @param value: number @param symbol: whether to include the currency symbol """ return currency(value).format(symbol, precision) PIDA-0.5.1/contrib/kiwi/kiwi/datatypes.py0000644000175000017500000005310010652670747016261 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Johan Dahlin # Ronaldo Maia # """Data type converters with locale and currency support. Provides routines for converting data to and from strings. Simple example: >>> from kiwi.datatypes import converter >>> converter.from_string(int, '1,234') '1234' >>> converter.from_string(float, '1,234') '1234.0' >>> converter.to_string(currency, currency('10.5')) '$10.50' """ import datetime import gettext import locale import re import sys import time from kiwi import ValueUnset from kiwi.enums import Alignment from kiwi.python import enum try: from decimal import Decimal, InvalidOperation HAVE_DECIMAL = True Decimal, InvalidOperation # pyflakes except: HAVE_DECIMAL = False class Decimal(float): pass InvalidOperation = ValueError if sys.platform == 'win32': try: import ctypes def GetLocaleInfo(value): s = ctypes.create_string_buffer("\000" * 255) ctypes.windll.kernel32.GetLocaleInfoA(0, value, s, 255) return str(s.value) except ImportError: def GetLocaleInfo(value): raise Exception( "ctypes is required for datetime types on win32") __all__ = ['ValidationError', 'lformat', 'converter', 'format_price'] _ = lambda m: gettext.dgettext('kiwi', m) number = (int, float, long, Decimal) class ValidationError(Exception): pass class ConverterRegistry: def __init__(self): self._converters = {} def add(self, converter_type): """ Adds converter_type as a new converter @param converter_type: a L{BaseConverter} subclass """ if not issubclass(converter_type, BaseConverter): raise TypeError("converter_type must be a BaseConverter subclass") c = converter_type() if c.type in self._converters: raise ValueError(converter_type) self._converters[c.type] = c def remove(self, converter_type): """ Removes converter_type from the registry @param converter_type: a L{BaseConverter} subclass """ if not issubclass(converter_type, BaseConverter): raise TypeError("converter_type must be a BaseConverter subclass") ctype = converter_type.type if not ctype in self._converters: raise KeyError(converter_type) del self._converters[ctype] def get_converter(self, converter_type): # This is a hack: # If we're a subclass of enum, create a dynamic subclass on the # fly and register it, it's necessary for enum.from_string to work. if (issubclass(converter_type, enum) and not converter_type in self._converters): self.add(type(enum.__class__.__name__ + 'EnumConverter', (_EnumConverter,), dict(type=converter_type))) try: return self._converters[converter_type] except KeyError: raise KeyError(converter_type) def get_converters(self, base_classes=None): if base_classes is None: return self._converters.values() converters = [] if object in base_classes: #: Ugly, but cannot remove from tuple! base_classes = list(base_classes) base_classes.remove(object) base_classes = tuple(base_classes) converters.append(self._converters[object]) for datatype in self._converters: if issubclass(datatype, base_classes): converters.append(self._converters[datatype]) return converters def check_supported(self, data_type): value = None for t in self._converters.values(): if t.type == data_type or t.type.__name__ == data_type: value = t.type break assert not isinstance(value, str), value if not value: type_names = [t.type.__name__ for t in self._converters.values()] raise TypeError("%s is not supported. Supported types are: %s" % (data_type, ', '.join(type_names))) return value def as_string(self, converter_type, value, format=None): """ Convert to a string @param converter_type: @param value: @param format: """ c = self.get_converter(converter_type) if c.as_string is None: return value if not isinstance(value, c.type): raise TypeError('data: %s must be of %r not %r' % ( value, c.type, type(value))) return c.as_string(value, format=format) def from_string(self, converter_type, value): """ Convert from a string @param converter_type: @param value: """ c = self.get_converter(converter_type) if c.from_string is None: return value return c.from_string(value) def str_to_type(self, value): for c in self._converters.values(): if c.type.__name__ == value: return c.type # Global converter, can be accessed from outside converter = ConverterRegistry() class BaseConverter(object): """ Abstract converter used by all datatypes @cvar type: @cvar name: The name of the datatype. @cvar align: The alignment of the datatype. Normally right for numbers and dates, left for others. Default is left. """ type = None name = None align = Alignment.LEFT def get_compare_function(self): """ @returns: """ return cmp def as_string(self, value, format): """ @param value: @param format: @returns: """ def from_string(self, value): """ @param value: @returns: """ def get_mask(self): """ @returns: """ return None class _StringConverter(BaseConverter): type = str name = _('String') def as_string(self, value, format=None): if format is None: format = '%s' return format % value def from_string(self, value): return str(value) converter.add(_StringConverter) class _UnicodeConverter(BaseConverter): type = unicode name = _('Unicode') def as_string(self, value, format=None): if format is None: format = u'%s' return format % value def from_string(self, value): return unicode(value) converter.add(_UnicodeConverter) class _IntConverter(BaseConverter): type = int name = _('Integer') align = Alignment.RIGHT def as_string(self, value, format=None): """Convert a float to a string""" if format is None: format = '%d' # Do not use lformat here, since an integer should always # be formatted without thousand separators. Think of the # use case of a port number, "3128" is desired, and not "3,128" return format % value def from_string(self, value): "Convert a string to an integer" if value == '': return ValueUnset conv = get_localeconv() thousands_sep = conv["thousands_sep"] # Remove all thousand separators, so int() won't barf at us if thousands_sep and thousands_sep in value: value = value.replace(thousands_sep, '') try: return self.type(value) except ValueError: raise ValidationError( _("%s could not be converted to an integer") % value) converter.add(_IntConverter) class _LongConverter(_IntConverter): type = long name = _('Long') converter.add(_LongConverter) class _BoolConverter(BaseConverter): type = bool name = _('Boolean') def as_string(self, value, format=None): return str(value) def from_string(self, value): "Convert a string to a boolean" if value == '': return ValueUnset if value.upper() in ('TRUE', '1'): return True elif value.upper() in ('FALSE', '0'): return False return ValidationError(_("'%s' can not be converted to a boolean") % value) converter.add(_BoolConverter) class _FloatConverter(BaseConverter): type = float name = _('Float') align = Alignment.RIGHT def as_string(self, value, format=None): """Convert a float to a string""" format_set = True if format is None: # From Objects/floatobject.c: # # The precision (12) is chosen so that in most cases, the rounding noise # created by various operations is suppressed, while giving plenty of # precision for practical use. format = '%.12g' format_set = False as_str = lformat(format, value) # If the format was not set, the resoult should be treated, as # follows. if not format_set and not value % 1: # value % 1 is used to check if value has an decimal part. If it # doen't then it's an integer # When format is '%g', if value is an integer, the result # will also be formated as an integer, so we add a '.0' conv = get_localeconv() as_str += conv.get('decimal_point') + '0' return as_str def from_string(self, value): """Convert a string to a float""" if value == '': return ValueUnset value = filter_locale(value) try: retval = float(value) except ValueError: raise ValidationError(_("This field requires a number, not %r") % value) return retval converter.add(_FloatConverter) class _DecimalConverter(_FloatConverter): type = Decimal name = _('Decimal') align = Alignment.RIGHT def from_string(self, value): if value == '': return ValueUnset value = filter_locale(value) try: retval = Decimal(value) except InvalidOperation: raise ValidationError(_("This field requires a number, not %r") % value) return retval converter.add(_DecimalConverter) # Constants for use with win32 LOCALE_SSHORTDATE = 31 LOCALE_STIMEFORMAT = 4099 DATE_REPLACEMENTS_WIN32 = [ (re.compile('HH?'), '%H'), (re.compile('hh?'), '%I'), (re.compile('mm?'), '%M'), (re.compile('ss?'), '%S'), (re.compile('tt?'), '%p'), (re.compile('dd?'), '%d'), (re.compile('MM?'), '%m'), (re.compile('yyyyy?'), '%Y'), (re.compile('yy?'), '%y') ] # This "table" contains, associated with each # key (ie. strftime "conversion specifications") # a tuple, which holds in the first position the # mask characters and in the second position # a "human-readable" format, used for outputting user # messages (see method _BaseDateTimeConverter.convert_format) DATE_MASK_TABLE = { '%m': ('00', _('mm')), '%y': ('00', _('yy')), '%d': ('00', _('dd')), '%Y': ('0000', _('yyyy')), '%H': ('00', _('hh')), '%M': ('00', _('mm')), '%S': ('00', _('ss')), '%T': ('00:00:00', _('hh:mm:ss')), # FIXME: locale specific '%r': ('00:00:00 LL', _('hh:mm:ss LL')), } class _BaseDateTimeConverter(BaseConverter): """ Abstract class for converting datatime objects to and from strings @cvar date_format: @cvar lang_constant: """ date_format = None align = Alignment.RIGHT def __init__(self): self._keep_am_pm = False self._keep_seconds = False def get_lang_constant_win32(self): raise NotImplementedError def get_lang_constant(self): # This is a method and not a class variable since it does not # exist on all supported platforms, eg win32 raise NotImplementedError def from_dateinfo(self, dateinfo): raise NotImplementedError def get_compare_function(self): # Provide a special comparison function that allows None to be # used, which the __cmp__/__eq__ methods for datatime objects doesn't def _datecmp(a, b): if a is None: if b is None: return 0 return 1 elif b is None: return -1 else: return cmp(a, b) return _datecmp def get_format(self): if sys.platform == 'win32': values = [] for constant in self.get_lang_constant_win32(): value = GetLocaleInfo(constant) values.append(value) format = " ".join(values) # Now replace them to strftime like masks so the logic # below still applies for pattern, replacement in DATE_REPLACEMENTS_WIN32: format = pattern.subn(replacement, format)[0] else: format = locale.nl_langinfo(self.get_lang_constant()) format = format.replace('%r', '%I:%M:%S %p') format = format.replace('%T', '%H:%M:%S') # Strip AM/PM if not self._keep_am_pm: if '%p' in format: format = format.replace('%p', '') # 12h -> 24h format = format.replace('%I', '%H') # Strip seconds if not self._keep_seconds: if '%S' in format: format = format.replace('%S', '') # Strip trailing characters while format[-1] in ('.', ':', ' '): format = format[:-1] return format def get_mask(self): mask = self.get_format() for format_char, mask_char in DATE_MASK_TABLE.items(): mask = mask.replace(format_char, mask_char[0]) return mask def as_string(self, value, format=None): "Convert a date to a string" if format is None: format = self.get_format() if value is None: return '' if isinstance(value, (datetime.date, datetime.datetime)): if value.year < 1900: raise ValidationError( _("You cannot enter a year before 1900")) return value.strftime(format) def _convert_format(self, format): "Convert the format string to a 'human-readable' format" for char in DATE_MASK_TABLE.keys(): format = format.replace(char, DATE_MASK_TABLE[char][1]) return format def from_string(self, value): "Convert a string to a date" if value == "": return None # We're only supporting strptime values for now, # perhaps we should add macros, to be able to write # yyyy instead of %Y format = self.get_format() try: # time.strptime (python 2.4) does not support %r # pending SF bug #1396946 dateinfo = time.strptime(value, format) date = self.from_dateinfo(dateinfo) except ValueError: raise ValidationError( _('This field requires a date of the format "%s" and ' 'not "%s"') % (self._convert_format(format), value)) if isinstance(date, (datetime.date, datetime.datetime)): if date.year < 1900: raise ValidationError( _("You cannot enter a year before 1900")) return date class _TimeConverter(_BaseDateTimeConverter): type = datetime.time name = _('Time') date_format = '%X' def get_lang_constant_win32(self): return [LOCALE_STIMEFORMAT] def get_lang_constant(self): return locale.T_FMT def from_dateinfo(self, dateinfo): # hour, minute, second return datetime.time(*dateinfo[3:6]) converter.add(_TimeConverter) class _DateTimeConverter(_BaseDateTimeConverter): type = datetime.datetime name = _('Date and Time') date_format = '%c' def get_lang_constant_win32(self): return [LOCALE_SSHORTDATE, LOCALE_STIMEFORMAT] def get_lang_constant(self): return locale.D_T_FMT def from_dateinfo(self, dateinfo): # year, month, day, hour, minute, second return datetime.datetime(*dateinfo[:6]) converter.add(_DateTimeConverter) class _DateConverter(_BaseDateTimeConverter): type = datetime.date name = _('Date') date_format = '%x' def get_lang_constant_win32(self): return [LOCALE_SSHORTDATE] def get_lang_constant(self): return locale.D_FMT def from_dateinfo(self, dateinfo): # year, month, day return datetime.date(*dateinfo[:3]) converter.add(_DateConverter) class _ObjectConverter(BaseConverter): type = object name = _('Object') as_string = None from_string = None converter.add(_ObjectConverter) class _EnumConverter(BaseConverter): type = enum name = _('Enum') def as_string(self, value, format=None): if not isinstance(value, self.type): raise ValidationError( "value must be an instance of %s, not %r" % ( self.type, value)) return value.name def from_string(self, value): names = self.type.names if not value in names: raise ValidationError( "Invalid value %s for enum %s" % (value, self.type)) return names[value] converter.add(_EnumConverter) def lformat(format, value): """Like locale.format but with grouping enabled""" return locale.format(format, value, 1) def get_localeconv(): conv = locale.localeconv() monetary_locale = locale.getlocale(locale.LC_MONETARY) numeric_locale = locale.getlocale(locale.LC_NUMERIC) # Patching glibc's output # See http://sources.redhat.com/bugzilla/show_bug.cgi?id=1294 if monetary_locale[0] == 'pt_BR': conv['p_cs_precedes'] = 1 conv['p_sep_by_space'] = 1 # Since locale 'C' doesn't have any information on monetary and numeric # locale, use default en_US, so we can have formated numbers if not monetary_locale[0]: conv["negative_sign"] = '-' conv["currency_symbol"] = '$' conv['mon_thousands_sep'] = '' conv['mon_decimal_point'] = '.' conv['p_sep_by_space'] = 0 if not numeric_locale[0]: conv['decimal_point'] = '.' return conv def filter_locale(value, monetary=False): """ Removes the locale specific data from the value string. Currently we only remove the thousands separator and convert the decimal point. The returned value of this function can safely be passed to float() @param value: value to convert @param monetary: if we should treat it as monetary data or not @returns: the value without locale specific data """ def _filter_thousands_sep(value, thousands_sep): if not thousands_sep: return value # Check so we don't have any thousand separators to the right # of the decimal point th_sep_count = value.count(thousands_sep) if th_sep_count and decimal_points: decimal_point_pos = value.index(decimal_point) if thousands_sep in value[decimal_point_pos+1:]: raise ValidationError(_("You have a thousand separator to the " "right of the decimal point")) check_value = value[:decimal_point_pos] else: check_value = value # Verify so the thousand separators are placed properly # TODO: Use conv['grouping'] for locales where it's not 3 parts = check_value.split(thousands_sep) # First part is a special case, It can be 1, 2 or 3 if 3 > len(parts[0]) < 1: raise ValidationError(_("Inproperly placed thousands separator")) # Middle parts should have a length of 3 for part in parts[1:]: if len(part) != 3: raise ValidationError(_("Inproperly placed thousand " "separators: %r" % (parts,))) # Remove all thousand separators return value.replace(thousands_sep, '') conv = get_localeconv() # Check so we only have one decimal point if monetary: decimal_point = conv["mon_decimal_point"] else: decimal_point = conv["decimal_point"] if decimal_point != '': decimal_points = value.count(decimal_point) if decimal_points > 1: raise ValidationError( _('You have more than one decimal point ("%s") ' ' in your number "%s"' % (decimal_point, value))) if monetary: sep = conv["mon_thousands_sep"] else: sep = conv["thousands_sep"] if sep and sep in value: value = _filter_thousands_sep(value, sep) # Replace all decimal points with . if decimal_point != '.' and decimal_point != '': value = value.replace(decimal_point, '.') return value # FIXME: Get rid of this from kiwi.currency import currency, format_price # Pyflakes currency format_price PIDA-0.5.1/contrib/kiwi/kiwi/decorators.py0000644000175000017500000000661710652670747016443 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Function and method decorators used in kiwi""" from kiwi.log import kiwi_log class deprecated(object): """ I am a decorator which prints a deprecation warning each time you call the decorated (and deprecated) function """ def __init__(self, new, log=None): """ @param new: the name of the new function replacing the old deprecated one @type new: string """ self._new = new self._log = log or kiwi_log def __call__(self, func): def wrapper(*args, **kwargs): self._log.warn("%s is deprecated, use %s instead" % (func.__name__, self._new)) return func(*args, **kwargs) try: wrapper.__name__ = func.__name__ except TypeError, e: # __name__ is readonly in Python 2.3 if e.args and e.args[0].find('readonly') != -1: pass else: raise return wrapper class signal_block(object): """ A decorator to be used on L{kiwi.ui.views.SlaveView} methods. It takes a list of arguments which is the name of the widget and the signal name separated by a dot. For instance: >>> class MyView(SlaveView): ... @signal_block('money.changed') ... def update_money(self): ... self.money.set_value(10) ... def on_money__changed(self): ... pass When calling update_money() the value of the spinbutton called money will be updated, but on_money__changed will not be called. """ def __init__(self, *signals): self.signals = [] for signal in signals: if not isinstance(signal, str): raise TypeError("signals must be a list of strings") if signal.count('.') != 1: raise TypeError("signal must have exactly one dot") self.signals.append(signal.split('.')) def __call__(self, func): def wrapper(view, *args, **kwargs): for name, signal in self.signals: widget = getattr(view, name, None) if widget is None: raise TypeError("Unknown widget %s in view " % name) view.handler_block(widget, signal) retval = func(view, *args, **kwargs) for name, signal in self.signals: widget = getattr(view, name, None) view.handler_unblock(widget, signal) return retval wrapper.__name__ = func.__name__ return wrapper PIDA-0.5.1/contrib/kiwi/kiwi/desktopparser.py0000644000175000017500000001142610652670747017156 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006 Johan Dahlin # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # # Based on gkeyfile.c from glib, written by # # Ray Strode # Matthias Clasen # from ConfigParser import ConfigParser # Private def _localize(option, locale): if locale: option = option + '[%s]' % locale return option def _tobool(s): if s == 'true': return True return False def _frombool(s): if s: return 'true' return 'false' class DesktopParser(ConfigParser): """ A DesktopParser for GNOME/KDE .desktop files. The API is similar to GKeyFile from glib. Example: >>> parser = DesktopParser() >>> parser.read('/usr/share/applications/gnome-terminal.desktop') >>> parser.get_locale('Desktop Entry', 'Comment', 'pt') """ def __init__(self, defaults=None): ConfigParser.__init__(self, defaults) self._list_separator = ';' # ConfigParser overrides def optionxform(self, optionstr): # .desktop files are case sensitive # The default implementation makes it lowercase, # so override to just use it as it was read return optionstr # Public def set_list_separator(self, separator): """ Sets the character which is used to separate values in lists. Typically ';' or ',' are used as separators. The default list separator is ';'. @param separator: the separator """ self._list_separator = separator def set_locale(self, section, option, locale, value): """ @param section: section name @param option: an option @param locale: a locale @param value: value to set """ self.set(section, _localize(option, locale), value) def get_locale(self, section, option, locale): """ @param section: section name @param option: an option @param locale: a locale """ return self.get(section, _localize(option, locale)) def get_string_list(self, section, option): """ @param section: section name @param option: an option """ return self.get(section, option).split(self._list_separator) def set_string_list(self, section, option, values): """ @param section: section name @param option: an option @param values: list of string values """ value = self._list_separator.join(values) self.set(section, option, value) def get_integer_list(self, section, option): """ @param section: section name @param option: an option """ return map(int, self.get_string_list(section, option)) def set_integer_list(self, section, option, values): """ @param section: section name @param option: an option @param values: list of integer values """ self.set_string_list(section, option, map(str, values)) def get_boolean_list(self, section, option): """ @param section: section name @param option: an option """ return map(_tobool, self.get_string_list(section, option)) def set_boolean_list(self, section, option, values): """ @param section: section name @param option: an option @param values: list of boolean values """ self.set_string_list(section, option, map(_frombool, values)) def set_string_list_locale(self, section, option, locale, values): """ @param section: section name @param option: an option @param locale: a locale @param values: list of string values """ self.set_string_list(section, _localize(option, locale), values) def get_string_list_locale(self, section, option, locale): """ @param section: section name @param option: an option @param locale: a locale """ return self.get_string_list(section, _localize(option, locale)) PIDA-0.5.1/contrib/kiwi/kiwi/dist.py0000644000175000017500000002437410652670747015241 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Distutils extensions and utilities""" from distutils.command.install_data import install_data from distutils.command.install_lib import install_lib from distutils.core import setup as DS_setup from distutils.dep_util import newer from distutils.log import info, warn from distutils.sysconfig import get_python_lib from fnmatch import fnmatch import os import new import sys class _VariableExtender: def __init__(self, distribution): install = distribution.get_command_obj('install') name = distribution.get_name() prefix = install.prefix if not prefix: prefix = sys.prefix # Remove trailing / if prefix[-1] == '/': prefix = prefix[:-1] self.prefix = prefix self.datadir = os.path.join('share', name) if self.prefix == '/usr': self.sysconfdir = '/etc' else: self.sysconfdir = os.path.join('etc') def extend(self, string, relative=False): """ @param string: string to replace @param relative: if True, assume the content of all variables to be relative to the prefix """ for name, var in [('sysconfdir', self.sysconfdir), ('datadir', self.datadir), ('prefix', self.prefix)]: if not relative and name != 'prefix': var = os.path.join(self.prefix, var) string = string.replace('$' + name, var) return string class KiwiInstallLib(install_lib): # Overridable by subclass resources = {} global_resources = {} def _get_template(self): return os.path.join(self.install_dir, self.distribution.get_name(), '__installed__.py') def generate_template(self): if 'bdist_wininst' in sys.argv: prefix = 'import sys\nprefix = sys.prefix' else: install = self.distribution.get_command_obj('install') # Use raw strings so UNC paths which has \\ works prefix = 'prefix = r"%s"' % install.prefix or sys.prefix filename = self._get_template() self.mkpath(os.path.dirname(filename)) fp = open(filename, 'w') fp.write('# Generated by setup.py do not modify\n') fp.write('import os\n') fp.write('%s\n' % prefix) self._write_dictionary(fp, 'resources', self.resources) self._write_dictionary(fp, 'global_resources', self.global_resources) fp.close() return filename def _write_dictionary(self, fp, name, dictionary): fp.write('%s = {}\n' % name) for key, value in dictionary.items(): value = value.replace('/', os.sep) value = self.varext.extend(value) value = value.replace(self.varext.prefix, '$prefix') parts = [] for part in value.split(os.sep): if part == "": part = os.sep if part == '$prefix': part = 'prefix' else: part = '"%s"' % part parts.append(part) fp.write("%s['%s'] = %s\n" % ( name, key, 'os.path.join(%s)' % ', '.join(parts))) def get_outputs(self): filename = self._get_template() files = [filename] + self._bytecode_filenames([filename]) return install_lib.get_outputs(self) + files def install(self): self.varext = _VariableExtender(self.distribution) return install_lib.install(self) + [self.generate_template()] # Backwards compat TemplateInstallLib = KiwiInstallLib class KiwiInstallData(install_data): def run(self): self.varext = _VariableExtender(self.distribution) # Extend variables in all data files data_files = [] for target, files in self.data_files[:]: data_files.append((self.varext.extend(target, True), files)) self.data_files = data_files return install_data.run(self) def get_site_packages_dir(*dirs): """ Gets the relative path of the site-packages directory This is mainly useful for setup.py usage: >>> setup(... data_files=[(get_site_packages_dir('foo'), files..)]) where files is a list of files to be installed in a directory called foo created in your site-packages directory @param dirs: directory names to be appended """ libdir = get_python_lib(plat_specific=False, standard_lib=True, prefix='') return os.path.join(libdir, 'site-packages', *dirs) def listfiles(*dirs): """ Lists all files in directories and optionally uses basic shell matching, example: >>> listfiles('data', 'glade', '*.glade') ['data/glade/Foo.glade', 'data/glade/Bar.glade', ...] @param dirs: directory parts """ dir, pattern = os.path.split(os.path.join(*dirs)) abspath = os.path.abspath(dir) if not os.path.exists(abspath): # TODO: Print a warning here? return [] return [os.path.join(dir, filename) for filename in os.listdir(abspath) if filename[0] != '.' and fnmatch(filename, pattern)] def compile_po_files(domain, dirname='locale'): """ Compiles po files to mo files. Note. this function depends on gettext utilities being installed @param domain: gettext domain @param dirname: base directory @returns: a list of po files """ if os.system('msgfmt 2> /dev/null') != 256: warn('msgfmt is missing, not installing translations') return [] data_files = [] for po in listfiles('po', '*.po'): lang = os.path.basename(po[:-3]) mo = os.path.join(dirname, lang, 'LC_MESSAGES', domain + '.mo') if not os.path.exists(mo) or newer(po, mo): directory = os.path.dirname(mo) if not os.path.exists(directory): info("creating %s" % directory) os.makedirs(directory) cmd = 'msgfmt -o %s %s' % (mo, po) info('compiling %s -> %s' % (po, mo)) if os.system(cmd) != 0: raise SystemExit("Error while running msgfmt") dest = os.path.dirname(os.path.join('share', mo)) data_files.append((dest, [mo])) return data_files def listpackages(root, exclude=None): """Recursivly list all packages in directory root Optionally exclude can be specified which is a string like foo/bar. @param root: directory @param exclude: optional packages to be skipped """ packages = [] if not os.path.exists(root): raise ValueError("%s does not exists" % (root,)) if not os.path.isdir(root): raise ValueError("%s must be a directory" % (root,)) if os.path.exists(os.path.join(root, '__init__.py')): packages.append(root.replace('/', '.')) for filename in os.listdir(root): full = os.path.join(root, filename) if os.path.isdir(full): packages.extend(listpackages(full)) if exclude: for package in packages[:]: if package.startswith(exclude): packages.remove(package) return packages def setup(**kwargs): """ A drop in replacement for distutils.core.setup which integrates nicely with kiwi.environ @kwarg resources: @kwarg global_resources: @kwarg templates: List of templates to install """ resources = {} global_resources = {} templates = [] if 'resources' in kwargs: resources = kwargs.pop('resources') if 'global_resources' in kwargs: global_resources = kwargs.pop('global_resources') if 'templates' in kwargs: templates = kwargs.pop('templates') def run_install(self): name = kwargs.get('name') if name: self.data_files.extend(compile_po_files(name)) KiwiInstallData.run(self) varext = _VariableExtender(self.distribution) for path, files in templates: install = self.distribution.get_command_obj('install') target = os.path.join(install.prefix, path) if install.root: if target[0] == '/': target = target[1:] target = os.path.join(install.root, target) if not os.path.exists(target): info("creating %s" % target) os.makedirs(target) for filename in files: data = open(filename).read() data = varext.extend(data) target_file = os.path.join(target, filename) info('installing template %s' % target_file) open(target_file, 'w').write(data) # Copied from gazpacho, needs to be tested #if 'bdist_wininst' in sys.argv: # prefix_code = 'import sys; prefix = sys.prefix' #else: # install = self.distribution.get_command_obj('install') # prefix = install.prefix # prefix_code = 'prefix = r"%s"' % prefix # distutils uses old style classes InstallData = new.classobj('InstallData', (KiwiInstallData,), dict(run=run_install)) InstallLib = new.classobj('InstallLib', (KiwiInstallLib,), dict(resources=resources, global_resources=global_resources)) kwargs['cmdclass'] = dict(install_data=InstallData, install_lib=InstallLib) DS_setup(**kwargs) PIDA-0.5.1/contrib/kiwi/kiwi/enums.py0000644000175000017500000000312010652670747015407 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2006-2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # from kiwi.python import enum class ComboColumn(enum): (LABEL, DATA) = range(2) class ComboMode(enum): (UNKNOWN, STRING, DATA) = range(3) class Alignment(enum): (LEFT, RIGHT) = range(2) class Direction(enum): (LEFT, RIGHT) = (1, -1) class ListType(enum): """ - NORMAL: Add, Remove, Edit - UNEDITABLE: Add, Remove - UNREMOVABLE: Add, Edit - READONLY: No buttons """ (NORMAL, READONLY, UNREMOVABLE, UNEDITABLE) = range(4) class SearchFilterPosition(enum): """ An enum used to indicate where a search filter should be added to a SearchContainer:: - TOP: top left corner - BOTTOM: bottom """ (TOP, BOTTOM) = range(2) PIDA-0.5.1/contrib/kiwi/kiwi/environ.py0000644000175000017500000003343610652670747015755 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Environment helpers: path mangling and resource management""" import gettext import imp import locale import os import sys from kiwi.log import Logger from kiwi.python import namedAny __all__ = ['Application', 'Library', 'app', 'environ', 'require_gazpacho', 'is_gazpacho_required'] log = Logger('environ') EnvironmentError = EnvironmentError # From http://tinyurl.com/77ukj def _is_frozen(): "Helper function to check if we're frozen in a py2exe'd file" return (hasattr(sys, "frozen") or # new py2exe hasattr(sys, "importers") # old py2exe or imp.is_frozen("__main__")) # tools/freeze class Environment: """Environment control When you want to access a resource on the filesystem, such as an image or a glade file you use this object. External libraries or applications are free to add extra directories""" def __init__(self, root='.'): self._resources = {} self._extensions = {} self._root = root # Add some compressed formats as alternative extensions to # "glade" resources. A patch has been added to gazpacho trunk # (rev. 2251) to support loading those compressed formats. self._add_extensions("glade", ".bz2", ".gz") self._add_resource_variable("glade", "KIWI_GLADE_PATH") self._add_resource_variable("image", "KIWI_IMAGE_PATH") def get_root(self): return self._root def get_log_level(self): return os.environ.get('KIWI_LOG') def _add_extensions(self, resource, *args): exts = self._extensions.setdefault(resource, []) exts.extend(list(args)) def _add_resource_variable(self, resource, variable): """Add resources from an environment variable""" env = os.environ.get(variable, '') for path in env.split(os.pathsep): if not path: continue self.add_resource(resource, env) def get_resource_paths(self, resource): if not resource in self._resources: raise EnvironmentError("No resource called: %s" % resource) return self._resources[resource] def add_resource(self, resource, path): path = os.path.join(self._root, path) if not os.path.isdir(path): raise EnvironmentError("path %s must be a directory" % path) reslist = self._resources.setdefault(resource, []) if not path in reslist: reslist.append(path) def add_resources(self, **kwargs): for resource, path in kwargs.items(): if resource == 'locale': try: self.add_resource(resource, path) except EnvironmentError: continue self.add_resource(resource, path) def find_resource(self, resource, name): """Locate a specific resource of called name of type resource""" resource_paths = self.get_resource_paths(resource) # Look for alternative extensions for this resource. # But check without extensions first exts = [""] + self._extensions.get(resource, []) # Check "scriptdir", which is the directory the script is ran from # and the working directory ("") after all the others fail scriptdir = os.path.dirname(os.path.abspath(sys.argv[0])) for path in resource_paths + [scriptdir, ""]: for ext in exts: filename = os.path.join(self._root, path, "".join((name, ext))) if os.path.exists(filename) and os.path.isfile(filename): return filename raise EnvironmentError("Could not find %s resource: %s" % ( resource, name)) def _get_epydoc(self): if sys.argv == ['IGNORE']: return True elif os.path.basename(sys.argv[0]) == 'epyrun': return True return False epydoc = property(_get_epydoc) class Library(Environment): """A Library is a local environment object, it's a subclass of the Environment class. It's used by libraries and applications (through the Application class) It provides a way to manage local resources, which should only be seen in the current context. Libraries are usually instantiated in __init__.py in the topmost package in your library, an example usage is kiwi itself which does: >>> from kiwi.environ import Library >>> lib = Library('kiwi') >>> if lib.uninstalled: >>> lib.add_global_resource('glade', 'glade') >>> lib.add_global_resource('pixmap', 'pixmaps') which is combined with the following class in setup.py: >>> from kiwi.dist import InstallLib >>> class InstallLib(TemplateInstallLib): >>> name = 'kiwi' >>> global_resources = dict(glade='$datadir/glade', >>> pixmap='$datadir/pixmaps') >>> >>> setup(..., >>> data_files=[('share/kiwi/glade', >>> listfiles('glade', '*.glade')), >>> ('share/kiwi/pixmaps', >>> listfiles('pixmaps', '*.png')), >>> cmdclass=dict(install_lib=InstallLib)) It may seems like a bit of work, but this is everything that's needed for kiwi to figure out how to load resources when installed and when running in an uninstalled mode, eg directly from the source tree. To locate a pixmap called kiwi.png the following is enough: >>> from kiwi.environ import environ >>> environ.find_resource('pixmap', 'kiwi.png') '/usr/share/kiwi/pixmaps/kiwi.png' # installed mode Which will lookup the resource kiwi.png in the domain pixmap, which points to $datadir/pixmaps (eg $prefix/share/kiwi/pixmaps) when running in installed mode and from $builddir/pixmaps otherwise. """ def __init__(self, name, root='..', dirname=None): """ Creates a new library, this is usually called in __init__.py in a toplevel package. All resources will be relative to the I{root} directory. @param name: name of the library @param root: root directory @param dirname: """ self.name = name # py2exe if _is_frozen(): log.info('py2exe found') executable = os.path.realpath(os.path.abspath(sys.executable)) root = os.path.dirname(executable) # normal else: if dirname == None: # Figure out the absolute path to the caller caller = sys._getframe(1).f_locals['__file__'] dirname = os.path.split(caller)[0] dirname = os.path.realpath(os.path.abspath(dirname)) root = os.path.abspath(os.path.join(dirname, root)) Environment.__init__(self, root=root) basedir = os.path.join(root, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages') if os.path.exists(basedir): sys.path.insert(0, basedir) g = globals() l = locals() try: module = __import__(name, g, l) except ImportError: raise ImportError("Failed to import module %s" % name) # Load installed try: module = __import__(name + '.__installed__', g, l, [name]) except ImportError: uninstalled = True else: uninstalled = False if not hasattr(module, 'resources'): raise ValueError("module %s.__installed__ must define a " "resources attribute" % name) if not hasattr(module, 'global_resources'): raise ValueError("module %s.__installed__ must define a " "global_resources attribute" % name) self.add_resources(**module.resources) self.add_global_resources(**module.global_resources) self.prefix = module.prefix self.uninstalled = uninstalled def _check_translation(self, domain, directory): loc = locale.getlocale()[0] # We're not interested in warnings for these locales if loc in (None, 'C', 'en_US'): return # check sv_SE and sv locales = [loc] if '_' in loc: locales.append(loc.split('_')[0]) for l in locales: path = os.path.join(directory, l, 'LC_MESSAGES', domain + '.mo') if os.path.exists(path): break else: log.warn('No %s translation found for domain %s' % (loc, domain)) def enable_translation(self, domain=None, localedir=None): """ Enables translation for a library @param domain: optional, if not specified name sent to constructor will be used @param localedir: directory to get locales from when running in uninstalled mode. If not specified a directory called 'locale' in the root will be used. """ if not domain: domain = self.name if not localedir: localedir = 'locale' if self.uninstalled: try: self.add_resource('locale', localedir) except EnvironmentError: pass # XXX: locale should not be a list localedir = self._resources.get('locale') if not localedir: # Only complain when running installed if not self.uninstalled: log.warn('no localedir for: %s' % domain) return directory = gettext.bindtextdomain(domain, localedir[0]) self._check_translation(domain, directory) # For libglade, but only on non-win32 systems if hasattr(locale, 'bindtextdomain'): locale.bindtextdomain(domain, localedir[0]) # Gtk+ only supports utf-8, it makes no sense to support # other encodings in kiwi it self # This is not present in Python 2.3 if hasattr(gettext, 'bind_textdomain_codeset'): gettext.bind_textdomain_codeset(domain, 'utf-8') def set_application_domain(self, domain): """ Sets the default application domain @param domain: the domain """ gettext.textdomain(domain) # For libglade, but only on non-win32 systems if hasattr(locale, 'textdomain'): locale.textdomain(domain) def add_global_resource(self, resource, path): """Convenience method to add a global resource. This is the same as calling kiwi.environ.environ.add_resource """ global environ environ.add_resource(resource, os.path.join(self._root, path)) def add_global_resources(self, **kwargs): for resource, path in kwargs.items(): self.add_global_resource(resource, path) class Application(Library): """Application extends a L{Library}. It's meant to be used by applications Libraries are usually instantiated in __init__.py in the topmost package in your library, an example usage is kiwi itself which does: >>> from kiwi.environ import Application >>> app = Application('gnomovision') >>> if app.uninstalled: >>> app.add_global_resource('glade', 'glade') >>> app.add_global_resource('pixmap', 'pixmaps') If you want to do translations, you also need to do the following: >>> app.enable_translation() @see: L{Library} for more information on how to integrate it with the standard distutils configuration. """ def __init__(self, name, root='..', path='main', dirname=None): global app if app is not None: raise TypeError("Application is already set to %r" % app) app = self if not dirname: dirname = os.path.abspath(os.path.dirname(sys.argv[0])) Library.__init__(self, name, root, dirname) self._path = path def _get_main(self): try: module = namedAny(self._path) except: log.warn('importing %s' % self._path) raise main = getattr(module, 'main', None) if not main or not callable(main): raise SystemExit("ERROR: Could not find item '%s' in module %s" % 'main', self._path) return main def enable_translation(self, domain=None, localedir=None): """ Enables translation for a application See L{Library.enable_translation}. """ Library.enable_translation(self, domain, localedir) old_domain = gettext.textdomain() if old_domain != 'messages': log.warn('overriding default domain, was %s' % old_domain) self.set_application_domain(domain) def run(self): main = self._get_main() try: sys.exit(main(sys.argv)) except KeyboardInterrupt: raise SystemExit _require_gazpacho_loader = False def require_gazpacho(): global _require_gazpacho_loader _require_gazpacho_loader = True def is_gazpacho_required(): global _require_gazpacho_loader return _require_gazpacho_loader # Global variables environ = Environment() app = None PIDA-0.5.1/contrib/kiwi/kiwi/interfaces.py0000644000175000017500000001166210652670747016415 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Johan Dahlin # """Interface specifications and utilities""" from kiwi.component import Interface class IProxyWidget(Interface): """ IProxyWidget is a widget that can be attached to a proxy. Signals:: content-changed: This must be emitted when the content changes Properties:: data-type: string, data type of the model model-attribute: string, name of the attribute in the model """ def read(): """ Reads the content of the widget and returns in an appropriate data type. ValueUnset is returned when the user has not modified the entry """ pass def update(value): """ Updates the content of the widget with a value """ pass class IValidatableProxyWidget(IProxyWidget): """ IValidatableProxyWidget extends IProxyWidget with validation support Signals:: validate: This emitted so each widget can provide it's own custom validation. validation-changed: This is emitted when the validation status changes, mainly used by the proxy. Properties:: mandatory: bool, if the widget is mandatory """ def is_valid(): pass def validate(force=False): pass class IEasyCombo(Interface): def prefill(itemdata, sort=False): """Fills the Combo with listitems corresponding to the itemdata provided. Parameters: - itemdata is a list of strings or tuples, each item corresponding to a listitem. The simple list format is as follows:: >>> [ label0, label1, label2 ] If you require a data item to be specified for each item, use a 2-item tuple for each element. The format is as follows:: >>> [ ( label0, data0 ), (label1, data1), ... ] - Sort is a boolean that specifies if the list is to be sorted by label or not. By default it is not sorted """ def append_item(label, data=None): """ Adds a single item to the Combo. @param label: a string with the text to be added @param data: the data to be associated with that item """ def clear(): """Removes all items from the widget""" def select(data): """ @param obj: data or text to select """ def select_item_by_position(position): """ Selects an item in the combo from a integer where 0 represents the first item. @param position: an integer """ def select_item_by_label(text): """ @param text: text to select """ def select_item_by_data(data): """ @param data: object to select """ def get_selected_label(): """ @returns: the label of the currently selected item """ def get_selected_data(): """ @returns: the data of the currently selected item """ def get_selected(): """ @returns: selected text or item or None if nothing is selected """ def get_model_strings(): pass def get_model_items(): pass class AbstractGladeAdaptor(Interface): """Abstract class that define the functionality an class that handle glade files should provide.""" def get_widget(self, widget_name): """Return the widget in the glade file that has that name""" def get_widgets(self): """Return a tuple with all the widgets in the glade file""" def attach_slave(self, name, slave): """Attaches a slaveview to the view this adaptor belongs to, substituting the widget specified by name. The widget specified *must* be a eventbox; its child widget will be removed and substituted for the specified slaveview's toplevel widget """ def signal_autoconnect(self, dic): """Connect the signals in the keys of dict with the objects in the values of dic """ class ISearchFilter(Interface): def get_state(): """ @rtype: L{QueryState} """ PIDA-0.5.1/contrib/kiwi/kiwi/log.py0000644000175000017500000001235510652670747015053 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """ Extension to the logging module This module defines a couple of extensions to the logging module included in the python standard distribution. It creates an additional logging handler that print log records on the standard output. This handler is only showing records which has a level set to logging.WARNING or higher by default. The messages printed by this handler can be modified by using the environment variable called KIWI_LOG. The syntax for the string which KIWI_LOG points to is the following:: domain ':' level [, domain ':', level] domain can contain wildcards such as * and ? level is an integer 1-5 which defines the minimal level: - B{5}: DEBUG - B{4}: INFO - B{3}: WARNING - B{2}: ERROR - B{1}: CRITICAL Examples:: KIWI_LOG="stoq*:5" will print all the messages in a domain starting with stoq with DEBUG or higher:: KIWI_LOG="kiwi*:4,stoq.*:5" will print all the messages with INFO or higher in all domains starting with kiwi, and all the messages in the stoq.* domains which are DEBUG or higher Inspiration for the syntax is taken from the U{debugging facilities} of the U{GStreamer} multimedia framework. """ import fnmatch import logging import os # Globals _console = None _filter = None class LogError(Exception): pass class Logger(object): # Backwards compatibility, we should probably replace the callsites # with import logging; logging.getLogger(name) def __new__(self, name): return logging.getLogger(name) class _Logger(logging.Logger): def __call__(self, message, *args, **kwargs): self.info(message, *args, **kwargs) logging.setLoggerClass(_Logger) class ReversedGlobalFilter(logging.Filter): """ It's like a reversed filter, the default behavior is to not show the message, you need to add custom filters for all the records you wish to see """ def __init__(self): logging.Filter.__init__(self) self.filters = [] def add_filter(self, f, level=logging.DEBUG): self.filters.append((f, level)) def filter(self, record): for f, level in self.filters: if (record.levelno >= level and fnmatch.fnmatch(record.name, f)): return True return False def set_log_file(filename, mask=None): """ @param filename: @param mask: optional """ file_handler = logging.FileHandler(filename, 'w') file_handler.setFormatter(logging.Formatter( '%(asctime)s %(name)-18s %(levelname)-8s %(message)s', datefmt='%F %T')) root = logging.getLogger() root.addHandler(file_handler) if mask: file_filter = ReversedGlobalFilter() file_filter.add_filter(mask, logging.DEBUG) file_handler.addFilter(file_filter) return file_handler.stream def set_log_level(name, level): """ @param name: logging category @param level: level """ global _filter _filter.add_filter(name, level) def _read_log_levels(console_filter): log_levels = {} # bootstrap issue, cannot depend on kiwi.environ log_level = os.environ.get('KIWI_LOG') if not log_level: return log_levels for part in log_level.split(','): if not ':' in part: continue if part.count(':') > 1: raise LogError("too many : in part %s" % part) name, level = part.split(':') try: level = int(level) except ValueError: raise LogError("invalid level: %s" % level) if level < 0 or level > 5: raise LogError("level must be between 0 and 5") level = 50 - (level * 10) console_filter.add_filter(name, level) def _create_console(): global _filter, _console console = logging.StreamHandler() console.setFormatter(logging.Formatter( "%(asctime)s %(name)-20s %(message)s", datefmt='%T')) root = logging.getLogger() root.addHandler(console) root.setLevel(logging.DEBUG) console_filter = ReversedGlobalFilter() # Always display warnings or higher on the console console_filter.add_filter('*', logging.WARNING) console.addFilter(console_filter) _read_log_levels(console_filter) # Set globals _filter = console_filter _console = console _create_console() kiwi_log = Logger('kiwi') PIDA-0.5.1/contrib/kiwi/kiwi/model.py0000644000175000017500000002357210652670747015375 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2002-2003, 2005-2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Christian Reis # Johan Dahlin # """Holds the models part of the Kiwi Framework""" import os import pickle from kiwi import ValueUnset from kiwi.log import Logger log = Logger('model') # # A model that implements half of an observer pattern; when its # attributes are changed, it notifies any proxies of the change. # class Model: """ The Model is a mixin to be used by domain classes when attached to Proxies. It also provides autonotification of changes to the attached proxies. Note that if using setters, a specific call to notify_proxies() may be necessary; see the doc for __setattr__.""" def __init__(self): self.ensure_init() def ensure_init(self): """ Sets up the variables so the Model's getattr hook and proxy notification work properly. """ # Work around setattr hook. The _v prefixes to the variables let # the ZODB know that there are non-persistant values. This # workaround is fine because the API protects them and it # doesn't affect any other persistence mechanism I know of. self.__dict__["_v_blocked_proxies"] = [] self.__dict__["_v_proxies"] = {} self.__dict__["_v_autonotify"] = 1 def disable_autonotify(self): """ disable automatic notification to proxies based on __setattr__. All changes to the model must be followed by a call to notify_proxies() to allow the proxies to notice the change.""" if not hasattr(self, "_v_proxies"): self.ensure_init() self._v_autonotify = 0 def notify_proxies(self, attr): """Notify proxies that an attribute value has changed.""" if not hasattr(self, "_v_proxies"): self.ensure_init() for proxy in self._v_proxies.get(attr, []): if proxy not in self._v_blocked_proxies: proxy.update(attr, ValueUnset, block=True) def register_proxy_for_attribute(self, attr, proxy): """ Attach a proxy to an attribute. The proxy will be notified of changes to that particular attribute (my means of Proxy.notify()).""" if not hasattr(self, "_v_proxies"): self.ensure_init() # XXX: should use weakref if possible, and if not, warn of leaks proxies = self._v_proxies if not proxies.has_key(attr): proxies[attr] = [proxy] else: if proxy in proxies[attr]: raise AssertionError, ("Tried to attach proxy %s " "twice to attribute `%s'." % ( proxy, attr )) proxies[attr].append(proxy) def unregister_proxy_for_attribute(self, attr, proxy): """Detach a proxy from an attribute.""" if not hasattr(self, "_v_proxies"): self.ensure_init() proxies = self._v_proxies if proxies.has_key(attr) and proxy in proxies[attr]: # Only one listener per attribute per proxy, so remove() # works proxies[attr].remove(proxy) def unregister_proxy(self, proxy): """Deattach a proxy completely from the model""" if not hasattr(self, "_v_proxies"): self.ensure_init() proxies = self._v_proxies for attribute in proxies.keys(): if proxy in proxies[attribute]: # Only one listener per attribute per proxy, so remove() # works proxies[attribute].remove(proxy) def flush_proxies(self): """Removes all proxies attached to Model""" self._v_proxies = {} self._v_blocked_proxies = [] def block_proxy(self, proxy): """ Temporarily block a proxy from receiving any notification. See unblock_proxy()""" if not hasattr(self, "_v_proxies"): self.ensure_init() blocked_proxies = self._v_blocked_proxies if proxy not in blocked_proxies: blocked_proxies.append(proxy) def unblock_proxy(self, proxy): """Re-enable notifications to a proxy""" if not hasattr(self, "_v_proxies"): self.ensure_init() blocked_proxies = self._v_blocked_proxies if proxy in blocked_proxies: blocked_proxies.remove(proxy) def __setattr__(self, attr, value): """ A special setattr hook that notifies the registered proxies that the model has changed. Work around it setting attributes directly to self.__dict__. Note that setattr() assumes that the name of the attribute being changed and the proxy attribute are the same. If this is not the case (as may happen when using setters) you must call notify_proxies() manually from the subclass' setter. """ # XXX: this should be done last, since the proxy notification # may raise an exception. Or do we ignore this fact? self.__dict__[attr] = value if not hasattr(self, "_v_proxies"): self.ensure_init() if self._v_autonotify and self._v_proxies.has_key(attr): self.notify_proxies(attr) # # A sample model that pickles itself into a file # class PickledModel(Model): """ PickledModel is a model that is able to save itself into a pickle using save(). This has all the limitations of a pickle: its instance variables must be picklable, or pickle.dump() will raise exceptions. You can prefix variables with an underscore to make them non-persistent (and you can restore them accordingly by overriding __setstate__, but don't forget to call PickledModel.__setstate__) """ def __init__(self): self._filename = None def __getstate__(self): """Gets the state from the instance to be pickled""" odict = self.__dict__ for key in odict.keys(): if key.startswith("_"): del odict[key] return odict def __setstate__(self, dict): """Sets the state to the instance when being unpickled""" Model.__dict__["__init__"](self) self.__dict__.update(dict) def save(self, filename=None): """ Saves the instance to a pickle filename. If no filename argument is provided, will try to use the internal _filename attribute that is set using set_filename() @param filename: optional filename to pass in """ filename = filename or self._filename if not filename: raise AttributeError( "No pickle specified, don't know where to save myself") fh = open(filename, "w") try: try: pickle.dump(self, fh) except pickle.PicklingError, e: raise AttributeError( "Tried to pickle an instance variable that isn't " "supported by pickle.dump(). To work around this, you " "can prefix the variable name with an underscore " " and it will be ignored by the pickle machinery " "in PickledModel. The original error " "follows:\n\n%s" % e) finally: fh.close() def set_filename(self, filename): """ Sets the name of the file which will be used to pickle the model""" self._filename = filename #@unpickle def unpickle(cls, filename=None): """ Loads an instance from a pickle file; if it fails for some reason, create a new instance. - filename: the file from which the pickle should be loaded. If file is not provided, the name of the class suffixed by ".pickle" is used (i.e. "FooClass.pickle" for the class FooClass). If the pickle file is damaged, it will be saved with the extension ".err"; if a file with that name also exists, it will use ".err.1" and so on. This is to avoid the damaged file being clobbered by an instance calling save() unsuspectingly. """ if not filename: filename = cls.__name__ + ".pickle" if not os.path.exists(filename): ret = cls() ret.set_filename(filename) return ret fh = open(filename, "r") try: data = fh.read() ret = pickle.loads(data) except (EOFError, KeyError): # save backup of original pickle with an extension of # .err, .err.1, .err.2, etc. stem = filename + ".err" i = 0 backup = stem while os.path.exists(backup): i = i + 1 backup = stem + ".%d" % i open(backup, "w").write(data) log.warn( "pickle in %r was broken, saving backup in %r and creating " "new <%s> instance\n""" % (filename, backup, cls.__name__)) ret = cls() fh.close() ret.set_filename(filename) return ret unpickle = classmethod(unpickle) # TODO: implement a Model that saves itself as CSV/XML? PIDA-0.5.1/contrib/kiwi/kiwi/python.py0000644000175000017500000002141010652670747015603 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005,2006 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Johan Dahlin # """Generic python addons""" import sys import warnings __all__ = ['ClassInittableMetaType', 'ClassInittableObject'] class ClassInittableMetaType(type): # pylint fails to understand this is a metaclass def __init__(self, name, bases, namespace): type.__init__(self, name, bases, namespace) self.__class_init__(namespace) class ClassInittableObject(object): """ I am an object which will call a classmethod called __class_init__ when I am created. Subclasses of me will also have __class_init__ called. Note that __class_init__ is called when the class is created, eg when the file is imported at the first time. It's called after the class is created, but before it is put in the namespace of the module where it is defined. """ __metaclass__ = ClassInittableMetaType def __class_init__(cls, namespace): """ Called when the class is created @param cls: class @param namespace: namespace for newly created @type namespace: dict """ __class_init__ = classmethod(__class_init__) class _ForwardedProperty(object): def __init__(self, attribute): self._attribute = attribute def __get__(self, instance, klass): if instance is None: return self return getattr(instance.target, self._attribute) def __set__(self, instance, value): if instance is None: raise TypeError setattr(instance.target, self._attribute, value) class AttributeForwarder(ClassInittableObject): """ AttributeForwarder is an object which is used to forward certain attributes to another object. @cvar attributes: list of attributes to be forwarded @ivar target: forwarded object """ attributes = None def __class_init__(cls, ns): if cls.__bases__ == (ClassInittableObject,): return if not 'attributes' in ns: raise TypeError( "the class variable attributes needs to be set for %s" % ( cls.__name__)) if "target" in ns['attributes']: raise TypeError("'target' is a reserved attribute") for attribute in ns['attributes']: setattr(cls, attribute, _ForwardedProperty(attribute)) __class_init__ = classmethod(__class_init__) def __init__(self, target): """ @param target: object to forward attributes to """ self.target = target # copied from twisted/python/reflect.py def namedAny(name): """Get a fully named package, module, module-global object, or attribute. @param name: @returns: object, module or attribute """ names = name.split('.') topLevelPackage = None moduleNames = names[:] while not topLevelPackage: try: trialname = '.'.join(moduleNames) topLevelPackage = __import__(trialname) except ImportError: # if the ImportError happened in the module being imported, # this is a failure that should be handed to our caller. # count stack frames to tell the difference. import traceback exc_info = sys.exc_info() if len(traceback.extract_tb(exc_info[2])) > 1: try: # Clean up garbage left in sys.modules. del sys.modules[trialname] except KeyError: # Python 2.4 has fixed this. Yay! pass raise exc_info[0], exc_info[1], exc_info[2] moduleNames.pop() obj = topLevelPackage for n in names[1:]: obj = getattr(obj, n) return obj class Settable: """ A mixin class for syntactic sugar. Lets you assign attributes by calling with keyword arguments; for example, C{x(a=b,c=d,y=z)} is the same as C{x.a=b;x.c=d;x.y=z}. The most useful place for this is where you don't want to name a variable, but you do want to set some attributes; for example, C{X()(y=z,a=b)}. """ def __init__(self, **kw): self._attrs = kw.keys() self._attrs.sort() for k, v in kw.iteritems(): setattr(self, k, v) def getattributes(self): """ Fetches the attributes used to create this object @returns: a dictionary with attributes """ return self._attrs def __repr__(self): return '<%s %s>' % (self.__class__.__name__, ', '.join( ['%s=%r' % (attr, getattr(self, attr)) for attr in self._attrs])) def qual(klass): """ @returns: fully qualified module and class name """ return klass.__module__ + '.' + klass.__name__ def clamp(x, low, high): """ Ensures that x is between the limits set by low and high. For example, * clamp(5, 10, 15) is 10. * clamp(15, 5, 10) is 10. * clamp(20, 15, 25) is 20. @param x: the value to clamp. @param low: the minimum value allowed. @param high: the maximum value allowed. @returns: the clamped value """ return min(max(x, low), high) def slicerange(slice, limit): """Takes a slice object and returns a range iterator @param slice: slice object @param limit: maximum value allowed @returns: iterator """ return xrange(*slice.indices(limit)) _no_deprecation = False def deprecationwarn(msg, stacklevel=2): """ Prints a deprecation warning """ global _no_deprecation if _no_deprecation: return warnings.warn(msg, DeprecationWarning, stacklevel=stacklevel) def disabledeprecationcall(func, *args, **kwargs): """ Disables all deprecation warnings during the function call to func """ global _no_deprecation old = _no_deprecation _no_deprecation = True retval = func(*args, **kwargs) _no_deprecation = old return retval class enum(int): """ enum is an enumered type implementation in python. To use it, define an enum subclass like this: >>> from kiwi.python import enum >>> >>> class Status(enum): >>> OPEN, CLOSE = range(2) >>> Status.OPEN '' All the integers defined in the class are assumed to be enums and values cannot be duplicated """ __metaclass__ = ClassInittableMetaType #@classmethod def __class_init__(cls, ns): cls.names = {} # name -> enum cls.values = {} # value -> enum for key, value in ns.items(): if isinstance(value, int): cls(value, key) __class_init__ = classmethod(__class_init__) #@classmethod def get(cls, value): """ Lookup an enum by value @param value: the value """ if not value in cls.values: raise ValueError("There is no enum for value %d" % (value,)) return cls.values[value] get = classmethod(get) def __new__(cls, value, name): """ @param value: value of the enum @param name: name of the enum """ if name in cls.names: raise ValueError("There is already an enum called %s" % (name,)) if value in cls.values: raise ValueError( "Error while creating enum %s of type %s, " "it has already been created as %s" % ( value, cls.__name__, cls.values[value])) self = super(enum, cls).__new__(cls, value) self.name = name cls.values[value] = self cls.names[name] = self setattr(cls, name, self) return self def __str__(self): return '<%s value %s>' % ( self.__class__.__name__, self.name) __repr__ = __str__ def all(seq): """ @returns: True if all items in seq are True """ for item in seq: if not item: return False return True def any(seq): """ @returns: True if any item in seq is True """ for item in seq: if item: return True return False PIDA-0.5.1/contrib/kiwi/kiwi/tasklet.py0000644000175000017500000007760710652670747015754 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005 Gustavo J. A. M. Carneiro # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Gustavo J. A. M. Carneiro # """ Pseudo-thread (coroutines) framework Introduction ============ This module adds infrastructure for managing tasklets. In this context, a X{tasklet} is defined as a routine that explicitly gives back control to the main program a certain points in the code, while waiting for certain events. Other terms that may be used to describe tasklets include I{coroutines}, or I{cooperative threads}. The main advantages of tasklets are: - Eliminates the danger of unexpected race conditions or deadlocks that happen with preemptive (regular) threads; - Reduces the number of callbacks in your code, that sometimes are so many that you end up with I{spaghetti code}. The fundamental block used to create tasklets is Python's generators. Generators are objects that are defined as functions, and when called produce iterators that return values defined by the body of the function, specifically C{yield} statements. The neat thing about generators are not the iterators themselves but the fact that a function's state is completely frozen and restored between one call to the iterator's C{next()} and the following one. This allows the function to return control to a program's main loop while waiting for an event, such as IO on a socket, thus allowing other code to run in the mean time. When the specified event occurs, the function regains control and continues executing as if nothing had happened. Structure of a tasklet ====================== At the outset, a tasklet is simply a python U{generator function}, i.e. a function or method containing one or more C{yield} statements. Tasklets add a couple more requirements to regular generator functions: 1. The values contained in C{yield} statements cannot be arbitrary (see below); 2. After each C{yield} that indicates events, the function L{kiwi.tasklet.get_event} must be called to retrieve the event that just occurred. Syntax for yield in tasklets ============================ Inside tasklet functions, C{yield} statements are used to suspend execution of the tasklet while waiting for certain events. Valid C{yield} values are: - A single L{Message} object, with a correctly set I{dest} parameter. With this form, a message is sent to the indicated tasklet. When C{yield} returns, no event is generated, so the tasklet should B{not} call L{get_event}. - One, or a sequence of: - L{WaitCondition}, meaning to wait for that specific condition - L{Tasklet}, with the same meaning as L{WaitForTasklet}C{(tasklet)} - generator, with the same meaning as L{WaitForTasklet}C{(Tasklet(gen))} In this case, the tasklet is suspended until either one of the indicated events occurs. The tasklet must call L{get_event} in this case. Launching a tasklet =================== To start a tasklet, the L{Tasklet} constructor must be used:: from kiwi import tasklet def my_task(x): [...] tasklet.Tasklet(my_task(x=0)) Alternatively, L{kiwi.tasklet.run} can be used to the same effect:: from kiwi import tasklet tasklet.run(my_task(x=0)) Yet another approach is to use the @tasklet.task decorator:: from kiwi import tasklet @tasklet.task def my_task(x): [...] raise StopIteration("return value") yield my_task(x=0) retval = tasklet.get_event().retval Examples ======== Background timeout task ----------------------- This example demonstrates basic tasklet structure and timeout events:: import gobject from kiwi import tasklet mainloop = gobject.MainLoop() def simple_counter(numbers): timeout = tasklet.WaitForTimeout(1000) for x in xrange(numbers): print x yield timeout tasklet.get_event() mainloop.quit() tasklet.run(simple_counter(10)) mainloop.run() Message passing --------------- This example extends the previous one and demonstrates message passing:: import gobject from kiwi import tasklet mainloop = gobject.MainLoop() @tasklet.task def printer(): msgwait = tasklet.WaitForMessages(accept=("quit", "print")) while True: yield msgwait msg = tasklet.get_event() if msg.name == "quit": return assert msg.name == 'print' print ">>> ", msg.value @tasklet.task def simple_counter(numbers, task): timeout = tasklet.WaitForTimeout(1000) for x in xrange(numbers): yield tasklet.Message('print', dest=task, value=x) yield timeout tasklet.get_event() yield tasklet.Message('quit', dest=task) mainloop.quit() task = printer() simple_counter(10, task) mainloop.run() """ import types import warnings import gobject if gobject.pygtk_version <= (2, 8): raise RuntimeError("PyGTK 2.8 or later is required for kiwi.tasklet") _event = None class task(object): """A decorator that modifies a tasklet function to avoid the need to call C{tasklet.run(func())} or C{tasklet.Tasklet(func())}. """ def __init__(self, func): self._func = func self.__name__ = func.__name__ self.__doc__ = func.__doc__ def __call__(self, *args, **kwargs): return Tasklet(self._func(*args, **kwargs)) def get_event(): """ Return the last event that caused the current tasklet to regain control. @warning: this function should be called exactly once after each yield that includes a wait condition. """ global _event assert _event is not None event = _event _event = None return event def run(gen): """Start running a generator as a L{Tasklet}. @parameter gen: generator object that implements the tasklet body. @return: a new L{Tasklet} instance, already running. @note: this is strictly equivalent to calling C{Tasklet(gen)}. """ return Tasklet(gen) class WaitCondition(object): ''' Base class for all wait-able condition objects. WaitConditions are used in a yield statement inside tasklets body for specifying what event(s) it should wait for in order to receive control once more.''' def __init__(self): '''Abstract base class, do not call directly''' self.triggered = False def arm(self, tasklet): '''Prepare the wait condition to receive events. When a wait condition receives the event it is waiting for, it should call the method L{wait_condition_fired} of the tasklet with the wait condition as argument. The method returns True or False; if it returns True, it means the WaitCondition object must "rearm" itself (continue to monitor events), otherwise it should disarm. @parameter tasklet: the tasklet instance the wait condition is to be associated with. @attention: this method normally should not be called directly by the programmer. ''' raise NotImplementedError def disarm(self): '''Stop the wait condition from receiving events. @attention: this method normally should not be called by the programmer.''' raise NotImplementedError class WaitForCall(WaitCondition): '''An object that waits until it is called. This example demonstrates how a tasklet waits for a callback:: import gobject from kiwi import tasklet mainloop = gobject.MainLoop() def my_task(): callback = tasklet.WaitForCall() gobject.timeout_add(1000, callback) yield callback mainloop.quit() tasklet.run(my_task()) mainloop.run() @ivar return_value: value to return when called ''' def __init__(self, return_value=None): ''' Creates a wait condition that is actually a callable object, and waits for a call to be made on it. @param return_value: value to return when called; can also be modified dynamically from the tasklet as the C{return_value} instance variable. ''' WaitCondition.__init__(self) self.return_value = return_value self.args = None self.kwargs = None self._callback = None def arm(self, tasklet): '''Overrides WaitCondition.arm''' self._callback = tasklet.wait_condition_fired def disarm(self): '''Overrides WaitCondition.disarm''' self._callback = None def __call__(self, *args, **kwargs): self.triggered = True self.args = args self.kwargs = kwargs self._callback(self) self.triggered = False return self.return_value class WaitForIO(WaitCondition): '''An object that waits for IO conditions on sockets or file descriptors. ''' def __init__(self, filedes, condition=gobject.IO_IN, priority=gobject.PRIORITY_DEFAULT): ''' @param filedes: object to monitor for IO @type filedes: int file descriptor, or a gobject.IOChannel, or an object with a C{fileno()} method, such as socket or unix file. @param condition: IO event mask @type condition: a set of C{gobject.IO_*} flags ORed together @param priority: mainloop source priority ''' WaitCondition.__init__(self) self.filedes = filedes self._condition = condition # listen condition self.condition = None # last occurred condition self._callback = None self._id = None self._priority = priority def arm(self, tasklet): '''Overrides WaitCondition.arm''' self._callback = tasklet.wait_condition_fired if self._id is None: try: ## http://bugzilla.gnome.org/show_bug.cgi?id=139176 iochan = isinstance(self.filedes, gobject.IOChannel) except AttributeError: iochan = False if iochan: self._id = self.filedes.add_watch(self._condition, self._io_cb, priority=self._priority) else: if isinstance(self.filedes, int): filedes = self.filedes else: filedes = self.filedes.fileno() self._id = gobject.io_add_watch(filedes, self._condition, self._io_cb, priority=self._priority) def disarm(self): '''Overrides WaitCondition.disarm''' if self._id is not None: gobject.source_remove(self._id) self._id = None self._callback = None def _io_cb(self, unused_filedes, condition): self.triggered = True self.condition = condition retval = self._callback(self) self.triggered = False if not retval: self._id = None return retval class WaitForTimeout(WaitCondition): '''An object that waits for a specified ammount of time (a timeout)''' def __init__(self, timeout, priority=gobject.PRIORITY_DEFAULT): '''An object that waits for a specified ammount of time. @param timeout: ammount of time to wait, in miliseconds @param priority: mainloop priority for the timeout event ''' WaitCondition.__init__(self) self.timeout = timeout self._id = None self._tasklet = None self._priority = priority def arm(self, tasklet): '''See L{WaitCondition.arm}''' if self._id is None: self._tasklet = tasklet self._id = gobject.timeout_add(self.timeout, self._timeout_cb, priority=self._priority) def disarm(self): '''See L{WaitCondition.disarm}''' if self._id is not None: gobject.source_remove(self._id) self._id = None self._tasklet = None def restart(self): '''Restart the timeout. Makes time counting start again from zero.''' tasklet = self._tasklet self.disarm() self.arm(tasklet) def _timeout_cb(self): self.triggered = True retval = self._tasklet.wait_condition_fired(self) assert retval is not None self.triggered = False if not retval: self._id = None return retval class WaitForIdle(WaitCondition): '''An object that waits for the main loop to become idle''' def __init__(self, priority=gobject.PRIORITY_DEFAULT_IDLE): '''An object that waits for the main loop to become idle, with a priority indicated by @priority''' WaitCondition.__init__(self) self._callback = None self._id = None self._priority = priority def arm(self, tasklet): '''See L{WaitCondition.arm}''' if self._id is None: self._callback = tasklet.wait_condition_fired self._id = gobject.idle_add(self._idle_cb, self._priority) def disarm(self): '''See L{WaitCondition.disarm}''' if self._id is not None: gobject.source_remove(self._id) self._id = None self._callback = None def _idle_cb(self): self.triggered = True retval = self._callback(self) self.triggered = False if not retval: self._id = None return retval class WaitForTasklet(WaitCondition): '''An object that waits for a tasklet to complete''' def __init__(self, tasklet): '''An object that waits for another tasklet to complete''' WaitCondition.__init__(self) self._tasklet = tasklet self._id = None self._idle_id = None self._callback = None self.retval = None def arm(self, tasklet): '''See L{WaitCondition.arm}''' self._callback = tasklet.wait_condition_fired if self._id is None: self._id = self._tasklet.add_join_callback(self._join_cb) ## Check if the tasklet is already finished _right now_ if self._tasklet.state == Tasklet.STATE_ZOMBIE: self._join_cb(self._tasklet, self._tasklet.return_value) def disarm(self): '''See L{WaitCondition.disarm}''' if self._idle_id is not None: gobject.source_remove(self._idle_id) self._idle_id = None if self._id is not None: self._tasklet.remove_join_callback(self._id) self._id = None self._callback = None def _join_cb(self, tasklet, retval): assert tasklet is self._tasklet assert self._idle_id is None self._id = None self._idle_id = gobject.idle_add(self._idle_cb) self.retval = retval def _idle_cb(self): self.triggered = True self._callback(self) self.triggered = False self._tasklet = None self._callback = None self._id = None self._idle_id = None return False class WaitForSignal(WaitCondition): '''An object that waits for a signal emission''' def __init__(self, obj, signal): '''Waits for a signal to be emitted on a specific GObject instance or class. @param obj: object monitor for the signal @type obj: gobject.GObject @param signal: signal name @type signal: str ''' WaitCondition.__init__(self) if isinstance(obj, type): if not issubclass(obj, gobject.GObject): raise TypeError("obj must be a GObject instance or class") self.object = None self.class_ = obj else: if not isinstance(obj, gobject.GObject): raise TypeError("obj must be a GObject instance or class") self.object = obj self.class_ = None if not gobject.signal_lookup(signal, obj): raise ValueError("gobject %r does not have a signal called %r" % (obj, signal)) self.signal = signal self._callback = None self._id = None self._destroy_id = None self.signal_args = None def arm(self, tasklet): '''See L{WaitCondition.arm}''' if self._id is None: self._callback = tasklet.wait_condition_fired if self.class_ is not None: self._id = gobject.add_emission_hook(self.class_, self.signal, self._signal_cb) else: self._id = self.object.connect(self.signal, self._signal_cb) if gobject.signal_lookup("destroy", self.object): self._destroy_id = self.object.connect("destroy", self._object_destroyed) def _object_destroyed(self, dummy_obj): self.object = None self._id = None self._destroy_id = None self._callback = None def disarm(self): '''See WaitCondition.disarm''' if self._id is not None: if self.class_ is not None: gobject.remove_emission_hook(self.class_, self.signal, self._id) else: self.object.disconnect(self._id) self._id = None self._callback = None if self._destroy_id is not None: self.object.disconnect(self._destroy_id) self._destroy_id = None def _signal_cb(self, obj, *args): if __debug__: if self.class_ is not None: assert isinstance(obj, self.class_) else: assert obj is self.object self.triggered = True self.object = obj self.signal_args = args retval = self._callback(self) self.triggered = False if not retval: self._id = None return retval class WaitForProcess(WaitCondition): '''An object that waits for a process to end''' def __init__(self, pid): ''' Creates an object that waits for a subprocess @parameter pid: Process identifier @type pid: int ''' WaitCondition.__init__(self) self.pid = pid self._callback = None self._id = None self.status = None def arm(self, tasklet): '''See L{WaitCondition.arm}''' self._callback = tasklet.wait_condition_fired if self._id is None: self._id = gobject.child_watch_add(self.pid, self._child_cb) def disarm(self): '''See L{WaitCondition.disarm}''' if self._id is not None: gobject.source_remove(self._id) self._id = None self._callback = None def _child_cb(self, unused_pid, status): self.triggered = True self.status = status self._callback(self) self.triggered = False self.status = None self._id = None class Message(object): '''A message that can be received by or sent to a tasklet.''' _slots_ = 'name', 'dest', 'value', 'sender' ACCEPT, DEFER, DISCARD = range(3) def __init__(self, name, dest=None, value=None, sender=None): ''' @param name: name of message @type name: str @param dest: destination tasklet for this message @type dest: L{Tasklet} @param value: value associated with the message @param sender: sender tasklet for this message @type sender: L{Tasklet} ''' assert isinstance(sender, (Tasklet, type(None))) assert isinstance(dest, (Tasklet, type(None))) assert isinstance(name, basestring) self.name = name self.value = value self.sender = sender self.dest = dest # def get_name(self): # """Return the message name""" # return self.name # def get_value(self): # """Return the message value""" # return self.value # def get_sender(self): # """Return the message sender""" # return self.sender # def get_dest(self): # """Return the message destination""" # return self.dest def _normalize_list_argument(arg, name): """returns a list of strings from an argument that can be either list of strings, None (returns []), or a single string returns ([arg])""" if arg is None: return [] elif isinstance(arg, basestring): return [arg] elif isinstance(arg, (list, tuple)): return arg raise TypeError("Argument '%s' must be None, a string, or " "a sequence of strings, not %r" % (name, type(arg))) class WaitForMessages(WaitCondition): '''An object that waits for messages to arrive''' def __init__(self, accept=None, defer=None, discard=None): '''Creates an object that waits for a set of messages to arrive. @warning: unlike other wait conditions, when a message is received, a L{Message} instance is returned by L{get_event()}, not the L{WaitForMessages} instance. @param accept: message name or names to accept (receive) in the current state @type accept: string or sequence of string @param defer: message name or names to defer (queue) in the current state @type defer: string or sequence of string @param discard: message name or names to discard (drop) in the current state @type discard: string or sequence of string ''' WaitCondition.__init__(self) self._tasklet = None accept = _normalize_list_argument(accept, 'accept') defer = _normalize_list_argument(defer, 'defer') discard = _normalize_list_argument(discard, 'discard') self.actions = dict() for name in accept: self.actions[name] = Message.ACCEPT for name in defer: self.actions[name] = Message.DEFER for name in discard: self.actions[name] = Message.DISCARD def arm(self, tasklet): '''Overrides WaitCondition.arm''' self._tasklet = tasklet tasklet.message_actions.update(self.actions) def disarm(self): '''Overrides WaitCondition.disarm''' assert self._tasklet is not None for name in self.actions: del self._tasklet.message_actions[name] class Tasklet(object): '''An object that launches and manages a tasklet. @ivar state: current execution state of the tasklet, one of the STATE_* contants. @ivar return_value: the value returned by the task function, or None. @cvar STATE_RUNNING: the tasklet function is currently executing code @cvar STATE_SUSPENDED: the tasklet function is currently waiting for an event @cvar STATE_MSGSEND: the tasklet function is currently sending a message @cvar STATE_ZOMBIE: the tasklet function has ended ''' STATE_RUNNING, STATE_SUSPENDED, STATE_MSGSEND, STATE_ZOMBIE = range(4) def __init__(self, gen=None, start=True): ''' Launch a generator tasklet. @param gen: a generator object that implements the tasklet main body @param start: whether to automatically start running the tasklet in the constructor If `gen` is omitted or None, L{run} should be overridden in a subclass. ''' self._event = None self._join_callbacks = {} self.wait_list = [] self._message_queue = [] self._message_actions = {} self.state = Tasklet.STATE_SUSPENDED self.return_value = None if gen is None: self.gen = self.run() else: assert isinstance(gen, types.GeneratorType) self.gen = gen if start: self._next_round() # bootstrap def start(self): """Starts the execution of the task, for use with tasklets created with start=False""" assert self.state == Tasklet.STATE_SUSPENDED self._next_round() def get_message_actions(self): """Dictionary mapping message names to actions ('accept' or 'discard' or 'defer'). Should normally not be accessed directly by the programmer. """ return self._message_actions message_actions = property(get_message_actions) def run(self): """ Method that executes the task. Should be overridden in a subclass if no generator is passed into the constructor. @warning: do NOT call this method directly; it is meant to be called by the tasklet framework. """ raise NotImplementedError( "Should be overridden in a subclass " "if no generator is passed into the constructor") def _invoke(self): global _event assert _event is None had_event = (self._event is not None) _event = self._event self.state = Tasklet.STATE_RUNNING try: gen_value = self.gen.next() except StopIteration, ex: self.state = Tasklet.STATE_ZOMBIE if ex.args: retval, = ex.args else: retval = None self._join(retval) return None else: self.state = Tasklet.STATE_SUSPENDED assert gen_value is not None if __debug__: if had_event and _event is not None: warnings.warn("Tasklet %s forgot to read an event!" % self) self._event = None return gen_value def _next_round(self): assert self.state == Tasklet.STATE_SUSPENDED old_wait_list = self.wait_list while True: # loop while tasklet yields tasklet.post_message(...) gen_value = self._invoke() if gen_value is None: return if isinstance(gen_value, Message): msg = gen_value self.state = Tasklet.STATE_MSGSEND msg.sender = self msg.dest.send_message(msg) continue # loop because we posted a message elif isinstance(gen_value, tuple): self.wait_list = list(gen_value) elif isinstance(gen_value, list): self.wait_list = gen_value else: self.wait_list = [gen_value] for i, val in enumerate(self.wait_list): if isinstance(val, WaitCondition): continue elif isinstance(val, types.GeneratorType): self.wait_list[i] = WaitForTasklet(Tasklet(val)) elif isinstance(val, Tasklet): self.wait_list[i] = WaitForTasklet(val) else: raise TypeError("yielded values must be WaitConditions," " generators, or a single Message") self._update_wait_conditions(old_wait_list) msg = self._dispatch_message() if msg is not None: self._event = msg continue ## send a message break def _dispatch_message(self): '''get next message that a tasklet wants to receive; discard messages that should be discarded''' ## while sending out messages, the tasklet implicitly queues ## all incoming messages if self.state == Tasklet.STATE_MSGSEND: return None ## filter out messages with discard action def _get_action(msg): try: return self._message_actions[msg.name] except KeyError: warnings.warn("Implicitly discarding message %s" " directed to tasklet %s" % (msg, self)) return Message.DISCARD if __debug__: self._message_queue = [msg for msg in self._message_queue if _get_action(msg) != Message.DISCARD] else: ## slightly more efficient version of the above self._message_queue = [msg for msg in self._message_queue if (self._message_actions.get(msg.name, Message.DISCARD) != Message.DISCARD)] ## find next ACCEPT-able message from queue, and pop it out for idx, msg in enumerate(self._message_queue): if self._message_actions[msg.name] == Message.ACCEPT: return self._message_queue.pop(idx) return None def _update_wait_conditions(self, old_wait_list): '''disarm wait conditions removed and arm new wait conditions''' ## disarm conditions removed from the wait list for cond in old_wait_list: if cond not in self.wait_list: cond.disarm() ## arm the conditions added to the wait list for cond in self.wait_list: if cond not in old_wait_list: cond.arm(self) def wait_condition_fired(self, triggered_cond): """Method that should be called when a wait condition fires""" assert triggered_cond in self.wait_list assert self._event is None self._event = triggered_cond self._next_round() self._event = None if self.wait_list is None: return False else: return (triggered_cond in self.wait_list) def add_join_callback(self, callback, *extra_args): ''' Add a callable to be invoked when the tasklet finishes. Return a connection handle that can be used in remove_join_callback() The callback will be called like this:: callback(tasklet, retval, *extra_args) where tasklet is the tasklet that finished, and retval its return value (or None). When a join callback is invoked, it is automatically removed, so calling L{remove_join_callback} afterwards produces a KeyError exception. ''' handle = hash(callback) while handle in self._join_callbacks: # handle collisions handle += 1 self._join_callbacks[handle] = callback, extra_args return handle def remove_join_callback(self, handle): '''Remove a join callback previously added with L{add_join_callback}''' del self._join_callbacks[handle] def _join(self, retval): for cond in self.wait_list: cond.disarm() self.gen = None self.return_value = retval self.wait_list = [] callbacks = self._join_callbacks.values() self._join_callbacks.clear() for callback, args in callbacks: callback(self, retval, *args) def send_message(self, message): """Send a message to be received by the tasklet as an event. @warning: Don't call this from another tasklet, only from the main loop! To send a message from another tasklet, yield a L{Message} with a correctly set 'dest' parameter. """ assert isinstance(message, Message) assert self._event is None if message.dest is None: message.dest = self self._message_queue.append(message) self._event = self._dispatch_message() if self._event is not None: self._next_round() PIDA-0.5.1/contrib/kiwi/kiwi/utils.py0000644000175000017500000003120510652670747015425 0ustar aliali# # Kiwi: a Framework and Enhanced Widgets for Python # # Copyright (C) 2005-2007 Async Open Source # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # # Author(s): Lorenzo Gil Sanchez # Johan Dahlin # """GObject utilities and addons""" import os import struct import sys import gobject HAVE_2_6 = gobject.pygtk_version[:2] <= (2, 6) # When we can depend on 2.8 clean this up, so ClassInittable does not # need to be tied to GObjectMeta, since it doesn't need to be a GObject # Always use type for epydoc, since GObjectMeta creates lots of trouble # for us when using fake objects. if HAVE_2_6 or os.path.basename(sys.argv[0]) == 'epyrun': metabase = type else: metabase = gobject.GObjectMeta def list_properties(gtype, parent=True): """ Return a list of all properties for GType gtype, excluding properties in parent classes """ pspecs = gobject.list_properties(gtype) if parent: return pspecs parent = gobject.type_parent(gtype) parent_pspecs = gobject.list_properties(parent) return [pspec for pspec in pspecs if pspec not in parent_pspecs] def type_register(gtype): """Register the type, but only if it's not already registered @param gtype: the class to register """ # copied from gobjectmodule.c:_wrap_type_register if (getattr(gtype, '__gtype__', None) != getattr(gtype.__base__, '__gtype__', None)): return False gobject.type_register(gtype) return True class _GObjectClassInittableMetaType(metabase): def __init__(self, name, bases, namespace): metabase.__init__(self, name, bases, namespace) self.__class_init__(namespace) class _GobjectClassInittableObject(object): __metaclass__ = _GObjectClassInittableMetaType def __class_init__(cls, namespace): pass __class_init__ = classmethod(__class_init__) class PropertyMeta(_GObjectClassInittableMetaType): """ Metaclass that takes into account properties and signals of baseclasses, even if they're not GObject subclasses. Which allows you to put signals and properties in mixin classes. """ # pylint fails to understand this is a metaclass def __init__(self, name, bases, namespace): def _update_bases(bases, props, signals): for base in bases: props.update(getattr(base, '__gproperties__', {})) signals.update(getattr(base, '__gsignals__', {})) _update_bases(base.__bases__, props, signals) for base in bases: if issubclass(base, gobject.GObject): # This will be fun. # Merge in properties and signals from all bases, this # is not the default behavior of PyGTK, but we need it props = namespace.setdefault('__gproperties__', {}) signals = namespace.setdefault('__gsignals__', {}) _update_bases(bases, props, signals) break # Workaround brokenness in PyGObject meta/type registration props = namespace.get('__gproperties__', {}) signals = namespace.get('__gsignals__', {}) if hasattr(self, '__gtype__'): self.__gproperties__ = props self.__gsignals__ = signals gtype = self.__gtype__ # Delete signals and properties which are already # present in the list signal_names = gobject.signal_list_names(gtype) for signal in signals.copy(): if signal in signal_names : del signals[signal] prop_names = [prop.name for prop in gobject.list_properties(gtype)] for prop in props.copy(): if prop in prop_names: del props[prop] if HAVE_2_6 and issubclass(self, gobject.GObject): gobject.type_register(self) _GObjectClassInittableMetaType.__init__(self, name, bases, namespace) # The metaclass forgets to remove properties and signals self.__gproperties__ = {} self.__gsignals__ = {} def __call__(self, *args, **kwargs): rv = super(PropertyMeta, self).__call__(*args, **kwargs) rv.__post_init__() return rv class PropertyObject(object): """ I am an object which maps GObject properties to attributes To be able to use me, you must also inherit from a gobject.GObject subclass. Example: >>> from kiwi.utils import PropertyObject, gproperty >>> class Person(PropertyObject, gobject.GObject): >>> gproperty('name', str) >>> gproperty('age', int) >>> gproperty('married', bool, False) >>> test = Test() >>> test.age = 20 >>> test.age 20 >>> test.married False """ __metaclass__ = PropertyMeta _default_values = {} def __init__(self, **kwargs): self._attributes = {} if not isinstance(self, gobject.GObject): raise TypeError("%r must be a GObject subclass" % self) defaults = self._default_values.copy() for kwarg in kwargs: if not kwarg in defaults: raise TypeError("Unknown keyword argument: %s" % kwarg) defaults.update(kwargs) for name, value in defaults.items(): self._set(name, value) def __class_init__(cls, namespace): # Do not try to register gobject subclasses # If you try to instantiate an object you'll get a warning, # So it is safe to ignore here. if not issubclass(cls, gobject.GObject): return # Create python properties for gobject properties, store all # the values in self._attributes, so do_set/get_property # can access them. Using set property for attribute assignments # allows us to add hooks (notify::attribute) when they change. default_values = {} for pspec in list_properties(cls, parent=False): prop_name = pspec.name.replace('-', '_') p = property(lambda self, n=prop_name: self._attributes[n], lambda self, v, n=prop_name: self.set_property(n, v)) setattr(cls, prop_name, p) if hasattr(pspec, 'default_value'): default_values[prop_name] = pspec.default_value cls._default_values.update(default_values) __class_init__ = classmethod(__class_init__) def __post_init__(self): """ A hook which is called after the constructor is called. It's mainly here to workaround http://bugzilla.gnome.org/show_bug.cgi?id=425501 so you can set properties at construction time """ def _set(self, name, value): func = getattr(self, 'prop_set_%s' % name, None) if callable(func) and func: value = func(value) self._attributes[name] = value def _get(self, name): func = getattr(self, 'prop_get_%s' % name, None) if callable(func) and func: return func() return self._attributes[name] def get_attribute_names(self): return self._attributes.keys() def is_default_value(self, attr, value): return self._default_values[attr] == value def do_set_property(self, pspec, value): prop_name = pspec.name.replace('-', '_') self._set(prop_name, value) def do_get_property(self, pspec): prop_name = pspec.name.replace('-', '_') return self._get(prop_name) def gsignal(name, *args, **kwargs): """ Add a GObject signal to the current object. It current supports the following types: - str, int, float, long, object, enum @param name: name of the signal @type name: string @param args: types for signal parameters, if the first one is a string 'override', the signal will be overridden and must therefor exists in the parent GObject. @keyword flags: A combination of; - gobject.SIGNAL_RUN_FIRST - gobject.SIGNAL_RUN_LAST - gobject.SIGNAL_RUN_CLEANUP - gobject.SIGNAL_NO_RECURSE - gobject.SIGNAL_DETAILED - gobject.SIGNAL_ACTION - gobject.SIGNAL_NO_HOOKS @keyword retval: return value in signal callback """ frame = sys._getframe(1) try: locals = frame.f_locals finally: del frame dict = locals.setdefault('__gsignals__', {}) if args and args[0] == 'override': dict[name] = 'override' else: retval = kwargs.get('retval', None) if retval is None: default_flags = gobject.SIGNAL_RUN_FIRST else: default_flags = gobject.SIGNAL_RUN_LAST flags = kwargs.get('flags', default_flags) if retval is not None and flags != gobject.SIGNAL_RUN_LAST: raise TypeError( "You cannot use a return value without setting flags to " "gobject.SIGNAL_RUN_LAST") dict[name] = (flags, retval, args) def _max(c): # Python 2.3 does not like bitshifting here return 2 ** ((8 * struct.calcsize(c)) - 1) - 1 _MAX_VALUES = {int : _max('i'), float: float(2**1024 - 2**971), long : _max('l') } _DEFAULT_VALUES = {str : '', float : 0.0, int : 0, long : 0L} def gproperty(name, ptype, default=None, nick='', blurb='', flags=gobject.PARAM_READWRITE, **kwargs): """ Add a GObject property to the current object. @param name: name of property @type name: string @param ptype: type of property @type ptype: type @param default: default value @param nick: short description @param blurb: long description @param flags: parameter flags, a combination of: - PARAM_READABLE - PARAM_READWRITE - PARAM_WRITABLE - PARAM_CONSTRUCT - PARAM_CONSTRUCT_ONLY - PARAM_LAX_VALIDATION Optional, only for int, float, long types: @keyword minimum: minimum allowed value @keyword maximum: maximum allowed value """ # General type checking if default is None: default = _DEFAULT_VALUES.get(ptype) elif not isinstance(default, ptype): raise TypeError("default must be of type %s, not %r" % ( ptype, default)) if not isinstance(nick, str): raise TypeError('nick for property %s must be a string, not %r' % ( name, nick)) nick = nick or name if not isinstance(blurb, str): raise TypeError('blurb for property %s must be a string, not %r' % ( name, blurb)) # Specific type checking if ptype == int or ptype == float or ptype == long: default = (kwargs.get('minimum', ptype(0)), kwargs.get('maximum', _MAX_VALUES[ptype]), default) elif ptype == bool: if (default is not True and default is not False): raise TypeError("default must be True or False, not %r" % ( default)) default = default, elif gobject.type_is_a(ptype, gobject.GEnum): if default is None: raise TypeError("enum properties needs a default value") elif not isinstance(default, ptype): raise TypeError("enum value %s must be an instance of %r" % (default, ptype)) default = default, elif ptype == str: default = default, elif ptype == object: if default is not None: raise TypeError("object types does not have default values") default = () else: raise NotImplementedError("type %r" % ptype) if flags < 0 or flags > 32: raise TypeError("invalid flag value: %r" % (flags,)) frame = sys._getframe(1) try: locals = frame.f_locals dict = locals.setdefault('__gproperties__', {}) finally: del frame dict[name] = (ptype, nick, blurb) + default + (flags,) def quote(msg): """ Similar to urllib.quote but for glibs GMarkup @param msg: string to quote @returns: quoted string """ msg = msg.replace('&', '&') msg = msg.replace('<', '<') msg = msg.replace('>', '>') return msg PIDA-0.5.1/contrib/kiwi/pixmaps/0002755000175000017500000000000010652671501014417 5ustar alialiPIDA-0.5.1/contrib/kiwi/pixmaps/validation-error-16.png0000644000175000017500000000066410652670656020650 0ustar alialiPNG  IHDR;mGgAMAOX2tEXtSoftwareAdobe ImageReadyqe<FIDATxbd@쁔np @10bE&0ݸEPB@1``7 /e$p@@ihn@*`UG' l P, &*bA ywF qº>|dj ' @(0[m뵫ltӉc ?~3rv$hÇdOc`bec`c PrаD !(1൨(@1A3S9lZL 3_<)a}CC>H/@@wp-*/ @^hh0q֤ IENDB`PIDA-0.5.1/contrib/kiwi/po/0002755000175000017500000000000010652671501013354 5ustar alialiPIDA-0.5.1/contrib/kiwi/po/POTFILES.list0000644000175000017500000000003410652671067015500 0ustar alialirecursive-include kiwi *.py PIDA-0.5.1/contrib/kiwi/po/fr.po0000644000175000017500000001343410652671067014335 0ustar aliali# vim: encoding=utf8 # # French translation for the Kiwi project. # # Copyright (C) 2007 Johan Dahlin # This file is distributed under the same license as the kiwi package. # Benoit Myard , 2007. # msgid "" msgstr "" "Project-Id-Version: kiwi 1.9.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-16 10:49-0300\n" "PO-Revision-Date: 2007-04-03 12:41-0300\n" "Last-Translator: Benoit Myard \n" "Language-Team: French \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../kiwi/currency.py:163 msgid "Currency" msgstr "Devise" #: ../kiwi/currency.py:179 ../kiwi/currency.py:196 #, python-format msgid "%s can not be converted to a currency" msgstr "%s n'a pu être converti vers une devise" #: ../kiwi/datatypes.py:237 msgid "String" msgstr "Chaîne" #: ../kiwi/datatypes.py:251 msgid "Unicode" msgstr "Unicode" #: ../kiwi/datatypes.py:265 msgid "Integer" msgstr "Entier" #: ../kiwi/datatypes.py:293 #, python-format msgid "%s could not be converted to an integer" msgstr "%s n'a pu être converti en entier" #: ../kiwi/datatypes.py:299 msgid "Long" msgstr "Entier long" #: ../kiwi/datatypes.py:304 msgid "Boolean" msgstr "Booléen" #: ../kiwi/datatypes.py:319 #, python-format msgid "'%s' can not be converted to a boolean" msgstr "'%s' n'a pu être converti en un booléen" #: ../kiwi/datatypes.py:325 msgid "Float" msgstr "Nombre en virgule flottante" #: ../kiwi/datatypes.py:368 ../kiwi/datatypes.py:388 #, python-format msgid "This field requires a number, not %r" msgstr "Ce champ nécessite un nombre et non pas %r" #: ../kiwi/datatypes.py:377 msgid "Decimal" msgstr "Nombre décimal" #: ../kiwi/datatypes.py:417 ../kiwi/datatypes.py:422 msgid "mm" msgstr "" #: ../kiwi/datatypes.py:418 msgid "yy" msgstr "" #: ../kiwi/datatypes.py:419 msgid "dd" msgstr "" #: ../kiwi/datatypes.py:420 msgid "yyyy" msgstr "" #: ../kiwi/datatypes.py:421 msgid "hh" msgstr "" #: ../kiwi/datatypes.py:423 msgid "ss" msgstr "" #: ../kiwi/datatypes.py:424 msgid "hh:mm:ss" msgstr "" #. FIXME: locale specific #: ../kiwi/datatypes.py:426 msgid "hh:mm:ss LL" msgstr "" #: ../kiwi/datatypes.py:521 ../kiwi/datatypes.py:556 msgid "You cannot enter a year before 1900" msgstr "" #: ../kiwi/datatypes.py:550 #, python-format msgid "This field requires a date of the format \"%s\" and not \"%s\"" msgstr "Ce champ nécessite une date de la forme \"%s\" et non pas \"%s\"" #: ../kiwi/datatypes.py:562 msgid "Time" msgstr "Heure" #: ../kiwi/datatypes.py:577 msgid "Date and Time" msgstr "Date et Heure" #: ../kiwi/datatypes.py:592 msgid "Date" msgstr "Date" #: ../kiwi/datatypes.py:607 msgid "Object" msgstr "Objet" #: ../kiwi/datatypes.py:615 msgid "Enum" msgstr "" #: ../kiwi/datatypes.py:684 msgid "You have a thousand separator to the right of the decimal point" msgstr "Un séparateur des milliers se trouve dans la partie décimale" #: ../kiwi/datatypes.py:696 msgid "Inproperly placed thousands separator" msgstr "Séparateur des milliers mal placé" #: ../kiwi/datatypes.py:701 #, python-format msgid "Inproperly placed thousand separators: %r" msgstr "Séparateur des milliers mal placé : %r" #: ../kiwi/datatypes.py:719 #, python-format msgid "You have more than one decimal point (\"%s\") in your number \"%s\"" msgstr "Il y a plus d'un séparateur décimal (\"%s\") dans le nombre \"%s\"" #: ../kiwi/ui/listdialog.py:309 #, python-format msgid "Do you want to remove %s ?" msgstr "" #: ../kiwi/ui/dateentry.py:74 msgid "_Today" msgstr "_Aujourd'hui" #: ../kiwi/ui/dateentry.py:75 msgid "_Cancel" msgstr "_Annuler" #: ../kiwi/ui/dateentry.py:76 msgid "_Select" msgstr "_Séléctionner" #: ../kiwi/ui/wizard.py:210 msgid "Finish" msgstr "Terminer" #: ../kiwi/ui/entry.py:594 #, python-format msgid "'%s' is not a valid object" msgstr "'%s' n'est pas un objet valide" #: ../kiwi/ui/objectlist.py:1908 msgid "Total:" msgstr "Total :" #: ../kiwi/ui/dialogs.py:101 msgid "Show more _details" msgstr "Afficher plus de _détails" #: ../kiwi/ui/dialogs.py:247 msgid "Open" msgstr "Ouvrir" #: ../kiwi/ui/dialogs.py:276 #, python-format msgid "Could not open file \"%s\"" msgstr "Impossible d'ouvrir le fichier \"%s\"" #: ../kiwi/ui/dialogs.py:277 #, python-format msgid "The file \"%s\" could not be opened. Permission denied." msgstr "Impossible d'ouvrir le fichier \"%s\". Permission refusée." #: ../kiwi/ui/dialogs.py:284 #, python-format msgid "A file named \"%s\" already exists" msgstr "Un fichier nommé \"%s\" existe déjà" #: ../kiwi/ui/dialogs.py:285 msgid "Do you wish to replace it with the current one?" msgstr "Souhaitez-vous le remplacer par l'élément courant ?" #: ../kiwi/ui/dialogs.py:291 msgid "Replace" msgstr "Remplacer" #: ../kiwi/ui/dialogs.py:297 msgid "Save" msgstr "Enregistrer" #: ../kiwi/ui/dialogs.py:352 msgid "Password:" msgstr "Mot de passe :" #: ../kiwi/ui/proxywidget.py:65 #, python-format msgid "Could not load image: %s" msgstr "Impossible de charger l'image : %s" #: ../kiwi/ui/proxywidget.py:306 #, python-format msgid "'%s' is not a valid value for this field" msgstr "'%s' n'est pas une valeur autorisée pour ce champ" #: ../kiwi/ui/proxywidget.py:340 msgid "This field is mandatory" msgstr "Ce champ est obligatoire" #: ../kiwi/ui/search.py:65 msgid "Any" msgstr "" #: ../kiwi/ui/search.py:72 #, fuzzy msgid "Today" msgstr "_Aujourd'hui" #: ../kiwi/ui/search.py:80 msgid "Yesterday" msgstr "" #: ../kiwi/ui/search.py:88 msgid "Last week" msgstr "" #: ../kiwi/ui/search.py:96 msgid "Last month" msgstr "" #: ../kiwi/ui/search.py:195 msgid "From:" msgstr "" #: ../kiwi/ui/search.py:205 #, fuzzy msgid "To:" msgstr "Total :" #: ../kiwi/ui/search.py:216 msgid "Custom day" msgstr "" #: ../kiwi/ui/search.py:217 msgid "Custom interval" msgstr "" #: ../kiwi/ui/search.py:554 msgid "Search:" msgstr "" PIDA-0.5.1/contrib/kiwi/po/kiwi.pot0000644000175000017500000001137610652671067015060 0ustar aliali# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-16 10:49-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: ../kiwi/currency.py:163 msgid "Currency" msgstr "" #: ../kiwi/currency.py:179 ../kiwi/currency.py:196 #, python-format msgid "%s can not be converted to a currency" msgstr "" #: ../kiwi/datatypes.py:237 msgid "String" msgstr "" #: ../kiwi/datatypes.py:251 msgid "Unicode" msgstr "" #: ../kiwi/datatypes.py:265 msgid "Integer" msgstr "" #: ../kiwi/datatypes.py:293 #, python-format msgid "%s could not be converted to an integer" msgstr "" #: ../kiwi/datatypes.py:299 msgid "Long" msgstr "" #: ../kiwi/datatypes.py:304 msgid "Boolean" msgstr "" #: ../kiwi/datatypes.py:319 #, python-format msgid "'%s' can not be converted to a boolean" msgstr "" #: ../kiwi/datatypes.py:325 msgid "Float" msgstr "" #: ../kiwi/datatypes.py:368 ../kiwi/datatypes.py:388 #, python-format msgid "This field requires a number, not %r" msgstr "" #: ../kiwi/datatypes.py:377 msgid "Decimal" msgstr "" #: ../kiwi/datatypes.py:417 ../kiwi/datatypes.py:422 msgid "mm" msgstr "" #: ../kiwi/datatypes.py:418 msgid "yy" msgstr "" #: ../kiwi/datatypes.py:419 msgid "dd" msgstr "" #: ../kiwi/datatypes.py:420 msgid "yyyy" msgstr "" #: ../kiwi/datatypes.py:421 msgid "hh" msgstr "" #: ../kiwi/datatypes.py:423 msgid "ss" msgstr "" #: ../kiwi/datatypes.py:424 msgid "hh:mm:ss" msgstr "" #. FIXME: locale specific #: ../kiwi/datatypes.py:426 msgid "hh:mm:ss LL" msgstr "" #: ../kiwi/datatypes.py:521 ../kiwi/datatypes.py:556 msgid "You cannot enter a year before 1900" msgstr "" #: ../kiwi/datatypes.py:550 #, python-format msgid "This field requires a date of the format \"%s\" and not \"%s\"" msgstr "" #: ../kiwi/datatypes.py:562 msgid "Time" msgstr "" #: ../kiwi/datatypes.py:577 msgid "Date and Time" msgstr "" #: ../kiwi/datatypes.py:592 msgid "Date" msgstr "" #: ../kiwi/datatypes.py:607 msgid "Object" msgstr "" #: ../kiwi/datatypes.py:615 msgid "Enum" msgstr "" #: ../kiwi/datatypes.py:684 msgid "You have a thousand separator to the right of the decimal point" msgstr "" #: ../kiwi/datatypes.py:696 msgid "Inproperly placed thousands separator" msgstr "" #: ../kiwi/datatypes.py:701 #, python-format msgid "Inproperly placed thousand separators: %r" msgstr "" #: ../kiwi/datatypes.py:719 #, python-format msgid "You have more than one decimal point (\"%s\") in your number \"%s\"" msgstr "" #: ../kiwi/ui/listdialog.py:309 #, python-format msgid "Do you want to remove %s ?" msgstr "" #: ../kiwi/ui/dateentry.py:74 msgid "_Today" msgstr "" #: ../kiwi/ui/dateentry.py:75 msgid "_Cancel" msgstr "" #: ../kiwi/ui/dateentry.py:76 msgid "_Select" msgstr "" #: ../kiwi/ui/wizard.py:210 msgid "Finish" msgstr "" #: ../kiwi/ui/entry.py:594 #, python-format msgid "'%s' is not a valid object" msgstr "" #: ../kiwi/ui/objectlist.py:1908 msgid "Total:" msgstr "" #: ../kiwi/ui/dialogs.py:101 msgid "Show more _details" msgstr "" #: ../kiwi/ui/dialogs.py:247 msgid "Open" msgstr "" #: ../kiwi/ui/dialogs.py:276 #, python-format msgid "Could not open file \"%s\"" msgstr "" #: ../kiwi/ui/dialogs.py:277 #, python-format msgid "The file \"%s\" could not be opened. Permission denied." msgstr "" #: ../kiwi/ui/dialogs.py:284 #, python-format msgid "A file named \"%s\" already exists" msgstr "" #: ../kiwi/ui/dialogs.py:285 msgid "Do you wish to replace it with the current one?" msgstr "" #: ../kiwi/ui/dialogs.py:291 msgid "Replace" msgstr "" #: ../kiwi/ui/dialogs.py:297 msgid "Save" msgstr "" #: ../kiwi/ui/dialogs.py:352 msgid "Password:" msgstr "" #: ../kiwi/ui/proxywidget.py:65 #, python-format msgid "Could not load image: %s" msgstr "" #: ../kiwi/ui/proxywidget.py:306 #, python-format msgid "'%s' is not a valid value for this field" msgstr "" #: ../kiwi/ui/proxywidget.py:340 msgid "This field is mandatory" msgstr "" #: ../kiwi/ui/search.py:65 msgid "Any" msgstr "" #: ../kiwi/ui/search.py:72 msgid "Today" msgstr "" #: ../kiwi/ui/search.py:80 msgid "Yesterday" msgstr "" #: ../kiwi/ui/search.py:88 msgid "Last week" msgstr "" #: ../kiwi/ui/search.py:96 msgid "Last month" msgstr "" #: ../kiwi/ui/search.py:195 msgid "From:" msgstr "" #: ../kiwi/ui/search.py:205 msgid "To:" msgstr "" #: ../kiwi/ui/search.py:216 msgid "Custom day" msgstr "" #: ../kiwi/ui/search.py:217 msgid "Custom interval" msgstr "" #: ../kiwi/ui/search.py:554 msgid "Search:" msgstr "" PIDA-0.5.1/contrib/kiwi/po/pl.po0000644000175000017500000001370310652671067014340 0ustar aliali# Polish translation for Kiwi framework. # Copyright (C) 2007 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the kiwi package. # Jarek Zgoda , 2007. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-16 10:52-0300\n" "PO-Revision-Date: 2007-07-15 21:16+0100\n" "Last-Translator: Jarek Zgoda \n" "Language-Team: Polish\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../kiwi/currency.py:163 msgid "Currency" msgstr "Waluta" #: ../kiwi/currency.py:179 ../kiwi/currency.py:196 #, python-format msgid "%s can not be converted to a currency" msgstr "%s nie może zostać skonwertowane na zapis walutowy" #: ../kiwi/datatypes.py:237 msgid "String" msgstr "Ciąg znaków" #: ../kiwi/datatypes.py:251 msgid "Unicode" msgstr "Unikod" #: ../kiwi/datatypes.py:265 msgid "Integer" msgstr "Liczba całkowita" #: ../kiwi/datatypes.py:293 #, python-format msgid "%s could not be converted to an integer" msgstr "%s nie może zostać skonwertowane na liczbę całkowitą" #: ../kiwi/datatypes.py:299 msgid "Long" msgstr "Długa liczba całkowita" #: ../kiwi/datatypes.py:304 msgid "Boolean" msgstr "Logiczny" #: ../kiwi/datatypes.py:319 #, python-format msgid "'%s' can not be converted to a boolean" msgstr "'%s' nie może zostać skonwertowane do wartości logicznej" #: ../kiwi/datatypes.py:325 msgid "Float" msgstr "Zmiennoprzecinkowy" #: ../kiwi/datatypes.py:368 ../kiwi/datatypes.py:388 #, python-format msgid "This field requires a number, not %r" msgstr "To pole wymaga liczby, a nie %r" #: ../kiwi/datatypes.py:377 msgid "Decimal" msgstr "Dziesiętny" #: ../kiwi/datatypes.py:417 ../kiwi/datatypes.py:422 msgid "mm" msgstr "" #: ../kiwi/datatypes.py:418 msgid "yy" msgstr "" #: ../kiwi/datatypes.py:419 msgid "dd" msgstr "" #: ../kiwi/datatypes.py:420 msgid "yyyy" msgstr "" #: ../kiwi/datatypes.py:421 msgid "hh" msgstr "" #: ../kiwi/datatypes.py:423 msgid "ss" msgstr "" #: ../kiwi/datatypes.py:424 msgid "hh:mm:ss" msgstr "" #. FIXME: locale specific #: ../kiwi/datatypes.py:426 msgid "hh:mm:ss LL" msgstr "" #: ../kiwi/datatypes.py:521 ../kiwi/datatypes.py:556 msgid "You cannot enter a year before 1900" msgstr "Nie można wprowadzić roku przed 1900" #: ../kiwi/datatypes.py:550 #, python-format msgid "This field requires a date of the format \"%s\" and not \"%s\"" msgstr "To pole wymaga daty w formacie \"%s\" a nie \"%s\"" #: ../kiwi/datatypes.py:562 msgid "Time" msgstr "Czas" #: ../kiwi/datatypes.py:577 msgid "Date and Time" msgstr "Data i czas" #: ../kiwi/datatypes.py:592 msgid "Date" msgstr "Data" #: ../kiwi/datatypes.py:607 msgid "Object" msgstr "Obiekt" #: ../kiwi/datatypes.py:615 msgid "Enum" msgstr "Wyliczenie" #: ../kiwi/datatypes.py:684 msgid "You have a thousand separator to the right of the decimal point" msgstr "" "Separator tysięcy znajduje się po prawej stronie separatora dziesiętnego" #: ../kiwi/datatypes.py:696 msgid "Inproperly placed thousands separator" msgstr "Źle umieszczony separator tysiecy" #: ../kiwi/datatypes.py:701 #, python-format msgid "Inproperly placed thousand separators: %r" msgstr "Źle umieszczony separator tysięcy: %r" #: ../kiwi/datatypes.py:719 #, python-format msgid "You have more than one decimal point (\"%s\") in your number \"%s\"" msgstr "" "Występuje więcej niż jeden separator dziesiętny (\"%s\") w liczbie \"%s\"" #: ../kiwi/ui/listdialog.py:309 #, python-format msgid "Do you want to remove %s ?" msgstr "Czy chcesz usunąć %s?" #: ../kiwi/ui/dateentry.py:74 msgid "_Today" msgstr "_Dziś" #: ../kiwi/ui/dateentry.py:75 msgid "_Cancel" msgstr "_Anuluj" #: ../kiwi/ui/dateentry.py:76 msgid "_Select" msgstr "_Wybierz" #: ../kiwi/ui/wizard.py:210 msgid "Finish" msgstr "Zakończ" #: ../kiwi/ui/entry.py:594 #, python-format msgid "'%s' is not a valid object" msgstr "'%s' nie jest poprawnym obiektem" #: ../kiwi/ui/objectlist.py:1908 msgid "Total:" msgstr "Ogółem:" #: ../kiwi/ui/dialogs.py:101 msgid "Show more _details" msgstr "Pokaż więcej _szczegółów" #: ../kiwi/ui/dialogs.py:247 msgid "Open" msgstr "Otwórz" #: ../kiwi/ui/dialogs.py:276 #, python-format msgid "Could not open file \"%s\"" msgstr "Nie można otworzyć pliku \"%s\"" #: ../kiwi/ui/dialogs.py:277 #, python-format msgid "The file \"%s\" could not be opened. Permission denied." msgstr "Nie udało się otworzyć pliku \"%s\". Brak wystarczających uprawnień." #: ../kiwi/ui/dialogs.py:284 #, python-format msgid "A file named \"%s\" already exists" msgstr "Plik o nazwie \"%s\" już istnieje" #: ../kiwi/ui/dialogs.py:285 msgid "Do you wish to replace it with the current one?" msgstr "Czy chcesz go zastąpić bieżącym?" #: ../kiwi/ui/dialogs.py:291 msgid "Replace" msgstr "Zastąp" #: ../kiwi/ui/dialogs.py:297 msgid "Save" msgstr "Zapisz" #: ../kiwi/ui/dialogs.py:352 msgid "Password:" msgstr "Hasło:" #: ../kiwi/ui/proxywidget.py:65 #, python-format msgid "Could not load image: %s" msgstr "Nie udało się załadować obrazka: %s" #: ../kiwi/ui/proxywidget.py:306 #, python-format msgid "'%s' is not a valid value for this field" msgstr "'%s' nie jest poprawną wartością dla tego pola" #: ../kiwi/ui/proxywidget.py:340 msgid "This field is mandatory" msgstr "Wypełnienie tego pola jest wymagane" #: ../kiwi/ui/search.py:65 msgid "Any" msgstr "Dowolny" #: ../kiwi/ui/search.py:72 msgid "Today" msgstr "Dziś" #: ../kiwi/ui/search.py:80 msgid "Yesterday" msgstr "Wczoraj" #: ../kiwi/ui/search.py:88 msgid "Last week" msgstr "W zeszłym tygodniu" #: ../kiwi/ui/search.py:96 msgid "Last month" msgstr "W zeszłym miesiącu" #: ../kiwi/ui/search.py:195 msgid "From:" msgstr "Od:" #: ../kiwi/ui/search.py:205 msgid "To:" msgstr "Do:" #: ../kiwi/ui/search.py:216 msgid "Custom day" msgstr "Wybrany dzień" #: ../kiwi/ui/search.py:217 msgid "Custom interval" msgstr "Wybrany przedział" #: ../kiwi/ui/search.py:554 msgid "Search:" msgstr "Szukaj:" PIDA-0.5.1/contrib/kiwi/po/pt_BR.po0000644000175000017500000001364110652671067014734 0ustar aliali# Tradução português para Kiwi # Copyright (C) 2005 Johan Dahlin # This file is distributed under the same license as the kiwi package. # Johan Dahlin , 2005 # # msgid "" msgstr "" "Project-Id-Version: kiwi 1.9.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-16 10:49-0300\n" "PO-Revision-Date: 2007-07-20 17:57-0300\n" "Last-Translator: Ronaldo Maia \n" "Language-Team: Brazilian Portuguese \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../kiwi/currency.py:163 msgid "Currency" msgstr "Monetário" #: ../kiwi/currency.py:179 ../kiwi/currency.py:196 #, python-format msgid "%s can not be converted to a currency" msgstr "%s não pode ser convertido para valor monetário" #: ../kiwi/datatypes.py:237 msgid "String" msgstr "String" #: ../kiwi/datatypes.py:251 msgid "Unicode" msgstr "Unicode" #: ../kiwi/datatypes.py:265 msgid "Integer" msgstr "Inteiro" #: ../kiwi/datatypes.py:293 #, python-format msgid "%s could not be converted to an integer" msgstr "%s não pode ser convertido para inteiro" #: ../kiwi/datatypes.py:299 msgid "Long" msgstr "Long" #: ../kiwi/datatypes.py:304 msgid "Boolean" msgstr "Boleano" #: ../kiwi/datatypes.py:319 #, python-format msgid "'%s' can not be converted to a boolean" msgstr "%s não pode ser convertido para boleano" #: ../kiwi/datatypes.py:325 msgid "Float" msgstr "Flutuante" #: ../kiwi/datatypes.py:368 ../kiwi/datatypes.py:388 #, python-format msgid "This field requires a number, not %r" msgstr "Este campo exige um numero, não %r" #: ../kiwi/datatypes.py:377 msgid "Decimal" msgstr "Decimal" #: ../kiwi/datatypes.py:417 ../kiwi/datatypes.py:422 msgid "mm" msgstr "mm" #: ../kiwi/datatypes.py:418 msgid "yy" msgstr "aa" #: ../kiwi/datatypes.py:419 msgid "dd" msgstr "dd" #: ../kiwi/datatypes.py:420 msgid "yyyy" msgstr "aaaa" #: ../kiwi/datatypes.py:421 msgid "hh" msgstr "hh" #: ../kiwi/datatypes.py:423 msgid "ss" msgstr "ss" #: ../kiwi/datatypes.py:424 msgid "hh:mm:ss" msgstr "hh:mm:ss" #. FIXME: locale specific #: ../kiwi/datatypes.py:426 msgid "hh:mm:ss LL" msgstr "hh:mm:ss LL" #: ../kiwi/datatypes.py:521 ../kiwi/datatypes.py:556 msgid "You cannot enter a year before 1900" msgstr "Não pode inserir um ano antes do 1900" #: ../kiwi/datatypes.py:550 #, python-format msgid "This field requires a date of the format \"%s\" and not \"%s\"" msgstr "Este campo exige uma data no formato \"%s\", e não \"%s\"" #: ../kiwi/datatypes.py:562 msgid "Time" msgstr "Hora" #: ../kiwi/datatypes.py:577 msgid "Date and Time" msgstr "Data e Hora" #: ../kiwi/datatypes.py:592 msgid "Date" msgstr "Data" #: ../kiwi/datatypes.py:607 msgid "Object" msgstr "Objeto" #: ../kiwi/datatypes.py:615 msgid "Enum" msgstr "Enumeração" #: ../kiwi/datatypes.py:684 msgid "You have a thousand separator to the right of the decimal point" msgstr "O numero tem um separador de milhares à direita do ponto decimal" #: ../kiwi/datatypes.py:696 msgid "Inproperly placed thousands separator" msgstr "Separador de milhares colocado em posição incorreta" #: ../kiwi/datatypes.py:701 #, python-format msgid "Inproperly placed thousand separators: %r" msgstr "Separadores de milhares colocado em posições incorretas: %r" #: ../kiwi/datatypes.py:719 #, python-format msgid "You have more than one decimal point (\"%s\") in your number \"%s\"" msgstr "Você tem mais de um ponto decimal (\"%s\") em seu número \"%s\"" #: ../kiwi/ui/listdialog.py:309 #, python-format msgid "Do you want to remove %s ?" msgstr "Você deseja remover %s ?" #: ../kiwi/ui/dateentry.py:74 msgid "_Today" msgstr "_Hoje" #: ../kiwi/ui/dateentry.py:75 msgid "_Cancel" msgstr "_Cancelar" #: ../kiwi/ui/dateentry.py:76 msgid "_Select" msgstr "_Selecionar" #: ../kiwi/ui/wizard.py:210 msgid "Finish" msgstr "Terminar" #: ../kiwi/ui/entry.py:594 #, python-format msgid "'%s' is not a valid object" msgstr "'%s' não é um objeto válido" #: ../kiwi/ui/objectlist.py:1908 msgid "Total:" msgstr "Total:" #: ../kiwi/ui/dialogs.py:101 msgid "Show more _details" msgstr "Exibir mais _detalhes" #: ../kiwi/ui/dialogs.py:247 msgid "Open" msgstr "Abrir" #: ../kiwi/ui/dialogs.py:276 #, python-format msgid "Could not open file \"%s\"" msgstr "Não foi possivel abrir o arquivo \"%s\"" #: ../kiwi/ui/dialogs.py:277 #, python-format msgid "The file \"%s\" could not be opened. Permission denied." msgstr "O arquivo \"%s\" não pode ser aberto. Permissão negada." #: ../kiwi/ui/dialogs.py:284 #, python-format msgid "A file named \"%s\" already exists" msgstr "Já existe um arquivo com o nome \"%s\"" #: ../kiwi/ui/dialogs.py:285 msgid "Do you wish to replace it with the current one?" msgstr "Deseja substituí-lo pelo arquivo atual?" #: ../kiwi/ui/dialogs.py:291 msgid "Replace" msgstr "Substituir" #: ../kiwi/ui/dialogs.py:297 msgid "Save" msgstr "Salvar" #: ../kiwi/ui/dialogs.py:352 msgid "Password:" msgstr "Senha:" #: ../kiwi/ui/proxywidget.py:65 #, python-format msgid "Could not load image: %s" msgstr "Não foi possivel abrir o imagem \"%s\"" #: ../kiwi/ui/proxywidget.py:306 #, python-format msgid "'%s' is not a valid value for this field" msgstr "'%s' não é um valor válido para este campo" #: ../kiwi/ui/proxywidget.py:340 msgid "This field is mandatory" msgstr "Esse campo é obrigatorio" #: ../kiwi/ui/search.py:65 msgid "Any" msgstr "Qualquer" #: ../kiwi/ui/search.py:72 msgid "Today" msgstr "Hoje" #: ../kiwi/ui/search.py:80 msgid "Yesterday" msgstr "Ontem" #: ../kiwi/ui/search.py:88 msgid "Last week" msgstr "Semana passada" #: ../kiwi/ui/search.py:96 msgid "Last month" msgstr "Mês passado" #: ../kiwi/ui/search.py:195 msgid "From:" msgstr "Para" #: ../kiwi/ui/search.py:205 msgid "To:" msgstr "a:" #: ../kiwi/ui/search.py:216 msgid "Custom day" msgstr "Dia" #: ../kiwi/ui/search.py:217 msgid "Custom interval" msgstr "Intervalo" #: ../kiwi/ui/search.py:554 msgid "Search:" msgstr "Pesquisar:" #~ msgid "Pixbuf" #~ msgstr "Pixbuf" PIDA-0.5.1/contrib/kiwi/po/sv.po0000644000175000017500000001354310652671067014357 0ustar aliali# Swedish translation for Kiwi # Copyright (C) 2005 Johan Dahlin # This file is distributed under the same license as the kiwi package. # Johan Dahlin , 2005 # # msgid "" msgstr "" "Project-Id-Version: kiwi 1.9.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-16 10:49-0300\n" "PO-Revision-Date: 2007-07-16 10:51-0300\n" "Last-Translator: Johan Dahlin \n" "Language-Team: Swedish \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../kiwi/currency.py:163 msgid "Currency" msgstr "Valuta" #: ../kiwi/currency.py:179 ../kiwi/currency.py:196 #, python-format msgid "%s can not be converted to a currency" msgstr "%s kunde inte konverteras till en valuta" #: ../kiwi/datatypes.py:237 msgid "String" msgstr "Sträng" #: ../kiwi/datatypes.py:251 msgid "Unicode" msgstr "Unicode" #: ../kiwi/datatypes.py:265 msgid "Integer" msgstr "Heltal" #: ../kiwi/datatypes.py:293 #, python-format msgid "%s could not be converted to an integer" msgstr "%s kunde inte konverteras till ett heltal" #: ../kiwi/datatypes.py:299 msgid "Long" msgstr "Long" #: ../kiwi/datatypes.py:304 msgid "Boolean" msgstr "Boolean" #: ../kiwi/datatypes.py:319 #, python-format msgid "'%s' can not be converted to a boolean" msgstr "'%s' kunde inte konverteras till en boolean" #: ../kiwi/datatypes.py:325 msgid "Float" msgstr "Flyttal" #: ../kiwi/datatypes.py:368 ../kiwi/datatypes.py:388 #, python-format msgid "This field requires a number, not %r" msgstr "Detta fältet kräver ett nummer, inte %r" #: ../kiwi/datatypes.py:377 msgid "Decimal" msgstr "Decimal" #: ../kiwi/datatypes.py:417 ../kiwi/datatypes.py:422 msgid "mm" msgstr "mm" #: ../kiwi/datatypes.py:418 msgid "yy" msgstr "åå" #: ../kiwi/datatypes.py:419 msgid "dd" msgstr "dd" #: ../kiwi/datatypes.py:420 msgid "yyyy" msgstr "åååå" #: ../kiwi/datatypes.py:421 msgid "hh" msgstr "tt" #: ../kiwi/datatypes.py:423 msgid "ss" msgstr "ss" #: ../kiwi/datatypes.py:424 msgid "hh:mm:ss" msgstr "tt:mm:ss" #. FIXME: locale specific #: ../kiwi/datatypes.py:426 msgid "hh:mm:ss LL" msgstr "tt:mm:ss LL" #: ../kiwi/datatypes.py:521 ../kiwi/datatypes.py:556 msgid "You cannot enter a year before 1900" msgstr "Du kan inte ange ett år innan 1900" #: ../kiwi/datatypes.py:550 #, python-format msgid "This field requires a date of the format \"%s\" and not \"%s\"" msgstr "Detta fältet kräver ett datum med formatet \"%s\" och inte \"%s\"" #: ../kiwi/datatypes.py:562 msgid "Time" msgstr "Tid" #: ../kiwi/datatypes.py:577 msgid "Date and Time" msgstr "Datum och tid" #: ../kiwi/datatypes.py:592 msgid "Date" msgstr "Datum" #: ../kiwi/datatypes.py:607 msgid "Object" msgstr "Objekt" #: ../kiwi/datatypes.py:615 msgid "Enum" msgstr "Enumeration" #: ../kiwi/datatypes.py:684 msgid "You have a thousand separator to the right of the decimal point" msgstr "Du har en tusenseparator till höger om decimalkommat" #: ../kiwi/datatypes.py:696 msgid "Inproperly placed thousands separator" msgstr "Felaktigt placerad tusenseparator" #: ../kiwi/datatypes.py:701 #, python-format msgid "Inproperly placed thousand separators: %r" msgstr "Felaktigt placerade tusenseparatorer: %r" #: ../kiwi/datatypes.py:719 #, python-format msgid "You have more than one decimal point (\"%s\") in your number \"%s\"" msgstr "Du har mer en än decimalkomma (\"%s\") i ditt nummer \"%s\"" #: ../kiwi/ui/listdialog.py:309 #, python-format msgid "Do you want to remove %s ?" msgstr "Will do ta bort %s ?" #: ../kiwi/ui/dateentry.py:74 msgid "_Today" msgstr "_Idag" #: ../kiwi/ui/dateentry.py:75 msgid "_Cancel" msgstr "_Avbryt" #: ../kiwi/ui/dateentry.py:76 msgid "_Select" msgstr "_Välj" #: ../kiwi/ui/wizard.py:210 msgid "Finish" msgstr "Slutför" #: ../kiwi/ui/entry.py:594 #, python-format msgid "'%s' is not a valid object" msgstr "'%s' är inte ett giltligt objekt" #: ../kiwi/ui/objectlist.py:1908 msgid "Total:" msgstr "Total:" #: ../kiwi/ui/dialogs.py:101 msgid "Show more _details" msgstr "Visa mer _detaljer" #: ../kiwi/ui/dialogs.py:247 msgid "Open" msgstr "Öppna" #: ../kiwi/ui/dialogs.py:276 #, python-format msgid "Could not open file \"%s\"" msgstr "Kunde inte öppna filen \"%s\"" #: ../kiwi/ui/dialogs.py:277 #, python-format msgid "The file \"%s\" could not be opened. Permission denied." msgstr "Filen \"%s\" kunde inte öppnas. Rättigheter saknas." #: ../kiwi/ui/dialogs.py:284 #, python-format msgid "A file named \"%s\" already exists" msgstr "En fil kallad \"%s\" existerar redan" #: ../kiwi/ui/dialogs.py:285 msgid "Do you wish to replace it with the current one?" msgstr "Vill du verkligen ersätta den med den nuvarande?" #: ../kiwi/ui/dialogs.py:291 msgid "Replace" msgstr "Ersätt" #: ../kiwi/ui/dialogs.py:297 msgid "Save" msgstr "Spara" #: ../kiwi/ui/dialogs.py:352 msgid "Password:" msgstr "Lösenord:" #: ../kiwi/ui/proxywidget.py:65 #, python-format msgid "Could not load image: %s" msgstr "Kunde inte öppna bilden: %s" #: ../kiwi/ui/proxywidget.py:306 #, python-format msgid "'%s' is not a valid value for this field" msgstr "'%s' är inte ett giltligt värde för detta fältet" #: ../kiwi/ui/proxywidget.py:340 msgid "This field is mandatory" msgstr "Detta fältet är obligatoriskt" #: ../kiwi/ui/search.py:65 msgid "Any" msgstr "Alla" #: ../kiwi/ui/search.py:72 msgid "Today" msgstr "Idag" #: ../kiwi/ui/search.py:80 msgid "Yesterday" msgstr "Igår" #: ../kiwi/ui/search.py:88 msgid "Last week" msgstr "Förra veckan" #: ../kiwi/ui/search.py:96 msgid "Last month" msgstr "Förra månaden" #: ../kiwi/ui/search.py:195 msgid "From:" msgstr "Från:" #: ../kiwi/ui/search.py:205 msgid "To:" msgstr "Till:" #: ../kiwi/ui/search.py:216 msgid "Custom day" msgstr "Dag" #: ../kiwi/ui/search.py:217 msgid "Custom interval" msgstr "Intervall" #: ../kiwi/ui/search.py:554 msgid "Search:" msgstr "Sök:" #~ msgid "Pixbuf" #~ msgstr "Pixbuf" #~ msgid "Datetime" #~ msgstr "Datumtid" PIDA-0.5.1/contrib/kiwi/tools/0002755000175000017500000000000010652671501014076 5ustar alialiPIDA-0.5.1/contrib/kiwi/tools/glade_conversor.py0000755000175000017500000000264210652670672017641 0ustar aliali#!/usr/bin/env python import os import sys filters = [ ('', ''), ("Kiwi2+Widgets+CheckButton", "kiwi+ui+widgets+checkbutton"), ("Kiwi2+Widgets+ComboBox", "kiwi+ui+widgets+combobox"), ("Kiwi2+Widgets+Entry", "kiwi+ui+widgets+entry"), ("Kiwi2+Widgets+Label", "kiwi+ui+widgets+label"), ("Kiwi2+Widgets+List+List", "ObjectList"), ("Kiwi2+Widgets+RadioButton", "kiwi+ui+widgets+radiobutton"), ("Kiwi2+Widgets+SpinButton", "kiwi+ui+widgets+spinbutton"), ("Kiwi2+Widgets+TextView", "kiwi+ui+widgets+textview"), ("kiwi+ui+widgets+list+List", "ObjectList"), ("kiwi+ui+widgets+entry+Entry", "ProxyEntry"), ("kiwi+ui+widgets+radiobutton+RadioButton", "ProxyRadioButton"), ("kiwi+ui+widgets+combobox+ComboBox", "ProxyComboBox"), ("kiwi+ui+widgets+combobox+ComboBoxEntry", "ProxyComboBoxEntry"), ] def apply_filter((first, second), line): if not first in line: return line return line.replace(first, second) def main(args): if len(args) < 2: print 'Need a filename' return filename = args[1] tmp = filename + '.tmp' out = open(tmp, 'w') for line in open(filename).readlines(): for filter in filters: line = apply_filter(filter, line) out.write(line) os.unlink(filename) os.rename(tmp, filename) if __name__ == '__main__': sys.exit(main(sys.argv)) PIDA-0.5.1/contrib/kiwi/tools/pylint.sh0000755000175000017500000000176010652670672015766 0ustar aliali#!/bin/sh # # TODO - We want these but they require some work # # W0302 - Module too long # W0622 - Redefined built-in variable # W0222 - Signature differs from overriden method # TODO="W0302,W0621,W0622,W0222" # # Disabled - We don't like this ones, turn them off # # F0202 - Bug in pylint # F0203 - Bug in pylint (Unable to resolve gtk.XXXX) # E0201 - Access to undefined member - breaks gtk' # W0201 - Attribute 'loaded_uis' defined outside __init__ # W0223 - Method 'add' is abstract in class 'xxx' but is not overriden # W0232 - Class has no __init__ method # W0511 - FIXME/TODO/XXX # W0613 - Unused argument # W0704 - Except doesn't do anything # DISABLE="E0201,F0202,F0203,W0201,W0223,W0232,W0511,W0613,W0704" MSGS="$TODO,$DISABLE" DIRECTORY="kiwi" pylint \ --disable-all \ --include-ids=y \ --enable-variables=y \ --enable-exceptions=y \ --enable-miscellaneous=y \ --enable-format=y \ --enable-classes=y \ --disable-msg=$MSGS \ --reports=n \ --enable-metrics=n \ $DIRECTORY PIDA-0.5.1/contrib/kiwi/tools/showcoverage0000755000175000017500000000550310652670672016531 0ustar aliali#!/usr/bin/env python import glob import os import sys class Presentation: def __init__(self, name, lines, covered): self.name = name self.lines = lines self.covered = covered if self.covered == 0: self.percent = 0 else: self.percent = 100 * self.covered / float(self.lines) def show(self, maxlen=20): format = '%%-%ds %%3d %%%% (%%4d / %%4d)' % maxlen print format % (self.name, self.percent, self.covered, self.lines) class Coverage: def __init__(self): self.files = [] self.total_lines = 0 self.total_covered = 0 def _strip_filename(self, filename): filename = os.path.basename(filename) if filename.endswith('.cover'): filename = filename[:-6] return filename def add_file(self, file): self.files.append(file) def show_results(self): self.maxlen = max(map(lambda f: len(self._strip_filename(f)), self.files)) print 'Coverage report:' print '-' * (self.maxlen + 23) for file in self.files: self.show_one(file) print '-' * (self.maxlen + 23) p = Presentation('Total', self.total_lines, self.total_covered) p.show(self.maxlen) def show_one(self, filename): f = open(filename) lines = [line for line in f.readlines() if (':' in line or line.startswith('>>>>>>')) and not line.strip().startswith('#') and not line.endswith(':\n')] uncovered_lines = [line for line in lines if line.startswith('>>>>>>')] if not lines: return filename = self._strip_filename(filename) p = Presentation(filename, len(lines), len(lines) - len(uncovered_lines)) p.show(self.maxlen) self.total_lines += p.lines self.total_covered += p.covered def generate(): directory = os.path.split(sys.argv[0])[0] base = os.path.abspath(os.path.join(directory, '..')) pattern = os.path.join(base, '_trial_temp', '*', 'kiwi.*') files = glob.glob(pattern) if not files: os.chdir(base) print 'Generating coverage data, please wait.' os.system('trial --coverage coverage --text tests') files = glob.glob(pattern) return [filename for filename in files if not 'unittest' in filename] def main(args): c = Coverage() if len(args) >= 2: files = args[1:] else: files = generate() files.sort() for file in files: if '__init__' in file: continue c.add_file(file) c.show_results() if __name__ == '__main__': sys.exit(main(sys.argv)) PIDA-0.5.1/contrib/kiwi/AUTHORS0000644000175000017500000000201610652671067014012 0ustar alialiMaintainers: Christian Reis Johan Dahlin Contributors (Kiwi1): Andreas Kostyrka Christian Reis Guilherme Salgado Jon Nelson Karl Putland Marcelo Corbani Ricardo Froelich Contributors (Kiwi2): Aaron Spike Ali Afshar Gustavo Sverzut Barbieri Gustavo Carneiro Dave Cook Daniel Saran R. da Cunha Ronaldo Maia Evandro Vale Miquelito Patrick K O'Brien Gustavo Rahal Henrique Romano Lorenzo Gil Sanchez Sidnei da Silva Marco Antonio Porcho Souza PIDA-0.5.1/contrib/kiwi/COPYING0000644000175000017500000006347610652671067014016 0ustar aliali GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. GNU LESSER GENERAL PUBLIC LICENSE 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. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! PIDA-0.5.1/contrib/kiwi/ChangeLog0000644000175000017500000034426210652671067014530 0ustar aliali2007-07-25 Johan Dahlin * kiwi/ui/entry.py (KiwiEntry.prefill): Handle duplicate entries by adding (n) to the end of the name 2007-07-25 Johan Dahlin * kiwi/ui/objectlist.py (Column.as_string): Call format_func before checking the format and the datatype, it should override everything. 2007-07-24 Johan Dahlin * kiwi/ui/dialogs.py (selectfolder): New function based around gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER. Based on patch by Paul Eipper (#3490) * kiwi/ui/objectlist.py (Column): Add ellipsize and font-desc properties. Handle list without selection. Add a font-desc parameter to ListLabel/SummaryLabel 2007-07-19 Fabio Morbec reviewed by: Johan Dahlin * kiwi/ui/widgets/combo.py (ProxyComboBoxEntry.prefill): Changed clear_entry param to True 2007-05-21 Johan Dahlin * NEWS: * debian/changelog: * doc/howto.tex: * kiwi/__version__.py: Release 1.9.16 2007-07-16 Johan Dahlin * po/pl.po: Add polish translation (Jarek Zgoda) * kiwi/ui/entry.py: Sometimes the entry completion breaks, #2901, patch by Ali Afshar. 2007-07-13 Fabio Morbec reviewed by: Johan Dahlin * kiwi/ui/comboentry.py (ComboEntry._on_entry__scroll_event): If don't have data in the model just return 2007-07-02 Johan Dahlin * glade3-plugin/setup.py (glade3_exists): Check for gladeui-1.0 instead of libgladeui-1.0, fixes #3461 * kiwi/ui/objectlist.py (ObjectTree): Add a row-expanded signal. Based on patch by Ali Afshar, fixes #3460 2007-06-14 Ronaldo Maia reviewed by: Johan Dahlin * examples/validation/personalinformation.glade: * examples/validation/personalinformation.py: Add a currency widget * kiwi/ui/widgets/label.py: Set alignment according to the datatype. 2007-06-09 Gustavo J. A. M. Carneiro * tests/test_tasklet.py: Disable the emission hook test if pygobject doens't support it. * kiwi/tasklet.py: Misc. pylint fixes and code cleanup. * kiwi/tasklet.py (Tasklet.add_join_callback): Allow passing extra user arguments to the callback. (Tasklet.start): New start() method to bootstrap the tasklet. (Tasklet.__init__): Add parameter to allow constructing tasklets without starting them. * tests/test_tasklet.py (TestWaitForSignal.testEmissionHook): Tesk emission hooks. * kiwi/tasklet.py (WaitForSignal): Add support for signal emission hooks by allowing the programmer to pass a GObject class instead of instance. 2007-06-06 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/widgets/label.py: Allow string replacements in ProxyLabels 2007-05-31 Johan Dahlin * kiwi/ui/proxy.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/textview.py: * tests/test_proxy.py: Make the proxy call widget.update(ValueUnset) for all proxy widgets when resetting the model (eg, proxy.set_model(None)). 2007-05-31 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/widgets/entry.py: * tests/test_Entry.py: Fix for #3408, Entry reads valueunset always, when empty 2007-05-31 Johan Dahlin * kiwi/ui/dateentry.py (DateEntry.get_date): Catch ValidationError and ignore it. 2007-05-29 Johan Dahlin * kiwi/db/sqlobj.py: Add support for full text indexes under postgres. * kiwi/ui/gaxmlloader.py: * kiwi/ui/views.py: Add support for loading GAXML files 2007-05-28 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/currency.py: * kiwi/ui/proxywidget.py: Allow proxy widgets to pass paramenters to the datatype converter. * kiwi/ui/widgets/entry.py: Never show the currency symbol on currency entries. Fixes #3398 2007-05-25 Ali Afshar reviewed by: Johan Dahlin * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/gazpacholoader.py: Add gazpacho support for ObjectTree, fixes #3389 === 1.9.15 === 2007-05-22 Johan Dahlin * kiwi/environ.py (Library.__init__): Save the prefix 2007-05-21 Johan Dahlin * NEWS: * debian/changelog: * doc/howto.tex: * doc/release-checklist.txt: * kiwi/__version__.py: Release 1.9.15 2007-05-21 Johan Dahlin * MANIFEST.in: * doc/Makefile: * tools/epyrun: Use pydoctor instead of epydoc. * kiwi/python.py (AttributeForwarder): Add a new class, which can be used to forward attributes to another object. 2007-05-17 Johan Dahlin * kiwi/datatypes.py (_BaseDateTimeConverter.from_string): Disallow dates before 1900, since the strftime implementation on Linux does not allow it. Fixes #3363 2007-05-15 Johan Dahlin * kiwi/ui/search.py (SearchSlaveDelegate.set_summary_label): Add support for setting a summary label 2007-05-14 Johan Dahlin * kiwi/ui/search.py (SearchFilter): Inherit from a widget and add a filter-position child property. Add normal properties to the filter and the container. Add a common base class and remove a couple of methods from the interface * tests/test_ObjectList.py (ConstructorTest.testGObjectNew): Add a test * kiwi/ui/objectlist.py (ObjectList.__init__): Make sure gobject.new works Fixes #3343 2007-05-13 Johan Dahlin * kiwi/ui/objectlist.py (Column): Turn attributes into a GObject property. * kiwi/ui/search.py (ComboSearchFilter.__init__): Make values optional (SearchContainer.add_filter): Only add the executer columns if we have an executer set, don't require one. 2007-05-11 Johan Dahlin * kiwi/log.py (set_log_file): Return the file handlers stream, useful when the application want to use other means to write to the same file. 2007-05-09 Johan Dahlin * kiwi/ui/search.py: (SearchSlaveDelegate.disable_search_entry): Add a method to make it possible to disable the search entry. (DateSearchFilter.set_use_date_entries): New, to disable the user selectable date entries (DateSearchFilter.get_end_date, DateSearchFilter.get_start_date): New accessors (DateSearchFilter.select): Add a position argument (DateSearchFilter.clear_options): New (DateSearchFilter.add_option_fixed) (DateSearchFilter.add_option_fixed_interval): New, added methods to make it possible to add fixed dates/interval 2007-05-04 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: * tests/test_ObjectList.py: Objectlist does not set a radio column value to true if all values are false. Fixes #3339 2007-05-03 Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.insert): * tests/test_ObjectList.py (MethodTest.testInsert): Implement, add tests. 2007-05-03 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/entry.py: (Masks): It was possible to move the cursor outside the editable fields sometimes. Fixes #3333 * tests/test_masks.py: some tests for this case. 2007-04-30 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Add an expander property to the Column. Fixes #3336 2007-04-26 Johan Dahlin * examples/entrymask.py: Add a new example * kiwi/__init__.py: Add an explicit version check and give up if the version is too old. 2007-04-25 Johan Dahlin * examples/search.py: * kiwi/db/query.py: * kiwi/db/sqlobj.py: * kiwi/ui/search.py: Add auto_search mode, which is enabled by default. Add optional columns, callback parameters to add_filter, to make it easier to integrate with the query executer. Add a generic query callback to the SQLObject executer. Use new api in example. * kiwi/ui/search.py: (DateSearchFilter._update_dates): When switching to a custom day, always set both to today. When switching to a custom interval, always use today and tomorrow. 2007-04-25 Johan Dahlin * kiwi/ui/search.py (DateSearchFilter.select): Add and reorganize internals a bit to be able to select by option_type. 2007-04-25 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.grab_focus): Add, fixes #3326 2007-04-20 Johan Dahlin * examples/search.py: Add an example. * kiwi/ui/search.py (DateSearchFilter): Make sure that you cannot select a start date after the end date and an end date before the start date. Add some comments explaning what's going on since the code is not that trivial any longer. 2007-04-19 Johan Dahlin * README: Mention that SQLObject is an optional dependency * kiwi/db/__init__.py: * kiwi/db/query.py: * kiwi/db/sqlobj.py: * kiwi/enums.py: * kiwi/interfaces.py: * kiwi/ui/search.py: Add a SearchContainer plus database independent infrastructure to construct queries. Include a SQLObject plugin 2007-04-19 Johan Dahlin * kiwi/ui/widgets/combo.py (ProxyComboBoxEntry.insert_item) (ProxyComboBox.insert_item, _EasyComboBoxHelper.insert_item): Implement insert_item. 2007-04-18 Johan Dahlin * kiwi/ui/dateentry.py (DateEntry.set_date): Allow None which means reset the date (DateEntry.get_date): Never return ValueUnset. * kiwi/ui/objectlist.py (ObjectList.sort_by_attribute): Add. Fixes #3311, based on patch by Ali Afshar. 2007-04-16 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Do not set a pixbuf if the data value is None. Fixes #3310 * kiwi/ui/objectlist.py: Set the sort columnm id so the columns end up sorted. Fixes #3032 2007-04-10 Johan Dahlin * kiwi/ui/proxywidget.py (ProxyWidgetMixin._as_string): Provide a default (string) converter if none is set. 2007-04-08 Johan Dahlin * kiwi/ui/widgets/entry.py (ProxyEntry.__post_init__): Remove dirty idle add hack and use __post_init__ instead. * kiwi/utils.py (PropertyObject.__post_init__): Add a hook which is called after the object construction is finished. 2007-04-03 Johan Dahlin * kiwi/environ.py (Environment.add_resource): Do not append the same resource path twice 2007-04-02 Johan Dahlin * tests/test_Entry.py: Add a test * kiwi/ui/widgets/entry.py (ProxyEntry.__init__): Add a workaround for http://bugzilla.gnome.org/show_bug.cgi?id=425501, fixes #3259 2007-03-28 Johan Dahlin * kiwi/environ.py (Library.__init__): Only add root/lib/pythonX.Y/site-packages to sys.path if it exists. === 1.9.14 === 2007-03-23 Johan Dahlin * Makefile: * NEWS: * debian/changelog: * doc/howto.tex: * kiwi/__version__.py: Release 1.9.14 2007-03-23 Johan Dahlin * gazpacho-plugin/kiwiwidgets.xml: Re-enable the ObjectList 2006-03-23 Johan Dahlin * glade3-plugin/kiwiwidgets.py: * glade3-plugin/kiwiwidgets.xml: * glade3-plugin/setup.py: Add Glade-3 plugin based on patch by Ali, fixes #3239 * kiwi/dist.py: Do not require a name parameter to be passed in. 2007-03-22 Johan Dahlin * kiwi/controllers.py (BaseController.on_key_press): gdk.keyval_name returns None if there is no name for the key, avoid raising an exception in that case, just silently ignore it. 2007-03-20 Johan Dahlin * kiwi/ui/objectlist.py: Set the __gtype_name__ on Column and ObjectTree 2007-03-19 Johan Dahlin * kiwi/ui/objectlist.py (Column): Mention the supported data types in the doc string. 2007-03-05 Johan Dahlin * kiwi/ui/listdialog.py (ListContainer): Add a return value to edit-item, inverse bool for remove-item. * examples/listdialog.py: New example * kiwi/ui/listdialog.py (ListDialog, ListContainer): New dialog to implement * kiwi/enums.py (ListType): new enum * kiwi/utils.py (quote): new function 2007-03-01 Johan Dahlin * kiwi/ui/comboentry.py (ComboEntry.set_active_iter): If object is None do not set the text of the entry 2007-02-28 Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.__setitem__): Update the cache when replacing an object (ObjectList.__contains__): Use iters cache for consitency with index() and speed. 2007-02-22 Johan Dahlin * setup.py (ext_modules): Make sure that the pkgconfig file for pygtk-2.0 is installed before checking which version we have. 2007-02-21 Johan Dahlin Change the extensions of all ui tests to .doctest. * tests/ui/diary2.doctest: Add two assertion checks. * tests/test_ui.py (test_filename): Use subprocess instead of popen2 * kiwi/ui/test/runner.py (Runner._iterate): Add support for checking the return value of a function. 2007-02-15 Johan Dahlin * kiwi/ui/gazpacholoader.py (DataTypeAdaptor.update): Set some sensible default data-types for the widgets * kiwi/ui/gazpacholoader.py (ModelProperty._on_widget__notify): Track the name as the default model-attribute * kiwi/ui/proxy.py (Proxy._setup_widget): Do not require model-attribute to be explicitly set. Use the widget name if it's not set. 2007-02-14 Johan Dahlin * kiwi/component.py: Make providedBy check in subclasses, makes it possible to use personalinformation.py without depending on zope.interface. 2007-02-09 Johan Dahlin * kiwi/ui/dateentry.py (_DateEntryPopup.popup): Filter out ValueUnset. 2007-02-08 Johan Dahlin * kiwi/ui/objectlist.py (ObjectList._treeview_search_equal_func): Make it case-insensitive by default. 2007-02-07 Johan Dahlin * kiwi/ui/icon.py (IconEntry._recompute): Use alternative methods to trigger a recompute(), fixes ComboEntry issue since ::change was emitted when it shouldn't be needed. 2007-02-06 Johan Dahlin * kiwi/ui/widgets/entry.py (ProxyEntry.read): * tests/test_Entry.py (EntryTest.testRead): Return ValueUnset when the entry is empty instead of an empty string. 2007-02-04 Johan Dahlin * kiwi.spec (Requires): * Makefile: * MANIFEST.in: Add rpm spec file. === 1.9.13 === 2007-02-01 Johan Dahlin * NEWS: * debian/changelog: * doc/howto.tex: * kiwi/__version__.py: Release 1.9.13 2007-02-01 Johan Dahlin * setup.py (pkgs): Use pkg-config instead of importing gtk which requires a display. * MANIFEST.in: Always include _kiwi.c 2007-01-31 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/icon.py: Fix workaround for recompute and call it after showing an icon on a widget. === 1.9.12 === 2007-01-29 Johan Dahlin * Makefile: * NEWS: * debian/changelog: * doc/howto.tex: * doc/release-checklist.txt: * kiwi/__version__.py: * po/fr.po: * po/kiwi.pot: * po/pt_BR.po: * po/sv.po: 1.9.12 2007-01-19 Johan Dahlin * kiwi/ui/comboentry.py (_ComboEntryPopup.set_selected_iter): Try setting the full model if everything else fails. Based on patch by Goedson Teixeira Paixao Fixes #3099. 2006-11-11 Marco Antonio Porcho Souza reviewed by: Johan Dahlin * kiwi/datatypes.py (_BaseDateTimeConverter._convert_format): Use YYYY/mm/dd instead %Y/%m/%d in error messages. Fixes #3041. 2007-01-16 Johan Dahlin * po/fr.po: Added French translation contributed by Benoit Myard. 2006-12-06 Johan Dahlin * kiwi/log.py (ReversedGlobalFilter.filter): Use >= instead of > * tests/test_log.py (LogTest.testStdErr): Add tests warnings printed on the console and to a log file. Fixes #2984 2006-12-05 Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.set_visible_rows): Impl. 2006-11-29 Johan Dahlin * kiwi/dist.py (listpackages): Provide better error messages. 2006-11-11 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/hyperlink.py: Make sure the hyperlink stays within it's own allocation. Fixes #2923 2006-11-02 Johan Dahlin * kiwi/ui/objectlist.py (ObjectTree._append_internal): Allow specify parents which are objects inserted in the tree. (ObjectTree.collapse, ObjectTree.expand): New methods, suggested by the incredible Ali (ObjectTree): kill ObjectRow, operate on instances inserted into the tree instead, to simplify the API 2006-10-29 Johan Dahlin * kiwi/datatypes.py: * kiwi/ui/objectlist.py: * examples/list/enumcombo.py: * tests/test_datatypes.py: Add support an enum data type and tests. Add support for displaying an enum as a combo cell renderer in the object list and example 2006-10-17 Gustavo J. A. M. Carneiro * kiwi/tasklet.py: * tests/test_tasklet.py: When arming a WaitForTasklet check if the state is zombie and join immediately. Add tests 2006-10-10 Johan Dahlin * kiwi/i18n/i18n.py (compile_po_files): Unlink the old POTFILES.in in case of an error. Check the result code of the pot generation, it may fail if the encoding is inconsistent. === 1.9.11 === 2006-10-09 Johan Dahlin * NEWS: * doc/howto.tex: * kiwi/__version__.py: Update * kiwi/ui/views.py (SlaveView._check_reserved): Raise ValueError instead of AttributeError, fixes #1998 (Sidnei da Silva) 2006-10-09 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Add right-click and emit double-click only when a row is selected, fixes #2817 2006-10-05 Ronaldo Maia reviewed by: Johan Dahlin * examples/validation/personalinformation.glade: Add an example. * kiwi/enums.py: * kiwi/ui/comboboxentry.py: Use KiwiEntry instead of ProxyEntry * kiwi/ui/entry.py: * kiwi/ui/widgets/entry.py: * tests/test_BaseView.py: Fix destruction of windows. * tests/test_Entry.py: * tests/test_masks.py: Refactor mask handling in KiwiEntry. Now it supports the use of fields. Fixes #2838 2006-10-04 Johan Dahlin * kiwi/component.py: * tests/test_component.py: Add remove_utility and an extra argument to get_utility which is similar to dict.get. Add tests too. 2006-09-29 Johan Dahlin * kiwi/ui/entry.py (KiwiEntry.__init__): Make ProxyEntries work with PyGTK 2.10 again. 2006-09-27 Johan Dahlin * kiwi/ui/objectlist.py (Column): Document properties (ObjectList): Document signals and properties 2006-09-25 Johan Dahlin * kiwi/environ.py (Library.enable_translation) (Library.set_application_domain): Call locale.textdomain/ locale.bindtextdomain aswell so libglade and other applications using libcs translation functions are going to work === 1.9.10 === 2006-09-15 Johan Dahlin * Makefile: add to simplify release process * setup.py: install documentation 2006-09-12 Johan Dahlin * kiwi/python.py (enum): Add a new data type * tests/test_python.py (EnumTest): and a test 2006-09-09 Gustavo J. A. M. Carneiro * tests/test_tasklet.py: Add WaitForCall test. * kiwi/tasklet.py (WaitForCall): Add a new WaitForCall wait condition, for waiting for a generic callback. * tests/test_tasklet.py: Add a few more tests. * kiwi/tasklet.py (Tasklet): Better handling of the StopIteration exception from tasklets. Add a new STATE_ZOMBIE state to indicate when a tasklet is finished. Add a new return_value instance variable to hold the (surprise!) return value. Document the public instance variables and STATE_* constants. (Tasklet._next_round): When a tasklet sends a message, set the 'sender' field of the message to the sending tasklet. 2006-09-11 Johan Dahlin * kiwi/ui/views.py (_open_glade): Sniff the content of the file and use libglade/gazpacho when appropriate. Fall back to gazpacho since dtd has not always been present 2006-09-08 Johan Dahlin * kiwi/ui/delegates.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/libgladeloader.py: * kiwi/ui/views.py: Kill gladename in views, delegates and glade adaptors 2006-09-05 Johan Dahlin * kiwi/dist.py (KiwiInstallLib.generate_template): Use a raw string here to allow the use of UNC paths on win32, pointed out by Aaron Spike. 2006-08-30 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/entry.py: Improve focus selection support for mask in entries. Fixes #2787 2006-08-30 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/currency.py: * kiwi/datatypes.py: * kiwi/enums.py: * kiwi/ui/objectlist.py: * kiwi/ui/widgets/entry.py: Alignment support for datatypes. Fixes #2786 2006-08-29 Johan Dahlin * kiwi/enums.py: * kiwi/interfaces.py: * kiwi/ui/comboentry.py: * kiwi/ui/combomixin.py: * kiwi/ui/test/recorder.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/combobox.py: * tests/test_comboentry.py: Get rid of ComboMixin, use composition instead. Add an IEasyCombo interface reflecting the current state of the adhook interface. Move the combo enums into kiwi.enums. 2006-08-25 Johan Dahlin * kiwi/dist.py (KiwiInstallData.run_install): Take into account --root to the distutils install command if it's sent in. === 1.9.9 === 2006-08-23 Johan Dahlin * NEWS: Update * kiwi/__version__.py: Bump 2006-08-23 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/entry.py(set_mask): Check if mask is None before converting it to unicode. * kiwi/datatypes.py: * kiwi/ui/widgets/entry.py: Datatype converters should provide a get_mask method. Fixes #2758 2006-08-22 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/gazpacholoader.py: Reload the list of datatypes on every update of the datatype editor, so new datatypes may appear. 2006-08-21 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/currency.py: * kiwi/datatypes.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/proxywidget.py: * kiwi/ui/widgets/button.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/filechooser.py: * kiwi/ui/widgets/fontbutton.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/textview.py: * tests/test_datatypes.py: Refactor datatypes and gazpacholoader: Each widgets hold the allowed datatypes for itself. Add name attribute for the converters. 2006-08-18 Johan Dahlin * kiwi/currency.py: Refactor current to a separate file * kiwi/datatypes.py (ConverterRegistry): add a remove method and make the add method a bit more robust * tests/test_datatypes.py: Add tests * po/kiwi.pot: Merge in new translations * po/sv.po: Update swedish translation * po/pt_BR.po: Update brazilian portuguese translation 2006-08-10 Johan Dahlin * kiwi/ui/__init__.py: * kiwi/__init__.py: Move pygtk import to kiwi/__init__.py so it's called before gobject is tried to be imported. 2006-08-02 Aaron Spike reviewed by: Johan Dahlin * AUTHORS: * kiwi/datatypes.py: * kiwi/ui/widgets/entry.py: * tests/test_datatypes.py: Add proper entry mask support on win32, fixes #2721. 2006-08-02 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/gadgets.py: * kiwi/ui/widgets/entry.py: DateEntry breaks when set invalid, fixes #2720. 2006-07-25 Johan Dahlin reviewed by: Henrique Romano * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/gazpacholoader.py: * kiwi/ui/widgets/button.py: * tests/test_proxy.py: Add ProxyButton support, Fixes #2698 2006-07-21 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/datatypes.py: Add a converter for gdk.Pixmap. Fixes #2697 * tests/test_datatypes.py: and some tests. 2006-07-17 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/proxywidget.py (validate): validate widget when force is True, even if not visible nor sensitive. Fixes #2682 2005-03-29 Ali Afshar reviewed by: Johan Dahlin (ValidatableProxyWidgetMixin.set_invalid): Set the background to red and set the tooltips if the fade is disabled, fixes #2685 2006-07-13 Johan Dahlin * kiwi/ui/proxywidget.py: * kiwi/ui/comboboxentry.py: * kiwi/ui/comboentry.py: * kiwi/ui/entry.py: * kiwi/ui/gadgets.py: * kiwi/ui/icon.py: * kiwi/ui/proxywidget.py: * kiwi/ui/widgets/spinbutton.py: Fetch the right background color instead of hard coding it to white. Add a get_background() method to ValidatableProxyWidgetMixin. Fixes #2681 (Gustavo Barbieri) 2006-07-03 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/datatypes.py: Decimal does not allow leading/trailing spaces. 2006-06-19 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/ui/widgets/combo.py: ProxyComboEntry extend ComboMixin. Fixes #2656 2006-06-19 Ronaldo Maia reviewed by: Johan Dahlin * kiwi/datatypes.py: Changes the way localeconv is retrived, so it gets checked and fixed if necessary, before using it. * tests/test_datatypes.py: Add some pickling tests. Fixes #2654 2006-06-12 Johan Dahlin * kiwi/ui/objectlist.py: Add use-markup property on column * examples/list/tree.py: Add a new example * kiwi/accessor.py: Fix a bug when specifying a default value * kiwi/ui/objectlist.py: Add ObjectTree 2006-06-12 Ronaldo Maia reviewed by: Johan Dahlin * examples/framework/color/completion.py: * kiwi/ui/comboentry.py: * kiwi/ui/entry.py: * kiwi/ui/entrycompletion.py: * kiwi/ui/widgets/entry.py: Add KiwiEntryCompletion, that suports custom popup window and treeview, and make ComboEntry use this completion. 2006-06-05 Johan Dahlin * tests/test_datatypes.py: Import Decimal from datatypes so the tests will pass on 2.3 * kiwi/datatypes.py: Make decimal support conditional 2006-06-01 Johan Dahlin * kiwi/ui/proxy.py: * kiwi/ui/proxywidget.py: * kiwi/ui/views.py: Treat invisible and insensitive widgets specially, eg, do not validate them and do not update the value in the model. 2006-05-31 Johan Dahlin * tests/test_datatypes.py (CurrencyTest.testFormatBR): Add a test and clean up locale handling in other tests. * kiwi/datatypes.py (filter_locale): Add a monetary parameter, which takes into account the monetary data. * kiwi/datatypes.py (currency.__new__): Use filter_locale with monetary=True here, fixes #2632 2006-05-24 David Cook reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Optimize refresh(), fixes #2612 2006-05-24 David Cook reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Add a sorted keyword, fixes #2611 2006-05-24 David Cook reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: Remove unnecessary on_column__clicked handler, fixes #2610 2006-05-04 Johan Dahlin * kiwi/dist.py: Add support for templates 2006-05-04 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/objectlist.py: display locale specific information for float, decimal and currency datatypes. Fixes #2562 2006-05-03 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/dateentry.py: * kiwi/ui/proxywidget.py: * kiwi/ui/widgets/entry.py: Add validation for DateEntry widget 2006-04-28 Johan Dahlin * kiwi/ui/test/main.py (excepthook): Add an exception hook * tests/runuitests.py: * tests/test_ui.py: Integrate ui tests into trial, remove old runuitests script. 2006-04-27 Johan Dahlin * kiwi/ui/test/player.py: * tests/runuitest.py: * tests/ui/diary.py: * tests/ui/diary2.py: Add a couple of ui tests 2006-04-27 Johan Dahlin * kiwi/ui/test/main.py: * kiwi/ui/test/player.py: * kiwi/ui/test/recorder.py: Improve logging, fix a bug in finish() 2006-04-27 Johan Dahlin * kiwi/ui/widgets/filechooser.py (ProxyFileChooserButton.__init__): Immitate PyGTK implementation argument wise * kiwi/ui/test/common.py: * kiwi/ui/test/listener.py: * kiwi/ui/test/main.py: Add optional support for emission hook. * README (Kiwi): Mention that PyGObject 2.10.0 is recommended for the UI test 2006-04-27 Johan Dahlin * kiwi/ui/widgets/entry.py (ProxyEntry.do_changed): Merge the the do_changed methods. * kiwi/environ.py (Environment.find_resource): Make sure it's a file 2006-04-26 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/dateentry.py: Fix DateEntry calendar popup, so it hides correctly. Fixes #2545 2006-04-26 Johan Dahlin * kiwi/dist.py (_VariableExtender.__init__): When prefix is not set, use sys.prefix instead. 2006-04-25 Ronaldo Maia reviewed by: jdahlin * tests/test_ObjectList.py: Add tests for list of RadioButtons 2006-04-25 Johan Dahlin * kiwi/ui/entry.py (KiwiEntry.prefill): Fix exceptions, fixes #2535 (Patrik O'Brien) === 1.9.8 === 2006-04-25 Johan Dahlin * NEWS: Update 2006-04-25 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/objectlist.py (Column.as_string): Use a slightly different logic to convert to a string, fixes boolean radio columns 2006-04-25 Johan Dahlin * kiwi/environ.py: Add support for .gz and .bz2 compressed glade files, supported by recent gazpacho versions. Also check in the current directory when everything else fails. Based on patch by Sidnei da Silva, fixes #2409 2006-04-24 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/objectlist.py: fixing when data is None in as_string * tests/test_ObjectList.py: a test case when data is None 2006-04-24 Johan Dahlin * kiwi/ui/test/main.py: * kiwi/ui/test/player.py: Run the script in a separate thread and the application in the main thread. Makes it easier to integrate the testsuite in applications that can't be run in threads. 2006-04-19 Ronaldo Maia reviewed by: jdahlin * examples/framework/sizegroup/shell.glade: * examples/framework/sizegroup/slave_view.glade: * examples/framework/sizegroup/slave_view2.glade: * examples/framework/sizegroup/slaves.py: Sizegroup merging example * kiwi/ui/views.py: When attaching a slave, merge the sizegroups with the same name 2006-04-19 Johan Dahlin * kiwi/ui/objectlist.py (_ContextMenu._on_menuitem__activate): Call row_changed for all rows after changing the visibility a column. This will make the treeview remeasure nicely. 2006-04-18 Johan Dahlin * kiwi/ui/widgets/radiobutton.py (ProxyRadioButton.__init__): Set the group after calling the parents constructor, otherwise we'll get :toggled emitted before the object is initialized properly. 2006-04-12 Patrick K O'Brien reviewed by: Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.__init__): Add row-activated signal and make the old double-click signal only be emitted when a double-click on the list. Fixes #2526 2006-04-11 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/objectlist.py: always use a converter to set the contents of a column in a ObjectList. Fixes #2523 2006-04-10 Johan Dahlin * kiwi/ui/combomixin.py (ComboMixin.prefill) (ComboMixin.append_item): Check for basestring instead of str so Unicode can be used, thanks to Alceste Scalas 2006-04-07 Patrick K O'Brien reviewed by: Johan Dahlin * kiwi/ui/objectlist.py: only respond to the toggled event when a boolean column is set to editable. Fixes #2525 2006-04-06 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/entry.py: * kiwi/ui/icon.py: * kiwi/ui/proxywidget.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/spinbutton.py: Refactor tooltips to fix bug #1954 2006-04-06 Ronaldo Maia reviewed by: jdahlin * examples/validation/personalinformation.py: * kiwi/ui/entry.py: * kiwi/ui/widgets/entry.py: * tests/test_Entry.py: Changed mask characters and some clean-ups. Fixes #2519 2006-04-04 Johan Dahlin * kiwi/ui/entry.py: Clean up the handling of the exact_completion property. * kiwi/python.py: Get rid of GObject stuff in base classes, move it to kiwi utils * kiwi/utils.py: inline class initiable meta here 2006-03-30 Johan Dahlin * kiwi/ui/dateentry.py (DateEntry.__init__): Only set the mask if it's not empty * kiwi/ui/widgets/entry.py (ProxyEntry.set_mask_for_data_type): Instead of skipping, try to do an educated guess of the mask. 2006-03-30 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/proxywidget.py: update the validation error tooltip every time the value changes (for tooltips that containg the invalid value) * po/kiwi.pot: * po/pt_BR.po: new translations * po/sv.po: 2005-03-29 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/widgets/colorbutton.py: * kiwi/ui/widgets/fontbutton.py: Add proxy widgets for gtk.ColorButton and gtk.FontButton. Fixes #2511 2006-03-29 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/gazpacholoader.py: * po/kiwi.pot: * po/pt_BR.po: * po/sv.po: pt_BR translation 2006-03-29 Ronaldo Maia reviewed by: jdahlin * kiwi/ui/widgets/spinbutton.py: No need to press enter to notify that the value of the spinbutton changed. * tests/test_proxy.py: Fixing some tests and adding new ones. 2006-03-28 Ronaldo Maia reviewed by: jdahlin * examples/framework/diary/diary2.py: When the list is empty, pass None to proxy.set_model * examples/framework/news/news4.py: Fixing methods, so they work properly with both callbacks and keyactions. 2006-03-26 Johan Dahlin * kiwi/desktopparser.py: Add a GKeyFile inspired .desktop parser 2006-03-24 Johan Dahlin * kiwi/ui/comboentry.py: * kiwi/ui/entry.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/entry.py: In ProxyComboEntry, only listen to content-changed on the ProxyEntry. In ProxyEntry, work around GtkEntry::changed which sometimes is emitted twice when set_text is called. In KiwiEntry.get_selected_by_iter, return None if the text from the model doesn't match the text 2006-03-23 Johan Dahlin * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/gazpacholoader.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/filechooser.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: * tests/test_CheckButton.py: * tests/test_Label.py: * tests/test_SpinButton.py: * tests/test_utils.py: Rename all widgets to Proxy..., update gazpacho and tests. 2006-03-23 Ronaldo Maia,,, reviewed by: jdahlin: * kiwi/ui/proxy.py: allow None to be passed to proxy.set_model * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/radiobutton.py: Allow None to be passed to update 2006-03-23 Johan Dahlin * kiwi/datatypes.py: Move setlocale() call to __init__.py * kiwi/environ.py (Library.enable_translation): Refactor a little bit try to locate the .mo file and give a warning if it cannot be found. 2006-03-22 Johan Dahlin * kiwi/dist.py (KiwiInstallLib._write_dictionary): Replace / with os.sep on win32, fixes installation. * kiwi/ui/entry.py: * kiwi/ui/widgets/entry.py: * tests/test_Entry.py: Move stuff from ProxyEntry to KiwiEntry, use a kiwi entry in the ComboEntry. 2006-03-22 Johan Dahlin * kiwi/ui/proxy.py: Rename from kiwi.proxies * kiwi/acccessor.py: Rename from accessors * kiwi/model.py: Rename from model * kiwi/proxies.py: Reorganize, Deprecate new_model, Rename model to _model and add a property. * kiwi/interfaces.py: * kiwi/proxywidget.py: Document/reorganize signals 2006-03-21 Johan Dahlin * kiwi/proxywidget.py: * kiwi/ui/proxywidget.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/filechooser.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: Rename mixins and move out of kiwi/ui/widgets/ 2006-03-21 Johan Dahlin * kiwi/interfaces.py: * kiwi/proxies.py: * kiwi/ui/views.py: * kiwi/ui/widgets/proxy.py: Rename interfaces and start to document 2006-03-21 Johan Dahlin * gazpacho-plugin/kiwiwidgets.xml: * tools/glade_conversor.py (filters): Update to reflect renames. * kiwi/ui/widgets/radiobutton.py (RadioButton.__init__): Rename RadioButton to ProxyRadioButton. Add gtk.RadioButton compatible arguments to ProxyRadioButton * kiwi/ui/widgets/entry.py: Rename Entry to ProxyEntry * kiwi/ui/gazpacholoader.py (GazpachoWidgetTree.__init__): Print deprecation warnings inside a glade file nicely * kiwi/python.py (deprecationwarn, disabledeprecationcall): New functions, to manage deprecation warnings 2005-12-14 Ali Afshar reviewed by: Johan Dahlin * kiwi/ui/widgets/filechooser.py: Add widget support for filechooserbutton and filechooserdialog. 2006-03-21 Johan Dahlin * kiwi/dist.py (setup): Add a distutils setup() replacement to simplify installation of kiwi packages. * kiwi/__init__.py: * kiwi/environ.py: * kiwi/log.py: Simplify localization for uninstalled applications. Use proper logging in environment 2006-03-20 Ronaldo Maia reviewed by: jdahlin * kiwi/datatypes.py: * tests/test_datatypes.py: Output a string in a locale dependent way, even if a format is not provided Fixes #2486 2006-03-20 Johan Dahlin * kiwi/component.py: * kiwi/interfaces.py: * kiwi/proxies.py: * kiwi/ui/views.py: * kiwi/ui/widgets/proxy.py: * tests/test_component.py: Add implements() and providedBy() in a (almost) zope.interface compatible way. Plenty of tests. Fixes #2501 2006-03-20 Johan Dahlin * kiwi/decorators.py (deprecated.wrapper): Add an optional log argument * kiwi/log.py (_log_levels): Add a global log object 2006-03-17 Johan Dahlin * kiwi/ui/comboentry.py (ComboEntry._on_entry_completion__match_selected): When entry completion is used, update the iter in the treeview aswell. 2006-03-13 Johan Dahlin * kiwi/ui/comboentry.py (_ComboEntryPopup.popup): Set the height on the treeview instead of the popup window. === 1.9.7 === 2006-03-11 Johan Dahlin * NEWS: Update 2006-03-07 Johan Dahlin * kiwi/ui/dateentry.py: * kiwi/ui/entry.py: * kiwi/ui/widgets/entry.py: Make sure the width of the dateentry is not longer than it needs to be, don't expand at all. 2006-03-06 Johan Dahlin * gazpacho-plugin/kiwiwidgets.py: * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/gazpacholoader.py: * kiwi/ui/hyperlink.py: Add gazpacho support for hyperlink. 2006-03-06 Johan Dahlin * kiwi/component.py: (get_utility): Add limited support for zope.interfaces. 2006-03-05 Johan Dahlin * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/dateentry.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/widgets/entry.py: * tests/test_dateentry.py: Add a DateEntry which has a toggle button that popups a calendar. 2006-03-05 Johan Dahlin * setup.py (version): Use the new features * kiwi/dist.py: Add support for $sysconfdir and add an install_data subclass which replaces variables. Fetch name from distribution object instead of subclass. Refactoring and cleanups 2006-03-02 Johan Dahlin * kiwi/datatypes.py (_IntConverter.as_string): Do not use a locale specific way of formatting integers. * tests/test_datatypes.py (IntTest.testAsStringUS) (IntTest.testAsStringUS): Add a test 2006-03-01 Johan Dahlin * kiwi/ui/objectlist.py (ObjectList.remove): Add an optional select argument to be consistent with append. * kiwi/ui/widgets/radiobutton.py (RadioButton._on_group_changed): Emit content changed when any radio button in the group changes 2006-02-24 Johan Dahlin * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/entry.py: * tests/test_comboentry.py: Add support for comboentry.select_item_by_data. Add tests and remove ComboMixin interface from ProxyComboEntry. Rename _create_completion to _get_completion. Reorganize methods in comboentry to match the 2006-02-24 Johan Dahlin * kiwi/datatypes.py (_BaseDateTimeConverter.get_format): Remove seconds and AM/PM for now, since we don't have enough infrastructure to support optional AM/PM. (filter_locale): Refactor to a function and replace decimal point with a . * tests/test_datatypes.py (CurrencyTest.testFormatBR): Add tests for decimal points which are not . 2006-02-23 Johan Dahlin * kiwi/ui/icon.py (IconEntry.deconstruct): Add support for drawing the icon on the left side and when xalign is higher than 0.5, draw the icon on the left side. * kiwi/datatypes.py (_BaseDateTimeConverter.from_string): * kiwi/ui/widgets/entry.py (DATE_MASK_TABLE): Add support for datetime.time * kiwi/ui/comboentry.py (ComboEntry.prefill): Use entry.prefill * kiwi/ui/widgets/entry.py (Entry.prefill): New method (Entry.set_completion_strings): Deprecate and use prefill internally 2006-02-23 Johan Dahlin * kiwi/ui/widgets/entry.py: Split out mask and icon to a separate entry which doesn't depend on the framework. Make get_empty_mask() public. 2006-02-22 Johan Dahlin * examples/framework/diary/diary.glade: * examples/framework/diary/diary.py: * kiwi/proxies.py: * kiwi/ui/delegates.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/libgladeloader.py: * kiwi/ui/views.py: Add ProxyDelegate and example. Rename proxy.new_model to proxy.set_model. Add sizegroup merging support (disabled). Add show_and_loop method on the view. Remove a few mutable default arguments. 2006-02-21 Johan Dahlin * kiwi/ui/widgets/entry.py (Entry.set_text): Set the cursor after the inserted text (Entry.prop_set_data_type): Right align entries which contain numbers by default * kiwi/ui/objectlist.py: Add Column.editable_attribute which is a row (object) specific way of saying if an attribute is editable or not. 2006-02-18 Patrick K O'Brien reviewed by: Johan Dahlin * kiwi/dist.py (TemplateInstallLib.install): win32 install fix Fixes bug 2446 === 1.9.6 === 2006-02-17 Johan Dahlin * NEWS: 1.9.6 * kiwi/ui/widgets/entry.py: (Entry._set_mask_for_data_type, Entry.prop_set_data_type): For datetime objects, set a default mask (Entry._insert_mask): Split into two (Entry.read): If the input is just the empty mask return an empty string. * tests/test_Entry.py: Add paste/cut support. Refactor cleanup. Add a couple of basic tests. 2006-02-16 Johan Dahlin * examples/validation/personalinformation.py: * kiwi/ui/widgets/entry.py: Add basic mask support to entry, still needs a few improvements. Also add a simple example. 2006-02-15 Johan Dahlin * kiwi/ui/comboentry.py: * kiwi/utils.py: * tests/test_gazpacholoader.py: * tests/test_utils.py: 2.6 compatibility added 2006-02-15 Johan Dahlin * kiwi/utils.py (PropertyMeta._update_bases): Make sure subclassing of a combobox works * tests/test_utils.py (Subclassing.testCombo): Add a test for this * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/comboboxentry.py: * kiwi/ui/comboentry.py: * kiwi/ui/combomixin.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/widgets/combo.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/list.py: * tools/glade_conversor.py: Refactor combos, beginning of gazpacho/proxy support for comboentry. 2006-02-14 Johan Dahlin * examples/framework/news/news2.py: * examples/framework/news/news3.py: * examples/framework/news/news4.py: * examples/list/editable.py: * examples/list/kitchensink.py: * examples/list/radio.py: * examples/list/searchbar.py: * examples/list/simple.py: * examples/list/sortable.py: * examples/list/stockicon.py: * gazpacho-plugin/kiwiwidgets.xml: * kiwi/ui/gazpacholoader.py: * kiwi/ui/objectlist.py: * kiwi/ui/test/listener.py: * kiwi/ui/widgets/list.py: * tests/test_List.py: * tests/test_ObjectList.py: * tools/glade_conversor.py: Rename List to ObjectList and move it to kiwi.ui. Update examples, tests, callsites and conversion helpers. Keep a backwards compatibility layer. 2006-02-14 Johan Dahlin * tests/test_utils.py: Add tests for various scenarios * kiwi/python.py: Use __init__ instead of __new__ in ClassInittableMetaType. * kiwi/utils.py: Simplify, make sure the GObject Meta class registers the GType instead of ourselves. That's the only way to make it honor __gtype_name__ in PyGTK 2.8.x. Remove some backwards compatibility code. And add a hack to make it pickup signals and properties. 2006-02-13 Johan Dahlin * examples/comboentry.py: * kiwi/ui/comboentry.py: Add a comboentry and example 2006-02-13 Johan Dahlin * examples/validation/personalinformation.glade: * examples/validation/personalinformation.py: * kiwi/datatypes.py: * kiwi/ui/gazpacholoader.py: * tests/test_datatypes.py: Add a unicode datatype, tests and modify a model attribute in personalinformation to use it. 2006-02-09 Johan Dahlin * kiwi/component.py: * kiwi/interfaces.py: * tests/test_component.py: Add a very basic component system, based on code by Ali Asfhar 2006-02-09 Johan Dahlin * tests/test_argcheck.py: 100% coverage here * kiwi/argcheck.py: Remove tests, they're in tests/ now * tools/showcoverage: Steal from gazpacho (who stole from flumotion) * kiwi/ui/widgets/list.py (List.index): Use is None instead of if, return treeiter instead or item.iter. (List.__iter__): Implement __iter__ in returned object so iter(klist) works. * tests/test_List.py: Add tests. Unit coverage up to 50% globally now. 2006-02-08 Johan Dahlin * kiwi/ui/widgets/list.py: * tests/test_List.py: Add klist::has-rows and tests. 2006-02-04 Johan Dahlin * setup.py: Move kiwiwidgets to a subdirectory to mirror the layout of the installed resources. 2006-02-03 Johan Dahlin * kiwi/ui/widgets/list.py (List._setup_columns): If there are no expanded columns add a fake one after the last one. 2006-02-03 Patrick K O'Brien reviewed by: Johan Dahlin * kiwi/ui/widgets/list.py: Use set_sort_column_id better with column ids. Simplify _on_column__clicked. When switching columns, always set to ascending sorting. Fixes #2420 2006-02-01 Johan Dahlin * kiwi/ui/dialogs.py: Really make it follow the HIG. Copied some code from eel-alert-dialog.c Import stuff from gazpacho, save, open, BaseDialog 2006-02-01 Patrick K O'Brien reviewed by: Johan Dahlin * kiwi/argcheck.py: * tests/test_argcheck.py: Add parenthesis in percent checker, so it actually works and does not allow values above 100. Add unit tests. === 1.9.5 === 2006-01-30 Johan Dahlin * NEWS: Update * kiwi/environ.py (Library.__init__): Do not use an empty from list, it'll fail otherwise === 1.9.4 === 2006-01-27 Johan Dahlin * NEWS: Update * kiwi/proxies.py (Proxy.update_many): New method, like update but takes a sequence instead 2006-01-25 Johan Dahlin * kiwi/proxies.py (Proxy.remove_widget, Proxy.add_widget): Add, to be able to add and remove widgets dynamically. Based on patch by Henrique Romano * kiwi/ui/test/common.py (Base.get_object): New function, to avoid accessing private members from subclasses. * kiwi/ui/test/player.py (Player.delete_window): delete the reference to the window, so it can be destroyed properly and new windows can be put here, eg when we reopen the dialog. 2006-01-24 Henrique Romano * kiwi/ui/widgets/label.py (Label.set_underline): Implements set_underline method and a minor cleanup on all the other text properties setters. 2006-01-23 Johan Dahlin * examples/list/stockicon.py: New example to demonstrate this feature. * kiwi/ui/widgets/list.py: Add Column:use_stock and Column:icon-size. 2006-01-16 Johan Dahlin * kiwi/python.py (Settable): Another idea from twisted imported, and improved. * kiwi/ui/test/listener.py: Pass signals to event types. Add a SkipEvent exception to be able to filter inside the event. Fix DoubleClick event on the list to be caught earlier. * kiwi/ui/test/player.py (ThreadSafeFunction._invoke): Refactor to always return False after the call. * kiwi/ui/test/listener.py: Add a new event for toolbutton clicks. 2006-01-13 Johan Dahlin * kiwi/ui/widgets/combobox.py (ComboBox.clear): Emit content-changed here so the model is updated. * kiwi/ui/test/listener.py: * kiwi/ui/widgets/list.py: Add get_selected_row_number() and double_click() methods to the klist. Add support for List::double-click signal in the ui test. Fixes #2385 2006-01-13 Johan Dahlin * kiwi/ui/test/common.py: Add support for GtkToolButtons * kiwi/ui/test/player.py: Update UI tests, to be able to run several times in the same process as done in stoqs ui test framework. 2006-01-13 Johan Dahlin * kiwi/environ.py: Add support for py2exe, based on patch by Carlos Augusto Marcicano * kiwi/argcheck.py (argcheck._type_check): Add support for sending in the default value, even if the type is diffent * tests/test_argcheck.py (ArgTest.func_none): Add test for this 2006-01-10 Johan Dahlin * examples/list/searchbar.py: New example * kiwi/ui/widgets/list.py (List.add_list): Be a bit smarter when cleaning the list, instead of removing all items, check if they're inserted first. Refactor remove and remove type sniffing. (Column.__init__): Set data_type to string as default. 2006-01-09 Johan Dahlin * tests/test_datatypes.py: Check if the locale is present before running the tests. Fixes #2377 2006-01-03 Johan Dahlin * kiwi/tasklet.py: Add a decorator, patch by Gustavo. Fixes 2368 2006-01-03 Johan Dahlin * kiwi/datatypes.py (_BaseDateTimeConverter.get_lang_constant): Move lang_constant from being a class variable to an accessor since its undefined on win32. Fixes #2358 2005-12-16 Johan Dahlin * kiwi/environ.py (Library, Library.__init__): improve doc string === kiwi 1.9.3 === 2005-12-15 Johan Dahlin * NEWS: Added * kiwi/__version__.py: Bump 2005-12-15 Johan Dahlin * kiwi/ui/widgets/list.py: Subclass propertyobject. 2005-12-14 Johan Dahlin * kiwi/ui/widgets/list.py (SummaryLabel.update_total): Use get_attribute and always convert to float. * kiwi/ui/views.py (SlaveView): Add simple notebook validation support. It'll mark the text of the labels red if there are any widgets inside which are mandatory or invalid. Fixes bug 2286. 2005-12-14 Ali Afshar reviewed by: Johan Dahlin * examples/hyperlink/hyperlink_demo.py: * kiwi/ui/hyperlink.py: 2005-12-14 Johan Dahlin * kiwi/ui/delegates.py (SlaveDelegate.__init__) (Delegate.__init__): API Change, move keyactions to the end of the list, to be as compatible as possible with the parent klasses. * kiwi/ui/delegates.py: * kiwi/ui/views.py: Cleanups: - Make a few variables private, remove duplicated code. - Reorganize variables - Refactor functionallity to methods 2005-12-13 Johan Dahlin * kiwi/ui/widgets/list.py: Add support for check buttons in kiwi lists. Also emit cell-edited when changing a radio button. * kiwi/datatypes.py (currency): (CurrencyConverter): Add support for currency. (format_price): Use currency.format * kiwi/ui/widgets/list.py (List._cell_data_func): Add support for currency here. 2005-12-09 Johan Dahlin * kiwi/ui/dialogs.py: Add dialogs, stolen and extended from gazpacho. 2005-12-06 Johan Dahlin * kiwi/ui/test/common.py: * kiwi/ui/test/listener.py: * kiwi/ui/test/player.py: Add API documentation, almost complete now. Also add a timeout for accessing widgets, this will help us to handle situations where it takes some time for some widgets to be created. Also ignore eventual errors when the window is closed. Fix support for selection of multiple rows in KiwiList and add support for ComboBox. * tools/epyrun: Do not ignore directories called test. 2005-12-06 Johan Dahlin * kiwi/datatypes.py (BoolConverter.from_string): Do not return a function, instead call the function and return the value of it. Sigh. (BoolConverter.from_string): Always return a boolean, default to False 2005-12-05 Johan Dahlin * kiwi/datatypes.py (converter): New baseclass (BaseDateTimeConverter): New baseclass for datetime.* (DateTimeConverter): (TimeConverter): New converters * kiwi/ui/widgets/list.py (List): Use datatype converter to convert to and from strings. Add some optimizations, save the converter instance and send it as an argument to the callbacks. * kiwi/ui/widgets/list.py (List.select_paths): New method, selects a list of paths. * kiwi/proxies.py (Proxy._setup_widgets): Cleanups: Use consistent exception messages Add and use ProxyError instead of TypeError Add a new check to ensure that there are no unsupported widgets added. Change a warning into an exception (Proxy._initialize_widgets): Remove error handler, unused and a hack, unique clashes will be handled in other ways. 2005-12-03 Johan Dahlin * kiwi/ui/test/listener.py: * kiwi/ui/test/player.py: Improve the generated output format to be readable, remove some old stuff. 2005-12-02 Johan Dahlin * bin/kiwi-ui-test: * kiwi/ui/test/__init__.py: * kiwi/ui/test/common.py: * kiwi/ui/test/listener.py: * kiwi/ui/test/main.py: * kiwi/ui/test/player.py: Check-in initial gui testing framework. A recorder, which generates code which uses a simple player object. 2005-12-01 Johan Dahlin * kiwi/dist.py (get_site_packages_dir): New function, to handle installation of modules in the site-packages directory in a platform independent way. * setup.py (version): Use it here. Fixes bug #2326 (Carlos Augusto Marcicano) 2005-11-28 Johan Dahlin * kiwi/ui/gazpacholoader.py (DataTypeProperty.save): Handle None, since it's now an allowed type 2005-11-26 Johan Dahlin * kiwi/ui/widgets/list.py (List.remove): Remove the iter from the cache before remove the row. 2005-11-24 Henrique Romano reviewed by: Johan Dahlin * kiwi/argcheck.py: Allowing a "extra_check" function be called to validate data after type_check is executed over all the parameters. 2005-11-24 Johan Dahlin * kiwi/datatypes.py: * kiwi/interfaces.py: * kiwi/log.py: * kiwi/proxies.py: * kiwi/ui/gadgets.py: * kiwi/ui/views.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: Validation progress: - Empty date fields are now working - ValueUnset are not checked everywhere in the widgets - Fadeout works reliably - validate calls read() instead, since it can now raise exceptions - _as_string/_from_string were inverted. Logging improvements: - Add logging to proxy/view/fade 2005-11-23 Johan Dahlin * kiwi/datatypes.py: * kiwi/interfaces.py: * kiwi/python.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: * kiwi/utils.py: Use PropertyObject for all widgets. Simplifies property handling. Merge in properties, even for non gobjects for all bases when using PropertyObject, so we can put properties and signals in the WidgetMixins. Add allowed_data_types, useful for SpinButton and RadioButton. 2005-11-22 Johan Dahlin * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/textview.py: Simplify, rename and privatize string converters. 2005-11-22 Johan Dahlin * kiwi/interfaces.py: * kiwi/proxies.py: * kiwi/ui/widgets/proxy.py: Kill default-value property 2005-11-22 Johan Dahlin * kiwi/datatypes.py: Handle format == None, for all converters. * setup.py (version): * MANIFEST.in: Install & include AUTHORS NEWS README in the tarball Do not include .pyc files in the tarball === kiwi 1.9.2 === 2005-11-21 Johan Dahlin * NEWS: Added * kiwi/__version__.py: Bump 2005-11-21 Johan Dahlin * examples/tasklet/simple.py: * examples/tasklet/test-tasklet.py: * examples/tasklet/test-tasklet2.py: * kiwi/tasklet.py: Add gtasklet from Gustavo Carneiro. 2005-11-21 Johan Dahlin * kiwi/controllers.py: * kiwi/datatypes.py: * kiwi/decorators.py: * kiwi/dist.py: * kiwi/environ.py: * kiwi/i18n/__init__.py: * kiwi/i18n/i18n.py: * kiwi/interfaces.py: * kiwi/models.py: * kiwi/python.py: * kiwi/ui/__init__.py: * kiwi/ui/gadgets.py: * kiwi/ui/gazpacholoader.py: * kiwi/utils.py: Add docstrings * tools/epyrun: Helper for running epydoc 2005-11-16 Johan Dahlin * kiwi/ui/widgets/list.py: Add cache property to column useful when using accessors which can be expensive to compute. 2005-11-15 Johan Dahlin * kiwi/ui/widgets/list.py (List._load): Catch StopIteration, in case we have an empty list. 2005-11-14 Gustavo Sverzut Barbieri reviewed by: Johan Dahlin * kiwi/ui/gazpacholoader.py: Do not use get_name for getting the name of objects, since it's only present for GtkWidgets, instead use get_data('gazpacho::object-id'), which is set on all objects. Fixes #2231. 2005-11-14 Johan Dahlin * kiwi/ui/widgets/list.py (SummaryLabel.update_total): Remove erroneousness show. (ListLabel.set_value): Rename from set_text 2005-11-11 Johan Dahlin * kiwi/ui/widgets/list.py (ListLabel): Add a new label, (actually an hbox), which can be put under or on top of a KiwiList to have a label vertically aligned with a column. (SummaryLabel): Subclass of ListLabel which summarizes the values of a column. * kiwi/ui/widgets/checkbutton.py (CheckButton.prop_set_data_type): Accept string here. * kiwi/ui/widgets/proxy.py (WidgetMixinSupportValidation): Refactor emitting of validation-changed since we also need to emit it when calling set_[valid|invalid|blank] and not only in validate_data. Fixes validation of mandatory entry with completion enabled. 2005-11-10 Johan Dahlin * kiwi/proxies.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/spinbutton.py: Move validate_data out of read, and do it manually afterwards, there's only two call sites anyway. 2005-11-09 Johan Dahlin * kiwi/ui/gadgets.py: * kiwi/ui/views.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/list.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: * kiwi/utils.py: Add new method utils.type_register, use it everywhere. 2005-11-09 Johan Dahlin * kiwi/ui/widgets/proxy.py (WidgetMixinSupportValidation.validate_data): Only set blank for mandatory widgets and when data is empty. Always convert '' to None for non-string types. * kiwi/ui/widgets/entry.py (Entry.read): Remove a couple of big hacks 2005-11-08 Johan Dahlin * kiwi/ui/widgets/entry.py (Entry.read): Add a hack to try to fix validation for empty entries. * kiwi/controllers.py (BaseController.__init__): Add support for automatic keyboard accelerators of type key_name or key_modifier_name. * kiwi/ui/widgets/proxy.py (WidgetMixinSupportValidation.set_invalid): Only connect to done here, that's the only place where we need to use it. When done, set text and icon. Also disconnect the signal so it won't be called multiple times. * kiwi/ui/widgets/entry.py (Entry._update_current_object): Add custom validation, when using objects. * kiwi/ui/gadgets.py (FadeOut.start): Add additional state, so we know if we're done when start() is called multiple times. Always emit done, since we're depending on it in other places. * kiwi/ui/widgets/proxy.py (WidgetMixinSupportValidation.don): Wait until the completion is done to set the text. * kiwi/ui/gazpacholoader.py (EntryDataType.get_data_types): Add Object, useful when using entry completion. 2005-11-07 Johan Dahlin * kiwi/ui/gadgets.py (FadeOut.stop): Simplify, use delayed.stop, avoids extra state and makes it reliable. Add API documentation. * kiwi/decorators.py (delayed.stop): New function * kiwi/ui/widgets/proxy.py (WidgetMixinSupportValidation.set_blank) (WidgetMixinSupportValidation.set_invalid) (WidgetMixinSupportValidation.set_valid): Make public and add API documentation. 2005-11-03 Johan Dahlin * kiwi/dist.py (compile_po_files): Handle the case when msgfmt is not found, print a warning and returning nothing, instead of failing later on. 2005-11-01 Johan Dahlin * kiwi/environ.py (app): Add global app reference (Library.__init__): Add better error messages and insert $prefix/lib/pythonX.Y/site-packages into sys.path 2005-10-31 Johan Dahlin * kiwi/ui/icon.py (IconEntry.update_background): Handle insensitive widgets aswell, always use the current state instead of hard coding GTK_STATE_NORMAL. 2005-10-14 Johan Dahlin * kiwi/ui/gadgets.py: * kiwi/ui/widgets/proxy.py: Add two signals (color-changed and done) to FadeOut and move it out to gadgets. 2005-10-14 Johan Dahlin * kiwi/interfaces.py: * kiwi/proxies.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/spinbutton.py: Do validation in read, simplifies the interaction between the proxy and the widgets. Add some basic documentation to the proxy/validation "interfaces". 2005-10-13 Johan Dahlin * kiwi/proxies.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: Kill WidgetMixin.update, move the check it did to the proxy itself. 2005-10-13 Johan Dahlin * kiwi/ui/widgets/entry.py (Entry.do_unrealize): * kiwi/ui/widgets/spinbutton.py (SpinButton.do_unrealize): Chain to parents realize so the parent gets a chance to free the relevent resources. 2005-10-11 Johan Dahlin * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/list.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: Redo validation. Fixes #2123 2005-10-10 Johan Dahlin * kiwi/ui/widgets/list.py (ColoredColumn): New column. (Column): Add two new hooks, renderer_func & on_attach_renderer 2005-10-09 Johan Dahlin * kiwi/__init__.py: * kiwi/dist.py: * kiwi/environ.py: * kiwi/ui/wizard.py: * setup.py: Add infrastructure for handling installed/uninstalled mode. Introduces Library and Application instances in kiwi.environ. Add distutils helpers. Use environ.Library in for kiwi itself (yay dogfooding). Remove old hack in wizard. 2005-10-08 Johan Dahlin * kiwi/argcheck.py (argcheck.__call__): Check so the number of arguments are equal before checking default values, so the user can get a useful exception * tests/test_argcheck.py (ArgTest.method2): Add a few more tests. 2005-10-07 Johan Dahlin * kiwi/ui/gazpacholoader.py (DataTypeProperty.save): Do not show editor for Radio/Check, they're always boolean. 2005-10-06 Johan Dahlin * kiwi/ui/widgets/list.py (List.refresh): New method. 2005-09-30 Johan Dahlin * kiwi/ui/widgets/comboboxentry.py (BaseComboBoxEntry): A reimplementation of GtkComboBoxEntry in python. The main difference between this one and the gtk+ one is that we put our own entry (a kiwi entry) instead of the standard one, which allows us to do put an icon in the right part, used by the validation. * kiwi/ui/widgets/tooltips.py (Tooltip.__init__): Add a tooltips implementation, which is a bit more flexible than the one in Gtk+. Currently unused. * tests/test_argcheck.py (ArgTest.testUserDefined): Add some basic tests. * kiwi/argcheck.py: Move in decorator from fiscalprinter. Add some additional tests and support for checking default values. 2005-09-28 Johan Dahlin * kiwi/ui/widgets/list.py: (List): do not save iterator in instance, save the iterators in a local table. * kiwi/ui/widgets/list.py: (List.count, List.index, List.extend): Add (List.insert, List.pop, List.reverse, List.sort): Add stubs (List.append, List.remove, List.update): Rename (List.add_instance, List.remove_instance, List.update_instance) (List.select_instance): Add backwards compat wrappers (List._load): Clean up, add iterator support. (List.select): Check selection mode * kiwi/utils.py (deprecated): New decorator * kiwi/ui/widgets/list.py: Remove a couple of methods, now when we have the iter in the instance. 2005-09-27 Johan Dahlin * kiwi/ui/widgets/list.py (List.get_previous, List.get_next): New methods. (List.add_instance, List._load): Set instance.iter upon insertion. (Column.__init__): Raise ValueError if iter is used, it's reserved (List.select): Rename from select_instance, use iter from instance, scroll to cell. 2005-09-27 Johan Dahlin * kiwi/accessors.py (CacheControl.invalidate): Use DefaultValue instead of AttributeError. Fixes bug #695: Do not eat AttributeError's triggered in kgetattr accessors. 2005-09-21 Johan Dahlin * kiwi/ui/widgets/combobox.py (ComboBox.__init__): Only emit changed once. 2005-09-20 Johan Dahlin * kiwi/ui/widgets/entry.py: Add support for objects mapped to strings for entry completion. Also add a property, exact-completion which decides if it should be case insensitive and match substrings. Fixes bug #2150 * tests/test_List.py: Make it pass again * kiwi/ui/widgets/list.py (List.get_columns): Return the columns here instead of the column definition string. (List.do_get_property): Access the column string directly here. 2005-09-16 Johan Dahlin * kiwi/ui/gazpacholoader.py: Clean up /a bit/, fixes #2144 by inheriting from ComboBoxAdatper aswell. 2005-09-15 Johan Dahlin * kiwi/ui/gazpacholoader.py (DataTypeProperty.save): Add support for data-type in KiwiLabel, fixes #2141 2005-09-13 Johan Dahlin * kiwi/utils.py: AMD64 fix ported from gazpachos copy. 2005-09-06 Johan Dahlin * kiwi/datatypes.py (FloatConverter._filter_locale): Rewrite, easier to read and understand now. Can't remember all bugs it fixes. (IntConverter.from_string): Remove the thousand separators. 2005-09-05 Johan Dahlin * kiwi/datatypes.py (format_price): Implement a function to format a number according to the current currency. * tests/test_datatypes.py (DataTypesTest.testFormatPrice): some basic tests. * kiwi/ui/widgets/list.py: Add radio property to Column and set the radio property of the CellRendererToggle if it's set to True. Add a new callback for handling CellRendererToogle::toggled which updates the value of the model. 2005-09-05 Johan Dahlin * kiwi/ui/widgets/list.py (List._setup_columns): Add check for sortable, add a searchable check. (Column): Add a searchable property. (List._setup_column): Set up a search equal func (List._search_equal_func): Define here, checks if the string starts with the value, seems to be the expected behavior. 2005-09-01 Henrique Romano * kiwi/ui/widgets/list.py (List._setup_columns): Just verifying if more than one column has the sorted attribute, if so raise ValueError. 2005-06-14 Henrique Romano reviewed by: Johan Dahlin * kiwi/ui/gazpacholoader.py: Update to work with gazpacho trunk. Add better data-type filters for various widgets. 2005-08-02 Johan Dahlin * kiwi/ui/widgets/list.py (Column, List): Add support for cell editing. 2005-08-01 Johan Dahlin * kiwi/ui/widgets/list.py (Column): subclass GObject implicitly. * kiwi/utils.py (PropertyObject): update to use ClassInittableObject refactor a little bit and add extra checks so we don't accidentally try to register a GObject type for it. Move out so it's not a subclass of GObject, but require it to be a subclass of GObject, this will make it possible to use the class for GObject subclasses * kiwi/python.py: New file, add ClassInittableObject. 2005-07-29 Johan Dahlin * bin/kiwi-i18n: * kiwi/i18n/__init__.py: * kiwi/i18n/i18n.py: * kiwi/i18n/msgfmt.py: * kiwi/i18n/pygettext.py: Add kiwi-i18n and copy in msgfmt/pygettext from the python distribution, modify msgfmt slightly to be usable from outside of the script. 2005-07-28 Johan Dahlin * kiwi/proxies.py: * kiwi/ui/views.py: * kiwi/ui/widgets/proxy.py: It turned out that the last patch was not flexible enough. Slaves and proxies had to be attached in the correct order, and sometimes the validation state of the widgets were not emitted. This patch changes the following two things: 1) Always emit validation state for all widgets when attaching a proxy to a view 2) Always emit validation state for slaves attached to views 2005-07-27 Johan Dahlin * kiwi/ui/widgets/list.py (Column.__init__): Mark title_pixmap as todo, since it's not implemented yet. Sort the other properties after the doc string. Add tooltips doc string. (List._cell_data_func): Clean up a little bit and add support for format func (Column): Add format_func attribute, documentation and basic error handling (Column.from_string): Make it a classmethod, not a staticmethod 2005-07-26 Johan Dahlin * examples/lang.glade: * examples/slaves.py: * kiwi/interfaces.py: * kiwi/ui/views.py: * kiwi/ui/widgets/proxy.py: Add per view validation, including example. Fixes #2096 r=kiko 2005-07-15 Johan Dahlin * kiwi/ui/widgets/list.py (Column): Almost a complete rewrite, using a GObject and properties. * kiwi/utils.py: Add a new PropertyObject, fix gproperty to work with float,int and enums 2005-07-14 Johan Dahlin * tests/test_utils.py (SliceTest): Add tests for slicerange * kiwi/ui/widgets/list.py: Clean up treemodel usage, use python api and define COL_MODEL as 0 and use it everywhere (List.__getitem__): Fix slicing once and for all. 2005-07-13 Johan Dahlin * kiwi/ui/widgets/list.py (List.add_list): Remove selection, GtkTreeView is different from a GtkCList, if this turns out to be a problem we'll rewrite/readd this code. (List.get_column_by_name): New method (List.__init__): require a list or column, tuples are not allowed (List.add_instance): Remove selection restore here aswell (List.__getitem__): Oh my, fix slicing. 2005-07-12 Johan Dahlin * kiwi/ui/widgets/list.py (List.__contains__): Clean up, use == instead of is (List.__setitem__, List.__getitem__): Support strings, refactor (List.__iter__): Impl. (List.get_selected, List.get_selected_rows): Separate, mimic gtk+ closely. (List.__init__): Select first item in list if we selection is allowed and we're inserting a list. 2005-07-11 Johan Dahlin * examples/completion.py: * gazpacho-plugin/kiwi2.xml: * kiwi/ui/widgets/entry.py: Add entry completion support and a small example. == 1.9.0 released == 2005-06-28 Johan Dahlin * kiwi/ui/__init__.py: Kill pygtk.require() it won't be missed since all systems which we can run on has pygtk 2.x installed as default. Should also speed up imports. * kiwi/__init__.py: * kiwi/environ.py: * kiwi/interfaces.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/libgladeloader.py: * kiwi/ui/loader.py: * kiwi/ui/views.py: Kill loader. Move glade/imagepath handling to kiwi.environ. Move abstract adaptor to kiwi.interfaces 2005-06-28 Johan Dahlin * README: * kiwi/__init__.py: * kiwi/ui/gazpacholoader.py: * kiwi/ui/libgladeloader.py: * kiwi/ui/loader.py: * kiwi/ui/views.py: How much I hate this, but: Write a libglade loader to use as fallback when gazpacho cannot be found. Add a hook to require gazpacho. 2005-06-27 Johan Dahlin * kiwi/ui/views.py: Allow us to function without a broker. * kiwi/ui/gadgets.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/utils.py: Move gtk related functions from kiwi.utils to kiwi.ui.gadgets 2005-06-27 Johan Dahlin * examples/PersonalInformation/personalinformation.py: * kiwi/initgtk.py: * kiwi/ui/__init__.py: * kiwi/ui/views.py: * kiwi/ui/wizard.py: Kill initgtk, move quit_if_last to views and pygtk.require() to kiwi.ui, adds a warning. 2005-06-27 Johan Dahlin * examples/PersonalInformation/personalinformation.glade: * examples/PersonalInformation/personalinformation.py: * gazpacho-plugin/kiwi2.py: * kiwi/interfaces.py: * kiwi/proxies.py: * kiwi/ui/delegates.py: * kiwi/ui/views.py: * kiwi/ui/widgets/__init__.py: * kiwi/ui/widgets/checkbutton.py: * kiwi/ui/widgets/combobox.py: * kiwi/ui/widgets/datatypes.py: * kiwi/ui/widgets/entry.py: * kiwi/ui/widgets/label.py: * kiwi/ui/widgets/proxy.py: * kiwi/ui/widgets/radiobutton.py: * kiwi/ui/widgets/spinbutton.py: * kiwi/ui/widgets/textview.py: Make the personinformation example work again. 2005-06-20 Johan Dahlin * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/WidgetProxy.py: * examples/PersonalInformation/personalinformation.glade: * examples/PersonalInformation/personalinformation.py: More validation work, allow non-string types to be validated. ComboBox cleanups, adding a mode variable (string or data) which helps. ComboBoxEntry only works for normal mode, editable, searchable and auto-completion currently broken 2005-06-18 Johan Dahlin * Kiwi2/Widgets/WidgetProxy.py (MixinSupportValidation._check_for_complaints): quit the idle, stops us from eating up the CPU. * Kiwi2/Delegates.py: * Kiwi2/Models.py: * Kiwi2/Proxies.py: * Kiwi2/Widgets/CheckButton.py: * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/RadioButton.py: * Kiwi2/Widgets/Wizard.py: More imports cleanup, remove unused cruft. 2005-06-18 Johan Dahlin * Kiwi2/Delegates.py: * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/__init__.py: * examples/News/news2.py: * examples/News/news4.py: * examples/PersonalInformation/personalinformation.py: * examples/Wizard/survey.py: * tests/Proxies/None.py: * tests/Proxies/Separator.py: * tests/test_Action.py: * tests/test_CheckButton.py: * tests/test_Entry.py: * tests/test_Label.py: * tests/test_List.py: * tests/test_SpinButton.py: Remove unused imports. Clean up the rest we touched Remove Proxies.OldVirtualProxy 2005-06-18 Johan Dahlin * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/WidgetProxy.py: * examples/PersonalInformation/personalinformation.py: Rework validation. Only validate when the model content changes. Simplify internal implementation of validation. Fix ComboBoxEntry validation once for all. Updated example and added comments and docstrings 2005-06-14 Johan Dahlin * gazpacho-plugin/kiwi2.py: Clean up and fix a bug, when moving rows update up/down buttons. 2005-06-14 Henrique Romano reviewed by: Johan Dahlin * Kiwi2/Views.py: Correcting the way as widgets verification is made. 2005-06-14 Johan Dahlin * Kiwi2/Widgets/List.py: * gazpacho-plugin/kiwi2.xml: Add List::selection-mode 2005-06-13 Henrique Romano reviewed by: Johan Dahlin * Kiwi2/Widgets/TextView.py: Just removing a useless callback. 2005-06-07 Henrique Romano * Kiwi2/Proxies.py: Also block/unblock all signals connected when updating widgets. * Kiwi2/Views.py: Allow signal_name to be None, if so block/unblock all the connected 2005-06-01 Henrique Romano reviwed by: Johan Dahlin * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/datatypes.py: * examples/PersonalInformation/personalinformation.py: * tests/test_datatypes.py: Add set_format, update testsuite. 2005-05-30 Johan Dahlin * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/Widgets/datatypes.py: Clean up datatypes. Introduce ConverterRegistry which is global object you can use to convert to and from strings. Use the new API everywhere, cleanup WidgetProxy a little bit. 2005-05-25 Johan Dahlin * Kiwi2/Views.py (SlaveView.attach_slave): Change show_all into show. 2005-05-24 Johan Dahlin * Kiwi2/Delegates.py: * Kiwi2/Models.py: * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/Widgets/CheckButton.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/Widgets/Wizard.py: * Kiwi2/utils.py: pychecker fixes, remove ListDelegate. (replaced by the List widget) 2005-05-24 Johan Dahlin * Kiwi2/Widgets/List.py: Clean up column handling a bit futher. Make pychecker happy. Remove decimal separator, we depend on 2.3 where we can set LC_NUMERIC properly 2005-05-23 Johan Dahlin * Kiwi2/Views.py (SignalBroker.handler_block, SignalBroker.handler_unblock): New methods, to block a signal tied to a widget. Patch by Henrique * Kiwi2/Widgets/List.py: Ensure we can't hide all columns. (ContextMenu): New object, factor out context menu from List. Make rebuild it when columns change lazily. It now displays the columns in correct order when columns are moved (or added/removed). 2005-05-20 Johan Dahlin * Kiwi2/Widgets/ComboBox.py: First validate, then return return get_selected_data(), which will return the data if you have some, otherwise the label will be returned. Fixes bug reported by Henrique. * Kiwi2/Widgets/List.py: Clean up justification handling. Rename col_definition to column, and always refer to treeview columns as treeview_column, to avoid confusion. (List): Setup sorting in a slightly saner way 2005-05-19 Johan Dahlin * Kiwi2/Controllers.py: * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/Widgets/List.py: * Kiwi2/utils.py: Update docstrings, to make epydoc happy * doc/Makefile: Clean up and add some more files * Kiwi2/WidgetProxies: Remove 2005-05-19 Johan Dahlin * AUTHORS: * Kiwi2/Controllers.py: * Kiwi2/Delegates.py: * Kiwi2/Models.py: * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/Widgets/CheckButton.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/RadioButton.py: * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/Widgets/Wizard.py: * Kiwi2/Widgets/__init__.py: * Kiwi2/Widgets/datatypes.py: * Kiwi2/__init__.py: * Kiwi2/accessors.py: * Kiwi2/initgtk.py: * Kiwi2/utils.py: * Kiwi2/version.py: Update authors, remove #! in all files and add licenses to a few files. 2005-05-19 Johan Dahlin * Kiwi2/Widgets/CheckButton.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/RadioButton.py: * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/__init__.py: * Kiwi2/__init__.py: Add documentation strings 2005-05-19 Lorenzo Gil Sanchez * Kiwi2/Widgets/WidgetProxy.py (MixinSupportValidation._validate_data): if the data is None don't try to validate * examples/PersonalInformation/personalinformation.py: validators don't need to handler the None case now. * Kiwi2/Widgets/SpinButton.py (SpinButton.update): fix stupid mistake * Kiwi2/Widgets/WidgetProxy.py (MixinSupportValidation.set_mandatory): don't redraw the widget here since that is done in the validate_data method. * examples/PersonalInformation/personalinformation.py : fix this example by allowing data to be None in the weight validation * Kiwi2/Widgets/SpinButton.py: simplify this class but using just the changed signal and forget about value-changed and output signals. Fix it for gtk+ 2.4 using the same technique as in the Entry * Kiwi2/Widgets/Entry.py: (Entry.__init__): setup the chain function in the constructor instead of the expose handler * Kiwi2/Widgets/ComboBox.py: - Make expose events work with GTK 2.4 and 2.6 - Put as much validation code as possible in MixinSupportValidation - Add some comments and docstring to parts that are not trivial - Fix drawing mandatory icons. Now when somebody changes a widget (either a proxy or the user) we check if the widget is empty to draw the mandatory icon (if the widget mandatory prop is True) - Reuse the validate signal in the ComboBoxEntry to perform aditional validations * Kiwi2/Widgets/WidgetProxy.py (MixinSupportValidation.is_correct) (MixinSupportValidation._validate_data) (MixinSupportValidation.__init__): properly initialize and manage the _blank_data attribute * Kiwi2/Views.py (SlaveView.check_widgets_validity) (SlaveView.register_validate_function): use the is_correct method to check if the data of a widget is ok. Document the register_validate_function to understand the meaning of its only parameter (SlaveView.attach_slave): show the slave after the attacment process * Kiwi2/Widgets/WidgetProxy.py (MixinSupportValidation.__init__): changed self.valid_data to self._blank_data to avoid confusing names * Kiwi2/Widgets/ComboBox.py (ComboBoxEntry.read): fix validation errors that were going to the console instead of showing in the interface. Update the model when the user edit the entry Don't listen to model row changes since we don't want to inform our proxy about them and we don't need to do anything special in the comboboxentry itself * Kiwi2/Proxies.py (Proxy._initialize_widgets): block the widgets when we initialize them so we don't get updates back to the model 2005-05-18 Johan Dahlin * Kiwi2/Widgets/List.py: Add expand property to Column. 2005-05-18 Lorenzo Gil Sanchez * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.select_item_by_data) (ComboProxyMixin.select_item_by_label): improve error messages to easily detect programming errors. * Kiwi2/Proxies.py (Proxy._setup_widgets): provides the widget name in the warning message. Now is much easier to fix this programming error. 2005-05-17 Lorenzo Gil Sanchez * Kiwi2/Widgets/List.py (Column.set_from_string, Column.__str__): swap the order the tooltip and format are read/wrote since that's the order Gazpacho is expecting 2005-05-17 Johan Dahlin * Kiwi2/Widgets/List.py (List._setup_columns): Check if it's configured before adding the columns, prevents the columns from being added twice when specifying data_types for all columns. 2005-05-16 Lorenzo Gil Sanchez * Kiwi2/Widgets/List.py (List.add_list): if we add an empty list we should not select and focus any row 2005-05-16 Johan Dahlin * Kiwi2/Widgets/List.py: Reformat to fit in 79 columns. 2005-05-14 Johan Dahlin * Kiwi2/Widgets/List.py: Protect selection, it's None if called after List is destroyed. * Kiwi2/initgtk.py: Guard pygtk.require, so we can import gtk before 2005-05-12 Lorenzo Gil Sanchez * Kiwi2/Widgets/List.py (List._create_column) (List._on_header__button_release_event): change the event from button_press to button_release to fix a bug related to right click menu on the List headers 2005-05-12 Johan Dahlin * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/datatypes.py: Add support for Column.Data, does not allow it for booleans. Remove some unused code and comments from gazpacho. Use locale.format to format strings, also use this for float types. r=lgs 2005-05-10 Johan Dahlin * Kiwi2/Widgets/List.py (List._setup_column): Fix justification. 2005-05-10 Lorenzo Gil Sanchez * Kiwi2/Widgets/List.py (str2type): remove the default data type attribute following kiko's suggestion since it's cleaner and easier to detect future errors. 2005-05-09 Sidnei da Silva reviewed by Johan Dahlin * setup.py (packages): add Kiwi2.Widgets. 2005-05-07 Lorenzo Gil Sanchez * Kiwi2/Widgets/List.py: added support for dates in KiwiList. (List._on_column__clicked): removed some code that should never been there 2005-05-04 Gustavo Rahal * Kiwi2/Widgets/Kiwi2/RadioButton.py: fixed error data-value * Kiwi2/Widgets/Kiwi2/List.py: minor code improvements from comments of last checkin * Kiwi2/Widgets/Kiwi2/Label.py: now kiwilabels have the ability to update the model * Kiwi2/Widgets/Kiwi2/WidgetProxy.py: minor fix in do_get_property 2005-05-03 Gustavo Rahal * tests/test_Entry.py: test now checks for ValueUnset instead of None * Kiwi2/Widgets/ComboBox.py: proper error checking * Kiwi2/Widgets/List.py: column with number are right justified by default. Fixed justification error 2005-04-26 Gustavo Rahal * Kiwi2/Widgets/List.py: fixed bug 1962 2005-04-26 Gustavo Rahal * Kiwi2/Widgets/SpinButton.py: minor cosmetic change in code to be similar to other widgets. * Kiwi2/Widgets/TextView.py: is data is None set text as empty 2005-04-25 Johan Dahlin reviewed by: Gustavo * Kiwi2/Proxies.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/WidgetProxy.py: use ValueUnset instead of None for MixinSupportValidation._validate_data. Update widgets slightly for this Fixes #1961 2005-04-25 Gustavo Rahal * Kiwi2/Widgets/ComboBox.py: bugs fixes. 1955, 1953, 1952, 1923 * Kiwi2/Widgets/TextView.py * Kiwi2/Widgets/Entry.py * Kiwi2/Widgets/SpinButton.py: checking of pygtk version done inside init * Kiwi2/Widgets/WidgetProxy.py: now check widgets validity is only called when the widget is attached to a view/delegate 2005-04-25 Johan Dahlin * Kiwi2/Views.py: Compare references, since some widgets overrides equality checks (eg, __nonzero__ for ComboBoxEntry) 2005-04-19 Gustavo Rahal * Kiwi2/View.py: * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/TextView.py * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/Entry.py * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/WidgetProxy.py: code clean up * gazpacho-plugin/kiwi2.py: added support for object data-type * Kiwi2/Widgets/datatype.py: added support for object data-type 2005-04-19 Daniel Saran R. da Cunha * Kiwi2/Widgets/ComboBox.py (ComboBoxEntry): Now ComboBoxEntry works properly with objects 2005-04-19 Gustavo Rahal * Kiwi2/Proxies.py: all widgets will have an attribute that specify the views/delegate/etc... that owns it. * Kiwi2/Views.py: added a global validation that check for correctness on all widgets, * Kiwi2/Widgets/ComboBox.py: refactoring, added ability to add items to the comboboxentry list (dafault it not to add). * Kiwi2/Widgets/List.py: added ability to slice kiwilist model * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/SpinButton.py: code refactoring due to changes on combobox. Some minor changes due to pygtk 2.6 * Kiwi2/Widgets/WidgetProxy.py: code refactoring * Kiwi2/examples/PersonalInformation/personalinformation.py: shows how to use global validation 2005-04-13 Daniel Saran R. da Cunha * Kiwi2/Widgets/ComboBox.py: make the Widget work properly with ValueUnset 2005-04-13 Gustavo Rahal * tests/test_Entry.py: test work with different locales * Kiwi2/Widgets/ComboBox.py: apparently mandatory icon is displayed correctly at all times! Uhu! * Kiwi2/Widgets/TextView.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/SpinButton.py: * Kiwi2/Widgets/WidgetProxy.py: moved code around 2005-04-13 Gustavo Rahal * Kiwi2/Widgets/TextView.py: added Kiwi TextView Widget * Kiwi2/Widgets/ComboBox.py: fixed mandatory icon display * Kiwi2/Widgets/CheckButton.py: renamed MixinSupportMandatory to MixinSupportValidation * Kiwi2/Widgets/RadioButton.py: same as CheckButton * Kiwi2/Widgets/__init__.py: added Kiwi TextView Widget * Kiwi2/Widgets/Entry.py: moved validation code to WidgetProxy.MixinSupportValidation * Kiwi2/Widgets/SpinButton.py: code reorganization to support custom validation * Kiwi2/Widgets/WidgetProxy.py: added custom validation code to the MixinSupportValidation class. * gazpacho-plugin/pixmaps/kiwitextview.png: added icon * gazpacho-plugin/kiwi2.xml: added kiwitextview * examples/PersonalInformation/personalinformation.py: added custom validation examples * examples/PersonalInformation/personalinformation.glade: added more widgets for custom validation examples 2005-04-11 Gustavo Rahal * Kiwi2/Widgets/CheckButton: fixed minor name mistake * Kiwi2/examples/survey.py: renamed survey object * Kiwi2/Views.py: added support for non-glade files to be slaves * Kiwi2/Widgets/Wizard.py: wizard interface is not a glade file anymore 2005-04-08 Gustavo Rahal * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/SpinButtonpy: added mandatory input capabilities * Kiwi2/Widgets/WidgetProxy.py: renamed class WidgetProxyMinin to Mixin and created a new class named MixinSupportMandatory for Kiwi Widgets that needs to be set as mandatory * Kiwi2/Widgets/RadioButton.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/CheckButton.py: class renaming 2005-04-07 Lorenzo Gil Sanchez * Kiwi2/Widgets/ComboBox.py: * Kiwi2/Widgets/CheckButton.py: * Kiwi2/Widgets/RadioButton.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/Widgets/SpinButtonpy: make the widgets play nicely with ValueUnset * Kiwi2/Widgets/datatypes.py (set_date_format): fix a typo * Kiwi2/Proxies.py (Proxy._initialize_widgets): allow to call update on the widgets with ValueUnset * tests/actions.glade: updated the format of the glade file * tests/test_datatypes.py (DataTypesTest.teststr2date): update the exceptions that are raised when the dates are wrong 2005-04-06 Gustavo Rahal * some code refactor of last commit 2005-04-05 Gustavo Rahal * Kiwi2/Widgets/Entry.py: fixed and improved input checking * Kiwi2/Widgets/datatypes.py: improve float number and date checkings to support locale variations * examples/PersonalInformation/personalinformation.py: added height entry * test/test_Entry.py: added KiwiEntry test case 2005-04-01 Evandro Vale Miquelito * Kiwi2/Widgets/List.py: Adding a new select_instance method. 2005-04-01 Daniel Saran R. da Cunha * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.select_item_by_data): Cleaning the code 2005-04-01 Daniel Saran R. da Cunha * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.select_item_by_data): Don't try to select a row if there is no data in the combo. 2005-03-30 Gustavo Rahal * doc/Makefile: generates epydoc api 2005-03-30 Gustavo Rahal * Kiwi2/Widgets/Label.py: added support for bold, italic and size attributes for labels * tests/test_Label.py: added a test case for Labels 2005-03-30 Gustavo Rahal * Kiwi2/Widgets/SpinButton.py: fixed set_data_type function * Kiwi2/Widgets/WidgetProxy.py: fixed __init__.py to set initial values for _data_type and _model_attribute 2005-03-30 Daniel Saran R. da Cunha * Kiwi2/Widgets/ComboBox.py: ComboBox now use the data, when it exists, instead of label of its items to update the model and view. 2005-03-29 Gustavo Rahal * Kiwi2/Widgets/SpinButton.py: added Spin Button support to Kiwi * Kiwi2/Widgets/__init__.py: spinbutton * Kiwi2/tests/test_SpinButton.py: test spinbutton * Kiwi2/gazpacho-plugin/kiwi2.xml: spin button * Kiwi2/gazpacho-plugin/kiwi2.py: new SpinBtnDataTypeAdaptor to deal with int and float only 2001-12-02 Daniel Saran R. da Cunha * Kiwi2/Widgets/WidgetProxy.py: don't try to set the widget content if the data is None * Kiwi2/Widgets/RadioButton.py: don't update the view if there is no data in the model 2005-03-29 Lorenzo Gil Sanchez * Kiwi2/Widgets/RadioButton.py (RadioButton.read): fixed small stylist problems 2001-12-02 Daniel Saran R. da Cunha * Kiwi2/Widgets/RadioButton.py: fix for a wrong return type 2005-03-29 Lorenzo Gil Sanchez * Kiwi2/__init__.py (imagepath): * Kiwi2/Views.py (GazpachoWidgetTree.__init__): use a path resolver to correctly load images (image_path_resolver): 2005-03-24 Gustavo Rahal * Kiwi2/__init__.py: fixed KIWI_GLADE_PATH * Kiwi2/Widgets/Wizard.py: new wizard widget * Kiwi2/Widgets/Wizard.glade: glade file that defines the structure of the wizard. * Kiwi2/examples/Wizard: directory with a new three step wizard example * Kiwi2/Widgets/CheckButton.py: since default data-types of widgets are string is was necessary a modication on the constructor to set to boolean * Kiwi2/Widgets/__init__.py: added Wizard widget 2005-03-24 Lorenzo Gil Sanchez * Kiwi2/Widgets/WidgetProxy.py (WidgetProxyMixin.update): add a warning when setting None in a widget * Kiwi2/examples/PersonalInformation: validation example * Kiwi2/Widgets/Entry.py: inform the user if he made a mistake while typing a entry by setting the background of the entry and adding a tooltip with a message explaining the error * Kiwi2/Widgets/datatypes.py (str2int, str2float): custom converter functions that raise exceptions with nice messages if they find errors in the data the user enters * Kiwi2/Widgets/WidgetProxy.py: (WidgetProxyMixin.set_default_value): allow to set a custom default value (WidgetProxyMixin.__init__): added a flag to know if the user set a default value different that the default value associated with the data type * Kiwi2/utils.py (merge_colors): function to merge the background of a widget between two different colors * Kiwi2/Proxies.py (Proxy._on_widget__content_changed): only update the model if the data is correct 2005-03-24 Gustavo Rahal * Kiwi2/Widgets/CheckButton.py: bool data-type is an object not a string anymore 2001-11-27 Daniel Saran R. da Cunha * Kiwi2/Widgets/Label.py (Label.set_color): method to change label's color 2005-03-23 Lorenzo Gil Sanchez * gazpacho-plugin/kiwi2.py (DataTypeAdaptor.create_editor): add support for date types 2001-11-27 Daniel Saran R. da Cunha * examples/SexSelection: New example to test Radio Button * Kiwi2/Widgets/WidgetProxy.py (WidgetProxyMixin.__init__): The default data type is a string because we always need a default. Otherwise gazpacho doesn't save this property. * gazpacho-plugin/kiwi2.xml: * Kiwi2/Widgets/__init__.py: * Kiwi2/Widgets/RadioButton.py : support for Radio Buttons 2005-03-23 Lorenzo Gil Sanchez * Kiwi2/Widgets/datatypes.py: added a date type and functions to set a data from a string a viceversa * tests/test_datatypes.py: added some tests for the datetypes * tests/test_Action.py: * tests/test_BaseView.py: * tests/test_CheckButton.py: * tests/test_ComboBox.py: * tests/test_Delegate.py: * tests/test_Entry.py: * tests/test_List.py: don't use delays and remove all the non essential refreshes. Also make it more unittest friendly 2001-11-26 Daniel Saran R. da Cunha * gazpacho-plugin/kiwi2.xml: * gazpacho-plugin/kiwi2.py (DataTypeAdaptor.create_editor): now the data-type does not need custom setter (neither getters). This fix a bug about Gazpacho not saving the data type properties. * gazpacho-plugin/kiwi2.xml: disabled hadjustment and vadjustment properties on the List since they are ScrollWindow properties that do not make sense with the list. 2005-03-22 Lorenzo Gil Sanchez * gazpacho-plugin/kiwi2.py (DataTypeAdaptor.create_editor): handle data-types as objects, not as strings * Kiwi2/Widgets/WidgetProxy.py: make data-type be an object property instead of a string one. * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/datatypes.py: * Kiwi2/Widgets/__init__.py: put all the type functionality in datatypes.py * Kiwi2/Proxies.py (Proxy._setup_widgets): make sure all the KiwiWidgets have a data-type * Kiwi2/Widgets/ComboBox.py (ComboBoxEntry.__init__): fixed a typo 2005-03-22 Johan Dahlin * Kiwi2/Controllers.py: * Kiwi2/Delegates.py: * Kiwi2/Proxies.py: * Kiwi2/Views.py: * Kiwi2/WidgetProxies/Base.py: * Kiwi2/WidgetProxies/CheckButton.py: * Kiwi2/WidgetProxies/Entry.py: * Kiwi2/WidgetProxies/OptionMenu.py: * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/WidgetProxy.py: * Kiwi2/accessors.py: * Kiwi2/initgtk.py: Convert exceptions to use raise(x) instead of raise, x 2005-03-22 Johan Dahlin * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.__len__): Add (ComboBox.clear): Call ComboProxyMixin.clear (ComboBoxEntry.clear): Ditto (ComboProxyMixin.get_selected_label): Add (ComboProxyMixin.select_item_by_position) (ComboProxyMixin.select_item_by_label) (ComboProxyMixin.select_item_by_data): Simplify 2005-03-22 Gustavo Rahal * Kiwi2/tests/test_CheckButton.py: added a test for CheckButtons * Kiwi2/Widgets/CheckButton.py: test for bool value in set_data_type 2005-03-22 Gustavo Rahal * Kiwi2/Widgets/CheckButton.py: added Checkbutton kiwi widget * Kiwi2/Widgets/__init__.py: support for Checkbutton * kiwi2.xml: added checkbutton to gazpacho-plugin 2005-03-22 Johan Dahlin * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.select_item_by_label): Use iter_next, not next_iter! (ComboProxyMixin.get_selected_data): Impl. for Henrique. 2005-03-22 Lorenzo Gil Sanchez * examples/NewsForm/newsform.py: added a size field to test validations in integer entries * Kiwi2/Widgets/WidgetProxy.py (WidgetProxyMixin.__init__): allow to initialize a widget with None values for their properties so we can correctly get the default value when setting the data type * gazpacho-plugin/kiwi2.xml: disable text-column which is not useful in ComboBoxEntry * Kiwi2/Widgets/ComboBox.py (ComboProxyMixin.prefill): allow prefill with the empty list * examples/FavouriteColor/color2.py: example of ComboBoxEntry * examples/FavouriteColor/color.py: example of ComboBox * kiwi2.xml: added combos to gazpacho-plugin * Kiwi2/Widgets/__init__.py: * Kiwi2/Widgets/ComboBox.py: first support for ComboBoxes (old OptionMenu) and ComboBoxEntries (old Combos) 2005-03-21 Evandro Vale Miquelito * Kiwi2/Widgets/List.py (List.update_instance): A small fix for update_instance method. Now we verify if the instance even exists in the list. 2005-03-21 Lorenzo Gil Sanchez * tests/test_List.py (DataTests.testContains): added a test for __contains__ (DataTests.testUpdatingOneInstance): fixed test * Kiwi2/Widgets/List.py (List.update_instance): added update_instance (List.__contains__): added __contains__ method * tests/test_List.py (DataTests.testUpdatingOneInstance): added a test for update_instance 2005-03-18 Lorenzo Gil Sanchez * tests/test_List.py (DataTests.testClearList): added test for the clear method * Kiwi2/Widgets/List.py (List.clear): added * Kiwi2/Views.py (SignalBroker._autoconnect_by_method_name): allow to connect to the actions objects and also improve error message when a method's signature is bad * Kiwi2/Widgets/List.py (List.remove_instance): added * tests/test_List.py: added several tests for removing and adding data to the list * tests/test_Action.py: test that menubars and toolbars work with Kiwi autoconnect mechanism * tests/test_Delegate.py: added tests to make sure that a signal handler is only called once 2005-03-16 Lorenzo Gil Sanchez * Kiwi2/Views.py (SlaveView._init_glade_adaptor): allow the container name to have the same name as the gladefile. Fix a bug with Daniel code 2005-03-15 Lorenzo Gil Sanchez * gazpacho-plugin/kiwi2.xml: add a libglade-module attribute since that is used now for writing the 'requires' tag * gazpacho-plugin/kiwi2.py: change the order of the arguments in the update_editor methods since that was changed in Gazpacho 2005-03-11 Lorenzo Gil Sanchez * Kiwi2/Views.py (GladeSignalBroker._connect_glade_signals): remove the double connection * tests/test_Delegate.py (DelegateTest.testButtons): add another test to check if the callbacks are being called more than one time * Kiwi2/Widgets/List.py: add two extra flags: _columns_created and _columns_configured to avoid creating the columns several times * tests/test_List.py (ListTest.testAddingOneInstance): improve test to check that the columns remain sane 2005-03-10 Lorenzo Gil Sanchez * Kiwi2/Views.py (GladeSignalBroker._connect_glade_signals): don't call autoconnect_by_method_name twice * Kiwi2/utils.py (get_foreground): fix the call to gdk_color_to_string * Kiwi2/Views.py (SlaveView.__init__): if we don't have a toplevel try to get it from the toplevel_name attribute * Kiwi2/Widgets/List.py (List): fixed several minor bugs discovered by writing a simple test * Kiwi2/Views.py (SlaveView): use Johan gsignal and gproperty functions to make the code more readable * Kiwi2/Widgets/List.py: remove some print debugging stuff * Kiwi2/Views.py (find_in_gladepath): fixed a typo * examples/* adapt examples to work with the new changes * Kiwi2/Controllers.py: don't use string module and ListType and TupleType. * Kiwi2/utils.py: put here some useful functions that are needed from several different places. * Kiwi2/Widgets/List.py: * Kiwi2/Widgets/Entry.py: * Kiwi2/Widgets/Label.py: * Kiwi2/Widgets/__init__.py: * Kiwi2/Widgets/WidgetProxy.py: new module to keep the KiwiWidgets. These widgets inherit from a gtk widget and from WidgetProxyMixin, which provides the functionality to work together with the Proxy Kiwi Framework class. * Kiwi2/__init__.py: move some utility functions to utils.py and better API of gladepath * Kiwi2/Delegates.py: remove GladeDelegate and GladeSlaveDelegate and put more arguments to Delegate and SlaveDelegate so we keep the same functionality. * Kiwi2/Views.py: remove GladeView and GladeSlaveView. Rename AbstractView to SlaveView so now we just have SlaveView and BaseView and they now how to handle glade files if the user gives them the apropiate constructor argument. Also, add add_proxy method to the views and make them work with proxies by composition. * Kiwi2/Proxies.py: change the VirtualProxy class to be the only Proxy class and work by composition of the View classes * gazpacho-plugin/kiwi2.py: * gazpacho-plugin/kiwi2.xml: added support for Kiwi Label and Kiwi Entry * gazpacho-plugin/README: added a note about how to create kiwi widget icons 2005-03-07 Lorenzo Gil Sanchez * Kiwi2/List.py (List.__init__): fix a typo * examples/Faren/faren3.py: this one uses a GladeDelegate * examples/Faren/faren2.py: second version of Faren example * examples/Faren/faren.py: ported the basic Temperature example * examples/Simple/simple.py: ported another example to Kiwi2 2005-03-05 Lorenzo Gil Sanchez * Kiwi2/Views.py (GazpachoWidgetTree.__init__): move the gladename and gladefile class attributes to GladeSlaveView and GladeView. Fix #1843 (GladeSlaveView, GladeView): * examples/HeyPlanet/heyglade3.py (MyView.gladefile): provide an example of setting the gladefile as a class attribute * Kiwi2/Delegates.py: add a keyactions argument to all the Delegates so we can use it for the Controller * examples/News/news4.py (Shell.__init__): provide an example of keyactions * Kiwi2/initgtk.py (quit_if_last): also quit if there is no toplevels (e.g., this function was called after hiding the last toplevel) * examples/News/news3.py (Shell.on_ok__clicked): stay on the safe side by checking if there is something selected * Kiwi2/Views.py (BaseView._setup_keypress_handler): put this method only in BaseView to avoid warnings * Kiwi2/List.py: use kgetattr instead of getattr when accesing the attributes of an instance * Kiwi2/initgtk.py (quit_if_last): don't treat hide windows as toplevels when seeing if we need to quit because usually there are several windows in a glade file. 2005-03-04 Lorenzo Gil Sanchez * Kiwi2/Views.py (SignalBroker): super() only works with new style classes 2005-03-03 Lorenzo Gil Sanchez * Kiwi2/List.py (List.add_list): block and unblock the selection signal on the selection object instead of the treeview object (List._load): add a progress_handler (non used so far) argument so the call from add_list() works (List._create_best_renderer_for_type): don't make the cells editable until we have a 'editable' property to switch this feature on and off. This is needed so we can still double click on the rows * Kiwi2/Views.py (GladeSlaveView.__init__): fix some errors so we can use this view * examples/News/news4.py: new version using a GladeSlaveDelegate with the KiwList in the file news_list.glade. Note how the column definitions are in the glade file this time and how we setup the signals of the KiwiList as any other widget. 2005-03-02 Lorenzo Gil Sanchez * Kiwi2/List.py: several changes to make the Columns serializable and so we can use List from Gazpacho * Kiwi2/__init__.py (ValueUnset): put this class at the beginning to avoid import problems (str2bool, str2enum, str2type): utility functions 2005-02-28 Lorenzo Gil Sanchez * Kiwi2/List.py: renamed KiwiList to List * Kiwi2/Delegates.py (ListDelegate.__init__): * examples/News/news3.py: * examples/News/news2.py: * Kiwi2/Delegates.py (KiwiListDelegate.__init__): don't need a scroll window anymore since that functionality is already in the KiwiList. * examples/News/news3.py: don't use KiwiListDelegate anymore but just a simple SlaveDelegate. Also fix the small bug about the color * Kiwi2/List.py (KiwiList.__init__): for now, use right click button on the header to show the popup menu. Thunderbird style button on top of the vertical scrollbar will be done later. 2005-02-26 Lorenzo Gil Sanchez * examples/News/news3.py: adapt the example to use the new signal feature of the KiwiList * Kiwi2/List.py: improve the List with a menu to show/hide columns and do column sorting and reordering. * Kiwi2/Delegates.py: move more things into KiwiList from KiwiListDelegate. Use signals in KiwiList for the selection and double click events. * Kiwi2/Views.py: optimize color conversion to string by using shifts instead of divisions * tests/run_all_tests.py: fix the first line of the script 2005-02-21 Lorenzo Gil Sanchez * Kiwi2/AbstractViews.py: we don't use this file anymore. Its contents were moved to Views.py * Kiwi2/__init__.py: put the ValueUnset class here so we avoid some cyclic dependencies * Kiwi2/Views.py (GladeSignalBroker._connect_glade_signals): make AbstractView inherit form GObject so we can enjoy GSignals in the views. (BaseView.connect): can't proxy these methods anymore because we have our own signals now * Kiwi2/Delegates.py: added the KiwiListDelegated. Not sure if we should deprecate this delegate now that we are pushing all the functionality to the Kiwi2.List class. * Kiwi2/initgtk.py: we need gobject for signals * examples/News/news3.py: * examples/News/news2.py: converted examples to use Kiwi2 2005-02-18 Lorenzo Gil Sanchez * Kiwi2/Views.py: implement attach_slave for GazpachoTree and so for GladeView and GladeSlaveView * Kiwi2/initgtk.py (quit_if_last): only see TOPLEVEL windows, no POPUPs * examples/News/news.py: adapted to Kiwi2. Also use TreeView instead of CList 2005-02-17 Lorenzo Gil Sanchez * Kiwi2/Views.py: forget about GazpachoViews. Make GladeView and GladeSlaveView use Gazpacho internally with delegation and don't use AbstractGladeView anymore. I still need to implement attach_slave with this delegation aproach. * Kiwi2/Delegates.py: minor PEP-8 changes and change Kiwi to Kiwi2 * tests/run_all_tests.py: convenience script to run all of the tests * tests/test_BaseViewInherit.py: adapted to run under unittest * examples/HeyPlanet/heygazpacho3.py: copy of heygazpacho3.py using Gazpachoview * examples/HeyPlanet/heygazpacho2.py: copy of heyglade2.py using GazpachoView * examples/HeyPlanet/heygazpacho.py: copy of heyglade.py using GazpachoView * Kiwi2/initgtk.py (quit_if_last): mainquit is deprecated, did you know that? * examples/HeyPlanet/heyglade3.py: * examples/HeyPlanet/heyglade2.py: * examples/HeyPlanet/heyglade.py: * examples/HeyPlanet/hey.py: adapted to Kiwi2 * examples/HeyPlanet/hey.glade: generated by Gazpacho, not Glade-2 2005-02-16 Lorenzo Gil Sanchez * tests/test_BaseView.py: make all the tests run under the unittest facilities. Integrate test_BaseView2.py here also. * Kiwi2/Views.py (AbstractView._gdk_color_to_string) (AbstractView.get_background, AbstractView.get_foreground): provide method to get the colors of a widget in the #XXXXX format - Comment out the classes we don't care about right now: GladeSlaveView and GladeView * Kiwi2/initgtk.py: don't load main_quit, it's deprecated (quit_if_last): add this function to be used as a delete-event handler so the user can open and close windows without worrying about when to call gtk.mainquit 2005-02-11 Lorenzo Gil Sanchez * Kiwi2/__init__.py (standard_widgets): fixed a bug about not importing sys and using sys.stderr * Kiwi2/AbstractViews.py (SignalBroker.__init__): refactor SignalBroker __init__ method so subclasses can customize it (SignalBroker._do_connections): new method that do all the connections this signal broker is supposed to make. In the base SignalBroker it only calls _autoconnect_by_method_name but in GladeSignalBroker it also calls _connect_glade_signals 2005-02-10 Lorenzo Gil Sanchez * Kiwi2/initgtk.py: added explanation on why do we have a separate initgtk.py module 2005-02-05 Lorenzo Gil Sanchez * setup.py: remove non used imports PIDA-0.5.1/contrib/kiwi/HACKING0000644000175000017500000000347410652671067013742 0ustar alialiCode repository layout ====================== bin/ Scripts which are going to be installed doc/ Manual, API reference etc examples/ Code examples and demonstrations of features in kiwi kiwi/ Source code goes in here gazpacho-plugin/ Gazpacho integration, also look at kiwi/ui/gazpacholoader.py glade/ Glade files used by kiwi itself pixmaps/ Pixmaps, images etc po/ Translations tests/ Unittests goes in here tools/ Useful scripts Contributing ============ Make sure all unittests pass. If possible, add a new, or modify an existing test after applying a patch. Feature requests, bug fixes or added features should be submitted through bugzilla, http://bugs.async.com.br/enter_bug.cgi?product=Kiwi Always run pyflakes on the whole source code before submitting, it's important that pyflakes does not show *ANY* warnings at all. Sometimes you have to add tiny hacks to workaround bugs in pyflakes. Try to keep ChangeLog updated Testsuite ========= To run the whole testsuite, type: $ trial tests in the root directory. To run a single file, class or test: $ trial tests.test_datatypes $ trial tests.test_datatypes.BoolTest $ trial tests.test_datatypes.BoolTest.testFromString Coverage ======== Code coverage should generally not increase after checkins, run the script showcoverage from the tools directory to get a listing of the current coverage: $ tools/showcoverage If you want to see the exact lines, go and dig in _trial_temp/coverage/ It's a little bit broken, so sometimes you have to delete _trial_temp between runs. Checklist ========= To make it easier to get your patch accepted, follow these steps. * PEP-8 compatible code * include test * does the whole testsuite run * no pyflakes warnings * if new/modified API: * add doc strings * example * ChangeLog entry * diff -u * bugzilla PIDA-0.5.1/contrib/kiwi/MANIFEST.in0000644000175000017500000000132510652671067014502 0ustar alialirecursive-include tests *.glade *.py recursive-include doc Makefile howto.tex *.txt *.tmpl *.eps *.png *.html *.dia recursive-include examples * recursive-include gazpacho-plugin *.py *.png *.xml AUTHORS COPYING recursive-include locale *.mo recursive-include glade *.glade recursive-include pixmaps *.png recursive-include po *.po kiwi.pot POTFILES.list include debian/changelog debian/compat debian/copyright debian/control debian/rules debian/pyversions include debian/pycompat debian/watch include debian/*.docs debian/*.install debian/*.examples debian/*.api debian/patches/*.diff include AUTHORS include ChangeLog include COPYING include MANIFEST.in include README include NEWS include kiwi/_kiwi.c include kiwi.spec PIDA-0.5.1/contrib/kiwi/Makefile0000644000175000017500000000174610652671067014413 0ustar alialiVERSION=$(shell python -c "execfile('kiwi/__version__.py'); print '.'.join(map(str, version))") PACKAGE=kiwi WEBDIR=/mondo/htdocs/async/projects/kiwi TARBALL_DIR=/mondo/htdocs/stoq.com.br/download/sources include common/async.mk all: python setup.py build_ext -i clean-docs: rm -fr doc/api rm -fr doc/howto clean: clean-docs debclean rm -fr $(BUILDDIR) rm -f MANIFEST rm -fr kiwi/_kiwi.so docs: make -s -C doc api howto web: clean-docs docs cp -r doc/api ${WEBDIR} cp -r doc/howto ${WEBDIR} cp -r doc/howto.ps ${WEBDIR} gzip ${WEBDIR}/howto.ps cd ${WEBDIR} && tar cfz howto.tar.gz howto cd ${WEBDIR} && tar cfz api.tar.gz api bdist: python setup.py -q bdist_wininst release-tag: svn cp -m "Tag $(VERSION)" . svn+ssh://svn.async.com.br/pub/kiwi/tags/$(VERSION) upload-release: sdist bdist scp dist/$(TARBALL) gnome.org: ssh gnome.org install-module $(TARBALL) scp dist/kiwi-$(VERSION).win32.exe gnome.org:/ftp/pub/GNOME/binaries/win32/kiwi/ .PHONY: bdist upload-release PIDA-0.5.1/contrib/kiwi/NEWS0000644000175000017500000001531610652671067013450 0ustar alialikiwi-1.9.16 16-july-2007 - Polish translation (Jarek Zgoda) - ObjectList updates - Support for currency marker in KiwiLabel (#3389, Ronaldo Maia) - Improvements to glade-3 plugin (Ali, #3461) - ObjectList::row-expanded signal (Ali, #3460) - Kiwi completion fixes/workaround (Ali, #2901) - Tasklet updates (Gustavo) - ValueUnset and KiwiEntry (Ali, #3408) - Gaxml support in loader - Gazpacho support for ObjectList (Ali, #3389) - Rml translation support - SQLObject full text index support kiwi-1.9.15 21-may-2007 - New simple DB API layer to integrate with SQLObject - Add SearchContainer/SearchGladeDelegate, simple and flexible widgets for search and display results from a database. - Entry mask bugfix to workaround PyGObject bug - Improved Glade-3 integration, ObjectList should work. - Sort column fix (Ali, fixes #3032) - Pixbuf column None fix (Ali, fixes #3310) - Add ObjectList.sort_by_attribute (Ali, fixes #3311) - Add ObjectList.grab_focus (Ali, fixes #3326) - Add expander property to Column (Ali, #3336) - Don't allow the user to move outside of fields in the entry masks (Ronaldo, fixes #3333) - At least one radio column should be True (Ali, #3339) - Make sure GObject.new works for ObjectList (#3343) - Don't allow year before 1900 ( fixes #3364) - Generate pydoctor documentation instead of epydoc. - Many other bug fixes kiwi-1.9.14: 23-march-2007 - Add glade-3 plugin (Ali) - Add ListDialog and ListContainer - Add RPM .spec file. - Fix right aligned entry validation (Johan, Ronaldo) - Make the ObjectList search case insensitive by default - Improve gazpacho plugin - ObjectList cache fixes for index/__contains__ - Add assertion support for ui test kiwi-1.9.13: 01-february-2007 - Workaround GtkEntry bug when resizing the size of its GtkWindows. (Johan, Ronaldo) - Include _kiwi.c in tarball, fixes build. (Johan) - Use pkg-config to find pygtk version. (Johan) kiwi-1.9.12: 29-january-2007 - Rewritten UI Test framework, not using threads - Improved Date Error message (#3041, Marco Antonio Porcho Souza) - ComboEntry.set_selected_iter improvements (#3099, Goedson Teixeira Paixao) - French translation (Benoit Myard) - Proper Hyperlink allocation (#2923, Ali Afshar) - Avoid tasklet zombies (Gustavo Carneiro) - Logging fixes (#2984, Johan) - Add enum type (Johan) - Backport any/all from Python 2.5 (Johan) - Simplify ObjectTree API (Johan) kiwi-1.9.11: 09-october-2006 - Much improved mask handling (#2838, Ronaldo) - libglade integration bugfixes - Workaround for PyGTK 2.10 bug - Add right-click to ObjectList (#2817, Ronaldo) - raise ValueError instead of AttributeError (#1998, Sidnei) kiwi-1.9.10: 15-september-2006 - API reference and howto included in the tarball - Remove some excessive of inheritance - A couple of new examples - Mask fixes; alignment and focus (Ronaldo) - win32 installation fixes (Aaron Spike) - Tasklet WaitForCall support (Gustavo) - Add a simple Enum implementation - Add new separated delegates for glade views kiwi-1.9.9: 23-august-2006 - KiwiEntry improvements (Johan, Patrick, Ronaldo) - Win32 installation fixes for Gazpacho & Kiwi (Johan) - DateEntry fixes (Ronaldo, Johan) - DateEntry win32 support (Aaron Spike) - Logging improvements (Johan) - Datatypes refactoring and improved Gazpacho integration (Ronaldo) - Add UI tests (Johan) - Bugs fixes: #2535: entry needs exceptions that don't fail (Patrick O'Brien) #2545: DateEntry calendar popup doesn't hide correctly (Ronaldo) #2562: Falha na exibição de valores do tipo "currency" (Lincoln, Ronaldo) #2610: [Patch] Remove unnecessary on_column__clicked handler (Dave Cook) #2611: Adding a sortable keyword to objectlist (Dave Cook) #2612: [Patch] objectlist.refresh() fix and optimization (Dave Cook) #2632: Problems with datatype currency when the language ... (Lincoln, Johan) #2654: Decimal precision is removed when pickling (Ronaldo) #2656: ComboEntry's clear() is missing (Ronaldo) #2681: Kiwi doesn't respect system colors after widget ... (Johan, Gustavo Barberi) #2682: Mandatory fields fails to validade if they state ... (Gustavo, Ronaldo) #2685: Make it able to turn fading of invalid stateness to ... (Ali, Johan) #2697: Add a pixbuf data type (Johan, Ronaldo) #2698: Add a ProxyButton (Johan, Henrique) #2720: DateEntry breaks when set invalid. (Ali) #2721: Missing locale specific date formatting information ... (Aaron Spike, Johan) #2758: Datatype converters should provide a get_mask method. (Ronaldo) kiwi-1.9.8: 25-april-2006 - distutils.setup() replacement - date tests - FileChooser & FileChooserButton - Rename all proxy widgets to start with Proxy - Win32 installation fixes - UI test threading fixes - Sizegroup merging (Ronaldo) - Mask improvements (Ronaldo) - ObjectList improvements (Johan, Ronaldo, Patrick) - Lots of bug fixes (Johan, Ronaldo, Sidnei) kiwi-1.9.7: 11-march-2006 - Much improved mask support - DateEntry widget - Re-add ProxyDelegate (lost since kiwi1) - Draw validation icon on the left side for right align entries - Many ComboEntry bug fixes - Distribution helper improvements - Limited support for zope.interfaces - Add a better HIG alert dialog - Improved logging (a la GStreamer) kiwi-1.9.6: 17-february-2006 - argcheck bugfix (Patrick K O'Brien) - Some basic dialogs imported from gazpacho - Fix kiwilist sorting (Patrick) - Much improved unittest coverage - Simple component system (Johan & Ali Afshar) - Better Unicode and Decimal support - ComboEntry widget - Rename list to ObjectList - Mask support for entry kiwi-1.9.5: 30-january-2006 - Installation bug fix kiwi-1.9.4: 27-january-2006 - Tasklet decorator (Gustavo) - List.add_list optimizations - py2exe support (Carlos Augusto Marcicano) - UI test fixes, support lists and toolbars - Plenty of bug fixes kiwi-1.9.3: 15-december-2005 - UI Test framework - Add a hyperlink widget (Ali Afshar) - Add a selectable box - Currency datatype - Simple dialogs - Logging helpers - Validation improvements - Documentation improvemnets kiwi-1.9.2: 21-november-2005 - Added tasklet (Gustavo Carneiro) - Wizard improvements (Evandro) - Added icon for entries - Documentation - Decorators - Bug fixes - Currency - Validation/Proxy refactoring - List labels - Colored column - Distutils/distribution helpers - Portuguese and Swedish translation PIDA-0.5.1/contrib/kiwi/README0000644000175000017500000000627710652671067013637 0ustar alialiKiwi: A Framework for developing graphical applications in Python. Kiwi is a framework composed of a set of modules, which eases Python development using PyGTK. Kiwi makes graphical applications *much* faster to code, with good architecture and more Python-like bindings; it also offers extended widgets based upon the original PyGTK widgets. * Requirements - GTK+ 2.6.x ftp://ftp.gtk.org/pub/gtk/v2.6/ - PyGTK 2.6.x ftp://ftp.gtk.org/pub/gtk/python/v2.6/ - Python > 2.3 http://www.python.org/download/ Optional - gazpacho > 0.6.5 http://ftp.gnome.org/pub/GNOME/sources/gazpacho/ - zope.interfaces http://www.zope.org/Products/ZopeInterface - sqlobject > 0.6.0 http://www.sqlobject.org/ Some features of the ui test framework requires PyGObject 2.10.0 or higher to function properly. It'll work without but some features are disabled. * Installation To install (having made sure that the dependencies are installed and working) do (as root normally): python setup.py install (You can optionally provide a prefix using the following form, but if you do remember to setup PYTHONPATH accordingly) python setup.py install [--prefix=] * Documentation Included in doc/howto/ and doc/api/ are HTML versions of the developer's guide and API reference, respectively. You can also browse the online versions at: - Developer's guide: http://www.async.com.br/projects/kiwi/howto/ - API documentation: http://www.async.com.br/projects/kiwi/api/ The developer's guide is available in compressed PostScript format from: http://www.async.com.br/projects/kiwi/howto.ps.gz You can regenerate all the documentation; just see doc/Makefile. To generate the API docs you will need: - epydoc http://epydoc.sf.net/ To generate the developer's guide you will need: - Python source (the tarball) http://www.python.org/download/ - LaTeX (various distributions) - latex2html http://saftsack.fs.uni-bayreuth.de/~latex2ht/current/ - GhostScript http://www.cs.wisc.edu/~ghost/ - NetPBM (for latex2html) http://netbpm.sf.net/ * Directory Layout examples/ Contains a number of examples, most of which are referenced in the documentation. kiwi/ Contains the module code itself; this is installed into your Python's site-packages directory by setup.py. doc/ Contains documentation for Kiwi, including the LaTeX source code which can be used to rebuild the docs. doc/Makefile contains commands to regenerate the full set of docs. extra/ Contains patches to GTK+ and PyGTK code, and helper scripts that can be used to accelerate certain tasks. tests/ Contains a number of test cases for the Kiwi widgets and framework. All checkins and additions of new code should be preceded by a working testcase. * Contact info: URL and download: http://www.async.com.br/projects/kiwi/ Maintainer: Johan Dahlin Original author: Christian Reis PIDA-0.5.1/contrib/kiwi/kiwi.spec0000644000175000017500000000727310652671067014573 0ustar aliali%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} Name: python-kiwi Version: 1.9.16 Release: 1%{?dist} Summary: Framework for Python GUI applications Group: Development/Libraries License: LGPL URL: http://www.async.com.br/projects/kiwi/ Source0: http://download.gnome.org/sources/kiwi/1.9/kiwi-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch BuildRequires: python-devel, pygtk2 >= 2.8, gettext Requires: pygtk2 >= 2.8 %description Kiwi consists of a set of classes and wrappers for PyGTK that were developed to provide a sort of framework for applications. Fully object-oriented, and roughly Smalltalk's MVC, Kiwi provides a simple, practical way to build forms, windows and widgets that transparently access and display your object data. %package gazpacho Group: Development/Libraries Summary: Gazpacho integration for kiwi Requires: gazpacho >= 0.6.6, %{name} = %{version}-%{release} %description gazpacho This package contains additional files necessary for integration with Gazpacho glade editor. %package docs Group: Documentation Summary: Documentation related to python-kiwi Requires: %{name} = %{version}-%{release} %description docs This package contains documentation that contains APIs and related materials, useful for reference when writing software using Kiwi. %prep %setup -q -n kiwi-%{version} sed -i -e 's|share/doc/kiwi|share/doc/%{name}-%{version}|' setup.py %build CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build %install rm -rf $RPM_BUILD_ROOT %{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT%{_defaultdocdir} # The install script mis-guesses where gazpacho is installed on # non-x86 platforms if [ "%{python_sitearch}" != "%{python_sitelib}" ]; then mv $RPM_BUILD_ROOT%{python_sitearch}/gazpacho \ $RPM_BUILD_ROOT%{python_sitelib}/ fi %{find_lang} kiwi %clean rm -rf $RPM_BUILD_ROOT %files -f kiwi.lang %defattr(-,root,root,-) %doc AUTHORS COPYING ChangeLog README NEWS %{_bindir}/* %{_datadir}/kiwi %{python_sitelib}/kiwi %files gazpacho %defattr(-,root,root,-) %doc COPYING %{python_sitelib}/gazpacho/widgets/* %{_datadir}/gazpacho/catalogs/* %{_datadir}/gazpacho/resources/* %{_datadir}/locale/*/LC_MESSAGES/kiwi.mo %files docs %defattr(-,root,root,-) %doc COPYING doc/* examples %changelog * Tue Jul 07 2007 Johan Dahlin 1.9.16-1 - Upstream 1.9.13 * Tue Feb 04 2007 Johan Dahlin 1.9.14-1 - Version 1.9.14 - Add .mo files * Tue Feb 04 2007 Johan Dahlin 1.9.13-1 - Upstream 1.9.13 * Sun Dec 17 2006 Konstantin Ryabitsev - 1.9.11-1 - Upstream 1.9.11 - Do not manually provide python-abi - Move docs into a subpackage - Do a better job with gazpacho dir ownerships * Sun Sep 03 2006 Konstantin Ryabitsev - 1.9.9-1 - Version 1.9.9 - Do not ghost - No more /etc/kiwi * Wed Jul 12 2006 Konstantin Ryabitsev - 1.9.8-1 - Fedora Extras rebuild. * Fri Jul 07 2006 Konstantin Ryabitsev - 1.9.8-0.4 - Build in slimmer build environments. * Fri Jun 16 2006 Konstantin Ryabitsev - 1.9.8-0.3 - Fix the incorrect gazpacho location on x86_64 (we are fully noarch) * Fri May 19 2006 Konstantin Ryabitsev - 1.9.8-0.2 - Initial packaging PIDA-0.5.1/contrib/kiwi/setup.cfg0000644000175000017500000000020010652671067014554 0ustar aliali[bdist_rpm] packager=Johan Dahlin build_requires=pygtk2 >= 2.8.0, gazpacho >= 0.6.6 requires=pygtk2 >= 2.8.0, gazpacho >= 0.6.6 PIDA-0.5.1/contrib/kiwi/setup.py0000644000175000017500000000607010652671067014460 0ustar aliali#!/usr/bin/env python # Setup.py for Kiwi # Code by Async Open Source # setup.py written by Christian Reis # re-written various times by Johan Dahlin """ kiwi offers a set of enhanced widgets for Python based on PyGTK. It also includes a framework designed to make creating Python applications using PyGTK and libglade much simpler. """ import commands from distutils.extension import Extension import sys from kiwi import kiwi_version from kiwi.dist import setup, listfiles, listpackages, get_site_packages_dir ext_modules = [] # Build a helper module for testing on gtk+ versions lower than 2.10. # Don't build it on windows due to easy availability compilers and # the lack of pkg-config. if sys.platform != 'win32' and not 'bdist_wininst' in sys.argv: exists = commands.getstatusoutput('pkg-config pygtk-2.0 --exists')[0] == 0 version = commands.getoutput('pkg-config pygtk-2.0 --modversion') if exists and version and map(int, version.split('.')) < [2, 10]: pkgs = 'gdk-2.0 gtk+-2.0 pygtk-2.0' cflags = commands.getoutput('pkg-config --cflags %s' % pkgs) libs = commands.getoutput('pkg-config --libs %s' % pkgs) include_dirs = [part.strip() for part in cflags.split('-I') if part] libraries = [part.strip() for part in libs.split('-l') if part] ext_modules.append(Extension('kiwi/_kiwi', ['kiwi/_kiwi.c'], include_dirs=include_dirs, libraries=libraries)) setup(name="kiwi", version=".".join(map(str, kiwi_version)), description="A framework and a set of enhanced widgets based on PyGTK", long_description=__doc__, author="Async Open Source", author_email="kiwi@async.com.br", url="http://www.async.com.br/projects/kiwi/", license="GNU LGPL 2.1 (see COPYING)", data_files=[('$datadir/glade', listfiles('glade', '*.glade')), ('$datadir/pixmaps', listfiles('pixmaps', '*.png')), ('share/gazpacho/catalogs', listfiles('gazpacho-plugin', 'kiwiwidgets.xml')), ('share/gazpacho/resources/kiwiwidgets', listfiles('gazpacho-plugin', 'resources', 'kiwiwidgets', '*.png')), (get_site_packages_dir('gazpacho', 'widgets'), listfiles('gazpacho-plugin', 'kiwiwidgets.py')), ('share/doc/kiwi', ('AUTHORS', 'ChangeLog', 'NEWS', 'README')), ('share/doc/kiwi/howto', listfiles('doc/howto/', '*')), ('share/doc/kiwi/api', listfiles('doc/api/', '*')), ], scripts=['bin/kiwi-i18n', 'bin/kiwi-ui-test'], packages=listpackages('kiwi'), ext_modules=ext_modules, resources=dict(locale='$prefix/share/locale'), global_resources=dict(glade='$datadir/glade', pixmap='$datadir/pixmaps'), ) PIDA-0.5.1/docs/0002755000175000017500000000000010652671501011263 5ustar alialiPIDA-0.5.1/docs/html/0002755000175000017500000000000010652671501012227 5ustar alialiPIDA-0.5.1/docs/html/images/0002755000175000017500000000000010652671501013474 5ustar alialiPIDA-0.5.1/docs/html/images/icons/0002755000175000017500000000000010652671501014607 5ustar alialiPIDA-0.5.1/docs/html/images/icons/callouts/0002755000175000017500000000000010652671501016435 5ustar alialiPIDA-0.5.1/docs/html/images/icons/callouts/1.png0000644000175000017500000000051110652670541017301 0ustar alialiPNG  IHDR s;bKGD#2cIDATxU 0.)Bft6#dH('XW 9cAM-!d>0(*?/c}֮5uƌ:x,TCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature58a072e070da22f6135cbd3e414546f9hj!tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/10.png0000644000175000017500000000055110652670541017365 0ustar alialiPNG  IHDR s;bKGD#2IDATx%!C#H,N^ [¶p\%${;/yI@l\\ySM}i㎋suȌaX̠ eڭvGj!=dR;?ݢCb kCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature386e83bb9bdfba3227f58bb897e2c8a5+ tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/11.png0000644000175000017500000000106510652670541017367 0ustar alialiPNG  IHDR ˰ pHYsttfxtIME-'kM8okǖejYVǗ˅F C3IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/14.png0000644000175000017500000000063310652670541017372 0ustar alialiPNG  IHDR ˰bKGD pHYsss"tIME x8(IDATx}=@O2\ق۰X"Y;@)lT!H!}=CDZ;9V DDDqf3qӉ~qXkTQp8|.)mUUz~9EQh 0hQE|jǣ,b)ntnwx<.|q~IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/15.png0000644000175000017500000000120010652670541017362 0ustar alialiPNG  IHDR ˰ pHYsttfxtIME0 JtEXtAuthorH tEXtDescription !# tEXtCopyright:tEXtCreation time5 tEXtSoftware]p: tEXtDisclaimertEXtWarningtEXtSourcetEXtComment̖tEXtTitle'IDATxu1˂`E`59-AZ[֜šhr /h1A-"6B||g":16BTDDD5dN\8纮.v,K墪eYO`8FQ\.kZNjb2H.1"l6{a0FQ\~^{<u6A|>OVefT eX,vrq?j x8mȔL?IDATZ9皦lN|9Nn+bbW*z)r]z=- !HIENDB`PIDA-0.5.1/docs/html/images/icons/callouts/2.png0000644000175000017500000000054110652670541017305 0ustar alialiPNG  IHDR s;bKGD#2{IDATx0 D?44,5, ]+fK UG{ukS@cBSChS{2y4Cms^% D+OJ)}:T5`/CtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature80eae529c94a7f47deccfada28acb9dfo tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/3.png0000644000175000017500000000053610652670541017312 0ustar alialiPNG  IHDR s;bKGD#2xIDATx%N@ 4^0+ F``a+&U qXҠq K ]pq˟3&=ۿ-#S:bmR&jQ5cLCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature80bbda2726ddace8ab8a01f59de2ebdbutEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/4.png0000644000175000017500000000053110652670541017306 0ustar alialiPNG  IHDR s;bKGD#2sIDATx!0C#XdeeP"\o+{%leʰ!b$ci1 q dCwCmJV$6huTj~<_²|㣴 KF6[CtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature9f82fcac9e039cbdb72380a4591324f5vtEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/5.png0000644000175000017500000000053410652670541017312 0ustar alialiPNG  IHDR s;bKGD#2vIDATx0  ~+_BhIlgvMZmmwb$|Sq$^%)%YP3]2Qj%|#[7/B_CtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignaturefe690463379eb25e562fcc8cc9b3c7e0߲9tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/6.png0000644000175000017500000000054310652670541017313 0ustar alialiPNG  IHDR s;bKGD#2}IDATx!0    FaPXXj' nn󩺵 oPHl\BuNح!i`d'נ,˖eԸgNLL< V?s8 YCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignatured25d7176d67a038afc1c56558e3dfb1atEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/7.png0000644000175000017500000000053010652670541017310 0ustar alialiPNG  IHDR s;bKGD#2rIDATx%0 OV"Y!LO Hd+H퇓e _pDlC0T+ʫ+ VAjݓ{O9lsLGIz>61GVSCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature298368142ac43cebd2586d8d1137c8df&9tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/8.png0000644000175000017500000000054510652670541017317 0ustar alialiPNG  IHDR s;bKGD#2IDATx0  v¬a` 544T ?ݻ/TܗW[Б!Dغ[`T3(fpgc31ؿ.0>_ +U99FbCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature57be19505c03f92f3847f535e9b114e94kCtEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/callouts/9.png0000644000175000017500000000054510652670541017320 0ustar alialiPNG  IHDR s;bKGD#2IDATx!0 GFVbJ,WX ^YkTb++#{?/Yٗy/j!Rj+~ E#y@!s.gEOr /P8bCtEXtSoftware@(#)ImageMagick 4.2.8 99/08/01 cristy@mystic.es.dupont.com!*tEXtSignature34623e5e4d48310e409b280afe24760214$tEXtPage12x12+0+0m}IENDB`PIDA-0.5.1/docs/html/images/icons/README0000644000175000017500000000034210652670541015467 0ustar alialiReplaced the plain DocBook XSL admonition icons with Jimmac's DocBook icons (http://jimmac.musichall.cz/ikony.php3). I dropped transparency from the Jimmac icons to get round MS IE and FOP PNG incompatibilies. Stuart Rackham PIDA-0.5.1/docs/html/images/icons/caution.png0000644000175000017500000000477210652670541016772 0ustar alialiPNG  IHDR00`nsRGBgAMA a cHRMz&u0`:pQ< xIDATXGiPUGEI9AKQ30STY#E4[!A+qA0F0e" G-ƅXn%a2,AdxeEAp?݇qsu|˗}ު@o筢Q.xSz~#ݻw,]ԩSo{oKDW@@@z`ddi,>z`/G1"_PPPz t1ccc>,)rq9{"SHHH/z߯_?>\ۏ[)2Ver!L{)S>,|+Pz-jWLz] o߾| h*#r6ZZVl^6Qdڷo_'Ҹ8N6rE$2ڜtB?x^MKvҒ"΄ &S@ke 4FO<lqްܺ%ׇ״/W+)22;L]EGGN 2%WF)..51ىC[M+ ڰX g]2u!Ҭ\8Uaڔe*J[I)XOאR G:a3uՈ3yLSie\kk*V]< aƓ%y啤仵,DN 4kXMД(4?zOs i L4_Knhۚ/,21iֹMWh *vE @&Rz@YG.^~ *\ao7[b@Q9HKRߗc 5*UmB-2El&XȀO-`r\$m$3Q)ڐt;Hi363WH;6U?6jS7Sm云K7_HY6Rh>%iSƘ l3ZXXu0"00/b7(.oJh -j⍠!JXdkn$w)w1h$hiO2숢qȡ>zL" 6pZ!+.Ajl&W6҂ `-U%Z־VS9DՔ1:;:1nfH]p>E6m@Cב|Z[s5hVyNVI!S*4*EN4u_iNNTV=&@t!CtQ.9b.\dpMjWJ6!kpN:%I 1dqѺq; jQ;]4 |WpP\?^>a·>~ٳg`"1q 뭬tǚ&}I; bWTm~>2j e.88؏|ZZZvvvQQ10 f3 n޼XZvvv~k SRi,--9r | 8{ 222 N8+xy@ﮩA Edp:a *<3ssX\q@C 3a(LfDbDccuN#}1b@IIIPJJ hrssYʰHDFɷ#R`B9 Yg:!"zzzwQ333`@|F{)+ƈS" ²u[;/ܡ@***X|}}-c4h G 3 SEf#ng^&x 36?~̙3q9x j BI*xēᇐ.n\'0aA,}W 1um8 aZƣ4l#k⑨뻽hs(̤B\fv6z B PObm W,R/}8-^lw 8ÏATedd!2o5DF~)3N=K-2G6%t]S fpv1LIENDB`PIDA-0.5.1/docs/html/images/icons/example.png0000644000175000017500000000446210652670541016757 0ustar alialiPNG  IHDR00`nsRGBgAMA a cHRMz&u0`:pQ<IDATXGyLG^dA V,yYGk@1EGqfcdѴرIFS踋[wmwi<~|>wAA>Ԉr,Ȉ~zƌ~9>>ۃz5C>jk֬'g gϚ*-Q"`(zH/%o>S;wnxA7n(T/./UX8@A~~Dkk>իWe$w PXXP7:.W+S__o]v!hʕCJo߾K!C\ggeggfe ddsӣ Νxb+**9r˗/}AA!Ȝa]}mmVVV&`H RӧVXU͛7~۷s=z~puqe%p755nڴaɓ' ^+C?M`) ,0UPeeʠ7`WWd޽#up@1?=쌡evI =HJJLJLLHO.?TA͛3g(|Vw]zuɒ%˗/gNK|2s… z6J{79 ,qQ11QQ1|m bQ(zZڳgϖ.] _ǩӦݻwpɗ>17_~((A$<9bĤI>P +5:h#/_O:%##Sᆝ۔䲲/^(7550͛7۶m w&L AHHg5pUU,{RW9sڵkXVN.uV{$Kk׮yyy9Y!AP'Hmo7.Vh&-tYRٳglذA8҂Z0%{M۷l٢V==쬭|||0L?͸ ٨ueR Oչ0{>4TIܸqZ4g#88F5f?z__.:XZйU5]pk&RЉ=$XzeݝIllmm )'i\2sL vp r-0ax 55mmiD2u=V7W7tتT*'''ggeݻ7;''/O. ro/O'hRRVVV|^tqAVbsj$yO q \yA/ae b\+2G/Iޝ: !Lr VvR٫meDmaiii.aanxu'.E]N zJ삞ʈ YrIXX 0S^yYXhRx){'Ge꒾S%-,,-ħLЏ5$觩 .<?y|L^I]o>,8z M&đJ31gN$@)0iHA@5}'_EQ[c[-զiK&{sΌ۫ 72Co*01& 0!F/)ݬo~0J@w61"0+t|l>gA^fre@ O%\\2_լF8^wO~}?ɱNCy﵃[Glj.^g%D,\\clPٛT*.HϿ xa\g' < J[]6{FC[fXДwR[:;zxaJ(I76z1=y Ye ;|wI>mca["V+Ik6N^V{fnc !pl`AQk9JJ4KVzv2#dj}f1¶n/%)fl/hHX]斵Ȋ 0dэi22’R;<:,hiZd>4]F0:b=wέ).+A k)I Le*h&Fw?`.#eB<9QXnBFIγ8/M=Hv.U ^-m0!x˥: %&uzƟH>7M-oFKƦKw+l7/m6*u$nVY:kZY*MG];!\;Dmuy+t|"4*Yg05%fG 5@6ccI 5Tl^%z&' p,\c0Ti&D_0gWmpuCVEdmAϊE`=ⳏPT̎N#Z'#-95(iH)Cp{]qakkEqк.Z͔f=܌GsiE_QRej#\`|xIV~V}On+94^ӍJ4fv,2-%R1 Bk^o~џ{~tϳ@ /e%R <hI['jӾ&'w/?=vzzZN5-6EZ w}}'o˴%v\IENDB`PIDA-0.5.1/docs/html/images/icons/important.png0000644000175000017500000000514110652670541017334 0ustar alialiPNG  IHDR00`nsRGBgAMA a cHRMz&u0`:pQ< IDATXG͙iPUGǛ,&$ ~͢MH  # (DȢ2*F7!`Cr qɸ|Xze]o=sgÇO%Sx64z4FcQ˗oݺe4O"#DT99s}|0isم nܸa%2FG4Bi47onmmmoo?!۶Uvr]8ts ;TJYdG >|8P=)9s',%13gdI4fqAݻ XcsIzE)LqqgϞ~eߙa1€Fco_WYyqZ#?WTd?b QWPpyt}s3HF66VACwpXQ^~A`4LkRϷB5t2E9hxF ,fsuU&2:qȑ+Wܽ{פ Trv63"b׮],___ooTq}d|AtSș& yyy"Hܴi,F$''GEExxxt=22RODԒH&߼y8?_S`d6ʳ_ppp^!(b,?K $qc=$#:&Fė5x9f̘(hxtҬ>Jzcp!VQL{ .?zT)---DU0///5Ii]̄dLpsۻw/WN@&Jbc8`n"%$$O>j@x;8Hvq'NyH<f 5K ҪQ锴 :-IcT_Μ:dڴiS,4p@ނ,9rK/ )Hz@pqqtܲe 6VJ,X0b)&&F=jƍJ=)/&<)dy0@O6[=Yts, R6l@( ܃%S]Τ"I޽{f~GMo)Hs(JpCvKQ~3 rZ{Qkݺu˳>>2+b!eH}byV;!Ib==5 "˦+i\\***!tz Uȁx!BhZK%RHd(ݗ/_ FId !j33.oSe_rQZ?jX2#glOOvGMnHL|(=]=ZmOw"#파+WI chR54LS0f}߻vqο ~~_nҁεKN|t4@63- ZZO?w/Z?8+Ky80c;_۷wfjB~ڀ#3?Ĥu0 ivv3kqLq JNNCCHdEetI4\+V3gOȬ PIA6o86laIӎ;+..ΉNGS ,OS/A @Dڂc'3g.\{Ç\@ΎQLRB^L,r͚5+C|Рw w-5I_Ӹts>|?V쑛;cƌy[bΕ8KH打+ n;;w٪UֲeJKKΝRM2%;;;##شL,_PP%̙SRRdP^b CO A)`ANL@yhW__Ī* %>)@$pHccի,gɓ'q{{2/ Xk .QK!dYj!1-{ u -e =K_cfa< 67%CL l! p,yџ~O-d)퀹FE*EW"0y_f hIENDB`PIDA-0.5.1/docs/html/images/icons/next.png0000644000175000017500000000242610652670541016300 0ustar alialiPNG  IHDRĴl;bKGD pHYs ?@"tIME )(IDATxڕُU{kު{vbpƱ'1.T_ .o@#Yh2сapY{ꮪ{}IK:ʯ'2X@uu 4]T1z=k}끷@ H#8VEׁ}1 p5ۉx֤\amkri:ռbrbBM\W1K?ߜЇANnY>FC6scpy]z3/]bHz Xx^D2+})%c!#O1se/poL;~ubٴ1b)( b6tG82ҕ4}FfZS-Ɯ3ѰȦPAƉJ72X#f+@S4 In2:wN+1||t;=qє|Mm>P" Xbr|QrN/pڀd2%#$TK2U ,l̤h)-}qVį5RtcHH캢H%ЁVG̀"welpmY{R4afсaM l,يbcCϪO4;|f@!*΢+EILF}zV/ZaɱL&w͇/EPg) RJ4JF6eKF{Ao$V@X!wG _|>oNQ?  UT\U i[qcex1[0~hY3-jk$95,3ԔN.b5tb͎mIc5.lS`0Y\`|x-83-I 4TvMOγ׈MR5#f QSJCf* ]sl`Kח<߽Am& olduNJD(@ =7 sǁ0d@~ɢH1(lAMxv3eLN%{_9ExIENDB`PIDA-0.5.1/docs/html/images/icons/note.png0000644000175000017500000000525210652670541016267 0ustar alialiPNG  IHDR00`nsRGBgAMA a cHRMz&u0`:pQ< (IDATXG PSWǫ3V.VʨL.Km֎Uk (TA^,CQPy%H!! IHB{{oc_`Dgw;9ù?;ܓ}?T;'ޝv^}s%滊xw ey%}ŋt :(gk|@vD<-W;z@/\O\._Om7?PG|_Ŝ$U[SvJEiU# Ӛ/,V bt90դɁ~i9*0v`#Q \p'C0,He ).5\aBZꫯrh6O'MGyx fY%M/.ݺɣ`) đʳp҄hM:Nȉh׃ZdP%yigϞAP5kfp%UAjI8>CIUN5Yb,9N#׉rGGGZbaʮȴ[6-{jYO΄ɵ5>|n`oh,ѮL4G}9MNS۲e˽{Zz55⊼~/IϦuz;&{G'(/"Ou gDŽuX4U;M5pݺu@֭cq*_VXiV L43>7x@:kM2 C sb5N6o|]7ڰaKZ5ň슢39N + * R@I܀kA⚰~AB?ҝ 23h%c⳦ZgZ!֯_"Wird3a-!^H Q3DeG7t;ˢs5( VqF/_~,kK-beM98q%Ǐ DTd 8oeC5BE#<b0!53F-[EBu*DutmwpW;U';]+g EEg24TM^UWG ktk" <ª.)t\ۓQu5K եeW. RasC얊(fkq ΧheLk׺i{)m5v-|1.(tS/J $Ԝ#R܈ ;"iBbh,XJȦp >ת)ؗAIYEJFpzDJƁ6'F9INF82TG P[}bHЮq A_@֑@yq̩.\Na4]ˋhLkōA9ޫG8PH}%'>m @KBqIS*B0G l~GA'L(ơ+7 uU%LU0ԐH-I]24#阠.  P__^QJ$Z0U][d\j۴%v}]Wm :'QhDdRX+Kh;ͽ 8'CJY$Bo cӖY԰ZY4k" ., 2!0Bt>JHHHK ;-Y`|xCWiו>\fRPnVU=G2P[u;`at( Pyq(vZ:YBLc6#+J." rQ#56]@M NZXK/c5k8ۣ5w02Iaa&U + u`=liM:K7k~Lr(D(=]E* F0kjLJd8ħV! ]߹sg C㇦'N +Gi5CtA&RVm& (]_?/":'Ě{'Ea0˜NԀiҥ(q U_PwY̔cwp;}@P2e.Vp ,XԀە+W%91ΟH`c\x1[/ٹЧŞ(+Sܜs'C>?!%̻h"??*>/D"Qss3 6444**Μ9pܰk\>>>G 2Lܿ Aӯr4M`@B׏b!anSa,X,U$I=п;@лj% [PB ,*㱀1TApдF'F?O~̽s_Q,#IENDB`PIDA-0.5.1/docs/html/images/icons/prev.png0000644000175000017500000000250410652670541016273 0ustar alialiPNG  IHDRĴl;bKGD pHYs ,tIME 4ٸIDATxڍۋ\U{Oթ[Wv3t0J 3 C-,B<"/ DP$q=L NtWwUr.{IO4`}ǷYk)~JG $")@]6]wa'A(L;#M`^Ò{R? .y9NMx|szͲjFSFoT*;A:OdٷCx$opTN吓!__'M{ÓwΏ|z>sqt}JhDeNõLVj*˾o깲3 >6yw[1榓|tζ-Q$[ ٔQa fsp{rەK* |O=R$i݈\DU8{KԈ9=H!-=mU,@Y =WuA9)t"2!͞el?hvő= K}(@'|eɺwa%*~ieԝ 5;|rv_~1x_\+PI4r ll|.!lo8p}X\ 1 $i[ƶk<59R]xΆs߃7#cmQd6FILҍԭr^nvnUߏK 򗱴g,2\Zxl[XeK2`,B^Hڄծ8Yw-?$*']r!i?I&i/$mh&U!7o?٧>~9jU~Uz(;;*P$0 bk#v|MF' #$\Arym֙‰\BϔL.3ݏf)֬]PoGxsƁV-Wg&sNj`Rěj*ʩF]fεNjtt#]\ Db=Nl2_8qZT*/ȑ#G4>_GkUCۚr2^t44zA+3*qj%Hr^ns@T*{/{hRhIENDB`PIDA-0.5.1/docs/html/images/icons/tip.png0000644000175000017500000000505210652670541016114 0ustar alialiPNG  IHDR00`nsRGBgAMA a cHRMz&u0`:pQ< IDATXGXmPSWN3δ2(kU"7@;  !BB7AOŠNYNYն춊ֲu٥ ": ]}n\%@w{\/}}ŋ/z.z(4T d?v]?]xN2XrffٳgOb6l/wڕuԩOMMa<»dkY+zhhHծ_ V@+1.k3t֝088"79b%z!֭STccc .-q Q0`"4Qim.(ifGHKCEEiBvQiiJxZ=<<̀yRޟrJPP{[KAS [y,i($ @{EP5Ll DHm2{Jb:tww*̶44]]]GlqJ ӌ,^n)FR.E@%/V(g }}}FFF 1m_h4ҌbD3F^(V [UɨܗYUZ؈-3 FX_>Vqiмm ibbTJNffUǩk8Rx 3 d4$/Ga.-Q'?ao[Nwb%V]rho޼ =a~ <B)hf &Npgwtzf&CϩKȭ˪ JoFag1 2Vco҄MsnG_yDu$"Ew={bB#pL@_X^&xmו;OM30` Bn伃 ٵHxn݂g.& qËնfArawlv|t}$Lܜ:)MPj~c8_vZ{CI Bڴj%Q0A 4tn*ξ7N=?^wBqqCФswwAIwt[z)&[S^fƝqmhŖHٖph%e9sf34| cEwi/D5\fraXN?kݽ7$ĄFh|rJdj^&(|c؜? DIgEnsՕĂYpP# `RXۙh [M首{].{$LU ӀcӉo Oz D)FaƅFX&2d\tΝ;$X |&66]_O^rp}nPWꙞ˝TЄ0RJ__tCETJWݦѸ[plk/7nD(VQQ.)>",lOC===4 =0 W;{nSSANΗ _ܞ1#@#yh)481.44nJf\M6v槟@=aJ(QCVr,IS%ۼyw ADgφiQA +wg>}9LshdMGdXf͹s_ũI-C}b aT諯oр{&40)@#+nXq/zeT{ PZ[+eu`Jxr&a! MqUىDuG\;;;U1)Щ`?+HkBm|,1n1e't{Iz{{UK+˂Ѱ?Uڝ$)blNNG 7n:Yc 7+r_LFžQ7sTdEXݑOo+)^ #vnQ;!owG|R{6d5Y?;ΉVi!xzSV @72G>~ժUG=V\x`B4L˵u֞pCX,k֬l~mD@"ƍVsЀÇCCC `C"2M㒒D+|f:ti v0 $u PnVt 0h4:F?^ZTN@o)1###xuɓDѨY.\vm]]LaH4ð/$ENIdɒ @"͛e,ݻWIĪ(RS% 1@1͉F0iiD!pw׮P{! m۶4Ҷ/ˈBueF;O,Q&9pR2u~ٲe~Jrr7)OI)!buh^!z&YuG~J4- DgJǪcn0ZΊ+d~ Bo߾=4R9Ot^XY (97ל9Q25o"±f;۷ˀL&,vA~o)FH)*O<-mT{20o~0ɣ7Vw>TƄڍy؉%ꫯ|VkCB>t2isj(S5V*0?ԯn:1}nԾsȄ׋`0̞=[Rйٶ߀IVs_3nUHPvJpX8MK2|AeUeV_u՚ҟhm|"Ɩ c@]zh<" jאN{u%Y@7''2<+?=wn6~h}j8vD| tI|%9ZMS6(;^t< @ȧke9!i7j;$1DkIŤ~-M׉,Ρ>MK屔䶛]7-<93;m ''n'D% ' "i"Vܬd֩:{ZKNgu^U7UV(:8DIzTIku E "hY0i\oTRq^yb}EQqVfgEsT6ޤCb1eF4NI:A%߈et8^_x>q9&+cͤ[5o~DH6ZDاsg. ^ ~~{80 .l?6;ZXZEn6s7"jWunɮwY߃ߋ͛{-w]Xb#a%%!L~G\[q;a_@M6v*$.Ӿޛ6|.დco߮/^hR"c!kzL\׉@Lj$m53\vttpUw5{^sL@`d~"!n$kE l"!eWDӱjmDQAEl^~Ҷoؕ0* 8F#&EiUUWϧ͌7/C$bܑd34NxgҨj\.jmm4>7bc5 ]>l驅Ed DMa q2yY=XV}bjִF Rq XS^Fم+_#Lfkd x?p! X;Z7xD&6C#M(Nmah۴ ڑ:}.h9ѾѽŋCipJ0cƌtlA tҥ0̝ +Y}z~88D"'BET`T'H%NKKS(`I_Š ;je0ΙelנA/R, '&Lфđ gΝC}؁$Zyy̙3#f`Y~)SL<9333bĄ:^SPP½xXC!O"CdB-Pq@"½x31 ?VRIENDB`PIDA-0.5.1/docs/html/handbook.html0000644000175000017500000015046410652670542014716 0ustar aliali PIDA Handbook

PIDA Handbook

Table of Contents

PIDA is an IDE (integrated development environment). PIDA is different from other IDEs in that it will use the tools that are already available rather than attempting to reinvent each one. PIDA is written in Python with the PyGTK toolkit, and although it is designed to be used to program in any language, PIDA has fancy Python IDE features.

This document describes PIDA features and usage. It is aimed at providing help for new comers and advanced users. Developer documentation is available in other sources for those who want to hack PIDA.

Caution Since PIDA is still being actively worked on, this manual is still incomplete and will grow with time.
<tito> aa_: PIDA is an acronym ? <Zapek> PIDA Is Da Acronym <mnemoc> Python Integrated Developmenet Architecture iirc <aa_> Zapek: I like it
— #pida on freenode.net

1. Introduction

There are many IDE around, and some are very good. But lots of them are also closed in the sense that they are limited in terms of extensibility or communication with other tools. On the other hand, some of you may want to change your editor for anything else, even if you have to rely on external tools to complete its features.

PIDA was designed with these problems in mind. PIDA's motto is to reuse the tools that proved to be useful and solid, and to provide the glue for them. PIDA is written in Python with PyGTK, is easily extensible through plug-ins and can embed any editor provided someone writes an adapter for it.

PIDA has a number of unique features, such as

  • Embedding Vim, Emacs or any editor.
    [Of course, the editor has to provide a way to communicate with for external programs. Moo, Scite, and probably GEdit could be candidates]

  • Using any version control system.

2. Requirements

Because of the nature of PIDA there are a number of tools that may optionally be used, but are not absolutely required. The list of requirements is therefore split into absolute and relative requirements.

2.1. Absolute Requirements

Python

Python is the programming language PIDA is written in. PIDA requires any version of Python greater than or equal to 2.4. Python is available from the `Python Web Site`_ or more likely packaged for your distribution. Most desktop Linuxes come with Python preinstalled.

PyGTK

PyGTK are the Python bindings for the GTK toolkit. Note that you will also need to have GTK installed for them to work. These are available from the `PyGTK Web Site` and the `GTK Web Site` or more likely packaged by your Linux distribution.

Kiwi

Kiwi is a helper library for PyGTK. It was decided a while ago that there should not be a duplication in effort in creating common widgets and patterns withing PyGTK programs. For this reason common things exist in Kiwi, and PIDA developers contribute fixes back upstream to Kiwi. Kiwi is available in the contrib directory of the source code. It is recommended to use this version if at all possible. It contains no changes from the original, but it is ensured to have all the latest fixes.

VTE

VTE is a GTK terminal widget that is used by gnome-terminal. PIDA uses this for many things, and since it is in most distributions that Gnome is in, we have made it an absolute requirement.

2.2. Compilation Requirements

Python development headers

These are usually available in your distribution as python-dev. They are the headers required for building python extensions.

PyGTK development headers

These are required to build the external moo_stub extension, and are usually available in your distribution as pygtk-dev or pygtk-devel etc. packages.

3. Installation

PIDA is still not considered finalised by its authors. The most recent version lies in SVN repository, but milestones are available though to provide a snapshot of the development to the less adventurers.

3.1. Compilation from sources

The source code comes with a standard Python installation method.

Build

The build step is necessary even when running from source so as to ensure that the extensions are built.

python setup.py build
Note
Debian Users

Due to the location of headers on Debian, users must first:

export PKG_CONFIG_PATH=/usr/lib/pkgconfig/python2
Install

Installation is the recommended method of running PIDA. Running from source should be reserved for people who know what they are doing.

python setup.py install
Note

you may need to use sudo or equivalent to obtain superuser access if you are installing to a global location.

Run from source

First copy the moo_stub.so (that was built in the build stage) from the build/ directory somewhere into PYTHONPATH or the working directory. Then execute:

Note

Running from source is generally reserved for developers of PIDA, or those people who really know what they are doing. It is very useful to be able to make a change and test it immediately. It is not recommended to use this as a general running method.

run-pida.sh

3.2. Obtaining the development source code

The current development version is hosted on Google Code Subversion repository. You can use the following command to anonymously check the latest source code.

svn checkout http://pida.googlecode.com/svn/trunk/ pida

PIDA can be executed from its source directory as outlined above.

3.3. Distribution packages

Though there is still a long way before PIDA can be considered mature, it is already packaged by several Linux and BSD(FIXME?) distributions. Use the guidelines of your distribution to install or remove PIDA from your system.

Table: Known distributions that provide PIDA
Distribution Distribution version PIDA version
Debian Etch (stable) 0.3.1
Debian Lenny (testing) 0.4.4
Debian Sid (unstable) 0.4.4
Gentoo FIXME. FIXME.
Ubuntu Breezy Badger 0.2.2
Ubuntu Dapper Drake 0.2.2
Ubuntu Edgy Eft 0.3.1
Ubuntu Feisty Fawn 0.3.1
Ubuntu Gutsy 0.4.4
Important There are chances that the version packaged is a bit outdated. Please consider trying to install the most recent version before reporting a bug. You can either compile pida from sources or try to use a package prepared for a more recent version of your distribution.

3.4. MS Windows

FIXME.

Some pointers on how to install PIDA dependencies can be found on http://code.google.com/p/pida/wiki/WindowsInstallation

3.5. Mac OS X

FIXME.

4. Getting started

FIXME.

4.1. First run wizard

Note This feature is planned for a future version of PIDA, which is still undetermined yet.

4.2. The PIDA window

Like any other IDE, PIDA provides in one window all the tools you need to edit your files, compile your programs, invoke external tools, explore a project space or a file hierarchy, and so on. The PIDA window is organized with a menu, a toolbar, multiple views and a status bar. Many of these elements are optional and can be hidden or displayed at will.

4.2.1. The menu bar

File

This menu offers all file related operations, like creating, saving, or closing a document, but also all version control operations. PIDA also provides sessions management, and the File menu permits to save the current session or load a previous one.

Edit

This menu serves two purposes. First, it provides facilities to search documents throughout a project, or directories. But PIDA preferences and shortcuts settings are also modifiable from here.

Project

This menu provides version control at project level. From there, it is also possible to modify the properties of a project and to start the configured controllers.

Tools

Additional utilities, like a terminal and a Python shell for PIDA introspection.

View

The PIDA window can be customized from there, displaying or hiding special views or elements like the menu bar or the tool bar. This menu also provides shortcuts to access quickly the most important views of the window, like the file manager.

Help

Provides only the credits window for now.

4.2.2. The status bar

The status bar provides live information on

  • The current project.

  • The current directory browsed in the file manager.

  • Information on t file currently edited, like its name, encoding and size.

4.2.3. The editor

The editor is the core element of PIDA. All other views only provide utilities to fill the missing features of the editor, or integrate important accessories — like a debugger — or give a quick access to external tools — like a terminal. The editor is also the central view of PIDA. All other views can be moved around it (see See Views).

PIDA can support any editor. Editor shortcuts and features directly depend on what editor you prefer. It is possible that some features of the chosen editor and PIDA features overlap. In this case, both can be used, but the feature implemented by PIDA will certainly provide better integration with the other tools of the IDE.

4.2.4. Views

All elements in the PIDA window, except the editor, the menu bar, the toolbar and status bar, can be moved (remember that the menu bar and the toolbar can be hidden though).

FIXME: must choose carefully the vocabulary for elements of the views and keep them consistent.

4.3. PIDA configuration

FIXME: gconf, .pida

5. Core services

FIXME.

5.1. Editor

5.1.1. Vim

FIXME.

5.1.2. Emacs

FIXME.

5.2. File Manager

FIXME.

5.3. Project Manager

FIXME.

5.4. Terminal

FIXME.

5.5. Version Control

FIXME.

5.6. Preferences

FIXME.

6. Plug-ins

FIXME.

6.1. Bookmark

Manage bookmark (files, directories…)

6.2. Checklist

FIXME.

6.3. GTags

GNU Global Integration » Build global index, search through database

6.4. Library

FIXME.

6.5. Man

Search and browse man page

6.6. PasteBin

Send code to a pastebin service

6.7. Python

Show class/function from python file, and show compilation errors

6.8. RFC

Download RFC index, search and view RFC pages inside PIDA

6.9. TODO

Manage a personal todo list per project

6.10. Trac

View bugs from Trac project inside PIDA

7. PIDA Service Authoring Guide

PIDA has a very general concept of services (you might call them plugins in another application). In general, a service is able to define any PIDA function, that is anything PIDA can do, a service can do it too.

PIDA is essentially a bunch of services bound together by a Boss. The services are discovered from service directories and loaded by a Service Manager for the Boss.

7.1. Service Overview

A service is comprised of a directory on the file system. This directory is a Python package with data.

The structure of this directory is like so for a service named "myservice":

    myservice/
        __init__.py
        myservice.py
        service.pida
        test_myservice.py
        data/
        glade/
        pixmaps/
        uidef/
            myservice.xml

7.2. Creating a starter service

As will be seen, there is a large amount of boiler plate involved in creating a service, and so we have provided the creator.py script in the tools/ directory of the source distribution. You should run this with the single argument service and you will be asked a few questions to complete the process:

Example: Using tools/creator.py
ali@book:~/working/pida$ python tools/creator.py service
Please enter the path for the service [/home/ali/working/pida/pida/services]: /tmp
Please enter the Service name: MyService
Creating service my service in /tmp

7.3. Individual Components

7.3.1. myservice.py

This is the file containing the Python code for the service. It is a Python module and should contain an attribute `Service`, which is the Class which will be instantiated as the service.

The service class has a number of class attributes which describe its behaviour. These behaviours are:

  • Configuration

  • Commands

  • Events

  • Features

  • Actions

Configuration

This is the global configuration options for the service.

Commands

Commands are the external interface for the service. They can be called by any other service, and this decoupling is cleaner than expecting, and calling an actual method on a service.

Events

Events are an asynchronous call back for the service. Any other service can subscribe to an event explicitly, and by subscribing is notified when an event occurs.

Features

Features are behaviours that a service expects other services to provide for it. If this makes no sense, imagine a situation in which a file-manager service expects any service to subscribe to its right-click menu on a file. In this way, the actions provided on that right-click menu are decentralized from the menu itself, and can be provided anywhere. This is very similar to a classical (e.g. Trac) extension point.

Actions

Actions are gtk.Actions and are used in the user interface. An action maps directly to a single toolbar and menu action, and contains the necessary information to create this user interface item from it, including label, stock image etc.

7.3.2. Other files and directories

init.py

This file is required so that Python recognises the directory as a legitimate Python package.

service.pida

This empty file is just present to identify the package as a PIDA service.

data/

This directory should contain any data files for the service that are not included in the other resource directories.

glade/

This directory contains the glade files for the service's views. Although views can be created using Python-only, it is recommended for more detailed plugin views that they use glade.

pixmaps/

This directory should contain any custom pixmaps for the service. These can be used in any way.

uidef/

This directory should contain the UI Definition XML files for the service. These are gtk.UIManager XML files, and define the menu bar and toolbar items for the service. The file myservice.xml is automatically loaded by PIDA, but others can exist in this directory and could be used to populate popup menus or to be further merged with the standard UI defnition.

7.4. Service Options

Options are currently stored in the GConf database. They are registered at activation time of the service. Each service has its own directory in the GConf database at /apps/pida/service_name. On registering the options, if they do not exist, they are set to the default value.

Service options are defined in the service's OptionsConfig. This class should be the options_config attribute of the service class, and should subclass pida.options.OptionsConfig.

The OptionsConfig has a method named create_options, which is called on service activation. This method should contain the calls to create_option to create the options. The signature for create_option is:

create_option(name, label, type, default, documentation)

For example:

class MyServiceOptions(OptionsConfig):

    def create_options(self):
        self.create_option(
            'myoption',
            'myoption label',
            OTypeString,
            'default_value',
            'A string describing the option',
        )


class MyService(Service):

    options_config = MyServiceOptions

7.5. Service Commands

Commands are the external interface for a service. This interface is specifically provided to other services for use of service activities.

7.5.1. Defining Commands

Commands are defined as methods on the commands_config attribute of the Service class. This attribute should reference a subclass of pida.core.commands.CommandsConfig class. Any method defined on that class will be available as a command on the service.

7.5.2. Calling service commands

Commands are called on a service using the cmd method of a service. Calling commands on other services must be performed through the Boss' cmd method which takes as an additional parameter then name of the target service.

For example, execute a shell from a service:

self.boss.cmd(
    'commander',        (1)
    'execute_shell',    (2)
)
  1. The target service name

  2. The target service command

7.5.3. Using arguments on service commands

All arguments to service commands must be passed as keyword arguments. Because of this, they can be passed in any order after the servicename, and commandname parameters.

For example, execute a shell from a service starting in an explicit directory:

self.boss.cmd(
    'commander',
    'execute_shell',
    cwd = '/',
)

7.6. Service Events

The events are asynchronous call back for the service. So any other service can subscribe its call back to an event, and it will get called back once the event occurs.

7.6.1. Events definition

To create a new event, like earlier, you just need to create a new class that you can call MyServiceEvents, and you bind it to your MyService class doing events_config = MyServiceEvents.

class MyServiceEvents(EventsConfig):
    def create_events(self):
        [...]
    def subscribe_foreign_events(self):
        [...]

class MyService(Service):
    events_config = MyServiceEvents

So in that example code, you can notice the two methods you need to implement to manage your own events. You have to define in create_events all the events your service is about to use, and in subscribe_foreign_events all the events from other services your service needs.

7.6.2. Create your own new events

So, once you have your EventsConfig ready, you need to implement the create_events method so you can have your own new events. Three steps are needed to create an event :

(1) You call self.create_event() on the event name
(2) You subscribe a new callback to the event you just made
(3) You implement the new event's callback so it acts when it is emitted.
    def create_events(self):
        self.create_event('myevent')                     # (1)
        self.subscribe_event('myevent', self.on_myevent) # (2)
        self.create_event('my_foreign_event')

    def on_myevent(self,param=None):                     # (3)
        print 'myevent receipt'
        if param != None:
            print 'with param:', param

7.6.3. Subscribe to other services' events

We have seen how we can bind callbacks to events you created in your own service. But you often need to interact with other services as well. To do so, you need to implement the subscribe_foreign_events() method the following way :

    def subscribe_foreign_events(self):
        self.subscribe_foreign_event('editor', 'started',
                                     self.on_editor_startup)

for each event you want to bind a call back, you need to call the subscribe_foreign_event() method. In the example above, when the editor service launches the started event, self.on_editor_startup() gets called.

self.subscribe_foreign_event('SERVICE_NAME', 'EVENT_NAME', CALLBACK_FUNCTION)

where SERVICE_NAME is the destination service, EVENT_NAME the event to bind to, CALLBACK_FUNCTION the function to be called when the event is emitted.

Now suppose you want to give other services' programmers an event of your own service. To do so, you need to call create_event() in create_events() with the name of your event (ie see my_foreign_event above).

Then in the foreign service, in the subscribe_foreign_events() method you just need to subscribe to the event:

    def subscribe_foreign_events(self):
        self.subscribe_foreign_event('myservice', 'my_foreign_event',
                                     self.on_myservice_foreign_event)

and finally define your callback.

7.6.4. Events emition

Now you have defined and bound all your events in your service and all you need is to emit them when you need them to be executed. Well, it's fairly simple, just call the emit() method :

    [...]
    self.emit('myevent')
    self.emit('myevent', param='hello world')
    self.emit('my_foreign_event')
    [...]
    self.get_service('myservice').emit('myevent')
    self.get_service('myservice').emit('myevent', param='hello from some other place')
    self.get_service('myservice').emit('my_foreign_event')
    [...]

As you can see in the examples above, emit can be used in different contexts and with or without parameters. As a rule, every event defined can be called using the emit() method either from your own service (ie in MyService) or from someone else's service (then you use get_service().emit()).

If your callback function needs parameters, you need to give the options to the emit method. You can also use, as in the above example, non-mandatory parameters.

7.7. Service Views

Service views are almost anything that appears visually in PIDA (apart from the main toolbar and menubar). All of these views belong to a service.

7.7.1. Creating Views

Views may be designed in Glade3, or in pure [PyGTK]. Each method of view creation has its advantages and disadvantages, and these are discussed below.

7.7.2. Glade3 Views

Views created with Glade3 have the following advantages:

  • Better maintainability

  • Automatic signal callback connection

The glade-file itself should be places in the directory glade/ in the service directory, and should be named appropriately so as not to conflict with any other service glade file. The extension .glade is preferred. So, for example a well named glade file is project-properties-editor.glade.

This glade file is used by subclassing pida.ui.views.PidaGladeView and setting the gladefile attribute on the class, for example for the glade file above:

from pida.ui.views import PidaGladeView

class ProjectPropertiesView(PidaGladeView):

    gladefile = 'project-properties-editor'
Note The glade file attribute omits the extension part of the file name.

The glade-file should contain a single top-level container (usually a gtk.Window), and this must have the same name as the glade file (without extension.

The widget inside this container will be taken out of the Window and incorporated into Pida's view.

All widgets in the glade view, are attached to the view instances namespace, so that they can be accessed from the instance, for example if there is a text entry called name_entry, the attribute self.name_entry or my_view.name_entry would reference that entry.

Signals of items in the glade view are automatically connected if you provide the correct method on the glade view. These methods are named as on_<widget_name>__<signal_name>. For example, if there is a button on the view called close_button, and you wish to connect to it's clicked signal, you would provide the following method in order to automatically connect the signal for the widget:

def on_close_button__clicked(self, button):
    print '%s was clicked!' % button

7.7.3. Pure PyGTK Views

These views should subclass pida.ui.views.PidaView and should create the necessary widgets by overriding the create_ui method. The widgets can be added to the view by using the view.add_main_widget(widget, expand=True, fill=True). The widgets will be added to the top-level VBox in the view.

There is no signal autoconnection, and widgets behave exactly as if they had been created with PyGTK in any other circumstance.

7.7.4. Instantiating views

The service can instantiate its views at any time. They should pass the instance of the service as the first parameter to the View constructor. The service will then be stored as the svc attribute on the view.

7.7.5. Adding Views to PIDA

Views are displayed at runtime by calling the window service's command add_view. The required paned must be passed as well as the view itself.

The paned attribute to the command should be one of:

  • Buffer

  • Plugin

  • Terminal

The buffer paned is the left sidebar, the plugin paned is the right sidebar, and the terminal paned is the bottom bar. In general the guidelines for which paned to add views to are:

  • Permanent views should be added to the Buffer paned

  • Views relating to the current document should be added to the Buffer or Plugin paned

  • Configuration or property views should be added to the Plugin paned

  • Multiple view (eg terminal emulators, diffs, and web browsers), or those with a lot of data should be added to the Terminal paned.

An example of adding a view of type MyServiceView to the Terminal paned is as follows:

# Override the start method as a hook to when the service starts
def start(self):
    view = MyServiceView(self)
    self.boss.cmd('window', 'add_view', paned='Terminal', view=view)

Numerous other examples are available in almost every service in pida.services.

7.7.6. View icons and labels

View icons (the image displayed on the paned button) are referred to by their stock ID, and set as a class attribute on the view icon_name. Similarly, the text associating the icon is set as a class attribute on the view called 'label_text`.

Additionally, an icon_name and/or a label_text attribute can be passed to the view constructor, and these will be displayed as the view's label and icon when it is added to the PIDA main view.

7.8. Using custom pixmaps in services

Any pixmap placed in the pixmaps directory in the service (myservice/pixmaps) will automatically be added as a stock image and can be used by the service using its name (without extension) for View icons or for gtk.Buttons or gtk.Images or any other widget which take a stock_id as an argument.

7.9. Links

  1. [PyGTK] PyGTK Website

8. PIDA Plugin Authoring Guide

PIDA plugins are very much identical to Services. Anything you can do in a Service, you can also do in a Plugin. There are however a few minor differences, based on the facts that:

  1. Plugins can be loaded and unlaoded at runtime

  2. Plugins can be published on the PIDA community and installed

PIDA uses Plugins for all non-core funcitonality, and we have been quite strict about this, so that we can maintain a different release schedule for the core, and individual plugins. Because of this, Plugins which you might expect to find in a standard IDE or a standard Python IDE must be installed. Fortunately this is a matter of a single click.

8.1. The service.pida file

The service.pida file in Plugins is in the ini format with themetadata under the [plugin] tag. It contains metadata that is used by the installer and by the community website. This metadata includes:

Table: Service.pida metadata
Attribute Description
plugin * Technical name of plugin (only [a-z0-9_] name)
name * Long name of plugin
version * Version of plugin
author * Name of author <email>
require_pida * Version of PIDA
category * Category of plugins
depends List of dependencies
lastupdate Date of last update
website Website of plugin

* These fields are mandatory

Example: An example service.pida file
[plugin]
plugin = snippets
name = Snippets
author = Ali Afshar <aafshar@gmail.com>
version = 0.1
require_pida = 0.5
depends = ""
category = code
description = Snippets: automatically enter repetitive text

8.2. Publishing Plugins

Plugin publishing can be done from the safety of the PIDA user interface. First you will need to create the service directory (our advice is to use the tools/creator.py script as outlined in the service authoring guide above). Once your plugin is created, in PIDA, select Plugin Manager from tools, and select the Publish tab. Enter the directory containing the service, and your username and password for the pida community website. You will be given a chance to edit the service metadata in the user interface. When you are happy, select the Make package and upload button to complete the process.

Note The plugin will be placed on standby, and approved by one of the developers. Once it is approved, anyone can download it.

8.3. Limitations to plugins

The single limitation to a plugin is that it should not (although it can) provide events that can be subscribed to. This is because a plugin can be loaded and unloaded at runtime, and so plugins that depend on other plugins' events will cease to function.

We say "should not" rather than "must not" because you can actually do what you like. We have a strong philosophy about not getting in your way, but be warned that you have been warned!

9. PIDA Coding Style Guidelines

First read [PEP8] (the PEP on how to write readable Python code). The PEP gives a number of good insights. The PEP gives a few options on things, and I shall try to clarify what I prefer here. Where this document differs from PEP8_, you should use what is presented here, unless you are a zealot in which case you should listen to the Python people (who are cleverer than me anyway). Also read PEP20_ while you are at it.

9.1. Indenting

4 Spaces, no tabs ever ever. This is not negotiable. Emacs users please check your settings, somehow tabs creep into emacs-written code.

9.2. Line Width

79 characters, perhaps 78 to be safe. This is negotiable, and there are times when 83 character lines are acceptable. You can be the judge. I am not sure many people use 80-character terminals these days, so we can be a bit less hard-line than the PEP.

You can split lines however you wish. I personally use 3 different forms of splitting depending on the purpose.

Long lists, dicts, or many paramteres to a function:

service_base_classes =  [
    OptionsMixin,
    commands_mixin,
    events_mixin,
    bindings_mixin,
]

Single extra bit:

def really_long_method_or_function_name(first_parameter, second_paramater,
    third_parameter)

Or:

def really_long_method_or_function_name(first_parameter, second_paramater,
                                        third_parameter)

It all depends on the use at the time, and we should remember to keep it readable.

9.3. Blank Lines

As [PEP8] for 2 lines between top-level classes and functions, with one line between methods.

Extra blank line "to indicate logical blocks" should be avoided at all costs in my opinion. Real logical blocks should be used to indicate logical blocks! If you have to do this, a comment is better than a blank line.

9.4. Imports

Only import the function or class you want to use, for example:

from pida.ui.views import PidaView, BaseView

There are a few common exceptions like:

import gtk

Multiple top-level imports are fine too if you like, but best grouped by where they are comming from:

import os, sys
import gtk, gobject, pango

Remember to import in this order:

  1. standard library imports

  2. related third party imports

  3. PIDA application/library specific imports

9.5. Whitespace

Yes:

def foo(blah, baz):

No:

def foo ( blah , baz ):

def foo(blah,baz):

(that space after a comma is basic punctuation)

[PEP8] has oodles on this.

9.6. Docstrings

I like having the triple quotes as doubles, and for them to be on empty lines, like so:

def foo():
    """
    This is the single-line docstring
    """

Docstrings are plain nice, so please try to use them for all functions. I am guilty of being lazy, so I can't blame anyone. Also we use API generation which uses these doc strings, so it all helps.

We use Pydoctor_ with ReStructured text directives for API generation, so I guess you should look them up too.

9.7. Strings

Single quoted, unless you need single quotes in them, in which case use double quotes:

my_string = 'I am a banana'
my_other_string = "I am a banana's uncle"

9.8. Naming

  • Modules as lowercase single words with no underscores, except test modules which should start with test_.

  • Functions as lower_case_with_underscores.

  • Classes is CamelCase. (Note: I hate camel case, but it is useful, even in Python to know the difference between a class and a function. Why? You can subclass a class.)

  • Module-level constants all in UPPERCASE_WITH_UNDERSCORES.

9.9. Conditional blocks

This is fine:

if blah:
    baz = 1
else:
    baz = 2

And better than:

    baz = 2
    if blah:
        baz = 1

But I am not going to argue, needs can force you into a certain style. Remember, readability is key.

9.10. Magic

I hate magic, perhaps because I am dumb. I am really wary of using some of Python's shoot-me-in-the-foot techniques because I have to maintain the code, so. I have made these mistakes myself, and have (hopefully learned from the mistakes. So:

Meta classes

Never! I have yet to see a use-case for metaclasses which did not relate to perverting some other library or external class. I am happy to be enlightened.

Decorators

Make perfect sense in some cases, but have the danger of being over used, so please think carefully whether you are using them to decorate behaviour, or just using them for the sake of it.

Inner classes

I have yet to see a use-case that requires these.

9.11. Bibliography

  1. [PEP8] Python Enhancement Proposal 8

  2. [PEP20] Python Enhancement Proposal 20

  3. [Pydoctor] Pydoctor Web Site

PIDA-0.5.1/docs/txt/0002755000175000017500000000000010652671501012102 5ustar alialiPIDA-0.5.1/docs/txt/handbook.txt0000644000175000017500000011352610652670537014446 0ustar aliali= PIDA Handbook ////////////////////////////////////////////////////////////////////////////// Please note here typo or documentation format rules that should be consistent throughout the whole document. - PIDA should always be written PIDA for now. A find and replace will change this if necessary. - The attitude of the writer (using first person, using 'you' or 'the user' to speak about the reader...) should be consistent throughout the whole document. It is not decided what this attitude shall be yet. - Anchors should be explicit and composed using the hierarchy of the document ie: services_project_manager for Project Manager title in Services. ////////////////////////////////////////////////////////////////////////////// PIDA is an IDE (integrated development environment). PIDA is different from other IDEs in that it will use the tools that are already available rather than attempting to reinvent each one. PIDA is written in Python with the PyGTK toolkit, and although it is designed to be used to program in any language, PIDA has fancy Python IDE features. This document describes PIDA features and usage. It is aimed at providing help for new comers and advanced users. Developer documentation is available in other sources for those who want to hack PIDA. CAUTION: Since PIDA is still being actively worked on, this manual is still incomplete and will grow with time. [#pida on freenode.net] _________________________________________________________ [verse] ......................................................... aa_: PIDA is an acronym ? PIDA Is Da Acronym Python Integrated Developmenet Architecture iirc Zapek: I like it ......................................................... _________________________________________________________ [[introduction]] Introduction ------------ There are many IDE around, and some are very good. But lots of them are also _closed_ in the sense that they are limited in terms of extensibility or communication with other tools. On the other hand, some of you may want to change your editor for anything else, even if you have to rely on external tools to complete its features. PIDA was designed with these problems in mind. PIDA's _motto_ is to reuse the tools that proved to be useful and solid, and to provide the glue for them. PIDA is written in Python with *PyGTK*, is easily extensible through plug-ins and can embed any editor provided someone writes an adapter for it. PIDA has a number of unique features, such as - Embedding Vim, Emacs or any editor. footnote:[Of course, the editor has to provide a way to communicate with for external programs. *Moo*, *Scite*, and probably *GEdit* could be candidates] - Using any version control system. [[requirements]] == Requirements Because of the nature of PIDA there are a number of tools that may optionally be used, but are not absolutely required. The list of requirements is therefore split into absolute and relative requirements. === Absolute Requirements .Python Python is the programming language PIDA is written in. PIDA requires any version of Python greater than or equal to 2.4. Python is available from the `Python Web Site`_ or more likely packaged for your distribution. Most desktop Linuxes come with Python preinstalled. .PyGTK PyGTK are the Python bindings for the GTK toolkit. Note that you will also need to have GTK installed for them to work. These are available from the `PyGTK Web Site`_ and the `GTK Web Site`_ or more likely packaged by your Linux distribution. .Kiwi Kiwi is a helper library for PyGTK. It was decided a while ago that there should not be a duplication in effort in creating common widgets and patterns withing PyGTK programs. For this reason common things exist in Kiwi, and PIDA developers contribute fixes back upstream to Kiwi. Kiwi is available in the contrib directory of the source code. It is recommended to use this version if at all possible. It contains no changes from the original, but it is ensured to have all the latest fixes. .VTE VTE is a GTK terminal widget that is used by gnome-terminal. PIDA uses this for many things, and since it is in most distributions that Gnome is in, we have made it an absolute requirement. === Compilation Requirements .Python development headers These are usually available in your distribution as python-dev. They are the headers required for building python extensions. .PyGTK development headers These are required to build the external moo_stub extension, and are usually available in your distribution as pygtk-dev or pygtk-devel etc. packages. [[installation]] == Installation PIDA is still not considered finalised by its authors. The most recent version lies in SVN repository, but milestones are available though to provide a snapshot of the development to the less adventurers. [[installation_compilation]] === Compilation from sources The source code comes with a standard Python installation method. .Build The build step is necessary even when running from source so as to ensure that the extensions are built. ------------------------------------------- python setup.py build ------------------------------------------- [NOTE] .Debian Users ============================================================================= Due to the location of headers on Debian, users must first: ----------------------------------------------------------------------------- export PKG_CONFIG_PATH=/usr/lib/pkgconfig/python2 ----------------------------------------------------------------------------- ============================================================================= .Install Installation is the recommended method of running PIDA. Running from source should be reserved for people who know what they are doing. ------------------------------------------------------------------------------ python setup.py install ------------------------------------------------------------------------------ [NOTE] ============================================================================== you may need to use sudo or equivalent to obtain superuser access if you are installing to a global location. ============================================================================== .Run from source First copy the moo_stub.so (that was built in the build stage) from the build/ directory somewhere into PYTHONPATH or the working directory. Then execute: [NOTE] ============================================================================== Running from source is generally reserved for developers of PIDA, or those people who really know what they are doing. It is very useful to be able to make a change and test it immediately. It is not recommended to use this as a general running method. ============================================================================== ------------------------------------------------------------------------------ run-pida.sh ------------------------------------------------------------------------------ === Obtaining the development source code The current development version is hosted on Google Code Subversion repository. You can use the following command to anonymously check the latest source code. ------------------------------------------------------- svn checkout http://pida.googlecode.com/svn/trunk/ pida ------------------------------------------------------- PIDA can be executed from its source directory as outlined above. [[installation_packages]] Distribution packages ~~~~~~~~~~~~~~~~~~~~~ Though there is still a long way before PIDA can be considered mature, it is already packaged by several Linux and BSD(FIXME?) distributions. Use the guidelines of your distribution to install or remove PIDA from your system. .Known distributions that provide PIDA `-------------`----------------------`----------- Distribution Distribution version PIDA version ------------------------------------------------- Debian Etch (_stable_) 0.3.1 Debian Lenny (_testing_) 0.4.4 Debian Sid (_unstable_) 0.4.4 Gentoo FIXME. FIXME. Ubuntu Breezy Badger 0.2.2 Ubuntu Dapper Drake 0.2.2 Ubuntu Edgy Eft 0.3.1 Ubuntu Feisty Fawn 0.3.1 Ubuntu Gutsy 0.4.4 ------------------------------------------------- IMPORTANT: There are chances that the version packaged is a bit outdated. Please consider trying to install the most recent version before reporting a bug. You can either compile pida from sources or try to use a package prepared for a more recent version of your distribution. [[installation_windows]] MS Windows ~~~~~~~~~~ FIXME. Some pointers on how to install PIDA dependencies can be found on http://code.google.com/p/pida/wiki/WindowsInstallation[] [[installation_mac]] Mac OS X ~~~~~~~~ FIXME. [[getting_started]] Getting started --------------- FIXME. [[getting_started_wizard]] First run wizard ~~~~~~~~~~~~~~~~ NOTE: This feature is planned for a future version of PIDA, which is still undetermined yet. [[getting_started_window]] The PIDA window ~~~~~~~~~~~~~~~ Like any other IDE, PIDA provides in one window all the tools you need to edit your files, compile your programs, invoke external tools, explore a project space or a file hierarchy, and so on. The PIDA window is organized with a menu, a toolbar, multiple views and a status bar. Many of these elements are optional and can be hidden or displayed at will. The menu bar ^^^^^^^^^^^^ *File*:: This menu offers all file related operations, like creating, saving, or closing a document, but also all version control operations. PIDA also provides sessions management, and the *File* menu permits to save the current session or load a previous one. *Edit*:: This menu serves two purposes. First, it provides facilities to search documents throughout a project, or directories. But PIDA preferences and shortcuts settings are also modifiable from here. *Project*:: This menu provides version control at project level. From there, it is also possible to modify the properties of a project and to start the configured controllers. *Tools*:: Additional utilities, like a terminal and a Python shell for PIDA introspection. *View*:: The PIDA window can be customized from there, displaying or hiding special views or elements like the menu bar or the tool bar. This menu also provides shortcuts to access quickly the most important views of the window, like the file manager. *Help*:: Provides only the credits window for now. The status bar ^^^^^^^^^^^^^^ The status bar provides live information on - The current project. - The current directory browsed in the file manager. - Information on t file currently edited, like its name, encoding and size. The editor ^^^^^^^^^^ The editor is the core element of PIDA. All other views only provide utilities to fill the missing features of the editor, or integrate important accessories -- like a debugger -- or give a quick access to external tools -- like a terminal. The editor is also the central view of PIDA. All other views can be moved around it (see xref:getting_started_window_views[See Views]). PIDA can support any editor. Editor shortcuts and features directly depend on what editor you prefer. It is possible that some features of the chosen editor and PIDA features overlap. In this case, both can be used, but the feature implemented by PIDA will certainly provide better integration with the other tools of the IDE. [[getting_started_window_views]] Views ^^^^^ All elements in the PIDA window, except the editor, the menu bar, the toolbar and status bar, can be moved (remember that the menu bar and the toolbar can be hidden though). FIXME: must choose carefully the vocabulary for elements of the views and keep them consistent. [[getting_started_configuration]] PIDA configuration ~~~~~~~~~~~~~~~~~~ FIXME: gconf, .pida [[services]] Core services ------------- FIXME. === Editor ==== Vim FIXME. ==== Emacs FIXME. File Manager ~~~~~~~~~~~~ FIXME. [[service_project_manager]] Project Manager ~~~~~~~~~~~~~~~ FIXME. [[service_terminal]] Terminal ~~~~~~~~ FIXME. [[service_version_control]] Version Control ~~~~~~~~~~~~~~~ FIXME. Preferences ~~~~~~~~~~~ FIXME. [[plugins]] Plug-ins -------- FIXME. Bookmark ~~~~~~~~ Manage bookmark (files, directories...) Checklist ~~~~~~~~~ FIXME. GTags ~~~~~ GNU Global Integration » Build global index, search through database Library ~~~~~~~ FIXME. Man ~~~ Search and browse man page PasteBin ~~~~~~~~ Send code to a pastebin service Python ~~~~~~ Show class/function from python file, and show compilation errors RFC ~~~ Download RFC index, search and view RFC pages inside PIDA TODO ~~~~ Manage a personal todo list per project Trac ~~~~ View bugs from Trac project inside PIDA == PIDA Service Authoring Guide PIDA has a very general concept of services (you might call them plugins in another application). In general, a service is able to define any PIDA function, that is anything PIDA can do, a service can do it too. PIDA is essentially a bunch of services bound together by a *Boss*. The services are discovered from service directories and loaded by a *Service Manager* for the Boss. === Service Overview A service is comprised of a directory on the file system. This directory is a Python package with data. The structure of this directory is like so for a service named "myservice": ---------------------------- myservice/ __init__.py myservice.py service.pida test_myservice.py data/ glade/ pixmaps/ uidef/ myservice.xml --------------------------- === Creating a starter service As will be seen, there is a large amount of boiler plate involved in creating a service, and so we have provided the creator.py script in the tools/ directory of the source distribution. You should run this with the single argument _service_ and you will be asked a few questions to complete the process: .Using tools/creator.py ------------------------------------------------------------------------ ali@book:~/working/pida$ python tools/creator.py service Please enter the path for the service [/home/ali/working/pida/pida/services]: /tmp Please enter the Service name: MyService Creating service my service in /tmp ------------------------------------------------------------------------ === Individual Components ==== myservice.py This is the file containing the Python code for the service. It is a Python module and should contain an attribute ``Service``, which is the Class which will be instantiated as the service. The service class has a number of class attributes which describe its behaviour. These behaviours are: - Configuration - Commands - Events - Features - Actions .Configuration This is the global configuration options for the service. .Commands Commands are the external interface for the service. They can be called by any other service, and this decoupling is cleaner than expecting, and calling an actual method on a service. .Events Events are an asynchronous call back for the service. Any other service can subscribe to an event explicitly, and by subscribing is notified when an event occurs. .Features Features are behaviours that a service expects other services to provide for it. If this makes no sense, imagine a situation in which a file-manager service expects any service to subscribe to its right-click menu on a file. In this way, the actions provided on that right-click menu are decentralized from the menu itself, and can be provided anywhere. This is very similar to a classical (e.g. Trac) *extension point*. .Actions Actions are gtk.Actions and are used in the user interface. An action maps directly to a single toolbar and menu action, and contains the necessary information to create this user interface item from it, including label, stock image etc. ==== Other files and directories .__init__.py This file is required so that Python recognises the directory as a legitimate Python package. .service.pida This empty file is just present to identify the package as a PIDA service. .data/ This directory should contain any data files for the service that are not included in the other resource directories. .glade/ This directory contains the glade files for the service's views. Although views can be created using Python-only, it is recommended for more detailed plugin views that they use glade. .pixmaps/ This directory should contain any custom pixmaps for the service. These can be used in any way. .uidef/ This directory should contain the UI Definition XML files for the service. These are gtk.UIManager XML files, and define the menu bar and toolbar items for the service. The file myservice.xml is automatically loaded by PIDA, but others can exist in this directory and could be used to populate popup menus or to be further merged with the standard UI defnition. === Service Options Options are currently stored in the GConf database. They are registered at activation time of the service. Each service has its own directory in the GConf database at /apps/pida/service_name. On registering the options, if they do not exist, they are set to the default value. Service options are defined in the service's OptionsConfig. This class should be the options_config attribute of the service class, and should subclass pida.options.OptionsConfig. The OptionsConfig has a method named create_options, which is called on service activation. This method should contain the calls to create_option to create the options. The signature for create_option is: -------------------------------------------------------- create_option(name, label, type, default, documentation) -------------------------------------------------------- For example: ------------------------------------------------ class MyServiceOptions(OptionsConfig): def create_options(self): self.create_option( 'myoption', 'myoption label', OTypeString, 'default_value', 'A string describing the option', ) class MyService(Service): options_config = MyServiceOptions ----------------------------------------------- === Service Commands Commands are the external interface for a service. This interface is specifically provided to other services for use of service activities. ==== Defining Commands Commands are defined as methods on the `commands_config` attribute of the Service class. This attribute should reference a subclass of `pida.core.commands.CommandsConfig` class. Any method defined on that class will be available as a command on the service. ==== Calling service commands Commands are called on a service using the `cmd` method of a service. Calling commands on other services must be performed through the Boss' `cmd` method which takes as an additional parameter then name of the target service. For example, execute a shell from a service: ------------------------------------------- self.boss.cmd( 'commander', <1> 'execute_shell', <2> ) ------------------------------------------- <1> The target service name <2> The target service command ==== Using arguments on service commands All arguments to service commands must be passed as keyword arguments. Because of this, they can be passed in any order after the servicename, and commandname parameters. For example, execute a shell from a service starting in an explicit directory: ------------------------------------------- self.boss.cmd( 'commander', 'execute_shell', cwd = '/', ) ------------------------------------------- === Service Events The events are asynchronous call back for the service. So any other service can subscribe its call back to an event, and it will get called back once the event occurs. ==== Events definition To create a new event, like earlier, you just need to create a new class that you can call 'MyServiceEvents', and you bind it to your 'MyService' class doing 'events_config = MyServiceEvents'. ------------------------------------------------ class MyServiceEvents(EventsConfig): def create_events(self): [...] def subscribe_foreign_events(self): [...] class MyService(Service): events_config = MyServiceEvents ------------------------------------------------ So in that example code, you can notice the two methods you need to implement to manage your own events. You have to define in create_events all the events your service is about to use, and in subscribe_foreign_events all the events from other services your service needs. ==== Create your own new events So, once you have your 'EventsConfig' ready, you need to implement the create_events method so you can have your own new events. Three steps are needed to create an event : (1) You call self.create_event() on the event name (2) You subscribe a new callback to the event you just made (3) You implement the new event's callback so it acts when it is emitted. ------------------------------------------------ def create_events(self): self.create_event('myevent') # (1) self.subscribe_event('myevent', self.on_myevent) # (2) self.create_event('my_foreign_event') def on_myevent(self,param=None): # (3) print 'myevent receipt' if param != None: print 'with param:', param ------------------------------------------------ ==== Subscribe to other services' events We have seen how we can bind callbacks to events you created in your own service. But you often need to interact with other services as well. To do so, you need to implement the subscribe_foreign_events() method the following way : ------------------------------------------------ def subscribe_foreign_events(self): self.subscribe_foreign_event('editor', 'started', self.on_editor_startup) ------------------------------------------------ for each event you want to bind a call back, you need to call the subscribe_foreign_event() method. In the example above, when the editor service launches the started event, self.on_editor_startup() gets called. ------------------------------------------------ self.subscribe_foreign_event('SERVICE_NAME', 'EVENT_NAME', CALLBACK_FUNCTION) ------------------------------------------------ where *SERVICE_NAME* is the destination service, *EVENT_NAME* the event to bind to, *CALLBACK_FUNCTION* the function to be called when the event is emitted. Now suppose you want to give other services' programmers an event of your own service. To do so, you need to call create_event() in create_events() with the name of your event (ie see 'my_foreign_event' above). Then in the foreign service, in the subscribe_foreign_events() method you just need to subscribe to the event: ------------------------------------------------ def subscribe_foreign_events(self): self.subscribe_foreign_event('myservice', 'my_foreign_event', self.on_myservice_foreign_event) ------------------------------------------------ and finally define your callback. ==== Events emition Now you have defined and bound all your events in your service and all you need is to emit them when you need them to be executed. Well, it's fairly simple, just call the emit() method : ------------------------------------------------ [...] self.emit('myevent') self.emit('myevent', param='hello world') self.emit('my_foreign_event') [...] self.get_service('myservice').emit('myevent') self.get_service('myservice').emit('myevent', param='hello from some other place') self.get_service('myservice').emit('my_foreign_event') [...] ------------------------------------------------ As you can see in the examples above, emit can be used in different contexts and with or without parameters. As a rule, every event defined can be called using the emit() method either from your own service (ie in 'MyService') or from someone else's service (then you use get_service().emit()). If your callback function needs parameters, you need to give the options to the emit method. You can also use, as in the above example, non-mandatory parameters. === Service Views Service views are almost anything that appears visually in PIDA (apart from the main toolbar and menubar). All of these views belong to a service. ==== Creating Views Views may be designed in Glade3, or in pure <>. Each method of view creation has its advantages and disadvantages, and these are discussed below. ==== Glade3 Views Views created with Glade3 have the following advantages: - Better maintainability - Automatic signal callback connection The glade-file itself should be places in the directory glade/ in the service directory, and should be named appropriately so as not to conflict with any other service glade file. The extension `.glade` is preferred. So, for example a well named glade file is `project-properties-editor.glade`. This glade file is used by subclassing `pida.ui.views.PidaGladeView` and setting the gladefile attribute on the class, for example for the glade file above: ----------------------------------------------------- from pida.ui.views import PidaGladeView class ProjectPropertiesView(PidaGladeView): gladefile = 'project-properties-editor' ---------------------------------------------------- [NOTE] The glade file attribute omits the extension part of the file name. The glade-file should contain a single top-level container (usually a `gtk.Window`), and this *must have the same name as the glade file (without extension*. The widget inside this container will be taken out of the Window and incorporated into Pida's view. All widgets in the glade view, are attached to the view instances namespace, so that they can be accessed from the instance, for example if there is a text entry called `name_entry`, the attribute `self.name_entry` or `my_view.name_entry` would reference that entry. Signals of items in the glade view are automatically connected if you provide the correct method on the glade view. These methods are named as `on___`. For example, if there is a button on the view called `close_button`, and you wish to connect to it's `clicked` signal, you would provide the following method in order to automatically connect the signal for the widget: ----------------------------------------------------- def on_close_button__clicked(self, button): print '%s was clicked!' % button ----------------------------------------------------- ==== Pure PyGTK Views These views should subclass `pida.ui.views.PidaView` and should create the necessary widgets by overriding the create_ui method. The widgets can be added to the view by using the `view.add_main_widget(widget, expand=True, fill=True)`. The widgets will be added to the top-level VBox in the view. There is no signal autoconnection, and widgets behave exactly as if they had been created with PyGTK in any other circumstance. ==== Instantiating views The service can instantiate its views at any time. They should pass the instance of the service as the first parameter to the View constructor. The service will then be stored as the `svc` attribute on the view. ==== Adding Views to PIDA Views are displayed at runtime by calling the 'window' service's command 'add_view'. The required paned must be passed as well as the view itself. The paned attribute to the command should be one of: - `Buffer` - `Plugin` - `Terminal` The buffer paned is the left sidebar, the plugin paned is the right sidebar, and the terminal paned is the bottom bar. In general the guidelines for which paned to add views to are: - Permanent views should be added to the Buffer paned - Views relating to the current document should be added to the Buffer or Plugin paned - Configuration or property views should be added to the Plugin paned - Multiple view (eg terminal emulators, diffs, and web browsers), or those with a lot of data should be added to the Terminal paned. An example of adding a view of type `MyServiceView` to the Terminal paned is as follows: --------------------------------------- # Override the start method as a hook to when the service starts def start(self): view = MyServiceView(self) self.boss.cmd('window', 'add_view', paned='Terminal', view=view) --------------------------------------- Numerous other examples are available in almost every service in `pida.services`. ==== View icons and labels View icons (the image displayed on the paned button) are referred to by their stock ID, and set as a class attribute on the view `icon_name`. Similarly, the text associating the icon is set as a class attribute on the view called 'label_text`. Additionally, an `icon_name` and/or a `label_text` attribute can be passed to the view constructor, and these will be displayed as the view's label and icon when it is added to the PIDA main view. === Using custom pixmaps in services Any pixmap placed in the pixmaps directory in the service (`myservice/pixmaps`) will automatically be added as a stock image and can be used by the service using its name (without extension) for View icons or for gtk.Buttons or gtk.Images or any other widget which take a stock_id as an argument. === Links + [[[PyGTK]]] http://pygtk.org[PyGTK Website] == PIDA Plugin Authoring Guide PIDA plugins are very much identical to Services. Anything you can do in a Service, you can also do in a Plugin. There are however a few minor differences, based on the facts that: 1. Plugins can be loaded and unlaoded at runtime 2. Plugins can be published on the PIDA community and installed PIDA uses Plugins for all non-core funcitonality, and we have been quite strict about this, so that we can maintain a different release schedule for the core, and individual plugins. Because of this, Plugins which you might expect to find in a standard IDE or a standard Python IDE must be installed. Fortunately this is a matter of a single click. === The service.pida file The service.pida file in Plugins is in the _ini_ format with themetadata under the [plugin] tag. It contains metadata that is used by the installer and by the community website. This metadata includes: .Service.pida metadata [grid="all"] '-------------'--------------------------------------------------------------- Attribute Description ------------------------------------------------------------------------------ plugin * Technical name of plugin (only [a-z0-9_] name) name * Long name of plugin version * Version of plugin author * Name of author require_pida * Version of PIDA category * Category of plugins depends List of dependencies lastupdate Date of last update website Website of plugin ------------------------------------------------------------------------------ _* These fields are mandatory_ .An example service.pida file ------------------------------------------------------------------------------ [plugin] plugin = snippets name = Snippets author = Ali Afshar version = 0.1 require_pida = 0.5 depends = "" category = code description = Snippets: automatically enter repetitive text ------------------------------------------------------------------------------ === Publishing Plugins Plugin publishing can be done from the safety of the PIDA user interface. First you will need to create the service directory (our advice is to use the tools/creator.py script as outlined in the service authoring guide above). Once your plugin is created, in PIDA, select _Plugin Manager_ from tools, and select the _Publish_ tab. Enter the directory containing the service, and your username and password for the pida community website. You will be given a chance to edit the service metadata in the user interface. When you are happy, select the _Make package and upload_ button to complete the process. [NOTE] The plugin will be placed on standby, and approved by one of the developers. Once it is approved, anyone can download it. === Limitations to plugins The single limitation to a plugin is that it should not (although it can) provide events that can be subscribed to. This is because a plugin can be loaded and unloaded at runtime, and so plugins that depend on other plugins' events will cease to function. We say _"should not"_ rather than _"must not"_ because you can actually do what you like. We have a strong philosophy about not getting in your way, but be warned that you have been warned! == PIDA Coding Style Guidelines First read <> (the PEP on how to write readable Python code). The PEP gives a number of good insights. The PEP gives a few options on things, and I shall try to clarify what I prefer here. Where this document differs from PEP8_, you should use what is presented here, unless you are a zealot in which case you should listen to the Python people (who are cleverer than me anyway). Also read PEP20_ while you are at it. === Indenting 4 Spaces, no tabs ever ever. This is not negotiable. Emacs users please check your settings, somehow tabs creep into emacs-written code. === Line Width 79 characters, perhaps 78 to be safe. This is negotiable, and there are times when 83 character lines are acceptable. You can be the judge. I am not sure many people use 80-character terminals these days, so we can be a bit less hard-line than the PEP. You can split lines however you wish. I personally use 3 different forms of splitting depending on the purpose. Long lists, dicts, or many paramteres to a function: ----------------------------- service_base_classes = [ OptionsMixin, commands_mixin, events_mixin, bindings_mixin, ] ----------------------------- Single extra bit: -------------------------------------------------------------------------------- def really_long_method_or_function_name(first_parameter, second_paramater, third_parameter) -------------------------------------------------------------------------------- Or: -------------------------------------------------------------------------------- def really_long_method_or_function_name(first_parameter, second_paramater, third_parameter) -------------------------------------------------------------------------------- It all depends on the use at the time, and we should remember to keep it readable. === Blank Lines As <> for 2 lines between top-level classes and functions, with one line between methods. Extra blank line "to indicate logical blocks" should be avoided at all costs in my opinion. Real logical blocks should be used to indicate logical blocks! If you have to do this, a comment is better than a blank line. === Imports Only import the function or class you want to use, for example: ---------------------------------------------------------- from pida.ui.views import PidaView, BaseView ---------------------------------------------------------- There are a few common exceptions like: ---------------------------- import gtk ---------------------------- Multiple top-level imports are fine too if you like, but best grouped by where they are comming from: -------------------------------- import os, sys import gtk, gobject, pango -------------------------------- Remember to import in this order: 1. standard library imports 2. related third party imports 3. PIDA application/library specific imports === Whitespace Yes: -------------------------------- def foo(blah, baz): -------------------------------- No: -------------------------------- def foo ( blah , baz ): def foo(blah,baz): -------------------------------- (that space after a comma is basic punctuation) <> has oodles on this. === Docstrings I like having the triple quotes as doubles, and for them to be on empty lines, like so: ------------------------------------------- def foo(): """ This is the single-line docstring """ ------------------------------------------- Docstrings are plain nice, so please try to use them for all functions. I am guilty of being lazy, so I can't blame anyone. Also we use API generation which uses these doc strings, so it all helps. We use Pydoctor_ with ReStructured text directives for API generation, so I guess you should look them up too. === Strings Single quoted, unless you need single quotes in them, in which case use double quotes: ------------------------------------------ my_string = 'I am a banana' my_other_string = "I am a banana's uncle" ------------------------------------------ === Naming - Modules as lowercase single words with no underscores, except test modules which should start with `test_`. - Functions as lower_case_with_underscores. - Classes is CamelCase. (Note: I hate camel case, but it is useful, even in Python to know the difference between a class and a function. Why? You can subclass a class.) - Module-level constants all in UPPERCASE_WITH_UNDERSCORES. === Conditional blocks This is fine: ----------------------- if blah: baz = 1 else: baz = 2 ----------------------- And better than: ----------------------- baz = 2 if blah: baz = 1 ----------------------- But I am not going to argue, needs can force you into a certain style. Remember, readability is key. === Magic I hate magic, perhaps because I am dumb. I am really wary of using some of Python's shoot-me-in-the-foot techniques because I have to maintain the code, so. I have made these mistakes myself, and have (hopefully learned from the mistakes. So: Meta classes:: Never! I have yet to see a use-case for metaclasses which did not relate to perverting some other library or external class. I am happy to be enlightened. Decorators:: Make perfect sense in some cases, but have the danger of being over used, so please think carefully whether you are using them to decorate behaviour, or just using them for the sake of it. Inner classes:: I have yet to see a use-case that requires these. === Bibliography + [[[PEP8]]] http://www.python.org/dev/peps/pep-0008/[Python Enhancement Proposal 8] + [[[PEP20]]] http://www.python.org/dev/peps/pep-0020/[Python Enhancement Proposal 20] + [[[Pydoctor]]] http://codespeak.net/~mwh/pydoctor/[Pydoctor Web Site] // vim: set filetype=asciidoc : PIDA-0.5.1/moo/0002755000175000017500000000000010652671501011125 5ustar alialiPIDA-0.5.1/moo/Makefile0000644000175000017500000000437610652670543012602 0ustar alialiHEADERS = moopaned.h moopane.h moobigpaned.h moomarshals.h stock-moo.h OBJECTS = moopaned.o moopane.o moobigpaned.o test.o moomarshals.o PICS = close.png sticky.png hide.png detach.png attach.png keepontop.png CFLAGS = `pkg-config --cflags gtk+-2.0` -W -Wall -DMooPane=_PidaMooPane \ -DMooPaneClass=_PidaMooPaneClass -DMooPaned=_PidaMooPaned -DMooPanedClass=_PidaMooPanedClass \ -DMooBigPaned=_PidaMooBigPaned -DMooBigPanedClass=_PidaMooBigPanedClass LIBS = `pkg-config --libs gtk+-2.0` PYGTK_DEFS_DIR=`pkg-config --variable=defsdir pygtk-2.0` test: $(OBJECTS) gcc $(LIBS) $(OBJECTS) -o test moomarshals.c: moomarshals.list echo '#include "moomarshals.h"' > $@.tmp && \ glib-genmarshal --prefix=_moo_marshal --body \ moomarshals.list >> $@.tmp && mv $@.tmp $@ moomarshals.h: moomarshals.list glib-genmarshal --prefix=_moo_marshal --header \ moomarshals.list > $@.tmp && mv $@.tmp $@ stock-moo.h: $(PICS) gdk-pixbuf-csource --static --build-list \ MOO_HIDE_ICON hide.png \ MOO_CLOSE_ICON close.png \ MOO_STICKY_ICON sticky.png \ MOO_DETACH_ICON detach.png \ MOO_ATTACH_ICON attach.png \ MOO_KEEP_ON_TOP_ICON keepontop.png \ > $@.tmp && mv $@.tmp $@ %-prepro.c: %.c $(HEADERS) gcc $(CFLAGS) $*.c -c -E > $*-prepro.c %.o: %.c $(HEADERS) gcc $(CFLAGS) $*.c -c -o $@ moomarshals.o: moomarshals.c moomarshals.h gcc $(CFLAGS) -Wno-unused moomarshals.c -c -o $@ clean: rm -f *.o test paned.tar.bz2 moomarshals.[ch] stock-moo.h moo-pygtk.c *.pyc dist: rm -fr paned.tar.bz2 paned/ && \ mkdir paned && \ cp moo-stub.c moo.override moo.defs moopaned.override moopaned.defs \ moopane.h moopaned.h moobigpaned.h $(PICS) \ moopane.c moopaned.c moobigpaned.c test.c \ moomarshals.list Makefile paned/ && \ tar cjf paned.tar.bz2 paned/ && \ rm -fr paned/ distcheck: make dist && tar xjf paned.tar.bz2 && cd paned && \ make && make dist && mv paned.tar.bz2 ../ && \ cd .. && rm -fr paned prepare: moomarshals.c moomarshals.h stock-moo.h moo-pygtk.c moo-pygtk.c: moo.defs moopaned.defs moo.override moopaned.override pygtk-codegen-2.0 --prefix _moo_stub \ --register $(PYGTK_DEFS_DIR)/gtk-types.defs \ --register $(PYGTK_DEFS_DIR)/gdk-types.defs \ --override moo.override \ --outfilename moo-pygtk.c \ moo.defs > $@.tmp && \ mv $@.tmp $@ PIDA-0.5.1/moo/__init__.py0000644000175000017500000000000010652670543013227 0ustar alialiPIDA-0.5.1/moo/__init__.pyc0000644000175000017500000000017410652671166013410 0ustar aliali cqFc@sdS(N((((s%/home/ali/tmp/em/pida/moo/__init__.pyssPIDA-0.5.1/moo/attach.png0000644000175000017500000000014110652670543013076 0ustar alialiPNG  IHDRRW(IDATc``` \IN qIENDB`PIDA-0.5.1/moo/close.png0000644000175000017500000000015410652670543012743 0ustar alialiPNG  IHDRRW3IDATcd`` 🁁 ]π 1BDP'8 MT cIENDB`PIDA-0.5.1/moo/detach.png0000644000175000017500000000014610652670543013067 0ustar alialiPNG  IHDRRW-IDATcd``π pId']8d $ f X IENDB`PIDA-0.5.1/moo/dsutils.py0000644000175000017500000000366510652670543013203 0ustar alialiimport sys import os def getoutput(cmd): """Return output (stdout or stderr) of executing cmd in a shell.""" return getstatusoutput(cmd)[1] def getstatusoutput(cmd): """Return (status, output) of executing cmd in a shell.""" if sys.platform == 'win32': pipe = os.popen(cmd, 'r') text = pipe.read() sts = pipe.close() or 0 if text[-1:] == '\n': text = text[:-1] return sts, text else: from commands import getstatusoutput return getstatusoutput(cmd) def pkgc_version_check(name, longname, req_version): is_installed = not os.system('pkg-config --exists %s' % name) if not is_installed: print "Could not find %s" % longname return 0 orig_version = getoutput('pkg-config --modversion %s' % name) version = map(int, orig_version.split('.')) pkc_version = map(int, req_version.split('.')) if version >= pkc_version: return 1 else: print "Warning: Too old version of %s" % longname print " Need %s, but %s is installed" % \ (pkc_version, orig_version) self.can_build_ok = 0 return 0 def pkc_get_include_dirs(names): if type(names) != tuple: names = (names,) retval = [] for name in names: output = getoutput('pkg-config --cflags-only-I %s' % name) retval.extend(output.replace('-I', '').split()) return retval def pkc_get_libraries(names): if type(names) != tuple: names = (names,) retval = [] for name in names: output = getoutput('pkg-config --libs-only-l %s' % name) retval.extend(output.replace('-l', '').split()) return retval def pkc_get_library_dirs(names): if type(names) != tuple: names = (names,) retval = [] for name in names: output = getoutput('pkg-config --libs-only-L %s' % name) retval.extend(output.replace('-L', '').split()) return retval PIDA-0.5.1/moo/dsutils.pyc0000644000175000017500000000516510652671166013345 0ustar aliali cqFc@sRddkZddkZdZdZdZdZdZdZdS(iNcCst|dS(s=Return output (stdout or stderr) of executing cmd in a shell.i(tgetstatusoutput(tcmd((s$/home/ali/tmp/em/pida/moo/dsutils.pyt getoutputscCstidjo^ti|d}|i}|ipd}|ddjo|d }n||fSnddkl}||SdS(s4Return (status, output) of executing cmd in a shell.twin32triis (RN(tsystplatformtostpopentreadtclosetcommandsR(RtpipettexttstsR((s$/home/ali/tmp/em/pida/moo/dsutils.pyRs cCstid| }|pd|GHdSntd|}tt|id}tt|id}||jodSn&d|GHd||fGHdt_dSdS( Nspkg-config --exists %ssCould not find %sispkg-config --modversion %st.isWarning: Too old version of %ss% Need %s, but %s is installed(RtsystemRtmaptinttsplittselft can_build_ok(tnametlongnamet req_versiont is_installedt orig_versiontversiont pkc_version((s$/home/ali/tmp/em/pida/moo/dsutils.pytpkgc_version_checks     cCsjt|tjo |f}ng}x=|D]5}td|}|i|iddiq-W|S(Nspkg-config --cflags-only-I %ss-It(ttypettupleRtextendtreplaceR(tnamestretvalRtoutput((s$/home/ali/tmp/em/pida/moo/dsutils.pytpkc_get_include_dirs(s #cCsjt|tjo |f}ng}x=|D]5}td|}|i|iddiq-W|S(Nspkg-config --libs-only-l %ss-lR(RR RR!R"R(R#R$RR%((s$/home/ali/tmp/em/pida/moo/dsutils.pytpkc_get_libraries1s #cCsjt|tjo |f}ng}x=|D]5}td|}|i|iddiq-W|S(Nspkg-config --libs-only-L %ss-LR(RR RR!R"R(R#R$RR%((s$/home/ali/tmp/em/pida/moo/dsutils.pytpkc_get_library_dirs:s #(RRRRRR&R'R((((s$/home/ali/tmp/em/pida/moo/dsutils.pyss     PIDA-0.5.1/moo/hide.png0000644000175000017500000000011710652670543012546 0ustar alialiPNG  IHDRRWIDATc`?`d``KlcT~ pIENDB`PIDA-0.5.1/moo/keepontop.png0000644000175000017500000000014610652670543013643 0ustar alialiPNG  IHDRRW-IDATc`@98$$X$0$g t IENDB`PIDA-0.5.1/moo/moo-stub.c0000644000175000017500000000111110652670543013033 0ustar aliali#include #include #include extern PyMethodDef _moo_stub_functions[]; void _moo_stub_add_constants(PyObject *module, const gchar *strip_prefix); void _moo_stub_register_classes(PyObject *d); void initmoo_stub (void) { PyObject *module; init_pygobject (); init_pygtk (); if (PyErr_Occurred ()) return; module = Py_InitModule ((char*) "moo_stub", _moo_stub_functions); if (module) { _moo_stub_add_constants (module, "MOO_"); _moo_stub_register_classes (PyModule_GetDict (module)); } } PIDA-0.5.1/moo/moo.defs0000644000175000017500000000005510652670543012565 0ustar aliali;; -*- scheme -*- (include "moopaned.defs") PIDA-0.5.1/moo/moo.override0000644000175000017500000000057210652670543013467 0ustar aliali/**/ %% headers #include #define NO_IMPORT_PYGOBJECT #include #include #include "moobigpaned.h" %% modulename moo_stub %% import gtk.Object as PyGtkObject_Type import gtk.Widget as PyGtkWidget_Type import gtk.Frame as PyGtkFrame_Type import gtk.Bin as PyGtkBin_Type import gtk.gdk.Pixbuf as PyGdkPixbuf_Type %% include moopaned.override PIDA-0.5.1/moo/moobigpaned.c0000644000175000017500000006356410652670543013576 0ustar aliali/* * moobigpaned.c * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "moobigpaned.h" #include "moomarshals.h" #ifdef MOO_COMPILATION #include "mooutils-gobject.h" #else #if GLIB_CHECK_VERSION(2,10,0) #define MOO_OBJECT_REF_SINK(obj) g_object_ref_sink (obj) #else #define MOO_OBJECT_REF_SINK(obj) gtk_object_sink (g_object_ref (obj)) #endif #endif static void moo_big_paned_finalize (GObject *object); static void moo_big_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void moo_big_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static gboolean moo_big_paned_expose (GtkWidget *widget, GdkEventExpose *event, MooBigPaned *paned); static void child_set_pane_size (GtkWidget *child, int size, MooBigPaned *paned); static gboolean check_children_order (MooBigPaned *paned); static void handle_drag_start (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned); static void handle_drag_motion (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned); static void handle_drag_end (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned); /* MOO_TYPE_BIG_PANED */ G_DEFINE_TYPE (MooBigPaned, moo_big_paned, GTK_TYPE_FRAME) enum { PROP_0, PROP_PANE_ORDER, PROP_ENABLE_HANDLE_DRAG, PROP_ENABLE_DETACHING, PROP_HANDLE_CURSOR_TYPE }; enum { SET_PANE_SIZE, NUM_SIGNALS }; static guint signals[NUM_SIGNALS]; static void moo_big_paned_class_init (MooBigPanedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = moo_big_paned_finalize; gobject_class->set_property = moo_big_paned_set_property; gobject_class->get_property = moo_big_paned_get_property; g_object_class_install_property (gobject_class, PROP_PANE_ORDER, g_param_spec_pointer ("pane-order", "pane-order", "pane-order", G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ENABLE_HANDLE_DRAG, g_param_spec_boolean ("enable-handle-drag", "enable-handle-drag", "enable-handle-drag", TRUE, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, PROP_ENABLE_DETACHING, g_param_spec_boolean ("enable-detaching", "enable-detaching", "enable-detaching", FALSE, G_PARAM_CONSTRUCT | G_PARAM_WRITABLE)); g_object_class_install_property (gobject_class, PROP_HANDLE_CURSOR_TYPE, g_param_spec_enum ("handle-cursor-type", "handle-cursor-type", "handle-cursor-type", GDK_TYPE_CURSOR_TYPE, GDK_HAND2, G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); signals[SET_PANE_SIZE] = g_signal_new ("set-pane-size", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooBigPanedClass, set_pane_size), NULL, NULL, _moo_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); } #define NTH_CHILD(paned,n) paned->paned[paned->order[n]] static void moo_big_paned_init (MooBigPaned *paned) { int i; paned->drop_pos = -1; /* XXX destroy */ for (i = 0; i < 4; ++i) { GtkWidget *child; paned->paned[i] = child = g_object_new (MOO_TYPE_PANED, "pane-position", (MooPanePosition) i, NULL); MOO_OBJECT_REF_SINK (child); gtk_widget_show (child); g_signal_connect_after (child, "set-pane-size", G_CALLBACK (child_set_pane_size), paned); g_signal_connect (child, "handle-drag-start", G_CALLBACK (handle_drag_start), paned); g_signal_connect (child, "handle-drag-motion", G_CALLBACK (handle_drag_motion), paned); g_signal_connect (child, "handle-drag-end", G_CALLBACK (handle_drag_end), paned); } paned->order[0] = MOO_PANE_POS_LEFT; paned->order[1] = MOO_PANE_POS_RIGHT; paned->order[2] = MOO_PANE_POS_TOP; paned->order[3] = MOO_PANE_POS_BOTTOM; paned->inner = NTH_CHILD (paned, 3); paned->outer = NTH_CHILD (paned, 0); gtk_container_add (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_add (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); g_assert (check_children_order (paned)); } static gboolean check_children_order (MooBigPaned *paned) { int i; if (GTK_BIN(paned)->child != NTH_CHILD (paned, 0)) return FALSE; for (i = 0; i < 3; ++i) if (GTK_BIN (NTH_CHILD (paned, i))->child != NTH_CHILD (paned, i+1)) return FALSE; return TRUE; } void moo_big_paned_set_pane_order (MooBigPaned *paned, int *order) { MooPanePosition new_order[4] = {8, 8, 8, 8}; int i; GtkWidget *child; g_return_if_fail (MOO_IS_BIG_PANED (paned)); g_return_if_fail (order != NULL); for (i = 0; i < 4; ++i) { g_return_if_fail (new_order[i] >= 4); g_return_if_fail (0 <= order[i] && order[i] < 4); new_order[i] = order[i]; } g_return_if_fail (check_children_order (paned)); for (i = 0; i < 4; ++i) { if (new_order[i] != paned->order[i]) break; } if (i == 4) return; child = moo_big_paned_get_child (paned); if (child) g_object_ref (child); gtk_container_remove (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_remove (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); if (child) gtk_container_remove (GTK_CONTAINER (NTH_CHILD (paned, 3)), child); for (i = 0; i < 4; ++i) paned->order[i] = new_order[i]; gtk_container_add (GTK_CONTAINER (paned), NTH_CHILD (paned, 0)); for (i = 0; i < 3; ++i) gtk_container_add (GTK_CONTAINER (NTH_CHILD (paned, i)), NTH_CHILD (paned, i+1)); paned->inner = NTH_CHILD (paned, 3); paned->outer = NTH_CHILD (paned, 0); if (child) { gtk_container_add (GTK_CONTAINER (paned->inner), child); g_object_unref (child); } g_assert (check_children_order (paned)); g_object_notify (G_OBJECT (paned), "pane-order"); } static void moo_big_paned_finalize (GObject *object) { MooBigPaned *paned = MOO_BIG_PANED (object); int i; for (i = 0; i < 4; ++i) g_object_unref (paned->paned[i]); if (paned->drop_outline) { g_critical ("%s: oops", G_STRLOC); gdk_window_set_user_data (paned->drop_outline, NULL); gdk_window_destroy (paned->drop_outline); } G_OBJECT_CLASS (moo_big_paned_parent_class)->finalize (object); } GtkWidget* moo_big_paned_new (void) { return g_object_new (MOO_TYPE_BIG_PANED, NULL); } static void child_set_pane_size (GtkWidget *child, int size, MooBigPaned *paned) { MooPanePosition pos; g_object_get (child, "pane-position", &pos, NULL); g_return_if_fail (paned->paned[pos] == child); g_signal_emit (paned, signals[SET_PANE_SIZE], 0, pos, size); } MooPane * moo_big_paned_insert_pane (MooBigPaned *paned, GtkWidget *pane_widget, MooPaneLabel *pane_label, MooPanePosition position, int index_) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), NULL); g_return_val_if_fail (position < 4, NULL); return moo_paned_insert_pane (MOO_PANED (paned->paned[position]), pane_widget, pane_label, index_); } void moo_big_paned_add_child (MooBigPaned *paned, GtkWidget *child) { g_return_if_fail (MOO_IS_BIG_PANED (paned)); gtk_container_add (GTK_CONTAINER (paned->inner), child); } void moo_big_paned_remove_child (MooBigPaned *paned) { g_return_if_fail (MOO_IS_BIG_PANED (paned)); gtk_container_remove (GTK_CONTAINER (paned->inner), GTK_BIN(paned->inner)->child); } GtkWidget *moo_big_paned_get_child (MooBigPaned *paned) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); return GTK_BIN(paned->inner)->child; } gboolean moo_big_paned_remove_pane (MooBigPaned *paned, GtkWidget *widget) { MooPaned *child; g_return_val_if_fail (MOO_IS_BIG_PANED (paned), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); if (!moo_big_paned_find_pane (paned, widget, &child)) return FALSE; return moo_paned_remove_pane (child, widget); } #define PROXY_FUNC(name) \ void \ moo_big_paned_##name (MooBigPaned *paned, \ GtkWidget *widget) \ { \ MooPane *pane; \ MooPaned *child = NULL; \ \ g_return_if_fail (MOO_IS_BIG_PANED (paned)); \ g_return_if_fail (GTK_IS_WIDGET (widget)); \ \ pane = moo_big_paned_find_pane (paned, widget, &child); \ g_return_if_fail (pane != NULL); \ \ moo_paned_##name (child, pane); \ } PROXY_FUNC (open_pane) PROXY_FUNC (present_pane) PROXY_FUNC (attach_pane) PROXY_FUNC (detach_pane) #undef PROXY_FUNC void moo_big_paned_hide_pane (MooBigPaned *paned, GtkWidget *widget) { MooPaned *child = NULL; g_return_if_fail (MOO_IS_BIG_PANED (paned)); g_return_if_fail (GTK_IS_WIDGET (widget)); moo_big_paned_find_pane (paned, widget, &child); g_return_if_fail (child != NULL); moo_paned_hide_pane (child); } MooPaned * moo_big_paned_get_paned (MooBigPaned *paned, MooPanePosition position) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (position < 4, NULL); return MOO_PANED (paned->paned[position]); } MooPane * moo_big_paned_find_pane (MooBigPaned *paned, GtkWidget *widget, MooPaned **child_paned) { int i; MooPane *pane; g_return_val_if_fail (MOO_IS_BIG_PANED (paned), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); if (child_paned) *child_paned = NULL; for (i = 0; i < 4; ++i) { pane = moo_paned_get_pane (MOO_PANED (paned->paned[i]), widget); if (pane) { if (child_paned) *child_paned = MOO_PANED (paned->paned[i]); return pane; } } return NULL; } static void moo_big_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooBigPaned *paned = MOO_BIG_PANED (object); int i; switch (prop_id) { case PROP_PANE_ORDER: moo_big_paned_set_pane_order (paned, g_value_get_pointer (value)); break; case PROP_ENABLE_HANDLE_DRAG: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "enable-handle-drag", g_value_get_boolean (value), NULL); break; case PROP_ENABLE_DETACHING: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "enable-detaching", g_value_get_boolean (value), NULL); break; case PROP_HANDLE_CURSOR_TYPE: for (i = 0; i < 4; ++i) g_object_set (paned->paned[i], "handle-cursor-type", (GdkCursorType) g_value_get_enum (value), NULL); g_object_notify (object, "handle-cursor-type"); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_big_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooBigPaned *paned = MOO_BIG_PANED (object); GdkCursorType cursor_type; switch (prop_id) { case PROP_PANE_ORDER: g_value_set_pointer (value, paned->order); break; case PROP_HANDLE_CURSOR_TYPE: g_object_get (paned->paned[0], "handle-cursor-type", &cursor_type, NULL); g_value_set_enum (value, cursor_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } GtkWidget * moo_big_paned_get_pane (MooBigPaned *paned, MooPanePosition position, int index_) { g_return_val_if_fail (MOO_IS_BIG_PANED (paned), NULL); g_return_val_if_fail (position < 4, NULL); return moo_pane_get_child (moo_paned_get_nth_pane (MOO_PANED (paned->paned[position]), index_)); } /*****************************************************************************/ /* rearranging panes */ static void create_drop_outline (MooBigPaned *paned); static int get_drop_position (MooBigPaned *paned, MooPaned *child, int x, int y); static void get_drop_area (MooBigPaned *paned, MooPaned *active_child, MooPanePosition position, GdkRectangle *rect); static void invalidate_drop_outline (MooBigPaned *paned); static void handle_drag_start (G_GNUC_UNUSED MooPaned *child, G_GNUC_UNUSED GtkWidget *pane_widget, MooBigPaned *paned) { g_return_if_fail (GTK_WIDGET_REALIZED (paned->outer)); g_signal_connect (paned->outer, "expose-event", G_CALLBACK (moo_big_paned_expose), paned); paned->drop_pos = -1; } static void handle_drag_motion (MooPaned *child, G_GNUC_UNUSED GtkWidget *pane_widget, MooBigPaned *paned) { int pos, x, y; g_return_if_fail (GTK_WIDGET_REALIZED (paned->outer)); gdk_window_get_pointer (paned->outer->window, &x, &y, NULL); pos = get_drop_position (paned, child, x, y); if (pos == paned->drop_pos) return; if (paned->drop_pos >= 0) { g_assert (paned->drop_outline != NULL); gdk_window_set_user_data (paned->drop_outline, NULL); gdk_window_destroy (paned->drop_outline); paned->drop_outline = NULL; invalidate_drop_outline (paned); } paned->drop_pos = pos; if (pos >= 0) { get_drop_area (paned, child, pos, &paned->drop_rect); g_assert (paned->drop_outline == NULL); create_drop_outline (paned); } } static void handle_drag_end (MooPaned *child, GtkWidget *pane_widget, MooBigPaned *paned) { int pos, x, y; MooPanePosition old_pos; MooPane *pane; g_return_if_fail (GTK_WIDGET_REALIZED (paned->outer)); gdk_window_get_pointer (paned->outer->window, &x, &y, NULL); pos = get_drop_position (paned, child, x, y); if (paned->drop_pos >= 0) { g_assert (paned->drop_outline != NULL); gdk_window_set_user_data (paned->drop_outline, NULL); gdk_window_destroy (paned->drop_outline); paned->drop_outline = NULL; invalidate_drop_outline (paned); } paned->drop_pos = -1; g_signal_handlers_disconnect_by_func (paned->outer, (gpointer) moo_big_paned_expose, paned); if (pos < 0) return; g_object_get (child, "pane-position", &old_pos, NULL); if ((int) old_pos == pos) return; pane = moo_paned_get_pane (child, pane_widget); g_object_ref (pane); moo_paned_remove_pane (child, pane_widget); _moo_paned_insert_pane (MOO_PANED (paned->paned[pos]), pane, -1); moo_pane_open (pane); _moo_pane_params_changed (pane); g_object_unref (pane); } static void get_drop_area (MooBigPaned *paned, MooPaned *active_child, MooPanePosition position, GdkRectangle *rect) { int width, height, size = 0; MooPanePosition active_position; width = paned->outer->allocation.width; height = paned->outer->allocation.height; g_object_get (active_child, "pane-position", &active_position, NULL); g_return_if_fail (active_position < 4); if (active_position == position) { size = moo_paned_get_pane_size (active_child) + moo_paned_get_button_box_size (active_child); } else { switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: size = width / 3; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: size = height / 3; break; } } switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: rect->y = paned->outer->allocation.y; rect->width = size; rect->height = height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: rect->x = paned->outer->allocation.x; rect->width = width; rect->height = size; break; } switch (position) { case MOO_PANE_POS_LEFT: rect->x = paned->outer->allocation.x; break; case MOO_PANE_POS_RIGHT: rect->x = paned->outer->allocation.x + width - size; break; case MOO_PANE_POS_TOP: rect->y = paned->outer->allocation.y; break; case MOO_PANE_POS_BOTTOM: rect->y = paned->outer->allocation.y + height - size; break; } } #define RECT_POINT_IN(rect,x,y) (x < (rect)->x + (rect)->width && \ y < (rect)->height + (rect)->y && \ x >= (rect)->x && y >= (rect)->y) static int get_drop_position (MooBigPaned *paned, MooPaned *child, int x, int y) { int width, height, i; MooPanePosition position; GdkRectangle rect; width = paned->outer->allocation.width; height = paned->outer->allocation.height; if (x < paned->outer->allocation.x || x >= paned->outer->allocation.x + width || y < paned->outer->allocation.y || y >= paned->outer->allocation.y + height) return -1; g_object_get (child, "pane-position", &position, NULL); g_return_val_if_fail (position < 4, -1); get_drop_area (paned, child, position, &rect); if (RECT_POINT_IN (&rect, x, y)) return position; for (i = 0; i < 4; ++i) { if (paned->order[i] == position) continue; get_drop_area (paned, child, paned->order[i], &rect); if (RECT_POINT_IN (&rect, x, y)) return paned->order[i]; } return -1; } static void invalidate_drop_outline (MooBigPaned *paned) { GdkRectangle line; GdkRegion *outline; outline = gdk_region_new (); line.x = paned->drop_rect.x; line.y = paned->drop_rect.y; line.width = 2; line.height = paned->drop_rect.height; gdk_region_union_with_rect (outline, &line); line.x = paned->drop_rect.x; line.y = paned->drop_rect.y + paned->drop_rect.height; line.width = paned->drop_rect.width; line.height = 2; gdk_region_union_with_rect (outline, &line); line.x = paned->drop_rect.x + paned->drop_rect.width; line.y = paned->drop_rect.y; line.width = 2; line.height = paned->drop_rect.height; gdk_region_union_with_rect (outline, &line); line.x = paned->drop_rect.x; line.y = paned->drop_rect.y; line.width = paned->drop_rect.width; line.height = 2; gdk_region_union_with_rect (outline, &line); gdk_window_invalidate_region (paned->outer->window, outline, TRUE); gdk_region_destroy (outline); } static gboolean moo_big_paned_expose (GtkWidget *widget, GdkEventExpose *event, MooBigPaned *paned) { GTK_WIDGET_CLASS(G_OBJECT_GET_CLASS (widget))->expose_event (widget, event); if (paned->drop_pos >= 0) { g_return_val_if_fail (paned->drop_outline != NULL, FALSE); gdk_draw_rectangle (paned->drop_outline, widget->style->fg_gc[GTK_STATE_NORMAL], FALSE, 0, 0, paned->drop_rect.width - 1, paned->drop_rect.height - 1); gdk_draw_rectangle (paned->drop_outline, widget->style->fg_gc[GTK_STATE_NORMAL], FALSE, 1, 1, paned->drop_rect.width - 3, paned->drop_rect.height - 3); } return FALSE; } static GdkBitmap * create_rect_mask (int width, int height) { GdkBitmap *bitmap; GdkGC *gc; GdkColor white = {0, 0, 0, 0}; GdkColor black = {1, 1, 1, 1}; bitmap = gdk_pixmap_new (NULL, width, height, 1); gc = gdk_gc_new (bitmap); gdk_gc_set_foreground (gc, &white); gdk_draw_rectangle (bitmap, gc, TRUE, 0, 0, width, height); gdk_gc_set_foreground (gc, &black); gdk_draw_rectangle (bitmap, gc, FALSE, 0, 0, width - 1, height - 1); gdk_draw_rectangle (bitmap, gc, FALSE, 1, 1, width - 3, height - 3); g_object_unref (gc); return bitmap; } static void create_drop_outline (MooBigPaned *paned) { static GdkWindowAttr attributes; int attributes_mask; GdkBitmap *mask; g_return_if_fail (paned->drop_outline == NULL); attributes.x = paned->drop_rect.x; attributes.y = paned->drop_rect.y; attributes.width = paned->drop_rect.width; attributes.height = paned->drop_rect.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.visual = gtk_widget_get_visual (paned->outer); attributes.colormap = gtk_widget_get_colormap (paned->outer); attributes.wclass = GDK_INPUT_OUTPUT; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; paned->drop_outline = gdk_window_new (paned->outer->window, &attributes, attributes_mask); gdk_window_set_user_data (paned->drop_outline, paned); mask = create_rect_mask (paned->drop_rect.width, paned->drop_rect.height); gdk_window_shape_combine_mask (paned->drop_outline, mask, 0, 0); g_object_unref (mask); gdk_window_show (paned->drop_outline); } PIDA-0.5.1/moo/moobigpaned.h0000644000175000017500000001035610652670543013572 0ustar aliali/* * moobigpaned.h * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifndef MOO_BIG_PANED_H #define MOO_BIG_PANED_H #include #include "moopaned.h" G_BEGIN_DECLS #define MOO_TYPE_BIG_PANED (moo_big_paned_get_type ()) #define MOO_BIG_PANED(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_BIG_PANED, MooBigPaned)) #define MOO_BIG_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_BIG_PANED, MooBigPanedClass)) #define MOO_IS_BIG_PANED(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), MOO_TYPE_BIG_PANED)) #define MOO_IS_BIG_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_BIG_PANED)) #define MOO_BIG_PANED_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_BIG_PANED, MooBigPanedClass)) typedef struct _MooBigPaned MooBigPaned; typedef struct _MooBigPanedPrivate MooBigPanedPrivate; typedef struct _MooBigPanedClass MooBigPanedClass; struct _MooBigPaned { GtkFrame parent; GtkWidget *paned[4]; /* indexed by PanePos */ MooPanePosition order[4]; /* inner is paned[order[3]]*/ GtkWidget *inner; GtkWidget *outer; int drop_pos; GdkRectangle drop_rect; GdkWindow *drop_outline; }; struct _MooBigPanedClass { GtkFrameClass parent_class; void (*set_pane_size) (MooBigPaned *paned, MooPanePosition position, int size); }; GType moo_big_paned_get_type (void) G_GNUC_CONST; GtkWidget *moo_big_paned_new (void); void moo_big_paned_set_pane_order (MooBigPaned *paned, int *order); MooPane *moo_big_paned_find_pane (MooBigPaned *paned, GtkWidget *pane_widget, MooPaned **child_paned); void moo_big_paned_add_child (MooBigPaned *paned, GtkWidget *widget); void moo_big_paned_remove_child (MooBigPaned *paned); GtkWidget *moo_big_paned_get_child (MooBigPaned *paned); MooPane *moo_big_paned_insert_pane (MooBigPaned *paned, GtkWidget *pane_widget, MooPaneLabel *pane_label, MooPanePosition position, int index_); gboolean moo_big_paned_remove_pane (MooBigPaned *paned, GtkWidget *pane_widget); GtkWidget *moo_big_paned_get_pane (MooBigPaned *paned, MooPanePosition position, int index_); MooPaned *moo_big_paned_get_paned (MooBigPaned *paned, MooPanePosition position); void moo_big_paned_open_pane (MooBigPaned *paned, GtkWidget *pane_widget); void moo_big_paned_hide_pane (MooBigPaned *paned, GtkWidget *pane_widget); void moo_big_paned_present_pane (MooBigPaned *paned, GtkWidget *pane_widget); void moo_big_paned_attach_pane (MooBigPaned *paned, GtkWidget *pane_widget); void moo_big_paned_detach_pane (MooBigPaned *paned, GtkWidget *pane_widget); G_END_DECLS #endif /* MOO_BIG_PANED_H */ PIDA-0.5.1/moo/moomarshals.list0000644000175000017500000000005110652670543014346 0ustar alialiBOOL:VOID VOID:INT VOID:OBJECT VOID:UINT PIDA-0.5.1/moo/moopane.c0000644000175000017500000010622010652670543012733 0ustar aliali/* * moopane.c * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "moomarshals.h" #include "moopaned.h" #include #include #include #define SPACING_IN_BUTTON 4 #ifdef MOO_COMPILATION #include "moostock.h" #include "mooutils-misc.h" #include "moocompat.h" #include "mooutils-gobject.h" #include "mooi18n.h" #else #define _(s) s #include "stock-moo.h" #define MOO_STOCK_HIDE ((const char*)MOO_HIDE_ICON) #define MOO_STOCK_STICKY ((const char*)MOO_STICKY_ICON) #define MOO_STOCK_CLOSE ((const char*)MOO_CLOSE_ICON) #define MOO_STOCK_DETACH ((const char*)MOO_DETACH_ICON) #define MOO_STOCK_ATTACH ((const char*)MOO_ATTACH_ICON) #define MOO_STOCK_KEEP_ON_TOP ((const char*)MOO_KEEP_ON_TOP_ICON) static void _moo_widget_set_tooltip (GtkWidget *widget, const char *tip) { static GtkTooltips *tooltips; g_return_if_fail (GTK_IS_WIDGET (widget)); if (!tooltips) tooltips = gtk_tooltips_new (); if (GTK_IS_TOOL_ITEM (widget)) gtk_tool_item_set_tooltip (GTK_TOOL_ITEM (widget), tooltips, tip, NULL); else gtk_tooltips_set_tip (tooltips, widget, tip, tip); } void _moo_window_set_icon_from_stock (GtkWindow *window, const char *name) { GdkPixbuf *icon; GtkStockItem dummy; g_return_if_fail (GTK_IS_WINDOW (window)); g_return_if_fail (name != NULL); if (gtk_stock_lookup (name, &dummy)) { icon = gtk_widget_render_icon (GTK_WIDGET (window), name, GTK_ICON_SIZE_BUTTON, 0); if (icon) { gtk_window_set_icon (GTK_WINDOW (window), icon); gdk_pixbuf_unref (icon); } } else { gtk_window_set_icon_name (GTK_WINDOW (window), name); } } #if GLIB_CHECK_VERSION(2,10,0) #define MOO_OBJECT_REF_SINK(obj) g_object_ref_sink (obj) #else #define MOO_OBJECT_REF_SINK(obj) gtk_object_sink (g_object_ref (obj)) #endif #endif struct _MooPane { GtkObject base; MooPaned *parent; GtkWidget *child; GtkWidget *child_holder; MooPaneLabel *label; GtkWidget *frame; GtkWidget *handle; GtkWidget *button; GtkWidget *label_widget; GtkWidget *icon_widget; GtkWidget *sticky_button; GtkWidget *detach_button; GtkWidget *close_button; /* XXX weak pointer */ gpointer focus_child; GtkWidget *window; GtkWidget *keep_on_top_button; GtkWidget *window_child_holder; MooPaneParams *params; guint detachable : 1; guint removable : 1; guint params_changed_blocked : 1; }; struct _MooPaneClass { GtkObjectClass base_class; gboolean (*remove) (MooPane *pane); }; G_DEFINE_TYPE (MooPane, moo_pane, GTK_TYPE_OBJECT) enum { PROP_0, PROP_LABEL, PROP_PARAMS, PROP_DETACHABLE, PROP_REMOVABLE }; enum { REMOVE, NUM_SIGNALS }; static guint signals[NUM_SIGNALS]; static void set_pane_window_icon_and_title (MooPane *pane) { if (pane->window && pane->label) { if (pane->label->icon_pixbuf) gtk_window_set_icon (GTK_WINDOW (pane->window), pane->label->icon_pixbuf); else if (pane->label->icon_stock_id) _moo_window_set_icon_from_stock (GTK_WINDOW (pane->window), pane->label->icon_stock_id); if (pane->label->window_title) gtk_window_set_title (GTK_WINDOW (pane->window), pane->label->window_title); else gtk_window_set_title (GTK_WINDOW (pane->window), pane->label->label); } } static void update_label_widgets (MooPane *pane) { if (pane->label && pane->label_widget) { gtk_label_set_text (GTK_LABEL (pane->label_widget), pane->label->label); g_object_set (pane->label_widget, "visible", pane->label->label != NULL, NULL); } if (pane->label && pane->icon_widget) { if (pane->label->icon_pixbuf) gtk_image_set_from_pixbuf (GTK_IMAGE (pane->icon_widget), pane->label->icon_pixbuf); else if (pane->label->icon_stock_id) gtk_image_set_from_stock (GTK_IMAGE (pane->icon_widget), pane->label->icon_stock_id, GTK_ICON_SIZE_MENU); g_object_set (pane->icon_widget, "visible", pane->label->icon_pixbuf || pane->label->icon_stock_id, NULL); } set_pane_window_icon_and_title (pane); } void moo_pane_set_label (MooPane *pane, MooPaneLabel *label) { MooPaneLabel *tmp; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (label != NULL); tmp = pane->label; pane->label = moo_pane_label_copy (label); moo_pane_label_free (tmp); update_label_widgets (pane); g_object_notify (G_OBJECT (pane), "label"); } MooPaneParams * moo_pane_get_params (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return moo_pane_params_copy (pane->params); } MooPaneLabel * moo_pane_get_label (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return moo_pane_label_copy (pane->label); } void moo_pane_set_params (MooPane *pane, MooPaneParams *params) { MooPaneParams *old_params; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (params != NULL); old_params = pane->params; pane->params = moo_pane_params_copy (params); if (old_params->detached != params->detached) { pane->params->detached = old_params->detached; if (old_params->detached) moo_paned_attach_pane (pane->parent, pane); else moo_paned_detach_pane (pane->parent, pane); } moo_pane_params_free (old_params); g_object_notify (G_OBJECT (pane), "params"); } void moo_pane_set_detachable (MooPane *pane, gboolean detachable) { g_return_if_fail (MOO_IS_PANE (pane)); if (detachable == pane->detachable) return; pane->detachable = detachable != 0; if (pane->params->detached && !detachable) moo_paned_attach_pane (pane->parent, pane); if (pane->detach_button) g_object_set (pane->detach_button, "visible", pane->detachable, NULL); g_object_notify (G_OBJECT (pane), "detachable"); } void moo_pane_set_removable (MooPane *pane, gboolean removable) { g_return_if_fail (MOO_IS_PANE (pane)); if (removable == pane->removable) return; pane->removable = removable != 0; if (pane->close_button) g_object_set (pane->close_button, "visible", pane->removable, NULL); g_object_notify (G_OBJECT (pane), "removable"); } gboolean moo_pane_get_detachable (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->detachable; } gboolean moo_pane_get_removable (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->removable; } static void moo_pane_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooPane *pane = MOO_PANE (object); switch (prop_id) { case PROP_LABEL: moo_pane_set_label (pane, g_value_get_boxed (value)); break; case PROP_PARAMS: moo_pane_set_params (pane, g_value_get_boxed (value)); break; case PROP_DETACHABLE: moo_pane_set_detachable (pane, g_value_get_boolean (value)); break; case PROP_REMOVABLE: moo_pane_set_removable (pane, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_pane_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooPane *pane = MOO_PANE (object); switch (prop_id) { case PROP_LABEL: g_value_set_boxed (value, pane->label); break; case PROP_PARAMS: g_value_set_boxed (value, pane->params); break; case PROP_DETACHABLE: g_value_set_boolean (value, pane->detachable != 0); break; case PROP_REMOVABLE: g_value_set_boolean (value, pane->removable != 0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_pane_init (MooPane *pane) { pane->detachable = TRUE; pane->removable = TRUE; pane->params = moo_pane_params_new (NULL, FALSE, FALSE, FALSE); pane->label = NULL; pane->child = NULL; pane->child_holder = NULL; pane->frame = NULL; pane->handle = NULL; pane->button = NULL; pane->label_widget = NULL; pane->icon_widget = NULL; pane->sticky_button = NULL; pane->detach_button = NULL; pane->close_button = NULL; pane->focus_child = NULL; pane->window = NULL; pane->keep_on_top_button = NULL; pane->window_child_holder = NULL; #ifdef MOO_COMPILATION _moo_stock_init (); #endif } static void moo_pane_finalize (GObject *object) { MooPane *pane = MOO_PANE (object); moo_pane_label_free (pane->label); moo_pane_params_free (pane->params); G_OBJECT_CLASS (moo_pane_parent_class)->finalize (object); } static void moo_pane_destroy (GtkObject *object) { MooPane *pane = MOO_PANE (object); if (pane->child) { GtkWidget *tmp = pane->child; pane->child = NULL; gtk_widget_destroy (tmp); g_object_unref (tmp); } if (pane->frame) { gtk_widget_unparent (pane->frame); pane->frame = NULL; } if (pane->window) { gtk_widget_destroy (pane->window); pane->window = NULL; } GTK_OBJECT_CLASS (moo_pane_parent_class)->destroy (object); } static void moo_pane_class_init (MooPaneClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass); gobject_class->set_property = moo_pane_set_property; gobject_class->get_property = moo_pane_get_property; gobject_class->finalize = moo_pane_finalize; gtkobject_class->destroy = moo_pane_destroy; g_object_class_install_property (gobject_class, PROP_LABEL, g_param_spec_boxed ("label", "label", "label", MOO_TYPE_PANE_LABEL, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_PARAMS, g_param_spec_boxed ("params", "params", "params", MOO_TYPE_PANE_PARAMS, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_DETACHABLE, g_param_spec_boolean ("detachable", "detachable", "detachable", TRUE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_REMOVABLE, g_param_spec_boolean ("removable", "removable", "removable", TRUE, G_PARAM_READWRITE)); signals[REMOVE] = g_signal_new ("remove", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooPaneClass, remove), g_signal_accumulator_true_handled, NULL, _moo_marshal_BOOL__VOID, G_TYPE_BOOLEAN, 0); } static void close_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) _moo_pane_try_remove (pane); } static void hide_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) moo_paned_hide_pane (pane->parent); } static void attach_button_clicked (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) _moo_paned_attach_pane (pane->parent, pane); } static void detach_button_clicked (MooPane *pane) { moo_paned_detach_pane (pane->parent, pane); } static void sticky_button_toggled (GtkToggleButton *button, MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); moo_paned_set_sticky_pane (pane->parent, gtk_toggle_button_get_active (button)); } static void update_sticky_button (MooPane *pane) { if (pane->parent) { gboolean sticky, active; g_object_get (pane->parent, "sticky-pane", &sticky, NULL); g_object_get (pane->sticky_button, "active", &active, NULL); if (active != sticky) g_object_set (pane->sticky_button, "active", sticky, NULL); } } static GtkWidget * create_button (MooPane *pane, GtkWidget *toolbar, const char *tip, gboolean toggle, int padding, const char *data) { GtkWidget *button; GtkWidget *icon; if (toggle) button = gtk_toggle_button_new (); else button = gtk_button_new (); g_object_set_data (G_OBJECT (button), "moo-pane", pane); gtk_button_set_focus_on_click (GTK_BUTTON (button), FALSE); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); _moo_widget_set_tooltip (button, tip); #ifdef MOO_COMPILATION icon = gtk_image_new_from_stock (data, MOO_ICON_SIZE_REAL_SMALL); #else { GdkPixbuf *pixbuf; pixbuf = gdk_pixbuf_new_from_inline (-1, (const guchar*)data, FALSE, NULL); icon = gtk_image_new_from_pixbuf (pixbuf); g_object_unref (pixbuf); } #endif gtk_container_add (GTK_CONTAINER (button), icon); gtk_box_pack_end (GTK_BOX (toolbar), button, FALSE, FALSE, padding); gtk_widget_show_all (button); return button; } static GtkWidget * create_frame_widget (MooPane *pane, MooPanePosition position, gboolean embedded) { GtkWidget *vbox, *toolbar, *separator, *handle, *table, *child_holder; vbox = gtk_vbox_new (FALSE, 0); gtk_widget_show (vbox); toolbar = gtk_hbox_new (FALSE, 0); handle = gtk_event_box_new (); gtk_widget_show (handle); gtk_box_pack_start (GTK_BOX (toolbar), handle, TRUE, TRUE, 3); pane->handle = handle; if (embedded) { GtkWidget *hide_button; pane->close_button = create_button (pane, toolbar, _("Remove pane"), FALSE, 3, MOO_STOCK_CLOSE); g_object_set_data (G_OBJECT (pane->close_button), "moo-pane", pane); g_signal_connect_swapped (pane->close_button, "clicked", G_CALLBACK (close_button_clicked), pane); hide_button = create_button (pane, toolbar, _("Hide pane"), FALSE, 0, MOO_STOCK_HIDE); pane->sticky_button = create_button (pane, toolbar, _("Sticky"), TRUE, 0, MOO_STOCK_STICKY); pane->detach_button = create_button (pane, toolbar, _("Detach pane"), FALSE, 0, MOO_STOCK_DETACH); g_signal_connect_swapped (hide_button, "clicked", G_CALLBACK (hide_button_clicked), pane); g_signal_connect_swapped (pane->detach_button, "clicked", G_CALLBACK (detach_button_clicked), pane); } else { GtkWidget *attach_button; attach_button = create_button (pane, toolbar, _("Attach"), FALSE, 0, MOO_STOCK_ATTACH); pane->keep_on_top_button = create_button (pane, toolbar, _("Keep on top"), TRUE, 0, MOO_STOCK_KEEP_ON_TOP); g_object_set_data (G_OBJECT (attach_button), "moo-pane", pane); g_signal_connect_swapped (attach_button, "clicked", G_CALLBACK (attach_button_clicked), pane); } gtk_widget_show (toolbar); gtk_box_pack_start (GTK_BOX (vbox), toolbar, FALSE, FALSE, 0); separator = gtk_hseparator_new (); gtk_widget_show (separator); gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 0); child_holder = gtk_vbox_new (FALSE, 0); gtk_widget_show (child_holder); gtk_box_pack_start (GTK_BOX (vbox), child_holder, TRUE, TRUE, 0); if (embedded) pane->child_holder = child_holder; else pane->window_child_holder = child_holder; table = gtk_table_new (2, 2, FALSE); switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: separator = gtk_vseparator_new (); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: separator = gtk_hseparator_new (); break; } gtk_widget_show (separator); switch (position) { case MOO_PANE_POS_LEFT: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 0, 1, 0, GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_TOP: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 0, 1, 0, GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_RIGHT: gtk_table_attach (GTK_TABLE (table), separator, 1, 2, 0, 1, 0, GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; case MOO_PANE_POS_BOTTOM: gtk_table_attach (GTK_TABLE (table), separator, 0, 1, 1, 2, 0, GTK_FILL, 0, 0); gtk_table_attach (GTK_TABLE (table), vbox, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); break; } return table; } static GtkWidget * create_label_widget (MooPanePosition position, GtkWidget **label_widget, GtkWidget **icon_widget) { GtkWidget *box = NULL; g_return_val_if_fail (position < 4, NULL); *label_widget = gtk_label_new (NULL); switch (position) { case MOO_PANE_POS_LEFT: gtk_label_set_angle (GTK_LABEL (*label_widget), 90); break; case MOO_PANE_POS_RIGHT: gtk_label_set_angle (GTK_LABEL (*label_widget), 270); break; default: break; } *icon_widget = gtk_image_new (); // else if (label->icon_stock_id) // icon = gtk_image_new_from_stock (label->icon_stock_id, // GTK_ICON_SIZE_MENU); // else if (label->icon_pixbuf) // icon = gtk_image_new_from_pixbuf (label->icon_pixbuf); // if (icon) // gtk_widget_show (icon); // if (text) // gtk_widget_show (text); switch (position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: box = gtk_vbox_new (FALSE, SPACING_IN_BUTTON); break; default: box = gtk_hbox_new (FALSE, SPACING_IN_BUTTON); break; } switch (position) { case MOO_PANE_POS_LEFT: gtk_box_pack_start (GTK_BOX (box), *label_widget, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box), *icon_widget, FALSE, FALSE, 0); break; default: gtk_box_pack_start (GTK_BOX (box), *icon_widget, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box), *label_widget, FALSE, FALSE, 0); break; } gtk_widget_show (box); return box; } static void paned_enable_detaching_notify (MooPane *pane) { gboolean enable; g_object_get (pane->parent, "enable-detaching", &enable, NULL); g_object_set (pane->detach_button, "visible", enable && pane->detachable, NULL); } static void paned_sticky_pane_notify (MooPane *pane) { update_sticky_button (pane); } static void create_widgets (MooPane *pane, MooPanePosition position, GdkWindow *pane_window) { GtkWidget *label; pane->frame = create_frame_widget (pane, position, TRUE); update_sticky_button (pane); gtk_widget_set_parent_window (pane->frame, pane_window); gtk_widget_set_parent (pane->frame, GTK_WIDGET (pane->parent)); gtk_box_pack_start (GTK_BOX (pane->child_holder), pane->child, TRUE, TRUE, 0); pane->button = gtk_toggle_button_new (); gtk_widget_show (pane->button); gtk_button_set_focus_on_click (GTK_BUTTON (pane->button), FALSE); // g_signal_connect (pane->button, "button-press-event", // G_CALLBACK (pane_button_click), paned); label = create_label_widget (position, &pane->label_widget, &pane->icon_widget); gtk_container_add (GTK_CONTAINER (pane->button), label); gtk_widget_show (label); update_label_widgets (pane); g_object_set_data (G_OBJECT (pane->button), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->child), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->frame), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->handle), "moo-pane", pane); g_signal_connect (pane->sticky_button, "toggled", G_CALLBACK (sticky_button_toggled), pane); } MooPane * _moo_pane_new (GtkWidget *child, MooPaneLabel *label) { MooPane *pane; g_return_val_if_fail (GTK_IS_WIDGET (child), NULL); pane = g_object_new (MOO_TYPE_PANE, NULL); pane->child = g_object_ref (child); gtk_widget_show (pane->child); g_object_set_data (G_OBJECT (pane->child), "moo-pane", pane); if (label) moo_pane_set_label (pane, label); return pane; } void _moo_pane_set_parent (MooPane *pane, gpointer parent, GdkWindow *pane_window) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (MOO_IS_PANED (parent)); g_return_if_fail (pane->parent == NULL); g_return_if_fail (pane->child != NULL); pane->parent = parent; create_widgets (pane, _moo_paned_get_position (parent), pane_window); g_signal_connect_swapped (parent, "notify::enable-detaching", G_CALLBACK (paned_enable_detaching_notify), pane); g_signal_connect_swapped (parent, "notify::sticky-pane", G_CALLBACK (paned_sticky_pane_notify), pane); } void _moo_pane_size_request (MooPane *pane, GtkRequisition *req) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_size_request (pane->frame, req); } void _moo_pane_size_allocate (MooPane *pane, GtkAllocation *allocation) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_size_allocate (pane->frame, allocation); } void _moo_pane_get_size_request (MooPane *pane, GtkRequisition *req) { g_return_if_fail (MOO_IS_PANE (pane) && pane->frame != NULL); gtk_widget_get_child_requisition (pane->frame, req); } GtkWidget * _moo_pane_get_frame (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->frame; } GtkWidget * _moo_pane_get_focus_child (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->focus_child; } GtkWidget * _moo_pane_get_button (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->button; } GtkWidget * _moo_pane_get_handle (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->handle; } GtkWidget * _moo_pane_get_window (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->window; } GtkWidget * moo_pane_get_child (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->child; } gpointer _moo_pane_get_parent (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), NULL); return pane->parent; } void _moo_pane_params_changed (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (!pane->params_changed_blocked) g_object_notify (G_OBJECT (pane), "params"); } void _moo_pane_freeze_params (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); pane->params_changed_blocked = TRUE; } void _moo_pane_thaw_params (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); pane->params_changed_blocked = FALSE; } gboolean _moo_pane_get_detached (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); return pane->params->detached; } void _moo_pane_unparent (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->parent) { g_signal_handlers_disconnect_by_func (pane->parent, (gpointer) paned_enable_detaching_notify, pane); g_signal_handlers_disconnect_by_func (pane->parent, (gpointer) paned_sticky_pane_notify, pane); pane->parent = NULL; gtk_container_remove (GTK_CONTAINER (pane->child_holder), pane->child); gtk_widget_unparent (pane->frame); pane->child_holder = NULL; pane->frame = NULL; pane->handle = NULL; pane->button = NULL; pane->label_widget = NULL; pane->icon_widget = NULL; pane->sticky_button = NULL; pane->detach_button = NULL; pane->close_button = NULL; if (pane->window) gtk_widget_destroy (pane->window); pane->window = NULL; pane->keep_on_top_button = NULL; pane->window_child_holder = NULL; pane->focus_child = NULL; } } static GtkWidget * find_focus (GtkWidget *widget) { GtkWidget *focus_child, *window; if (!widget) return NULL; window = gtk_widget_get_toplevel (widget); if (!GTK_IS_WINDOW (window)) return NULL; focus_child = gtk_window_get_focus (GTK_WINDOW (window)); if (focus_child && gtk_widget_is_ancestor (focus_child, widget)) return focus_child; else return NULL; } void _moo_pane_update_focus_child (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (pane->focus_child) g_object_remove_weak_pointer (pane->focus_child, &pane->focus_child); pane->focus_child = find_focus (pane->child); if (pane->focus_child) g_object_add_weak_pointer (pane->focus_child, &pane->focus_child); } static gboolean pane_window_delete_event (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); moo_paned_attach_pane (pane->parent, pane); return TRUE; } static void keep_on_top_button_toggled (GtkToggleButton *button, MooPane *pane) { gboolean active; g_return_if_fail (MOO_IS_PANE (pane)); active = gtk_toggle_button_get_active (button); pane->params->keep_on_top = active; if (pane->params->keep_on_top) { GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (pane->parent)); if (GTK_IS_WINDOW (parent)) gtk_window_set_transient_for (GTK_WINDOW (pane->window), GTK_WINDOW (parent)); } else { gtk_window_set_transient_for (GTK_WINDOW (pane->window), NULL); } _moo_pane_params_changed (pane); } static gboolean pane_window_configure (GtkWidget *window, GdkEventConfigure *event, MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), FALSE); g_return_val_if_fail (pane->window == window, FALSE); pane->params->window_position.x = event->x; pane->params->window_position.y = event->y; pane->params->window_position.width = event->width; pane->params->window_position.height = event->height; _moo_pane_params_changed (pane); return FALSE; } static void create_pane_window (MooPane *pane) { int width = -1; int height = -1; GtkWidget *frame; GtkWindow *window; if (pane->window) return; pane->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); window = GTK_WINDOW (pane->window); set_pane_window_icon_and_title (pane); switch (_moo_paned_get_position (pane->parent)) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: width = moo_paned_get_pane_size (pane->parent); height = GTK_WIDGET(pane->parent)->allocation.height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: height = moo_paned_get_pane_size (pane->parent); width = GTK_WIDGET(pane->parent)->allocation.width; break; } gtk_window_set_default_size (window, width, height); g_signal_connect_swapped (window, "delete-event", G_CALLBACK (pane_window_delete_event), pane); frame = create_frame_widget (pane, _moo_paned_get_position (pane->parent), FALSE); gtk_widget_show (frame); gtk_container_add (GTK_CONTAINER (pane->window), frame); g_object_set_data (G_OBJECT (pane->window), "moo-pane", pane); g_object_set_data (G_OBJECT (pane->keep_on_top_button), "moo-pane", pane); g_signal_connect (pane->keep_on_top_button, "toggled", G_CALLBACK (keep_on_top_button_toggled), pane); g_signal_connect (pane->window, "configure-event", G_CALLBACK (pane_window_configure), pane); } /* XXX gtk_widget_reparent() doesn't work here for some reasons */ /* shouldn't it work now, as I fixed GTK_NO_WINDOW flag? */ static void reparent (GtkWidget *widget, GtkWidget *old_container, GtkWidget *new_container) { g_object_ref (widget); gtk_container_remove (GTK_CONTAINER (old_container), widget); gtk_container_add (GTK_CONTAINER (new_container), widget); g_object_unref (widget); } void _moo_pane_detach (MooPane *pane) { gboolean visible; g_return_if_fail (MOO_IS_PANE (pane)); if (pane->params->detached) return; pane->params->detached = TRUE; create_pane_window (pane); reparent (pane->child, pane->child_holder, pane->window_child_holder); if (pane->params->keep_on_top) { GtkWidget *parent = gtk_widget_get_toplevel (GTK_WIDGET (pane->parent)); if (GTK_IS_WINDOW (parent)) gtk_window_set_transient_for (GTK_WINDOW (pane->window), GTK_WINDOW (parent)); } else { gtk_window_set_transient_for (GTK_WINDOW (pane->window), NULL); } if (pane->focus_child) gtk_widget_grab_focus (pane->focus_child); else gtk_widget_child_focus (pane->child, GTK_DIR_TAB_FORWARD); g_object_get (pane->window, "visible", &visible, NULL); if (!visible && pane->params->window_position.width > 0 && pane->params->window_position.height > 0) { gtk_window_move (GTK_WINDOW (pane->window), pane->params->window_position.x, pane->params->window_position.y); gtk_window_set_default_size (GTK_WINDOW (pane->window), pane->params->window_position.width, pane->params->window_position.height); } gtk_window_present (GTK_WINDOW (pane->window)); _moo_pane_params_changed (pane); } void _moo_pane_attach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); if (!pane->params->detached) return; pane->params->detached = FALSE; if (pane->focus_child) g_object_remove_weak_pointer (pane->focus_child, &pane->focus_child); pane->focus_child = find_focus (pane->child); if (pane->focus_child) g_object_add_weak_pointer (pane->focus_child, &pane->focus_child); reparent (pane->child, pane->window_child_holder, pane->child_holder); gtk_widget_hide (pane->window); _moo_pane_params_changed (pane); } void _moo_pane_try_remove (MooPane *pane) { gboolean ret; g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); g_object_ref (pane); g_signal_emit (pane, signals[REMOVE], 0, &ret); if (!ret && pane->parent && pane->child) moo_paned_remove_pane (pane->parent, pane->child); g_object_unref (pane); } void moo_pane_open (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_open_pane (pane->parent, pane); } void moo_pane_present (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_present_pane (pane->parent, pane); } void moo_pane_attach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_attach_pane (pane->parent, pane); } void moo_pane_detach (MooPane *pane) { g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (pane->parent != NULL); moo_paned_detach_pane (pane->parent, pane); } int moo_pane_get_index (MooPane *pane) { g_return_val_if_fail (MOO_IS_PANE (pane), -1); if (pane->parent) return moo_paned_get_pane_num (pane->parent, pane->child); else return -1; } PIDA-0.5.1/moo/moopane.h0000644000175000017500000001332210652670543012740 0ustar aliali/* * moopane.h * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifndef MOO_PANE_H #define MOO_PANE_H #include G_BEGIN_DECLS #define MOO_TYPE_PANE (moo_pane_get_type ()) #define MOO_PANE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_PANE, MooPane)) #define MOO_PANE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_PANE, MooPaneClass)) #define MOO_IS_PANE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), MOO_TYPE_PANE)) #define MOO_IS_PANE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_PANE)) #define MOO_PANE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_PANE, MooPaneClass)) #define MOO_TYPE_PANE_LABEL (moo_pane_label_get_type ()) #define MOO_TYPE_PANE_PARAMS (moo_pane_params_get_type ()) typedef struct _MooPane MooPane; typedef struct _MooPaneClass MooPaneClass; typedef struct _MooPaneLabel MooPaneLabel; typedef struct _MooPaneParams MooPaneParams; struct _MooPaneLabel { char *icon_stock_id; GdkPixbuf *icon_pixbuf; char *label; char *window_title; }; struct _MooPaneParams { GdkRectangle window_position; guint detached : 1; guint maximized : 1; guint keep_on_top : 1; }; GType moo_pane_get_type (void) G_GNUC_CONST; GType moo_pane_label_get_type (void) G_GNUC_CONST; GType moo_pane_params_get_type (void) G_GNUC_CONST; void moo_pane_set_label (MooPane *pane, MooPaneLabel *label); /* result must be freed with moo_pane_label_free() */ MooPaneLabel *moo_pane_get_label (MooPane *pane); void moo_pane_set_params (MooPane *pane, MooPaneParams *params); /* result must be freed with moo_pane_params_free() */ MooPaneParams *moo_pane_get_params (MooPane *pane); void moo_pane_set_detachable (MooPane *pane, gboolean detachable); gboolean moo_pane_get_detachable (MooPane *pane); void moo_pane_set_removable (MooPane *pane, gboolean removable); gboolean moo_pane_get_removable (MooPane *pane); GtkWidget *moo_pane_get_child (MooPane *pane); int moo_pane_get_index (MooPane *pane); void moo_pane_open (MooPane *pane); void moo_pane_present (MooPane *pane); void moo_pane_attach (MooPane *pane); void moo_pane_detach (MooPane *pane); MooPaneParams *moo_pane_params_new (GdkRectangle *window_position, gboolean detached, gboolean maximized, gboolean keep_on_top); MooPaneParams *moo_pane_params_copy (MooPaneParams *params); void moo_pane_params_free (MooPaneParams *params); MooPaneLabel *moo_pane_label_new (const char *icon_stock_id, GdkPixbuf *pixbuf, const char *label, const char *window_title); MooPaneLabel *moo_pane_label_copy (MooPaneLabel *label); void moo_pane_label_free (MooPaneLabel *label); MooPane *_moo_pane_new (GtkWidget *child, MooPaneLabel *label); gpointer _moo_pane_get_parent (MooPane *pane); GtkWidget *_moo_pane_get_frame (MooPane *pane); void _moo_pane_update_focus_child (MooPane *pane); GtkWidget *_moo_pane_get_focus_child (MooPane *pane); GtkWidget *_moo_pane_get_button (MooPane *pane); GtkWidget *_moo_pane_get_handle (MooPane *pane); GtkWidget *_moo_pane_get_window (MooPane *pane); void _moo_pane_params_changed (MooPane *pane); void _moo_pane_freeze_params (MooPane *pane); void _moo_pane_thaw_params (MooPane *pane); void _moo_pane_size_request (MooPane *pane, GtkRequisition *req); void _moo_pane_get_size_request (MooPane *pane, GtkRequisition *req); void _moo_pane_size_allocate (MooPane *pane, GtkAllocation *allocation); gboolean _moo_pane_get_detached (MooPane *pane); void _moo_pane_attach (MooPane *pane); void _moo_pane_detach (MooPane *pane); void _moo_pane_set_parent (MooPane *pane, gpointer parent, GdkWindow *window); void _moo_pane_unparent (MooPane *pane); void _moo_pane_try_remove (MooPane *pane); G_END_DECLS #endif /* MOO_PANE_H */ PIDA-0.5.1/moo/moopaned.c0000644000175000017500000030516510652670543013110 0ustar aliali/* * moopaned.c * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "moomarshals.h" #include "moopaned.h" #include #include #include #ifdef MOO_COMPILATION # include "mooutils-gobject.h" #else # if GLIB_CHECK_VERSION(2,10,0) # define MOO_OBJECT_REF_SINK(obj) g_object_ref_sink (obj) # else # define MOO_OBJECT_REF_SINK(obj) gtk_object_sink (g_object_ref (obj)) # endif #endif #define MIN_PANE_SIZE 10 typedef enum { FOCUS_NONE = 0, FOCUS_CHILD, FOCUS_PANE, FOCUS_BUTTON } FocusPosition; struct _MooPanedPrivate { MooPanePosition pane_position; GdkWindow *bin_window; GdkWindow *handle_window; GdkWindow *pane_window; /* XXX weak pointer */ gpointer focus_child; /* focused grandchild of bin->child */ MooPane *focus_pane; FocusPosition focus; gboolean button_real_focus; /* button was focused by keyboard navigation */ gboolean dont_move_focus; /* do not try to move focus in open_pane/hide_pane */ MooPane *current_pane; GSList *panes; gboolean close_on_child_focus; int position; gboolean button_box_visible; int button_box_size; gboolean handle_visible; int handle_size; gboolean pane_widget_visible; int pane_widget_size; gboolean enable_border; int border_size; gboolean sticky; gboolean handle_prelit; gboolean in_drag; int drag_start; gboolean enable_handle_drag; gboolean handle_in_drag; gboolean handle_button_pressed; int handle_drag_start_x; int handle_drag_start_y; GdkCursorType handle_cursor_type; guint enable_detaching : 1; }; static void moo_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void moo_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static GObject *moo_paned_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties); static void moo_paned_destroy (GtkObject *object); static void moo_paned_realize (GtkWidget *widget); static void moo_paned_unrealize (GtkWidget *widget); static void moo_paned_map (GtkWidget *widget); static void moo_paned_unmap (GtkWidget *widget); static gboolean moo_paned_focus (GtkWidget *widget, GtkDirectionType direction); static void moo_paned_set_focus_child (GtkContainer *container, GtkWidget *widget); static void moo_paned_size_request (GtkWidget *widget, GtkRequisition *requisition); static void moo_paned_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gboolean moo_paned_expose (GtkWidget *widget, GdkEventExpose *event); static gboolean moo_paned_motion (GtkWidget *widget, GdkEventMotion *event); static gboolean moo_paned_enter (GtkWidget *widget, GdkEventCrossing *event); static gboolean moo_paned_leave (GtkWidget *widget, GdkEventCrossing *event); static gboolean moo_paned_button_press (GtkWidget *widget, GdkEventButton *event); static gboolean moo_paned_button_release (GtkWidget *widget, GdkEventButton *event); static gboolean moo_paned_key_press (GtkWidget *widget, GdkEventKey *event); static int pane_index (MooPaned *paned, MooPane *pane); static MooPane *get_nth_pane (MooPaned *paned, guint index_); static void moo_paned_open_pane_real (MooPaned *paned, guint index); static void moo_paned_hide_pane_real (MooPaned *paned); static void moo_paned_set_pane_size_real(MooPaned *paned, int size); static void moo_paned_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data); static void moo_paned_add (GtkContainer *container, GtkWidget *widget); static void moo_paned_remove (GtkContainer *container, GtkWidget *widget); static void realize_handle (MooPaned *paned); static void realize_pane (MooPaned *paned); static void draw_handle (MooPaned *paned, GdkEventExpose *event); static void draw_border (MooPaned *paned, GdkEventExpose *event); static void button_box_visible_notify (MooPaned *paned); static void pane_button_toggled (GtkToggleButton *button, MooPaned *paned); static gboolean handle_button_press (GtkWidget *widget, GdkEventButton *event, MooPaned *paned); static gboolean handle_button_release (GtkWidget *widget, GdkEventButton *event, MooPaned *paned); static gboolean handle_motion (GtkWidget *widget, GdkEventMotion *event, MooPaned *paned); static gboolean handle_expose (GtkWidget *widget, GdkEventExpose *event, MooPaned *paned); static void handle_realize (GtkWidget *widget, MooPaned *paned); static void moo_paned_set_handle_cursor_type (MooPaned *paned, GdkCursorType cursor_type, gboolean really_set); static void moo_paned_set_enable_detaching (MooPaned *paned, gboolean enable); G_DEFINE_TYPE (MooPaned, moo_paned, GTK_TYPE_BIN) enum { PANED_PROP_0, PANED_PROP_PANE_POSITION, PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS, PANED_PROP_STICKY_PANE, PANED_PROP_ENABLE_HANDLE_DRAG, PANED_PROP_HANDLE_CURSOR_TYPE, PANED_PROP_ENABLE_DETACHING, PANED_PROP_ENABLE_BORDER }; enum { PANED_SET_PANE_SIZE, PANED_HANDLE_DRAG_START, PANED_HANDLE_DRAG_MOTION, PANED_HANDLE_DRAG_END, PANED_PANE_PARAMS_CHANGED, PANED_NUM_SIGNALS }; static guint paned_signals[PANED_NUM_SIGNALS]; static void moo_paned_class_init (MooPanedClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); g_type_class_add_private (klass, sizeof (MooPanedPrivate)); gobject_class->set_property = moo_paned_set_property; gobject_class->get_property = moo_paned_get_property; gobject_class->constructor = moo_paned_constructor; gtkobject_class->destroy = moo_paned_destroy; widget_class->realize = moo_paned_realize; widget_class->unrealize = moo_paned_unrealize; widget_class->map = moo_paned_map; widget_class->unmap = moo_paned_unmap; widget_class->expose_event = moo_paned_expose; widget_class->size_request = moo_paned_size_request; widget_class->size_allocate = moo_paned_size_allocate; widget_class->motion_notify_event = moo_paned_motion; widget_class->enter_notify_event = moo_paned_enter; widget_class->leave_notify_event = moo_paned_leave; widget_class->button_press_event = moo_paned_button_press; widget_class->button_release_event = moo_paned_button_release; widget_class->focus = moo_paned_focus; widget_class->key_press_event = moo_paned_key_press; container_class->forall = moo_paned_forall; container_class->set_focus_child = moo_paned_set_focus_child; container_class->remove = moo_paned_remove; container_class->add = moo_paned_add; klass->set_pane_size = moo_paned_set_pane_size_real; g_object_class_install_property (gobject_class, PANED_PROP_PANE_POSITION, g_param_spec_enum ("pane-position", "pane-position", "pane-position", MOO_TYPE_PANE_POSITION, MOO_PANE_POS_LEFT, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS, g_param_spec_boolean ("close-pane-on-child-focus", "close-pane-on-child-focus", "close-pane-on-child-focus", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PANED_PROP_STICKY_PANE, g_param_spec_boolean ("sticky-pane", "sticky-pane", "sticky-pane", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_HANDLE_DRAG, g_param_spec_boolean ("enable-handle-drag", "enable-handle-drag", "enable-handle-drag", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_DETACHING, g_param_spec_boolean ("enable-detaching", "enable-detaching", "enable-detaching", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PANED_PROP_HANDLE_CURSOR_TYPE, g_param_spec_enum ("handle-cursor-type", "handle-cursor-type", "handle-cursor-type", GDK_TYPE_CURSOR_TYPE, GDK_HAND2, G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PANED_PROP_ENABLE_BORDER, g_param_spec_boolean ("enable-border", "enable-border", "enable-border", TRUE, G_PARAM_CONSTRUCT | G_PARAM_READWRITE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("handle-size", "handle-size", "handle-size", 0, G_MAXINT, 5, G_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, g_param_spec_int ("button-spacing", "button-spacing", "button-spacing", 0, G_MAXINT, 0, G_PARAM_READABLE)); paned_signals[PANED_SET_PANE_SIZE] = g_signal_new ("set-pane-size", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (MooPanedClass, set_pane_size), NULL, NULL, _moo_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); paned_signals[PANED_HANDLE_DRAG_START] = g_signal_new ("handle-drag-start", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooPanedClass, handle_drag_start), NULL, NULL, _moo_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); paned_signals[PANED_HANDLE_DRAG_MOTION] = g_signal_new ("handle-drag-motion", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooPanedClass, handle_drag_motion), NULL, NULL, _moo_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); paned_signals[PANED_HANDLE_DRAG_END] = g_signal_new ("handle-drag-end", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooPanedClass, handle_drag_end), NULL, NULL, _moo_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GTK_TYPE_WIDGET); paned_signals[PANED_PANE_PARAMS_CHANGED] = g_signal_new ("pane-params-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooPanedClass, pane_params_changed), NULL, NULL, _moo_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT); } static void moo_paned_init (MooPaned *paned) { GTK_WIDGET_SET_FLAGS (paned, GTK_NO_WINDOW); paned->priv = G_TYPE_INSTANCE_GET_PRIVATE (paned, MOO_TYPE_PANED, MooPanedPrivate); paned->button_box = NULL; paned->priv->pane_position = -1; paned->priv->handle_window = NULL; paned->priv->pane_window = NULL; paned->priv->bin_window = NULL; paned->priv->current_pane = NULL; paned->priv->panes = NULL; paned->priv->enable_border = TRUE; paned->priv->button_box_visible = FALSE; paned->priv->button_box_size = 0; paned->priv->handle_visible = FALSE; paned->priv->handle_size = 0; paned->priv->pane_widget_visible = FALSE; paned->priv->pane_widget_size = 0; paned->priv->sticky = FALSE; paned->priv->position = -1; paned->priv->handle_prelit = FALSE; paned->priv->in_drag = FALSE; paned->priv->drag_start = -1; } static GObject* moo_paned_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GObject *object; MooPaned *paned; int button_spacing; object = G_OBJECT_CLASS(moo_paned_parent_class)->constructor (type, n_construct_properties, construct_properties); paned = MOO_PANED (object); gtk_widget_style_get (GTK_WIDGET (paned), "button-spacing", &button_spacing, NULL); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->button_box = gtk_vbox_new (FALSE, button_spacing); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->button_box = gtk_hbox_new (FALSE, button_spacing); break; default: g_warning ("%s: invalid 'pane-position' property value '%u'," "falling back to MOO_PANE_POS_LEFT", G_STRLOC, paned->priv->pane_position); paned->priv->pane_position = MOO_PANE_POS_LEFT; paned->button_box = gtk_vbox_new (FALSE, button_spacing); break; } MOO_OBJECT_REF_SINK (paned->button_box); gtk_widget_set_parent_window (paned->button_box, paned->priv->bin_window); gtk_widget_set_parent (paned->button_box, GTK_WIDGET (paned)); gtk_widget_show (paned->button_box); g_signal_connect_swapped (paned->button_box, "notify::visible", G_CALLBACK (button_box_visible_notify), paned); return object; } static void moo_paned_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooPaned *paned = MOO_PANED (object); switch (prop_id) { case PANED_PROP_PANE_POSITION: paned->priv->pane_position = g_value_get_enum (value); break; case PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS: paned->priv->close_on_child_focus = g_value_get_boolean (value); g_object_notify (object, "close-pane-on-child-focus"); break; case PANED_PROP_ENABLE_BORDER: paned->priv->enable_border = g_value_get_boolean (value); gtk_widget_queue_resize (GTK_WIDGET (paned)); g_object_notify (object, "enable_border"); break; case PANED_PROP_STICKY_PANE: moo_paned_set_sticky_pane (paned, g_value_get_boolean (value)); break; case PANED_PROP_ENABLE_HANDLE_DRAG: paned->priv->enable_handle_drag = g_value_get_boolean (value); if (!paned->priv->enable_handle_drag) moo_paned_set_handle_cursor_type (paned, 0, FALSE); else moo_paned_set_handle_cursor_type (paned, paned->priv->handle_cursor_type, TRUE); g_object_notify (object, "enable-handle-drag"); break; case PANED_PROP_ENABLE_DETACHING: moo_paned_set_enable_detaching (paned, g_value_get_boolean (value)); break; case PANED_PROP_HANDLE_CURSOR_TYPE: if (paned->priv->enable_handle_drag) moo_paned_set_handle_cursor_type (paned, g_value_get_enum (value), TRUE); else paned->priv->handle_cursor_type = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_paned_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooPaned *paned = MOO_PANED (object); switch (prop_id) { case PANED_PROP_PANE_POSITION: g_value_set_enum (value, paned->priv->pane_position); break; case PANED_PROP_CLOSE_PANE_ON_CHILD_FOCUS: g_value_set_boolean (value, paned->priv->close_on_child_focus); break; case PANED_PROP_STICKY_PANE: g_value_set_boolean (value, paned->priv->sticky); break; case PANED_PROP_ENABLE_HANDLE_DRAG: g_value_set_boolean (value, paned->priv->enable_handle_drag); break; case PANED_PROP_ENABLE_BORDER: g_value_set_boolean (value, paned->priv->enable_border); break; case PANED_PROP_ENABLE_DETACHING: g_value_set_boolean (value, paned->priv->enable_detaching ? TRUE : FALSE); break; case PANED_PROP_HANDLE_CURSOR_TYPE: g_value_set_enum (value, paned->priv->handle_cursor_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void moo_paned_destroy (GtkObject *object) { GSList *l; MooPaned *paned = MOO_PANED (object); for (l = paned->priv->panes; l != NULL; l = l->next) gtk_object_destroy (l->data); GTK_OBJECT_CLASS(moo_paned_parent_class)->destroy (object); for (l = paned->priv->panes; l != NULL; l = l->next) g_object_unref (l->data); g_slist_free (paned->priv->panes); paned->priv->panes = NULL; } GtkWidget* moo_paned_new (MooPanePosition pane_position) { return GTK_WIDGET (g_object_new (MOO_TYPE_PANED, "pane-position", pane_position, NULL)); } MooPanePosition _moo_paned_get_position (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), 0); return paned->priv->pane_position; } static void moo_paned_realize (GtkWidget *widget) { static GdkWindowAttr attributes; gint attributes_mask; MooPaned *paned; paned = MOO_PANED (widget); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); widget->window = gtk_widget_get_parent_window (widget); g_object_ref (widget->window); attributes.x = widget->allocation.x; attributes.y = widget->allocation.y; attributes.width = widget->allocation.width; attributes.height = widget->allocation.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.wclass = GDK_INPUT_OUTPUT; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; paned->priv->bin_window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask); gdk_window_set_user_data (paned->priv->bin_window, widget); widget->style = gtk_style_attach (widget->style, widget->window); gtk_style_set_background (widget->style, paned->priv->bin_window, GTK_STATE_NORMAL); realize_pane (paned); if (paned->button_box) gtk_widget_set_parent_window (paned->button_box, paned->priv->bin_window); if (GTK_BIN(paned)->child) gtk_widget_set_parent_window (GTK_BIN(paned)->child, paned->priv->bin_window); } static void realize_handle (MooPaned *paned) { static GdkWindowAttr attributes; gint attributes_mask; GtkWidget *widget = GTK_WIDGET (paned); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: attributes.y = 0; attributes.width = paned->priv->handle_size; attributes.height = widget->allocation.height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: attributes.x = 0; attributes.width = widget->allocation.width; attributes.height = paned->priv->handle_size; break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: attributes.x = paned->priv->pane_widget_size; break; case MOO_PANE_POS_RIGHT: attributes.x = 0; break; case MOO_PANE_POS_TOP: attributes.y = paned->priv->pane_widget_size; break; case MOO_PANE_POS_BOTTOM: attributes.y = 0; break; } attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_POINTER_MOTION_HINT_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_EXPOSURE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.wclass = GDK_INPUT_OUTPUT; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: attributes.cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: attributes.cursor = gdk_cursor_new (GDK_SB_V_DOUBLE_ARROW); break; } attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR; paned->priv->handle_window = gdk_window_new (paned->priv->pane_window, &attributes, attributes_mask); gdk_window_set_user_data (paned->priv->handle_window, widget); gtk_style_set_background (widget->style, paned->priv->handle_window, GTK_STATE_NORMAL); gdk_cursor_unref (attributes.cursor); } static void get_pane_window_rect (MooPaned *paned, GdkRectangle *rect) { *rect = GTK_WIDGET(paned)->allocation; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: rect->width = paned->priv->pane_widget_size + paned->priv->handle_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: rect->height = paned->priv->pane_widget_size + paned->priv->handle_size; break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: rect->x += paned->priv->button_box_size; break; case MOO_PANE_POS_RIGHT: rect->x += GTK_WIDGET(paned)->allocation.width - rect->width - paned->priv->button_box_size; break; case MOO_PANE_POS_TOP: rect->y += paned->priv->button_box_size; break; case MOO_PANE_POS_BOTTOM: rect->y += GTK_WIDGET(paned)->allocation.height - rect->height - paned->priv->button_box_size; break; } } static void realize_pane (MooPaned *paned) { static GdkWindowAttr attributes; gint attributes_mask; GtkWidget *widget = GTK_WIDGET (paned); GdkRectangle rect; get_pane_window_rect (paned, &rect); attributes.x = rect.x; attributes.y = rect.y; attributes.width = rect.width; attributes.height = rect.height; attributes.window_type = GDK_WINDOW_CHILD; attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; attributes.visual = gtk_widget_get_visual (widget); attributes.colormap = gtk_widget_get_colormap (widget); attributes.wclass = GDK_INPUT_OUTPUT; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; paned->priv->pane_window = gdk_window_new (widget->window, &attributes, attributes_mask); gdk_window_set_user_data (paned->priv->pane_window, widget); gtk_style_set_background (widget->style, paned->priv->pane_window, GTK_STATE_NORMAL); realize_handle (paned); } static void moo_paned_unrealize (GtkWidget *widget) { MooPaned *paned = MOO_PANED (widget); if (paned->priv->handle_window) { gdk_window_set_user_data (paned->priv->handle_window, NULL); gdk_window_destroy (paned->priv->handle_window); paned->priv->handle_window = NULL; paned->priv->handle_visible = FALSE; paned->priv->handle_size = 0; } if (paned->priv->pane_window) { gdk_window_set_user_data (paned->priv->pane_window, NULL); gdk_window_destroy (paned->priv->pane_window); paned->priv->pane_window = NULL; paned->priv->pane_widget_visible = FALSE; paned->priv->pane_widget_size = 0; } if (paned->priv->bin_window) { gdk_window_set_user_data (paned->priv->bin_window, NULL); gdk_window_destroy (paned->priv->bin_window); paned->priv->bin_window = NULL; } GTK_WIDGET_CLASS(moo_paned_parent_class)->unrealize (widget); } static void add_button_box_requisition (MooPaned *paned, GtkRequisition *requisition, GtkRequisition *child_requisition) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: requisition->width += child_requisition->width; requisition->height = MAX (child_requisition->height, requisition->height); paned->priv->button_box_size = child_requisition->width; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: requisition->height += child_requisition->height; requisition->width = MAX (child_requisition->width, requisition->width); paned->priv->button_box_size = child_requisition->height; break; } } static void add_handle_requisition (MooPaned *paned, GtkRequisition *requisition) { gtk_widget_style_get (GTK_WIDGET (paned), "handle_size", &paned->priv->handle_size, NULL); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: requisition->width += paned->priv->handle_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: requisition->height += paned->priv->handle_size; break; } } static void add_pane_widget_requisition (MooPaned *paned, GtkRequisition *requisition, GtkRequisition *child_requisition) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: requisition->height = MAX (child_requisition->height, requisition->height); if (paned->priv->sticky) { requisition->width += child_requisition->width; } else { requisition->width = MAX (child_requisition->width + paned->priv->button_box_size + paned->priv->handle_size, requisition->width); } break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: requisition->width = MAX (child_requisition->width, requisition->width); if (paned->priv->sticky) { requisition->height += child_requisition->height; } else { requisition->height = MAX (child_requisition->height + paned->priv->button_box_size + paned->priv->handle_size, requisition->height); } break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->priv->pane_widget_size = MAX (paned->priv->position, child_requisition->width); paned->priv->position = paned->priv->pane_widget_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->priv->pane_widget_size = MAX (paned->priv->position, child_requisition->height); paned->priv->position = paned->priv->pane_widget_size; break; } } static void moo_paned_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkBin *bin = GTK_BIN (widget); MooPaned *paned = MOO_PANED (widget); GtkRequisition child_requisition; requisition->width = 0; requisition->height = 0; if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) { gtk_widget_size_request (bin->child, &child_requisition); requisition->width += child_requisition.width; requisition->height += child_requisition.height; } if (paned->priv->button_box_visible) { gtk_widget_size_request (paned->button_box, &child_requisition); add_button_box_requisition (paned, requisition, &child_requisition); } else { paned->priv->button_box_size = 0; } if (paned->priv->handle_visible) add_handle_requisition (paned, requisition); else paned->priv->handle_size = 0; if (paned->priv->pane_widget_visible) { _moo_pane_size_request (paned->priv->current_pane, &child_requisition); add_pane_widget_requisition (paned, requisition, &child_requisition); } else { paned->priv->pane_widget_size = 0; } if (paned->priv->enable_border) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->priv->border_size = widget->style->xthickness; requisition->width += paned->priv->border_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->priv->border_size = widget->style->ythickness; requisition->height += paned->priv->border_size; break; } } else { paned->priv->border_size = 0; } } static void get_pane_widget_allocation (MooPaned *paned, GtkAllocation *allocation) { allocation->x = 0; allocation->y = 0; allocation->width = GTK_WIDGET(paned)->allocation.width; allocation->height = GTK_WIDGET(paned)->allocation.height; switch (paned->priv->pane_position) { case MOO_PANE_POS_RIGHT: allocation->x += paned->priv->handle_size; /* fall through */ case MOO_PANE_POS_LEFT: allocation->width = paned->priv->pane_widget_size; break; case MOO_PANE_POS_BOTTOM: allocation->y += paned->priv->handle_size; /* fall through */ case MOO_PANE_POS_TOP: allocation->height = paned->priv->pane_widget_size; break; } } static void get_button_box_allocation (MooPaned *paned, GtkAllocation *allocation) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: allocation->y = 0; allocation->height = GTK_WIDGET(paned)->allocation.height; allocation->width = paned->priv->button_box_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: allocation->x = 0; allocation->width = GTK_WIDGET(paned)->allocation.width; allocation->height = paned->priv->button_box_size; break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: allocation->x = 0; break; case MOO_PANE_POS_RIGHT: allocation->x = GTK_WIDGET(paned)->allocation.width - allocation->width; break; case MOO_PANE_POS_TOP: allocation->y = 0; break; case MOO_PANE_POS_BOTTOM: allocation->y = GTK_WIDGET(paned)->allocation.height - allocation->height; break; } } static void get_bin_child_allocation (MooPaned *paned, GtkAllocation *allocation) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: allocation->y = 0; allocation->height = GTK_WIDGET(paned)->allocation.height; allocation->width = GTK_WIDGET(paned)->allocation.width - paned->priv->button_box_size - paned->priv->border_size; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: allocation->x = 0; allocation->width = GTK_WIDGET(paned)->allocation.width; allocation->height = GTK_WIDGET(paned)->allocation.height - paned->priv->button_box_size - paned->priv->border_size; break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: allocation->x = paned->priv->button_box_size + paned->priv->border_size; break; case MOO_PANE_POS_RIGHT: allocation->x = 0; break; case MOO_PANE_POS_TOP: allocation->y = paned->priv->button_box_size + paned->priv->border_size; break; case MOO_PANE_POS_BOTTOM: allocation->y = 0; break; } if (paned->priv->sticky) { int add = paned->priv->handle_size + paned->priv->pane_widget_size; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: allocation->x += add; allocation->width -= add; break; case MOO_PANE_POS_RIGHT: allocation->width -= add; break; case MOO_PANE_POS_TOP: allocation->y += add; allocation->height -= add; break; case MOO_PANE_POS_BOTTOM: allocation->height -= add; break; } } } static void clamp_handle_size (MooPaned *paned) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0, GTK_WIDGET(paned)->allocation.width); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->priv->handle_size = CLAMP (paned->priv->handle_size, 0, GTK_WIDGET(paned)->allocation.height); break; } } static void clamp_button_box_size (MooPaned *paned) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0, GTK_WIDGET(paned)->allocation.width - paned->priv->handle_size); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->priv->button_box_size = CLAMP (paned->priv->button_box_size, 0, GTK_WIDGET(paned)->allocation.height - paned->priv->handle_size); break; } } static void clamp_child_requisition (MooPaned *paned, GtkRequisition *requisition) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: requisition->width = CLAMP (requisition->width, 0, GTK_WIDGET(paned)->allocation.width - paned->priv->handle_size - paned->priv->button_box_size - paned->priv->border_size); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: requisition->height = CLAMP (requisition->height, 0, GTK_WIDGET(paned)->allocation.height - paned->priv->handle_size - paned->priv->button_box_size - paned->priv->border_size); break; } } static void clamp_pane_widget_size (MooPaned *paned, GtkRequisition *child_requisition) { int min_size; int max_size = 0; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: max_size = GTK_WIDGET(paned)->allocation.width - paned->priv->handle_size - paned->priv->button_box_size; if (paned->priv->sticky) max_size -= child_requisition->width; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: max_size = GTK_WIDGET(paned)->allocation.height - paned->priv->handle_size - paned->priv->button_box_size; if (paned->priv->sticky) max_size -= child_requisition->height; break; } min_size = CLAMP (MIN_PANE_SIZE, 0, max_size); paned->priv->pane_widget_size = CLAMP (paned->priv->pane_widget_size, min_size, max_size); } static void get_handle_window_rect (MooPaned *paned, GdkRectangle *rect) { switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: rect->y = 0; rect->width = paned->priv->handle_size; rect->height = GTK_WIDGET(paned)->allocation.height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: rect->x = 0; rect->height = paned->priv->handle_size; rect->width = GTK_WIDGET(paned)->allocation.width; break; } switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: rect->x = paned->priv->pane_widget_size; break; case MOO_PANE_POS_RIGHT: rect->x = 0; break; case MOO_PANE_POS_TOP: rect->y = paned->priv->pane_widget_size; break; case MOO_PANE_POS_BOTTOM: rect->y = 0; break; } } static void moo_paned_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { GtkBin *bin; MooPaned *paned; GtkAllocation child_allocation; GtkRequisition child_requisition = {0, 0}; widget->allocation = *allocation; bin = GTK_BIN (widget); paned = MOO_PANED (widget); if (!paned->priv->handle_visible) paned->priv->handle_size = 0; if (!paned->priv->button_box_visible) paned->priv->button_box_size = 0; if (!paned->priv->pane_widget_visible) paned->priv->pane_widget_size = 0; if (bin->child && GTK_WIDGET_VISIBLE (bin->child)) gtk_widget_get_child_requisition (bin->child, &child_requisition); if (paned->priv->handle_visible) clamp_handle_size (paned); if (paned->priv->button_box_visible) clamp_button_box_size (paned); clamp_child_requisition (paned, &child_requisition); if (paned->priv->pane_widget_visible) { clamp_pane_widget_size (paned, &child_requisition); paned->priv->position = paned->priv->pane_widget_size; } if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (paned->priv->bin_window, allocation->x, allocation->y, allocation->width, allocation->height); if (paned->priv->button_box_visible) { get_button_box_allocation (paned, &child_allocation); gtk_widget_size_allocate (paned->button_box, &child_allocation); } if (bin->child) { get_bin_child_allocation (paned, &child_allocation); gtk_widget_size_allocate (bin->child, &child_allocation); } if (GTK_WIDGET_REALIZED (widget)) { GdkRectangle rect; if (paned->priv->pane_widget_visible) { get_pane_window_rect (paned, &rect); gdk_window_move_resize (paned->priv->pane_window, rect.x, rect.y, rect.width, rect.height); } if (paned->priv->handle_visible) { get_handle_window_rect (paned, &rect); gdk_window_move_resize (paned->priv->handle_window, rect.x, rect.y, rect.width, rect.height); } } if (paned->priv->pane_widget_visible) { get_pane_widget_allocation (paned, &child_allocation); _moo_pane_size_allocate (paned->priv->current_pane, &child_allocation); } } static void moo_paned_map (GtkWidget *widget) { MooPaned *paned = MOO_PANED (widget); gdk_window_show (paned->priv->bin_window); GTK_WIDGET_CLASS(moo_paned_parent_class)->map (widget); if (paned->priv->handle_visible) { gdk_window_show (paned->priv->pane_window); gdk_window_show (paned->priv->handle_window); } } static void moo_paned_unmap (GtkWidget *widget) { MooPaned *paned = MOO_PANED (widget); if (paned->priv->handle_window) gdk_window_hide (paned->priv->handle_window); if (paned->priv->pane_window) gdk_window_hide (paned->priv->pane_window); if (paned->priv->bin_window) gdk_window_hide (paned->priv->bin_window); GTK_WIDGET_CLASS(moo_paned_parent_class)->unmap (widget); } static void moo_paned_forall (GtkContainer *container, gboolean include_internals, GtkCallback callback, gpointer callback_data) { MooPaned *paned = MOO_PANED (container); GtkBin *bin = GTK_BIN (container); GSList *l; if (bin->child) callback (bin->child, callback_data); if (include_internals) { callback (paned->button_box, callback_data); for (l = paned->priv->panes; l != NULL; l = l->next) callback (_moo_pane_get_frame (l->data), callback_data); } } static gboolean moo_paned_expose (GtkWidget *widget, GdkEventExpose *event) { MooPaned *paned = MOO_PANED (widget); if (paned->priv->button_box_visible) gtk_container_propagate_expose (GTK_CONTAINER (paned), paned->button_box, event); if (GTK_BIN(paned)->child && GTK_WIDGET_DRAWABLE (GTK_BIN(paned)->child)) gtk_container_propagate_expose (GTK_CONTAINER (paned), GTK_BIN(paned)->child, event); if (paned->priv->pane_widget_visible) gtk_container_propagate_expose (GTK_CONTAINER (paned), _moo_pane_get_frame (paned->priv->current_pane), event); if (paned->priv->handle_visible && event->window == paned->priv->handle_window) draw_handle (paned, event); if (paned->priv->button_box_visible && !paned->priv->pane_widget_visible && paned->priv->border_size && event->window == paned->priv->bin_window) draw_border (paned, event); return TRUE; } #if 0 /* TODO */ static GdkEventExpose *clip_bin_child_event (MooPaned *paned, GdkEventExpose *event) { GtkAllocation child_alloc; GdkRegion *child_rect; GdkEventExpose *child_event; get_bin_child_allocation (paned, &child_alloc); child_rect = gdk_region_rectangle ((GdkRectangle*) &child_alloc); child_event = (GdkEventExpose*) gdk_event_copy ((GdkEvent*) event); gdk_region_intersect (child_event->region, child_rect); gdk_region_get_clipbox (child_event->region, &child_event->area); gdk_region_destroy (child_rect); return child_event; } #endif static void moo_paned_add (GtkContainer *container, GtkWidget *child) { GtkBin *bin = GTK_BIN (container); g_return_if_fail (GTK_IS_WIDGET (child)); if (bin->child != NULL) { g_warning ("Attempting to add a widget with type %s to a %s, " "but as a GtkBin subclass a %s can only contain one widget at a time; " "it already contains a widget of type %s", g_type_name (G_OBJECT_TYPE (child)), g_type_name (G_OBJECT_TYPE (bin)), g_type_name (G_OBJECT_TYPE (bin)), g_type_name (G_OBJECT_TYPE (bin->child))); return; } gtk_widget_set_parent_window (child, MOO_PANED(container)->priv->bin_window); gtk_widget_set_parent (child, GTK_WIDGET (bin)); bin->child = child; } static void moo_paned_remove (GtkContainer *container, GtkWidget *widget) { MooPaned *paned = MOO_PANED (container); g_return_if_fail (widget == GTK_BIN(paned)->child); GTK_CONTAINER_CLASS(moo_paned_parent_class)->remove (container, widget); } static void draw_handle (MooPaned *paned, GdkEventExpose *event) { GtkWidget *widget = GTK_WIDGET (paned); GtkStateType state; GdkRectangle area = {0, 0, 0, 0}; GtkOrientation orientation = GTK_ORIENTATION_VERTICAL; int shadow_size = 0; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: shadow_size = widget->style->xthickness; area.width = paned->priv->handle_size; area.height = widget->allocation.height; if (area.width <= 3*shadow_size) shadow_size = 0; area.x += shadow_size; area.width -= shadow_size; orientation = GTK_ORIENTATION_VERTICAL; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: shadow_size = widget->style->ythickness; area.width = widget->allocation.width; area.height = paned->priv->handle_size; if (area.height <= 3 * shadow_size) shadow_size = 0; area.y += shadow_size; area.height -= shadow_size; orientation = GTK_ORIENTATION_HORIZONTAL; break; } if (gtk_widget_is_focus (widget)) state = GTK_STATE_SELECTED; else if (paned->priv->handle_prelit) state = GTK_STATE_PRELIGHT; else state = GTK_WIDGET_STATE (widget); gtk_paint_handle (widget->style, paned->priv->handle_window, state, GTK_SHADOW_NONE, &event->area, widget, "paned", area.x, area.y, area.width, area.height, orientation); if (shadow_size) { if (orientation == GTK_ORIENTATION_VERTICAL) { area.x -= shadow_size; area.width = shadow_size; gtk_paint_vline (widget->style, paned->priv->handle_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", area.y, area.y + area.height, area.x); area.x = paned->priv->handle_size - shadow_size; gtk_paint_vline (widget->style, paned->priv->handle_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", area.y, area.y + area.height, area.x); } else { area.y -= shadow_size; area.height = shadow_size; gtk_paint_hline (widget->style, paned->priv->handle_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", area.x, area.x + area.width, area.y); area.y = paned->priv->handle_size - shadow_size; gtk_paint_hline (widget->style, paned->priv->handle_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", area.x, area.x + area.width, area.y); } } } static void draw_border (MooPaned *paned, GdkEventExpose *event) { GdkRectangle rect; GtkWidget *widget = GTK_WIDGET (paned); rect.x = paned->priv->button_box_size; rect.y = paned->priv->button_box_size; switch (paned->priv->pane_position) { case MOO_PANE_POS_RIGHT: rect.x = widget->allocation.width - paned->priv->button_box_size - paned->priv->border_size; case MOO_PANE_POS_LEFT: rect.y = 0; rect.height = widget->allocation.height; rect.width = paned->priv->border_size; gtk_paint_vline (widget->style, paned->priv->bin_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", rect.y, rect.y + rect.height, rect.x); break; case MOO_PANE_POS_BOTTOM: rect.y = widget->allocation.height - paned->priv->button_box_size - paned->priv->border_size; case MOO_PANE_POS_TOP: rect.x = 0; rect.width = widget->allocation.width; rect.height = paned->priv->border_size; gtk_paint_hline (widget->style, paned->priv->bin_window, GTK_STATE_NORMAL, &event->area, widget, "moo-paned", rect.x, rect.x + rect.width, rect.y); break; } } void moo_paned_set_sticky_pane (MooPaned *paned, gboolean sticky) { g_return_if_fail (MOO_IS_PANED (paned)); if (paned->priv->sticky != sticky) { paned->priv->sticky = sticky; if (GTK_WIDGET_REALIZED (paned)) gtk_widget_queue_resize (GTK_WIDGET (paned)); g_object_notify (G_OBJECT (paned), "sticky-pane"); } } MooPane * moo_paned_get_nth_pane (MooPaned *paned, guint n) { g_return_val_if_fail (MOO_IS_PANED (paned), NULL); return get_nth_pane (paned, n); } MooPane * moo_paned_get_pane (MooPaned *paned, GtkWidget *widget) { MooPane *pane; g_return_val_if_fail (MOO_IS_PANED (paned), NULL); g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); pane = g_object_get_data (G_OBJECT (widget), "moo-pane"); if (pane && _moo_pane_get_parent (pane) == paned) return pane; else return NULL; } int moo_paned_get_pane_num (MooPaned *paned, GtkWidget *widget) { MooPane *pane; g_return_val_if_fail (MOO_IS_PANED (paned), -1); g_return_val_if_fail (GTK_IS_WIDGET (widget), -1); pane = g_object_get_data (G_OBJECT (widget), "moo-pane"); if (pane) return pane_index (paned, pane); else return -1; } static gboolean moo_paned_motion (GtkWidget *widget, G_GNUC_UNUSED GdkEventMotion *event) { MooPaned *paned = MOO_PANED (widget); if (paned->priv->in_drag) { int size = 0; GtkRequisition requisition; _moo_pane_get_size_request (paned->priv->current_pane, &requisition); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: gdk_window_get_pointer (paned->priv->bin_window, &size, NULL, NULL); if (paned->priv->pane_position == MOO_PANE_POS_RIGHT) size = widget->allocation.width - size; size -= (paned->priv->drag_start + paned->priv->button_box_size); size = CLAMP (size, requisition.width, widget->allocation.width - paned->priv->button_box_size - paned->priv->handle_size); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: gdk_window_get_pointer (paned->priv->bin_window, NULL, &size, NULL); if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM) size = widget->allocation.height - size; size -= (paned->priv->drag_start + paned->priv->button_box_size); size = CLAMP (size, requisition.height, widget->allocation.height - paned->priv->button_box_size - paned->priv->handle_size); break; } if (size != paned->priv->pane_widget_size) moo_paned_set_pane_size (paned, size); } return FALSE; } static void get_handle_rect (MooPaned *paned, GdkRectangle *rect) { rect->x = rect->y = 0; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: rect->width = paned->priv->handle_size; rect->height = GTK_WIDGET(paned)->allocation.height; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: rect->height = paned->priv->handle_size; rect->width = GTK_WIDGET(paned)->allocation.width; break; } } static gboolean moo_paned_enter (GtkWidget *widget, GdkEventCrossing *event) { MooPaned *paned = MOO_PANED (widget); GdkRectangle rect; if (event->window == paned->priv->handle_window && !paned->priv->in_drag) { paned->priv->handle_prelit = TRUE; get_handle_rect (paned, &rect); gdk_window_invalidate_rect (paned->priv->handle_window, &rect, FALSE); return TRUE; } return FALSE; } static gboolean moo_paned_leave (GtkWidget *widget, GdkEventCrossing *event) { MooPaned *paned = MOO_PANED (widget); GdkRectangle rect; if (event->window == paned->priv->handle_window && !paned->priv->in_drag) { paned->priv->handle_prelit = FALSE; get_handle_rect (paned, &rect); gdk_window_invalidate_rect (paned->priv->handle_window, &rect, FALSE); return TRUE; } return FALSE; } static gboolean moo_paned_button_press (GtkWidget *widget, GdkEventButton *event) { MooPaned *paned = MOO_PANED (widget); if (!paned->priv->in_drag && (event->window == paned->priv->handle_window) && (event->button == 1) && paned->priv->pane_widget_visible) { paned->priv->in_drag = TRUE; /* This is copied from gtkpaned.c */ gdk_pointer_grab (paned->priv->handle_window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK, NULL, NULL, event->time); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: paned->priv->drag_start = event->x; break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: paned->priv->drag_start = event->y; break; } return TRUE; } return FALSE; } static gboolean moo_paned_button_release (GtkWidget *widget, GdkEventButton *event) { MooPaned *paned = MOO_PANED (widget); if (paned->priv->in_drag && (event->button == 1)) { paned->priv->in_drag = FALSE; paned->priv->drag_start = -1; gdk_display_pointer_ungrab (gtk_widget_get_display (widget), event->time); return TRUE; } return FALSE; } int moo_paned_get_pane_size (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), 0); return paned->priv->position; } int moo_paned_get_button_box_size (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), 0); return paned->priv->button_box_size; } static void moo_paned_set_pane_size_real (MooPaned *paned, int size) { GtkWidget *widget; GdkRectangle rect; g_return_if_fail (MOO_IS_PANED (paned)); if (!GTK_WIDGET_REALIZED (paned)) { paned->priv->position = size; return; } widget = GTK_WIDGET (paned); switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: case MOO_PANE_POS_RIGHT: size = CLAMP (size, 0, widget->allocation.width - paned->priv->button_box_size - paned->priv->handle_size); break; case MOO_PANE_POS_TOP: case MOO_PANE_POS_BOTTOM: size = CLAMP (size, 0, widget->allocation.height - paned->priv->button_box_size - paned->priv->handle_size); break; } if (size == paned->priv->position) return; paned->priv->position = size; if (!paned->priv->pane_widget_visible) return; /* button box redrawing is too slow */ if (!paned->priv->button_box_visible) { gtk_widget_queue_resize (widget); return; } rect.x = widget->allocation.x; rect.y = widget->allocation.y; rect.width = widget->allocation.width; rect.height = widget->allocation.height; switch (paned->priv->pane_position) { case MOO_PANE_POS_LEFT: rect.x += paned->priv->button_box_size; rect.width -= paned->priv->button_box_size; break; case MOO_PANE_POS_RIGHT: rect.width -= paned->priv->button_box_size; break; case MOO_PANE_POS_TOP: rect.y += paned->priv->button_box_size; rect.height -= paned->priv->button_box_size; break; case MOO_PANE_POS_BOTTOM: rect.height -= paned->priv->button_box_size; break; } gtk_widget_queue_resize_no_redraw (widget); } void moo_paned_set_pane_size (MooPaned *paned, int size) { g_return_if_fail (MOO_IS_PANED (paned)); g_signal_emit (paned, paned_signals[PANED_SET_PANE_SIZE], 0, size); } static void button_box_visible_notify (MooPaned *paned) { gboolean visible = GTK_WIDGET_VISIBLE (paned->button_box); if (paned->priv->button_box_visible == visible) return; if (paned->priv->panes) paned->priv->button_box_visible = visible; if (GTK_WIDGET_REALIZED (paned)) gtk_widget_queue_resize (GTK_WIDGET (paned)); } void _moo_paned_insert_pane (MooPaned *paned, MooPane *pane, int position) { GtkWidget *handle; g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (_moo_pane_get_parent (pane) == NULL); g_object_ref (pane); _moo_pane_set_parent (pane, paned, paned->priv->pane_window); if (position < 0 || position > (int) moo_paned_n_panes (paned)) position = moo_paned_n_panes (paned); gtk_container_add_with_properties (GTK_CONTAINER (paned->button_box), _moo_pane_get_button (pane), "expand", FALSE, "fill", FALSE, "pack-type", GTK_PACK_START, "position", position, NULL); paned->priv->panes = g_slist_insert (paned->priv->panes, pane, position); g_signal_connect (_moo_pane_get_button (pane), "toggled", G_CALLBACK (pane_button_toggled), paned); handle = _moo_pane_get_handle (pane); g_signal_connect (handle, "button-press-event", G_CALLBACK (handle_button_press), paned); g_signal_connect (handle, "button-release-event", G_CALLBACK (handle_button_release), paned); g_signal_connect (handle, "motion-notify-event", G_CALLBACK (handle_motion), paned); g_signal_connect (handle, "expose-event", G_CALLBACK (handle_expose), paned); gtk_widget_show (paned->button_box); paned->priv->button_box_visible = TRUE; if (GTK_WIDGET_VISIBLE (paned)) gtk_widget_queue_resize (GTK_WIDGET (paned)); } MooPane * moo_paned_insert_pane (MooPaned *paned, GtkWidget *pane_widget, MooPaneLabel *pane_label, int position) { MooPane *pane; g_return_val_if_fail (MOO_IS_PANED (paned), NULL); g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), NULL); g_return_val_if_fail (pane_label != NULL, NULL); g_return_val_if_fail (pane_widget->parent == NULL, NULL); pane = _moo_pane_new (pane_widget, pane_label); _moo_paned_insert_pane (paned, pane, position); MOO_OBJECT_REF_SINK (pane); return pane; } gboolean moo_paned_remove_pane (MooPaned *paned, GtkWidget *pane_widget) { MooPane *pane; int index; g_return_val_if_fail (MOO_IS_PANED (paned), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (pane_widget), FALSE); pane = g_object_get_data (G_OBJECT (pane_widget), "moo-pane"); g_return_val_if_fail (pane != NULL, FALSE); g_return_val_if_fail (g_slist_find (paned->priv->panes, pane) != NULL, FALSE); if (paned->priv->current_pane == pane) { index = pane_index (paned, pane); if (index > 0) index = index - 1; else if (moo_paned_n_panes (paned) > 1) index = 1; else index = -1; if (index >= 0) moo_paned_open_pane (paned, get_nth_pane (paned, index)); else moo_paned_hide_pane (paned); } if (_moo_pane_get_detached (pane)) { _moo_pane_freeze_params (pane); moo_paned_attach_pane (paned, pane); _moo_pane_thaw_params (pane); } g_signal_handlers_disconnect_by_func (_moo_pane_get_button (pane), (gpointer) pane_button_toggled, paned); g_signal_handlers_disconnect_by_func (_moo_pane_get_handle (pane), (gpointer) handle_button_press, paned); g_signal_handlers_disconnect_by_func (_moo_pane_get_handle (pane), (gpointer) handle_button_release, paned); g_signal_handlers_disconnect_by_func (_moo_pane_get_handle (pane), (gpointer) handle_motion, paned); g_signal_handlers_disconnect_by_func (_moo_pane_get_handle (pane), (gpointer) handle_expose, paned); g_signal_handlers_disconnect_by_func (_moo_pane_get_handle (pane), (gpointer) handle_realize, paned); gtk_container_remove (GTK_CONTAINER (paned->button_box), _moo_pane_get_button (pane)); paned->priv->panes = g_slist_remove (paned->priv->panes, pane); _moo_pane_unparent (pane); g_object_unref (pane); if (!moo_paned_n_panes (paned)) { paned->priv->handle_visible = FALSE; paned->priv->handle_size = 0; if (paned->priv->pane_window) gdk_window_hide (paned->priv->pane_window); gtk_widget_hide (paned->button_box); paned->priv->button_box_visible = FALSE; } gtk_widget_queue_resize (GTK_WIDGET (paned)); return TRUE; } guint moo_paned_n_panes (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), 0); return g_slist_length (paned->priv->panes); } static GtkWidget * find_focus (GtkWidget *widget) { GtkWidget *focus_child, *window; if (!widget) return NULL; window = gtk_widget_get_toplevel (widget); if (!GTK_IS_WINDOW (window)) return NULL; focus_child = gtk_window_get_focus (GTK_WINDOW (window)); if (focus_child && gtk_widget_is_ancestor (focus_child, widget)) return focus_child; else return NULL; } static GtkWidget * find_focus_child (MooPaned *paned) { return find_focus (GTK_BIN(paned)->child); } static void moo_paned_set_focus_child (GtkContainer *container, GtkWidget *widget) { MooPaned *paned = MOO_PANED (container); FocusPosition new_focus = FOCUS_NONE; MooPane *new_focus_pane = NULL; MooPane *old_focus_pane = paned->priv->focus_pane; GSList *l; if (widget) { if (widget == GTK_BIN(paned)->child) new_focus = FOCUS_CHILD; else if (widget == paned->button_box) new_focus = FOCUS_BUTTON; if (!new_focus) { for (l = paned->priv->panes; l != NULL; l = l->next) { MooPane *pane = l->data; if (widget == _moo_pane_get_frame (pane)) { new_focus_pane = pane; new_focus = FOCUS_PANE; break; } } } if (!new_focus) { g_critical ("%s: oops", G_STRLOC); GTK_CONTAINER_CLASS(moo_paned_parent_class)->set_focus_child (container, widget); paned->priv->focus = FOCUS_NONE; paned->priv->focus_pane = NULL; g_return_if_reached (); } } if (new_focus != FOCUS_BUTTON) paned->priv->button_real_focus = FALSE; switch (paned->priv->focus) { case FOCUS_NONE: case FOCUS_BUTTON: break; case FOCUS_CHILD: if (new_focus != FOCUS_CHILD) { if (paned->priv->focus_child) g_object_remove_weak_pointer (paned->priv->focus_child, &paned->priv->focus_child); paned->priv->focus_child = find_focus_child (paned); if (paned->priv->focus_child) g_object_add_weak_pointer (paned->priv->focus_child, &paned->priv->focus_child); } break; case FOCUS_PANE: g_assert (old_focus_pane != NULL); if (new_focus_pane != old_focus_pane) _moo_pane_update_focus_child (old_focus_pane); break; } GTK_CONTAINER_CLASS(moo_paned_parent_class)->set_focus_child (container, widget); paned->priv->focus = new_focus; paned->priv->focus_pane = new_focus_pane; if (new_focus == FOCUS_CHILD && paned->priv->close_on_child_focus && !paned->priv->sticky) { paned->priv->dont_move_focus = TRUE; moo_paned_hide_pane (paned); paned->priv->dont_move_focus = FALSE; } } static gboolean focus_to_child (MooPaned *paned, GtkDirectionType direction) { return GTK_BIN(paned)->child && gtk_widget_child_focus (GTK_BIN(paned)->child, direction); } static gboolean focus_to_pane (MooPaned *paned, GtkDirectionType direction) { MooPane *pane = paned->priv->current_pane; if (!pane) return FALSE; else if (find_focus (_moo_pane_get_frame (pane))) return gtk_widget_child_focus (_moo_pane_get_frame (pane), direction); else return gtk_widget_child_focus (moo_pane_get_child (pane), direction) || gtk_widget_child_focus (_moo_pane_get_frame (pane), direction); } static gboolean focus_to_button (MooPaned *paned, GtkDirectionType direction) { if (gtk_widget_child_focus (paned->button_box, direction)) { paned->priv->button_real_focus = TRUE; return TRUE; } else { return FALSE; } } static gboolean moo_left_paned_focus (MooPaned *paned, GtkDirectionType direction) { switch (paned->priv->focus) { case FOCUS_NONE: switch (direction) { case GTK_DIR_LEFT: case GTK_DIR_UP: return focus_to_child (paned, direction) || (paned->priv->current_pane && focus_to_pane (paned, direction)) || focus_to_button (paned, direction); case GTK_DIR_RIGHT: case GTK_DIR_DOWN: return focus_to_button (paned, direction) || focus_to_child (paned, direction); default: g_return_val_if_reached (FALSE); } case FOCUS_CHILD: if (focus_to_child (paned, direction)) return TRUE; switch (direction) { case GTK_DIR_LEFT: return (paned->priv->current_pane && focus_to_pane (paned, direction)) || focus_to_button (paned, direction); default: return FALSE; } case FOCUS_PANE: if (focus_to_pane (paned, direction)) return TRUE; switch (direction) { case GTK_DIR_LEFT: return focus_to_button (paned, direction); case GTK_DIR_RIGHT: return focus_to_child (paned, direction); default: return FALSE; } case FOCUS_BUTTON: if (focus_to_button (paned, direction)) return TRUE; switch (direction) { case GTK_DIR_RIGHT: return focus_to_pane (paned, direction) || focus_to_child (paned, direction); default: return FALSE; } } g_return_val_if_reached (FALSE); } static gboolean moo_left_paned_tab_focus (MooPaned *paned, gboolean forward, GtkDirectionType direction) { if (forward) { switch (paned->priv->focus) { case FOCUS_NONE: return focus_to_button (paned, direction) || focus_to_child (paned, direction); case FOCUS_CHILD: return focus_to_child (paned, direction); case FOCUS_PANE: return focus_to_pane (paned, direction) || focus_to_child (paned, direction); case FOCUS_BUTTON: return focus_to_button (paned, direction) || focus_to_pane (paned, direction) || focus_to_child (paned, direction); } } else { switch (paned->priv->focus) { case FOCUS_NONE: case FOCUS_CHILD: return focus_to_child (paned, direction) || focus_to_pane (paned, direction) || focus_to_button (paned, direction); case FOCUS_PANE: return focus_to_pane (paned, direction) || focus_to_button (paned, direction); case FOCUS_BUTTON: return focus_to_button (paned, direction); } } g_return_val_if_reached (FALSE); } static gboolean moo_top_paned_tab_focus (MooPaned *paned, GtkDirectionType direction) { switch (paned->priv->focus) { case FOCUS_NONE: return focus_to_button (paned, direction) || focus_to_child (paned, direction); case FOCUS_BUTTON: return focus_to_button (paned, direction) || focus_to_pane (paned, direction) || focus_to_child (paned, direction); case FOCUS_PANE: return focus_to_pane (paned, direction) || focus_to_child (paned, direction); case FOCUS_CHILD: return focus_to_child (paned, direction); } g_return_val_if_reached (FALSE); } static gboolean moo_paned_focus (GtkWidget *widget, GtkDirectionType direction) { MooPaned *paned = MOO_PANED (widget); gboolean invert = FALSE; gboolean flip = FALSE; paned->priv->button_real_focus = FALSE; if (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD) { switch (paned->priv->pane_position) { case MOO_PANE_POS_RIGHT: return moo_left_paned_tab_focus (paned, direction != GTK_DIR_TAB_FORWARD, direction); case MOO_PANE_POS_LEFT: return moo_left_paned_tab_focus (paned, direction == GTK_DIR_TAB_FORWARD, direction); case MOO_PANE_POS_BOTTOM: case MOO_PANE_POS_TOP: return moo_top_paned_tab_focus (paned, direction); } } switch (paned->priv->pane_position) { case MOO_PANE_POS_RIGHT: invert = TRUE; /* fall through */ case MOO_PANE_POS_LEFT: break; case MOO_PANE_POS_BOTTOM: invert = TRUE; /* fall through */ case MOO_PANE_POS_TOP: flip = TRUE; break; } if (invert) { switch (direction) { case GTK_DIR_RIGHT: direction = GTK_DIR_LEFT; break; case GTK_DIR_LEFT: direction = GTK_DIR_RIGHT; break; case GTK_DIR_UP: direction = GTK_DIR_DOWN; break; case GTK_DIR_DOWN: direction = GTK_DIR_UP; break; default: g_return_val_if_reached (FALSE); } } if (flip) { switch (direction) { case GTK_DIR_RIGHT: direction = GTK_DIR_DOWN; break; case GTK_DIR_DOWN: direction = GTK_DIR_RIGHT; break; case GTK_DIR_LEFT: direction = GTK_DIR_UP; break; case GTK_DIR_UP: direction = GTK_DIR_LEFT; break; default: g_return_val_if_reached (FALSE); } } return moo_left_paned_focus (paned, direction); } void moo_paned_present_pane (MooPaned *paned, MooPane *pane) { g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (_moo_pane_get_parent (pane) == paned); if (paned->priv->current_pane == pane) { paned->priv->dont_move_focus = FALSE; if (!find_focus (moo_pane_get_child (pane))) { if (_moo_pane_get_focus_child (pane)) { gtk_widget_grab_focus (_moo_pane_get_focus_child (pane)); } else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD)) { paned->priv->button_real_focus = FALSE; gtk_widget_grab_focus (_moo_pane_get_button (pane)); } } return; } else if (_moo_pane_get_detached (pane)) { gtk_window_present (GTK_WINDOW (_moo_pane_get_window (pane))); } else { moo_paned_open_pane (paned, pane); } } static void moo_paned_open_pane_real (MooPaned *paned, guint index) { MooPane *pane; FocusPosition old_focus; g_return_if_fail (index < moo_paned_n_panes (paned)); pane = get_nth_pane (paned, index); g_return_if_fail (pane != NULL); if (paned->priv->current_pane == pane) return; old_focus = paned->priv->focus; if (paned->priv->current_pane) { MooPane *old_pane = paned->priv->current_pane; paned->priv->current_pane = NULL; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_moo_pane_get_button (old_pane)), FALSE); gtk_widget_hide (_moo_pane_get_frame (old_pane)); } if (GTK_WIDGET_MAPPED (paned)) { gdk_window_show (paned->priv->pane_window); gdk_window_show (paned->priv->handle_window); } gtk_widget_set_parent_window (_moo_pane_get_frame (pane), paned->priv->pane_window); paned->priv->current_pane = pane; gtk_widget_show (_moo_pane_get_frame (pane)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_moo_pane_get_button (pane)), TRUE); if (_moo_pane_get_detached (pane)) moo_paned_attach_pane (paned, pane); paned->priv->handle_visible = TRUE; paned->priv->pane_widget_visible = TRUE; if (paned->priv->position > 0) paned->priv->pane_widget_size = paned->priv->position; /* XXX it's wrong, it should look if button was clicked */ if (!paned->priv->dont_move_focus && (old_focus != FOCUS_BUTTON || !paned->priv->button_real_focus)) { if (_moo_pane_get_focus_child (pane)) { gtk_widget_grab_focus (_moo_pane_get_focus_child (pane)); } else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD)) { paned->priv->button_real_focus = FALSE; gtk_widget_grab_focus (_moo_pane_get_button (pane)); } } gtk_widget_queue_resize (GTK_WIDGET (paned)); } /* XXX invalidate space under the pane */ static void moo_paned_hide_pane_real (MooPaned *paned) { GtkWidget *button; FocusPosition old_focus; if (!paned->priv->current_pane) return; button = _moo_pane_get_button (paned->priv->current_pane); old_focus = paned->priv->focus; gtk_widget_hide (_moo_pane_get_frame (paned->priv->current_pane)); if (GTK_WIDGET_REALIZED (paned)) { gdk_window_hide (paned->priv->handle_window); gdk_window_hide (paned->priv->pane_window); } paned->priv->current_pane = NULL; paned->priv->pane_widget_visible = FALSE; paned->priv->handle_visible = FALSE; gtk_widget_queue_resize (GTK_WIDGET (paned)); /* XXX it's wrong, it should look if button was clicked */ if (!paned->priv->dont_move_focus && old_focus != FOCUS_NONE && (old_focus != FOCUS_BUTTON || !paned->priv->button_real_focus)) { if (paned->priv->focus_child) { gtk_widget_grab_focus (paned->priv->focus_child); } else if (!GTK_BIN(paned)->child || !gtk_widget_child_focus (GTK_BIN(paned)->child, GTK_DIR_TAB_FORWARD)) { if (GTK_WIDGET_VISIBLE (button)) gtk_widget_grab_focus (button); } else { GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (paned)); gtk_widget_child_focus (toplevel, GTK_DIR_TAB_FORWARD); } } gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE); } static void pane_button_toggled (GtkToggleButton *button, MooPaned *paned) { MooPane *pane; pane = g_object_get_data (G_OBJECT (button), "moo-pane"); g_return_if_fail (MOO_IS_PANE (pane)); if (!gtk_toggle_button_get_active (button)) { if (paned->priv->current_pane == pane) moo_paned_hide_pane (paned); } else if (!paned->priv->current_pane || paned->priv->current_pane != pane) { moo_paned_open_pane (paned, pane); } } void moo_paned_hide_pane (MooPaned *paned) { g_return_if_fail (MOO_IS_PANED (paned)); moo_paned_hide_pane_real (paned); } void moo_paned_open_pane (MooPaned *paned, MooPane *pane) { g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (_moo_pane_get_parent (pane) == paned); moo_paned_open_pane_real (paned, pane_index (paned, pane)); } static int pane_index (MooPaned *paned, MooPane *pane) { return g_slist_index (paned->priv->panes, pane); } static MooPane * get_nth_pane (MooPaned *paned, guint index_) { return g_slist_nth_data (paned->priv->panes, index_); } MooPane * moo_paned_get_open_pane (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), NULL); return paned->priv->current_pane; } gboolean moo_paned_is_open (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), FALSE); return paned->priv->current_pane != NULL; } GType moo_pane_position_get_type (void) { static GType type = 0; if (G_UNLIKELY (!type)) { static const GEnumValue values[] = { { MOO_PANE_POS_LEFT, (char*) "MOO_PANE_POS_LEFT", (char*) "left" }, { MOO_PANE_POS_RIGHT, (char*) "MOO_PANE_POS_RIGHT", (char*) "right" }, { MOO_PANE_POS_TOP, (char*) "MOO_PANE_POS_TOP", (char*) "top" }, { MOO_PANE_POS_BOTTOM, (char*) "MOO_PANE_POS_BOTTOM", (char*) "bottom" }, { 0, NULL, NULL } }; type = g_enum_register_static ("MooPanePosition", values); } return type; } GSList * moo_paned_list_panes (MooPaned *paned) { g_return_val_if_fail (MOO_IS_PANED (paned), NULL); return g_slist_copy (paned->priv->panes); } static gboolean handle_button_press (GtkWidget *widget, GdkEventButton *event, MooPaned *paned) { #if 1 GdkCursor *cursor; #endif if (event->button != 1 || event->type != GDK_BUTTON_PRESS) return FALSE; if (!paned->priv->enable_handle_drag) return FALSE; g_return_val_if_fail (!paned->priv->handle_in_drag, FALSE); g_return_val_if_fail (!paned->priv->handle_button_pressed, FALSE); paned->priv->handle_button_pressed = TRUE; paned->priv->handle_drag_start_x = event->x; paned->priv->handle_drag_start_y = event->y; #if 1 cursor = gdk_cursor_new (paned->priv->handle_cursor_type); g_return_val_if_fail (cursor != NULL, TRUE); gdk_window_set_cursor (widget->window, cursor); gdk_cursor_unref (cursor); #endif return TRUE; } static gboolean handle_motion (GtkWidget *widget, GdkEventMotion *event, MooPaned *paned) { MooPane *pane; GtkWidget *child; if (!paned->priv->handle_button_pressed) return FALSE; pane = g_object_get_data (G_OBJECT (widget), "moo-pane"); child = moo_pane_get_child (pane); g_return_val_if_fail (child != NULL, FALSE); if (!paned->priv->handle_in_drag) { if (!gtk_drag_check_threshold (widget, paned->priv->handle_drag_start_x, paned->priv->handle_drag_start_y, event->x, event->y)) return FALSE; paned->priv->handle_in_drag = TRUE; g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_START], 0, child); } g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_MOTION], 0, child); return TRUE; } static gboolean handle_button_release (GtkWidget *widget, G_GNUC_UNUSED GdkEventButton *event, MooPaned *paned) { MooPane *pane; GtkWidget *child; if (paned->priv->handle_button_pressed) { #if 1 gdk_window_set_cursor (widget->window, NULL); #endif paned->priv->handle_button_pressed = FALSE; } if (!paned->priv->handle_in_drag) return FALSE; paned->priv->handle_in_drag = FALSE; pane = g_object_get_data (G_OBJECT (widget), "moo-pane"); child = moo_pane_get_child (pane); g_return_val_if_fail (child != NULL, FALSE); g_signal_emit (paned, paned_signals[PANED_HANDLE_DRAG_END], 0, child); return TRUE; } #define HANDLE_HEIGHT 12 static gboolean handle_expose (GtkWidget *widget, GdkEventExpose *event, MooPaned *paned) { int height; if (!paned->priv->enable_handle_drag) return FALSE; height = MIN (widget->allocation.height, HANDLE_HEIGHT); gtk_paint_handle (widget->style, widget->window, widget->state, GTK_SHADOW_ETCHED_IN, &event->area, widget, "moo-pane-handle", 0, (widget->allocation.height - height) / 2, widget->allocation.width, height, GTK_ORIENTATION_HORIZONTAL); return TRUE; } static void handle_realize (G_GNUC_UNUSED GtkWidget *widget, MooPaned *paned) { #if 0 GdkCursor *cursor; #endif g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_PANED(paned)->priv->bin_window != NULL); if (!paned->priv->enable_handle_drag) return; #if 0 cursor = gdk_cursor_new (paned->priv->handle_cursor_type); g_return_if_fail (cursor != NULL); gdk_window_set_cursor (widget->window, cursor); gdk_cursor_unref (cursor); #endif } static void moo_paned_set_handle_cursor_type (MooPaned *paned, GdkCursorType cursor_type, gboolean set) { // GSList *l; GdkCursor *cursor = NULL; // for (l = paned->priv->panes; l != NULL; l = l->next) // { // MooPane *pane = l->data; // // if (pane->handle && pane->handle->window) // { // #if 0 // if (set && !cursor) // { // cursor = gdk_cursor_new (cursor_type); // g_return_if_fail (cursor != NULL); // } // // gdk_window_set_cursor (pane->handle->window, cursor); // #endif // } // } if (set) { paned->priv->handle_cursor_type = cursor_type; g_object_notify (G_OBJECT (paned), "handle-cursor-type"); } if (cursor) gdk_cursor_unref (cursor); } MooPaneLabel * moo_pane_label_new (const char *icon_stock_id, GdkPixbuf *pixbuf, const char *text, const char *window_title) { MooPaneLabel *label = g_new0 (MooPaneLabel, 1); label->icon_stock_id = g_strdup (icon_stock_id); label->label = g_strdup (text); label->window_title = g_strdup (window_title); if (pixbuf) label->icon_pixbuf = g_object_ref (pixbuf); return label; } MooPaneLabel* moo_pane_label_copy (MooPaneLabel *label) { MooPaneLabel *copy; g_return_val_if_fail (label != NULL, NULL); copy = g_new0 (MooPaneLabel, 1); copy->icon_stock_id = g_strdup (label->icon_stock_id); copy->label = g_strdup (label->label); copy->window_title = g_strdup (label->window_title); if (label->icon_pixbuf) copy->icon_pixbuf = g_object_ref (label->icon_pixbuf); return copy; } void moo_pane_label_free (MooPaneLabel *label) { if (label) { g_free (label->icon_stock_id); g_free (label->label); g_free (label->window_title); if (label->icon_pixbuf) g_object_unref (label->icon_pixbuf); g_free (label); } } void moo_paned_detach_pane (MooPaned *paned, MooPane *pane) { g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (_moo_pane_get_parent (pane) == paned); if (_moo_pane_get_detached (pane)) return; if (pane == paned->priv->current_pane) moo_paned_hide_pane (paned); _moo_pane_detach (pane); gtk_widget_queue_resize (GTK_WIDGET (paned)); } void moo_paned_attach_pane (MooPaned *paned, MooPane *pane) { g_return_if_fail (MOO_IS_PANED (paned)); g_return_if_fail (MOO_IS_PANE (pane)); g_return_if_fail (_moo_pane_get_parent (pane) == paned); if (!_moo_pane_get_detached (pane)) return; _moo_pane_attach (pane); gtk_widget_queue_resize (GTK_WIDGET (paned)); } static void moo_paned_set_enable_detaching (MooPaned *paned, gboolean enable) { if (paned->priv->enable_detaching != enable) { paned->priv->enable_detaching = enable != 0; g_object_notify (G_OBJECT (paned), "enable-detaching"); } } void _moo_paned_attach_pane (MooPaned *paned, MooPane *pane) { GtkWidget *focus_child; g_return_if_fail (MOO_IS_PANED (paned)); moo_paned_attach_pane (paned, pane); paned->priv->dont_move_focus = TRUE; moo_paned_open_pane (paned, pane); paned->priv->dont_move_focus = TRUE; focus_child = _moo_pane_get_focus_child (pane); if (focus_child) gtk_widget_grab_focus (focus_child); else if (!gtk_widget_child_focus (moo_pane_get_child (pane), GTK_DIR_TAB_FORWARD)) gtk_widget_grab_focus (_moo_pane_get_button (pane)); } MooPaneParams * moo_pane_params_new (GdkRectangle *window_position, gboolean detached, gboolean maximized, gboolean keep_on_top) { MooPaneParams *p; p = g_new0 (MooPaneParams, 1); if (window_position) p->window_position = *window_position; else p->window_position.width = p->window_position.height = -1; p->detached = detached != 0; p->maximized = maximized != 0; p->keep_on_top = keep_on_top != 0; return p; } MooPaneParams* moo_pane_params_copy (MooPaneParams *params) { MooPaneParams *copy; g_return_val_if_fail (params != NULL, NULL); copy = g_new (MooPaneParams, 1); memcpy (copy, params, sizeof (MooPaneParams)); return copy; } void moo_pane_params_free (MooPaneParams *params) { g_free (params); } GType moo_pane_label_get_type (void) { static GType type = 0; if (G_UNLIKELY (!type)) type = g_boxed_type_register_static ("MooPaneLabel", (GBoxedCopyFunc) moo_pane_label_copy, (GBoxedFreeFunc) moo_pane_label_free); return type; } GType moo_pane_params_get_type (void) { static GType type = 0; if (G_UNLIKELY (!type)) type = g_boxed_type_register_static ("MooPaneParams", (GBoxedCopyFunc) moo_pane_params_copy, (GBoxedFreeFunc) moo_pane_params_free); return type; } static gboolean moo_paned_key_press (GtkWidget *widget, GdkEventKey *event) { static int delta = 5; int add = 0; guint mask = GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK; MooPaned *paned = MOO_PANED (widget); if ((event->state & mask) == (GDK_CONTROL_MASK | GDK_MOD1_MASK)) { switch (event->keyval) { case GDK_Up: case GDK_KP_Up: if (paned->priv->pane_position == MOO_PANE_POS_TOP) add = -delta; else if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM) add = delta; break; case GDK_Down: case GDK_KP_Down: if (paned->priv->pane_position == MOO_PANE_POS_TOP) add = delta; else if (paned->priv->pane_position == MOO_PANE_POS_BOTTOM) add = -delta; break; case GDK_Left: case GDK_KP_Left: if (paned->priv->pane_position == MOO_PANE_POS_LEFT) add = -delta; else if (paned->priv->pane_position == MOO_PANE_POS_RIGHT) add = delta; break; case GDK_Right: case GDK_KP_Right: if (paned->priv->pane_position == MOO_PANE_POS_LEFT) add = delta; else if (paned->priv->pane_position == MOO_PANE_POS_RIGHT) add = -delta; break; } } if (!add) return GTK_WIDGET_CLASS(moo_paned_parent_class)->key_press_event (widget, event); moo_paned_set_pane_size (paned, moo_paned_get_pane_size (paned) + add); return TRUE; } PIDA-0.5.1/moo/moopaned.defs0000644000175000017500000002532410652670543013603 0ustar aliali;; -*- scheme -*- (define-object BigPaned (in-module "Moo") (parent "GtkFrame") (c-name "MooBigPaned") (gtype-id "MOO_TYPE_BIG_PANED") ) (define-object Paned (in-module "Moo") (parent "GtkBin") (c-name "MooPaned") (gtype-id "MOO_TYPE_PANED") (fields '("GtkWidget*" "button_box") ) ) (define-object Pane (in-module "Moo") (parent "GtkObject") (c-name "MooPane") (gtype-id "MOO_TYPE_PANE") ) (define-boxed PaneLabel (in-module "Moo") (c-name "MooPaneLabel") (gtype-id "MOO_TYPE_PANE_LABEL") (copy-func "moo_pane_label_copy") (release-func "moo_pane_label_free") (docstring "PaneLabel(icon_stock_id=None, icon_pixbuf=None, icon_widget=None, label_text=None, window_title=None).") ) (define-boxed PaneParams (in-module "Moo") (c-name "MooPaneParams") (gtype-id "MOO_TYPE_PANE_PARAMS") (copy-func "moo_pane_params_copy") (release-func "moo_pane_params_free") (fields '("GdkRectangle" "window_position") '("gboolean" "detached") '("gboolean" "maximized") '("gboolean" "keep_on_top") ) (docstring "PaneParams(window_position=None, detached=False, maximized=False, keep_on_top=False).\n" "\n" "window_position is a gdk.Rectangle instance or None; negative width or height in it\n" "means the position is not set.") ) (define-enum PanePosition (in-module "Moo") (c-name "MooPanePosition") (gtype-id "MOO_TYPE_PANE_POSITION") ) ;; From ./moo/mooutils/moobigpaned.h (define-function moo_big_paned_new (c-name "moo_big_paned_new") (is-constructor-of "MooBigPaned") (return-type "GtkWidget*") ) (define-method find_pane (of-object "MooBigPaned") (c-name "moo_big_paned_find_pane") (return-type "MooPane*") (parameters '("GtkWidget*" "pane_widget") '("MooPaned**" "child_paned") ) (docstring "find_pane(pane_widget) -> (pane, paned) or None.") ) (define-method set_pane_order (of-object "MooBigPaned") (c-name "moo_big_paned_set_pane_order") (return-type "none") (parameters '("int*" "order") ) ) (define-method add_child (of-object "MooBigPaned") (c-name "moo_big_paned_add_child") (return-type "none") (parameters '("GtkWidget*" "widget") ) (docstring "add_child(widget) -> None.\n" "\n" "Analogous to gtk.Container.add(), adds widget as the main child of BigPaned widget.") ) (define-method remove_child (of-object "MooBigPaned") (c-name "moo_big_paned_remove_child") (return-type "none") (docstring "remove_child() -> None.\n" "\n" "Analogous to gtk.Container.remove(), removes the main child widget.") ) (define-method get_child (of-object "MooBigPaned") (c-name "moo_big_paned_get_child") (return-type "GtkWidget*") (docstring "get_child() -> gtk.Widget.\n" "\n" "Returns the main child widget.") ) (define-method get_paned (of-object "MooBigPaned") (c-name "moo_big_paned_get_paned") (return-type "MooPaned*") (parameters '("MooPanePosition" "position") ) (docstring "get_paned(pos) -> Paned.\n" "\n" "Returns the paned widget at position pos.") ) (define-method insert_pane (of-object "MooBigPaned") (c-name "moo_big_paned_insert_pane") (return-type "MooPane*") (parameters '("GtkWidget*" "pane_widget") '("MooPaneLabel*" "pane_label") '("MooPanePosition" "position") '("int" "index_") ) (docstring "insert_pane(pane_widget, pane_label, position, index) -> Pane.\n" "\n" "Returns newly created pane object.") ) (define-method remove_pane (of-object "MooBigPaned") (c-name "moo_big_paned_remove_pane") (return-type "gboolean") (parameters '("GtkWidget*" "pane_widget") ) (docstring "remove_pane(pane_widget) -> bool.\n" "\n" "Returns True if pane_widget was removed.") ) (define-method get_pane (of-object "MooBigPaned") (c-name "moo_big_paned_get_pane") (return-type "GtkWidget*") (parameters '("MooPanePosition" "position") '("int" "index_") ) (docstring "get_pane(position, index) -> gtk.Widget.") ) (define-method open_pane (of-object "MooBigPaned") (c-name "moo_big_paned_open_pane") (return-type "none") (parameters '("GtkWidget*" "pane_widget") ) (docstring "open_pane(pane_widget) -> None.") ) (define-method hide_pane (of-object "MooBigPaned") (c-name "moo_big_paned_hide_pane") (return-type "none") (parameters '("GtkWidget*" "pane_widget") ) (docstring "hide_pane(pane_widget) -> None.") ) (define-method present_pane (of-object "MooBigPaned") (c-name "moo_big_paned_present_pane") (return-type "none") (parameters '("GtkWidget*" "pane_widget") ) (docstring "present_pane(pane_widget) -> None.\n" "\n" "Opens the pane or presents the pane window if it's detached.") ) (define-method attach_pane (of-object "MooBigPaned") (c-name "moo_big_paned_attach_pane") (return-type "none") (parameters '("GtkWidget*" "pane_widget") ) (docstring "attach_pane(pane_widget) -> None.") ) (define-method detach_pane (of-object "MooBigPaned") (c-name "moo_big_paned_detach_pane") (return-type "none") (parameters '("GtkWidget*" "pane_widget") ) (docstring "detach_pane(pane_widget) -> None.") ) ;; From ./moo/mooutils/moopaned.h (define-function moo_paned_new (c-name "moo_paned_new") (is-constructor-of "MooPaned") (return-type "GtkWidget*") (properties '("pane_position" (optional)) ) ) (define-method insert_pane (of-object "MooPaned") (c-name "moo_paned_insert_pane") (return-type "MooPane*") (parameters '("GtkWidget*" "pane_widget") '("MooPaneLabel*" "pane_label") '("int" "position") ) ) (define-method remove_pane (of-object "MooPaned") (c-name "moo_paned_remove_pane") (return-type "gboolean") (parameters '("GtkWidget*" "pane_widget") ) ) (define-method n_panes (of-object "MooPaned") (c-name "moo_paned_n_panes") (return-type "guint") ) (define-method list_panes (of-object "MooPaned") (c-name "moo_paned_list_panes") (return-type "GSList*") ) (define-method get_nth_pane (of-object "MooPaned") (c-name "moo_paned_get_nth_pane") (return-type "MooPane*") (parameters '("guint" "n") ) ) (define-method get_pane_num (of-object "MooPaned") (c-name "moo_paned_get_pane_num") (return-type "int") (parameters '("GtkWidget*" "widget") ) ) (define-method set_sticky_pane (of-object "MooPaned") (c-name "moo_paned_set_sticky_pane") (return-type "none") (parameters '("gboolean" "sticky") ) ) (define-method set_pane_size (of-object "MooPaned") (c-name "moo_paned_set_pane_size") (return-type "none") (parameters '("int" "size") ) ) (define-method get_pane_size (of-object "MooPaned") (c-name "moo_paned_get_pane_size") (return-type "int") ) (define-method get_button_box_size (of-object "MooPaned") (c-name "moo_paned_get_button_box_size") (return-type "int") ) (define-method get_open_pane (of-object "MooPaned") (c-name "moo_paned_get_open_pane") (return-type "MooPane*") ) (define-method is_open (of-object "MooPaned") (c-name "moo_paned_is_open") (return-type "gboolean") ) (define-method open_pane (of-object "MooPaned") (c-name "moo_paned_open_pane") (return-type "none") (parameters '("MooPane*" "pane") ) ) (define-method present_pane (of-object "MooPaned") (c-name "moo_paned_present_pane") (return-type "none") (parameters '("MooPane*" "pane") ) ) (define-method hide_pane (of-object "MooPaned") (c-name "moo_paned_hide_pane") (return-type "none") ) (define-method detach_pane (of-object "MooPaned") (c-name "moo_paned_detach_pane") (return-type "none") (parameters '("MooPane*" "pane") ) ) (define-method attach_pane (of-object "MooPaned") (c-name "moo_paned_attach_pane") (return-type "none") (parameters '("MooPane*" "pane") ) ) (define-function moo_pane_params_new (c-name "moo_pane_params_new") (is-constructor-of "MooPaneParams") (return-type "MooPaneParams*") (parameters '("GdkRectangle*" "window_position" (null-ok) (default "NULL")) '("gboolean" "detached" (null-ok) (default "FALSE")) '("gboolean" "maximized" (null-ok) (default "FALSE")) '("gboolean" "keep_on_top" (null-ok) (default "FALSE")) ) ) (define-method copy (of-object "MooPaneParams") (c-name "moo_pane_params_copy") (return-type "MooPaneParams*") ) (define-function moo_pane_label_new (c-name "moo_pane_label_new") (is-constructor-of "MooPaneLabel") (return-type "MooPaneLabel*") (parameters '("const-char*" "icon_name" (null-ok) (default "NULL")) '("GdkPixbuf*" "icon_pixbuf" (null-ok) (default "NULL")) '("const-char*" "label_text" (null-ok) (default "NULL")) '("const-char*" "window_title" (null-ok) (default "NULL")) ) ) (define-method copy (of-object "MooPaneLabel") (c-name "moo_pane_label_copy") (return-type "MooPaneLabel*") ) (define-method free (of-object "MooPaneLabel") (c-name "moo_pane_label_free") (return-type "none") ) ;; From /home/muntyan/projects/moo/moo/mooutils/moopane.h (define-method set_label (of-object "MooPane") (c-name "moo_pane_set_label") (return-type "none") (parameters '("MooPaneLabel*" "label") ) ) (define-method get_label (of-object "MooPane") (c-name "moo_pane_get_label") (return-type "MooPaneLabel*") ) (define-method set_params (of-object "MooPane") (c-name "moo_pane_set_params") (return-type "none") (parameters '("MooPaneParams*" "params") ) ) (define-method get_params (of-object "MooPane") (c-name "moo_pane_get_params") (return-type "MooPaneParams*") ) (define-method set_detachable (of-object "MooPane") (c-name "moo_pane_set_detachable") (return-type "none") (parameters '("gboolean" "detachable") ) ) (define-method get_detachable (of-object "MooPane") (c-name "moo_pane_get_detachable") (return-type "gboolean") ) (define-method set_removable (of-object "MooPane") (c-name "moo_pane_set_removable") (return-type "none") (parameters '("gboolean" "removable") ) ) (define-method get_removable (of-object "MooPane") (c-name "moo_pane_get_removable") (return-type "gboolean") ) (define-method get_child (of-object "MooPane") (c-name "moo_pane_get_child") (return-type "GtkWidget*") ) (define-method get_index (of-object "MooPane") (c-name "moo_pane_get_index") (return-type "int") ) (define-method open (of-object "MooPane") (c-name "moo_pane_open") (return-type "none") ) (define-method present (of-object "MooPane") (c-name "moo_pane_present") (return-type "none") ) (define-method attach (of-object "MooPane") (c-name "moo_pane_attach") (return-type "none") ) (define-method detach (of-object "MooPane") (c-name "moo_pane_detach") (return-type "none") ) PIDA-0.5.1/moo/moopaned.h0000644000175000017500000001155610652670543013113 0ustar aliali/* * moopaned.h * * Copyright (C) 2004-2007 by Yevgen Muntyan * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * See COPYING file that comes with this distribution. */ #ifndef MOO_PANED_H #define MOO_PANED_H #include "moopane.h" #include G_BEGIN_DECLS #define MOO_TYPE_PANED (moo_paned_get_type ()) #define MOO_PANED(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), MOO_TYPE_PANED, MooPaned)) #define MOO_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MOO_TYPE_PANED, MooPanedClass)) #define MOO_IS_PANED(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), MOO_TYPE_PANED)) #define MOO_IS_PANED_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOO_TYPE_PANED)) #define MOO_PANED_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MOO_TYPE_PANED, MooPanedClass)) #define MOO_TYPE_PANE_POSITION (moo_pane_position_get_type ()) typedef struct _MooPaned MooPaned; typedef struct _MooPanedPrivate MooPanedPrivate; typedef struct _MooPanedClass MooPanedClass; typedef enum { MOO_PANE_POS_LEFT = 0, MOO_PANE_POS_RIGHT, MOO_PANE_POS_TOP, MOO_PANE_POS_BOTTOM } MooPanePosition; struct _MooPaned { GtkBin bin; GtkWidget *button_box; MooPanedPrivate *priv; }; struct _MooPanedClass { GtkBinClass bin_class; void (*set_pane_size) (MooPaned *paned, int size); void (*handle_drag_start) (MooPaned *paned, GtkWidget *pane_widget); void (*handle_drag_motion) (MooPaned *paned, GtkWidget *pane_widget); void (*handle_drag_end) (MooPaned *paned, GtkWidget *pane_widget); void (*pane_params_changed) (MooPaned *paned, guint index_); }; GType moo_paned_get_type (void) G_GNUC_CONST; GType moo_pane_position_get_type (void) G_GNUC_CONST; GtkWidget *moo_paned_new (MooPanePosition pane_position); MooPane *moo_paned_insert_pane (MooPaned *paned, GtkWidget *pane_widget, MooPaneLabel *pane_label, int position); gboolean moo_paned_remove_pane (MooPaned *paned, GtkWidget *pane_widget); guint moo_paned_n_panes (MooPaned *paned); GSList *moo_paned_list_panes (MooPaned *paned); MooPane *moo_paned_get_nth_pane (MooPaned *paned, guint n); int moo_paned_get_pane_num (MooPaned *paned, GtkWidget *widget); MooPane *moo_paned_get_pane (MooPaned *paned, GtkWidget *widget); void moo_paned_set_sticky_pane (MooPaned *paned, gboolean sticky); void moo_paned_set_pane_size (MooPaned *paned, int size); int moo_paned_get_pane_size (MooPaned *paned); int moo_paned_get_button_box_size (MooPaned *paned); MooPane *moo_paned_get_open_pane (MooPaned *paned); gboolean moo_paned_is_open (MooPaned *paned); void moo_paned_open_pane (MooPaned *paned, MooPane *pane); void moo_paned_present_pane (MooPaned *paned, MooPane *pane); void moo_paned_hide_pane (MooPaned *paned); void moo_paned_attach_pane (MooPaned *paned, MooPane *pane); void moo_paned_detach_pane (MooPaned *paned, MooPane *pane); MooPanePosition _moo_paned_get_position (MooPaned *paned); void _moo_paned_attach_pane (MooPaned *paned, MooPane *pane); void _moo_paned_insert_pane (MooPaned *paned, MooPane *pane, int position); G_END_DECLS #endif /* MOO_PANED_H */ PIDA-0.5.1/moo/moopaned.override0000644000175000017500000000155310652670543014477 0ustar aliali/**/ %% override moo_big_paned_find_pane kwargs static PyObject * _wrap_moo_big_paned_find_pane (PyGObject *self, PyObject *args, PyObject *kwargs) { static char *kwlist[] = { (char*) "pane_widget", NULL }; PyGObject *widget; MooPaned *child; PyObject *ret; MooPane *pane; if (!PyArg_ParseTupleAndKeywords (args, kwargs, (char*) "O!:MooBigPaned.find_pane", kwlist, &PyGtkWidget_Type, &widget)) return NULL; pane = moo_big_paned_find_pane (MOO_BIG_PANED (self->obj), GTK_WIDGET (widget->obj), &child); if (!pane) { Py_INCREF (Py_None); return Py_None; } ret = PyTuple_New (2); PyTuple_SET_ITEM (ret, 0, pygobject_new (G_OBJECT (pane))); PyTuple_SET_ITEM (ret, 1, pygobject_new (G_OBJECT (child))); return ret; } %% PIDA-0.5.1/moo/sticky.png0000644000175000017500000000013110652670543013137 0ustar alialiPNG  IHDRRW IDATc` 3b1 "Kb}aHIENDB`PIDA-0.5.1/moo/test.c0000644000175000017500000000701110652670543012252 0ustar aliali#include "moobigpaned.h" #include static void add_panes (GtkWidget *paned, MooPanePosition pane_position) { GtkWidget *textview; GtkTextBuffer *buffer; MooPaneLabel *label; textview = gtk_text_view_new (); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), GTK_WRAP_WORD); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); gtk_text_buffer_insert_at_cursor (buffer, "Hi there. Hi there. " "Hi there. Hi there. Hi there. Hi there. Hi there. ", -1); label = moo_pane_label_new (GTK_STOCK_OK, NULL, "TextView", "TextView"); moo_big_paned_insert_pane (MOO_BIG_PANED (paned), textview, label, pane_position, -1); moo_pane_label_free (label); label = moo_pane_label_new (GTK_STOCK_CANCEL, NULL, "A label", "A label"); moo_big_paned_insert_pane (MOO_BIG_PANED (paned), gtk_label_new ("foolala"), label, pane_position, -1); moo_pane_label_free (label); } int main (int argc, char *argv[]) { GtkWidget *window, *paned, *textview, *swin; GtkTextBuffer *buffer; gtk_init (&argc, &argv); // gdk_window_set_debug_updates (TRUE); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size (GTK_WINDOW (window), 800, 600); g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL); paned = moo_big_paned_new (); g_object_set (paned, "enable-detaching", TRUE, NULL); gtk_widget_show (paned); gtk_container_add (GTK_CONTAINER (window), paned); textview = gtk_text_view_new (); gtk_widget_show (textview); swin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); moo_big_paned_add_child (MOO_BIG_PANED (paned), swin); // gtk_container_add (GTK_CONTAINER (swin), textview); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (swin), gtk_label_new ("LABEL")); gtk_widget_show_all (swin); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (textview), GTK_WRAP_WORD); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); gtk_text_buffer_insert_at_cursor (buffer, "Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. " "Click a button. Click a button. Click a button. Click a button. ", -1); add_panes (paned, MOO_PANE_POS_RIGHT); add_panes (paned, MOO_PANE_POS_LEFT); add_panes (paned, MOO_PANE_POS_TOP); add_panes (paned, MOO_PANE_POS_BOTTOM); gtk_widget_show_all (window); gtk_main (); return 0; } PIDA-0.5.1/pida/0002755000175000017500000000000010652671501011250 5ustar alialiPIDA-0.5.1/pida/core/0002755000175000017500000000000010652671501012200 5ustar alialiPIDA-0.5.1/pida/core/__init__.py0000644000175000017500000000006510652670643014316 0ustar aliali# do this to make sure import pida.core.environment PIDA-0.5.1/pida/core/actions.py0000644000175000017500000001670610652670643014230 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005-2006 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """ Action support for PIDA services. """ # gtk import(s) import gtk # pida core import(s) from pida.core.base import BaseConfig from pida.core.options import OptionItem, manager, OTypeString ( TYPE_RADIO, TYPE_TOGGLE, TYPE_NORMAL, TYPE_MENUTOOL, ) = range(4) class PidaMenuToolAction(gtk.Action): """ Custom gtk.Action subclass for handling toolitems with a dropdown menu attached. """ __gtype_name__ = "PidaMenuToolAction" def __init__(self, *args, **kw): gtk.Action.__init__(self, *args, **kw) self.set_tool_item_type(gtk.MenuToolButton) _ACTIONS = { TYPE_NORMAL: gtk.Action, TYPE_TOGGLE: gtk.ToggleAction, TYPE_RADIO: gtk.RadioAction, TYPE_MENUTOOL: PidaMenuToolAction, } accelerator_group = gtk.AccelGroup() class ActionsConfig(BaseConfig): """ The base action configurator. Services using actions should subclass this, and set their actions_config class attribute to the class. It will be instantiated on service activation with the service instance passed as the parameter to the constructor. The service will be available as the svc attribute in the configurator instance. """ accelerator_group = accelerator_group def create(self): """ Called to initialize this configurator. Will initialise attributes, call create_actions, then register the actions and the ui definitions with the Boss. """ self._actions = gtk.ActionGroup(self.svc.get_name()) self._keyboard_options = {} self.create_actions() if self.svc.boss is not None: self.ui_merge_id = self.svc.boss.add_action_group_and_ui( self._actions, '%s.xml' % self.svc.get_name() ) def create_actions(self): """ Called to create the actions. Create your service actions actions here. Each action should be created with a call to create_action. These actions will be added to the action group for the service, and can be used for any purpose. """ def remove_actions(self): self.svc.boss.remove_action_group_and_ui(self._actions, self.ui_merge_id) def create_action(self, name, atype, label, tooltip, stock_id, callback=None, accel_string='NOACCEL'): """ Create an action for this service. :param name: The unique name for the action. This must be unique for the service, so choose names wisely. For example: `show_project_properties` :param atype: This is the type of action, and maps directly to a type of gtk.Action. Types include: - TYPE_NORMAL: A normal gtk.Action - TYPE_TOGGLE: A gtk.ToggleAction - TYPE_RADIO: A gtk.RadioAction - TYPE_MENUTOOL: A custom Action which contains a dropdown menu when rendered as a tool item :param label: The label to display on proxies of the action. :param toolip: The tool tip to display on proxies of the action. :param stock_id: The stock id of the icon to display on proxies of the action. :param callback: The callback function to be called when the action is activated. This function should take the action as a parameter. :param accel_string: The accelerator string set as the default accelerator for this action, or 'NOACCEL' for actions that do not need an accelerator. To be used these actions must be proxied as items in one of the menus or toolbars. """ aclass = _ACTIONS[atype] act = aclass(name=name, label=label, tooltip=tooltip, stock_id=stock_id) self._actions.add_action(act) if callback is None: callback = getattr(self, 'act_%s' % name, None) if callback is not None: act.connect('activate', callback) if accel_string != 'NOACCEL': self._create_key_option(act, name, label, tooltip, accel_string) def _create_key_option(self, act, name, label, tooltip, accel_string): opt = OptionItem(self._get_group_name(), name, label, OTypeString, accel_string, tooltip, self._on_shortcut_notify) opt.action = act opt.stock_id = act.get_property('stock-id') self._keyboard_options[name] = opt manager.register_option(opt) act.set_accel_group(self.accelerator_group) act.set_accel_path(self._create_accel_path(name)) act.connect_accelerator() def _get_shortcut_gconf_key(self, name): return '/app/pida/keyboard_shortcuts/%s/%s' % (self.svc.get_name(), name) def _get_group_name(self): return 'keyboard_shortcuts/%s' % self.svc.get_name() def get_action(self, name): """ Get the named action """ return self._actions.get_action(name) def get_action_group(self): """ Get the action group """ return self._actions def get_keyboard_options(self): """ Get the keyboard options. The keyboard options are a dict which stores the GConf directory containing the values for the keyboard shortcuts for the actions that do not have NOACCEL set. These are persisted on first run, and then loaded from GConf to maintian user preferences. """ return self._keyboard_options def _create_accel_path(self, name): return '/%s' % name def _set_action_keypress(self, name, accel_string): keyval, modmask = gtk.accelerator_parse(accel_string) gtk.accel_map_change_entry(self._create_accel_path(name), keyval, modmask, True) def _set_action_keypress_from_option(self, option): self._set_action_keypress(option.name, manager.get_value(option)) def _on_shortcut_notify(self, client, id, entry, option, *args): self._set_action_keypress_from_option(option) def subscribe_keyboard_shortcuts(self): """ Set the keyboard shortcuts for the actions with keyboard shortcuts enabled. """ for name, opt in self._keyboard_options.items(): self._set_action_keypress_from_option(opt) PIDA-0.5.1/pida/core/application.py0000755000175000017500000001023310652670643015063 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # system import(s) import os import sys import signal import warnings from pida.core.signalhandler import PosixSignalHandler # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext def die_cli(message, exception=None): """Die in a command line way.""" print message if exception: print exception print _('Exiting. (this is fatal)') sys.exit(1) # First gtk import, let's check it try: import gtk from gtk import gdk gdk.threads_init() if gtk.pygtk_version < (2, 8): die_cli(_('PIDA requires PyGTK >= 2.8. It only found %(major)s.%(minor)s') % {'major':gtk.pygtk_version[:2][0], 'minor':gtk.pygtk_version[:2][1]}) except ImportError, e: die_cli(_('PIDA requires Python GTK bindings. They were not found.'), e) try: from kiwi.ui.dialogs import error def die_gui(message, exception): """Die in a GUI way.""" error(_('Fatal error, cannot start PIDA'), long='%s\n%s' % (message, exception)) die_cli(message) except ImportError, e: die_cli(_('Kiwi needs to be installed to run PIDA'), e) # Python 2.4 if sys.version_info < (2,4): die_gui(_('Python 2.4 is required to run PIDA. Only %(major)s.%(minor)s was found.') % {'major':sys.version_info[:2][0], 'minor':sys.version_info[:2][1]}) # This can test if PIDA is installed try: from pida.core.environment import Environment from pida.core.boss import Boss from pida import PIDA_VERSION except ImportError, e: die_gui(_('The pida package could not be found.'), e) def run_version(env): print _('PIDA, version %s') % PIDA_VERSION return 0 def run_pida(env): b = Boss(env) PosixSignalHandler(b) try: start_success = b.start() except Exception, e: print e start_success = False if start_success: gdk.threads_enter() b.loop_ui() gdk.threads_leave() return 0 else: return 1 def force_quit(signum, frame): os.kill(os.getpid(), 9) # Set the signal handler and a 5-second alarm def set_trace(): import linecache def traceit(frame, event, arg): ss = frame.f_code.co_stacksize fn = frame.f_code.co_filename ln = frame.f_lineno co = linecache.getline(fn, ln).strip() print '%s %s:%s %s' % (ss * '>', fn, ln, co) for k, i in frame.f_locals.items(): try: print '%s=%s' % (k, i) except: print k, 'unable to print value' print sys.settrace(traceit) def main(): env = Environment(sys.argv) sys.argv = sys.argv[:1] if env.is_debug(): os.environ['PIDA_DEBUG'] = '1' os.environ['PIDA_LOG_STDERR'] = '1' else: warnings.filterwarnings("ignore") if env.is_trace(): set_trace() if env.is_version(): run_func = run_version else: run_func = run_pida exit_val = run_func(env) signal.signal(signal.SIGALRM, force_quit) signal.alarm(3) sys.exit(exit_val) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/base.py0000644000175000017500000000036610652670643013475 0ustar aliali class BaseConfig(object): def __init__(self, service): self.svc = service self.create() def create(self): """Override to do the creations""" def get_service_name(self): return self.svc.get_name() PIDA-0.5.1/pida/core/boss.py0000644000175000017500000001022510652670643013524 0ustar alialiimport sys import gtk from pida.core.servicemanager import ServiceManager from pida.core.log import build_logger from pida.ui.icons import IconRegister from pida.ui.window import PidaWindow from pida.ui.splash import SplashScreen from pida.utils.firstrun import FirstTimeWindow # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class Boss(object): def __init__(self, env=None): self._env = env self.log = build_logger('pida') self.show_splash() self._sm = ServiceManager(self) self._run_first_time() self._window = PidaWindow(self) def _run_first_time(self): if not self._env.has_firstrun() or self._env.is_firstrun(): ft = FirstTimeWindow(self._sm.get_available_editors()) success, editor = ft.run(self._env.get_firstrun_filename()) self.override_editor = editor self.quit_before_started = not success else: self.override_editor = None self.quit_before_started = False def start(self): if self.quit_before_started: return False else: self._sm.activate_services() if self.override_editor is not None: self.get_service('editor').set_opt('editor_type', self.override_editor) editor_name = self.get_service('editor').opt('editor_type') self._sm.activate_editor(editor_name) self._icons = IconRegister() self._window.start() self._sm.start_services() self._sm.start_editor() return True def stop(self, force=False): if force or self.window.yesno_dlg(_('Are you sure you want to quit PIDA ?')): gtk.main_quit() self._sm.stop() else: return True def loop_ui(self): gtk.main() def get_service(self, servicename): return self._sm.get_service(servicename) def get_services(self): return self._sm.get_services() def get_service_dirs(self): if self._env is None: return [] else: return [ self._env.get_base_service_directory(), self._env.get_local_service_directory(), ] def get_editor_dirs(self): if self._env is None: return [] else: return [ self._env.get_base_editor_directory(), ] def get_editor(self): return self._sm.editor editor = property(get_editor) def get_plugins(self): return self._sm.get_plugins() def start_plugin(self, plugin_path): return self._sm.start_plugin(plugin_path) def stop_plugin(self, plugin_name): return self._sm.stop_plugin(plugin_name) def subscribe_event(self, servicename, event, callback): svc = self.get_service(servicename) svc.subscribe_event(event, callback) def unsubscribe_event(self, servicename, event, callback): svc = self.get_service(servicename) svc.unsubscribe_event(event, callback) def subscribe_feature(self, servicename, feature, instance): svc = self.get_service(servicename) return svc.subscribe_feature(feature, instance) def unsubscribe_feature(self, servicename, feature_object): svc = self.get_service(servicename) svc.unsubscribe_feature(feature_object) def add_action_group_and_ui(self, actiongroup, uidef): self._window.add_action_group(actiongroup) return self._window.add_uidef(uidef) def remove_action_group_and_ui(self, actiongroup, ui_merge_id): self._window.remove_uidef(ui_merge_id) self._window.remove_action_group(actiongroup) def cmd(self, servicename, commandname, **kw): return self.get_service(servicename).cmd(commandname, **kw) def get_pida_home(self): return self._env.pida_home def get_window(self): return self._window window = property(get_window) def show_splash(self): self._splash = SplashScreen() self._splash.show_splash() def hide_splash(self): self._splash.hide_splash() PIDA-0.5.1/pida/core/charfinder.py0000644000175000017500000001105110652670643014661 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import codecs import re class IEncodingDetector: def __call__(self, stream, filename, mimetype): """Should return None and not found and a string with the encoding when it is found.""" #def open_encoded(filename, *args, **kwargs): # """Opens and auto detects the encoding""" # stream = open(filename, "rb") # encoding = find_encoding_stream(stream) # stream.seek(0) # return codecs.EncodedFile(stream, encoding, *args, **kwargs) class MimeDetector: def __init__(self): self.mimes = {} def register(self, mime, sniffer): self.mimes[mime] = sniffer def __call__(self, stream, filename, mimetype): try: return self.mimes[mimetype](stream, filename, mimetype) except KeyError: pass class DumbDetector: encodings = ["utf-8", "iso-8859-15", "windows-1252"] def __call__(self, stream, filename, mimetype): for encoding in self.encodings: try: codecs.open(filename, encoding=encoding).read() return encoding except UnicodeDecodeError: pass return "ascii" class DetectorManager: def __init__(self, *args): self.detectors = list(args) self.last_resort = DumbDetector() def __call__(self, stream, filename, mimetype): for encoder in self.detectors: encoding = encoder(stream, filename, mimetype) stream.seek(0) if encoding is not None: break if encoding is None: return self.last_resort(stream, filename, mimetype) return encoding def append_detector(self, detector): self.detectors.append(detector) def insert_detector(sef, index, detector): self.detectors.insert(index, detector) ############################################################################## # These are the singletons used so that other programs can register # XXX: this could be moved later to a plugin.Registry MIME_DETECTOR = MimeDetector() DETECTOR_MANAGER = DetectorManager(MIME_DETECTOR) ############################################################################## # Register a Mime-type sniffer class PythonDetector: PY_ENC = re.compile(r"coding: ([\w\-_0-9]+)") def _sniff_python_line(self, line): return self.PY_ENC.search(line).group(1) def __call__(self, stream, filename, mimetype): try: return self._sniff_python_line(stream.readline()) except AttributeError: pass try: return self._sniff_python_line(stream.readline()) except AttributeError: pass MIME_DETECTOR.register(("text", "x-python"), PythonDetector()) ############################################################################## # Register chardet, which is an optional detection scheme try: from chardet.universaldetector import UniversalDetector def chardet_sniff(stream, filename, mimetype): detector = UniversalDetector() chunk = stream.read(4086) while chunk != "": detector.feed(chunk) if detector.done: break chunk = stream.read(4086) detector.close() return detector.result["encoding"] DETECTOR_MANAGER.append_detector(chardet_sniff) except ImportError: passPIDA-0.5.1/pida/core/commands.py0000644000175000017500000000251410652670643014361 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from pida.core.base import BaseConfig class CommandsConfig(BaseConfig): def call(self, name, **kw): cmd = getattr(self, name) val = cmd(**kw) return val # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/document.py0000644000175000017500000002761010652670643014402 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import mimetypes import stat import gobject import base import time from charfinder import DETECTOR_MANAGER import codecs import actions # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class document_handler(object): globs = [] type_name = 'document' def init(self): self.__filenames = {} self.__docids = {} def create_document(self, filename): pass def view_document(self, document): pass def relpath(target, basepath=os.curdir): """ Return a relative path to the target from either the current dir or an optional base dir. Base can be a directory specified either as absolute or relative to current dir. """ if not os.path.exists(target): raise OSError, _('Target does not exist: ')+target if not os.path.isdir(basepath): raise OSError, _('Base is not a directory or does not exist: ')+basepath base_list = (os.path.abspath(basepath)).split(os.sep) target_list = (os.path.abspath(target)).split(os.sep) # On the windows platform the target may be on a completely different # drive from the base. if os.name in ['nt', 'dos', 'os2'] and base_list[0] != target_list[0]: msg = _('Target is on a different drive to base. Target: %(target)s, base: %(base)s') msg %= {target:target_list[0].upper(), base:base_list[0].upper()} raise OSError(msg) # Starting from the filepath root, work out how much of the filepath is # shared by base and target. for i in range(min(len(base_list), len(target_list))): if base_list[i] != target_list[i]: break else: # If we broke out of the loop, i is pointing to the first differing # path elements. If we didn't break out of the loop, i is pointing to # identical path elements. Increment i so that in all cases it points # to the first differing path elements. i+=1 rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:-1] if rel_list: rel_list = rel_list + [''] return os.path.join(*rel_list) else: return '' new_file_index = 1 class Document(object): """Base document class.""" """A real file on disk.""" icon_name = 'new' markup_prefix = '' markup_directory_color = '#0000c0' markup_attributes = ['project_name', 'project_relative_path', 'basename', 'directory_colour'] markup_string = ('' '%(project_name)s:' '' '%(project_relative_path)s/' '%(basename)s') contexts = [] is_new = False def __init__(self, boss, filename=None, markup_attributes=None, markup_string=None, contexts=None, icon_name=None, handler=None, detect_encoding=DETECTOR_MANAGER): self.boss = boss self.__handler = handler self.__filename = filename self.__unique_id = time.time() self.__project = None self.__newfile_index = None self.__detect_encoding = detect_encoding self.creation_time = time.time() if filename is None: global new_file_index self.__newfile_index = new_file_index new_file_index = new_file_index + 1 if markup_attributes is not None: self.markup_attributes = markup_attributes if markup_string is not None: self.markup_string = markup_string self.project, self.project_relative_path = self.get_project_relative_path() self.__reset() def __reset(self): self.__lines = None self.__string = None self.__stat = None self.__mimetype = None self.__encoding = None reset = __reset def __load(self): if self.__filename is None: return if self.__stat is None: self.__stat = self.__load_stat() if self.__mimetype is None: self.__mimetype = self.__load_mimetype() #if self.__encoding is not None: # Loading was already found, we're done #AA we like to load again!! #assert self.__string is not None #assert self.__lines is not None #return # lines and string depend on encoding try: stream = open(self.__filename, "rb") try: fname = self.__filename mime = self.__mimetype self.__encoding = self.__detect_encoding(stream, fname, mime) stream.seek(0) stream = codecs.EncodedFile(stream, self.__encoding) self.__lines = list(stream) self.__string = "".join(self.__lines) finally: stream.close() except IOError: # When there's a problem set the encoding to None and the rest too self.__encoding = None self.__lines = None self.__string = None raise # Also warn the log about it self.log.warn(_('failed to open file %s'), self.filename) def __load_stat(self): try: stat_info = os.stat(self.__filename) except OSError: stat_info = None return stat_info def __load_mimetype(self): typ, encoding = mimetypes.guess_type(self.__filename) if typ is None: mimetype = ('', '') else: mimetype = tuple(typ.split('/')) return mimetype def __iter__(self): self.__load() return iter(self.__lines) def get_lines(self): return self.__iter__() lines = property(get_lines) def __len__(self): self.__load() return self.__stat[stat.ST_SIZE] length = property(__len__) def __get_string(self): self.__load() return self.__string string = property(__get_string) def __get_stat(self): self.__stat = self.__load_stat() return self.__stat stat = property(__get_stat) def get_mtime(self): return self.stat[stat.ST_MTIME] modified_time = property(get_mtime) def get_size(self): return self.stat[stat.ST_SIZE] size = property(get_size) def __get_mimetype(self): return self.__mimetype mimetype = property(__get_mimetype) __encoding = None def get_encoding(self): self.__load() return self.__encoding encoding = property(get_encoding) def get_directory(self): return os.path.dirname(self.filename) directory = property(get_directory) def get_directory_basename(self): return os.path.basename(self.directory) directory_basename = property(get_directory_basename) def get_basename(self): return os.path.basename(self.filename) basename = property(get_basename) def get_directory_colour(self): return self.markup_directory_color directory_colour = property(get_directory_colour) def poll(self): self.__load() new_stat = self.__load_stat() if new_stat is None: return False if new_stat.st_mtime != self.__stat.st_mtime: self.__stat = new_stat self.__reset() return True else: return False def poll_until_change(self, callback, delay=1000): def do_poll(): poll = self.poll() if poll: callback() return False else: return True gobject.timeout_add(delay, do_poll) def get_filename(self): return self.__filename def set_filename(self, filename): self.__filename = filename filename = property(get_filename, set_filename) def get_unique_id(self): return self.__unique_id unique_id = property(get_unique_id) def get_markup(self): prefix = '%s ' % self.markup_prefix if self.filename is not None: s = self.markup_string % self.__build_markup_dict() else: s = 'New File %s' % self.__newfile_index return '%s%s' % (prefix, s) markup = property(get_markup) def __build_markup_dict(self): markup_dict = {} for attr in self.markup_attributes: markup_dict[attr] = getattr(self, attr) return markup_dict def get_handler(self): return self.__handler handler = property(get_handler) def get_project_name(self): if self.project is not None: return self.project.get_display_name() else: return '' project_name = property(get_project_name) def get_project_relative_path(self): match = self.boss.cmd('project', 'get_project_for_document', document=self) if match is None: return None, os.sep.join(self.directory.split(os.path.sep)[-2:]) else: project, path = match return project, path def set_project(self, project): self.__project = project def get_is_new(self): return self.filename is None is_new = property(get_is_new) def get_newfile_index(self): return self.__newfile_index newfile_index = property(get_newfile_index) class DocumentCache(object): def __init__(self, result_call): self._get_result = result_call self._cache = {} def get_result(self, document): try: result, mtime = self._cache[document.unique_id] except KeyError: result = mtime = None docmtime = document.stat.st_mtime if docmtime != mtime: result = self._get_result(document) self._cache[document.unique_id] = (result, docmtime) return result import unittest class DocumentCacheTest(unittest.TestCase): def setUp(self): self.calls = 0 def call(doc): self.calls += 1 return 1 self.cache = DocumentCache(call) class MockD: class stat: m_time = 1 unique_id = 1 self.doc = MockD() def test_get(self): self.assertEqual(self.calls, 0) self.assertEqual(self.cache.get_result(self.doc), 1) self.assertEqual(self.calls, 1) self.assertEqual(self.cache.get_result(self.doc), 1) self.assertEqual(self.calls, 1) self.doc.stat.m_time = 2 self.assertEqual(self.cache.get_result(self.doc), 1) self.assertEqual(self.calls, 2) def test(): unittest.main() PIDA-0.5.1/pida/core/editors.py0000644000175000017500000001154410652670643014234 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from pida.core.actions import ActionsConfig, TYPE_NORMAL from pida.core.commands import CommandsConfig from pida.core.service import Service # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class EditorActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'undo', TYPE_NORMAL, _('Undo'), _('Undo the last editor action'), gtk.STOCK_UNDO, self.on_undo, 'Z', ) self.create_action( 'redo', TYPE_NORMAL, _('Redo'), _('Redo the last editor action'), gtk.STOCK_REDO, self.on_redo, 'Y', ) self.create_action( 'cut', TYPE_NORMAL, _('Cut'), _('Cut the selection in the editor'), gtk.STOCK_CUT, self.on_cut, 'X', ) self.create_action( 'copy', TYPE_NORMAL, _('Copy'), _('Copy the selection in the editor'), gtk.STOCK_COPY, self.on_copy, 'C', ) self.create_action( 'paste', TYPE_NORMAL, _('Paste'), _('Paste the clipboard in the editor'), gtk.STOCK_PASTE, self.on_paste, 'V', ) self.create_action( 'save', TYPE_NORMAL, _('Save'), _('Save the current document'), gtk.STOCK_SAVE, self.on_save, 'S', ) self.create_action( 'focus_editor', TYPE_NORMAL, _('Focus Editor'), _('Focus the editor component window'), 'application-edit', self.on_focus_editor, 'e', ) def on_undo(self, action): self.svc.undo() def on_redo(self, action): self.svc.redo() def on_cut(self, action): self.svc.cut() def on_copy(self, action): self.svc.copy() def on_paste(self, action): self.svc.paste() def on_save(self, action): self.svc.save() def on_focus_editor(self, action): self.svc.grab_focus() class EditorCommandsConfig(CommandsConfig): def open(self, document): self.svc.open(document) def close(self, document): self.svc.close(document) def goto_line(self, line): self.svc.goto_line(line) def define_sign_type(self, type, icon, linehl, text, texthl): self.svc.define_sign_type(type, icon, linehl, text, texthl) def undefine_sign_type(self, type): self.svc.undefine_sign_type(type) def get_current_line_number(self): return self.svc.get_current_line() def show_sign(self, type, file_name, line): self.svc.show_sign(type, file_name, line) def hide_sign(self, type, file_name, line): self.svc.hide_sign(type, file_name, line) def call_with_current_word(self, callback): self.svc.call_with_current_word(callback) def call_with_selection(self, callback): self.svc.call_with_selection(callback) def grab_focus(self): self.svc.grab_focus() def delete_current_word(self): self.svc.delete_current_word() def insert_text(self, text): self.svc.insert_text(text) class EditorService(Service): actions_config = EditorActionsConfig commands_config = EditorCommandsConfig @classmethod def get_sanity_errors(cls): return [] # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/environment.py0000644000175000017500000000514210652670643015124 0ustar alialiimport os from optparse import OptionParser from kiwi.environ import Library, environ # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext library = Library('pida', root='../') library.add_global_resource('glade', 'resources/glade') library.add_global_resource('uidef', 'resources/uidef') library.add_global_resource('pixmaps', 'resources/pixmaps') def get_resource_path(resource, name): return environ.find_resource(resource, name) def get_uidef_path(name): return get_resource_path('uidef', name) def get_glade_path(name): return get_resource_path('glade', name) def get_pixmap_path(name): return get_resource_path('pixmaps', name) class Environment(object): pida_home = os.path.expanduser('~/.pida2') def __init__(self, argv): if not os.path.exists(self.pida_home): os.mkdir(self.pida_home) self.get_options(argv) self.env = dict(os.environ) def get_options(self, argv): op = OptionParser() op.add_option('-v', '--version', action='store_true', help=_('Print version information and exit.')) op.add_option('-D', '--debug', action='store_true', help=_('Run PIDA with added debug information.')) op.add_option('-T', '--trace', action='store_true', help=_('Run PIDA with tracing.')) op.add_option('-F', '--firstrun', action='store_true', help=_('Run the PIDA first run wizard.')) self.opts, self.args = op.parse_args(argv) def is_version(self): return self.opts.version def is_debug(self): return self.opts.debug def is_trace(self): return self.opts.trace def is_firstrun(self): return self.opts.firstrun def get_base_service_directory(self): return os.path.join( os.path.dirname(os.path.dirname(__file__)), 'services') def get_local_service_directory(self): path = os.path.join(self.pida_home, 'services') if not os.path.exists(path): os.mkdir(path) return path def get_base_editor_directory(self): return os.path.join( os.path.dirname(os.path.dirname(__file__)), 'editors') def get_plugins_directory(self): path = os.path.join(self.pida_home, 'plugins') if not os.path.exists(path): os.mkdir(path) return path def get_firstrun_filename(self): return os.path.join(self.pida_home, 'first_run_wizard') def has_firstrun(self): return os.path.exists(self.get_firstrun_filename()) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/events.py0000644000175000017500000001220410652670643014061 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005-2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import time from base import BaseConfig class Event(object): """ An event dispatcher is the central events object. To use it you must first create an event with the ``create_event`` method, this will return an event source which is basically the function you'll use to trigger the event. After that you register the callbacks. Its usage follows: >>> dispatcher = Event() >>> evt_src = dispatcher.create_event ("on-ring-event") >>> def callback1 (): ... print "riiiing!" >>> dispatcher.register("on-ring-event", callback1) >>> evt_src () riiiing! """ def __init__(self): self.__events = {} def create_event (self, event_name): self.__events[event_name] = [] def event_source (*args, **kwargs): for callback in self.__events[event_name]: callback(*args, **kwargs) return event_source def create_events (self, event_names, event_sources = None): """ This is a utility method that creates or fills a dict-like object and returns it. The keys are the event names and the values are the event sources. """ if event_sources is None: event_sources = {} for evt_name in event_names: event_sources[evt_name] = self.create_event(evt_name) return event_sources def has_event(self, event_name): return event_name in self.__events def register (self, event_name, callback): assert self.has_event(event_name) self.__events[event_name].append(callback) def unregister (self, event_name, callback): assert self.has_event(event_name) self.__events[event_name].remove(callback) def emit(self, event_name, **kw): for callback in self.__events.get(event_name): # TODO: remove this after profilling is done current_time = time.time() callback(**kw) elapsed = time.time() - current_time if elapsed >= 0.001: try: classname = callback.im_self.__class__.__name__ kind = callback.im_self.__class__.__module__ # im within pida - strip useless extra informations if kind.startswith("pida"): kind = kind.split('.')[1] #self.log.debug( "%s: %f -- %s: %s" % ( # event_name, elapsed, kind, classname)) except AttributeError, v: #self.log.debug( #"Error couldnt extract callback informations - %s"%v) #self.log.debug( # "%s: %f -- %r" % (event_name, elapsed, callback)) pass def get(self, event_name): return self.__events[event_name] def list_events(self): return self.__events.keys() class EventsConfig(BaseConfig): def create(self): self._events = Event() self._foreign_events = {} self.create_events() def create_events(self): """Create your events here""" def create_event(self, name): self._events.create_event(name) def subscribe_foreign_events(self): """Subscribe to events here""" def subscribe_foreign_event(self, servicename, event, callback): self._foreign_events[(servicename, event)] = callback self.svc.subscribe_foreign_event(servicename, event, callback) def unsubscribe_foreign_events(self): for (servicename, eventname), callback in self._foreign_events.items(): self.svc.unsubscribe_foreign_event(servicename, eventname, callback) def subscribe_event(self, event, callback): self._events.register(event, callback) def unsubscribe_event(self, event, callback): self._events.unregister(event, callback) def get(self, event): return self._events.get(event) def emit(self, event, **kw): return self._events.emit(event, **kw) PIDA-0.5.1/pida/core/features.py0000644000175000017500000000571510652670643014404 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from pida.core.base import BaseConfig from pida.core.plugins import Registry class FeaturesConfig(BaseConfig): def create(self): self._features = Registry() self._featurenames = [] self._foreign_feature_objects = {} self.create_features() def create_features(self): """Create the features here""" def create_feature(self, name): self._featurenames.append(name) def list_features(self): return self._featurenames def subscribe_foreign_features(self): """Subscribe to features here""" def unsubscribe_foreign_features(self): for (servicename, featurename), feature_objects in self._foreign_feature_objects.items(): for feature_object in feature_objects: self.svc.unsubscribe_foreign_feature(servicename, feature_object) del self._foreign_feature_objects[(servicename, featurename)] def has_foreign_feature(self, servicename, featurename): for (service, feature), feature_object in self._foreign_feature_objects.items(): if servicename == service and featurename == feature: return True return False def subscribe_foreign_feature(self, servicename, featurename, instance): feature_object = self.svc.subscribe_foreign_feature(servicename, featurename, instance) self._foreign_feature_objects.setdefault((servicename, featurename), []).append(feature_object) def subscribe_feature(self, featurename, instance): return self._features.register_plugin( instance=instance, features=(featurename,) ) def unsubscribe_feature(self, feature_object): self._features.unregister(feature_object) def get_feature_providers(self, featurename): return self._features.get_features(featurename) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/interfaces.py0000644000175000017500000000430010652670643014676 0ustar aliali try: from protocols import Interface except ImportError: class Interface: """A dummy""" class IBaseConfig(Interface): def create(): """Create all the items in this configuration""" class IOptions(IBaseConfig): def add_option(group, name, label, doc): """Add a configuration group""" class IEvents(IBaseConfig): def create_event(name): """Create an Event""" class ICommands(IBaseConfig): """The commands for a plugin""" class IFeatures(IBaseConfig): """The features for a plugin""" class IActions(IBaseConfig): """The actions for a service""" def create_actions(): """Create actions here""" class IService(Interface): def get_name(): """Get the name for the service""" class IPlugin(Interface): """A plugin""" class IEditor(Interface): def start(): """Start the editor""" def started(): """Called when the editor has started""" def get_current(): """Get the current document""" def open(document): """Open a document""" def open_many(documents): """Open a few documents""" def close(): """Close the current document""" def close_all(): """Close all the documents""" def save(): """Save the current document""" def save_as(filename): """Save the current document as another filename""" def revert(): """Revert to the loaded version of the file""" def goto_line(linenumber): """Goto a line""" def cut(): """Cut to the clipboard""" def copy(): """Copy to the clipboard""" def paste(): """Paste from the clipboard""" def grab_focus(): """Grab the focus""" def set_undo_sensitive(sensitive): """Set the undo action sensitivity""" def set_redo_sensitive(sensitive): """Set the redo action sensitivity""" def set_save_sensitive(sensitive): """Set the save action sensitivity""" def set_revert_sensitive(sensitive): """Set the revert sensitivity""" class IProjectController(Interface): """A Project Controller""" class IFileManager(Interface): """A File Manager""" PIDA-0.5.1/pida/core/locale.py0000644000175000017500000000173010652670643014016 0ustar alialiimport gettext import os import gtk.glade class Locale(object): def __init__(self, modulename): self.modulename = modulename self.localepath = self.get_base_locale_directory() gettext.bindtextdomain(self.modulename, self.localepath) gettext.textdomain(self.modulename) def get_base_locale_directory(self): # except for main pida if self.modulename == 'pida': return os.path.join(os.path.dirname( os.path.dirname(__file__)), 'resources', 'locale') # for service/plugin return os.path.join(os.path.dirname( os.path.dirname(__file__)), 'services', self.modulename, 'locale') def gettext(self, message): return gettext.dgettext(self.modulename, message) def bindglade(self): gtk.glade.bindtextdomain(self.modulename, self.localepath) gtk.glade.textdomain(self.modulename) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/log.py0000644000175000017500000000411410652670643013337 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005-2006 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import logging import logging.handlers def build_logger(name, filepath=None): format_str = ('%(asctime)s ' '%(levelname)s ' '%(module)s.%(name)s:%(lineno)s ' '%(message)s') format = logging.Formatter(format_str) # logger logger = logging.getLogger(name) # to file if filepath is not None: handler = logging.handlers.RotatingFileHandler(filepath, 'a', 16000, 3) else: handler = logging.StreamHandler() handler.setFormatter(format) logger.addHandler(handler) # optionally to stdout #if 'PIDA_LOG_STDERR' in os.environ: # handler = logging.StreamHandler() # handler.setFormatter(format) # logger.addHandler(handler) if 'PIDA_DEBUG' in os.environ: level = logging.DEBUG else: level = logging.INFO logger.setLevel(level) return logger PIDA-0.5.1/pida/core/options.py0000644000175000017500000001076410652670643014261 0ustar alialiimport gconf from pida.core.base import BaseConfig class OptionsManager(object): def __init__(self, boss=None): self._client = gconf.client_get_default() self.initialize_gconf() def initialize_gconf(self): self.add_directory('pida') self.add_directory('pida', 'keyboard_shortcuts') def add_directory(self, *parts): self._client.add_dir( '/'.join(['/apps'] + list(parts)), gconf.CLIENT_PRELOAD_NONE ) def add_service_directory(self, service): self.add_directory('pida', service.get_name()) def register_option(self, option): val = self._client.get(option.key) if val is None: option.set(self._client, option.default) if option.callback is not None: self.add_notify(option, option.callback) def add_notify(self, option, callback, *args): args = tuple([option] + list(args)) if len(args) == 1: args = args[0] self._client.notify_add(option.key, callback, args) def get_value(self, option): return option.get(self._client) def set_value(self, option, value): return option.set(self._client, value) class OTypeBase(object): def _getter(self, client): return getattr(client, 'get_%s' % self.gconf_name) def _setter(self, client): return getattr(client, 'set_%s' % self.gconf_name) def get(self, client, key): return self._getter(client)(key) def set(self, client, key, value): return self._setter(client)(key, value) class OTypeString(OTypeBase): """A string configuration type""" gconf_name = 'string' class OTypeBoolean(OTypeBase): """A Boolean configuration type""" gconf_name = 'bool' class OTypeInteger(OTypeBase): """An integer configuration type""" gconf_name = 'int' class OTypeStringList(OTypeBase): """A list of strings configuration type""" gconf_name = 'list' def get(self, client, key): return self._getter(client)(key, gconf.VALUE_STRING) def set(self, client, key, value): return self._setter(client)(key, gconf.VALUE_STRING, value) class OTypeFile(OTypeString): """For files""" class OTypeFont(OTypeString): """Fonts""" class OTypeStringOption(OTypeString): """String from a list of options""" # Awful def otype_string_options_factory(options): return type('', (OTypeStringOption,), {'options': options}) class OptionItem(object): def __init__(self, group, name, label, rtype, default, doc, callback): self.group = group self.name = name self.label = label self.rtype = rtype() self.doc = doc self.default = default self.key = self._create_key() self.callback = callback def _create_key(self): return '/apps/pida/%s/%s' % (self.group, self.name) def get(self, client): return self.rtype.get(client, self.key) def set(self, client, value): return self.rtype.set(client, self.key, value) def get_value(self): return manager.get_value(self) def set_value(self, value): return manager.set_value(self, value) value = property(get_value, set_value) def add_notify(self, callback, *args): manager.add_notify(self, callback, *args) manager = OptionsManager() class OptionsConfig(BaseConfig): manager = manager def create(self): self._options = {} self.create_options() self.register_options() def create_options(self): """Create the options here""" def register_options(self): self.manager.add_service_directory(self.svc) for option in self._options.values(): self.manager.register_option(option) def create_option(self, name, label, rtype, default, doc, callback=None): opt = OptionItem(self.svc.get_name(), name, label, rtype, default, doc, callback) self.add_option(opt) return opt def add_option(self, option): self._options[option.name] = option def get_option(self, optname): return self._options[optname] def get_value(self, optname): return self.manager.get_value(self.get_option(optname)) def set_value(self, optname, value): return self.manager.set_value(self.get_option(optname), value) def __len__(self): return len(self._options) def iter_options(self): return self._options.values() PIDA-0.5.1/pida/core/plugins.py0000644000175000017500000004543310652670643014250 0ustar aliali"""A flexible plugin framework. Clear your mind of any previously defined concept of a plugin. Key components: * Registry: stores a set of plugins * Plugin: defines a set of behaviours * Registry key: unique behavioural identifier Types of definable behaviour: 1. Singleton 2. Feature 3. Extension Point/Extender A plugin can register any number of the above behaviour types. 1. Singleton When a plugin registers as a singleton for a key, it is saying "I provide the behaviour", so when the registry is looked up for that key, the object is returned. At this point, please consider that an ideal registry key may be an Interface definition (formal or otherwise), so when you ask for the behaviour by interface you are actually returned an object implementing that interface. 2. Feature When a plugin defines a Feature, it is again saying "I provide the behaviour", the difference with singleton is that many plugins can define a feature, and these plugins are aggregated and can be looked up by registry key. The look up returns a list of objects that claim to provide the key. 3. Extension point An extension point is identical to a feature except that the keys for it must be predefined and are fixed. While a plugin may invent a feature and others can join it, it is expected that whatever creates the registry formally defines the extension points and they are then fixed. This can be used to simulate the behaviour of traditional (Eclipse or Trac) extension points. The plugin itself supplies the Extender (that which extends), while the registry contains the Extension point itself (that which is to be extended). Defining Plugins: 1. Singletons a. First you will need a registry item: reg = Registry() b. now define a behavioural interface: class IAmYellow(Interface): def get_shade(): "get the shade of yellow" c. now write a class that implements this behaviour: class Banana(object): def get_shade(self): return 'light and greeny' d. create an instance of the plugin plugin = Banana() e. register it with the registry: reg.register_plugin( instance=plugin, singletons=(IAmYellow,) ) f. get the item from the registry at a later time: plugin = reg.get_singleton(IAmYellow) print plugin.get_shade() Things to note: * Attempting to register another plugin with a singleton of IAmYellow will fail. * Looking up a non-existent singleton will raise a SingletonError. """ import weakref ############################################################################## ## Core data types def copy_docs(cls): def decorator(func): func.__doc__ = getattr(cls, func.__name__).__doc__ return func return decorator class NamedSets(object): """ The theory of the plugin architecture has its foundations on this simple structure which is a simple collection of named sets. Each key is associated to a set and the available operations are: to add elements to the named set or to remove them. """ def __getitem__(self, name): """ Returns the named set. @param name: the name of the set @return: an iterator to the named set. """ raise NotImplementedError def add(self, name, value): """ Add one one value to the named set. @param name: the name of the set @param value: the value to be added to the set """ raise NotImplementedError def remove(self, name, value): """ Remove the `value` from the set named `name`. @param name: the name of the set to remove the value from @param value: the value to remove from the named set """ raise NotImplementedError def keys(self): """ Return a collection of the names of the existing sets. """ return self.data.keys() names = keys def __delitem__(self, name): """ Remove the named set. @param name: the name of the set to be removed. """ del self.data[name] @copy_docs(list) def __repr__(self): return "<%s: %r>"%( self.__class__.__name__, self.data ) @copy_docs(list) def __len__(self): return len(self.data) @copy_docs(list) def __iter__(self): return iter(self.data) class StrictNamedSets(NamedSets): """ A strict named sets is a `NamedSets` that has fixed predefined sets. In order to access a set, for adding or removing elements, you must initialize it first. Trying to perform an operation on a undefined named set will result in a `KeyError`. """ def __init__(self, names=()): """ Creates a strict named sets by providing an optional number of keys to define. @param names: the sets to initialize. """ self.data = dict((name, set()) for name in set(names)) @copy_docs(NamedSets) def __getitem__(self, name): return self.data[name] @copy_docs(NamedSets) def add(self, key, value): self.data[key].add(value) @copy_docs(NamedSets) def remove(self, key, value): return self.data[key].discard(value) class DynamicNamedSets(NamedSets): """ In a dynamic named set the sets are created (empty sets) when you access them. """ def __init__(self): """Creates an empty dynamic named sets object.""" self.data = {} @copy_docs(NamedSets) def __getitem__(self, key): return self.data.get(key, ()) @copy_docs(NamedSets) def remove(self, key, value): key_set = self.data.get(key, None) if key_set is not None: key_set.discard(value) if not key_set: del self.data[key] return value @copy_docs(NamedSets) def add(self, key, value): self.data.setdefault(key, set()).add(value) @copy_docs(NamedSets) def __delitem__(self, key): self.data.pop(key, None) @copy_docs(dict) def clear(self): self.data.clear() ############################################################################## ## Base classes class Plugin(object): """A possible implementation of a Plugin. A plugin holds an object. When the 'get_instance' method is called, by suplying a registry, the held object is returned. If you extend `Plugin` you can change this by suplying one instance for an appropriate registry, or generating an instance every time the method is called. You can create a plugin's instance by issuing an `instance` or a `factory` function. The factory function receives an argument, the context registry and returns the object this plugin holds. If you use the factory it is called only once, to set the holded object, when the `get_instance` method is called. """ def __init__(self, instance=None, factory=None): if factory is not None and not callable(factory): raise TypeError("If you specify a factory it must be a callable object.", factory) if factory is None: self.instance = instance else: self.factory = factory def get_instance(self, registry): """Returns the object associated with the `Plugin`.""" try: return self.instance except AttributeError: self.instance = self.factory(registry) return self.instance def reset(self): """When this plugin contains a factory makes it regen the instance.""" if hasattr(self,"instance"): del self.instance def unplug(self, registry): """This method is called when the service is removed from the registry""" ############################################################################## ## Implementations class ExtensionPointError(StandardError): """Raised when there's an error of some sort""" class ExtensionPoint(object): """This class is based on Eclipse's plugin architecture. An extension point is a class for defining a number of named sets, we'll address each named list an extension. Conceptually an `ExtensionPoint` is a special case of a `NamedList`, they have an equal interface. In order to access extensions we have to initialize the `ExtensionPoint` by calling the `init_extensions` method. Before initializing the `ExtensionPoint` we can add objects in any extensions. Objects added before initialization that are contained in an extension not initialized will be silentely discarded. After the `ExtensionPoint` is initialized, when objects are added to an extension, they are activated, calling the protected method `_activate`. The `_activate` method can be create to mutate objects when they are inserted into the extension. Objects added to extensions before the `ExtensionPoint` is initialized are only activated when the `init_extensions` method is called. """ def __init__(self): """Creates a new extension point object.""" self.lazy = DynamicNamedSets() def _activate(self, extender): """ This method is called when the object is placed in an initialized extension. """ return extender def init_extensions(self, extension_points): """ Initializes the valid extensions. """ self.data = StrictNamedSets(extension_points) for ext_pnt in self.lazy: try: for extender in self.lazy[ext_pnt]: self.data.add(ext_pnt, self._activate(extender)) except KeyError: pass del self.lazy @copy_docs(NamedSets) def add(self, name, value): """Adds one more element to the extension point, or named list.""" try: self.data.add(name, self._activate(value)) except AttributeError: self.lazy.add(name, value) @copy_docs(NamedSets) def __getitem__(self, key): try: return self.data[key] except AttributeError: raise ExtensionPointError("Not initialized, run init() first") get_extension_point = __getitem__ def has_init(self): """ Verifies if the extension point was already initialized. """ return hasattr(self, "data") @copy_docs(NamedSets) def keys(self): try: return self.data.keys() except: raise ExtensionPointError("Not initialized, run init() first") class PluginExtensionPoint(ExtensionPoint): """This is an `ExtensionPoint` prepared to hold `Plugin`s.""" def __init__(self, registry): self._registry = weakref.ref(registry) ExtensionPoint.__init__(self) @copy_docs(ExtensionPoint) def _activate(self, plugin): # in this case we want to hold the actual instance and not the plugin return plugin.get_instance(self._registry()) class FactoryDict(object): """ A factory dict is a dictionary that creates objects, once, when they are first accessed from a factory supplied at runtime. The factory accepts one argument, the suplied key, and generates an object to be held on the dictionary. """ def __init__(self, factory): """ Creates a `FactoryDict` instance with the appropriate factory function. @param factory: the function that creates objects according to the supplied key. """ self.data = {} self.factory = factory @copy_docs(dict) def __getitem__(self, key): try: return self.data[key] except KeyError: val = self.data[key] = self.factory(key) return val @copy_docs(dict) def __delitem__(self, key): try: del self.data[key] except KeyError: pass @copy_docs(dict) def __repr__(self): return repr(self.data) ############################################################################## ## Use case of the classes defined above class SingletonError(StandardError): """Raised when you there's a problem related to Singletons.""" class PluginEntry(object): def __init__(self, plugin, features, singletons, extension_points, extends): self.plugin = plugin self.features = list(features) self.singletons = list(singletons) self.extends = dict(extends) self.extension_points = list(extension_points) def get_instance(self, *args, **kwargs): return self.plugin.get_instance(*args, **kwargs) class PluginFactoryCreator(object): """ This is a factory of plugin factories. Instances of this class are the factories needed on `Registry.register`, where the only thing you change is the actual `Plugin` factory. This class is needed when you need to specify a class that extends from `Plugin`. @param singletons: the singletons where the plugin will be registred @param features: the features where the plugin will be registred @param extends: the extension points the plugin will be registred @param extension_points: the extension points this plugins defines """ def __init__(self, plugin_factory): self.plugin_factory = plugin_factory def __call__(self, **kwargs): singletons = kwargs.pop("singletons", ()) features = kwargs.pop("features", ()) extends = kwargs.pop("extends", ()) extension_points = kwargs.pop("extension_points", ()) if len(singletons) == len(features) == 0: raise TypeError("You must specify at least one feature or one singleton key") plugin = self.plugin_factory(**kwargs) return plugin, features, singletons, extension_points, extends # This is the default factory that uses the class Plugin PluginFactory = PluginFactoryCreator(Plugin) class Registry(object): def __init__(self, plugin_factory=PluginFactory): self.singletons = {} self.plugins = {} self.plugin_factory = plugin_factory plugin_factory = lambda x: PluginExtensionPoint(self) self.ext_points = FactoryDict(plugin_factory) self.features = DynamicNamedSets() def register(self, plugin, features, singletons, extension_points, extends): """ Register a plugin with in features, singletons and extension points. This method should not be handled directly, use 'register_plugin' instead. @param features: the features this plugin is associated with. @param singletons: a list of singletons this plugin is registred to. @param extension_points: a list of a tuple of two elements: the name of the extension point and the extension points defined on that extension point. @param extends: a list of a tuple of two elements: the name of an extension point and the extension it should be registred. """ # Check for singletons conflicts # In this case we do not allow overriding an existing Singleton for key in singletons: try: val = self.singletons[key] raise SingletonError(key) except KeyError: pass for key in singletons: self.singletons[key] = plugin for feat in features: self.features.add(feat, plugin) # initialize all the extensions in each extension point for holder_id, points in extension_points: self.ext_points[holder_id].init_extensions(points) extension_points = [name for name, points in extension_points] for holder_id, extension_point in extends: self.ext_points[holder_id].add(extension_point, plugin) self.plugins[plugin] = PluginEntry(plugin, features, singletons, extension_points, extends) return plugin def get_plugin_from_singleton(self, singleton): """Returns the plugin associated with this singleton.""" try: return self.singletons[singleton] except KeyError: raise SingletonError(singleton) def unregister(self, plugin): """Removes a plugin from the registry.""" entry = self.plugins[plugin] for key in entry.singletons: del self.singletons[key] for feat in entry.features: self.features.remove(feat, plugin) for holder_id in entry.extension_points: del self.ext_points[holder_id] for holder_id, ext_pnt in entry.extends.iteritems(): self.ext_points[holder_id].remove(ext_pnt, plugin) del self.plugins[plugin] plugin.unplug(self) def register_plugin(self, *args, **kwargs): """Register a new plugin.""" return self.register(*self.plugin_factory(*args, **kwargs)) def get_features(self, feature, *args, **kwargs): for feature in self.features[feature]: yield feature.get_instance(self, *args, **kwargs) def get_singleton(self, singleton, *args, **kwargs): return self.get_plugin_from_singleton(singleton).get_instance(self, *args, **kwargs) def get_extension_point(self, holder_id, extension_point): return self.ext_points[holder_id].get_extension_point(extension_point) def get_extension_point_def(self, holder_id): return self.ext_points[holder_id].keys() def _check_plugin(self, plugin): entry = self.plugins[plugin] if len(entry.features) == 0 and len(entry.singletons) == 0: self.unregister(plugin) def unregister_singleton(self, singleton): try: plugin = self.singletons.pop(singleton) entry = self.plugins[plugin] entry.singletons.remove(singleton) self._check_plugin(plugin) except KeyError: raise SingletonError(singleton) def unregister_feature(self, feature, plugin): """ In order to remove a feature u must have the associated plugin. """ self.features.remove(feature, plugin) entry = self.plugins[plugin] entry.features.remove(feature) self._check_plugin(plugin) def __iter__(self): return iter(self.plugins) def clear(self): self.services = {} self.features.clear() for plugin in self.plugins: plugin.singeltons = [] plugin.features = [] plugin.unplug(self) self.plugins.clear() PIDA-0.5.1/pida/core/projects.py0000644000175000017500000002444710652670643014422 0ustar aliali"""Project features for PIDA""" import os from string import Template from weakref import proxy from pida.utils.configobj import ConfigObj from pida.utils.path import get_relative_path # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class ProjectControllerMananger(object): """ Manager to know about all controller types, and load them for projects. Controller types are registered with the manager, and provided to projects as they are loaded. This object knows about the boss, and allows the boss to be given to the projects and controllers. """ def __init__(self, boss=None): self.boss = boss self.clear_controllers() def clear_controllers(self): self._controller_types = {} def register_controller(self, controller): # XXX: fix to raise error if already exists self._controller_types[controller.name] = controller def get_controller_type(self, name): return self._controller_types.get(name, None) def create_project(self, project_file): project = Project(self, project_file) return project class Project(object): """ A PIDA project. This is essentially a bag for the options and controllers contained by the project. """ def __init__(self, manager, project_file): self.manager = manager self.boss = self.manager.boss self.project_file = project_file self.source_directory = os.path.dirname(self.project_file) self.name = os.path.basename(self.source_directory) self._create_options() self._create_controllers() def _create_controllers(self): self.controllers = [] for section in self.options.sections: controller_name = self.options.get(section).get('controller', None) if controller_name is not None: controller_type = self.manager.get_controller_type(controller_name) if controller_type is not None: self.controllers.append(controller_type(self, section)) else: self.boss.log.debug(_('no controller type for %s') % controller_name) else: self.boss.log.debug(_('no controller defined for %s') % section) def _create_options(self): self.options = ConfigObj(self.project_file) def reload(self): self._create_options() self._create_controllers() def add_controller(self, controller_type, section_name = None): # first get a free section name if section_name is None: cnum = 0 for controller in self.controllers: if controller.name == controller_type.name: cnum += 1 section_name = '%s.%s' % (controller_type.name, cnum) self.options[section_name] = {} self.options[section_name]['controller'] = controller_type.name controller = controller_type(self, section_name) if not len(self.controllers): self.options[section_name]['default'] = 'True' self.controllers.append(controller) self.save() return controller def remove_controller(self, controller): self.controllers.remove(controller) del self.options[controller.config_section] default = self.get_default_controller() if default is None: if len(self.controllers): self.controllers[0].default = True self.save() def get_default_controller(self): for controller in self.controllers: if controller.default: return controller def save(self): self.options.write() def _get_actions(self): actions = [] for controller in self.controllers: actions.extend(controller.get_actions()) return actions def _get_actions_of_kind(self, kind): actions = [] for controller in self.controllers: actions.extend([(controller, action) for action in controller.get_actions_of_kind(kind)]) return actions def set_option(self, section, name, value): self.options[section][name] = value self.options.write() def get_markup(self): return '%s\n%s' % (self.get_display_name(), self.source_directory) markup = property(get_markup) def save_section(self, section_name, section): self.options[section_name] = section self.save() def get_section(self, section_name): return self.options.get(section_name, None) def get_name(self): return self.name def get_display_name(self): return self.options.get('name', None) or self.get_name() display_name = property(get_display_name) def set_display_name(self, display_name): self.options['name'] = display_name self.options.write() def get_relative_path_for(self, filename): return get_relative_path(self.source_directory, filename) class ProjectKeyDefinition(object): """ Project attribute definition. An attribute shoulf have a name, a label and whether it is required by the project's execute action in order to perform its task. """ def __init__(self, name, label, required=False): self.name = name self.label = label self.required = required class ProjectKeyItem(object): """ Helper to allow project attributes to be displayed. Changing the value attribute will cause the project that the attribute is part of to be updated and saved. This is useful for the kiwi objectlist. """ def __init__(self, definition, project, controller): self.required = definition.required if self.required: self.label = '%s' % definition.label else: self.label = definition.label self.name = definition.name self._project = project self._controller = controller def get_value(self): return self._controller.get_option(self.name) def set_value(self, value): self._controller.set_option(self.name, value) self._project.save() value = property(get_value, set_value) class ProjectController(object): """ Project Controller. A project may have any number of controllers. Each type of controller should override the execute method, which will be called when the controller is executed. The attributes list is a list of options that can be graphically changed by the user. Each attribute should be of type ProjectKeyDefinition. The controller should also define a name (a unique key) and a label (for user interface display). """ name = '' label = '' attributes = [ ProjectKeyDefinition('cwd', _('Working Directory'), False), ProjectKeyDefinition('env', _('Environment Variables'), False), ] def __init__(self, project, config_section): self.project = proxy(project) self.boss = self.project.boss self.config_section = config_section self._default = False self._os_env = self._copy_os_env() def execute(self): """Execute this controller, for overriding""" def get_options(self): return self.project.options.get(self.config_section) def get_option(self, name): return self.get_options().get(name, None) def set_option(self, name, value): if self.get_options() is not None: self.get_options()[name] = value else: self.boss.log.debug('Deleted controller attempting to set value') def get_project_option(self, name): return self.project.options.get(name, None) def execute_commandargs(self, args, env=None, cwd=None): #TODO: Bad dependency self.boss.cmd('commander', 'execute', commandargs=[self._interpolate_command(arg) for arg in args], env=env or self.get_env(), cwd=cwd or self.get_cwd(), title=self.config_section, icon='gtk-execute', ) def execute_commandline(self, command, env=None, cwd=None): self.boss.cmd('commander', 'execute', commandargs=['bash', '-c', self._interpolate_command(command)], env=env or self.get_env(), cwd=cwd or self.get_cwd(), title=self.config_section, icon='gtk-execute', ) def _interpolate_command(self, command): current_document = self.boss.cmd('buffer', 'get_current') if current_document is not None: current_document_filename = current_document.filename current_document_directory = current_document.directory else: current_document_filename = None current_document_directory = None vars = dict( source_directory = self.project.source_directory, current_document_filename = current_document_filename, current_document_directory = current_document_directory ) return Template(command).substitute(vars) def create_key_items(self): for attr in self.attributes: yield ProjectKeyItem(attr, self.project, self) def get_markup(self): return ('%s\n%s' % (self.config_section, self.label)) markup = property(get_markup) def set_default(self, value): aval = (value and 'True') or '' self.set_option('default', aval) self.project.save() def get_default(self): if self.get_option('default') is None: self.set_option('default', '') return bool(self.get_option('default')) default = property(get_default, set_default) def get_cwd(self): cwd = self.get_option('cwd') if cwd is None: return self.project.source_directory elif os.path.isabs(cwd): return cwd else: return os.path.join(self.project.source_directory, cwd) def get_env(self): env = [] env.extend(self._os_env) opt_env = self.get_option('env') if opt_env is None: return env else: env.extend(opt_env.split()) return env def _copy_os_env(self): env = [] for k, v in os.environ.items(): env.append('%s=%s' % (k, v)) return env PIDA-0.5.1/pida/core/service.py0000644000175000017500000001717210652670643014226 0ustar aliali""" PIDA Services """ # PIDA Imports from pida.core.interfaces import IOptions, IEvents, ICommands, IActions, IFeatures from pida.core.plugins import Registry from pida.core.events import EventsConfig from pida.core.options import OptionsConfig from pida.core.actions import ActionsConfig from pida.core.commands import CommandsConfig from pida.core.features import FeaturesConfig # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class Service(object): """Base Service Class""" label = None options_config = OptionsConfig events_config = EventsConfig commands_config = CommandsConfig features_config = FeaturesConfig actions_config = ActionsConfig def __init__(self, boss=None): self.boss = boss self.log_debug('Loading Service') self.reg = Registry() def create_all(self): """ Called to create all the services by the {servicemanager.ServiceManager} """ self._register_options_config(self.options_config) self._register_events_config(self.events_config) self._register_commands_config(self.commands_config) self._register_feature_config(self.features_config) self._register_actions_config(self.actions_config) def subscribe_all(self): self._subscribe_foreign_events() self._subscribe_foreign_features() self._subscribe_keyboard_shortcuts() def get_name(self): return self.servicename @classmethod def get_name_cls(cls): return cls.servicename def get_label(self): return self.label or self.get_name().capitalize() @classmethod def get_label_cls(cls): return cls.label or cls.get_name_cls().capitalize() def pre_start(self): """Override to pre start up""" def start(self): """Override for main phase of startup""" def stop(self): """Override to stop service""" def stop_components(self): # Will remove everything self._unsubscribe_foreign_events() self._unsubscribe_foreign_features() self._unregister_actions_config() ########## # Options def _register_options_config(self, config_cls): instance = config_cls(self) self.reg.register_plugin( instance=instance, singletons=(IOptions,) ) # Public Options API def get_options(self): return self.reg.get_singleton(IOptions) def get_option(self, name): return self.get_options().get_option(name) def opt(self, name): return self.get_options().get_value(name) def set_opt(self, name, value): return self.get_options().set_value(name, value) ########## # Commands def _register_commands_config(self, config_cls): self.reg.register_plugin( instance = config_cls(self), singletons=(ICommands,) ) # Public Commands API def _get_commands(self): return self.reg.get_singleton(ICommands) def cmd(self, commandname, *args, **kw): if args: raise TypeError( _('You must call command %(cmd)s in service %(svc)s with named arguments') % {'cmd':commandname, 'svc':self.get_name()}) else: return self._get_commands().call(commandname, **kw) ########## # Events # Private Events API def _register_events_config(self, config_cls): self.reg.register_plugin( instance = config_cls(self), singletons=(IEvents,) ) def _subscribe_foreign_events(self): self._get_events().subscribe_foreign_events() def _unsubscribe_foreign_events(self): self._get_events().unsubscribe_foreign_events() # Public Events API def _get_events(self): return self.reg.get_singleton(IEvents) def get_event(self, name): return self._get_events().get(name) def subscribe_foreign_event(self, servicename, name, callback): self.boss.subscribe_event(servicename, name, callback) def unsubscribe_foreign_event(self, servicename, name, callback): self.boss.unsubscribe_event(servicename, name, callback) def subscribe_event(self, name, callback): self._get_events().subscribe_event(name, callback) def unsubscribe_event(self, name, callback): self._get_events().unsubscribe_event(name, callback) def emit(self, name, **kw): self._get_events().emit(name, **kw) ########## # Features def _register_feature_config(self, config_cls): self.reg.register_plugin( instance = config_cls(self), singletons=(IFeatures,) ) def _subscribe_foreign_features(self): self._get_features().subscribe_foreign_features() def _unsubscribe_foreign_features(self): self._get_features().unsubscribe_foreign_features() def _get_features(self): return self.reg.get_singleton(IFeatures) # Public Feature API def list_features(self): return self._get_features().list_features() def has_foreign_feature(self, servicename, featurename): return self._get_features().has_foreign_feature(servicename, featurename) def subscribe_feature(self, feature, instance): return self._get_features().subscribe_feature(feature, instance) def unsubscribe_feature(self, feature_object): self._get_features().unsubscribe_feature(feature_object) def subscribe_foreign_feature(self, servicename, feature, instance): return self.boss.subscribe_feature(servicename, feature, instance) def unsubscribe_foreign_feature(self, servicename, feature_object): self.boss.unsubscribe_feature(servicename, feature_object) def features(self, name): return self._get_features().get_feature_providers(name) ########## # Actions def _register_actions_config(self, config_cls): self.reg.register_plugin( instance = config_cls(self), singletons=(IActions,) ) def _unregister_actions_config(self): self._get_actions().remove_actions() def _subscribe_keyboard_shortcuts(self): self._get_actions().subscribe_keyboard_shortcuts() def _get_actions(self): return self.reg.get_singleton(IActions) def get_action_group(self): return self._get_actions().get_action_group() def get_action(self, name): return self._get_actions().get_action(name) def get_keyboard_options(self): return self._get_actions().get_keyboard_options() # Logging def log_debug(self, message): self.boss.log.debug('svc: %s: %s' % (self.get_name(), message)) def log_info(self, message): self.boss.log.info('svc: %s: %s' % (self.get_name(), message)) def log_warn(self, message): self.boss.log.warn('svc: %s: %s' % (self.get_name(), message)) def log_error(self, message): self.boss.log.error('svc: %s: %s' % (self.get_name(), message)) # window proxy def get_window(self): return self.boss.get_window() window = property(get_window) def save_dlg(self, *args, **kw): return self.window.save_dlg(*args, **kw) def open_dlg(self, *args, **kw): return self.window.open_dlg(*args, **kw) def info_dlg(self, *args, **kw): return self.window.info_dlg(*args, **kw) def error_dlg(self, *args, **kw): return self.window.error_dlg(*args, **kw) def yesno_dlg(self, *args, **kw): return self.window.yesno_dlg(*args, **kw) def error_list_dlg(self, msg, errs): return self.window.error_list_dlg('%s\n\n* %s' % (msg, '\n\n* '.join(errs))) PIDA-0.5.1/pida/core/servicemanager.py0000644000175000017500000002154110652670643015554 0ustar alialiimport os, imp from pida.core.interfaces import IService, IEditor, IPlugin from pida.core.plugins import Registry from pida.core.environment import library, environ # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext def sort_services_func(s1, s2): return cmp(s1.servicename, s2.servicename) class ServiceLoadingError(Exception): """An error loading a service""" class ServiceModuleError(ServiceLoadingError): """No Service class in service module""" class ServiceDependencyError(ServiceLoadingError): """Service does not have the necessary dependencies to start""" class ServiceLoader(object): def __init__(self, boss=None): self.boss = boss def get_all_services(self, service_dirs): classes = [] for service_path in self._find_all_service_paths(service_dirs): try: service_class = self.get_one_service(service_path) except ServiceLoadingError, e: self.boss.log.error('Service error: %s: %s' % (e.__class__.__name__, e)) service_class = None if service_class is not None: classes.append(service_class) classes.sort(sort_services_func) return classes def get_one_service(self, service_path): module = self._load_service_module(service_path) if module is not None: service_class = self._load_service_class(module) if service_class is not None: service_class.servicename = module.servicename service_class.servicefile_path = module.servicefile_path return service_class def load_all_services(self, service_dirs, boss): services = [] for service_class in self.get_all_services(service_dirs): services.append(self._instantiate_service(service_class, boss)) services.sort(sort_services_func) return services def load_one_service(self, service_path, boss): service_class = self.get_one_service(service_path) if service_class is not None: return self._instantiate_service(service_class, boss) def get_all_service_files(self, service_dirs): for service_path in self._find_all_service_paths(service_dirs): yield os.path.basename(service_path), self._get_servicefile_path(service_path) def _instantiate_service(self, service_class, boss): return service_class(boss) def _find_service_paths(self, service_dir): for f in os.listdir(service_dir): service_path = os.path.join(service_dir, f) if self._has_servicefile(service_path): yield service_path def _find_all_service_paths(self, service_dirs): for service_dir in service_dirs: if os.path.isdir(service_dir): for service_path in self._find_service_paths(service_dir): yield service_path def _get_servicefile_path(self, service_path, servicefile_name='service.pida'): return os.path.join(service_path, servicefile_name) def _has_servicefile(self, service_path): return os.path.exists(self._get_servicefile_path(service_path)) def _load_service_module(self, service_path): name = os.path.basename(service_path) try: fp, pathname, description = imp.find_module(name, [service_path]) except Exception, e: raise ServiceLoadingError('%s: %s' % (name, e)) try: module = imp.load_module(name, fp, pathname, description) except ImportError, e: raise ServiceDependencyError('%s: %s' % (name, e)) module.servicename = name module.servicefile_path = self._get_servicefile_path(service_path) self._register_service_env(name, service_path) return module def _load_service_class(self, module): try: service = module.Service except AttributeError, e: raise ServiceModuleError('Service has no Service class') service.servicemodule = module return service def _register_service_env(self, servicename, service_path): for name in ['glade', 'uidef', 'pixmaps', 'data']: path = os.path.join(service_path, name) if os.path.isdir(path): library.add_global_resource(name, path) class ServiceManager(object): def __init__(self, boss): self._boss = boss self._loader = ServiceLoader(self._boss) self._reg = Registry() self._plugin_objects = {} def get_service(self, name): return self._reg.get_singleton(name) def get_services(self): services = list(self._reg.get_features(IService)) services.sort(sort_services_func) return services def get_plugins(self): plugins = list(self._reg.get_features(IPlugin)) plugins.sort(sort_services_func) return plugins def get_services_not_plugins(self): services = self.get_services() plugins = self.get_plugins() return [s for s in services if s not in plugins] def activate_services(self): self._register_services() self._create_services() self._subscribe_services() self._pre_start_services() def start_plugin(self, plugin_path): plugin = self._loader.load_one_service(plugin_path, self._boss) pixmaps_dir = os.path.join(plugin_path, 'pixmaps') self._boss._icons.register_file_icons_for_directory(pixmaps_dir) if plugin is not None: self._register_plugin(plugin) plugin.create_all() plugin.subscribe_all() plugin.pre_start() plugin.start() return plugin else: self._boss.log.error('Unable to load plugin from %s' % plugin_path) def stop_plugin(self, plugin_name): plugin = self.get_service(plugin_name) if plugin is not None: # Check plugin is a plugin not a service if plugin in self.get_plugins(): plugin.log_debug('Stopping') plugin.stop_components() plugin.stop() self._reg.unregister(self._plugin_objects[plugin_name]) return plugin else: self._boss.log.error('ServiceManager: Cannot stop services') else: self._boss.log.error('ServiceManager: Cannot find plugin %s' % plugin_name) def _register_services(self): for svc in self._loader.load_all_services( self._boss.get_service_dirs(), self._boss): self._register_service(svc) def _register_service(self, service): self._reg.register_plugin( instance=service, singletons=( service.servicename, ), features=( IService, ) ) def _register_plugin(self, plugin): plugin_object = self._reg.register_plugin( instance=plugin, singletons=( plugin.servicename, ), features=( IService, IPlugin, ) ) self._plugin_objects[plugin.servicename] = plugin_object def _create_services(self): for svc in self.get_services(): svc.log_debug('Creating Service') svc.create_all() def _subscribe_services(self): for svc in self.get_services(): svc.log_debug('Subscribing Service') svc.subscribe_all() def _pre_start_services(self): for svc in self.get_services(): svc.log_debug('Pre Starting Service') svc.pre_start() def start_services(self): for svc in self.get_services(): svc.log_debug('Starting Service') svc.start() def get_available_editors(self): dirs = self._boss.get_editor_dirs() return self._loader.get_all_services(dirs) def activate_editor(self, name): self.load_editor(name) self.editor.create_all() self.editor.subscribe_all() self.editor.pre_start() def start_editor(self): self.register_editor(self.editor) self.editor.start() def load_editor(self, name): for editor in self.get_available_editors(): if editor.servicename == name: self.editor = editor(self._boss) return self.editor raise AttributeError(_('No editor found')) def register_editor(self, service): self._reg.register_plugin( instance=service, singletons=( service.servicename, IEditor, ), features=( IService, ) ) def stop(self): for svc in self.get_services(): svc.stop() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/core/signalhandler.py0000644000175000017500000000061310652670643015371 0ustar aliali import signal # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class PosixSignalHandler(object): def __init__(self, boss): self.boss = boss signal.signal(signal.SIGTERM, self.handle_SIGTERM) def handle_SIGTERM(self, signum, frame): self.boss.log.error(_('PIDA stopped by SIGTERM')) self.boss.stop(force=True) PIDA-0.5.1/pida/editors/0002755000175000017500000000000010652671501012721 5ustar alialiPIDA-0.5.1/pida/editors/emacs/0002755000175000017500000000000010652671501014011 5ustar alialiPIDA-0.5.1/pida/editors/emacs/uidef/0002755000175000017500000000000010652671501015105 5ustar alialiPIDA-0.5.1/pida/editors/emacs/uidef/emacs.xml0000644000175000017500000000422710652670644016731 0ustar aliali PIDA-0.5.1/pida/editors/emacs/__init__.py0000644000175000017500000000225410652670644016132 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """The Emacs editor for Pida.""" # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/emacs/emacs.py0000644000175000017500000002441110652670644015462 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """The Emacs editor core classes for Pida. This module and other Pida Emacs related classes are based on preliminary works by Ali Afshar (see Emacs module in Pida 0.2.2). The Emacs editor for Pida is also, heavily based on the Vim editor. """ import logging import os import gobject import gtk # PIDA Imports from pida.ui.views import PidaView from pida.core.log import build_logger from pida.core.editors import EditorService, _ # Emacs specific from pida.utils.emacs.emacsembed import EmacsEmbedWidget from pida.utils.emacs.emacscom import EmacsClient, EmacsServer, EMACS_SCRIPT class EmacsView(PidaView): def create_ui(self): self._emacs = EmacsEmbedWidget('emacs', self.svc.script_path) self.add_main_widget(self._emacs) def run(self): self._emacs.run() def grab_input_focus(self): self._emacs.grab_input_focus() class EmacsCallback(object): """Emacs editor callback behaviours. Communication with Emacs process is handled by EmacsClient in the pIDA->Emacs way, and EmacsServer the other way. On occurence of a message, EmacsServer extracts a request name and arguments, and then tries to invoke the matching method on the EmacsCallback object. Callbacks' names are built with the Emacs message names, prefixed with 'cb_'. Each callback accepts exactly one argument. """ def __init__(self, svc): """Constructor.""" self._log = logging.getLogger('emacs') self._svc = svc self._server = EmacsServer(self) def connect(self): """Establish the link with Emacs.""" return self._server.connect() def cb_pida_pong(self, foo): """Emacs response to a ping. This message is used to test connection at startup. """ self._log.debug('emacs ready') self._svc.emit_editor_started() return True def cb_window_configuration_change_hook(self, filename): """Buffer changed event. Actually, this hook is called whenever the window containing the buffer changes. So notification can occur only when window is resized or split for example. """ self._svc.top_buffer = filename current = self._svc.current_document if filename and (not current or current.filename != filename): self._log.debug('emacs buffer changed "%s"' % filename) if os.path.isdir(filename): self._svc.boss.cmd('filemanager', 'browse', new_path=filename) self._svc.boss.cmd('filemanager', 'present_view') else: self._svc.boss.cmd('buffer', 'open_file', file_name=filename) return True def cb_kill_buffer_hook(self, filename): """Buffer closed event.""" if filename: self._log.debug('emacs buffer killed "%s"' % filename) self._svc.remove_file(filename) self._svc.boss.get_service('buffer').cmd('close_file', file_name=filename) return True def cb_find_file_hooks(self, filename): """File opened event.""" # Nothing to do here. The window configuration change hook will # provide notification for the new buffer. if filename: self._log.debug('emacs buffer opened "%s"' % filename) return True def cb_after_save_hook(self, filename): """Buffer saved event.""" self._log.debug('emacs buffer saved "%s"' % filename) self._svc.boss.cmd('buffer', 'current_file_saved') return True def cb_kill_emacs_hook(self, foo): """Emacs killed event.""" self._log.debug('emacs killed') self._svc.inactivate_client() self._svc.boss.stop(force=True) return False # Service class class Emacs(EditorService): """The Emacs service. This service is the Emacs editor driver. Emacs instance creation is decided there and orders for Emacs are sent to it which forwards them to the EmacsClient instance. """ def _create_initscript(self): self.script_path = os.path.join( self.boss.get_pida_home(), 'pida_emacs_init.el') f = open(self.script_path, 'w') f.write(EMACS_SCRIPT) f.close() def emit_editor_started(self): self.boss.get_service('editor').emit('started') def pre_start(self): """Start the editor""" self._log = build_logger('emacs') self._create_initscript() self._documents = {} # The current document. Its value is set by Pida and used to drop # useless messages to emacs. self._current = None # The current buffer displayed. Its value is set by the EmacsCallback # instance and is used as well to prevent sending useless messages. self._top_buffer = '' self._current_line = 1 self._cb = EmacsCallback(self) self._client = EmacsClient() self._view = EmacsView(self) # Add the view to the top level window. Only after that, it will be # possible to add a socket in the view. self.boss.cmd('window', 'add_view', paned='Editor', view=self._view) # Now create the socket and embed the Emacs window. self._view.run() if self._cb.connect(): gobject.timeout_add(250, self._client.ping) def stop(self): self._client.quit() def _get_current_document(self): return self._current def _set_current_document(self, document): self._current = document current_document = property(fget=_get_current_document, fset=_set_current_document, fdel=None, doc="The document currently edited") def _get_top_buffer(self): return self._top_buffer def _set_top_buffer(self, filename): self._top_buffer = filename top_buffer = property(fget=_get_top_buffer, fset=_set_top_buffer, fdel=None, doc="The last buffer reported as being viewed by emacs") def inactivate_client(self): self._client.inactivate() def open(self, document): """Open a document""" if document is not self._current: if self.top_buffer != document.filename: if document.unique_id in self._documents: self._client.change_buffer(document.filename) else: self._client.open_file(document.filename) self._documents[document.unique_id] = document self.current_document = document def open_many(documents): """Open a few documents""" pass def close(self, document): if document.unique_id in self._documents: self._remove_document(document) self._client.close_buffer(document.filename) def remove_file(self, filename): document = self._get_document_for_filename(filename) if document is not None: self._remove_document(document) def _remove_document(self, document): del self._documents[document.unique_id] def _get_document_for_filename(self, filename): for uid, doc in self._documents.iteritems(): if doc.filename == filename: return doc def close_all(self): """Close all the documents""" def save(self): """Save the current document""" self._client.save_buffer() def save_as(self, filename): """Save the current document as another filename""" pass # TODO def revert(self): """Revert to the loaded version of the file""" self._client.revert_buffer() def goto_line(self, line): """Goto a line""" self._client.goto_line(line + 1) self.grab_focus() def cut(self): """Cut to the clipboard""" self._client.cut() def copy(self): """Copy to the clipboard""" self._client.copy() def paste(self): """Paste from the clipboard""" self._client.paste() def undo(self): self._client.undo() def redo(self): self._client.redo() def grab_focus(self): """Grab the focus""" self._view.grab_input_focus() def define_sign_type(self, name, icon, linehl, text, texthl): # TODO pass def undefine_sign_type(self, name): # TODO pass #def _add_sign(self, type, filename, line): #def _del_sign(self, type, filename, line): def show_sign(self, type, filename, line): # TODO pass def hide_sign(self, type, filename, line): # TODO pass def set_current_line(self, line_number): self._current_line = line_number def get_current_line(self): return self._current_line #def call_with_current_word(self, callback): # return self._com.get_cword(self.server, callback) #def call_with_selection(self, callback): # return self._com.get_selection(self.server, callback) def set_path(self, path): return self._client.set_directory(path) # Required Service attribute for service loading Service = Emacs # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/emacs/service.pida0000644000175000017500000000000010652670644016303 0ustar alialiPIDA-0.5.1/pida/editors/emacs/test_emacs.py0000644000175000017500000000222010652670644016513 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/vim/0002755000175000017500000000000010652671501013514 5ustar alialiPIDA-0.5.1/pida/editors/vim/uidef/0002755000175000017500000000000010652671501014610 5ustar alialiPIDA-0.5.1/pida/editors/vim/uidef/vim.xml0000644000175000017500000000422710652670645016140 0ustar aliali PIDA-0.5.1/pida/editors/vim/__init__.py0000644000175000017500000000222010652670645015627 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/vim/service.pida0000644000175000017500000000000010652670645016007 0ustar alialiPIDA-0.5.1/pida/editors/vim/test_vim.py0000644000175000017500000000222010652670645015722 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/vim/vim.py0000644000175000017500000002113510652670645014671 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os # PIDA Imports from pida.ui.views import PidaView from pida.utils.vim.vimembed import VimEmbedWidget from pida.utils.vim.vimcom import VimCom, VIMSCRIPT from pida.core.editors import EditorService, _ class VimView(PidaView): def create_ui(self): self._vim = VimEmbedWidget('gvim', self.svc.script_path) self.add_main_widget(self._vim) def run(self): return self._vim.run() def get_server_name(self): return self._vim.get_server_name() def grab_input_focus(self): self._vim.grab_input_focus() class VimCallback(object): def __init__(self, svc): self.svc = svc def vim_new_serverlist(self, servers): if self.svc.server in servers: self.svc.init_vim_server() def vim_bufferchange(self, server, cwd, file_name, bufnum): if server == self.svc.server: if file_name: if os.path.abspath(file_name) != file_name: file_name = os.path.join(cwd, file_name) if os.path.isdir(file_name): self.svc.boss.cmd('filemanager', 'browse', new_path=file_name) self.svc.boss.cmd('filemanager', 'present_view') self.svc.open_last() else: self.svc.boss.cmd('buffer', 'open_file', file_name=file_name) def vim_bufferunload(self, server, file_name): if server == self.svc.server: if file_name: self.svc.remove_file(file_name) self.svc.boss.get_service('buffer').cmd('close_file', file_name=file_name) def vim_filesave(self, server, file_name): if server == self.svc.server: self.svc.boss.cmd('buffer', 'current_file_saved') def vim_cursor_move(self, server, line_number): if server == self.svc.server: self.svc.set_current_line(int(line_number)) def vim_shutdown(self, server, args): if server == self.svc.server: self.svc.boss.stop(force=True) def vim_complete(self, server, findstart, base, line, start): # do this a few times #self.svc._com.add_completion(server, 'banana') pass # Service class class Vim(EditorService): """Describe your Service Here""" ##### Vim Things def _create_initscript(self): self.script_path = os.path.join(self.boss.get_pida_home(), 'pida_vim_init.vim') f = open(self.script_path, 'w') f.write(VIMSCRIPT) f.close() def init_vim_server(self): if self.started == False: self._com.stop_fetching_serverlist() self.started = True self._emit_editor_started() def _emit_editor_started(self): self.boss.get_service('editor').emit('started') def get_server_name(self): return self._view.get_server_name() server = property(get_server_name) def pre_start(self): """Start the editor""" self.started = False self._create_initscript() self._cb = VimCallback(self) self._com = VimCom(self._cb) self._view = VimView(self) self.boss.cmd('window', 'add_view', paned='Editor', view=self._view) self._documents = {} self._current = None self._sign_index = 0 self._signs = {} self._current_line = 1 success = self._view.run() if not success: err = _('There was a problem running the "gvim" ' 'executable. This is usually because it is not ' 'installed. Please check that you can run "gvim" ' 'from the command line.') self.error_dlg(err) raise RuntimeError(err) def open(self, document): """Open a document""" if document is not self._current: if document.unique_id in self._documents: fn = document.filename self._com.change_buffer(self.server, fn) self._com.foreground(self.server) else: self._com.open_file(self.server, document.filename) self._documents[document.unique_id] = document self._current = document def open_many(documents): """Open a few documents""" def open_last(self): self._com.change_buffer(self.server, '#') def close(self, document): if document.unique_id in self._documents: self._remove_document(document) self._com.close_buffer(self.server, document.filename) def remove_file(self, file_name): document = self._get_document_for_filename(file_name) if document is not None: self._remove_document(document) def _remove_document(self, document): del self._documents[document.unique_id] def _get_document_for_filename(self, file_name): for uid, doc in self._documents.iteritems(): if doc.filename == file_name: return doc def close_all(): """Close all the documents""" def save(self): """Save the current document""" self._com.save(self.server) def save_as(filename): """Save the current document as another filename""" def revert(): """Revert to the loaded version of the file""" def goto_line(self, line): """Goto a line""" self._com.goto_line(self.server, line) self.grab_focus() def cut(self): """Cut to the clipboard""" self._com.cut(self.server) def copy(self): """Copy to the clipboard""" self._com.copy(self.server) def paste(self): """Paste from the clipboard""" self._com.paste(self.server) def undo(self): self._com.undo(self.server) def redo(self): self._com.redo(self.server) def grab_focus(self): """Grab the focus""" self._view.grab_input_focus() def define_sign_type(self, name, icon, linehl, text, texthl): self._com.define_sign(self.server, name, icon, linehl, text, texthl) def undefine_sign_type(self, name): self._com.undefine_sign(self.server, name) def _add_sign(self, type, filename, line): self._sign_index += 1 self._signs[(filename, line, type)] = self._sign_index return self._sign_index def _del_sign(self, type, filename, line): return self._signs.pop((filename, line, type)) def show_sign(self, type, filename, line): index = self._add_sign(type, filename, line) self._com.show_sign(self.server, index, type, filename, line) def hide_sign(self, type, filename, line): try: index = self._del_sign(type, filename, line) self._com.hide_sign(self.server, index, filename) except KeyError: self.window.error_dlg(_('Tried to remove non-existent sign')) def set_current_line(self, line_number): self._current_line = line_number def get_current_line(self): return self._current_line def delete_current_word(self): self._com.delete_cword(self.server) def insert_text(self, text): self._com.insert_text(self.server, text) def call_with_current_word(self, callback): return self._com.get_cword(self.server, callback) def call_with_selection(self, callback): return self._com.get_selection(self.server, callback) def set_path(self, path): return self._com.set_path(self.server, path) # Required Service attribute for service loading Service = Vim # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/editors/__init__.py0000644000175000017500000000000010652670645015026 0ustar alialiPIDA-0.5.1/pida/resources/0002755000175000017500000000000010652671501013262 5ustar alialiPIDA-0.5.1/pida/resources/glade/0002755000175000017500000000000010652671501014336 5ustar alialiPIDA-0.5.1/pida/resources/glade/base_view.glade0000644000175000017500000000146110652670646017311 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK PIDA-0.5.1/pida/resources/glade/blank_view.glade0000644000175000017500000000137210652670646017467 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK PIDA-0.5.1/pida/resources/glade/main_window.glade0000644000175000017500000000355410652670646017665 0ustar aliali 800 600 True True False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True False 2 PIDA-0.5.1/pida/resources/glade/main_window_old.glade0000644000175000017500000001045410652670646020520 0ustar aliali 800 600 True True False True 220 True True 450 True True True True False False True True False True True 450 True True True False False True True 1 True False 2 PIDA-0.5.1/pida/resources/glade/main_window_old_old.glade0000644000175000017500000000604610652670646021360 0ustar aliali 800 600 True True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True False 2 PIDA-0.5.1/pida/resources/glade/test_view.glade0000644000175000017500000000536510652670646017365 0ustar aliali test_act test action 250 440 True True True button1 True 1 2 I AM A TEST True 0.0 1 2 PIDA-0.5.1/pida/resources/locale/0002755000175000017500000000000010652671501014521 5ustar alialiPIDA-0.5.1/pida/resources/locale/fr_FR/0002755000175000017500000000000010652671501015517 5ustar alialiPIDA-0.5.1/pida/resources/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501017304 5ustar alialiPIDA-0.5.1/pida/resources/locale/fr_FR/LC_MESSAGES/pida.po0000644000175000017500000001646610652670645020604 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 11:59+0200\n" "PO-Revision-Date: 2007-05-02 11:59+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../pida/core/projects.py:65 #, python-format msgid "no controller type for %s" msgstr "aucun type de controleur pour %s" #: ../pida/core/projects.py:68 #, python-format msgid "no controller defined for %s" msgstr "aucun controleur défini pour %s" #: ../pida/core/projects.py:193 msgid "Working Directory" msgstr "Répertoire de travail" #: ../pida/core/projects.py:194 msgid "Environment Variables" msgstr "Variables d'environement" #: ../pida/core/servicemanager.py:153 msgid "No editor found" msgstr "Aucun éditeur trouvé" #: ../pida/core/service.py:110 #, python-format msgid "You must call command %(cmd)s in service %(svc)s with named arguments" msgstr "Vous devez appeller la commande %(cmd)s dans le service %(svc)s with named arguments" #: ../pida/core/application.py:37 msgid "Exiting. (this is fatal)" msgstr "Quitte. (c'est une erreur fatale)" #: ../pida/core/application.py:46 #, python-format msgid "PIDA requires PyGTK >= 2.8. It only found %(major)s.%(minor)s" msgstr "PIDA requière PyGTK >= 2.8. Vous n'avez que la version %(major)s.%(minor)s" #: ../pida/core/application.py:49 msgid "PIDA requires Python GTK bindings. They were not found." msgstr "PIDA requière les bindings Python GTK. Ils n'ont pas été trouvés." #: ../pida/core/application.py:56 msgid "Fatal error, cannot start PIDA" msgstr "Erreur fatale, impossible de démarrer PIDA" #: ../pida/core/application.py:61 msgid "Kiwi needs to be installed to run PIDA" msgstr "Kiwi est requis pour démarrer PIDA" #: ../pida/core/application.py:66 #, python-format msgid "Python 2.4 is required to run PIDA. Only %(major)s.%(minor)s was found." msgstr "Python 2.4 est requis pour démarrer PIDA. Seul la version %(major)s.%(minor)s a été trouvée." #: ../pida/core/application.py:76 msgid "The pida package could not be found." msgstr "Le paquet PIDA n'a pas été trouvé." #: ../pida/core/application.py:80 #, python-format msgid "PIDA, version %s" msgstr "PIDA, version %s" #: ../pida/core/environment.py:44 msgid "Print version information and exit." msgstr "Affiche les informations et quitte" #: ../pida/core/environment.py:46 msgid "Run PIDA with added debug information." msgstr "Démarre PIDA avec des informations de debuggage" #: ../pida/core/boss.py:35 msgid "Are you sure you want to quit PIDA ?" msgstr "Voulez-vous quitter PIDA ?" #: ../pida/core/signalhandler.py:16 msgid "PIDA stopped by SIGTERM" msgstr "PIDA a été stoppé par le signal SIGTERM" #: ../pida/core/document.py:65 msgid "Target does not exist: " msgstr "La destination n'existe pas: " #: ../pida/core/document.py:68 msgid "Base is not a directory or does not exist: " msgstr "Base est un répertoire ou n'existe pas: " #: ../pida/core/document.py:76 #, python-format msgid "" "Target is on a different drive to base. Target: %(target)s, base: %(base)s" msgstr "Target est sur un chemin différent de base. Target: %(target)s, base: %(base)s" #: ../pida/core/document.py:191 #, python-format msgid "failed to open file %s" msgstr "erreur à l'ouverture du fichier %s" #: ../pida/editors/vim/vim.py:52 msgid "Undo" msgstr "Annuler" #: ../pida/editors/vim/vim.py:53 msgid "Undo the last editor action" msgstr "Annule la dernière action de l'éditeur" #: ../pida/editors/vim/vim.py:61 msgid "Redo" msgstr "Refaire" #: ../pida/editors/vim/vim.py:62 msgid "Redo the last editor action" msgstr "Refait la dernière action de l'éditeur" #: ../pida/editors/vim/vim.py:70 msgid "Cut" msgstr "Couper" #: ../pida/editors/vim/vim.py:71 msgid "Cut the selection in the editor" msgstr "Couper la sélection de l'éditeur" #: ../pida/editors/vim/vim.py:79 msgid "Copy" msgstr "Copier" #: ../pida/editors/vim/vim.py:80 msgid "Copy the selection in the editor" msgstr "Copier la sélection de l'éditeur" #: ../pida/editors/vim/vim.py:88 msgid "Paste" msgstr "Coller" #: ../pida/editors/vim/vim.py:89 msgid "Paste the clipboard in the editor" msgstr "Coller le presse-papier dans l'éditeur" #: ../pida/editors/vim/vim.py:97 ../pida/ui/terminal.py:285 msgid "Save" msgstr "Sauvegarder" #: ../pida/editors/vim/vim.py:98 msgid "Save the current document" msgstr "Sauvegarder le document courant" #: ../pida/editors/vim/vim.py:352 msgid "Tried to remove non-existent sign" msgstr "Tentative de suppression d'un signe qui n'existe pas" #: ../pida/ui/uimanager.py:13 msgid "File" msgstr "Fichier" #: ../pida/ui/uimanager.py:13 msgid "File Menu" msgstr "Menu Fichier" #: ../pida/ui/uimanager.py:14 msgid "Edit" msgstr "Edition" #: ../pida/ui/uimanager.py:14 msgid "Edit Menu" msgstr "Menu d'édition" #: ../pida/ui/uimanager.py:15 msgid "Project" msgstr "Projet" #: ../pida/ui/uimanager.py:15 msgid "Project Menu" msgstr "Menu du projet" #: ../pida/ui/uimanager.py:16 msgid "Language" msgstr "Langage" #: ../pida/ui/uimanager.py:16 msgid "Language Menu" msgstr "Menu langage" #: ../pida/ui/uimanager.py:17 msgid "Debug" msgstr "Debug" #: ../pida/ui/uimanager.py:17 msgid "Debug Menu" msgstr "Menu debug" #: ../pida/ui/uimanager.py:18 msgid "Tools" msgstr "Outils" #: ../pida/ui/uimanager.py:18 msgid "Tools Menu" msgstr "Menu des outels" #: ../pida/ui/uimanager.py:19 msgid "Debug Pida" msgstr "Debugger Pida" #: ../pida/ui/uimanager.py:19 msgid "Debug Pida Menu" msgstr "Menu du debug Pida" #: ../pida/ui/uimanager.py:20 msgid "View" msgstr "Fenêtres" #: ../pida/ui/uimanager.py:20 msgid "View Menu" msgstr "Menu des fenêtres" #: ../pida/ui/uimanager.py:21 msgid "Help" msgstr "Aide" #: ../pida/ui/uimanager.py:21 msgid "Help Menu" msgstr "Menu d'aide" #: ../pida/ui/splash.py:40 msgid "PIDA is starting..." msgstr "PIDA démarre..." #: ../pida/ui/splash.py:45 msgid "and it loves you!" msgstr "et il vous aime!" #: ../pida/ui/terminal.py:180 #, python-format msgid "No match named \"%s\" was found" msgstr "Aucune recherche nommé \"%s\" n'a été trouvée" #: ../pida/ui/terminal.py:284 msgid "Open" msgstr "Ouvrir" #: ../pida/ui/terminal.py:284 msgid "Open this file" msgstr "Ouvrir ce fichier" #: ../pida/ui/terminal.py:285 msgid "Save This File" msgstr "Sauvegarder" #: ../pida/ui/views.py:23 msgid "Untitled Pida View" msgstr "Nouvelle vue Pida" #: ../pida/ui/views.py:85 msgid "Pida View" msgstr "Vue Pida" #: ../pida/ui/books.py:29 msgid "Must at least define a notebook name" msgstr "Il faut définir au moins un nom à l'onglet" #: ../pida/ui/books.py:32 msgid "Must at leaste define a Name" msgstr "Il faut définir au moins un Nom" #: ../pida/ui/books.py:135 #, python-format msgid "No Notebook attests to having that name %s" msgstr "Aucun onglet n'a de nom %s" #: ../pida/ui/books.py:181 msgid "This view is already in the manager" msgstr "Cette vue est déja affichée" #: ../pida/ui/books.py:226 msgid "View is not in any Notebook" msgstr "La vue n'est dans aucun onglet" #: ../pida/ui/objectlist.py:57 msgid "Sort" msgstr "Trier" #: ../pida/ui/widgets.py:121 msgid "Select File" msgstr "Sélectionnez un fichier" #: ../pida/ui/window.py:62 msgid "PIDA Loves You!" msgstr "PIDA vous aime !" PIDA-0.5.1/pida/resources/pixmaps/0002755000175000017500000000000010652671501014743 5ustar alialiPIDA-0.5.1/pida/resources/pixmaps/pida-icon.png0000755000175000017500000000344110652670646017330 0ustar alialiPNG  IHDR00WbKGD pHYs  tIME : IDATho]W}__?8q4ijH@BBtP!2EbU1C !P_@[PMIۤv8ǎs}{G<ءdIG{kܖInz\:lyl1u%5(bC\v."L{ׂ>B\@m=pbx!1AcL 8ތlϱqygx ;1>}uWfC4OZW 7!;HqvC'$'Zgp܀w=ɮGxe~;(DZW hYBR$e- rf7'y sF 1U2;-9o]ϷlvQvԸ [*lʓ[S&iRhPB,pO`!Y+[C}=ʁ#[uYn+Hc]baÛ>Ɇi] trlyNpCk:zkq^^V, s`nak?F́B-PjQ(Bh]fp^ѺD_on܋`l2GdV%ZS:;)<&o$E/oVJ" 3BJi%482^V5_OFでkV|ƃ.DZLs+ ocd^%>hհ\q:> 'M,=fq[5Y [{?_?OLJ+܍/0@V=*->LR_MU:Cxtx`sThwGk=Uׯv}2! ܮwIseI(6(7hOC1DgP2=fO@O\6DDtRJ.a(ziZNHݥ\,G)N | vI1zr9S()X"dGd TAgPv\.[픴A)!b |7.5>⥌$!j+\$a^ ,c噈t.O@H,Lg/a1#7DHXnb0 z?4G6NP1p}2I7d8\4ll/4"ƞH5] ї'+x*?Lx*:Mf|ȶzeSb9U/^aTH >\ HeEU_/|ÌngaNPagꤳ);Θ/Ѯ꧷cu?㗘] ;jBf^ʏRz |1c16}Jzl송YR&3M'¯ + RzWSNۘ_8 칌frE]y2is9?BK#z%p?W8\,orϧyiN7b'w?+' Z2T*]ZDNGJ[t[nmY3?D9ZIENDB`PIDA-0.5.1/pida/resources/pixmaps/pida-icon.svg0000644000175000017500000002465310652670646017350 0ustar aliali image/svg+xml PIDA-0.5.1/pida/resources/pixmaps/view_close.gif0000644000175000017500000000011110652670646017571 0ustar alialiGIF89a?_?!,h6+ IT6.];PIDA-0.5.1/pida/resources/pixmaps/view_detach.gif0000644000175000017500000000010510652670646017717 0ustar alialiGIF89aov!,osk:!h]ĶO;PIDA-0.5.1/pida/resources/uidef/0002755000175000017500000000000010652671501014356 5ustar alialiPIDA-0.5.1/pida/resources/uidef/base.xml0000644000175000017500000000212010652670646016014 0ustar aliali PIDA-0.5.1/pida/resources/uidef/debugger_stackview_toolbar.xml0000644000175000017500000000100010652670646022464 0ustar aliali PIDA-0.5.1/pida/services/0002755000175000017500000000000010652671501013073 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/0002755000175000017500000000000010652671501015757 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/locale/0002755000175000017500000000000010652671501017216 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/locale/fr_FR/0002755000175000017500000000000010652671501020214 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501022001 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/locale/fr_FR/LC_MESSAGES/appcontroller.po0000644000175000017500000000113010652670611025217 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../appcontroller.py:46 msgid "Quit PIDA" msgstr "Quitter PIDA" #: ../../appcontroller.py:47 msgid "Exit the application" msgstr "Quitter l'application" PIDA-0.5.1/pida/services/appcontroller/uidef/0002755000175000017500000000000010652671501017053 5ustar alialiPIDA-0.5.1/pida/services/appcontroller/uidef/appcontroller.xml0000644000175000017500000000275210652670612022467 0ustar aliali PIDA-0.5.1/pida/services/appcontroller/__init__.py0000644000175000017500000000222010652670612020064 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/appcontroller/appcontroller.py0000644000175000017500000000421110652670612021213 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE # locale from pida.core.locale import Locale locale = Locale('appcontroller') _ = locale.gettext class AppcontrollerActions(ActionsConfig): def create_actions(self): self.create_action( 'quit_pida', TYPE_NORMAL, _('Quit PIDA'), _('Exit the application'), gtk.STOCK_QUIT, self.on_quit_pida, 'q' ) def on_quit_pida(self, action): self.svc.boss.stop() # Service class class Appcontroller(Service): """Describe your Service Here""" actions_config = AppcontrollerActions # Required Service attribute for service loading Service = Appcontroller # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/appcontroller/service.pida0000644000175000017500000000000010652670612020244 0ustar alialiPIDA-0.5.1/pida/services/appcontroller/test_appcontroller.py0000644000175000017500000000222010652670612022250 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/buffer/0002755000175000017500000000000010652671501014344 5ustar alialiPIDA-0.5.1/pida/services/buffer/glade/0002755000175000017500000000000010652671501015420 5ustar alialiPIDA-0.5.1/pida/services/buffer/glade/buffer_list.glade0000644000175000017500000000261210652670636020731 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN PIDA-0.5.1/pida/services/buffer/locale/0002755000175000017500000000000010652671501015603 5ustar alialiPIDA-0.5.1/pida/services/buffer/locale/fr_FR/0002755000175000017500000000000010652671501016601 5ustar alialiPIDA-0.5.1/pida/services/buffer/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020366 5ustar alialiPIDA-0.5.1/pida/services/buffer/locale/fr_FR/LC_MESSAGES/buffer.po0000644000175000017500000000306310652670635022206 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: buffer.py:60 msgid "Buffers" msgstr "Buffers" #: buffer.py:67 msgid "Time Opened" msgstr "Temps d'ouverture" #: buffer.py:68 msgid "File path" msgstr "Chemin du fichier" #: buffer.py:69 msgid "File name" msgstr "Nom du fichier" #: buffer.py:70 msgid "Mime Type" msgstr "Type mime" #: buffer.py:71 msgid "File Length" msgstr "Taille du fichier" #: buffer.py:72 msgid "Last Modified" msgstr "Dernière modification" #: buffer.py:108 buffer.py:118 msgid "Open File" msgstr "Ouvrir un fichier" #: buffer.py:109 msgid "Open a file with a graphical file browser" msgstr "Ouvrir un fichier avec l'explorateur de fichier" #: buffer.py:119 msgid "Open this file" msgstr "Ouvrir ce fichier" #: buffer.py:128 msgid "New File" msgstr "Nouveau fichier" #: buffer.py:129 msgid "Create a temporary new file" msgstr "Créer un fichier temporaire" #: buffer.py:138 msgid "Create File" msgstr "Créer un fichier" #: buffer.py:139 msgid "Create a new file" msgstr "Créer un nouveau fichier" #: buffer.py:148 msgid "Close Document" msgstr "Fermer le document" #: buffer.py:149 msgid "Close the current document" msgstr "Fermer le document courant" PIDA-0.5.1/pida/services/buffer/uidef/0002755000175000017500000000000010652671501015440 5ustar alialiPIDA-0.5.1/pida/services/buffer/uidef/buffer-file-menu.xml0000644000175000017500000000036610652670636021325 0ustar aliali PIDA-0.5.1/pida/services/buffer/uidef/buffer.xml0000644000175000017500000000436110652670636017445 0ustar aliali PIDA-0.5.1/pida/services/buffer/__init__.py0000644000175000017500000000222010652670636016457 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/buffer/buffer.py0000644000175000017500000002414110652670636016177 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os from tempfile import mkstemp import gtk from kiwi.ui.objectlist import Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.ui.objectlist import AttrSortCombo from pida.core.document import Document # locale from pida.core.locale import Locale locale = Locale('buffer') _ = locale.gettext LIST_COLUMNS = [ Column('markup', use_markup=True), Column("basename", visible=False, searchable=True), ] class BufferListView(PidaGladeView): gladefile = 'buffer_list' locale = locale icon_name = 'package_office' label_text = _('Buffers') def create_ui(self): self.buffers_ol.set_columns(LIST_COLUMNS) self.buffers_ol.set_headers_visible(False) self._sort_combo = AttrSortCombo(self.buffers_ol, [ ('creation_time', _('Time Opened')), ('filename', _('File path')), ('basename', _('File name')), ('mimetype', _('Mime Type')), ('length', _('File Length')), ('modified_time', _('Last Modified')), #('Project', _('Project_name')) ], 'creation_time' ) self._sort_combo.show() self.main_vbox.pack_start(self._sort_combo, expand=False) def add_document(self, document): self.buffers_ol.append(document) def remove_document(self, document): self.buffers_ol.remove(document) def set_document(self, document): if self.buffers_ol.get_selected() is not document: self.buffers_ol.select(document) def on_buffers_ol__selection_changed(self, ol, item): self.svc.view_document(item) def on_buffers_ol__double_click(self, ol, item): self.svc.boss.editor.cmd('grab_focus') def on_buffers_ol__right_click(self, ol, item, event=None): self.svc.boss.cmd('contexts', 'popup_menu', context='file-menu', event=event, file_name=self.svc.get_current().filename) def get_current_buffer_index(self): return self.buffers_ol.index(self.buffers_ol.get_selected()) def select_buffer_by_index(self, index): self.buffers_ol.select(self.buffers_ol[index]) def next_buffer(self): index = self.get_current_buffer_index() newindex = index + 1 if newindex == len(self.buffers_ol): newindex = 0 self.select_buffer_by_index(newindex) def prev_buffer(self): index = self.get_current_buffer_index() newindex = index - 1 if newindex == -1: newindex = len(self.buffers_ol) - 1 self.select_buffer_by_index(newindex) class BufferActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'open_file', TYPE_NORMAL, _('Open File'), _('Open a file with a graphical file browser'), gtk.STOCK_OPEN, self.on_open_file, 'O', ) self.create_action( 'open-for-file', TYPE_NORMAL, _('Open File'), _('Open this file'), gtk.STOCK_OPEN, self.on_open_for_file, 'NOACCEL', ) self.create_action( 'new_file', TYPE_NORMAL, _('New File'), _('Create a temporary new file'), gtk.STOCK_NEW, self.on_new_file, 'N', ) self.create_action( 'create_file', TYPE_NORMAL, _('Create File'), _('Create a new file'), gtk.STOCK_ADD, self.on_add_file, 'A', ) self.create_action( 'close', TYPE_NORMAL, _('Close Document'), _('Close the current document'), gtk.STOCK_CLOSE, self.on_close, 'W', ) self.create_action( 'switch_next_buffer', TYPE_NORMAL, _('Next Buffer'), _('Switch to the next buffer'), gtk.STOCK_GO_DOWN, self.on_next_buffer, 'Down', ) self.create_action( 'switch_prev_buffer', TYPE_NORMAL, _('Previous Buffer'), _('Switch to the previous buffer'), gtk.STOCK_GO_UP, self.on_prev_buffer, 'Up', ) def on_open_file(self, action): file_name = self.svc.boss.window.open_dlg() if file_name: self.svc.open_file(file_name) def on_new_file(self, action): fd, file_name = mkstemp() os.close(fd) self.svc.open_file(file_name) def on_add_file(self, action): file_name = self.svc.boss.window.save_dlg() if file_name: f = open(file_name, 'w') f.close() self.svc.open_file(file_name) def on_close(self, action): self.svc.close_current() def on_open_for_file(self, action): file_name = action.contexts_kw['file_name'] self.svc.open_file(file_name) def on_next_buffer(self, action): self.svc.get_view().next_buffer() def on_prev_buffer(self, action): self.svc.get_view().prev_buffer() class BufferFeaturesConfig(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'buffer-file-menu.xml')) class BufferEventsConfig(EventsConfig): def create_events(self): self.create_event('document-saved') self.create_event('document-changed') class BufferCommandsConfig(CommandsConfig): def open_file(self, file_name): self.svc.open_file(file_name) def close_file(self, file_name): self.svc.close_file(file_name) def current_file_saved(self): self.svc.file_saved() def get_view(self): return self.svc.get_view() def get_current(self): return self.svc.get_current() def get_documents(self): return self.svc.get_documents() # Service class class Buffer(Service): """ Buffer is a graphical manager for vim buffers. """ commands_config = BufferCommandsConfig actions_config = BufferActionsConfig events_config = BufferEventsConfig features_config = BufferFeaturesConfig def pre_start(self): self._documents = {} self._current = None self._view = BufferListView(self) self.get_action('close').set_sensitive(False) self._refresh_buffer_action_sensitivities() def get_view(self): return self._view def _refresh_buffer_action_sensitivities(self): for action_name in ['switch_next_buffer', 'switch_prev_buffer']: self.get_action(action_name).set_sensitive(len(self._documents) > 0) def open_file(self, file_name): doc = self._get_document_for_filename(file_name) if doc is None: doc = Document(self.boss, file_name) self._add_document(doc) self.view_document(doc) def close_current(self): if self._current is not None: self._remove_document(self._current) self.boss.editor.cmd('close', document=self._current) def close_file(self, file_name): document = self._get_document_for_filename(file_name) if document is not None: self._remove_document(document) self.boss.editor.cmd('close', document=document) def _get_document_for_filename(self, file_name): for uid, doc in self._documents.iteritems(): if doc.filename == file_name: return doc def _add_document(self, document): self._documents[document.unique_id] = document self._view.add_document(document) self._refresh_buffer_action_sensitivities() def _remove_document(self, document): del self._documents[document.unique_id] self._view.remove_document(document) self._refresh_buffer_action_sensitivities() def view_document(self, document): if document is not None and self._current is not document: self._current = document self._view.set_document(document) self.boss.editor.cmd('open', document=document) self.emit('document-changed', document=document) self.get_action('close').set_sensitive(document is not None) def file_saved(self): self.emit('document-saved', document=self._current) def get_current(self): return self._current def get_documents(self): return self._documents # Required Service attribute for service loading Service = Buffer # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/buffer/service.pida0000644000175000017500000000000010652670636016637 0ustar alialiPIDA-0.5.1/pida/services/buffer/test_buffer.py0000644000175000017500000000222010652670636017230 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/bugreport/0002755000175000017500000000000010652671501015104 5ustar alialiPIDA-0.5.1/pida/services/bugreport/glade/0002755000175000017500000000000010652671501016160 5ustar alialiPIDA-0.5.1/pida/services/bugreport/glade/bugreport.glade0000644000175000017500000002207710652670640021200 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 300 300 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 6 6 <b>Report PIDA Bug</b> True False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 4 1 6 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 Title: True GTK_FILL GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 0 3 Description: True 2 3 GTK_FILL GTK_FILL True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 GTK_FILL True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_WRAP_WORD 3 3 3 4 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-ok True 1 False 2 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 3 PIDA-0.5.1/pida/services/bugreport/locale/0002755000175000017500000000000010652671501016343 5ustar alialiPIDA-0.5.1/pida/services/bugreport/locale/fr_FR/0002755000175000017500000000000010652671501017341 5ustar alialiPIDA-0.5.1/pida/services/bugreport/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501021126 5ustar alialiPIDA-0.5.1/pida/services/bugreport/locale/fr_FR/LC_MESSAGES/bugreport.po0000644000175000017500000000212010652670637023501 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:10+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: bugreport.py:55 msgid "Bug Report" msgstr "Rapport de bug" #: bugreport.py:78 #, python-format msgid "" "Bug Reported:\n" "%s" msgstr "" "Rapport de bug:\n" "%s%s" #: bugreport.py:80 #, python-format msgid "" "Bug Report Failed:\n" "%s" msgstr "" "Rapport de bug échoué:\n" "%s" #: bugreport.py:105 msgid "Bug report" msgstr "Rapport de bug" #: bugreport.py:106 msgid "Make a bug report" msgstr "Rédiger un rapport de bug" #: glade/bugreport.glade.h:1 msgid "Report PIDA Bug" msgstr "Rapport de bug PIDA" #: glade/bugreport.glade.h:2 msgid "Description:" msgstr "Description:" #: glade/bugreport.glade.h:3 msgid "Title:" msgstr "Titre:" PIDA-0.5.1/pida/services/bugreport/uidef/0002755000175000017500000000000010652671501016200 5ustar alialiPIDA-0.5.1/pida/services/bugreport/uidef/bugreport.xml0000644000175000017500000000300010652670640020725 0ustar aliali PIDA-0.5.1/pida/services/bugreport/__init__.py0000644000175000017500000000222010652670640017212 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/bugreport/bugreport.py0000644000175000017500000001124710652670640017475 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from cgi import escape import gtk import gobject # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.utils.launchpadder.gtkgui import PasswordDialog from pida.utils.launchpadder.lplib import save_local_config, get_local_config,\ report from pida.utils.gthreads import AsyncTask, gcall # locale from pida.core.locale import Locale locale = Locale('bugreport') _ = locale.gettext class BugreportView(PidaGladeView): gladefile = 'bugreport' locale = locale icon_name = 'error' label_text = _('Bug Report') def on_ok_button__clicked(self, button): self.email, self.password = get_local_config() if self.email is None: self.get_pass() self.progress_bar.set_text('') task = AsyncTask(self.report, self.report_complete) task.start() self._pulsing = True self.progress_bar.show() gobject.timeout_add(100, self._pulse) def on_close_button__clicked(self, button): self.svc.get_action('show_bugreport').set_active(False) def report(self): title = self.title_entry.get_text() buf = self.description_text.get_buffer() description = buf.get_text(buf.get_start_iter(), buf.get_end_iter()) return report(None, self.email, self.password, 'pida', title, description) def report_complete(self, success, data): if success: self.svc.boss.cmd('notify', 'notify', title=_('Bug Reported'), data=data) else: self.svc.boss.cmd('notify', 'notify', title=_('Bug Report Failed'), data=data) self.title_entry.set_text('') self.description_text.get_buffer().set_text('') self.progress_bar.hide() self._pulsing = False def _pulse(self): self.progress_bar.pulse() return self._pulsing def get_pass(self): pass_dlg = PasswordDialog() def pass_response(dlg, resp): dlg.hide() if resp == gtk.RESPONSE_ACCEPT: self.email, self.password, save = dlg.get_user_details() if save: save_local_config(self.email, self.password) dlg.destroy() pass_dlg.connect('response', pass_response) pass_dlg.run() def can_be_closed(self): self.svc.get_action('show_bugreport').set_active(False) class BugreportActions(ActionsConfig): def create_actions(self): self.create_action( 'show_bugreport', TYPE_TOGGLE, _('Bug report'), _('Make a bug report'), 'error', self.on_report, 'b' ) def on_report(self, action): if action.get_active(): self.svc.show_report() else: self.svc.hide_report() # Service class class Bugreport(Service): """Describe your Service Here""" actions_config = BugreportActions def pre_start(self): self._view = BugreportView(self) def show_report(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_report(self): self.boss.cmd('window', 'remove_view', view=self._view) # Required Service attribute for service loading Service = Bugreport # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/bugreport/service.pida0000644000175000017500000000000010652670640017372 0ustar alialiPIDA-0.5.1/pida/services/bugreport/test_bugreport.py0000644000175000017500000000222010652670640020523 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/commander/0002755000175000017500000000000010652671501015040 5ustar alialiPIDA-0.5.1/pida/services/commander/locale/0002755000175000017500000000000010652671501016277 5ustar alialiPIDA-0.5.1/pida/services/commander/locale/fr_FR/0002755000175000017500000000000010652671501017275 5ustar alialiPIDA-0.5.1/pida/services/commander/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501021062 5ustar alialiPIDA-0.5.1/pida/services/commander/locale/fr_FR/LC_MESSAGES/commander.po0000644000175000017500000000633610652670625023403 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../commander.py:59 msgid "Terminal Font" msgstr "Police du terminal" #: ../../commander.py:62 msgid "The font used in terminals" msgstr "La police utilisée dans les terminaux" #: ../../commander.py:67 msgid "Terminal Transparency" msgstr "Transparence du terminal" #: ../../commander.py:70 msgid "Whether terminals will be transparent" msgstr "Détermine la transparence des terminaux" #: ../../commander.py:75 msgid "Use a background image" msgstr "Utiliser un fond d'écran" #: ../../commander.py:78 msgid "Whether a background image will be displayed" msgstr "Détermine l'image du fond d'écran des terminaux" #: ../../commander.py:83 msgid "Background image file" msgstr "Image du fond d'écran" #: ../../commander.py:86 msgid "The file used for the background image" msgstr "Le fichier utilisé pour le fond d'écran" #: ../../commander.py:91 msgid "Cursor Blinks" msgstr "Curseur clignotant" #: ../../commander.py:94 msgid "Whether the cursor will blink" msgstr "Indique si le curseur doit clignoter" #: ../../commander.py:99 msgid "Scrollback line numer" msgstr "Nombre de lignes" #: ../../commander.py:102 msgid "The number of lines in the terminal scrollback buffer" msgstr "Le nombre de lignes à stocker en mémoire" #: ../../commander.py:107 msgid "The shell command" msgstr "La commande shell" #: ../../commander.py:110 msgid "The command that will be used for shells" msgstr "La commande qui sera utilisée pour le shell" #: ../../commander.py:115 msgid "The shell arguments" msgstr "Arguments du shell" #: ../../commander.py:118 msgid "The arguments to pass to the shell command" msgstr "Les arguments passés au shell" #: ../../commander.py:127 msgid "Run Shell" msgstr "Démarrer un shell" #: ../../commander.py:128 msgid "Open a shell prompt" msgstr "Ouvrir un shell" #: ../../commander.py:137 msgid "Shell in file directory" msgstr "Ouvrir un shell dans le répertoire du fichier" #: ../../commander.py:138 msgid "Open a shell prompt in the parent directory of this file" msgstr "Ouvrir un shell dans le répertoire parent du fichier" #: ../../commander.py:147 msgid "Shell in directory" msgstr "Shell dans le répertoire" #: ../../commander.py:148 msgid "Open a shell prompt in the directory" msgstr "Ouvrir un shell dans le répertoire" #: ../../commander.py:169 msgid "Command" msgstr "Commande" #: ../../commander.py:214 msgid "Close this terminal" msgstr "Fermer ce terminal" #: ../../commander.py:217 msgid "Copy the selection to the clipboard" msgstr "Copie la sélection dans le presse-papier" #: ../../commander.py:222 msgid "Paste the contents of the clipboard" msgstr "Coller le contenu du presse-papier" #: ../../commander.py:323 msgid "Child exited" msgstr "Le programme a terminé" #: ../../commander.py:324 msgid "Press any key to close." msgstr "Appuyer sur une touche pour fermer." PIDA-0.5.1/pida/services/commander/uidef/0002755000175000017500000000000010652671501016134 5ustar alialiPIDA-0.5.1/pida/services/commander/uidef/commander-dir-menu.xml0000644000175000017500000000037410652670625022351 0ustar aliali PIDA-0.5.1/pida/services/commander/uidef/commander-file-menu.xml0000644000175000017500000000037610652670625022514 0ustar aliali PIDA-0.5.1/pida/services/commander/uidef/commander.xml0000644000175000017500000000254210652670625020632 0ustar aliali PIDA-0.5.1/pida/services/commander/__init__.py0000644000175000017500000000222010652670626017152 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/commander/commander.py0000644000175000017500000003601410652670626017370 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os, subprocess import gtk, gobject # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig, OTypeString, OTypeBoolean, \ OTypeInteger, OTypeFile, OTypeFont, OTypeStringList from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida import PIDA_VERSION from pida.utils.gthreads import AsyncTask from pida.ui.views import PidaView from pida.ui.terminal import PidaTerminal from pida.ui.buttons import create_mini_button # locale from pida.core.locale import Locale locale = Locale('commander') _ = locale.gettext def get_default_system_shell(): if 'SHELL' in os.environ: return os.environ['SHELL'] else: return 'bash' class CommanderOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'font', _('Terminal Font'), OTypeFont, 'Monospace 10', _('The font used in terminals'), ) self.create_option( 'transparent', _('Terminal Transparency'), OTypeBoolean, False, _('Whether terminals will be transparent'), ) self.create_option( 'use_background_image', _('Use a background image'), OTypeBoolean, False, _('Whether a background image will be displayed'), ) self.create_option( 'background_image_file', _('Background image file'), OTypeFile, '', _('The file used for the background image'), ) self.create_option( 'cursor_blinks', _('Cursor Blinks'), OTypeBoolean, False, _('Whether the cursor will blink') ) self.create_option( 'scrollback_lines', _('Scrollback line numer'), OTypeInteger, 100, _('The number of lines in the terminal scrollback buffer'), ) self.create_option( 'scrollbar_visible', _('Show terminal scrollbar'), OTypeBoolean, True, _('Whether a scrollbar should be shown'), ) self.create_option( 'allow_bold', _('Allow bold in the terminal'), OTypeBoolean, True, _('Whether bold text is allowed in the terminal'), ) self.create_option( 'audible_bell', _('Emit audible bell in terminal'), OTypeBoolean, False, _('Whether an audible bell will be emitted in the terminal'), ) self.create_option( 'shell_command', _('The shell command'), OTypeString, get_default_system_shell(), _('The command that will be used for shells') ) self.create_option( 'shell_command_args', _('The shell arguments'), OTypeStringList, [], _('The arguments to pass to the shell command'), ) class CommanderActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'shell', TYPE_NORMAL, _('Run Shell'), _('Open a shell prompt'), 'terminal', self.execute_shell, 'T', ) self.create_action( 'terminal-for-file', TYPE_NORMAL, _('Shell in file directory'), _('Open a shell prompt in the parent directory of this file'), 'terminal', self.on_terminal_for_file, 'NOACCEL', ) self.create_action( 'terminal-for-dir', TYPE_NORMAL, _('Shell in directory'), _('Open a shell prompt in the directory'), 'terminal', self.on_terminal_for_dir, 'NOACCEL', ) def execute_shell(self, action): self.svc.cmd('execute_shell', cwd=self.svc.get_current_project_directory()) def on_terminal_for_file(self, action): cwd = os.path.dirname(action.contexts_kw['file_name']) self.svc.cmd('execute_shell', cwd=cwd) def on_terminal_for_dir(self, action): cwd = action.contexts_kw['dir_name'] self.svc.cmd('execute_shell', cwd=cwd) class CommanderCommandsConfig(CommandsConfig): def execute(self, commandargs, env=[], cwd=os.getcwd(), title=_('Command'), icon='terminal', eof_handler=None, use_python_fork=False, parser_func=None): return self.svc.execute(commandargs, env, cwd, title, icon, eof_handler, use_python_fork, parser_func) def execute_shell(self, env=[], cwd=os.getcwd(), title='Shell'): shell_command = self.svc.opt('shell_command') shell_args = self.svc.opt('shell_command_args') commandargs = [shell_command] + shell_args return self.svc.execute(commandargs, env=env, cwd=cwd, title=title, icon=None) class CommanderFeaturesConfig(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'commander-file-menu.xml')) self.subscribe_foreign_feature('contexts', 'dir-menu', (self.svc.get_action_group(), 'commander-dir-menu.xml')) class CommanderEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('project', 'project_switched', self.svc.set_current_project) class TerminalView(PidaView): icon_name = 'terminal' def create_ui(self): self._pid = None self._hb = gtk.HBox() self._hb.show() self.add_main_widget(self._hb) self._term = PidaTerminal(**self.svc.get_terminal_options()) self._term.parent_view = self self._term.connect('window-title-changed', self.on_window_title_changed) self._term.connect('selection-changed', self.on_selection_changed) self._term.show() self._create_scrollbar() self._create_bar() self._hb.pack_start(self._term) if self.svc.opt('scrollbar_visible'): self._hb.pack_start(self._scrollbar, expand=False) self._hb.pack_start(self._bar, expand=False) self.master = None self.slave = None def _create_scrollbar(self): self._scrollbar = gtk.VScrollbar() self._scrollbar.set_adjustment(self._term.get_adjustment()) self._scrollbar.show() def _create_bar(self): self._bar = gtk.VBox(spacing=1) self._copy_button = create_mini_button( gtk.STOCK_COPY, _('Copy the selection to the clipboard'), self.on_copy_clicked) self._copy_button.set_sensitive(False) self._bar.pack_start(self._copy_button, expand=False) self._paste_button = create_mini_button( gtk.STOCK_PASTE, _('Paste the contents of the clipboard'), self.on_paste_clicked) self._bar.pack_start(self._paste_button, expand=False) self._title = gtk.Label() self._title.set_alignment(0.5, 1) self._title.set_padding(0, 3) self._title.set_angle(270) self._title.set_size_request(0,0) self._bar.pack_start(self._title) self._bar.show_all() def execute(self, commandargs, env, cwd, eof_handler=None, use_python_fork=False, parser_func=None): title_text = ' '.join(commandargs) self._title.set_text(title_text) if eof_handler is None: eof_handler = self.on_exited self.eof_handler = eof_handler if use_python_fork: if parser_func == None: self._python_fork(commandargs, env, cwd) else: self._python_fork_parse(commandargs, env, cwd, parser_func) else: self._vte_fork(commandargs, env, cwd) def _python_fork_waiter(self, popen): exit_code = popen.wait() return exit_code def _python_fork_complete(self, exit_code): gobject.timeout_add(200, self.eof_handler, self._term) def _python_fork_preexec_fn(self): os.setpgrp() def _python_fork(self, commandargs, env, cwd): self._term.connect('commit', self.on_commit_python) # TODO: Env broken env = dict(os.environ) env['TERM'] = 'xterm' (master, slave) = os.openpty() self.slave = slave self.master = master self._term.set_pty(master) p = subprocess.Popen(commandargs, stdin=slave, stdout=slave, preexec_fn=self._python_fork_preexec_fn, stderr=slave, env=env, cwd=cwd, close_fds=True) self._pid = p.pid t = AsyncTask(self._python_fork_waiter, self._python_fork_complete) t.start(p) def _python_fork_parse(self, commandargs, env, cwd, parser_func): self._term.connect('commit', self.on_commit_python) env = dict(os.environ) env['TERM'] = 'xterm' master, self.slave = os.openpty() self._term.set_pty(master) self.master, slave = os.openpty() p = subprocess.Popen(commandargs, stdout=slave, stderr=subprocess.STDOUT, stdin=slave, close_fds=True) self._pid = p.pid gobject.io_add_watch(self.master, gobject.IO_IN, self._on_python_fork_parse_stdout, parser_func) self._term.connect('key-press-event', self._on_python_fork_parse_key_press_event, self.master) def _on_python_fork_parse_key_press_event(self, term, event, fd): if event.hardware_keycode == 22: os.write(fd, "") elif event.hardware_keycode == 98: os.write(fd, "OA") elif event.hardware_keycode == 104: os.write(fd, "OB") elif event.hardware_keycode == 100: os.write(fd, "OD") elif event.hardware_keycode == 102: os.write(fd, "OC") else: data = event.string os.write(fd, data) return True def _on_python_fork_parse_stdout(self, fd, state, parser = None): data = os.read(fd,1024) os.write(self.slave, data) if parser != None: parser(data) return True def _vte_env_map_to_list(self, env): return ['%s=%s' % (k, v) for (k, v) in env.items()] def _vte_fork(self, commandargs, env, cwd): self._term.connect('child-exited', self.eof_handler) self._pid = self._term.fork_command(commandargs[0], commandargs, env, cwd) def close_view(self): self.svc.boss.cmd('window', 'remove_view', view=self) def on_exited(self, term): self._term.feed_text(_('Child exited')+'\r\n', '1;34') self._term.feed_text(_('Press any key to close.')) self._term.connect('commit', self.on_press_any_key) def can_be_closed(self): self.kill() return True def kill(self): if self._pid is not None: try: os.kill(self._pid, 9) except OSError: self.svc.log_debug('PID %s has already gone' % self._pid) def on_selection_changed(self, term): self._copy_button.set_sensitive(self._term.get_has_selection()) def on_copy_clicked(self, button): self._term.copy_clipboard() def on_paste_clicked(self, button): self._term.paste_clipboard() def on_press_any_key(self, term, data, datalen): self.close_view() def on_commit_python(self, term, data, datalen): if data == '\x03': os.kill(self._pid, 2) def on_window_title_changed(self, term): self._title.set_text(term.get_window_title()) # Service class class Commander(Service): """Describe your Service Here""" commands_config = CommanderCommandsConfig actions_config = CommanderActionsConfig options_config = CommanderOptionsConfig features_config = CommanderFeaturesConfig events_config = CommanderEvents def start(self): self._terminals = [] self.current_project = None def execute(self, commandargs, env, cwd, title, icon, eof_handler=None, use_python_fork=False, parser_func=None): env_pida = env env_pida.append('PIDA_VERSION=%s' % PIDA_VERSION) current_project = self.boss.cmd('project', 'get_current_project') if current_project: env_pida.append('PIDA_PROJECT=%s' % current_project.source_directory) t = TerminalView(self, title, icon) t.execute(commandargs, env_pida, cwd, eof_handler, use_python_fork, parser_func) self.boss.cmd('window', 'add_view', paned='Terminal', view=t) self._terminals.append(t) return t def get_terminal_options(self): options = dict( font_from_string=self.opt('font'), background_transparent=self.opt('transparent'), cursor_blinks=self.opt('cursor_blinks'), scrollback_lines=self.opt('scrollback_lines'), allow_bold = self.opt('allow_bold'), audible_bell = self.opt('audible_bell'), ) if self.opt('use_background_image'): imagefile = self.opt('background_image_file') options['background_image_file'] = imagefile return options def set_current_project(self, project): self.current_project = project def get_current_project_directory(self): if self.current_project is not None: return self.current_project.source_directory else: return os.getcwd() # Required Service attribute for service loading Service = Commander # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/commander/service.pida0000644000175000017500000000000010652670626017332 0ustar alialiPIDA-0.5.1/pida/services/commander/test_commander.py0000644000175000017500000000222010652670626020417 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/contexts/0002755000175000017500000000000010652671501014742 5ustar alialiPIDA-0.5.1/pida/services/contexts/uidef/0002755000175000017500000000000010652671501016036 5ustar alialiPIDA-0.5.1/pida/services/contexts/uidef/dir-menu.xml0000644000175000017500000000026010652670633020301 0ustar aliali PIDA-0.5.1/pida/services/contexts/uidef/file-menu.xml0000644000175000017500000000026010652670633020442 0ustar aliali PIDA-0.5.1/pida/services/contexts/uidef/url-menu.xml0000644000175000017500000000026010652670633020325 0ustar aliali PIDA-0.5.1/pida/services/contexts/__init__.py0000644000175000017500000000222010652670633017052 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/contexts/contexts.py0000644000175000017500000000725610652670633017200 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.environment import get_uidef_path CONTEXT_TYPES = [ 'file-menu', 'dir-menu', 'url-menu', ] class ContextFeaturesConfig(FeaturesConfig): def create_features(self): for context in CONTEXT_TYPES: self.create_feature(context) class ContextCommandsConfig(CommandsConfig): def get_menu(self, context, **kw): return self.svc.get_menu(context, **kw) def popup_menu(self, context, event=None, **kw): menu = self.get_menu(context, **kw) menu.show_all() if event is None: button = 3 time = gtk.get_current_event_time() else: button = event.button time = event.time menu.popup(None, None, None, button, time) class ContextEventsConfig(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('plugins', 'plugin_started', self.plugins_changed) self.subscribe_foreign_event('plugins', 'plugin_stopped', self.plugins_changed) def plugins_changed(self, plugin): self.svc.create_uims() # Service class class Contexts(Service): """Describe your Service Here""" features_config = ContextFeaturesConfig commands_config = ContextCommandsConfig events_config = ContextEventsConfig def start(self): self.create_uims() def create_uims(self): self._uims = {} for context in CONTEXT_TYPES: uim = self._uims[context] = gtk.UIManager() uim.add_ui_from_file(self.get_base_ui_definition_path(context)) for ag, uidef in self.features(context): uim.insert_action_group(ag, 0) uidef_path = get_uidef_path(uidef) uim.add_ui_from_file(uidef_path) def get_base_ui_definition_path(self, context): file_name = '%s.xml' % context return get_uidef_path(file_name) def get_menu(self, context, **kw): for group in self._uims[context].get_action_groups(): for action in group.list_actions(): action.contexts_kw = kw menu = self._uims[context].get_toplevels('popup')[0] return menu # Required Service attribute for service loading Service = Contexts # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/contexts/service.pida0000644000175000017500000000000010652670633017232 0ustar alialiPIDA-0.5.1/pida/services/contexts/test_contexts.py0000644000175000017500000000222010652670633020221 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/editor/0002755000175000017500000000000010652671501014361 5ustar alialiPIDA-0.5.1/pida/services/editor/locale/0002755000175000017500000000000010652671501015620 5ustar alialiPIDA-0.5.1/pida/services/editor/locale/fr_FR/0002755000175000017500000000000010652671501016616 5ustar alialiPIDA-0.5.1/pida/services/editor/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020403 5ustar alialiPIDA-0.5.1/pida/services/editor/locale/fr_FR/LC_MESSAGES/editor.po0000644000175000017500000000111010652670615022225 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../editor.py:45 msgid "Editor Type" msgstr "Type d'éditeur" #: ../../editor.py:48 msgid "The Editor used" msgstr "L'éditeur utilisé" PIDA-0.5.1/pida/services/editor/__init__.py0000644000175000017500000000222010652670616016472 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/editor/editor.py0000644000175000017500000000431310652670616016226 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig, otype_string_options_factory from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE # locale from pida.core.locale import Locale locale = Locale('editor') _ = locale.gettext class EditorOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'editor_type', _('Editor Type'), otype_string_options_factory(['vim', 'emacs']), 'vim', _('The Editor used'), ) class EditorEvents(EventsConfig): def create_events(self): self.create_event('started') # Service class class Editor(Service): """Describe your Service Here""" options_config = EditorOptionsConfig events_config = EditorEvents # Required Service attribute for service loading Service = Editor # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/editor/service.pida0000644000175000017500000000000010652670616016652 0ustar alialiPIDA-0.5.1/pida/services/editor/test_editor.py0000644000175000017500000000222010652670616017260 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filemanager/0002755000175000017500000000000010652671501015345 5ustar alialiPIDA-0.5.1/pida/services/filemanager/locale/0002755000175000017500000000000010652671501016604 5ustar alialiPIDA-0.5.1/pida/services/filemanager/locale/fr_FR/0002755000175000017500000000000010652671501017602 5ustar alialiPIDA-0.5.1/pida/services/filemanager/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501021367 5ustar alialiPIDA-0.5.1/pida/services/filemanager/locale/fr_FR/LC_MESSAGES/filemanager.po0000644000175000017500000000556610652670640024216 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 12:41+0200\n" "PO-Revision-Date: 2007-05-02 12:41+0200\n" "Last-Translator: Mathieu Virbel \n" "Language-Team: None \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: filemanager.py:145 msgid "Files" msgstr "Fichiers" #: filemanager.py:177 msgid "Directories First" msgstr "Répertoire en premier" #: filemanager.py:178 msgid "File Name" msgstr "Nom de fichier" #: filemanager.py:179 msgid "Case Sensitive File Name" msgstr "Nom de fichier sensible à la casse" #: filemanager.py:180 msgid "File Path" msgstr "Chemin du fichier" #: filemanager.py:181 msgid "Extension" msgstr "Extension" #: filemanager.py:182 msgid "Version Control Status" msgstr "Status de version" #: filemanager.py:336 msgid "Show hidden files" msgstr "Afficher les fichiers cachés" #: filemanager.py:339 msgid "Shows hidden files" msgstr "Afficher les fichiers cachés" #: filemanager.py:343 msgid "Remember last Path" msgstr "Se souvenir du dernier chemin" #: filemanager.py:346 msgid "Remembers the last browsed path" msgstr "Sauvegarder le dernier chemin parcouru" #: filemanager.py:350 msgid "Last browsed Path" msgstr "Dernier chemin parcouru" #: filemanager.py:353 msgid "The last browsed path" msgstr "Le dernier chemin parcouru" #: filemanager.py:357 msgid "Hide regex" msgstr "Cacher les fichiers" #: filemanager.py:360 msgid "Cacher les fichiers qui match l'expression régulière" msgstr "" #: filemanager.py:368 msgid "Browse the file directory" msgstr "Parcourir le répertoire du fichier" #: filemanager.py:369 msgid "Browse the parent directory of this file" msgstr "Parcourir le répertoire parent de ce fichier" #: filemanager.py:378 filemanager.py:379 msgid "Browse the directory" msgstr "Parcourir le répertoire" #: filemanager.py:388 msgid "Show file browser" msgstr "Afficher l'explorateur de fichier" #: filemanager.py:389 msgid "Show the file browser view" msgstr "Afficher l'explorateur de fichier" #: filemanager.py:398 msgid "Go Up" msgstr "Parent" #: filemanager.py:399 msgid "Go to the parent directory" msgstr "Aller au répertoire parent" #: filemanager.py:408 msgid "Open Terminal" msgstr "Ouvrir une console" #: filemanager.py:409 msgid "Open a terminal in this directory" msgstr "Ouvrir une console dans ce répertoire" #: filemanager.py:418 msgid "Refresh Directory" msgstr "Actualiser le répertoire" #: filemanager.py:419 msgid "Refresh the current directory" msgstr "Actualiser le répertoire courant" #: filemanager.py:428 msgid "Project Root" msgstr "Racine du projet" #: filemanager.py:429 msgid "Browse the root of the current project" msgstr "Parcourir la racine du projet" PIDA-0.5.1/pida/services/filemanager/uidef/0002755000175000017500000000000010652671501016441 5ustar alialiPIDA-0.5.1/pida/services/filemanager/uidef/filemanager-dir-menu.xml0000644000175000017500000000037010652670641023155 0ustar aliali PIDA-0.5.1/pida/services/filemanager/uidef/filemanager-file-menu.xml0000644000175000017500000000037210652670641023320 0ustar aliali PIDA-0.5.1/pida/services/filemanager/uidef/filemanager-toolbar.xml0000644000175000017500000000075610652670641023107 0ustar aliali PIDA-0.5.1/pida/services/filemanager/uidef/filemanager.xml0000644000175000017500000000257210652670641021445 0ustar aliali PIDA-0.5.1/pida/services/filemanager/__init__.py0000644000175000017500000000222010652670641017454 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filemanager/filemanager.py0000644000175000017500000005262310652670641020203 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from weakref import proxy import gtk from os import listdir, path import os import shutil import cgi import re # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.options import OptionsConfig, OTypeBoolean, OTypeString from pida.core.environment import get_uidef_path from pida.utils.gthreads import GeneratorTask, AsyncTask from pida.ui.views import PidaView from pida.ui.objectlist import AttrSortCombo from kiwi.ui.objectlist import Column, ColoredColumn, ObjectList # locale from pida.core.locale import Locale locale = Locale('filemanager') _ = locale.gettext state_text = dict( hidden=' ', none='?', new='A', modified='M', ignored=' ', normal=' ', error='E', empty='!', conflict='C', removed='D', missing='!', max='+', external='>', ) state_style = dict( # tuples of (color, is_bold, is_italic) hidden=('lightgrey', False, True), ignored=('lightgrey', False, True), #TODO: better handling of normal directories none=('#888888', False, True), normal=('black', False, False), error=('darkred', True, True), empty=('black', False, True), modified=('darkred', True, False), conflict=('darkred', True, True), removed=('#c06060', True, True), missing=('#00c0c0', True, False), new=('blue', True, False), max=('#c0c000', False, False), external=('#333333', False, True), ) class FileEntry(object): """The model for file entries""" def __init__(self, name, parent_path, manager): self._manager = manager self.state = 'normal' self.name = name self.lower_name = self.name.lower() self.parent_path = parent_path self.path = os.path.join(parent_path, name) self.extension = os.path.splitext(self.name)[-1] self.extension_sort = self.extension, self.lower_name self.is_dir = os.path.isdir(self.path) self.is_dir_sort = not self.is_dir, self.lower_name self.icon_stock_id = self.get_icon_stock_id() self.visible = False def get_markup(self): return self.format(cgi.escape(self.name)) markup = property(get_markup) def get_icon_stock_id(self): if path.isdir(self.path): return 'stock_folder' else: #TODO: get a real mimetype icon return 'text-x-generic' def get_state_markup(self): text = state_text.get(self.state, ' ') wrap = '%s' return wrap%self.format(text) state_markup = property(get_state_markup) def format(self, text): color, b, i = state_style.get(self.state, ('black', False, False)) if b: text = '%s' % text if i: text = '%s' % text return '%s' % (color, text) class FilemanagerView(PidaView): _columns = [ Column("icon_stock_id", use_stock=True), Column("state_markup", use_markup=True), Column("markup", use_markup=True), Column("lower_name", visible=False, searchable=True), ] label_text = _('Files') icon_name = 'file-manager' def create_ui(self): self._vbox = gtk.VBox() self._vbox.show() self.create_toolbar() self.create_file_list() self._clipboard_file = None self._fix_paste_sensitivity() self.add_main_widget(self._vbox) def create_file_list(self): self.file_list = ObjectList() self.file_list.set_headers_visible(False) self.file_list.set_columns(self._columns); self.file_list.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) #XXX: real file self.file_list.connect('selection-changed', self.on_selection_changed) self.file_list.connect('row-activated', self.on_file_activated) self.file_list.connect('right-click', self.on_file_right_click) self.entries = {} self.update_to_path(self.svc.path) self.file_list.show() self._vbox.pack_start(self.file_list) self._sort_combo = AttrSortCombo(self.file_list, [ ('is_dir_sort', _('Directories First')), ('lower_name', _('File Name')), ('name', _('Case Sensitive File Name')), ('path', _('File Path')), ('extension_sort', _('Extension')), ('state', _('Version Control Status')), ], 'is_dir_sort') self._sort_combo.show() self._vbox.pack_start(self._sort_combo, expand=False) self.on_selection_changed(self.file_list, None) def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('filemanager-toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_MENU) self._vbox.pack_start(self._toolbar, expand=False) self._toolbar.show_all() def add_or_update_file(self, name, basepath, state): if basepath != self.path: return entry = self.entries.setdefault(name, FileEntry(name, basepath, self)) entry.state = state self.show_or_hide(entry) def show_or_hide(self, entry): from operator import and_ def check(checker): return checker( name=entry.name, path=entry.parent_path, state=entry.state, ) if self.svc.opt('show_hidden'): show = True else: show = reduce(and_, map(check, self.svc.features('file_hidden_check'))) if show: if entry.visible: self.file_list.update(entry) else: self.file_list.append(entry) entry.visible = True else: if entry.visible: self.file_list.remove(entry) entry.visible = False def update_to_path(self, new_path=None): if new_path is None: new_path = self.path else: self.path = new_path self.file_list.clear() self.entries.clear() for lister in self.svc.features('file_lister'): GeneratorTask(lister, self.add_or_update_file).start(self.path) self.create_ancest_tree() # This is painful, and will always break # So use the following method instead def update_single_file(self, name, basepath): def _update_file(oname, obasepath, state): if oname == name and basepath == obasepath: if name not in self.entries: self.entries[oname] = FileEntry(oname, obasepath, self) self.entries[oname].state = state self.show_or_hide(self.entries[oname]) for lister in self.svc.features('file_lister'): GeneratorTask(lister, _update_file).start(self.path) def update_single_file(self, name, basepath): if basepath != self.path: return if name not in self.entries: self.entries[name] = FileEntry(name, basepath, self) self.show_or_hide(self.entries[name]) def update_removed_file(self, filename): entry = self.entries.pop(filename, None) if entry is not None and entry.visible: self.file_list.remove(entry) def on_file_activated(self, rowitem, fileentry): if os.path.exists(fileentry.path): if fileentry.is_dir: self.svc.browse(fileentry.path) else: self.svc.boss.cmd('buffer', 'open_file', file_name=fileentry.path) else: self.update_removed_file(fileentry.name) def on_file_right_click(self, ol, item, event=None): if item.is_dir: self.svc.boss.cmd('contexts', 'popup_menu', context='dir-menu', dir_name=item.path, event=event) else: self.svc.boss.cmd('contexts', 'popup_menu', context='file-menu', file_name=item.path, event=event) def on_selection_changed(self, ol, item): for act_name in ['toolbar_copy', 'toolbar_delete']: self.svc.get_action(act_name).set_sensitive(item is not None) def rename_file(self, old, new, entry): print 'renaming', old, 'to' ,new def create_ancest_tree(self): task = AsyncTask(self._get_ancestors, self._show_ancestors) task.start(self.path) def _on_act_up_ancestor(self, action, directory): self.svc.browse(directory) def _show_ancestors(self, ancs): toolitem = self.svc.get_action('toolbar_up').get_proxies()[0] menu = gtk.Menu() for anc in ancs: action = gtk.Action(anc, anc, anc, 'directory') action.connect('activate', self._on_act_up_ancestor, anc) menuitem = action.create_menu_item() menu.add(menuitem) menu.show_all() toolitem.set_menu(menu) def _get_ancestors(self, directory): ancs = [directory] while directory != '/': parent = os.path.dirname(directory) ancs.append(parent) directory = parent return ancs def get_selected_filename(self): fileentry = self.file_list.get_selected() if fileentry is not None: return fileentry.path def copy_clipboard(self): current = self.get_selected_filename() if os.path.exists(current): self._clipboard_file = current else: self._clipboard_file = None self._fix_paste_sensitivity() def _fix_paste_sensitivity(self): self.svc.get_action('toolbar_paste').set_sensitive(self._clipboard_file is not None) def paste_clipboard(self): task = AsyncTask(self._paste_clipboard, lambda: None) task.start() def _paste_clipboard(self): newname = os.path.join(self.path, os.path.basename(self._clipboard_file)) if newname == self._clipboard_file: self.svc.error_dlg(_('Cannot copy files to themselves.')) return if not os.path.exists(self._clipboard_file): self.svc.error_dlg(_('Source file has vanished.')) if os.path.exists(newname): self.svc.error_dlg(_('Destination already exists.')) return if os.path.isdir(self._clipboard_file): shutil.copytree(self._clipboard_file, newname) else: shutil.copy2(self._clipboard_file, newname) def remove_path(self, path): task = AsyncTask(self._remove_path, lambda: None) task.start(path) def _remove_path(self, path): if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) if path == self._clipboard_file: self._clipboard_file = None self._fix_paste_sensitivity() class FilemanagerEvents(EventsConfig): def create_events(self): self.create_event('browsed_path_changed') self.create_event('file_renamed') def subscribe_foreign_events(self): self.subscribe_event('file_renamed', self.svc.rename_file) self.subscribe_foreign_event('project', 'project_switched', self.svc.on_project_switched) class FilemanagerCommandsConfig(CommandsConfig): def browse(self, new_path): self.svc.browse(new_path) def get_browsed_path(self): return self.svc.path def get_view(self): return self.svc.get_view() def present_view(self): return self.svc.boss.cmd('window', 'present_view', view=self.svc.get_view()) def update_file(self, filename, dirname): if dirname == self.svc.get_view().path: self.svc.get_view().update_single_file(filename, dirname) def update_removed_file(self, filename, dirname): if dirname == self.svc.get_view().path: self.svc.get_view().update_removed_file(filename) def refresh(self): self.svc.get_view().update_to_path() class FilemanagerFeatureConfig(FeaturesConfig): def create_features(self): self.create_feature('file_manager') self.create_feature('file_hidden_check') self.create_feature('file_lister') def subscribe_foreign_features(self): self.subscribe_feature('file_hidden_check', self.svc.check_hidden_regex) self.subscribe_feature('file_lister', self.svc.file_lister) self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'filemanager-file-menu.xml')) self.subscribe_foreign_feature('contexts', 'dir-menu', (self.svc.get_action_group(), 'filemanager-dir-menu.xml')) class FileManagerOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'show_hidden', _('Show hidden files'), OTypeBoolean, True, _('Shows hidden files')) self.create_option( 'last_browsed_remember', _('Remember last Path'), OTypeBoolean, True, _('Remembers the last browsed path')) self.create_option( 'last_browsed', _('Last browsed Path'), OTypeString, path.expanduser('~'), _('The last browsed path')) self.create_option( 'hide_regex', _('Hide regex'), OTypeString, '^\.|.*\.py[co]$', _('Hides files that match the regex')) class FileManagerActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'browse-for-file', TYPE_NORMAL, _('Browse the file directory'), _('Browse the parent directory of this file'), 'file-manager', self.on_browse_for_file, 'NOACCEL', ) self.create_action( 'browse-for-dir', TYPE_NORMAL, _('Browse the directory'), _('Browse the directory'), 'file-manager', self.on_browse_for_dir, 'NOACCEL', ) self.create_action( 'show_filebrowser', TYPE_NORMAL, _('Show file browser'), _('Show the file browser view'), 'file-manager', self.on_show_filebrowser, 'f' ) self.create_action( 'toolbar_up', TYPE_MENUTOOL, _('Go Up'), _('Go to the parent directory'), gtk.STOCK_GO_UP, self.on_toolbar_up, 'Up', ) self.create_action( 'toolbar_terminal', TYPE_NORMAL, _('Open Terminal'), _('Open a terminal in this directory'), 'terminal', self.on_toolbar_terminal, 'NOACCEL', ) self.create_action( 'toolbar_refresh', TYPE_NORMAL, _('Refresh Directory'), _('Refresh the current directory'), gtk.STOCK_REFRESH, self.on_toolbar_refresh, 'NOACCEL', ) self.create_action( 'toolbar_projectroot', TYPE_NORMAL, _('Project Root'), _('Browse the root of the current project'), 'user-home', self.on_toolbar_projectroot, 'NOACCEL', ) self.create_action( 'toolbar_copy', TYPE_NORMAL, _('Copy File'), _('Copy selected file to the clipboard'), gtk.STOCK_COPY, self.on_toolbar_copy, 'NOACCEL', ) self.create_action( 'toolbar_paste', TYPE_NORMAL, _('Paste File'), _('Paste selected file from the clipboard'), gtk.STOCK_PASTE, self.on_toolbar_paste, 'NOACCEL', ) self.create_action( 'toolbar_delete', TYPE_NORMAL, _('Delete File'), _('Delete the selected file'), gtk.STOCK_DELETE, self.on_toolbar_delete, 'NOACCEL', ) self.create_action( 'toolbar_toggle_hidden', TYPE_TOGGLE, _('Show Hidden Files'), _('Toggle the display of hidden files'), gtk.STOCK_SELECT_ALL, self.on_toggle_hidden, 'NOACCEL', ) def on_browse_for_file(self, action): new_path = path.dirname(action.contexts_kw['file_name']) self.svc.cmd('browse', new_path=new_path) self.svc.cmd('present_view') def on_browse_for_dir(self, action): new_path = action.contexts_kw['dir_name'] self.svc.cmd('browse', new_path=new_path) self.svc.cmd('present_view') def on_show_filebrowser(self, action): self.svc.cmd('present_view') def on_toolbar_up(self, action): self.svc.go_up() def on_toolbar_terminal(self, action): self.svc.boss.cmd('commander','execute_shell', cwd=self.svc.path) def on_toggle_hidden(self, action): self.svc.set_opt('show_hidden', action.get_active()) self.on_toolbar_refresh(action) def on_toolbar_refresh(self, action): self.svc.get_view().update_to_path() def on_toolbar_projectroot(self, action): self.svc.browse(self.svc.current_project.source_directory) def on_toolbar_copy(self, action): self.svc.get_view().copy_clipboard() def on_toolbar_paste(self, action): self.svc.get_view().paste_clipboard() def on_toolbar_delete(self, action): current = self.svc.get_view().get_selected_filename() if current is not None: if self.svc.yesno_dlg( _('Are you sure you want to delete the selected file: %s?' % current) ): self.svc.get_view().remove_path(current) # Service class class Filemanager(Service): """the Filemanager service""" options_config = FileManagerOptionsConfig features_config = FilemanagerFeatureConfig events_config = FilemanagerEvents commands_config = FilemanagerCommandsConfig actions_config = FileManagerActionsConfig def pre_start(self): self.path = self.opt('last_browsed') def start(self): self.file_view = FilemanagerView(self) self.emit('browsed_path_changed', path=self.path) self.on_project_switched(None) self.get_action('toolbar_toggle_hidden').set_active( self.opt('show_hidden')) def get_view(self): return self.file_view def browse(self, new_path): new_path = path.abspath(new_path) if new_path == self.path: return else: self.path = new_path self.set_opt('last_browsed', new_path) self.file_view.update_to_path(new_path) self.emit('browsed_path_changed', path=new_path) def go_up(self): dir = path.dirname(self.path) if not dir: dir = "/" #XXX: unportable, what about non-unix self.browse(dir) def check_hidden_regex(self, name, path, state): _re = self.opt('hide_regex') if not re: return True else: return re.match(_re, name) is None def file_lister(self, basepath): for name in listdir(basepath): yield name, basepath, 'normal' def rename_file(self, old, new, basepath): pass def on_project_switched(self, project): self.current_project = project self.get_action('toolbar_projectroot').set_sensitive(project is not None) # Required Service attribute for service loading Service = Filemanager # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filemanager/service.pida0000644000175000017500000000000010652670641017634 0ustar alialiPIDA-0.5.1/pida/services/filemanager/test_filemanager.py0000644000175000017500000000222010652670641021226 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filewatcher/0002755000175000017500000000000010652671501015370 5ustar alialiPIDA-0.5.1/pida/services/filewatcher/__init__.py0000644000175000017500000000222010652670622017476 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filewatcher/filewatcher.py0000644000175000017500000001050110652670622020235 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gobject # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig, OTypeBoolean # locale from pida.core.locale import Locale locale = Locale('filewatcher') _ = locale.gettext try: import gamin have_gamin = True except ImportError: have_gamin = False class FileWatcherOptions(OptionsConfig): def create_options(self): self.create_option( 'enable_gamin', _('Enable Gamin'), OTypeBoolean, False, _('Whether Gamin wil be enabled'), self.on_enabled_changed ) def on_enabled_changed(self, client, id, entry, option): if option.value: self.svc.start_gamin() else: self.svc.stop_gamin() class FilewatcherEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('filemanager', 'browsed_path_changed', self.svc.on_browsed_path_changed) # Service class class Filewatcher(Service): """the File watcher service""" events_config = FilewatcherEvents options_config = FileWatcherOptions def pre_start(self): self.dir = None self.started = False self.gamin = None self.cache = [] if have_gamin: self.gamin = gamin.WatchMonitor() self.gamin.no_exists() def start(self): if self.gamin and self.opt('enable_gamin'): self.start_gamin() def start_gamin(self): if have_gamin: self.started = True gobject.timeout_add(1000, self._period_check) def stop(self): self.stop_gamin() self.gamin = None def stop_gamin(self): self.started = False def on_browsed_path_changed(self, path): self.set_directory(path) def set_directory(self, dir): if not self.gamin: self.dir = dir return # stop watching old directory if self.dir is not None and self.dir != dir: self.gamin.stop_watch(self.dir) # setting new directory self.dir = dir self.gamin.watch_directory(self.dir, self._callback) def _callback(self, name, event): if event == gamin.GAMAcknowledge: return # don't send many time the same event if [name, event] in self.cache: return self.cache.append([name, event]) if event == gamin.GAMChanged or event == gamin.GAMCreated: command = 'update_file' elif event == gamin.GAMDeleted or event == gamin.GAMMoved: command = 'update_removed_file' else: command = None if command: self.boss.cmd('filemanager', command, filename=name, dirname=self.dir) def _period_check(self): if not self.started: return False self.cache = [] self.gamin.handle_events() return True # Required Service attribute for service loading Service = Filewatcher # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/filewatcher/service.pida0000644000175000017500000000000010652670622017656 0ustar alialiPIDA-0.5.1/pida/services/filewatcher/test_filewatcher.py0000644000175000017500000000222010652670622021273 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/grepper/0002755000175000017500000000000010652671501014537 5ustar alialiPIDA-0.5.1/pida/services/grepper/glade/0002755000175000017500000000000010652671501015613 5ustar alialiPIDA-0.5.1/pida/services/grepper/glade/grepper-window.glade0000644000175000017500000001636210652670624021575 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Is this match a Regular Expression RE True False 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER Select A Directory to search in 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK recurse through directories -r True False 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 4 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 3 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-find True False 5 False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_PROGRESS_TOP_TO_BOTTOM False 1 1 PIDA-0.5.1/pida/services/grepper/locale/0002755000175000017500000000000010652671501015776 5ustar alialiPIDA-0.5.1/pida/services/grepper/locale/fr_FR/0002755000175000017500000000000010652671501016774 5ustar alialiPIDA-0.5.1/pida/services/grepper/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020561 5ustar alialiPIDA-0.5.1/pida/services/grepper/locale/fr_FR/LC_MESSAGES/grepper.po0000644000175000017500000000423410652670623022572 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/grepper-window.glade.h:1 msgid "-r" msgstr "-r" #: glade/grepper-window.glade.h:2 msgid "Is this match a Regular Expression" msgstr "Est-ce que ca concorde avec l'expression régulière" #: glade/grepper-window.glade.h:3 msgid "RE" msgstr "RE" #: glade/grepper-window.glade.h:4 msgid "Select A Directory to search in" msgstr "Sélectionner un répertoire de recherche" #: glade/grepper-window.glade.h:6 msgid "recurse through directories" msgstr "Parcourir les sous répertoires" #: grepper.py:96 msgid "Find in files" msgstr "Chercher dans les fichiers" #: grepper.py:97 msgid "Show the grepper view" msgstr "Afficher le chercheur" #: grepper.py:106 msgid "Find word in project" msgstr "Chercher le mot dans le projet" #: grepper.py:107 msgid "Find the current word in the current project" msgstr "Chercher le mot sélectionné dans le projet" #: grepper.py:116 msgid "Find word in document directory" msgstr "Chercher le mot dans le répertoire courant" #: grepper.py:117 msgid "Find the current word in current document directory" msgstr "Chercher le mot courant dans le répetoire courant" #: grepper.py:138 msgid "There is no current document." msgstr "Il n'y a pas de document." #: grepper.py:143 msgid "Find in Files" msgstr "Chercher dans les fichiers" #: grepper.py:202 msgid "Empty search string" msgstr "Recherche vide" #: grepper.py:206 msgid "Path does not exist" msgstr "Le chemin n'existe pas" #: grepper.py:217 #, python-format msgid "Improper regular expression \"%s\"" msgstr "Expression régulière invalide \"%s\"" #: grepper.py:240 msgid "Maximum Results" msgstr "Résultats maximum" #: grepper.py:243 msgid "The maximum number of results to find (approx)." msgstr "Nombre maximum de résultat à chercher (approx)." PIDA-0.5.1/pida/services/grepper/uidef/0002755000175000017500000000000010652671501015633 5ustar alialiPIDA-0.5.1/pida/services/grepper/uidef/grepper.xml0000644000175000017500000000354710652670624020035 0ustar aliali PIDA-0.5.1/pida/services/grepper/TODO0000644000175000017500000000051210652670624015230 0ustar aliali1) get better path data by default. Right now I'm not being smart about this, it just uses the home directory. In the future it should attempt to pull this data from the project directory or the path of the open buffer. 2) format matches data. Right now those columns get too large, and there is not highlighting of matches etc. PIDA-0.5.1/pida/services/grepper/__init__.py0000644000175000017500000000222010652670624016647 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/grepper/grepper.py0000644000175000017500000003324010652670624016562 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os, re, sre_constants, cgi import gtk, gobject from glob import fnmatch from kiwi.ui.objectlist import Column from pida.ui.views import PidaGladeView, PidaView from pida.core.commands import CommandsConfig from pida.core.service import Service from pida.core.events import EventsConfig from pida.core.options import OptionsConfig, OTypeInteger from pida.core.actions import ActionsConfig, TYPE_NORMAL, TYPE_MENUTOOL, TYPE_TOGGLE from pida.utils.gthreads import GeneratorTask, gcall from pida.utils.testing import refresh_gui # locale from pida.core.locale import Locale locale = Locale('grepper') _ = locale.gettext class GrepperItem(object): """ A match item in grepper. Contains the data for the matches path, and linenumber that if falls on, as well as actual line of code that matched, and the matches data. """ def __init__(self, path, linenumber=None, line=None, matches=None): self._path = path self._line = line self._matches = matches self.linenumber = linenumber self.path = self._escape_text(self._path) self.line = self._format_line() def _markup_match(self, text): return ('%s' % self._escape_text(text)) def _escape_text(self, text): return cgi.escape(text) # This has to be quite ugly since we need to markup and split and escape as # we go along. Since once we have escaped things, we won't know where they # are # def _format_line(self): line = self._line line_pieces = [] for match in self._matches: # ignore empty string matches if not match: continue # this should never happen if match not in line: continue # split the line into before the match, and after # leaving the after for searching prematch, line = line.split(match, 1) line_pieces.append(self._escape_text(prematch)) line_pieces.append(self._markup_match(match)) # Append the remainder line_pieces.append(self._escape_text(line)) # strip() the line to give the view more vertical space return ''.join(line_pieces).strip() class GrepperActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_grepper', TYPE_NORMAL, _('Find in files'), _('Show the grepper view'), gtk.STOCK_FIND, self.on_show_grepper, 'g' ) self.create_action( 'grep_current_word', TYPE_NORMAL, _('Find word in project'), _('Find the current word in the current project'), gtk.STOCK_FIND, self.on_grep_current_word, 'question' ) self.create_action( 'grep_current_word_file', TYPE_NORMAL, _('Find word in document directory'), _('Find the current word in current document directory'), gtk.STOCK_FIND, self.on_grep_current_word_file, 'question' ) def on_show_grepper(self, action): self.svc.show_grepper_in_project_source_directory() def on_grep_current_word(self, action): self.svc.grep_current_word() def on_grep_current_word_file(self, action): document = self.svc.boss.cmd('buffer', 'get_current') if document is not None: self.svc.grep_current_word(document.directory) else: self.svc.error_dlg(_('There is no current document.')) class GrepperView(PidaGladeView): gladefile = 'grepper-window' locale = locale label_text = _('Find in Files') icon_name = gtk.STOCK_FIND def create_ui(self): self.grepper_dir = '' self.matches_list.set_columns([ Column('linenumber', editable=False, title="#",), Column('path', editable=False, use_markup=True, sorted=True), Column('line', expand=True, editable=False, use_markup=True), ]) # we should set this to the current project I think self.path_chooser.set_filename(os.path.expanduser('~/')) self.recursive.set_active(True) self.re_check.set_active(True) self.task = GeneratorTask(self.svc.grep, self.append_to_matches_list, self.grep_complete) self.running = False def on_matches_list__row_activated(self, rowitem, grepper_item): self.svc.boss.cmd('buffer', 'open_file', file_name=grepper_item.path) self.svc.boss.editor.cmd('goto_line', line=grepper_item.linenumber + 1) self.svc.boss.editor.cmd('grab_focus') def append_to_matches_list(self, grepper_item): # select the first item (slight hack) select = not len(self.matches_list) self.matches_list.append(grepper_item, select=select) def on_find_button__clicked(self, button): if self.running: self.stop() self.grep_complete() else: self.start_grep() def on_pattern_entry__activate(self, entry): self.start_grep() def _translate_glob(self, glob): return fnmatch.translate(glob).rstrip('$') def set_location(self, location): self.path_chooser.set_filename(location) # setting the location takes a *long* time self._hacky_extra_location = location def start_grep_for_word(self, word): if not word: self.svc.error_dlg(_('Empty search string')) self.close() else: self.pattern_entry.set_text(word) self.start_grep() def start_grep(self): self.matches_list.clear() pattern = self.pattern_entry.get_text() location = self.path_chooser.get_filename() if location is None: location = self._hacky_extra_location recursive = self.recursive.get_active() # needs a patched kiwi self.matches_list.grab_focus() # so do this evil hack for now # TODO: remove this when kiwi patch is accepted! self.matches_list._treeview.grab_focus() # data checking is done here as opposed to in the grep functions # because of threading if not pattern: self.svc.error_dlg(_('Empty search string')) return False if not os.path.exists(location): self.svc.boss.get_window().error_dlg(_('Path does not exist')) return False if not self.re_check.get_active(): pattern = self._translate_glob(pattern) try: regex = re.compile(pattern) except sre_constants.error, e: # More verbose error dialog self.svc.boss.get_window().error_dlg( _('Improper regular expression "%s"') % pattern, str(e)) return False self.grep_started() self.task.start(location, regex, recursive) def can_be_closed(self): self.stop() return True def close(self): self.svc.boss.cmd('window', 'remove_view', view=self) def grep_started(self): self.running = True self.progress_bar.show() gobject.timeout_add(100, self.pulse) self.find_button.set_label(gtk.STOCK_STOP) def grep_complete(self): self.running = False self.find_button.set_label(gtk.STOCK_FIND) self.progress_bar.hide() def pulse(self): self.progress_bar.pulse() return self.running def stop(self): self.task.stop() self.grep_complete() class GrepperCommandsConfig(CommandsConfig): # Are either of these commands necessary? def get_view(self): return self.svc.get_view() def present_view(self): return self.svc.boss.cmd('window', 'present_view', view=self.svc.get_view()) class GrepperOptions(OptionsConfig): def create_options(self): self.create_option( 'maximum_results', _('Maximum Results'), OTypeInteger, 500, _('The maximum number of results to find (approx).'), ) class GrepperEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('project', 'project_switched', self.svc.set_current_project) class Grepper(Service): # format this docstring """ Search text in files. Grepper is a graphical grep tool used for search through the contents of files for a given match or regular expression. """ actions_config = GrepperActionsConfig events_config = GrepperEvents options_config = GrepperOptions BINARY_RE = re.compile(r'[\000-\010\013\014\016-\037\200-\377]|\\x00') def pre_start(self): self.current_project_source_directory = None self._views = [] def show_grepper_in_project_source_directory(self): if self.current_project_source_directory is None: path = os.getcwd() else: path = self.current_project_source_directory return self.show_grepper(path) def show_grepper(self, path): view = GrepperView(self) view.set_location(path) self.boss.cmd('window', 'add_view', paned='Terminal', view=view) self._views.append(view) return view def grep_current_word(self, path=None): if path is None: view = self.show_grepper_in_project_source_directory() else: view = self.show_grepper(path) self.boss.editor.cmd('call_with_current_word', callback=view.start_grep_for_word) def grep(self, top, regex, recursive=False, show_hidden=False): """ grep is a wrapper around _grep_file_list and _grep_file. """ self._result_count = 0 if os.path.isfile(top): file_results = self._grep_file(top, regex) for result in file_results: yield result elif recursive: for root, dirs, files in os.walk(top): # Remove hidden directories if os.path.basename(root).startswith('.') and not show_hidden: del dirs[:] continue for matches in self._grep_file_list(files, root, regex): yield matches else: for matches in self._grep_file_list(os.listdir(top), top, regex): yield matches def _grep_file_list(self, file_list, root, regex, show_hidden=False): """ Grep for a list of files. takes as it's arguments a list of files to grep, the directory containing that list, and a regular expression to search for in them (optionaly whether or not to search hidden files). _grep_file_list itterates over that file list, and calls _grep_file on each of them with the supplied arguments. """ for file in file_list: if self._result_count > self.opt('maximum_results'): break if file.startswith(".") and not show_hidden: continue # never do this, always use os.path.join # filename = "%s/%s" % (root, file,) filename = os.path.join(root, file) file_results = self._grep_file(filename, regex) for result in file_results: yield result def _grep_file(self, filename, regex): """ Grep a file. Takes as it's arguments a full path to a file, and a regular expression to search for. It returns a generator that yields a GrepperItem for each cycle, that contains the path, line number and matches data. """ try: f = open(filename, 'r') for linenumber, line in enumerate(f): if self.BINARY_RE.search(line): break line_matches = regex.findall(line) if len(line_matches): self._result_count += 1 yield GrepperItem(filename, linenumber, line, line_matches) except IOError: pass def set_current_project(self, project): self.current_project_source_directory = project.source_directory #self.set_view_location(project.source_directory) def set_view_location(self, directory): self._view.set_location(directory) def stop(self): for view in self._views: view.stop() Service = Grepper # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/grepper/service.pida0000644000175000017500000000000010652670624017027 0ustar alialiPIDA-0.5.1/pida/services/grepper/test_grepper.py0000644000175000017500000000222010652670624017613 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/help/0002755000175000017500000000000010652671501014023 5ustar alialiPIDA-0.5.1/pida/services/help/locale/0002755000175000017500000000000010652671501015262 5ustar alialiPIDA-0.5.1/pida/services/help/locale/fr_FR/0002755000175000017500000000000010652671501016260 5ustar alialiPIDA-0.5.1/pida/services/help/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020045 5ustar alialiPIDA-0.5.1/pida/services/help/locale/fr_FR/LC_MESSAGES/help.po0000644000175000017500000000106210652670617021341 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../help.py:72 msgid "About" msgstr "A propos de" #: ../../help.py:73 msgid "About PIDA" msgstr "A propos de PIDA" PIDA-0.5.1/pida/services/help/uidef/0002755000175000017500000000000010652671501015117 5ustar alialiPIDA-0.5.1/pida/services/help/uidef/help.xml0000644000175000017500000000116010652670617016574 0ustar aliali PIDA-0.5.1/pida/services/help/__init__.py0000644000175000017500000000222010652670620016127 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/help/help.py0000644000175000017500000000577310652670620015340 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from gtk import gdk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.environment import get_pixmap_path from pida import PIDA_NAME, PIDA_VERSION, PIDA_AUTHORS, PIDA_COPYRIGHT, \ PIDA_LICENSE, PIDA_WEBSITE, PIDA_SHORT_DESCRIPTION # locale from pida.core.locale import Locale locale = Locale('help') _ = locale.gettext class PidaAboutDialog(gtk.AboutDialog): def __init__(self, boss): gtk.AboutDialog.__init__(self) self.set_transient_for(boss.get_window()) self.set_name(PIDA_NAME) self.set_version(PIDA_VERSION) self.set_logo(self._create_logo()) self.set_copyright(PIDA_COPYRIGHT) self.set_license(PIDA_LICENSE) self.set_wrap_license(True) self.set_authors(PIDA_AUTHORS) self.set_website(PIDA_WEBSITE) self.set_comments(PIDA_SHORT_DESCRIPTION) def _create_logo(self): pb = gdk.pixbuf_new_from_file_at_size( get_pixmap_path('pida-icon.svg'), 128, 128) return pb class HelpActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'help_about', TYPE_NORMAL, _('About'), _('About PIDA'), gtk.STOCK_HELP, self.show_about_dialog ) def show_about_dialog(self, action): dialog = PidaAboutDialog(self.svc.boss) resp = dialog.run() dialog.destroy() # Service class class Help(Service): """Describe your Service Here""" actions_config = HelpActionsConfig # Required Service attribute for service loading Service = Help # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/help/service.pida0000644000175000017500000000000010652670620016307 0ustar alialiPIDA-0.5.1/pida/services/help/test_help.py0000644000175000017500000000222010652670620016357 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/manhole/0002755000175000017500000000000010652671501014516 5ustar alialiPIDA-0.5.1/pida/services/manhole/locale/0002755000175000017500000000000010652671501015755 5ustar alialiPIDA-0.5.1/pida/services/manhole/locale/fr_FR/0002755000175000017500000000000010652671501016753 5ustar alialiPIDA-0.5.1/pida/services/manhole/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020540 5ustar alialiPIDA-0.5.1/pida/services/manhole/locale/fr_FR/LC_MESSAGES/manhole.po0000644000175000017500000000140710652670607022531 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../manhole.py:49 msgid "PIDA Internal Shell" msgstr "Shell interne de PIDA" #: ../../manhole.py:50 msgid "Open the PIDA Internal Shell" msgstr "Ouvrir un shell PIDA" #: ../../manhole.py:66 msgid "Debug PIDA" msgstr "Débugger PIDA" #: ../../manhole.py:70 msgid "PIDA Shell. Keep breathing." msgstr "Shell de PIDA. Respirez." PIDA-0.5.1/pida/services/manhole/uidef/0002755000175000017500000000000010652671501015612 5ustar alialiPIDA-0.5.1/pida/services/manhole/uidef/manhole.xml0000644000175000017500000000247210652670607017770 0ustar aliali PIDA-0.5.1/pida/services/manhole/__init__.py0000644000175000017500000000222010652670607016627 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/manhole/manhole.py0000644000175000017500000000626610652670607016531 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaView from pida.utils.pyconsole import Console # locale from pida.core.locale import Locale locale = Locale('manhole') _ = locale.gettext class ManholeActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_manhole', TYPE_TOGGLE, _('PIDA Internal Shell'), _('Open the PIDA Internal Shell'), 'face-monkey', self.on_show_manhole, 'M', ) def on_show_manhole(self, action): if action.get_active(): self.svc.show_manhole() else: self.svc.hide_manhole() class ManholeView(PidaView): icon_name = 'face-monkey' label_text = _('Debug PIDA') def create_ui(self): console = Console(locals=self.svc.get_local_dict(), banner=_("PIDA Shell. Keep breathing."), use_rlcompleter=False) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(console) sw.show_all() self.add_main_widget(sw) def can_be_closed(self): self.svc.get_action('show_manhole').set_active(False) # Service class class Manhole(Service): """Describe your Service Here""" actions_config = ManholeActionsConfig def start(self): self._view = ManholeView(self) def show_manhole(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._view) def hide_manhole(self): self.boss.cmd('window', 'remove_view', view=self._view) def get_local_dict(self): return dict(boss=self.boss) # Required Service attribute for service loading Service = Manhole # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/manhole/service.pida0000644000175000017500000000000010652670607017007 0ustar alialiPIDA-0.5.1/pida/services/manhole/test_manhole.py0000644000175000017500000000222010652670607017552 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/notify/0002755000175000017500000000000010652671501014403 5ustar alialiPIDA-0.5.1/pida/services/notify/locale/0002755000175000017500000000000010652671501015642 5ustar alialiPIDA-0.5.1/pida/services/notify/locale/fr_FR/0002755000175000017500000000000010652671501016640 5ustar alialiPIDA-0.5.1/pida/services/notify/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020425 5ustar alialiPIDA-0.5.1/pida/services/notify/locale/fr_FR/LC_MESSAGES/notify.po0000644000175000017500000000307610652670634022307 0ustar aliali# PIDA. # Copyright (C) The PIDA Team # This file is distributed under the same license as the PIDA package. # Mathieu Virbel , 2007. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-22 12:28+0200\n" "PO-Revision-Date: 2007-05-22 12:28+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: notify.py:72 msgid "Notifications" msgstr "Notifications" #: notify.py:96 msgid "Clear history" msgstr "Effacer l'historique" #: notify.py:134 notify.py:229 msgid "North East" msgstr "Nord Est" #: notify.py:136 notify.py:230 msgid "North West" msgstr "Nord Ouest" #: notify.py:138 notify.py:231 notify.py:234 msgid "South East" msgstr "Sud Est" #: notify.py:140 notify.py:232 msgid "South West" msgstr "Sud Ouest" #: notify.py:209 msgid "Show notifications" msgstr "Afficher les notifications" #: notify.py:212 msgid "Show notifications popup" msgstr "Afficher les popups de notifications" #: notify.py:218 msgid "Timeout" msgstr "Délai" #: notify.py:221 msgid "Timeout before hiding a notification" msgstr "Délai avant de cacher la notification" #: notify.py:227 msgid "Gravity" msgstr "Position" #: notify.py:235 msgid "Position of notifications popup" msgstr "Position de la popup de notification" #: notify.py:255 msgid "Show notification history" msgstr "Afficher l'historique des notifications" #: notify.py:256 msgid "Show the notifications history" msgstr "Afficher l'historique des notifications" PIDA-0.5.1/pida/services/notify/uidef/0002755000175000017500000000000010652671501015477 5ustar alialiPIDA-0.5.1/pida/services/notify/uidef/notify.xml0000644000175000017500000000254310652670635017542 0ustar aliali PIDA-0.5.1/pida/services/notify/__init__.py0000644000175000017500000000222010652670635016515 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/notify/notify.py0000644000175000017500000002436210652670635016301 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # # Some part of code are taken of old Gajim version : # http://trac.gajim.org/browser/trunk/src/tooltips.py # import cgi import gtk, gobject import datetime import locale from kiwi.ui.objectlist import Column, ObjectList from pida.core.environment import get_uidef_path from pida.ui.views import PidaView from pida.core.commands import CommandsConfig from pida.core.service import Service from pida.core.options import OptionsConfig, OTypeInteger, OTypeBoolean, otype_string_options_factory from pida.core.actions import ActionsConfig, TYPE_NORMAL, TYPE_MENUTOOL, TYPE_TOGGLE from pida.ui.buttons import create_mini_button # locale from pida.core.locale import Locale _locale = Locale('notify') _ = _locale.gettext class NotifyItem(object): def __init__(self, data, title, stock, timeout, callback): self.data = cgi.escape(data) self.title = cgi.escape(title) self.stock = stock self.timeout = timeout self.time = datetime.datetime.today().strftime( locale.nl_langinfo(locale.D_T_FMT)) self.callback = callback def get_markup(self): if self.title != '': return '%s\n%s' % (self.title, self.data) return self.data markup = property(get_markup) def cb_clicked(self, w, ev): if self.callback is not None: self.callback(self) class NotifyView(PidaView): label_text = _('Notifications') icon_name = gtk.STOCK_INDEX def create_ui(self): self._hbox = gtk.HBox(spacing=3) self._hbox.set_border_width(6) self.create_list() self.create_toolbar() self.add_main_widget(self._hbox) self._hbox.show_all() def create_list(self): self.notify_list = ObjectList([ Column('stock', use_stock=True), Column('time', sorted=True, order=gtk.SORT_DESCENDING), Column('markup', use_markup=True, expand=True), ]) self.notify_list.set_headers_visible(False) self.notify_list.connect('double-click', self.on_notify_list_click) self._hbox.pack_start(self.notify_list) def create_toolbar(self): self._bar = gtk.VBox(spacing=1) self._clear_button = create_mini_button( gtk.STOCK_DELETE, _('Clear history'), self.on_clear_button) self._bar.pack_start(self._clear_button, expand=False) self._hbox.pack_start(self._bar, expand=False) self._bar.show_all() def on_notify_list_click(self, olist, item): item.cb_clicked(None, None) def on_clear_button(self, w): self.clear() def add_item(self, item): self.notify_list.append(item) def can_be_closed(self): self.svc.get_action('show_notify').set_active(False) def clear(self): self.notify_list.clear() class NotifyPopupView(object): def __init__(self, svc): self.svc = svc self.win = gtk.Window(gtk.WINDOW_POPUP) self.win.set_border_width(3) self.win.set_resizable(False) self.win.set_name('gtk-tooltips') self.win.connect_after('expose_event', self.expose) self.win.connect('size-request', self.on_size_request) self.win.set_events(gtk.gdk.POINTER_MOTION_MASK) self.win.set_transient_for(self.svc.boss.get_window()) self.vbox = gtk.VBox() self.win.add(self.vbox) self.counter = 0 def set_gravity(self, gravity): if gravity == _('North East'): self.win.set_gravity(gtk.gdk.GRAVITY_NORTH_EAST) if gravity == _('North West'): self.win.set_gravity(gtk.gdk.GRAVITY_NORTH_WEST) if gravity == _('South East'): self.win.set_gravity(gtk.gdk.GRAVITY_SOUTH_EAST) if gravity == _('South West'): self.win.set_gravity(gtk.gdk.GRAVITY_SOUTH_WEST) def expose(self, widget, event): style = self.win.get_style() size = self.win.get_size() style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, self.win, 'tooltip', 0, 0, -1, 1) style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, self.win, 'tooltip', 0, size[1] - 1, -1, 1) style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, self.win, 'tooltip', 0, 0, 1, -1) style.paint_flat_box(self.win.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, self.win, 'tooltip', size[0] - 1, 0, 1, -1) return True def on_size_request(self, widget, requisition): width, height = self.win.get_size() gravity = self.win.get_gravity() x = 0 y = 0 if gravity == gtk.gdk.GRAVITY_NORTH_EAST or gravity == gtk.gdk.GRAVITY_SOUTH_EAST: x = gtk.gdk.screen_width() - width if gravity == gtk.gdk.GRAVITY_SOUTH_WEST or gravity == gtk.gdk.GRAVITY_SOUTH_EAST: y = gtk.gdk.screen_height() - height self.win.move(x, y) def add_item(self, item): # create layout eventbox = gtk.EventBox() eventbox.set_events(eventbox.get_events() | gtk.gdk.BUTTON_PRESS_MASK) eventbox.connect('button-press-event', item.cb_clicked) hbox = gtk.HBox() # create stock image = gtk.image_new_from_stock(item.stock, gtk.ICON_SIZE_MENU) image.set_padding(4, 5) hbox.pack_start(image, expand=False) # create markup label = gtk.Label() label.set_alignment(0, 0.5) label.set_padding(10, 5) label.set_markup(item.markup) hbox.pack_start(label) # add item in vbox, and show popup eventbox.add(hbox) self.vbox.pack_start(eventbox) self.win.show_all() # don't remenber to hide him later self.counter += 1 gobject.timeout_add(item.timeout, self._remove_item, eventbox) def _remove_item(self, widget): self.vbox.remove(widget) self.counter -= 1 # hide window if we don't have elements if self.counter == 0: self.win.hide() class NotifyOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'show_notify', _('Show notifications'), OTypeBoolean, True, _('Show notifications popup'), self.on_show_notify ) self.create_option( 'timeout', _('Timeout'), OTypeInteger, 6000, _('Timeout before hiding a notification'), self.on_change_timeout ) self.create_option( 'gravity', _('Gravity'), otype_string_options_factory([ _('North East'), _('North West'), _('South East'), _('South West'), ]), _('South East'), _('Position of notifications popup'), self.on_gravity_change ) def on_show_notify(self, client, id, entry, option): self.svc._show_notify = option.get_value() def on_change_timeout(self, client, id, entry, option): self.svc._timeout = option.get_value() def on_gravity_change(self, client, id, entry, option): self.svc._popup.set_gravity(option.get_value()) class NotifyActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_notify', TYPE_TOGGLE, _('Show notification history'), _('Show the notifications history'), '', self.on_show_notify, '', ) def on_show_notify(self, action): if action.get_active(): self.svc.show_notify() else: self.svc.hide_notify() class NotifyCommandsConfig(CommandsConfig): def notify(self, data, **kw): self.svc.notify(data=data, **kw) class Notify(Service): """ Notify user from something append """ actions_config = NotifyActionsConfig commands_config = NotifyCommandsConfig options_config = NotifyOptionsConfig def start(self): self._view = NotifyView(self) self._popup = NotifyPopupView(self) self._has_loaded = False self._show_notify = self.opt('show_notify') self._timeout = self.opt('timeout') self._popup.set_gravity(self.opt('gravity')) def show_notify(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_notify(self): self.boss.cmd('window', 'remove_view', view=self._view) def add_notify(self, item): self._view.add_item(item) if self._show_notify: self._popup.add_item(item) def notify(self, data, title='', stock=gtk.STOCK_DIALOG_INFO, timeout=-1, callback=None): if timeout == -1: timeout = self._timeout self.add_notify(NotifyItem(data=data, title=title, stock=stock, timeout=timeout, callback=callback)) def stop(self): if self.get_action('show_notify').get_active(): self.hide_notify() Service = Notify # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/notify/service.pida0000644000175000017500000000000010652670635016675 0ustar alialiPIDA-0.5.1/pida/services/notify/test_notify.py0000644000175000017500000000222010652670635017325 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/openwith/0002755000175000017500000000000010652671502014731 5ustar alialiPIDA-0.5.1/pida/services/openwith/glade/0002755000175000017500000000000010652671502016005 5ustar alialiPIDA-0.5.1/pida/services/openwith/glade/openwith-editor.glade0000644000175000017500000003102610652670620022124 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 250 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN 250 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 3 2 6 6 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 2 3 GTK_FILL True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 1 2 GTK_FILL True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Glob: 2 3 GTK_FILL GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Command: 1 2 GTK_FILL GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Name: GTK_FILL GTK_FILL 1 1 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 GTK_BUTTONBOX_START True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-new True True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-delete True 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-close True True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-save True 1 False 1 False 2 PIDA-0.5.1/pida/services/openwith/locale/0002755000175000017500000000000010652671501016167 5ustar alialiPIDA-0.5.1/pida/services/openwith/locale/fr_FR/0002755000175000017500000000000010652671501017165 5ustar alialiPIDA-0.5.1/pida/services/openwith/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502020753 5ustar alialiPIDA-0.5.1/pida/services/openwith/locale/fr_FR/LC_MESSAGES/openwith.po0000644000175000017500000000241510652670620023150 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/openwith-editor.glade.h:1 msgid "Command:" msgstr "Commande:" #: glade/openwith-editor.glade.h:2 msgid "Glob:" msgstr "Glob:" #: glade/openwith-editor.glade.h:3 msgid "Name:" msgstr "Nom:" #: openwith.py:54 msgid "unnamed" msgstr "sans titre" #: openwith.py:72 openwith.py:162 msgid "Open With" msgstr "Ouvrir avec" #: openwith.py:119 #, python-format msgid "Are you sure you want to delete %s" msgstr "Etes-vous sûr de vouloir supprimer %s" #: openwith.py:152 msgid "Configure Open With" msgstr "Configurer Ouvrir avec" #: openwith.py:153 msgid "Show the Open With Editor" msgstr "Afficher l'éditeur Ouvrir avec" #: openwith.py:163 msgid "Open a file with" msgstr "Ouvrir ce fichier avec" #: openwith.py:77 msgid "Name" msgstr "Nom" #: openwith.py:78 msgid "Command" msgstr "Commande" #: openwith.py:78 msgid "Glob" msgstr "Glob" PIDA-0.5.1/pida/services/openwith/uidef/0002755000175000017500000000000010652671502016025 5ustar alialiPIDA-0.5.1/pida/services/openwith/uidef/openwith-file-menu.xml0000644000175000017500000000051010652670621022256 0ustar aliali PIDA-0.5.1/pida/services/openwith/uidef/openwith.xml0000644000175000017500000000261110652670621020403 0ustar aliali PIDA-0.5.1/pida/services/openwith/__init__.py0000644000175000017500000000222010652670621017035 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/openwith/openwith.py0000644000175000017500000002026110652670621017140 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os, glob import gtk from pida.utils.configobj import ConfigObj from kiwi.ui.objectlist import Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView # locale from pida.core.locale import Locale locale = Locale('openwith') _ = locale.gettext class OpenWithItem(object): def __init__(self, section=None): if section is not None: self.name = section['name'] self.command = section['command'] self.glob = section['glob'] else: self.name = _('unnamed') self.command = '' self.glob = '*' def as_dict(self): return dict( name=self.name, command=self.command, glob=self.glob, ) def match(self, file_name): return glob.fnmatch.fnmatch(file_name, self.glob) class OpenWithEditor(PidaGladeView): gladefile = 'openwith-editor' locale = locale icon_name = gtk.STOCK_OPEN label_text = _('Open With') def create_ui(self): self.items_ol.set_columns([ Column('name', title=_('Name')), Column('command', title=_('Command')), Column('glob', title=_('Glob')), ]) self._current = None self._block_changed = False def prefill(self, config): for section in config: item = OpenWithItem(config[section]) self.items_ol.append(item) def set_current(self, item): self._current = item self._block_changed = True if item is None: self.name_entry.set_text('') self.command_entry.set_text('') self.glob_entry.set_text('') self.attrs_table.set_sensitive(False) self.delete_button.set_sensitive(False) else: self.name_entry.set_text(item.name) self.command_entry.set_text(item.command) self.glob_entry.set_text(item.glob) self.attrs_table.set_sensitive(True) self.delete_button.set_sensitive(True) self._block_changed = False def on_new_button__clicked(self, button): new = OpenWithItem() self.items_ol.append(new, select=True) self.save_button.set_sensitive(True) def on_save_button__clicked(self, button): self.svc.save([i for i in self.items_ol]) self.save_button.set_sensitive(False) def on_close_button__clicked(self, button): self.svc.get_action('show_openwith').set_active(False) def on_delete_button__clicked(self, button): if self.svc.boss.get_window().yesno_dlg( _('Are you sure you want to delete %s') % self._current.name): self.items_ol.remove(self._current, select=True) self.save_button.set_sensitive(True) def on_items_ol__selection_changed(self, ol, item): self.set_current(item) def on_name_entry__changed(self, entry): if not self._block_changed: self._current.name = entry.get_text() self.item_changed() def on_command_entry__changed(self, entry): if not self._block_changed: self._current.command = entry.get_text() self.item_changed() def on_glob_entry__changed(self, entry): if not self._block_changed: self._current.glob = entry.get_text() self.item_changed() def item_changed(self): self.save_button.set_sensitive(True) self.items_ol.update(self._current) def can_be_closed(self): self.svc.get_action('show_openwith').set_active(False) class OpenWithActions(ActionsConfig): def create_actions(self): self.create_action( 'show_openwith', TYPE_TOGGLE, _('Configure Open With'), _('Show the Open With Editor'), 'gnome-settings', self.on_show_openwith, '[' ) self.create_action( 'openwith-for-file', TYPE_NORMAL, _('Open With'), _('Open a file with'), gtk.STOCK_OPEN, self.on_openwith_for_file, 'NOACCEL', ) def on_show_openwith(self, action): if action.get_active(): self.svc.show_editor() else: self.svc.hide_editor() def on_openwith_for_file(self, action): menuitem = action.get_proxies()[0] menuitem.remove_submenu() menu = gtk.Menu() menuitem.set_submenu(menu) file_name = action.contexts_kw['file_name'] for item in self.svc.get_items_for_file(file_name): act = gtk.Action(item.name, item.name, item.command, gtk.STOCK_EXECUTE) act.connect('activate', self.on_open_with, file_name, item) mi = act.create_menu_item() menu.append(mi) menu.append(gtk.SeparatorMenuItem()) act = self.svc.get_action('show_openwith') menu.append(act.create_menu_item()) menu.show_all() def on_open_with(self, action, file_name, item): command = item.command % file_name self.svc.boss.cmd('commander', 'execute', commandargs=['bash', '-c', command], title=item.name, icon=gtk.STOCK_OPEN) class OpenWithFeatures(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'openwith-file-menu.xml')) # Service class class Openwith(Service): """Describe your Service Here""" actions_config = OpenWithActions features_config = OpenWithFeatures def pre_start(self): self._filename = os.path.join(self.boss.get_pida_home(), 'openwith.ini') self._config = ConfigObj(self._filename) if not os.path.exists(self._filename): default = self.create_default_item() self._config[default.name] = default.as_dict() self._config.write() self._view = OpenWithEditor(self) self._view.prefill(self._config) def show_editor(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_editor(self): self.boss.cmd('window', 'remove_view', view=self._view) def save(self, items): self._config.clear() for item in items: self._config[item.name] = item.as_dict() self._config.write() def get_items(self): for section in self._config: yield OpenWithItem(self._config[section]) def get_items_for_file(self, file_name): for item in self.get_items(): if item.match(file_name): yield item def create_default_item(self): return OpenWithItem(dict(name="See", glob="*", command="see %s")) # Required Service attribute for service loading Service = Openwith # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/openwith/service.pida0000644000175000017500000000000010652670621017215 0ustar alialiPIDA-0.5.1/pida/services/openwith/test_openwith.py0000644000175000017500000000222010652670621020172 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/optionsmanager/0002755000175000017500000000000010652671502016122 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/glade/0002755000175000017500000000000010652671502017176 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/glade/options-editor.glade0000644000175000017500000000440710652670613023160 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 400 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 1 PIDA-0.5.1/pida/services/optionsmanager/locale/0002755000175000017500000000000010652671501017360 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/locale/fr_FR/0002755000175000017500000000000010652671501020356 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502022144 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/locale/fr_FR/LC_MESSAGES/optionsmanager.po0000644000175000017500000000116210652670612025531 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: optionsmanager.py:145 msgid "Edit Preferences" msgstr "Editer les préférences" #: optionsmanager.py:146 msgid "Edit the PIDA preferences" msgstr "Editer les préférences de PIDA" PIDA-0.5.1/pida/services/optionsmanager/uidef/0002755000175000017500000000000010652671502017216 5ustar alialiPIDA-0.5.1/pida/services/optionsmanager/uidef/optionsmanager.xml0000644000175000017500000000265510652670613022776 0ustar aliali PIDA-0.5.1/pida/services/optionsmanager/__init__.py0000644000175000017500000000222010652670613020227 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/optionsmanager/optionsmanager.py0000644000175000017500000001623310652670613021527 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from textwrap import wrap import gtk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.ui.widgets import get_widget_for_type from kiwi import ValueUnset # locale from pida.core.locale import Locale locale = Locale('optionsmanager') _ = locale.gettext def service_sort_func(s1, s2): return cmp(s1.get_label(), s2.get_label()) def options_sort_func(o, o1): return cmp(o1.name, o2.name) class PidaOptionsView(PidaGladeView): gladefile = 'options-editor' locale = locale label_text = 'Preferences' icon_name = 'gnome-settings' def create_ui(self): self.current = None self.refresh_ui() def clear_ui(self): while self.options_book.get_n_pages(): self.options_book.remove_page(-1) self._services_display = [] self._service_pages = {} def refresh_ui(self): current = self.current self.clear_ui() self._services = [] for svc in self.svc.boss.get_services(): if len(svc.get_options()): self._services.append(svc) self._services_display.append( (svc.get_label(), svc), ) self._services.sort(service_sort_func) self._tips = gtk.Tooltips() self.service_combo.prefill(self._services_display) if current is not None: try: self.service_combo.update(current) except KeyError: self.service_combo.update(self.current) def _add_service(self, svc): self._service_pages[svc.servicename] = self.options_book.get_n_pages() self.options_book.append_page(self._create_page(svc)) self.options_book.show_all() def _create_page(self, svc): mainvb = gtk.VBox(spacing=0) mainvb.set_border_width(6) label = gtk.Label() label.set_markup('%s' % svc.get_label()) label.set_alignment(0, 0.5) mainvb.pack_start(label, expand=False) optvb = gtk.VBox() optsw = gtk.ScrolledWindow() optsw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) optvb.set_border_width(6) optsw.add_with_viewport(optvb) mainvb.pack_start(optsw) labelsizer = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) widgetsizer = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) options = list(svc.get_options().iter_options()) options.sort() for opt in options: vb = gtk.VBox(spacing=2) vb.set_border_width(6) eb = gtk.EventBox() eb.add(vb) optvb.pack_start(eb, expand=False) hb = gtk.HBox(spacing=6) vb.pack_start(hb) optlabel = gtk.Label() optlabel.set_text('\n'.join(wrap(opt.label, 20))) optlabel.set_alignment(0, 0) labelsizer.add_widget(optlabel) hb.pack_start(optlabel, expand=False) optwidget = get_widget_for_type(opt.rtype) widgetsizer.add_widget(optwidget) hb.pack_start(optwidget, expand=True) value = opt.get_value() optwidget.update(value) optwidget.connect('content-changed', self._on_option_changed, opt) opt.add_notify(self._on_option_changed_elsewhere, optwidget) self._tips.set_tip(eb, opt.doc) return mainvb def on_service_combo__content_changed(self, cmb): self.current = svc = self.service_combo.read() if not svc.servicename in self._service_pages: self._add_service(svc) pagenum = self._service_pages[svc.servicename] self.options_book.set_current_page(pagenum) def _on_option_changed(self, widget, option): widgval = widget.read() optval = option.get_value() # various hacks if widgval is None: return if widgval == ValueUnset: widgval = '' if widgval != optval: option.set_value(widgval) def _on_option_changed_elsewhere(self, client, id, entry, (option, widget)): widgval = widget.read() optval = option.get_value() if optval != widgval: widget.update(option.get_value()) def can_be_closed(self): self.svc.get_action('show_options').set_active(False) class OptionsActions(ActionsConfig): def create_actions(self): self.create_action( 'show_options', TYPE_TOGGLE, _('Edit Preferences'), _('Edit the PIDA preferences'), 'properties', self.on_show_options, 'asciitilde' ) def on_show_options(self, action): if action.get_active(): self.svc.show_options() else: self.svc.hide_options() class OptionsEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('plugins', 'plugin_started', self.plugin_changed) self.subscribe_foreign_event('plugins', 'plugin_stopped', self.plugin_changed) def plugin_changed(self, plugin): if len(plugin.get_options()): self.svc.refresh_view() # Service class class Optionsmanager(Service): """Describe your Service Here""" actions_config = OptionsActions events_config = OptionsEvents def start(self): self._view = PidaOptionsView(self) def show_options(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_options(self): self.boss.cmd('window', 'remove_view', view=self._view) def refresh_view(self): self._view.refresh_ui() # Required Service attribute for service loading Service = Optionsmanager # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/optionsmanager/service.pida0000644000175000017500000000000010652670613020407 0ustar alialiPIDA-0.5.1/pida/services/optionsmanager/test_optionsmanager.py0000644000175000017500000000222010652670613022555 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/plugins/0002755000175000017500000000000010652671502014555 5ustar alialiPIDA-0.5.1/pida/services/plugins/glade/0002755000175000017500000000000010652671502015631 5ustar alialiPIDA-0.5.1/pida/services/plugins/glade/plugins-edit.glade0000644000175000017500000000517210652670605021241 0ustar aliali 400 400 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_SPREAD True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-close True False False 10 1 PIDA-0.5.1/pida/services/plugins/glade/plugins-manager.glade0000644000175000017500000006543110652670605021732 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 10 No plugin selected True True PANGO_WRAP_WORD_CHAR False 10 1 75 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 False GTK_WRAP_WORD False False False 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-delete True False 10 False False 10 GTK_PACK_END 3 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Installed plugins tab False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN 1 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True Download available plugins False 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 10 No plugin selected True True PANGO_WRAP_WORD_CHAR False 10 3 75 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 False GTK_WRAP_WORD False False False 4 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-refresh True False 10 True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Install/Upgrade 1 False 10 1 False False 10 GTK_PACK_END 4 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Available plugins tab 1 False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 10 <b>Publish your plugin</b> You can create a package plugin, and upload it on community website : <i>http://pida.co.uk/community/</i> True False False 10 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 3 2 5 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER 1 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 1 2 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 1 2 2 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Plugin directory True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Login 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Password 2 3 False False 10 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-edit True False False 5 False False 5 1 10 2 75 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 False GTK_WRAP_WORD False False False 3 True False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 10 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 gtk-go-up True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 Make package and upload 1 False False 4 GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False False 5 2 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Publish tab 2 False False PIDA-0.5.1/pida/services/plugins/locale/0002755000175000017500000000000010652671501016013 5ustar alialiPIDA-0.5.1/pida/services/plugins/locale/fr_FR/0002755000175000017500000000000010652671501017011 5ustar alialiPIDA-0.5.1/pida/services/plugins/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502020577 5ustar alialiPIDA-0.5.1/pida/services/plugins/locale/fr_FR/LC_MESSAGES/plugins.po0000644000175000017500000000655610652670605022635 0ustar aliali# PIDA. # Copyright (C) The PIDA Team # This file is distributed under the same license as the PIDA package. # Mathieu Virbel , 2007. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-20 23:18+0200\n" "PO-Revision-Date: 2007-05-20 23:18+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/plugins-manager.glade.h:1 msgid "" "Publish your plugin\n" "\n" "You can create a package plugin, \n" "and upload it on community website : \n" "http://pida.co.uk/community/" msgstr "Publier votre plugin\n" "\n" "Vous pouvez créer votre paquet, \n" "et le mettre à disposition sur le \n" "site web communautaire : \n" "http://pida.co.uk/community" #: glade/plugins-manager.glade.h:6 msgid "Available plugins" msgstr "Plugins disponibles" #: glade/plugins-manager.glade.h:7 plugins.py:436 msgid "Download available plugins" msgstr "Télécharger les plugins disponibles" #: glade/plugins-manager.glade.h:8 msgid "Install/Upgrade" msgstr "Installer" #: glade/plugins-manager.glade.h:9 msgid "Installed plugins" msgstr "Plugins installés" #: glade/plugins-manager.glade.h:10 msgid "Login" msgstr "Identifiant" #: glade/plugins-manager.glade.h:11 msgid "Make package and upload" msgstr "Construire le paquet et le publier" #: glade/plugins-manager.glade.h:12 plugins.py:210 plugins.py:226 msgid "No plugin selected" msgstr "Aucun plugin sélectionné" #: glade/plugins-manager.glade.h:13 msgid "Password" msgstr "Mot de passe" #: glade/plugins-manager.glade.h:14 msgid "Plugin directory" msgstr "Répertoire du plugin" #: glade/plugins-manager.glade.h:15 msgid "Publish" msgstr "Publier" #: plugins.py:111 msgid "Edit a plugin" msgstr "Editer le plugin" #: plugins.py:116 plugins.py:128 msgid "Name" msgstr "Nom" #: plugins.py:117 msgid "Value" msgstr "Valeur" #: plugins.py:130 msgid "Plugin long name" msgstr "Nom (long) du plugin" #: plugins.py:132 plugins.py:596 msgid "Author" msgstr "Auteur" #: plugins.py:134 plugins.py:176 plugins.py:593 msgid "Version" msgstr "Version" #: plugins.py:136 plugins.py:602 msgid "Depends" msgstr "Dépendances" #: plugins.py:138 msgid "Require PIDA version" msgstr "Version requise de PIDA" #: plugins.py:139 plugins.py:599 msgid "Category" msgstr "Catégorie" #: plugins.py:141 msgid "Description" msgstr "Description" #: plugins.py:158 plugins.py:329 msgid "Plugins manager" msgstr "Gestionnaire de plugins" #: plugins.py:168 plugins.py:174 msgid "Plugin" msgstr "Plugin" #: plugins.py:170 msgid "Enabled" msgstr "Activer" #: plugins.py:330 msgid "Show the plugins manager" msgstr "Afficher le gestionnaire de plugin" #: plugins.py:363 msgid "Start plugin list" msgstr "Liste des plugins à démarrer" #: plugins.py:364 msgid "List of plugin to start" msgstr "Liste des plugins à démarrer" #: plugins.py:456 #, python-format msgid "Download %s" msgstr "Télécharger %s" #: plugins.py:498 #, python-format msgid "Are you sure to delete \"%s\" plugin ?" msgstr "Etes-vous sûr de vouloir supprimer le plugin \"%s\" ?" #: plugins.py:547 msgid "Community response : " msgstr "Réponse du site web : " #: plugins.py:549 msgid "Error while posting plugin : " msgstr "Error à la publication : " #: plugins.py:605 msgid "Require PIDA" msgstr "Version de PIDA" PIDA-0.5.1/pida/services/plugins/uidef/0002755000175000017500000000000010652671502015651 5ustar alialiPIDA-0.5.1/pida/services/plugins/uidef/plugins.xml0000644000175000017500000000246010652670605020057 0ustar aliali PIDA-0.5.1/pida/services/plugins/__init__.py0000644000175000017500000000222010652670605016663 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/plugins/plugins.py0000644000175000017500000006021610652670605016616 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk import xmlrpclib import cgi import gobject import tarfile import os import base64 import shutil from kiwi.ui.objectlist import Column from pida import PIDA_VERSION from pida.ui.views import PidaGladeView from pida.core.commands import CommandsConfig from pida.core.service import Service from pida.core.events import EventsConfig from pida.core.options import OptionsConfig, OTypeBoolean from pida.core.actions import ActionsConfig, TYPE_NORMAL, TYPE_MENUTOOL, TYPE_TOGGLE from pida.utils.gthreads import GeneratorTask, AsyncTask, gcall from pida.core.servicemanager import ServiceLoader, ServiceLoadingError from pida.core.options import OptionItem, manager, OTypeStringList, OTypeString from pida.utils.web import fetch_url from pida.utils.configobj import ConfigObj from pida.utils.path import walktree # consts PLUGIN_RPC_URL = 'http://pida.co.uk/RPC2' # locale from pida.core.locale import Locale locale = Locale('plugins') _ = locale.gettext def get_value(tab, key): if not tab.has_key(key): return '' return tab[key] class PluginsItem(object): def __init__(self, infos, directory=None, enabled=False, isnew=False): self.isnew = isnew self.plugin = get_value(infos, 'plugin') self.require_pida = get_value(infos, 'require_pida') self.name = get_value(infos, 'name') self.author = get_value(infos, 'author') self.version = get_value(infos, 'version') self.description = get_value(infos, 'description') self.category = get_value(infos, 'category') self.url = get_value(infos, 'url') self.depends = get_value(infos, 'depends') self.directory = directory self.enabled = enabled def get_markup(self): if self.isnew: return '!N %s' % self.name return self.name markup = property(get_markup) class PluginsEditItem(object): def __init__(self, key, name, value): self.key = key self.name = name self.value = value class PluginsEditView(PidaGladeView): gladefile = 'plugins-edit' locale = locale label_text = _('Edit a plugin') icon_name = gtk.STOCK_EXECUTE def create_ui(self): self.attr_list.set_columns([ Column('name', title=_('Name'), data_type=str), Column('value', title=_('Value'), data_type=str, editable=True, expand=True), ]) def set_item(self, item): self.item = item if item is None: self.attr_list.clear() return list = [] list.append(PluginsEditItem('plugin', _('Name'), item.plugin)) list.append(PluginsEditItem('name', _('Plugin long name'), item.name)) list.append(PluginsEditItem('author', _('Author'), item.author)) list.append(PluginsEditItem('version', _('Version'), item.version)) list.append(PluginsEditItem('depends', _('Depends'), item.depends)) list.append(PluginsEditItem('require_pida', _('Require PIDA version'), item.require_pida)) list.append(PluginsEditItem('category', _('Category'), item.category)) list.append(PluginsEditItem('description', _('Description'), item.description)) self.attr_list.add_list(list, clear=True) def on_attr_list__cell_edited(self, w, item, value): setattr(self.item, getattr(item, 'key'), getattr(item, 'value')) self.svc._view.update_publish_infos() self.svc.write_informations(self.item) def on_close_button__clicked(self, w): self.svc.hide_plugins_edit() class PluginsView(PidaGladeView): gladefile = 'plugins-manager' locale = locale label_text = _('Plugins manager') icon_name = gtk.STOCK_EXECUTE def create_ui(self): self._current = None self.item = None self.installed_item = None self.plugins_dir = '' self.first_start = True self.installed_list.set_columns([ Column('name', title=_('Plugin'), sorted=True, data_type=str, expand=True), Column('enabled', title=_('Enabled'), data_type=bool, editable=True) ]) self.available_list.set_columns([ Column('markup', title=_('Plugin'), sorted=True, data_type=str, expand=True, use_markup=True), Column('version', title=_('Version'), data_type=str), ]) def can_be_closed(self): self.svc.get_action('show_plugins').set_active(False) def clear_installed(self): self.installed_list.clear() def add_installed(self, item): self.installed_list.append(item) def clear_available(self): self.available_list.clear() def add_available(self, item): self.available_list.append(item) def on_available_refresh_button__clicked(self, w): self.svc.fetch_available_plugins() def after_notebook__switch_page(self, notebook, pointer, index): if index == 1: if self.first_start: self.first_start = False gcall(self.svc.fetch_available_plugins) else: self.svc.update_installed_plugins() def on_available_list__selection_changed(self, ot, item): self._current = item # no item, clear fields if item is None: self.available_title.set_text(_('No plugin selected')) self.available_description.get_buffer().set_text('') self.available_install_button.set_sensitive(False) return # fill fields markup = self.svc._get_item_markup(item) self.available_title.set_markup(markup) self.available_description.get_buffer().set_text(item.description) self.available_install_button.set_sensitive(True) def on_installed_list__selection_changed(self, ot, item): self.installed_item = item # no item, clear fields if item is None: self.installed_title.set_text(_('No plugin selected')) self.installed_description.get_buffer().set_text('') self.installed_delete_button.set_sensitive(False) return # fill fields markup = self.svc._get_item_markup(item) self.installed_title.set_markup(markup) self.installed_description.get_buffer().set_text(item.description) self.installed_delete_button.set_sensitive(True) def on_publish_directory__selection_changed(self, w): directory = self.publish_directory.get_filename() if self.svc.is_plugin_directory(directory): self.item = self.svc.read_plugin_informations(directory) self.item.directory = directory self.publish_button.set_sensitive(True) self.publish_edit_button.set_sensitive(True) self.svc._viewedit.set_item(self.item) self.update_publish_infos() else: self.item = None self.publish_button.set_sensitive(False) self.publish_edit_button.set_sensitive(False) self.svc._viewedit.set_item(None) self.update_publish_infos() def on_available_install_button__clicked(self, w): if not self._current: return self.svc.download(self._current) def on_installed_list__cell_edited(self, w, item, value): if value != 'enabled': return if not item.directory: return if item.enabled: success = self.svc.start_plugin(item.directory) item.enabled = success else: self.svc.stop_plugin(item.plugin) self.svc.save_running_plugin() def on_publish_button__clicked(self, w): directory = self.publish_directory.get_filename() login = self.publish_login.get_text() password = self.publish_password.get_text() self.svc.upload(directory, login, password) def on_installed_delete_button__clicked(self, w): self.svc.delete(self.installed_item) def on_publish_edit_button__clicked(self, w): self.svc.show_plugins_edit() def update_publish_infos(self): if self.item is None: self.publish_infos.set_text('') self.publish_description.get_buffer().set_text('') return self.publish_infos.set_markup(self.svc._get_item_markup(self.item)) self.publish_description.get_buffer().set_text(self.item.description) def start_pulse(self, title): self._pulsing = True self.available_progress.set_text(title) self.available_progress.show_all() self.available_refresh_button.set_sensitive(False) gobject.timeout_add(100, self._pulse) def stop_pulse(self): self.available_progress.hide() self.available_refresh_button.set_sensitive(True) self._pulsing = False def _pulse(self): self.available_progress.pulse() return self._pulsing def start_publish_pulse(self, title): self._publish_pulsing = True self.publish_progress.set_text(title) self.publish_progress.show_all() self.publish_button.set_sensitive(False) self.publish_edit_button.set_sensitive(False) gobject.timeout_add(100, self._publish_pulse) def stop_publish_pulse(self): self.publish_progress.hide() self.publish_button.set_sensitive(True) self.publish_edit_button.set_sensitive(True) self._publish_pulsing = False def _publish_pulse(self): self.publish_progress.pulse() return self._publish_pulsing class PluginsActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_plugins', TYPE_TOGGLE, _('Plugins manager'), _('Show the plugins manager'), gtk.STOCK_EXECUTE, self.on_show_plugins, '' ) def on_show_plugins(self, action): if action.get_active(): self.svc.show_plugins() else: self.svc.hide_plugins() class PluginsCommandsConfig(CommandsConfig): # Are either of these commands necessary? def get_view(self): return self.svc.get_view() def present_view(self): return self.svc.boss.cmd('window', 'present_view', view=self.svc.get_view()) class PluginsOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'rpc_url', _('Webservice Url'), OTypeString, PLUGIN_RPC_URL, _('URL of Webservice to download plugins'), self.on_rpc_url) self.create_option( 'check_for_updates', _('Check updates'), OTypeBoolean, True, _('Check for plugins updates in background'), self.on_check_for_updates) def on_rpc_url(self, client, id, entry, option): self.svc.rpc_url = option.get_value() def on_check_for_updates(self, client, id, entry, option): self.svc.check_for_updates(option.get_value()) class PluginsEvents(EventsConfig): def create_events(self): self.create_event('plugin_started') self.create_event('plugin_stopped') class Plugins(Service): """ Plugins manager service """ actions_config = PluginsActionsConfig options_config = PluginsOptionsConfig events_config = PluginsEvents rpc_url = PLUGIN_RPC_URL def pre_start(self): self._check = False self._check_notify = False self._check_event = False self._loader = ServiceLoader(self.boss) self._view = PluginsView(self) self._viewedit = PluginsEditView(self) self.task = None self.plugin_path = self.boss._env.get_plugins_directory() self._start_list = OptionItem('plugins', 'start_list', _('Start plugin list'), OTypeStringList, [], _('List of plugin to start'), None) manager.register_option(self._start_list) def start(self): self.rpc_url = self.opt('rpc_url') self.update_installed_plugins(start=True) self.check_for_updates(self.get_option('check_for_updates').get_value()) def show_plugins(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) self.update_installed_plugins() def start_plugin(self, plugin_path): try: plugin = self.boss.start_plugin(plugin_path) self.emit('plugin_started', plugin=plugin) self.boss.cmd('notify', 'notify', title=_('Plugins'), data = _('Started %(plugin)s plugin' % {'plugin':plugin.get_label()})) return True except ServiceLoadingError, e: self.boss.cmd('notify', 'notify', title=_('Plugins'), data = _('Could not start plugin: %(plugin_path)s\n%(error)s' % {'error':str(e), 'plugin_path':plugin_path})) return False def stop_plugin(self, plugin_name): plugin = self.boss.stop_plugin(plugin_name) self.emit('plugin_stopped', plugin=plugin) self.boss.cmd('notify', 'notify', title=_('Plugins'), data = _('Stopped %(plugin)s plugin' % {'plugin':plugin.get_label()})) def hide_plugins(self): self.boss.cmd('window', 'remove_view', view=self._view) def show_plugins_edit(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._viewedit) def hide_plugins_edit(self): self.boss.cmd('window', 'remove_view', view=self._viewedit) def update_installed_plugins(self, start=False): self._view.clear_installed() l_installed = list(self._loader.get_all_service_files([self.plugin_path])) if start: start_list = manager.get_value(self._start_list) running_list = [plugin.servicename for plugin in self.boss.get_plugins()] loading_errors = [] for service_name, service_file in l_installed: # read config plugin_item = self.read_plugin_informations( servicefile=service_file) if plugin_item.plugin in running_list: plugin_item.enabled = True # start mode if start: if service_name not in start_list: continue plugin_path = os.path.dirname(service_file) try: plugin = self.boss.start_plugin(plugin_path) self.emit('plugin_started', plugin=plugin) plugin_item.enabled = True except ServiceLoadingError, e: self.log_error(e) else: self._view.add_installed(plugin_item) def fetch_available_plugins(self): if self.task: self.task.stop() def add_in_list(list, isnew): if isnew and self._check_notify: self.boss.cmd('notify', 'notify', title=_('Plugins'), data=_('Version %(version)s of %(plugin)s is available !') \ % {'version':list['version'], 'plugin':list['plugin']}) self._view.add_available(PluginsItem(list, isnew=isnew)) def stop_pulse(): self._check_notify = False self._view.stop_pulse() self._view.clear_available() self.task = GeneratorTask(self._fetch_available_plugins, add_in_list, stop_pulse) self.task.start() def _fetch_available_plugins(self): # get installed items l_installed = list(self._loader.get_all_service_files([self.plugin_path])) installed_list = [] for service_name, service_file in l_installed: plugin_item = self.read_plugin_informations( servicefile=service_file) installed_list.append(plugin_item) self._view.start_pulse(_('Download available plugins')) try: proxy = xmlrpclib.ServerProxy(self.rpc_url) plist = proxy.plugins.list({'version': PIDA_VERSION}) for k in plist: item = plist[k] inst = None isnew = False for plugin in installed_list: if plugin.plugin == item['plugin']: inst = plugin if inst is not None: isnew = (inst.version != item['version']) yield item, isnew except: pass def download(self, item): if not item.url or item.url == '': return self._view.start_pulse(_('Download %s') % item.name) def download_complete(url, content): self._view.stop_pulse() if content != '': self.install(item, content) fetch_url(item.url, download_complete) def install(self, item, content): # write plugin plugin_path = os.path.join(self.plugin_path, item.plugin) filename = os.path.join(self.plugin_path, os.path.basename(item.url)) file = open(filename, 'wb') file.write(content) file.close() # check if we need to stop and remove him l_installed = [p[0] for p in self._loader.get_all_service_files([self.plugin_path])] item.directory = plugin_path if item.plugin in l_installed: self.delete(item, force=True) # extract him tar = tarfile.open(filename, 'r:gz') for tarinfo in tar: tar.extract(tarinfo, path=self.plugin_path) tar.close() os.unlink(filename) # start service self.start_plugin(plugin_path) self.boss.cmd('notify', 'notify', title=_('Plugins'), data=_('Installation of %s completed') % item.plugin) def delete(self, item, force=False): if not item: return if not item.directory: return if not os.path.exists(item.directory): return if not force: if not self.boss.get_window().yesno_dlg( _('Are you sure to delete "%s" plugin ?' % item.name)): return running_list = [plugin.servicename for plugin in self.boss.get_plugins()] if item.plugin in running_list: self.stop_plugin(item.plugin) shutil.rmtree(item.directory, True) self.update_installed_plugins() def upload(self, directory, login, password): # first, check for a service.pida file if not self.is_plugin_directory(directory): return # extract plugin name plugin = os.path.basename(directory) # get filelist self._view.start_publish_pulse('Listing files') skipped_directory = [ '.svn', 'CVS' ] list = [] for top, names in walktree(top=directory, skipped_directory=skipped_directory): list.append(top) for name in names: list.append(os.path.join(top, name)) # remove some unattended files skipped_extentions = [ 'swp', 'pyc' ] list = [ name for name in list if name.split('.')[-1] not in skipped_extentions ] # make tarfile self._view.start_publish_pulse('Building package') filename = os.tmpnam() tar = tarfile.open(filename, 'w:gz') for name in list: arcname = plugin + name[len(directory):] tar.add(name, arcname=arcname, recursive=False) tar.close() def upload_do(login, password, plugin, filename): try: try: file = open(filename, 'rb') data = file.read() file.close() proxy = xmlrpclib.ServerProxy(self.rpc_url) code = proxy.plugins.push(login, password, plugin, base64.b64encode(data)) gcall(self.boss.cmd, 'notify', 'notify', title=_('Plugins'), data=_('Package upload success !')) except xmlrpclib.Fault, fault: print _('Error while posting plugin : '), fault except: pass finally: os.unlink(filename) self._view.stop_publish_pulse() self._view.start_publish_pulse('Upload to community website') task = AsyncTask(upload_do) task.start(login, password, plugin, filename) def ensure_view_visible(self): action = self.get_action('show_plugins') if not action.get_active(): action.set_active(True) self.boss.cmd('window', 'present_view', view=self._view) def is_plugin_directory(self, directory): return os.path.exists(os.path.join(directory, 'service.pida')) def read_plugin_informations(self, directory=None, servicefile=None): if servicefile is None: servicefile = os.path.join(directory, 'service.pida') config = ConfigObj(servicefile) return PluginsItem(config['plugin'], directory=os.path.dirname(servicefile)) def write_informations(self, item): if not item.directory: return config = ConfigObj(os.path.join(item.directory, 'service.pida')) section = config['plugin'] for key in [ 'plugin', 'name', 'author', 'version', 'require_pida', 'depends', 'category', 'description' ]: section[key] = getattr(item, key) config.write() def save_running_plugin(self): list = [plugin.servicename for plugin in self.boss.get_plugins()] manager.set_value(self._start_list, list) def _get_item_markup(self, item): markup = '%s' % cgi.escape(item.name) if item.version != '': markup += '\n%s : %s' % (_('Version'), cgi.escape(item.version)) if item.author != '': markup += '\n%s : %s' % (_('Author'), cgi.escape(item.author)) if item.category != '': markup += '\n%s : %s' % (_('Category'), cgi.escape(item.category)) if item.depends != '': markup += '\n%s : %s' % (_('Depends'), cgi.escape(item.depends)) if item.require_pida != '': markup += '\n%s : %s' % (_('Require PIDA'), cgi.escape(item.require_pida)) return markup def check_for_updates(self, check): # already activated, skip if self._check and check: return # disabled: if self._check and not check: self._check = check return # enable if not self._check and check: self._check = check # check now self._check_for_updates() return def _check_for_updates(self): self._check_event = False if not self._check: return self._check_notify = True self.fetch_available_plugins() # relaunch event in 30 minutes if not self._check_event: gobject.timeout_add(30 * 60 * 1000, self._check_for_updates) self._check_event = True Service = Plugins # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/plugins/service.pida0000644000175000017500000000000010652670605017043 0ustar alialiPIDA-0.5.1/pida/services/plugins/test_plugins.py0000644000175000017500000000222010652670605017644 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/project/0002755000175000017500000000000010652671502014542 5ustar alialiPIDA-0.5.1/pida/services/project/glade/0002755000175000017500000000000010652671502015616 5ustar alialiPIDA-0.5.1/pida/services/project/glade/project-properties.glade0000644000175000017500000002714510652670627022472 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 400 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK <b>Project Properties</b> True False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True False 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Project Name: False True False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-edit True False 2 False 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Add Controller: False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 False 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Controller Name: False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 False 4 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 3 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-add True False True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-delete True False 1 False 5 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN 1 6 PIDA-0.5.1/pida/services/project/glade/project_list.glade0000644000175000017500000000242710652670627021327 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN PIDA-0.5.1/pida/services/project/locale/0002755000175000017500000000000010652671501016000 5ustar alialiPIDA-0.5.1/pida/services/project/locale/fr_FR/0002755000175000017500000000000010652671501016776 5ustar alialiPIDA-0.5.1/pida/services/project/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502020564 5ustar alialiPIDA-0.5.1/pida/services/project/locale/fr_FR/LC_MESSAGES/project.po0000644000175000017500000000736610652670626022612 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/project-properties.glade.h:1 #, fuzzy msgid "Project Properties" msgstr "Propriétés du projets" #: glade/project-properties.glade.h:2 #, fuzzy msgid "Add Controller" msgstr "Contrôleurs" #: glade/project-properties.glade.h:3 project.py:139 msgid "Name" msgstr "Nom" #: project.py:78 msgid "Generic Execution" msgstr "Execution générique" #: project.py:81 msgid "Execution Command" msgstr "Commande d'éxecution" #: project.py:88 msgid "Controller has no command set" msgstr "Le contrôleur n'a pas de commande" #: project.py:103 msgid "Projects" msgstr "Projets" #: project.py:129 project.py:233 msgid "Project Properties" msgstr "Propriétés du projets" #: project.py:135 msgid "Controllers" msgstr "Contrôleurs" #: project.py:136 msgid "Defaut" msgstr "Défaut" #: project.py:140 msgid "Value" msgstr "Valeur" #: project.py:168 msgid "Please enter a controller name" msgstr "Veuillez indiquer un nom pour le contrôleur" #: project.py:173 #, python-format msgid "This project already has a controller named %s" msgstr "Ce project a déjà un contrôleur nommé %s" #: project.py:185 #, python-format msgid "Are you sure you want to delete controller \"%s\" from this project?" msgstr "Etes-vous sur de vouloir supprimer le contrôleur \"%s\" de ce projet ?" #: project.py:206 msgid "Add Project" msgstr "Ajouter un projet" #: project.py:207 msgid "Adds a new project" msgstr "Ajouter un nouveau projet" #: project.py:215 msgid "Execute Default" msgstr "Exécuter Défaut" #: project.py:216 msgid "Execute the project" msgstr "Exécuter le projet" #: project.py:224 msgid "Remove from workspace" msgstr "Supprimer de l'espace de travail" #: project.py:225 msgid "Remove the current project from the workspace" msgstr "Supprimer le projet courant de l'espace de travail" #: project.py:234 msgid "Show the project property editor" msgstr "Afficher les propriétés du projet" #: project.py:242 msgid "Execution Controllers" msgstr "Exécuter les contrôleurs" #: project.py:243 msgid "Configurations with which to execute the project" msgstr "Configurer avec lequel le projet est exécuté" #: project.py:254 msgid "Select a directory to add" msgstr "Sélectionné le répertoire à ajouter" #: project.py:269 msgid "This project has no controllers" msgstr "Ce projet n'a pas de contrôleurs" #: project.py:293 msgid "Project Directories" msgstr "Répertoire du projet" #: project.py:296 msgid "The current directories in the workspace" msgstr "Le répertoire courant dans l'espace de travail" #: project.py:301 msgid "Last Project" msgstr "Dernier projet" #: project.py:304 msgid "The last project selected. " msgstr "Le dernier projet sélectionné. " #: project.py:305 msgid "(Do not change this unless you know what you are doing)" msgstr "(Ne pas changez sauf si vous savez ce que vous faites)" #: project.py:365 #, python-format msgid "Project path %s has disappeared" msgstr "Le chemin du projet %s a disparu" #: project.py:383 msgid "The directory does not contain a project file, " msgstr "Le chemin ne contient aucun fichier de projet" #: project.py:384 msgid "do you want to create one?" msgstr "voulez vous en créer un ?" #: project.py:434 #, python-format msgid "Are you sure you want to remove project \"%s\" from the workspace?" msgstr "" "Etes-vous sûr de vouloir supprimer le projet \"%s\" de l'espace de travail ?" PIDA-0.5.1/pida/services/project/uidef/0002755000175000017500000000000010652671502015636 5ustar alialiPIDA-0.5.1/pida/services/project/uidef/project.xml0000644000175000017500000000373210652670627020040 0ustar aliali PIDA-0.5.1/pida/services/project/__init__.py0000644000175000017500000000222010652670630016646 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/project/project.py0000644000175000017500000004726710652670630016601 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk from pida.utils.configobj import ConfigObj from kiwi.ui.objectlist import Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.options import OptionsConfig, OTypeStringList, OTypeFile from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig, TYPE_NORMAL, TYPE_MENUTOOL, \ TYPE_TOGGLE from pida.core.interfaces import IProjectController from pida.core.projects import ProjectControllerMananger, ProjectController, \ ProjectKeyDefinition from pida.ui.views import PidaGladeView from pida.ui.objectlist import AttrSortCombo # locale from pida.core.locale import Locale locale = Locale('project') _ = locale.gettext def open_directory_dialog(parent, title, folder=''): filechooser = gtk.FileChooserDialog(title, parent, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) filechooser.set_default_response(gtk.RESPONSE_OK) if folder: filechooser.set_current_folder(folder) response = filechooser.run() if response != gtk.RESPONSE_OK: filechooser.destroy() return path = filechooser.get_filename() if path and os.access(path, os.R_OK): filechooser.destroy() return path # Some generic project controllers class GenericExecutionController(ProjectController): name = 'GENERIC_EXECUTION' label = _('Generic Execution') attributes = [ ProjectKeyDefinition('command', _('Execution Command'), True), ] + ProjectController.attributes def execute(self): command = self.get_option('command') if not command: self.boss.get_window().error_dlg( _('Controller has no command set') ) return self.execute_commandline( command, ) PROJECT_LIST_COLUMNS = [ Column('markup', use_markup=True) ] class ProjectListView(PidaGladeView): gladefile = 'project_list' locale = locale label_text = _('Projects') icon_name = 'package_utilities' def create_ui(self): self.project_ol.set_headers_visible(False) self.project_ol.set_columns(PROJECT_LIST_COLUMNS) self._sort_combo = AttrSortCombo(self.project_ol, [ ('display_name', 'Name'), ('source_directory', 'Full Path'), ('name', 'Directory Name'), ], 'display_name' ) self._sort_combo.show() self.main_vbox.pack_start(self._sort_combo, expand=False) def on_project_ol__selection_changed(self, ol, project): self.svc.set_current_project(project) def on_project_ol__double_click(self, ol, project): self.svc.boss.cmd('filemanager', 'browse', new_path=project.source_directory) self.svc.boss.cmd('filemanager', 'present_view') def on_project_ol__right_click(self, ol, project, event): self.svc.boss.cmd('contexts', 'popup_menu', context='dir-menu', dir_name=project.source_directory, event=event) def set_current_project(self, project): self.project_ol.select(project) def update_project(self, project): self.project_ol.update(project) class ProjectPropertiesView(PidaGladeView): gladefile = 'project-properties' locale = locale label_text = _('Project Properties') icon_name = 'package_utilities' def create_ui(self): self.controllers_list.set_columns([ Column('markup', use_markup=True, expand=True, title=_('Controllers')), Column('default', radio=True, data_type=bool, editable=True, title=_('Defaut')) ]) self.items_list.set_columns([ Column('label', title=_('Name'), expand=True, use_markup=True), Column('value', title=_('Value'), editable=True, expand=True), ]) sg = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) sg.add_widget(self.project_name_label) sg.add_widget(self.name_label) sg.add_widget(self.add_controller_label) self._project = None def set_project(self, project): self._project = project self.controllers_list.clear() self.project_label.set_text('') self.project_name_entry.set_text('') if self._project is not None: self.update_project_label() self.project_name_entry.set_text(self._project.get_display_name()) for controller in self._project.controllers: self.controllers_list.append(controller) def update_project_label(self): self.project_label.set_markup(self._project.source_directory) def on_controllers_list__selection_changed(self, ol, controller): self.items_list.clear() if controller is not None: for item in controller.create_key_items(): self.items_list.append(item) self.delete_button.set_sensitive(controller is not None) def set_controllers(self, controllers): self.controllers_combo.prefill([(controller.label, controller) for controller in controllers]) def on_add_button__clicked(self, button): name = self.name_entry.get_text() if not name: self.svc.boss.get_window().error_dlg( _('Please enter a controller name')) return for controller in self._project.controllers: if controller.config_section == name: self.svc.boss.get_window().error_dlg( _('This project already has a controller named %s') % name) return self.name_entry.set_text('') controller_type = self.controllers_combo.read() controller = self._project.add_controller(controller_type, name) self.controllers_list.append(controller, select=True) self.svc.set_current_project(self._project) def on_delete_button__clicked(self, button): controller = self.controllers_list.get_selected() if controller is not None: if self.svc.boss.get_window().yesno_dlg( _('Are you sure you want to delete controller "%s" from this project?') % controller.config_section): self.controllers_list.remove(controller) self._project.remove_controller(controller) self.svc.set_current_project(self._project) def on_name_edit_button__clicked(self, button): if self._project is None: return if button.get_label() == gtk.STOCK_EDIT: self.project_name_entry.set_sensitive(True) button.set_label(gtk.STOCK_SAVE) self.project_name_entry.grab_focus() else: display_name = self.project_name_entry.get_text() if display_name: self._project.set_display_name(display_name) self.update_project_label() self.svc.project_list.update_project(self._project) self.project_name_entry.set_sensitive(False) button.set_label(gtk.STOCK_EDIT) else: self.project_name_entry.set_text(self._project.get_display_name()) self.project_name_entry.grab_focus() self.svc.error_dlg(_('Do not set empty project names')) def can_be_closed(self): self.svc.get_action('project_properties').set_active(False) class ProjectEventsConfig(EventsConfig): def create_events(self): self.create_event('project_switched') def subscribe_foreign_events(self): self.subscribe_foreign_event('plugins', 'plugin_started', self.plugin_started) self.subscribe_foreign_event('plugins', 'plugin_stopped', self.plugin_stopped) def plugin_started(self, plugin): if plugin.has_foreign_feature('project', IProjectController): self.svc.refresh_controllers() def plugin_stopped(self, plugin): self.svc.refresh_controllers() class ProjectActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'project_add', TYPE_NORMAL, _('Add Project'), _('Adds a new project'), gtk.STOCK_ADD, self.on_project_add, ) self.create_action( 'project_execute', TYPE_MENUTOOL, _('Execute Default'), _('Execute the project'), 'package_utilities', self.on_project_execute, ) self.create_action( 'project_remove', TYPE_NORMAL, _('Remove from workspace'), _('Remove the current project from the workspace'), gtk.STOCK_DELETE, self.on_project_remove, ) self.create_action( 'project_properties', TYPE_TOGGLE, _('Project Properties'), _('Show the project property editor'), 'settings', self.on_project_properties, ) self.create_action( 'project_execution_menu', TYPE_NORMAL, _('Execution Controllers'), _('Configurations with which to execute the project'), gtk.STOCK_EXECUTE, self.on_project_execution_menu, ) def on_project_remove(self, action): self.svc.remove_current_project() def on_project_add(self, action): path = open_directory_dialog( self.svc.boss.get_window(), _('Select a directory to add') ) if path: self.svc.cmd('add_directory', project_directory=path) def on_project_execute(self, action): controller = self.svc.get_default_controller() if controller is None: controllers = self.svc.get_controllers() if controllers: controller = controllers[0] if controller is not None: controller.execute() else: self.svc.boss.get_window().error_dlg( _('This project has no controllers')) def on_project_properties(self, action): self.svc.show_properties(action.get_active()) def on_project_execution_menu(self, action): menuitem = action.get_proxies()[0] menuitem.remove_submenu() menuitem.set_submenu(self.svc.create_menu()) class ProjectFeaturesConfig(FeaturesConfig): def create_features(self): self.create_feature(IProjectController) def subscribe_foreign_features(self): self.subscribe_foreign_feature('project', IProjectController, GenericExecutionController) class ProjectOptions(OptionsConfig): def create_options(self): self.create_option( 'project_dirs', _('Project Directories'), OTypeStringList, [], _('The current directories in the workspace'), ) self.create_option( 'last_project', _('Last Project'), OTypeFile, '', (_('The last project selected. ') + _('(Do not change this unless you know what you are doing)')) ) class ProjectCommandsConfig(CommandsConfig): def add_directory(self, project_directory): self.svc.add_directory(project_directory) def get_view(self): return self.svc.get_view() def get_current_project(self): return self.svc.get_current_project() def save_to_current_project(self, section_name, section_data): self.svc.get_current_project().save_section(section_name, section_data) def get_current_project_data(self, section_name): return self.svc.get_current_project().get_section(section_name) def get_project_for_document(self, document): return self.svc.get_project_for_document(document) # Service class class Project(Service): """The project manager service""" features_config = ProjectFeaturesConfig commands_config = ProjectCommandsConfig events_config = ProjectEventsConfig actions_config = ProjectActionsConfig options_config = ProjectOptions def pre_start(self): self._projects = [] self.set_current_project(None) self._manager = ProjectControllerMananger(self.boss) self._register_controllers() ### self.project_list = ProjectListView(self) self.project_properties_view = ProjectPropertiesView(self) self.project_properties_view.set_controllers(self.features(IProjectController)) self._read_options() def start(self): last = self.opt('last_project') for project in self._projects: if last: if project.source_directory == last: self.project_list.set_current_project(project) def refresh_controllers(self): self._manager.clear_controllers() self._register_controllers() if self._project is not None: self.set_current_project(self._project) def _register_controllers(self): for controller_type in self.features(IProjectController): self._manager.register_controller(controller_type) def _read_options(self): for dirname in self.opt('project_dirs'): path = os.path.join(dirname, '%s.pidaproject' % os.path.basename(dirname)) if os.path.exists(path): project = self._load_project(path) else: self.log_warn(_('Project path %s has disappeared') % path) def _save_options(self): self.set_opt('project_dirs', [p.source_directory for p in self._projects]) def get_view(self): return self.project_list def add_directory(self, project_directory): # Add a directory to the project list project_file = '%s.pidaproject' % os.path.basename(project_directory) for name in os.listdir(project_directory): if name == project_file: project = self.load_and_set_project(os.path.join(project_directory, name)) self._save_options() return project if self.boss.get_window().yesno_dlg( _('The directory does not contain a project file, ') + _('do you want to create one?') ): self.create_project_file(project_directory) self._save_options() def create_project_file(self, project_directory): project_name = os.path.basename(project_directory) file_name = '%s.pidaproject' % project_name path = os.path.join(project_directory, file_name) self._create_blank_project_file(project_name, path) self.load_and_set_project(path) def _create_blank_project_file(self, name, file_path): config = ConfigObj(file_path) config['name'] = name config.write() def set_current_project(self, project): self._project = project self.get_action('project_remove').set_sensitive(project is not None) self.get_action('project_execute').set_sensitive(project is not None) self.get_action('project_properties').set_sensitive(project is not None) self.get_action('project_execution_menu').set_sensitive(project is not None) if project is not None: project.reload() self.project_properties_view.set_project(project) self.emit('project_switched', project=project) toolitem = self.get_action('project_execute').get_proxies()[0] toolitem.set_menu(self.create_menu()) self.get_action('project_execute').set_sensitive(len(project.controllers) > 0) self.set_opt('last_project', project.source_directory) self.boss.editor.set_path(project.source_directory) def get_current_project(self): return self._project def load_and_set_project(self, project_file): project = self._load_project(project_file) self.set_current_project(project) def _load_project(self, project_file): project = self._manager.create_project(project_file) self._projects.append(project) self.project_list.project_ol.append(project) return project def remove_current_project(self): self.remove_project(self._project) def remove_project(self, project): if self.boss.get_window().yesno_dlg( _('Are you sure you want to remove project "%s" from the workspace?') % project.name ): self._projects.remove(project) self.project_list.project_ol.remove(project, select=True) self._save_options() def get_default_controller(self): if self._project is not None: return self._project.get_default_controller() def get_controllers(self): if self._project is not None: return self._project.controllers def create_menu(self): if self._project is not None: menu = gtk.Menu() for controller in self._project.controllers: def _callback(act, controller): controller.execute() act = gtk.Action(controller.config_section, controller.config_section, controller.execute.im_func.func_doc, gtk.STOCK_EXECUTE) act.connect('activate', _callback, controller) mi = act.create_menu_item() menu.add(mi) menu.show_all() return menu def show_properties(self, visible): if visible: self.boss.cmd('window', 'add_view', paned='Plugin', view=self.project_properties_view) else: self.boss.cmd('window', 'remove_view', view=self.project_properties_view) def get_project_for_document(self, document): matches = [] for project in self._projects: match = project.get_relative_path_for(document.filename) if match is not None: matches.append((project, match)) num_matches = len(matches) if num_matches == 0: return None elif num_matches == 1: return matches[0][0], os.sep.join(matches[0][1][-3:-1]) else: shortest = None for i, (project, match) in enumerate(matches): if (shortest is None) or (len(match) < len(matches[shortest][1])): shortest = i return matches[shortest][0], os.sep.join(matches[shortest][1][-3:-1]) # Required Service attribute for service loading Service = Project # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/project/service.pida0000644000175000017500000000000010652670630017026 0ustar alialiPIDA-0.5.1/pida/services/project/test_project.py0000644000175000017500000000222010652670630017614 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/rpc/0002755000175000017500000000000010652671502013660 5ustar alialiPIDA-0.5.1/pida/services/rpc/__init__.py0000644000175000017500000000222010652670610015762 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/rpc/rpc.py0000644000175000017500000000371710652670610015023 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.utils.grpc import LocalServerDispatcher class PidaDispatcher(LocalServerDispatcher): def __init__(self, svc): self.svc = svc LocalServerDispatcher.__init__(self, 9124) def remote_open(self, file_name): self.svc.boss.cmd('buffer', 'open_file', file_name=file_name) # Service class class Rpc(Service): """Describe your Service Here""" def start(self): self._dispatcher = PidaDispatcher(self) # Required Service attribute for service loading Service = Rpc # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/rpc/service.pida0000644000175000017500000000000010652670610016142 0ustar alialiPIDA-0.5.1/pida/services/rpc/test_rpc.py0000644000175000017500000000222010652670610016046 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/sessions/0002755000175000017500000000000010652671502014742 5ustar alialiPIDA-0.5.1/pida/services/sessions/locale/0002755000175000017500000000000010652671501016200 5ustar alialiPIDA-0.5.1/pida/services/sessions/locale/fr_FR/0002755000175000017500000000000010652671501017176 5ustar alialiPIDA-0.5.1/pida/services/sessions/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502020764 5ustar alialiPIDA-0.5.1/pida/services/sessions/locale/fr_FR/LC_MESSAGES/sessions.po0000644000175000017500000000257310652670630023200 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../../sessions.py:112 msgid "Load Session" msgstr "Charger la session" #: ../../../sessions.py:113 msgid "Load a saved session" msgstr "Charger une session sauvergardée" #: ../../../sessions.py:122 msgid "Save Session" msgstr "Sauvegarder la session" #: ../../../sessions.py:123 msgid "Save your current session" msgstr "Sauvegarder la session courante" #: ../../../sessions.py:132 msgid "Save Session As" msgstr "Sauvegarder la session sous" #: ../../../sessions.py:133 msgid "Save your current session with a given filename" msgstr "Sauvergarder la session courante dans un fichier personnalisé" #: ../../../sessions.py:164 msgid "Load last session on startup" msgstr "Charger la dernière session au démarrage" #: ../../../sessions.py:172 msgid "Clear old buffers when loading session" msgstr "Effacer les anciens buffers au chargement d'une session" #: ../../../sessions.py:180 msgid "Sessions Properties" msgstr "Propriétés des sessions" PIDA-0.5.1/pida/services/sessions/uidef/0002755000175000017500000000000010652671502016036 5ustar alialiPIDA-0.5.1/pida/services/sessions/uidef/sessions.xml0000644000175000017500000000310410652670630020423 0ustar aliali PIDA-0.5.1/pida/services/sessions/__init__.py0000644000175000017500000000222010652670631017047 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/sessions/service.pida0000644000175000017500000000000010652670631017227 0ustar alialiPIDA-0.5.1/pida/services/sessions/sessions.py0000644000175000017500000002364210652670631017171 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk import gobject from pida.utils.configobj import ConfigObj from tempfile import mkstemp # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, \ TYPE_TOGGLE from pida.core.options import OptionsConfig from pida.core.options import OTypeString, OTypeBoolean, \ OTypeInteger, OTypeFile, OTypeFont, OTypeStringList from pida.ui.views import PidaGladeView # locale from pida.core.locale import Locale locale = Locale('sessions') _ = locale.gettext class SessionObject(object): """ Represents a session name - the name of the session files - a list of files to open into the buffers project - the currently opened project path - the path to the session file """ def new(self, session_path, files, project=None): """ create a new session """ self.name, self._path = self._parse_session_path(session_path) self._files = files # self._project = project self._config = ConfigObj() self._config.filename = self._path def save(self, file_path=None): """ write this session out to a file """ if file_path: self._config.filename = file_path self._config['name'] = self.name self._config['files'] = self._files # self._config['project'] = self._project self._config.write() def load(self, session_path): """ load a session file """ self._path = session_path self._config = ConfigObj(self._path) if self._config.has_key('name') and self._config.has_key('files'): self.name = self._config.get('name') self._files = self._config.get('files') # self._project = self._config['project'] else: # something has gone horribly wrong # return false and log it in the service raise IOError('Failed to read config file') def _parse_session_path(self, path): base_name = os.path.basename(path).split(".") name = base_name[0] ext = base_name[-1] if ext != "session": path = path + ".session" return (name, path,) def get_files(self): return self._files def set_files(self, files): self._files = files def get_path(self): return self._path class SessionsActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'load_session', TYPE_NORMAL, _('Load Session'), _('Load a saved session'), None, self.on_load_session, '', ) self.create_action( 'save_session', TYPE_NORMAL, _('Save Session'), _('Save your current session'), None, self.on_save_session, '' ) self.create_action( 'save_session_as', TYPE_NORMAL, _('Save Session As'), _('Save your current session with a given filename'), None, self.on_save_session_as, '' ) def on_load_session(self, action): file_path = self.svc.boss.window.open_dlg(folder = self.svc.sessions_dir) if file_path: self.svc.load_session(file_path) def on_save_session(self, action): if self.svc.current_session: self.svc.save_current_session() return def on_save_session_as(self, action): if self.svc.current_session: name = self.svc.current_session.name + ".session" file_path = self.svc.boss.window.save_dlg(current_name = name, folder = self.svc.sessions_dir) else: file_path = self.svc.boss.window.save_dlg(folder = self.svc.sessions_dir) if file_path: self.svc.save_current_session(file_path) return class SessionsOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'load_last_session', _('Load last session on startup'), OTypeBoolean, True, _('Load last session on startup'), ) self.create_option( 'clear_old_buffers', _('Clear old buffers when loading session'), OTypeBoolean, False, _('Clear old buffers when loading session'), ) gladefile = 'sessions-properties' label_text = _('Sessions Properties') icon_name = 'package_utilities' def create_ui(self): pass class SessionsEventsConfig(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.svc.save_last_session) self.subscribe_foreign_event('editor', 'started', self.svc.load_last_session) class Sessions(Service): """ Store opened buffers for later use. Session is a tool to save and restore the state of pida at any given point in time. This should include 1) opened buffers, 2) FileManager locations, possibly more. should allow for multiple sessions to be saved and restored always save the last session get the buffer service buffer = boss.get_service('buffer') get the list of buffers # added this function to the buffer service current_buffers = buffer.get_documents() files = [buffer.filename for buffer in current_buffers] """ # TODO - Save the last session on close # TODO - Allow restoring of last session on startup actions_config = SessionsActionsConfig options_config = SessionsOptionsConfig events_config = SessionsEventsConfig last_session_file = 'last.session' def pre_start(self): self.sessions_dir = os.path.join(self.boss.get_pida_home(), 'sessions') self.last_session_path = os.path.join(self.sessions_dir, self.last_session_file) if not os.path.exists(self.sessions_dir): os.mkdir(self.sessions_dir) self.last_session = SessionObject() if not os.path.exists(self.last_session_path): self.last_session.new(self.last_session_path, []) self.last_session.save() else: self.last_session.load(self.last_session_path) self._set_current_session(None) def load_last_session(self): if self.opt('load_last_session'): self.load_session(self.last_session_path) def load_session(self, file_path, set_current=None): """ load the saved session file from disk """ session = SessionObject() try: session.load(file_path) if set_current: self._set_current_session(session) self.load_buffers(session.get_files()) except IOError: # when we catch this exception we should really make an attempt # at repairing whatever session file it was failing on. self.log_warn(_('Session file:%s failed to load') % file_path) return def save_last_session(self, document): self.last_session.set_files(self._get_current_buffers()) self.last_session.save() def save_current_session(self, file_path=None): """ If no file_path is given save_current_session assumes you will be saving to the default last_session path which is /sessions/last.session """ if self.current_session: self.current_session.set_files(self._get_current_buffers()) self.current_session.save(file_path) else: self.current_session = SessionObject() self.current_session.new(file_path, files = self._get_current_buffers()) self.current_session.save() def _set_current_session(self, session): self.current_session = session self.get_action('save_session').set_sensitive(session is not None) def _get_current_buffers(self): """ retrieve the list of currently opened buffers from the buffer manager. """ documents = self.boss.cmd('buffer', 'get_documents') files = [] for buffer, file in documents.iteritems(): files.append(file.filename) return files def load_buffers(self, files): """ load each file in self.buffers into the buffer manager """ if len(files): self.boss.cmd('buffer', 'open_file', file_name=files.pop()) else: return gobject.timeout_add(1000, self.load_buffers, files) # Required Service attribute for service loading Service = Sessions # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/sessions/test_sessions.py0000644000175000017500000000222010652670631020215 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/shortcuts/0002755000175000017500000000000010652671502015132 5ustar alialiPIDA-0.5.1/pida/services/shortcuts/locale/0002755000175000017500000000000010652671501016370 5ustar alialiPIDA-0.5.1/pida/services/shortcuts/locale/fr_FR/0002755000175000017500000000000010652671501017366 5ustar alialiPIDA-0.5.1/pida/services/shortcuts/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502021154 5ustar alialiPIDA-0.5.1/pida/services/shortcuts/locale/fr_FR/LC_MESSAGES/shortcuts.po0000644000175000017500000000146610652670622023561 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../../shortcuts.py:54 msgid "Shortcuts" msgstr "Raccourcis claviers" #: ../../../shortcuts.py:82 msgid "Capture Shortcut" msgstr "Capturer le raccourci" #: ../../../shortcuts.py:140 msgid "Edit Shortcuts" msgstr "Editer les raccourcis claviers" #: ../../../shortcuts.py:141 msgid "Show the PIDA keyboard shortcut editor" msgstr "Afficher l'éditeur de raccourcis clavier" PIDA-0.5.1/pida/services/shortcuts/uidef/0002755000175000017500000000000010652671502016226 5ustar alialiPIDA-0.5.1/pida/services/shortcuts/uidef/shortcuts.xml0000644000175000017500000000266110652670623021014 0ustar aliali PIDA-0.5.1/pida/services/shortcuts/__init__.py0000644000175000017500000000222010652670623017240 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/shortcuts/service.pida0000644000175000017500000000000010652670623017420 0ustar alialiPIDA-0.5.1/pida/services/shortcuts/shortcuts.py0000644000175000017500000001431010652670623017542 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from kiwi.ui.objectlist import ObjectTree, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaView # locale from pida.core.locale import Locale locale = Locale('shortcuts') _ = locale.gettext class ServiceListItem(object): def __init__(self, svc): self._svc = svc self.label = svc.get_name().capitalize() self.doc = '' self.stock_id = '' class ShortcutsView(PidaView): icon_name = 'key_bindings' label_text = _('Shortcuts') def create_ui(self): self.shortcuts_list = ObjectTree( [ Column('stock_id', use_stock=True), Column('label', sorted=True), Column('value'), Column('doc'), ] ) self.shortcuts_list.set_headers_visible(False) self._current = None self.shortcuts_list.connect('selection-changed', self._on_selection_changed) self.shortcuts_list.connect('double-click', self._on_list_double_click) vbox = gtk.VBox(spacing=6) vbox.set_border_width(6) self.add_main_widget(vbox) for service in self.svc.boss.get_services() + [ self.svc.boss.get_editor()]: if len(service.get_keyboard_options()): sli = ServiceListItem(service) self.shortcuts_list.append(None, sli) for opt in service.get_keyboard_options().values(): self.shortcuts_list.append(sli, opt) self.shortcuts_list.show_all() hbox = gtk.HBox(spacing=6) l = gtk.Label(_('Capture Shortcut')) hbox.pack_start(l, expand=False) self._capture_entry = gtk.Entry() hbox.pack_start(self._capture_entry) self._capture_entry.connect('key-press-event', self._on_capture_keypress) self._capture_entry.set_sensitive(False) vbox.pack_start(self.shortcuts_list) vbox.pack_start(hbox, expand=False) vbox.show_all() self.get_toplevel().set_size_request(350, 0) def decorate_service(self, service): return ServiceListItem(service) def _on_selection_changed(self, otree, item): if isinstance(item, ServiceListItem): self._current = None self._capture_entry.set_sensitive(False) self._capture_entry.set_text('') else: self._current = item self._capture_entry.set_sensitive(True) self._capture_entry.set_text(item.value) def _on_list_double_click(self, otree, item): self._capture_entry.grab_focus() self._capture_entry.select_region(0, -1) def _on_capture_keypress(self, entry, event): # svn.gnome.org/viewcvs/gazpacho/trunk/gazpacho/actioneditor.py # Tab must be handled as normal. Otherwise we can't move from # the entry. if event.keyval == gtk.keysyms.Tab: return False modifiers = event.get_state() & gtk.accelerator_get_default_mod_mask() modifiers = int(modifiers) # Check if we should clear the entry clear_keys = [gtk.keysyms.Delete, gtk.keysyms.KP_Delete, gtk.keysyms.BackSpace] if modifiers == 0: if event.keyval in clear_keys: entry.set_text('') return True # Check if the accelerator is valid and add it to the entry if gtk.accelerator_valid(event.keyval, modifiers): accelerator = gtk.accelerator_name(event.keyval, modifiers) entry.set_text(accelerator) self._current.value = accelerator return True def can_be_closed(self): self.svc.get_action('show_shortcuts').set_active(False) class ShortcutsActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_shortcuts', TYPE_TOGGLE, _('Edit Shortcuts'), _('Show the PIDA keyboard shortcut editor'), 'key_bindings', self.on_show_shortcuts, 'K', ) def on_show_shortcuts(self, action): if action.get_active(): self.svc.show_shortcuts() else: self.svc.hide_shortcuts() # Service class class Shortcuts(Service): """Describe your Service Here""" actions_config = ShortcutsActionsConfig def start(self): self._view = ShortcutsView(self) def show_shortcuts(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_shortcuts(self): self.boss.cmd('window', 'remove_view', view=self._view) # Required Service attribute for service loading Service = Shortcuts # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/shortcuts/test_shortcuts.py0000644000175000017500000000222010652670623020576 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/statusbar/0002755000175000017500000000000010652671502015104 5ustar alialiPIDA-0.5.1/pida/services/statusbar/__init__.py0000644000175000017500000000222010652670634017214 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/statusbar/service.pida0000644000175000017500000000000010652670634017374 0ustar alialiPIDA-0.5.1/pida/services/statusbar/statusbar.py0000644000175000017500000002021510652670634017471 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk import locale import datetime # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.options import OptionsConfig, OTypeBoolean from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE # locale from pida.core.locale import Locale _locale = Locale('statusbar') _ = _locale.gettext class StatusbarEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.on_document_changed) self.subscribe_foreign_event('project', 'project_switched', self.on_project_switched) self.subscribe_foreign_event('filemanager', 'browsed_path_changed', self.on_browsed_path_changed) def on_document_changed(self, document): self.svc.set_label('document', (document.get_basename(), document)) self.svc.set_label('document_encoding', document.get_encoding()) dt = datetime.datetime.fromtimestamp(document.get_mtime()) text = dt.strftime(locale.nl_langinfo(locale.D_T_FMT)) self.svc.set_label('document_mtime', text) size = document.get_size() for ext in ['o', 'Ko', 'Mo', 'Go', 'To']: if size > 1024 * 10: size = size / 1024 else: break self.svc.set_label('document_size', '%d%s' % (size, ext)) def on_project_switched(self, project): self.svc.set_label('project', project.get_display_name()) def on_browsed_path_changed(self, path): self.svc.set_label('path', (path, path)) class StatusbarOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'show_statusbar', _('Show the statusbar'), OTypeBoolean, True, _('Whether the statusbar will be shown'), self.on_show_ui, ) def on_show_ui(self, client, id, entry, option): self.svc.show_statusbar(option.get_value()) class TabLabel(gtk.HBox): def __init__(self, icon_name, text): gtk.HBox.__init__(self, spacing=2) if None in [icon_name, text]: return None self.__label = gtk.Label(text) self.__label.set_padding(5, 5) self.__icon = gtk.image_new_from_stock(icon_name, gtk.ICON_SIZE_SMALL_TOOLBAR) self.pack_start(self.__icon, expand=False) self.pack_start(self.__label, expand=False) self.show_all() def set_text(self, text): self.__label.set_text(text) class StatusMenu(gtk.EventBox): def __init__(self, icon_name, text, activate_callback): gtk.EventBox.__init__(self) self.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.connect('button-press-event', self._on_eventbox__clicked) self.activate_callback = activate_callback self._history = [] self._hb = gtk.HBox(spacing=2) self.add(self._hb) self._label = gtk.Label(text) self._label.set_padding(5, 5) self._icon = gtk.image_new_from_stock(icon_name, gtk.ICON_SIZE_SMALL_TOOLBAR) self._hb.pack_start(self._icon, expand=False) self._hb.pack_start(self._label, expand=False) self.show_all() def set_text(self, (text, value)): if value == -1: value = text self._label.set_text(text) if text: self.add_history((text, value)) def add_history(self, (text, value)): if (text, value) in self._history: self._history.remove((text, value)) self._history.append((text, value)) def _on_eventbox__clicked(self, eventbox, event): self.popup_menu(event) def popup_menu(self, event): menu = gtk.Menu() for text, value in self._history: mi = gtk.MenuItem(text) mi.connect('activate', self.activate_callback, value) menu.add(mi) menu.show_all() menu.popup(None, None, None, event.button, event.time) # Service class class Statusbar(Service): """PIDA Statusbar""" options_config = StatusbarOptionsConfig events_config = StatusbarEvents def start(self): self._statusbar = self.window.get_statusbar() self._places = {} self.create_ui() self.set_default_values() self.show_statusbar(self.opt('show_statusbar')) def create_ui(self): w = TabLabel('package_utilities','') self.add_status('project', widget=w, text='No project') w = StatusMenu('file-manager','', self.on_filemanager_history_activate) self.add_status('path', widget=w, expand=True) w = StatusMenu('package_office','', self.on_buffer_history_activate) self.add_status('document', widget=w, text='No document', expand=True) w = gtk.Label() w.set_padding(5, 0) self.add_status('document_mtime', widget=w) w = gtk.Label() w.set_padding(5, 0) self.add_status('document_encoding', widget=w) w = gtk.Label() w.set_padding(5, 0) self.add_status('document_size', widget=w) def set_default_values(self): project = self.boss.cmd('project', 'get_current_project') if project is not None: self.set_label('project', project.get_display_name()) path = self.boss.cmd('filemanager', 'get_browsed_path') self.set_label('path', (path, path)) def add_status(self, name, widget, text='', expand=False): #widget.set_text(text) separator = gtk.VSeparator() # add in ui if len(self._places) > 0: self._statusbar.pack_start(separator, expand=False) self._statusbar.pack_start(widget, expand=expand, padding=6) if self.opt('show_statusbar'): self._statusbar.show_all() # save in cache self._places[name] = { 'widget':widget, } self._places['_separator_'+name] = { 'widget':separator, } def set_label(self, name, label): if hasattr(self, '_places'): self._places[name]['widget'].set_text(label) def remove_status(self, name): if not self._places.has_key(name): return status = self._places[name] separator = self._places['_separator_'+name] self._statusbar.remove(status['widget']) self._statusbar.remove(separator['widget']) del self._places[name] del self._places['_separator_'+name] def show_statusbar(self, visibility): self.window.set_statusbar_visibility(visibility) def on_filemanager_history_activate(self, menuitem, value): self.boss.cmd('filemanager', 'browse', new_path=value) self.boss.cmd('filemanager', 'present_view') def on_buffer_history_activate(self, menuitem, value): self.boss.cmd('buffer', 'open_file', file_name=value.filename) # Required Service attribute for service loading Service = Statusbar # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/statusbar/test_statusbar.py0000644000175000017500000000222010652670634020524 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/versioncontrol/0002755000175000017500000000000010652671502016162 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/glade/0002755000175000017500000000000010652671502017236 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/glade/commit-dialog.glade0000644000175000017500000002520110652670614022762 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 Please enter a commit message for: False 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 True 1 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 GTK_BUTTONBOX_START True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-back True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-forward True 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-new True 2 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 gtk-copy True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 _Diff True 1 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-ok True 1 1 False 2 PIDA-0.5.1/pida/services/versioncontrol/glade/version-control-log.glade0000644000175000017500000000451410652670614024163 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 6 6 False PIDA-0.5.1/pida/services/versioncontrol/locale/0002755000175000017500000000000010652671501017420 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/locale/fr_FR/0002755000175000017500000000000010652671501020416 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502022204 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/locale/fr_FR/LC_MESSAGES/versioncontrol.po0000644000175000017500000001134610652670613025637 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/commit-dialog.glade.h:1 #, fuzzy msgid "Please enter a commit message for:" msgstr "Afficher le message de commit" #: glade/commit-dialog.glade.h:2 msgid "_Diff" msgstr "" #: glade/commit-dialog.glade.h:3 msgid "gtk-cancel" msgstr "" #: glade/commit-dialog.glade.h:4 msgid "gtk-go-back" msgstr "" #: glade/commit-dialog.glade.h:5 msgid "gtk-go-forward" msgstr "" #: glade/commit-dialog.glade.h:6 msgid "gtk-new" msgstr "" #: glade/commit-dialog.glade.h:7 msgid "gtk-ok" msgstr "" #: versioncontrol.py:60 versioncontrol.py:294 versioncontrol.py:304 #: versioncontrol.py:313 versioncontrol.py:323 msgid "Differences" msgstr "Différences" #: versioncontrol.py:91 versioncontrol.py:264 msgid "Version Control Log" msgstr "Log du contrôle de version" #: versioncontrol.py:101 msgid "" " Version Control Log Started\n" "\n" msgstr "" " Log du contrôle de version démarré\n" "\n" #: versioncontrol.py:138 versioncontrol.py:333 versioncontrol.py:342 #: versioncontrol.py:351 versioncontrol.py:361 msgid "Commit" msgstr "Commiter" #: versioncontrol.py:192 msgid "No Commit Message." msgstr "Aucun message de commit." #: versioncontrol.py:265 msgid "Show the version control log" msgstr "Afficher le log du contrôle de version" #: versioncontrol.py:274 msgid "Commit Message" msgstr "Message de commit" #: versioncontrol.py:275 msgid "Show the commit message" msgstr "Afficher le message de commit" #: versioncontrol.py:284 msgid "More Version Control" msgstr "Avancé" #: versioncontrol.py:285 msgid "More Version Control Commands" msgstr "Plus de commandes du contrôle de version" #: versioncontrol.py:295 msgid "Version Control differences for the current document" msgstr "Afficher les différences du document courant" #: versioncontrol.py:305 msgid "Get the version control differences for the current project" msgstr "Récupérer les différences du projet courant" #: versioncontrol.py:314 msgid "Get the version control diff on this file" msgstr "Récupérer les différences sur ce fichier" #: versioncontrol.py:324 msgid "Get the version control diff on this directory" msgstr "Récupérer les différences sur ce répertoire" #: versioncontrol.py:334 msgid "Commit the current document" msgstr "Commiter le document courant" #: versioncontrol.py:343 msgid "Commit the current project" msgstr "Commiter le projet courant" #: versioncontrol.py:352 msgid "Commit the selected file" msgstr "Commiter le fichier sélectionné" #: versioncontrol.py:362 msgid "Commit the selected directory" msgstr "Commiter le répertoire sélectionné" #: versioncontrol.py:371 versioncontrol.py:380 versioncontrol.py:389 #: versioncontrol.py:399 msgid "Update" msgstr "Mettre à jour" #: versioncontrol.py:372 msgid "Update the current document" msgstr "Mettre à jour le document courant" #: versioncontrol.py:381 msgid "Update the current project" msgstr "Mettre à jour le projet courant" #: versioncontrol.py:390 versioncontrol.py:400 msgid "Update the selected file" msgstr "Mettre à jour le fichier sélectionné" #: versioncontrol.py:409 versioncontrol.py:418 versioncontrol.py:428 msgid "Add" msgstr "Ajouter" #: versioncontrol.py:410 msgid "Add the current document" msgstr "Ajouter le document courant" #: versioncontrol.py:419 versioncontrol.py:429 msgid "Add the selected file" msgstr "Ajouter le fichier sélectionné" #: versioncontrol.py:438 versioncontrol.py:448 versioncontrol.py:458 msgid "Remove" msgstr "Supprimer" #: versioncontrol.py:439 msgid "Remove the current document" msgstr "Supprimer le document courant" #: versioncontrol.py:449 msgid "Remove the selected file" msgstr "Supprimer le fichier sélectionné" #: versioncontrol.py:459 msgid "Remove the selected directory" msgstr "Supprimer le répertoire sélectionné" #: versioncontrol.py:468 versioncontrol.py:478 versioncontrol.py:488 #: versioncontrol.py:498 msgid "Revert" msgstr "Annuler les modifications" #: versioncontrol.py:469 msgid "Revert the current document" msgstr "Revenir à la dernière version connue" #: versioncontrol.py:479 msgid "Revert the current project" msgstr "Annuler les modifications du projet courant" #: versioncontrol.py:489 msgid "Revert the selected file" msgstr "Annuler les modifications du fichier sélectionné" #: versioncontrol.py:499 msgid "Revert the selected directory" msgstr "Annuler les modifications du repértoire sélectionné" PIDA-0.5.1/pida/services/versioncontrol/uidef/0002755000175000017500000000000010652671502017256 5ustar alialiPIDA-0.5.1/pida/services/versioncontrol/uidef/versioncontrol-dir-menu.xml0000644000175000017500000000121310652670614024602 0ustar aliali PIDA-0.5.1/pida/services/versioncontrol/uidef/versioncontrol-file-menu.xml0000644000175000017500000000120310652670614024742 0ustar aliali PIDA-0.5.1/pida/services/versioncontrol/uidef/versioncontrol.xml0000644000175000017500000000512310652670614023070 0ustar aliali PIDA-0.5.1/pida/services/versioncontrol/__init__.py0000644000175000017500000000222010652670615020271 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/versioncontrol/service.pida0000644000175000017500000000000010652670615020451 0ustar alialiPIDA-0.5.1/pida/services/versioncontrol/test_versioncontrol.py0000644000175000017500000000222010652670615022657 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/versioncontrol/versioncontrol.py0000644000175000017500000005433310652670615021634 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os.path import time from cgi import escape import gtk, pango # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaView, PidaGladeView from pida.ui.htmltextview import HtmlTextView from pida.ui.buttons import create_mini_button from pida.utils.gthreads import AsyncTask, gcall # locale from pida.core.locale import Locale locale = Locale('versioncontrol') _ = locale.gettext try: from pygments import highlight from pygments.lexers import DiffLexer from pygments.formatters import HtmlFormatter except ImportError: highlight = None class DiffViewer(PidaView): icon_name = gtk.STOCK_COPY label_text = _('Differences') def create_ui(self): hb = gtk.HBox() self.add_main_widget(hb) sb = gtk.ScrolledWindow() sb.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) sb.set_shadow_type(gtk.SHADOW_IN) sb.set_border_width(3) self._html = HtmlTextView() self._html.set_left_margin(6) self._html.set_right_margin(6) sb.add(self._html) hb.pack_start(sb) hb.show_all() def set_diff(self, diff): if highlight is None: data = '
\n%s
\n' % escape(diff) else: data = highlight(diff, DiffLexer(), HtmlFormatter(noclasses=True)) self._html.display_html(data) def can_be_closed(self): return True class VersionControlLog(PidaGladeView): gladefile = 'version-control-log' icon_name = gtk.STOCK_CONNECT label_text = _('Version Control Log') def create_ui(self): self._buffer = self.log_text.get_buffer() self._buffer.create_tag('time', foreground='#0000c0') self._buffer.create_tag('argument', weight=700) self._buffer.create_tag('title', style=pango.STYLE_ITALIC) self._buffer.create_tag('result', font='Monospace') self.append_time() self.append_stock(gtk.STOCK_CONNECT) self.append(_(' Version Control Log Started\n\n'), 'argument') def append_entry(self, text, tag): self.append(text, tag) def append_time(self): self.append('%s\n' % time.asctime(), 'time') def append_stock(self, stock_id): anchor = self._buffer.create_child_anchor(self._buffer.get_end_iter()) im = gtk.Image() im.set_from_stock(stock_id, gtk.ICON_SIZE_MENU) im.show() self.log_text.add_child_at_anchor(im, anchor) def append_action(self, action, argument, stock_id): self.append_time() self.append_stock(stock_id) self.append_entry(' %s: ' % action, 'title') self.append_entry('%s\n' % argument, 'argument') def append_result(self, result): self.append_entry('%s\n\n' % result.strip(), 'result') def append(self, text, tag): self._buffer.insert_with_tags_by_name( self._buffer.get_end_iter(), text, tag) gcall(self.log_text.scroll_to_iter, self._buffer.get_end_iter(), 0) def can_be_closed(self): self.svc.get_action('show_vc_log').set_active(False) class CommitViewer(PidaGladeView): gladefile = 'commit-dialog' icon_name = gtk.STOCK_GO_UP label_text = _('Commit') def create_ui(self): self._buffer = self.commit_text.get_buffer() self._history_index = 0 self._history = [] self._path = None def _update_view(self): self.ok_button.set_sensitive(self._path is not None) self.prev_button.set_sensitive(self._history_index != 0) self.next_button.set_sensitive(self._history_index != len(self._history)) self.new_button.set_sensitive(self._history_index != len(self._history)) def set_path(self, path): self._path = path self._update_view() self._set_path_label() def get_message(self): return self._buffer.get_text(self._buffer.get_start_iter(), self._buffer.get_end_iter()) def _set_path_label(self): if self._path is not None: self.path_label.set_markup('%s' % escape(self._path)) else: self.path_label.set_text('') def _commit(self, msg): self._history.append(msg) self._history_index = len(self._history) self._clear_text() self._update_view() self.svc.commit_path(self._path, msg) self.close() def _clear_text(self): self._buffer.set_text('') def _show_history(self): if self._history_index == len(self._history): self._clear_text() else: self._buffer.set_text(self._history[self._history_index]) self.commit_text.grab_focus() self._update_view() def on_ok_button__clicked(self, button): msg = self.get_message().strip() if not msg: self.svc.error_dlg(_('No Commit Message.')) else: self._commit(msg) def on_close_button__clicked(self, button): self.close() def on_prev_button__clicked(self, button): self._history_index -= 1 self._show_history() def on_next_button__clicked(self, button): self._history_index += 1 self._show_history() def on_new_button__clicked(self, button): self._history_index = len(self._history) self._show_history() def on_diff_button__clicked(self, button): self.svc.diff_path(self._path) def close(self): self.set_path(None) self.svc.get_action('show_commit').set_active(False) class VersioncontrolFeaturesConfig(FeaturesConfig): def create_features(self): self.create_feature("workdir-manager") def subscribe_foreign_features(self): from pida.utils.anyvc import all_known for mgr in all_known: self.subscribe_feature("workdir-manager", mgr) self.subscribe_foreign_feature( "filemanager", "file_lister", self.svc.list_files ) self.subscribe_foreign_feature( "filemanager", "file_hidden_check", self.svc.ignored_file_checker ) self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'versioncontrol-file-menu.xml')) self.subscribe_foreign_feature('contexts', 'dir-menu', (self.svc.get_action_group(), 'versioncontrol-dir-menu.xml')) class VersionControlEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.svc.on_document_changed) self.subscribe_foreign_event('project', 'project_switched', self.svc.on_project_changed) class VersioncontrolCommandsConfig(CommandsConfig): def get_workdirmanager(self,path): return self.svc.get_workdir_manager_for_path(path) class VersionControlActions(ActionsConfig): def create_actions(self): self.create_action( 'show_vc_log', TYPE_TOGGLE, _('Version Control Log'), _('Show the version control log'), gtk.STOCK_CONNECT, self.on_show_vc_log, '2', ) self.create_action( 'show_commit', TYPE_TOGGLE, _('Commit Message'), _('Show the commit message'), gtk.STOCK_GO_UP, self.on_show_commit, 'NOACCEL' ) self.create_action( 'more_vc_menu', TYPE_NORMAL, _('More Version Control'), _('More Version Control Commands'), gtk.STOCK_CONNECT, lambda *a: None, 'NOACCEL' ) self.create_action( 'diff_document', TYPE_NORMAL, _('Differences'), _('Version Control differences for the current document'), gtk.STOCK_COPY, self.on_diff_document, 'd', ) self.create_action( 'diff_project', TYPE_NORMAL, _('Differences'), _('Get the version control differences for the current project'), gtk.STOCK_COPY, self.on_diff_project, ) self.create_action( 'diff_for_file', TYPE_NORMAL, _('Differences'), _('Get the version control diff on this file'), gtk.STOCK_COPY, self.on_diff_for_file, 'NOACCEL', ) self.create_action( 'diff_for_directory', TYPE_NORMAL, _('Differences'), _('Get the version control diff on this directory'), gtk.STOCK_COPY, self.on_diff_for_dir, 'NOACCEL', ) self.create_action( 'commit_document', TYPE_NORMAL, _('Commit'), _('Commit the current document'), gtk.STOCK_GO_UP, self.on_commit_document, ) self.create_action( 'commit_project', TYPE_NORMAL, _('Commit'), _('Commit the current project'), gtk.STOCK_GO_UP, self.on_commit_project, ) self.create_action( 'commit_for_file', TYPE_NORMAL, _('Commit'), _('Commit the selected file'), gtk.STOCK_GO_UP, self.on_commit_for_file, 'NOACCEL' ) self.create_action( 'commit_for_dir', TYPE_NORMAL, _('Commit'), _('Commit the selected directory'), gtk.STOCK_GO_UP, self.on_commit_for_directory, 'NOACCEL' ) self.create_action( 'update_document', TYPE_NORMAL, _('Update'), _('Update the current document'), gtk.STOCK_GO_DOWN, self.on_update_document, ) self.create_action( 'update_project', TYPE_NORMAL, _('Update'), _('Update the current project'), gtk.STOCK_GO_DOWN, self.on_update_project, ) self.create_action( 'update_for_file', TYPE_NORMAL, _('Update'), _('Update the selected file'), gtk.STOCK_GO_DOWN, self.on_update_for_file, 'NOACCEL' ) self.create_action( 'update_for_dir', TYPE_NORMAL, _('Update'), _('Update the selected file'), gtk.STOCK_GO_DOWN, self.on_update_for_dir, 'NOACCEL' ) self.create_action( 'add_document', TYPE_NORMAL, _('Add'), _('Add the current document'), gtk.STOCK_ADD, self.on_add_document, ) self.create_action( 'add_for_file', TYPE_NORMAL, _('Add'), _('Add the selected file'), gtk.STOCK_ADD, self.on_add_for_file, 'NOACCEL' ) self.create_action( 'add_for_dir', TYPE_NORMAL, _('Add'), _('Add the selected file'), gtk.STOCK_ADD, self.on_add_for_dir, 'NOACCEL' ) self.create_action( 'remove_document', TYPE_NORMAL, _('Remove'), _('Remove the current document'), gtk.STOCK_DELETE, self.on_remove_document, 'NOACCEL', ) self.create_action( 'remove_for_file', TYPE_NORMAL, _('Remove'), _('Remove the selected file'), gtk.STOCK_DELETE, self.on_remove_for_file, 'NOACCEL', ) self.create_action( 'remove_for_dir', TYPE_NORMAL, _('Remove'), _('Remove the selected directory'), gtk.STOCK_DELETE, self.on_remove_for_dir, 'NOACCEL', ) self.create_action( 'revert_document', TYPE_NORMAL, _('Revert'), _('Revert the current document'), gtk.STOCK_UNDO, self.on_revert_document, 'NOACCEL' ) self.create_action( 'revert_project', TYPE_NORMAL, _('Revert'), _('Revert the current project'), gtk.STOCK_UNDO, self.on_revert_project, 'NOACCEL' ) self.create_action( 'revert_for_file', TYPE_NORMAL, _('Revert'), _('Revert the selected file'), gtk.STOCK_UNDO, self.on_revert_for_file, 'NOACCEL' ) self.create_action( 'revert_for_dir', TYPE_NORMAL, _('Revert'), _('Revert the selected directory'), gtk.STOCK_UNDO, self.on_revert_for_dir, 'NOACCEL' ) def on_show_vc_log(self, action): if action.get_active(): self.svc.show_log() else: self.svc.hide_log() def on_show_commit(self, action): if action.get_active(): self.svc.show_commit() else: self.svc.hide_commit() def on_diff_document(self, action): path = self.svc.current_document.filename self.svc.diff_path(path) def on_diff_project(self, action): path = self.svc.current_project.source_directory self.svc.diff_path(path) def on_diff_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.diff_path(path) def on_diff_for_dir(self, action): path = action.contexts_kw['dir_name'] self.svc.diff_path(path) def on_commit_document(self, action): path = self.svc.current_document.filename self.svc.commit_path_dialog(path) def on_commit_project(self, action): path = self.svc.current_project.source_directory self.svc.commit_path_dialog(path) def on_commit_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.commit_path_dialog(path) def on_commit_for_directory(self, action): path = action.contexts_kw['dir_name'] self.svc.commit_path_dialog(path) def on_update_document(self, action): path = self.svc.current_document.filename self.svc.update_path(path) def on_update_project(self, action): path = self.svc.current_project.source_directory self.svc.update_path(path) def on_update_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.update_path(path) def on_update_for_dir(self, action): path = action.contexts_kw['dir_name'] self.svc.update_path(path) def on_add_document(self, action): path = self.svc.current_document.filename self.svc.add_path(path) def on_add_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.add_path(path) def on_add_for_dir(self, action): path = action.contexts_kw['dir_name'] self.svc.add_path(path) def on_remove_document(self, action): path = self.svc.current_document.filename self.svc.remove_path(path) def on_remove_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.remove_path(path) def on_remove_for_dir(self, action): path = action.contexts_kw['dir_name'] self.svc.remove_path(path) def on_revert_document(self, action): path = self.svc.current_document.filename self.svc.revert_path(path) def on_revert_project(self, action): path = self.svc.current_project.source_directory self.svc.revert_path(path) def on_revert_for_file(self, action): path = action.contexts_kw['file_name'] self.svc.revert_path(path) def on_revert_for_dir(self, action): path = action.contexts_kw['dir_name'] self.svc.revert_path(path) # Service class class Versioncontrol(Service): """The Versioncontrol service""" features_config = VersioncontrolFeaturesConfig commands_config = VersioncontrolCommandsConfig actions_config = VersionControlActions events_config = VersionControlEvents def pre_start(self): self.on_document_changed(None) self.on_project_changed(None) def start(self): self._log = VersionControlLog(self) self._commit = CommitViewer(self) def ignored_file_checker(self, path, name, state): return not ( state == "hidden" or state == "ignored") def get_workdir_manager_for_path(self, path): for vcm in self.features("workdir-manager"): try: return vcm(path) #TODO: this shouldnt need an exception except ValueError: pass def list_files(self, path): workdir = self.get_workdir_manager_for_path(path) if workdir is not None: for item in workdir.list(paths=[path], recursive=False): abspath = item.abspath name = os.path.basename (abspath) path = os.path.dirname(abspath) yield name, path, item.state def diff_path(self, path): self._log.append_action('Diffing', path, gtk.STOCK_COPY) task = AsyncTask(self._do_diff, self._done_diff) task.start(path) def _do_diff(self, path): vc = self.get_workdir_manager_for_path(path) return vc.diff(paths=[path]) def _done_diff(self, diff): view = DiffViewer(self) self.boss.cmd('window', 'add_view', paned='Terminal', view=view) view.set_diff(diff) def execute(self, action, path, stock_id, **kw): vc = self.get_workdir_manager_for_path(path) commandargs = [vc.cmd] + getattr(vc, 'get_%s_args' % action)(paths=[path], **kw) self._log.append_action(action.capitalize(), path, stock_id) def _executed(term): self._executed(action, path, stock_id, term) self.boss.cmd('commander', 'execute', commandargs=commandargs, cwd=vc.base_path, eof_handler=_executed, use_python_fork=True, title=action.capitalize(), icon=stock_id) def _executed(self, action, path, stock_id, term): self._log.append_result(term.get_all_text()) self.boss.cmd('window', 'remove_view', view=term.parent_view) self.boss.cmd('notify', 'notify', title=_('Version Control %(action)s Completed') % {'action': action.capitalize()}, data=path, stock=stock_id) self.boss.cmd('filemanager', 'refresh') def update_path(self, path): self.execute('update', path, gtk.STOCK_GO_DOWN) def commit_path(self, path, message=None): self.execute('commit', path, gtk.STOCK_GO_UP, message=message) def commit_path_dialog(self, path): self._commit.set_path(path) self.ensure_commit_visible() def revert_path(self, path): self.execute('revert', path, gtk.STOCK_UNDO) def add_path(self, path): self.execute('add', path, gtk.STOCK_ADD) def remove_path(self, path): self.execute('remove', path, gtk.STOCK_REMOVE) def on_document_changed(self, document): for action in ['diff_document', 'revert_document', 'add_document', 'remove_document', 'update_document', 'commit_document']: self.get_action(action).set_sensitive(document is not None) self.current_document = document def on_project_changed(self, project): for action in ['diff_project', 'revert_project', 'update_project', 'commit_project']: self.get_action(action).set_sensitive(project is not None) self.current_project = project def show_log(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._log) def hide_log(self): self.boss.cmd('window', 'remove_view', view=self._log) def ensure_log_visible(self): action = self.get_action('show_vc_log') if not action.get_active(): action.set_active(True) else: self.boss.cmd('window', 'present_view', view=self._log) def show_commit(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._commit) def hide_commit(self): self.boss.cmd('window', 'remove_view', view=self._commit) def ensure_commit_visible(self): action = self.get_action('show_commit') if not action.get_active(): action.set_active(True) else: self.boss.cmd('window', 'present_view', view=self._commit) # Required Service attribute for service loading Service = Versioncontrol # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/webbrowser/0002755000175000017500000000000010652671502015255 5ustar alialiPIDA-0.5.1/pida/services/webbrowser/locale/0002755000175000017500000000000010652671501016513 5ustar alialiPIDA-0.5.1/pida/services/webbrowser/locale/fr_FR/0002755000175000017500000000000010652671501017511 5ustar alialiPIDA-0.5.1/pida/services/webbrowser/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502021277 5ustar alialiPIDA-0.5.1/pida/services/webbrowser/locale/fr_FR/LC_MESSAGES/webbrowser.po0000644000175000017500000000077310652670605024030 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../../webbrowser.py:136 msgid "Browser" msgstr "Navigateur" PIDA-0.5.1/pida/services/webbrowser/uidef/0002755000175000017500000000000010652671502016351 5ustar alialiPIDA-0.5.1/pida/services/webbrowser/uidef/webbrowser-url-menu.xml0000644000175000017500000000062710652670606023025 0ustar aliali PIDA-0.5.1/pida/services/webbrowser/__init__.py0000644000175000017500000000222010652670606017364 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/webbrowser/service.pida0000644000175000017500000000000010652670606017544 0ustar alialiPIDA-0.5.1/pida/services/webbrowser/test_webbrowser.py0000644000175000017500000000222010652670606021045 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/webbrowser/webbrowser.py0000644000175000017500000002024110652670606020011 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import urlparse import pida.utils.pywebbrowser as webbrowser import gtk try: import gtkhtml2 except: gtkhtml2 = None # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.utils.web import fetch_url from pida.ui.views import PidaView # locale from pida.core.locale import Locale locale = Locale('webbrowser') _ = locale.gettext def get_url_mark(url): if '#' in url: url, mark = url.rsplit('#', 1) else: mark = None return url, mark class HtmlWidget(gtk.ScrolledWindow): def __init__(self, manager=None): gtk.ScrolledWindow.__init__(self) self.__view = gtkhtml2.View() self.add(self.__view) self.__document = gtkhtml2.Document() self.__view.set_document(self.__document) self.__document.connect('request-url', self.cb_request_url) self.__document.connect('link-clicked', self.cb_link_clicked) self.__current_url = None self.__current_mark = None self.__fetching_url = None self.__fetching_mark = None self.__manager = manager self.__urlqueue = [] def load_url(self, url): url, mark = get_url_mark(url) self.__fetching_mark = mark self.__fetching_url = url if url != self.__current_url: self.__manager.stop_button.set_sensitive(True) self.__document.clear() self.__document.open_stream('text/html') fetch_url(url, self.fetch_complete) else: self.finished(url) def fetch_complete(self, url, data): self.__document.write_stream(data) self.__document.close_stream() self.finished(url) def cb_loader_data(self, data): self.__document.write_stream(data) def cb_loader_finished(self, url): self.__document.close_stream() self.finished(url) def stop(self): self.cb_loader_finished(self.__fetching_url) def back(self): if len(self.__urlqueue) > 1: self.__urlqueue.pop() url = self.__urlqueue.pop() self.load_url(url) def finished(self, url): self.__current_url = url self.__current_mark = self.__fetching_mark if self.__current_mark: self.__view.jump_to_anchor(self.__current_mark) else: self.__view.jump_to_anchor('') durl = url if self.__current_mark: durl = durl + '#' + self.__current_mark self.__manager.stop_button.set_sensitive(False) self.__manager.location.set_text(url) self.__urlqueue.append(url) self.__manager.back_button.set_sensitive(len(self.__urlqueue) > 1) def cb_request_url(self, doc, url, stream): def _data(url, data): stream.write(data) stream.close() url = urlparse.urljoin(self.__fetching_url, url) fetch_url(url, _data) def cb_link_clicked(self, doc, url): url = urlparse.urljoin(self.__current_url, url) self.load_url(url) class BrowserView(PidaView): ICON_NAME = 'gtk-library' SHORT_TITLE = _('Browser') HAS_TITLE = False def create_ui(self): bar = gtk.HBox() self.back_button = gtk.ToolButton(stock_id=gtk.STOCK_GO_BACK) self.stop_button = gtk.ToolButton(stock_id=gtk.STOCK_STOP) bar.pack_start(self.back_button, expand=False) bar.pack_start(self.stop_button, expand=False) self.back_button.connect('clicked', self.cb_toolbar_clicked, 'back') self.stop_button.connect('clicked', self.cb_toolbar_clicked, 'stop') self.add_main_widget(bar, expand=False) self.location = gtk.Entry() bar.pack_start(self.location) self.location.connect('activate', self.cb_url_entered) self.__browser = HtmlWidget(self) self.add_main_widget(self.__browser) self.status_bar = gtk.Statusbar() self.status_context = self.status_bar.get_context_id('web') self.add_main_widget(self.status_bar, expand=False) self.get_toplevel().show_all() self._close_callback=None def connect_closed(self, callback): self._close_callback = callback def cb_url_entered(self, entry): url = self.location.get_text() self.fetch(url) def fetch(self, url): self.__browser.load_url(url) def cb_toolbar_clicked(self, button, name): if name == 'back': self.__browser.back() else: self.__browser.stop() def can_be_closed(self): if self._close_callback is not None: self._close_callback(self) else: self.svc.boss.cmd('window', 'remove_view', view=self) class WebCommands(CommandsConfig): def browse(self, url): self.svc.browse(url) def get_web_browser(self): return BrowserView class WebFeatures(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('contexts', 'url-menu', (self.svc.get_action_group(), 'webbrowser-url-menu.xml')) class WebActions(ActionsConfig): def create_actions(self): self.create_action( 'open_url_for_url', TYPE_NORMAL, _('Open URL'), _('Open a url in the builtin browser'), gtk.STOCK_OPEN, self.on_open_url_for_url, ) self.create_action( 'copy_clipboard_for_url', TYPE_NORMAL, _('Copy URL to clipboard'), _('Copy this URL to the clipboard'), gtk.STOCK_COPY, self.on_copy_url_for_url, ) self.create_action( 'open_url_external_for_url', TYPE_NORMAL, _('Open URL in external web browser'), _('Open the selected URL in an external web browser'), 'internet', self.on_open_url_external_for_url, ) def on_open_url_for_url(self, action): url = action.contexts_kw['url'] self.svc.browse(url) def on_copy_url_for_url(self, action): url = action.contexts_kw['url'] for clipboard_type in ['PRIMARY', 'CLIPBOARD']: gtk.Clipboard(selection=clipboard_type).set_text(url) def on_open_url_external_for_url(self, action): url = action.contexts_kw['url'] webbrowser.open(url) # Service class class Webbrowser(Service): """Describe your Service Here""" commands_config = WebCommands features_config = WebFeatures actions_config = WebActions def pre_start(self): self._views = [] def browse(self, url): if gtkhtml2 is None: webbrowser.open(url) else: view = BrowserView(self) view.fetch(url) self.boss.cmd('window', 'add_view', paned='Terminal', view=view) # Required Service attribute for service loading Service = Webbrowser # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/window/0002755000175000017500000000000010652671502014403 5ustar alialiPIDA-0.5.1/pida/services/window/locale/0002755000175000017500000000000010652671501015641 5ustar alialiPIDA-0.5.1/pida/services/window/locale/fr_FR/0002755000175000017500000000000010652671501016637 5ustar alialiPIDA-0.5.1/pida/services/window/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671502020425 5ustar alialiPIDA-0.5.1/pida/services/window/locale/fr_FR/LC_MESSAGES/window.po0000644000175000017500000000240510652670631022275 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../../window.py:63 msgid "Show Toolbar" msgstr "Afficher la barre d'outils" #: ../../../window.py:64 msgid "Toggle the visible state of the toolbar" msgstr "Changer le status de visibilité de la barre d'outils" #: ../../../window.py:73 msgid "Show Menubar" msgstr "Afficher le menu" #: ../../../window.py:74 msgid "Toggle the visible state of the menubar" msgstr "Changer le status de visibilité du menu" #: ../../../window.py:105 msgid "Show the toolbar" msgstr "Afficher la barre d'outils" #: ../../../window.py:108 msgid "Whether the main toolbar will be shown" msgstr "Indique si la barre d'outils sera affichée" #: ../../../window.py:114 msgid "Show the menubar" msgstr "Afficher la barre de menu" #: ../../../window.py:117 msgid "Whether the main menubar will be shown" msgstr "Indique si la barre de menu sera affichée" PIDA-0.5.1/pida/services/window/uidef/0002755000175000017500000000000010652671502015477 5ustar alialiPIDA-0.5.1/pida/services/window/uidef/window.xml0000644000175000017500000000314510652670631017533 0ustar aliali PIDA-0.5.1/pida/services/window/__init__.py0000644000175000017500000000222010652670632016511 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/window/service.pida0000644000175000017500000000000010652670632016671 0ustar alialiPIDA-0.5.1/pida/services/window/test_window.py0000644000175000017500000000222010652670632017320 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/window/window.py0000644000175000017500000001412610652670632016271 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.options import OptionsConfig, OTypeBoolean from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE # locale from pida.core.locale import Locale locale = Locale('window') _ = locale.gettext class WindowCommandsConfig(CommandsConfig): def add_view(self, paned, view, removable=True, present=True): self.svc.window.add_view(paned, view, removable, present) def add_detached_view(self, paned, view, size=(500,400)): self.add_view(paned, view) self.detach_view(view, size) def remove_view(self, view): self.svc.window.remove_view(view) def detach_view(self, view, size): self.svc.window.detach_view(view, size) def present_view(self, view): self.svc.window.present_view(view) class WindowActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'show_toolbar', TYPE_TOGGLE, _('Show Toolbar'), _('Toggle the visible state of the toolbar'), 'face-glasses', self.on_show_ui, 'l', ) self.create_action( 'show_menubar', TYPE_TOGGLE, _('Show Menubar'), _('Toggle the visible state of the menubar'), 'face-glasses', self.on_show_ui, 'u', ) self.create_action( 'switch_next_term', TYPE_NORMAL, _('Next terminal'), _('Switch to the next terminal'), gtk.STOCK_GO_FORWARD, self.on_switch_next_term, 'Right', ) self.create_action( 'switch_prev_term', TYPE_NORMAL, _('Previous terminal'), _('Switch to the previous terminal'), gtk.STOCK_GO_BACK, self.on_switch_prev_term, 'Left', ) self.create_action( 'focus_terminal', TYPE_NORMAL, _('Focus terminal'), _('Focus terminal pane terminal'), 'terminal', self.on_focus_terminal, 'i', ) def on_focus_terminal(self, action): self.svc.window.present_paned('Terminal') def on_switch_next_term(self, action): self.svc.window.switch_next_view('Terminal') def on_switch_prev_term(self, action): self.svc.window.switch_prev_view('Terminal') def on_show_ui(self, action): val = action.get_active() self.svc.set_opt(action.get_name(), val) getattr(self.svc, action.get_name())(val) class WindowEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.on_document_changed) self.subscribe_foreign_event('editor', 'started', self.on_editor_started) def on_document_changed(self, document): self.svc.window.set_title(document.filename) def on_editor_started(self): self.svc.boss.hide_splash() self.svc.window.show() class WindowOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'show_toolbar', _('Show the toolbar'), OTypeBoolean, True, _('Whether the main toolbar will be shown'), self.on_show_ui, ) self.create_option( 'show_menubar', _('Show the menubar'), OTypeBoolean, True, _('Whether the main menubar will be shown'), self.on_show_ui, ) def on_show_ui(self, client, id, entry, option): self.svc.get_action(option.name).set_active(option.get_value()) # Service class class Window(Service): """The PIDA Window Manager""" commands_config = WindowCommandsConfig options_config = WindowOptionsConfig actions_config = WindowActionsConfig events_config = WindowEvents def start(self): # Explicitly add the permanent views for service in ['project', 'filemanager', 'buffer']: view = self.boss.cmd(service, 'get_view') self.cmd('add_view', paned='Buffer', view=view, removable=False, present=False) self._fix_visibilities() def _fix_visibilities(self): for name in ['show_toolbar', 'show_menubar']: val = self.opt(name) self.get_action(name).set_active(val) getattr(self, name)(val) def show_toolbar(self, visibility): self.window.set_toolbar_visibility(visibility) def show_menubar(self, visibility): self.window.set_menubar_visibility(visibility) # Required Service attribute for service loading Service = Window # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/services/__init__.py0000644000175000017500000000000010652670641015174 0ustar alialiPIDA-0.5.1/pida/ui/0002755000175000017500000000000010652671502011666 5ustar alialiPIDA-0.5.1/pida/ui/__init__.py0000644000175000017500000000052710652670647014012 0ustar aliali import icons from kiwi.ui.widgets.combo import ProxyComboBox from kiwi.ui.widgets.textview import ProxyTextView from kiwi.ui.objectlist import ObjectList, ObjectTree ### Monkey Patching from pida.ui.objectlist import sort_by_attribute if not hasattr(ObjectList, 'sort_by_attribute'): ObjectList.sort_by_attribute = sort_by_attribute PIDA-0.5.1/pida/ui/books.py0000644000175000017500000001472310652670647013373 0ustar aliali import gtk ORIENTATION_SIDEBAR_LEFT = 0 ORIENTATION_SIDEBAR_RIGHT = 1 BOOK_TERMINAL = 'Terminal' BOOK_EDITOR = 'Editor' BOOK_BUFFER = 'Buffer' BOOK_PLUGIN = 'Plugin' # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class BaseBookConfig(object): def __init__(self, orientation): self._orientation = orientation def get_tabs_visible(self): return True def get_tab_position(self): return gtk.POS_TOP def get_notebook_name(self): raise NotImplementedError(_('Must at least define a notebook name')) def get_name(self): raise NotImplementedError(_('Must at leaste define a Name')) def create_tab_label(self, icon, text): if None in [icon, text]: return None label = gtk.Label(text) if self.get_tab_position() in [gtk.POS_TOP, gtk.POS_BOTTOM]: b_factory = gtk.HBox else: b_factory = gtk.VBox label.set_angle(270) b = b_factory(spacing=2) b.pack_start(icon) b.pack_start(label) b.show_all() return b class TerminalBookConfig(BaseBookConfig): def get_notebook_name(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return 'br_book' else: return 'bl_book' def get_name(self): return 'Terminal' class EditorBookConfig(BaseBookConfig): def get_notebook_name(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return 'tr_book' else: return 'tl_book' def get_tabs_visible(self): return False def get_name(self): return 'Editor' class BufferBookConfig(BaseBookConfig): def get_notebook_name(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return 'tl_book' else: return 'tr_book' def get_tab_position(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return gtk.POS_RIGHT else: return gtk.POS_LEFT def get_name(self): return 'Buffer' class PluginBookConfig(BaseBookConfig): def get_notebook_name(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return 'bl_book' else: return 'br_book' def get_tab_position(self): if self._orientation == ORIENTATION_SIDEBAR_LEFT: return gtk.POS_RIGHT else: return gtk.POS_LEFT def get_name(self): return 'Plugin' class BookConfigurator(object): def __init__(self, orientation): self._orientation = orientation self._configs = {} self._books = {} self._widget_names = {} for conf in [ TerminalBookConfig, EditorBookConfig, PluginBookConfig, BufferBookConfig ]: book_config = conf(self._orientation) self._configs[book_config.get_name()] = book_config self._widget_names[book_config.get_notebook_name()] = book_config def get_config(self, name): try: return self._configs[name] except KeyError: print self._configs raise KeyError(_('No Notebook attests to having that name %s') % name) def configure_book(self, name, book): conf = self._widget_names[name] self._books[conf.get_name()] = book book.set_show_tabs(conf.get_tabs_visible()) book.set_tab_pos(conf.get_tab_position()) book.remove_page(0) if conf.get_name() != BOOK_EDITOR: # Cannot drag to the editor terminal for now book.set_group_id(0) def get_book(self, name): return self._books[name] def get_books(self): return self._books.items() def get_names(self): return self._books.keys() class BookManager(object): def __init__(self, configurator): self._conf = configurator self._views = dict() for k in self._conf.get_names(): self._views[k] = dict() def add_view(self, bookname, view): if not self.has_view(view): self._views[bookname][view.get_unique_id()] = view book = self._get_book(bookname) tab_label = self._create_tab_label( bookname, view.create_tab_label_icon(), view.get_tab_label_text()) book.append_page(view.get_toplevel(), tab_label=tab_label) book.set_current_page(-1) book.show_all() book.set_tab_detachable(view.get_toplevel(), True) self._focus_page(bookname) else: raise ValueError(_('This view is already in the manager')) def remove_view(self, view): book_name = self._get_book_for_view(view) book = self._get_book(book_name) book.remove(view.get_toplevel()) del self._views[book_name][view.get_unique_id()] def move_view(self, bookname, view): self.remove_view(view) self.add_view(bookname, view) def has_view(self, view): return view.get_unique_id() in self._get_view_uids() def next_page(self, bookname): book = self._get_book(bookname) if self._get_current_page(bookname) == book.get_n_pages() - 1: book.set_current_page(0) else: book.next_page() self._focus_page(bookname) def prev_page(self, bookname): book = self._get_book(bookname) if self._get_current_page(bookname) == 0: book.set_current_page(book.get_n_pages() - 1) else: book.prev_page() self._focus_page(bookname) def _get_current_page(self, bookname): return self._get_book(bookname).get_current_page() def _focus_page(self, bookname): book = self._get_book(bookname) book.get_nth_page(book.get_current_page()).grab_focus() def _get_book(self, name): return self._conf.get_book(name) def _get_book_for_view(self, view): for name, views in self._views.items(): if view.get_unique_id() in views: return name raise ValueError(_('View is not in any Notebook')) def _get_view_uids(self): uids = [] for book in self._views.values(): uids.extend(book.keys()) return uids def _create_tab_label(self, bookname, icon, text): conf = self._conf.get_config(bookname) return conf.create_tab_label(icon, text) PIDA-0.5.1/pida/ui/buttons.py0000644000175000017500000000054610652670647013752 0ustar alialiimport gtk def create_mini_button(stock_id, tooltip, click_callback): tip = gtk.Tooltips() tip.enable() im = gtk.Image() im.set_from_stock(stock_id, gtk.ICON_SIZE_MENU) but = gtk.Button() but.set_image(im) but.connect('clicked', click_callback) eb = gtk.EventBox() eb.add(but) tip.set_tip(eb, tooltip) return eb PIDA-0.5.1/pida/ui/htmltextview.py0000644000175000017500000005346710652670647015032 0ustar aliali### Copyright (C) 2005 Gustavo J. A. M. Carneiro ### ### This library is free software; you can redistribute it and/or ### modify it under the terms of the GNU Lesser General Public ### License as published by the Free Software Foundation; either ### version 2 of the License, or (at your option) any later version. ### ### This library is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ### Lesser General Public License for more details. ### ### You should have received a copy of the GNU Lesser General Public ### License along with this library; if not, write to the ### Free Software Foundation, Inc., 59 Temple Place - Suite 330, ### Boston, MA 02111-1307, USA. ''' A gtk.TextView-based renderer for XHTML-IM, as described in: http://www.jabber.org/jeps/jep-0071.html ''' import gobject import pango import gtk import xml.sax, xml.sax.handler import re import warnings from cStringIO import StringIO import urllib2 import operator __all__ = ['HtmlTextView'] whitespace_rx = re.compile("\\s+") allwhitespace_rx = re.compile("^\\s*$") ## pixels = points * display_resolution display_resolution = 0.3514598*(gtk.gdk.screen_height() / float(gtk.gdk.screen_height_mm())) def _parse_css_color(color): '''_parse_css_color(css_color) -> gtk.gdk.Color''' if color.startswith("rgb(") and color.endswith(')'): r, g, b = [int(c)*257 for c in color[4:-1].split(',')] return gtk.gdk.Color(r, g, b) else: return gtk.gdk.color_parse(color) class HtmlHandler(xml.sax.handler.ContentHandler): def __init__(self, textview, startiter): xml.sax.handler.ContentHandler.__init__(self) self.textbuf = textview.get_buffer() self.textview = textview self.iter = startiter self.text = '' self.styles = [] # a gtk.TextTag or None, for each span level self.list_counters = [] # stack (top at head) of list # counters, or None for unordered list self.is_pre = False def _parse_style_color(self, tag, value): color = _parse_css_color(value) tag.set_property("foreground-gdk", color) def _parse_style_background_color(self, tag, value): color = _parse_css_color(value) tag.set_property("background-gdk", color) if gtk.gtk_version >= (2, 8): tag.set_property("paragraph-background-gdk", color) if gtk.gtk_version >= (2, 8, 5) or gobject.pygtk_version >= (2, 8, 1): def _get_current_attributes(self): attrs = self.textview.get_default_attributes() self.iter.backward_char() self.iter.get_attributes(attrs) self.iter.forward_char() return attrs else: ## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455 def _get_current_style_attr(self, propname, comb_oper=None): tags = [tag for tag in self.styles if tag is not None] tags.reverse() is_set_name = propname + "-set" value = None for tag in tags: if tag.get_property(is_set_name): if value is None: value = tag.get_property(propname) if comb_oper is None: return value else: value = comb_oper(value, tag.get_property(propname)) return value class _FakeAttrs(object): __slots__ = ("font", "font_scale") def _get_current_attributes(self): attrs = self._FakeAttrs() attrs.font_scale = self._get_current_style_attr("scale", operator.mul) if attrs.font_scale is None: attrs.font_scale = 1.0 attrs.font = self._get_current_style_attr("font-desc") if attrs.font is None: attrs.font = self.textview.style.font_desc return attrs def __parse_length_frac_size_allocate(self, textview, allocation, frac, callback, args): callback(allocation.width*frac, *args) def _parse_length(self, value, font_relative, callback, *args): '''Parse/calc length, converting to pixels, calls callback(length, *args) when the length is first computed or changes''' if value.endswith('%'): frac = float(value[:-1])/100 if font_relative: attrs = self._get_current_attributes() font_size = attrs.font.get_size() / pango.SCALE callback(frac*display_resolution*font_size, *args) else: ## CSS says "Percentage values: refer to width of the closest ## block-level ancestor" ## This is difficult/impossible to implement, so we use ## textview width instead; a reasonable approximation.. alloc = self.textview.get_allocation() self.__parse_length_frac_size_allocate(self.textview, alloc, frac, callback, args) self.textview.connect("size-allocate", self.__parse_length_frac_size_allocate, frac, callback, args) elif value.endswith('pt'): # points callback(float(value[:-2])*display_resolution, *args) elif value.endswith('em'): # ems, the height of the element's font attrs = self._get_current_attributes() font_size = attrs.font.get_size() / pango.SCALE callback(float(value[:-2])*display_resolution*font_size, *args) elif value.endswith('ex'): # x-height, ~ the height of the letter 'x' ## FIXME: figure out how to calculate this correctly ## for now 'em' size is used as approximation attrs = self._get_current_attributes() font_size = attrs.font.get_size() / pango.SCALE callback(float(value[:-2])*display_resolution*font_size, *args) elif value.endswith('px'): # pixels callback(int(value[:-2]), *args) else: warnings.warn("Unable to parse length value '%s'" % value) def __parse_font_size_cb(length, tag): tag.set_property("size-points", length/display_resolution) __parse_font_size_cb = staticmethod(__parse_font_size_cb) def _parse_style_font_size(self, tag, value): try: scale = { "xx-small": pango.SCALE_XX_SMALL, "x-small": pango.SCALE_X_SMALL, "small": pango.SCALE_SMALL, "medium": pango.SCALE_MEDIUM, "large": pango.SCALE_LARGE, "x-large": pango.SCALE_X_LARGE, "xx-large": pango.SCALE_XX_LARGE, } [value] except KeyError: pass else: attrs = self._get_current_attributes() tag.set_property("scale", scale / attrs.font_scale) return if value == 'smaller': tag.set_property("scale", pango.SCALE_SMALL) return if value == 'larger': tag.set_property("scale", pango.SCALE_LARGE) return self._parse_length(value, True, self.__parse_font_size_cb, tag) def _parse_style_font_style(self, tag, value): try: style = { "normal": pango.STYLE_NORMAL, "italic": pango.STYLE_ITALIC, "oblique": pango.STYLE_OBLIQUE, } [value] except KeyError: warnings.warn("unknown font-style %s" % value) else: tag.set_property("style", style) def __frac_length_tag_cb(length, tag, propname): tag.set_property(propname, length) __frac_length_tag_cb = staticmethod(__frac_length_tag_cb) def _parse_style_margin_left(self, tag, value): self._parse_length(value, False, self.__frac_length_tag_cb, tag, "left-margin") def _parse_style_margin_right(self, tag, value): self._parse_length(value, False, self.__frac_length_tag_cb, tag, "right-margin") def _parse_style_font_weight(self, tag, value): ## TODO: missing 'bolder' and 'lighter' try: weight = { '100': pango.WEIGHT_ULTRALIGHT, '200': pango.WEIGHT_ULTRALIGHT, '300': pango.WEIGHT_LIGHT, '400': pango.WEIGHT_NORMAL, '500': pango.WEIGHT_NORMAL, '600': pango.WEIGHT_BOLD, '700': pango.WEIGHT_BOLD, '800': pango.WEIGHT_ULTRABOLD, '900': pango.WEIGHT_HEAVY, 'normal': pango.WEIGHT_NORMAL, 'bold': pango.WEIGHT_BOLD, } [value] except KeyError: warnings.warn("unknown font-style %s" % value) else: tag.set_property("weight", weight) def _parse_style_font_family(self, tag, value): tag.set_property("family", value) def _parse_style_text_align(self, tag, value): try: align = { 'left': gtk.JUSTIFY_LEFT, 'right': gtk.JUSTIFY_RIGHT, 'center': gtk.JUSTIFY_CENTER, 'justify': gtk.JUSTIFY_FILL, } [value] except KeyError: warnings.warn("Invalid text-align:%s requested" % value) else: tag.set_property("justification", align) def _parse_style_text_decoration(self, tag, value): if value == "none": tag.set_property("underline", pango.UNDERLINE_NONE) tag.set_property("strikethrough", False) elif value == "underline": tag.set_property("underline", pango.UNDERLINE_SINGLE) tag.set_property("strikethrough", False) elif value == "overline": warnings.warn("text-decoration:overline not implemented") tag.set_property("underline", pango.UNDERLINE_NONE) tag.set_property("strikethrough", False) elif value == "line-through": tag.set_property("underline", pango.UNDERLINE_NONE) tag.set_property("strikethrough", True) elif value == "blink": warnings.warn("text-decoration:blink not implemented") else: warnings.warn("text-decoration:%s not implemented" % value) ## build a dictionary mapping styles to methods, for greater speed __style_methods = dict() for style in ["background-color", "color", "font-family", "font-size", "font-style", "font-weight", "margin-left", "margin-right", "text-align", "text-decoration"]: try: method = locals()["_parse_style_%s" % style.replace('-', '_')] except KeyError: warnings.warn("Style attribute '%s' not yet implemented" % style) else: __style_methods[style] = method del style ## -- def _get_style_tags(self): return [tag for tag in self.styles if tag is not None] def _begin_span(self, style, tag=None): if style is None: self.styles.append(tag) return None if tag is None: tag = self.textbuf.create_tag() for attr, val in [item.split(':', 1) for item in style.split(';')]: attr = attr.strip().lower() val = val.strip() try: method = self.__style_methods[attr] except KeyError: warnings.warn("Style attribute '%s' requested " "but not yet implemented" % attr) else: method(self, tag, val) self.styles.append(tag) def _end_span(self): self.styles.pop(-1) def _insert_text(self, text): tags = self._get_style_tags() if tags: self.textbuf.insert_with_tags(self.iter, text, *tags) else: self.textbuf.insert(self.iter, text) def _flush_text(self): if not self.text: return if not self.is_pre: text = self.text.replace('\n', '') else: text = self.text self._insert_text(text) self.text = '' def _anchor_event(self, tag, textview, event, iter, href, type_): if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: self.textview.emit("url-clicked", href, type_) return True return False def characters(self, content): self.text += content return if allwhitespace_rx.match(content) is not None: return if self.text: self.text += ' ' self.text += whitespace_rx.sub(' ', content) def startElement(self, name, attrs): self._flush_text() try: style = attrs['style'] except KeyError: style = None tag = None if name == 'a': tag = self.textbuf.create_tag() tag.set_property('foreground', '#0000ff') tag.set_property('underline', pango.UNDERLINE_SINGLE) try: type_ = attrs['type'] except KeyError: type_ = None tag.connect('event', self._anchor_event, attrs['href'], type_) tag.is_anchor = True if name in ['pre', 'tt']: tag = self.textbuf.create_tag() tag.set_property('family', 'Monospace') self.is_pre = True self._begin_span(style, tag) if name == 'br': pass # handled in endElement elif name == 'p': if not self.iter.starts_line(): self._insert_text("\n") elif name == 'div': if not self.iter.starts_line(): self._insert_text("\n") elif name == 'span': pass elif name == 'ul': if not self.iter.starts_line(): self._insert_text("\n") self.list_counters.insert(0, None) elif name == 'ol': if not self.iter.starts_line(): self._insert_text("\n") self.list_counters.insert(0, 0) elif name == 'li': if self.list_counters[0] is None: li_head = unichr(0x2022) else: self.list_counters[0] += 1 li_head = "%i." % self.list_counters[0] self.text = ' '*len(self.list_counters)*4 + li_head + ' ' elif name == 'img': try: ## Max image size = 10 MB (to try to prevent DoS) mem = urllib2.urlopen(attrs['src']).read(10*1024*1024) ## Caveat: GdkPixbuf is known not to be safe to load ## images from network... this program is now potentially ## hackable ;) loader = gtk.gdk.PixbufLoader() loader.write(mem); loader.close() pixbuf = loader.get_pixbuf() except Exception, ex: pixbuf = None try: alt = attrs['alt'] except KeyError: alt = "Broken image" if pixbuf is not None: tags = self._get_style_tags() if tags: tmpmark = self.textbuf.create_mark(None, self.iter, True) self.textbuf.insert_pixbuf(self.iter, pixbuf) if tags: start = self.textbuf.get_iter_at_mark(tmpmark) for tag in tags: self.textbuf.apply_tag(tag, start, self.iter) self.textbuf.delete_mark(tmpmark) else: self._insert_text("[IMG: %s]" % alt) elif name == 'body': pass elif name == 'a': pass elif name in ['pre', 'tt']: pass # handled above else: warnings.warn("Unhandled element '%s'" % name) def endElement(self, name): self._flush_text() if name == 'p': if not self.iter.starts_line(): self._insert_text("\n") elif name == 'div': if not self.iter.starts_line(): self._insert_text("\n") elif name == 'span': pass elif name == 'br': self._insert_text("\n") elif name == 'ul': self.list_counters.pop() elif name == 'ol': self.list_counters.pop() elif name == 'li': self._insert_text("\n") elif name == 'img': pass elif name == 'body': pass elif name == 'a': pass elif name in ['pre', 'tt']: self.is_pre = False else: warnings.warn("Unhandled element '%s'" % name) self._end_span() class HtmlTextView(gtk.TextView): __gtype_name__ = 'HtmlTextView' __gsignals__ = { 'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type } def __init__(self): gtk.TextView.__init__(self) self.set_wrap_mode(gtk.WRAP_CHAR) self.set_editable(False) self._changed_cursor = False self.connect("motion-notify-event", self.__motion_notify_event) self.connect("leave-notify-event", self.__leave_event) self.connect("enter-notify-event", self.__motion_notify_event) self.set_pixels_above_lines(3) self.set_pixels_below_lines(3) def __leave_event(self, widget, event): if self._changed_cursor: window = widget.get_window(gtk.TEXT_WINDOW_TEXT) window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) self._changed_cursor = False def __motion_notify_event(self, widget, event): x, y, _ = widget.window.get_pointer() x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) tags = widget.get_iter_at_location(x, y).get_tags() for tag in tags: if getattr(tag, 'is_anchor', False): is_over_anchor = True break else: is_over_anchor = False if not self._changed_cursor and is_over_anchor: window = widget.get_window(gtk.TEXT_WINDOW_TEXT) window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) self._changed_cursor = True elif self._changed_cursor and not is_over_anchor: window = widget.get_window(gtk.TEXT_WINDOW_TEXT) window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM)) self._changed_cursor = False return False def clear_html(self): self.get_buffer().set_text('') def display_html(self, html): html = '
%s
' % html buffer = self.get_buffer() eob = buffer.get_end_iter() ## this works too if libxml2 is not available parser = xml.sax.make_parser(['drv_libxml2']) # parser.setFeature(xml.sax.handler.feature_validation, True) parser.setContentHandler(HtmlHandler(self, eob)) sio = StringIO() sio.write(html) sio.flush() sio.seek(0) parser.parse(sio) if not eob.starts_line(): buffer.insert(eob, "\n") if gobject.pygtk_version < (2, 8): gobject.type_register(HtmlTextView) if __name__ == '__main__': htmlview = HtmlTextView() def url_cb(view, url, type_): print "url-clicked", url, type_ htmlview.connect("url-clicked", url_cb) htmlview.display_html('
Hello
\n' '
\n' ' World\n' '
\n') htmlview.display_html("
") htmlview.display_html("""

OMG, I'm green with envy!

""") htmlview.display_html("
") htmlview.display_html("""

As Emerson said in his essay Self-Reliance:

"A foolish consistency is the hobgoblin of little minds."

""") htmlview.display_html("
") htmlview.display_html("""

Hey, are you licensed to Jabber?

A License to Jabber

""") htmlview.display_html("""
  • One
  • Two
  • Three
""") htmlview.display_html("""
  1. One
  2. Two
  3. Three
""") htmlview.show() sw = gtk.ScrolledWindow() sw.set_property("hscrollbar-policy", gtk.POLICY_AUTOMATIC) sw.set_property("vscrollbar-policy", gtk.POLICY_AUTOMATIC) sw.set_property("border-width", 0) sw.add(htmlview) sw.show() frame = gtk.Frame() frame.set_shadow_type(gtk.SHADOW_IN) frame.show() frame.add(sw) w = gtk.Window() w.add(frame) w.set_default_size(400, 300) w.show_all() w.connect("destroy", lambda w: gtk.main_quit()) gtk.main() PIDA-0.5.1/pida/ui/icons.py0000644000175000017500000000411310652670647013361 0ustar alialiimport os import gtk, gtk.gdk from kiwi.environ import environ class IconRegister(object): def __init__(self): self._factory = gtk.IconFactory() self._factory.add_default() self._register_theme_icons() self._register_file_icons() def _register_file_icons(self): for directory in environ.get_resource_paths('pixmaps'): self.register_file_icons_for_directory(directory) def register_file_icons_for_directory(self, directory): for filename in os.listdir(directory): name, ext = os.path.splitext(filename) if ext in ['.png', '.gif', '.svg', '.jpg']: path = os.path.join(directory, filename) self._stock_add(name) self._register_file_icon(name, path) def _register_theme_icons(self): stock_ids = gtk.stock_list_ids() for name in gtk.icon_theme_get_default().list_icons(): if name not in stock_ids: self._stock_add(name) self._register_theme_icon(name) def _stock_add(self, name, label=None): if label is None: label = name.capitalize() gtk.stock_add([(name, label, 0, 0, None)]) def _register_theme_icon(self, name): icon_set = gtk.IconSet() self._register_icon_set(icon_set, name) def _register_file_icon(self, name, filename): #im = gtk.Image() #im.set_from_file(filename) #pb = im.get_pixbuf() pb = gtk.gdk.pixbuf_new_from_file_at_size(filename, 32, 32) icon_set = gtk.IconSet(pb) self._register_icon_set(icon_set, name) # this is broken for some reason #gtk.icon_theme_add_builtin_icon(name, gtk.ICON_SIZE_SMALL_TOOLBAR, pb) def _register_icon_set(self, icon_set, name): source = gtk.IconSource() source.set_icon_name(name) icon_set.add_source(source) self._factory.add(name, icon_set) #w = gtk.Window() #i = gtk.Image() #i.set_from_stock("foo", gtk.ICON_SIZE_DIALOG) #w.add(i) #w.show_all() #w.connect('destroy', gtk.main_quit) #gtk.main() PIDA-0.5.1/pida/ui/interfaces.py0000644000175000017500000000045210652670647014373 0ustar aliali from protocols import Interface class IView(Interface): def get_unique_id(self): """Return the unique id for the view""" def get_toplevel(self): """Return the toplevel widget for the view""" def create_tab_label(self): """Create a tab label for the view""" PIDA-0.5.1/pida/ui/objectlist.py0000644000175000017500000001043110652670647014410 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from kiwi.ui.objectlist import Column _ = lambda t: t class AttrSortCombo(gtk.HBox): def __init__(self, objectlist, attributes, default): """ @var objectlist: the objectlist to act on @var attributes: a sequence of attribute, title pairs @var default: the default attribute to sort by """ gtk.HBox.__init__(self, spacing=3) self.set_border_width(3) self._objectlist = objectlist self._model = gtk.ListStore(str, str) self._order_button = gtk.ToggleToolButton( stock_id=gtk.STOCK_SORT_DESCENDING) self._order_button.connect('toggled', self._on_order_toggled) self._order_button.show() # Use a real combo to avoid internal dependency self._combo = gtk.ComboBox(model=self._model) self._combo.set_size_request(1, 1) self._combo.connect('changed', self._on_selection_changed) cell = gtk.CellRendererText() self._combo.pack_start(cell, True) self._combo.add_attribute(cell, 'text', 1) for name, title in attributes: iter = self._model.append((name, title)) if name == default: self._combo.set_active_iter(iter) self._combo.show_all() self._label = gtk.Label(_('Sort')) self._label.show() self.pack_start(self._label, expand=False) self.pack_start(self._combo) self.pack_start(self._order_button, expand=False) def _on_selection_changed(self, combo): self._sort() def _on_order_toggled(self, button): self._sort() def _sort(self): self._objectlist.sort_by_attribute(self._get_attribute(), self._get_order()) def _get_order(self): if self._order_button.get_active(): return gtk.SORT_DESCENDING else: return gtk.SORT_ASCENDING def _get_attribute(self): return self._model[self._combo.get_active_iter()][0] def sort_by_attribute(self, attribute, order=gtk.SORT_ASCENDING): """Sort by an attribute in the model.""" def _sort_func(model, iter1, iter2): attr1 = getattr(model[iter1][0], attribute, None) attr2 = getattr(model[iter2][0], attribute, None) return cmp(attr1, attr2) unused_sort_col_id = len(self._columns) self._model.set_sort_func(unused_sort_col_id, _sort_func) self._model.set_sort_column_id(unused_sort_col_id, order) class PBC(Column): pb = None def __init__(self, *args, **kw): self.pb = None Column.__init__(self, use_stock=True, *args, **kw) def cell_data_func(self, tree_column, renderer, model, treeiter, (column, renderer_prop)): "To render the data of a cell renderer pixbuf" row = model[treeiter] data = column.get_attribute(row[COL_MODEL], column.attribute, None) if data is not None: if self.pb is None: self.pb = gtk.gdk.pixbuf_new_from_file(data) pixbuf = self.pb renderer.set_property(renderer_prop, pixbuf) print pixbuf # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/ui/paneds.py0000644000175000017500000000616210652670647013526 0ustar alialiimport gtk from moo_stub import BigPaned, PaneLabel, PaneParams from pida.utils.gthreads import gcall PANE_TERMINAL = 'Terminal' PANE_EDITOR = 'Editor' PANE_BUFFER = 'Buffer' PANE_PLUGIN = 'Plugin' POS_MAP = { PANE_TERMINAL: gtk.POS_BOTTOM, PANE_BUFFER: gtk.POS_LEFT, PANE_PLUGIN: gtk.POS_RIGHT, } class PidaPaned(BigPaned): def __init__(self): BigPaned.__init__(self) self.set_property('enable-detaching', True) for pane in self.get_all_paneds(): pane.set_pane_size(200) #pane.set_sticky_pane(True) def get_all_pos(self): return [gtk.POS_BOTTOM, gtk.POS_LEFT, gtk.POS_RIGHT] def get_all_paneds(self): for pos in self.get_all_pos(): yield self.get_paned(pos) def add_view(self, name, view, removable=True, present=True): if name == PANE_EDITOR: self.add_child(view.get_toplevel()) else: POS = POS_MAP[name] lab = PaneLabel(view.icon_name, None, view.label_text) pane = self.insert_pane(view.get_toplevel(), lab, POS, POS) if not removable: pane.set_property('removable', False) pane.connect('remove', view.on_remove_attempt) if present: gcall(self.present_pane, view.get_toplevel()) self.show_all() def remove_view(self, view): self.remove_pane(view.get_toplevel()) def detach_view(self, view, size=(400,300)): paned, pos = self.find_pane(view.get_toplevel()) paned.detach_pane(pos) self._center_on_parent(view, size) def present_view(self, view): pane, pos = self.find_pane(view.get_toplevel()) pane.present() def get_open_pane(self, name): POS = POS_MAP[name] paned = self.get_paned(POS) pane = paned.get_open_pane() return paned, pane def switch_next_pane(self, name): paned, pane = self.get_open_pane(name) if pane is None: num = -1 else: num = pane.get_index() newnum = num + 1 if newnum == paned.n_panes(): newnum = 0 newpane = paned.get_nth_pane(newnum) newpane.present() def switch_prev_pane(self, name): paned, pane = self.get_open_pane(name) if pane is None: num = paned.n_panes() else: num = pane.get_index() newnum = num - 1 if newnum == -1: newnum = paned.n_panes() - 1 newpane = paned.get_nth_pane(newnum) newpane.present() def present_paned(self, name): paned, pane = self.get_open_pane(name) if pane is None: num = 0 else: num = pane.get_index() pane = paned.get_nth_pane(num) if pane is not None: pane.present() def _center_on_parent(self, view, size): gdkwindow = view.get_parent_window() px, py, pw, ph, pbd = view.svc.boss.get_window().window.get_geometry() w, h = size cx = (pw - w) / 2 cy = (ph - h) / 2 gdkwindow.move_resize(cx, cy, w, h) #gdkwindow.resize(w, h) PIDA-0.5.1/pida/ui/popupwindow.py0000644000175000017500000001445210652670647014650 0ustar aliali import gtk, gobject class PopupWindow(gtk.Window): __gtype_name__ = 'PopupWindow' def __init__(self): gtk.Window.__init__(self) self.set_decorated(False) self.add_events(gtk.gdk.FOCUS_CHANGE_MASK) #self.connect('set-focus-child', self.on_focus_child) self.connect('focus-out-event', self.focus_out) self.connect('focus-in-event', self.focus_in) def focus_out(self, window, event): print 'fo' def focus_in(self, window, event): print 'fi' #def do_set_focus_child(self, widget): # print widget gobject.type_register(PopupWindow) class PopupEntryWindow(PopupWindow): def __init__(self): PopupWindow.__init__(self) self._create_ui() self._vals = {} def _create_ui(self): self._frame = gtk.Frame() self.add(self._frame) self._frame.set_border_width(6) hb = gtk.HBox(spacing=6) hb.set_border_width(6) self._frame.add(hb) vb = gtk.VBox() hb.pack_start(vb, expand=False) vb.pack_start(self._create_labels(), expand=False) vb.pack_start(self._create_entries_box(), expand=False) hb.pack_start(self._create_preview_pane(), expand=False) def _create_labels(self): vb = gtk.VBox() self._primary_label = gtk.Label() vb.pack_start(self._primary_label, expand=False) return vb def _create_preview_pane(self): self._preview_text = gtk.TextView() self._preview_text.set_left_margin(6) self._preview_text.set_right_margin(6) self._preview_text.set_cursor_visible(False) self._preview_text.set_editable(False) #self._preview_text.set_app_paintable(True) #self._preview_text.connect('expose-event', self._on_preview_text_expose_event) #w = gtk.Window() #w.set_name('gtk-tooltips') #self._ttstyle = w.style #self._preview_text.modify_base(gtk.STATE_NORMAL, #self._ttstyle.base[gtk.STATE_NORMAL]) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self._preview_text) sw.set_size_request(200, 200) return sw def _create_entries_box(self): self._entries = {} self._entries_order = [] self._entries_vb = gtk.VBox(spacing=6) self._label_sizer = gtk.SizeGroup(gtk.ORIENTATION_HORIZONTAL) return self._entries_vb def add_entry(self, name, label_text): hb = gtk.HBox(spacing=6) label = gtk.Label() label.set_text(label_text) self._label_sizer.add_widget(label) hb.pack_start(label) entry = gtk.Entry() hb.pack_start(entry, expand=False) entry.connect('changed', self.on_entry_changed, name) self._entries_vb.pack_start(hb, expand=False) self._entries[name] = entry self._entries_order.append(entry) entry.connect('key-press-event', self.on_entry_keypress, name) def grab_entry(self): self._entry.grab_focus() def set_title_label(self, value): self._frame.set_label(value) def set_primary_label(self, value): self._primary_label.set_text(value) def set_secondary_label(self, value): self._secondary_label.set_text(value) def on_entry_changed(self, entry, name): self._vals[name] = entry.get_text() self._preview_text.get_buffer().set_text(self._snippet.substitute(self._vals)) def on_entry_keypress(self, entry, event, name): print name, [event.keyval, event.state, event.string] if event.string == '\r': if event.state & gtk.gdk.CONTROL_MASK: print 'would exit' else: index = self._entries_order.index(entry) if index == len(self._entries_order) - 1: print 'would exit' else: self._entries_order[index + 1].grab_focus() elif event.string == '\x1b': print 'would exit' def _on_preview_text_expose_event(self, textview, event): #self._ttstyle.attach(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT)) textview.style.paint_flat_box(textview.get_window(gtk.TEXT_WINDOW_TEXT), gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, textview, "tooltip", 0, 0, -1, -1) #self._ttstyle.apply_default_background(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT), # True, gtk.STATE_NORMAL, None, 0, 0, -1, -1) #self._ttstyle.set_background(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT), # gtk.STATE_NORMAL) return False class MockSnippet(object): def __init__(self): self.title = 'HTML Tag' #from string import Template #self._t = Template('<$tag class="$class">\n') from jinja import from_string self._t = from_string("""<{{ tag }}{% if class %} class="{{ class }}"{% endif %}>\n""") #self._defaults = {'class': '', 'extra': ''} self._t = from_string( ''' {# This is a comment #} class {{ name }}({% if super %}{{ super }}{% else %}object{% endif %}): {% if docstring %} """{{ docstring }}"""\n{% endif %} def __init__(self): ''' ) def get_variables(self): #nodupes = [] #[nodupes.append(''.join(i)) for i in #self._t.pattern.findall(self._t.template) if ''.join(i) not in nodupes] return ['name', 'super', 'docstring'] def substitute(self, vals): #newvals = dict(vals) #for k in self._defaults: # if k not in newvals: # newvals[k] = self._defaults[k] return self._t.render(vals) class MulitpleVariableWindow(PopupEntryWindow): def __init__(self, snippet): PopupEntryWindow.__init__(self) self._snippet = snippet self.set_title_label(self._snippet.title) self._create_entries() def _create_entries(self): for variable in self._snippet.get_variables(): self.add_entry(variable, variable) if __name__ == '__main__': w1 = gtk.Window() w1.resize(400, 400) w1.add(gtk.TextView()) w1.show_all() w = MulitpleVariableWindow(MockSnippet()) w.set_transient_for(w1) w.show_all() gtk.main() PIDA-0.5.1/pida/ui/splash.py0000644000175000017500000000373210652670647013546 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk, gobject from pida.utils.testing import refresh_gui # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class SplashScreen(gtk.Window): def __init__(self): gtk.Window.__init__(self) self.set_decorated(False) vb = gtk.VBox() self.add(vb) l = gtk.Label(_('PIDA is starting...')) l.set_alignment(0.5, 1) l.show() vb.pack_start(l) l = gtk.Label() l.set_markup(_('and it loves you!')) l.set_alignment(0.5, 0) l.show() vb.pack_start(l) vb.show() self.resize(200, 75) self.set_position(gtk.WIN_POS_CENTER_ALWAYS) def show_splash(self): self.show() refresh_gui() def hide_splash(self): self.hide_all() self.destroy() # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/ui/terminal.py0000644000175000017500000002232410652670647014065 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """ The PIDA Terminal Widget The widget `PidaTerminal` encapsulates some of the common functions of VTE in a more usable format. """ from math import floor import re import gtk from kiwi.utils import gsignal, type_register from vte import Terminal # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class TerminalMatch(object): """ A match for terminal text """ def __init__(self, name, match_re, match_groups_re, callback): self.name = name self.match_re = match_re self.match_groups_re = re.compile(match_groups_re) self.callback = callback def __call__(self, *args, **kw): self.callback(*args, **kw) class TerminalMenuMatch(TerminalMatch): """ A match for terminal text that pops up a menu """ def __init__(self, name, match_re, match_groups_re, actions=None): TerminalMatch.__init__(self, name, match_re, match_groups_re, self._popup) self.actions = [] def _popup(self, event, *args): menu = self._generate_menu(args) menu.popup(None, None, None, event.button, event.time) def _generate_menu(self, args): menu = gtk.Menu() for action in self.actions: menu_item = action.create_menu_item() menu.add(menu_item) action.match_args = args return menu def register_action(self, action): """ Register an action with the menu for this match :param action: A gtk.Action """ self.actions.append(action) class PidaTerminal(Terminal): __gtype_name__ = 'PidaTerminal' gsignal('match-right-clicked', gtk.gdk.Event, int, str) def __init__(self, **kw): Terminal.__init__(self) self._fix_size() self._fix_events() self._connect_internal() self._init_matches() self.set_properties(**kw) def set_properties(self, **kw): """ Set properties on the widget """ for key, val in kw.items(): getattr(self, 'set_%s' % key)(val) def _fix_size(self): """ Fix the size of the terminal. Initially the widget starts very large, and is unable to be resized by conventional means. """ self.set_size_request(50, 50) def _fix_events(self): self.add_events(gtk.gdk.BUTTON_PRESS_MASK) def _connect_internal(self): """ Connect the internal signals """ self.connect('button-press-event', self._on_button_press) self.connect('match-right-clicked', self._on_match_right_clicked) def _init_matches(self): """ Initialize the matching system """ self._matches = {} def _get_position_from_pointer(self, x, y): """ Get the row/column position for a pointer position """ cw = self.get_char_width() ch = self.get_char_height() return int(floor(x / cw)), int(floor(y / ch)) def _on_button_press(self, term, event): """ Called on a button press """ if event.button == 3: col, row = self._get_position_from_pointer(event.x, event.y) match = self.match_check(col, row) if match is not None: match_str, match_num = match self.emit('match-right-clicked', event, match_num, match_str) def _on_match_right_clicked(self, term, event, match_num, match_str): """ Called when there is a right click on the terminal. Internally, this checks whether there has been a match, and fires the required call back or menu. """ if match_num in self._matches: rematch = self._matches[match_num].match_groups_re.match(match_str) match_val = [match_str] if rematch is not None: groups = rematch.groups() if groups: match_val = groups self._matches[match_num](event, *match_val) def get_named_match(self, name): """ Get a match object for the name :param name: the name of the match object :raises KeyError: If the named match does not exist """ for match in self._matches.values(): if match.name == name: return match raise KeyError(_('No match named "%s" was found') % name) def match_add_match(self, match): """ Add a match object. """ match_num = self.match_add(match.match_re) self._matches[match_num] = match return match_num def match_add_callback(self, name, match_str, match_groups, callback): """ Add a match with a callback. :param name: the name of the match :param match_str: the regular expression to match :param match_groups: a regular expression of groups which wil be passed as parameters to the callback function :param callback: the callback function to be called with the result of the match """ match = TerminalMatch(name, match_str, match_groups, callback) return self.match_add_match(match) def match_add_menu(self, name, match_str, match_groups, menu=None): """ Add a menu match object. """ match = TerminalMenuMatch(name, match_str, match_groups, menu) return self.match_add_match(match) def match_menu_register_action(self, name, action): """ Register an action with the named match :param name: The name of the match :param action: A gtk.Action to use in the menu """ self.get_named_match(name).register_action(action) def feed_text(self, text, color=None): """ Feed text to the terminal, optionally coloured. """ if color is not None: text = '\x1b[%sm%s\x1b[0m' % (color, text) self.feed(text) def get_all_text(self): col, row = self.get_cursor_position() return self.get_text_range(0, 0, row, col, lambda *a: True) class popen(object): def __init__(self, cmdargs, callback, kwargs): self.__running = False self.__readbuf = [] self.__callback = callback self.run(cmdargs, **kwargs) def run(self, cmdargs, **kwargs): console = subprocess.Popen(args=cmdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) self.__running = True self.__readtag = gobject.io_add_watch( console.stdout, gobject.IO_IN, self.cb_read) self.__huptag = gobject.io_add_watch( console.stdout, gobject.IO_HUP, self.cb_hup) self.pid = console.pid def cb_read(self, fd, cond): data = os.read(fd.fileno(), 1024) self.__readbuf.append(data) return True def cb_hup(self, fd, cond): while True: data = os.read(fd.fileno(), 1024) if data == '': break self.__readbuf.append(data) self.__callback(''.join(self.__readbuf)) self.__running = False gobject.source_remove(self.__readtag) return False if __name__ == '__main__': w = gtk.Window() w.resize(400,400) t = PidaTerminal( word_chars = "-A-Za-z0-9,./?%&#_\\~" ) t.fork_command('bash') def mc(event, val): print event, val t.match_add_callback('python-line', 'line [0-9]+', 'line ([0-9]+)', mc) t.match_add_menu('file', r'/[/A-Za-z0-9_\-]+', '') a = gtk.Action('open', _('Open'), _('Open this file'), gtk.STOCK_OPEN) b = gtk.Action('save', _('Save'), _('Save This File'), gtk.STOCK_SAVE) def act(action): print action.match_args t.set_background_image_file('/usr/share/xfce4/backdrops/flower.png') a.connect('activate', act) t.match_menu_register_action('file', a) t.match_menu_register_action('file', b) w.add(t) w.show_all() t.fork_command('bash') gtk.main() PIDA-0.5.1/pida/ui/uimanager.py0000644000175000017500000000436710652670647014231 0ustar aliali import gtk from pida.core.environment import get_uidef_path # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext base_menu_actions = [ ('FileMenu', None, _('File'), 'f', _('File Menu'), None), ('EditMenu', None, _('Edit'), 'e', _('Edit Menu'), None), ('ProjectMenu', None, _('Project'), 'p', _('Project Menu'), None), ('LanguageMenu', None, _('Language'), 'l', _('Language Menu'), None), ('DebugMenu', None, _('Debug'), 'l', _('Debug Menu'), None), ('ToolsMenu', None, _('Tools'), 't', _('Tools Menu'), None), ('ToolsDebug', None, _('Debug Pida'), '', _('Debug Pida Menu'), None), ('ViewMenu', None, _('View'), 'v', _('View Menu'), None), ('HelpMenu', None, _('Help'), 'h', _('Help Menu'), None), ] class PidaUIManager(object): def __init__(self): self._uim = gtk.UIManager() self._ags = {} self._load_base_actions() self._load_base_ui() def _load_base_ui(self): uidef = get_uidef_path('base.xml') self.add_ui_from_file(uidef) def _load_base_actions(self): self._base_ag = gtk.ActionGroup(name='base_actions') self._base_ag.add_actions(base_menu_actions) self.add_action_group(self._base_ag) def add_action_group(self, group): self._uim.insert_action_group(group, len(self._ags)) self._ags[group.get_name()] = group self.ensure_update() def remove_action_group(self, group): self._uim.remove_action_group(group) del self._ags[group.get_name()] self.ensure_update() def get_toolbar(self): return self._uim.get_toplevels(gtk.UI_MANAGER_TOOLBAR)[0] def get_menubar(self): return self._uim.get_toplevels(gtk.UI_MANAGER_MENUBAR)[0] def add_ui_from_file(self, path): merge_id = self._uim.add_ui_from_file(path) self.ensure_update() return merge_id def add_ui_from_string(self, string): merge_id = self._uim.add_ui_from_string(string) self.ensure_update() return merge_id def remove_ui(self, ui_merge_id): self._uim.remove_ui(ui_merge_id) self.ensure_update() def ensure_update(self): self._uim.ensure_update() PIDA-0.5.1/pida/ui/views.py0000644000175000017500000000752710652670647013417 0ustar alialiimport gtk, gobject from kiwi.ui.delegates import GladeSlaveDelegate, SlaveDelegate from kiwi.utils import gsignal, gproperty, type_register, PropertyObject from pida.core.environment import get_pixmap_path from pida.utils.unique import create_unique_id # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class PidaViewWidget(PropertyObject, gtk.VBox): __gtype_name__ = 'PidaViewWidget' gproperty('title-text', str, default=_('Untitled Pida View')) gsignal('close-clicked') gsignal('detach-clicked') def __init__(self): gtk.VBox.__init__(self) self._child = None self._create_ui() PropertyObject.__init__(self) def _create_ui(self): self._create_top_bar() self._create_widget_holder() def _create_top_bar(self): self._top_bar = gtk.HBox() self.pack_start(self._top_bar, expand=False) self._title_label = gtk.Label() self._top_bar.pack_start(self._title_label) self._top_buttons = gtk.HBox() self._top_bar.pack_start(self._top_buttons, expand=False) self._detach_button = gtk.ToolButton(icon_widget=self._create_detach_button()) self._top_buttons.pack_start(self._detach_button) self._close_button = gtk.ToolButton(icon_widget=self._create_close_button()) self._top_buttons.pack_start(self._close_button) def _create_widget_holder(self): self._widget_holder = gtk.Frame() self.pack_start(self._widget_holder) def _create_close_button(self): im = gtk.Image() im.set_from_file(get_pixmap_path('view_close.gif')) return im def _create_detach_button(self): im = gtk.Image() im.set_from_file(get_pixmap_path('view_detach.gif')) return im def prop_set_title_text(self, val): if val is not None: self._title_label.set_text(val) def add_main_widget(self, child): self._widget_holder.add(child) self._child = child def remove_main_widget(self): self._widget_holder.remove(self._child) def get_main_widget(self): return self._child def do_add(self, widget): self.add_main_widget(widget) class PidaViewMixin(object): icon_name = gtk.STOCK_INFO label_text = _('Pida View') def create_ui(self): """Create the user interface here""" def get_unique_id(self): return self._uid def create_tab_label_icon(self): return gtk.image_new_from_stock(self.icon_name, gtk.ICON_SIZE_MENU) def get_tab_label_text(self): return self.label_text def get_parent_window(self): return self.get_toplevel().get_parent_window() parent_window = property(get_parent_window) def on_remove_attempt(self, pane): return not self.can_be_closed() def can_be_closed(self): return False class PidaGladeView(GladeSlaveDelegate, PidaViewMixin): def __init__(self, service, title=None, icon=None, *args, **kw): if hasattr(self, 'locale') and self.locale is not None: self.locale.bindglade() self.svc = service GladeSlaveDelegate.__init__(self, *args, **kw) self._uid = create_unique_id() self.label_text = title or self.label_text self.icon_name = icon or self.icon_name self.create_ui() class PidaView(SlaveDelegate, PidaViewMixin): def __init__(self, service, title=None, icon=None, *args, **kw): self.svc = service self._main_widget = gtk.VBox() SlaveDelegate.__init__(self, toplevel=self._main_widget, *args, **kw) self._uid = create_unique_id() self.label_text = title or self.label_text self.icon_name = icon or self.icon_name self.create_ui() def add_main_widget(self, widget, *args, **kw): self._main_widget.pack_start(widget, *args, **kw) PIDA-0.5.1/pida/ui/widgets.py0000644000175000017500000001225410652670647013721 0ustar aliali from cgi import escape import gtk import gobject from kiwi.ui.gadgets import gdk_color_to_string from kiwi.ui.widgets.entry import ProxyEntry from kiwi.ui.widgets.label import ProxyLabel from kiwi.ui.widgets.combo import ProxyComboBox from kiwi.ui.widgets.spinbutton import ProxySpinButton from kiwi.ui.widgets.fontbutton import ProxyFontButton from kiwi.ui.widgets.checkbutton import ProxyCheckButton from kiwi.ui.widgets.colorbutton import ProxyColorButton from kiwi.ui.widgets.filechooser import ProxyFileChooserButton from kiwi.utils import gsignal from kiwi.ui.objectlist import ObjectList, Column from pida.core.options import OTypeBoolean, OTypeString, OTypeInteger, \ OTypeStringList, OTypeFile, OTypeFont, OTypeStringOption # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class CleverProxyColorButton(ProxyColorButton): def update(self, val): col = gtk.gdk.color_parse(val) super(CleverProxyColorButton, self).update(col) def read(self): col = super(CleverProxyColorButton, self).read() return gdk_color_to_string(col) class ProxyStringListItem(object): def __init__(self, value): self.value = value class ProxyStringList(gtk.VBox): gsignal('content-changed') def __init__(self): gtk.VBox.__init__(self, spacing=3) self.set_border_width(6) self.set_size_request(0, 150) self._ol = ObjectList([Column('value', expand=True)]) self._ol.set_headers_visible(False) self._ol.connect('selection_changed', self._on_ol_selection) self._ol.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.pack_start(self._ol) hb = gtk.HButtonBox() self.value_entry = gtk.Entry() self.value_entry.connect('changed', self._on_value_changed) self.value_entry.set_sensitive(False) self.pack_start(self.value_entry, expand=False) self.add_button = gtk.Button(stock=gtk.STOCK_NEW) self.add_button.connect('clicked', self._on_add) hb.pack_start(self.add_button, expand=False) self.rem_button = gtk.Button(stock=gtk.STOCK_REMOVE) self.rem_button.connect('clicked', self._on_rem) self.rem_button.set_sensitive(False) hb.pack_start(self.rem_button, expand=False) self.pack_start(hb, expand=False) self._current = None self._block = False def _on_add(self, button): item = ProxyStringListItem('New Item') self._ol.append(item, select=True) self._emit_changed() def _on_rem(self, button): self._ol.remove(self._current, select=True) self._emit_changed() def _on_ol_selection(self, ol, item): self.rem_button.set_sensitive(item is not None) self._current = item if item is not None: self.value_entry.set_text(item.value) self.value_entry.set_sensitive(True) else: self.value_entry.set_sensitive(False) self.value_entry.set_text('') def _on_value_changed(self, entry): if self._current is not None: self._block = True self._current.value = entry.get_text() self._ol.update(self._current) self._emit_changed() def _emit_changed(self): self.emit('content-changed') def update(self, value): if not self._block: self._ol.add_list(self.create_items(value)) self._block = False def read(self): return [i.value for i in self._ol] def create_items(self, value): return [ProxyStringListItem(v) for v in value] def get_widget_for_type(rtype_instance): rtype = rtype_instance.__class__ if rtype is OTypeBoolean: return ProxyCheckButton() elif rtype is OTypeStringList: return ProxyStringList() elif rtype is OTypeFile: w = ProxyFileChooserButton(_('Select File')) w.set_action(gtk.FILE_CHOOSER_ACTION_OPEN) return w #elif rtype is types.readonlyfile: # w = ProxyFileChooserButton('Select File') # w.set_sensitive(False) # #w.set_action(gtk.FILE_CHOOSER_ACTION_SAVE) # return w #elif rtype in [types.directory]: # w = ProxyFileChooserButton(title='Select Directory') # w.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) # return w elif rtype is OTypeFont: return ProxyFontButton() #elif rtype is types.color: # return CleverProxyColorButton() elif rtype is OTypeInteger: w = ProxySpinButton() w.set_adjustment(gtk.Adjustment(0, 0, 10000, 1)) return w elif isinstance(rtype_instance, OTypeStringOption): w = ProxyComboBox() w.prefill([(v, v) for v in rtype.options]) return w #elif rtype.__name__ is 'intrange': # adjvals = rtype.lower, rtype.upper, rtype.step # adj = gtk.Adjustment(0, *adjvals) # w = ProxySpinButton() # w.set_adjustment(adj) # return w #elif rtype is types.readonly: # return FormattedLabel(VC_NAME_MU) #elif rtype.__name__ is OTypeStringList: # return w else: w = ProxyEntry(data_type=str) w.set_width_chars(18) return w PIDA-0.5.1/pida/ui/window.py0000644000175000017500000001134510652670647013562 0ustar alialiimport gtk from kiwi.ui.dialogs import save, open as opendlg, info, error, yesno#, get_input from pida.ui.uimanager import PidaUIManager from pida.ui.paneds import PidaPaned from pida.core.environment import get_uidef_path, get_pixmap_path from pida.core.actions import accelerator_group # locale from pida.core.locale import Locale locale = Locale('pida') _ = locale.gettext class Window(gtk.Window): def __init__(self, boss, *args, **kw): self._boss = boss gtk.Window.__init__(self, *args, **kw) self.set_icon_from_file(get_pixmap_path('pida-icon.png')) self.add_accel_group(accelerator_group) self.connect('delete-event', self._on_delete_event) self.create_all() def _on_delete_event(self, window, event): return self._boss.stop() def create_all(self): pass # Dialogs def save_dlg(self, *args, **kw): return save(parent = self, *args, **kw) def open_dlg(self, *args, **kw): return opendlg(parent = self, *args, **kw) def info_dlg(self, *args, **kw): return info(parent = self, *args, **kw) def error_dlg(self, *args, **kw): return error(parent = self, *args, **kw) def yesno_dlg(self, *args, **kw): return yesno(parent = self, *args, **kw) == gtk.RESPONSE_YES def error_list_dlg(self, msg, errs): return self.error_dlg('%s\n\n* %s' % (msg, '\n\n* '.join(errs))) def input_dlg(self, *args, **kw): return get_input(parent=self, *args, **kw) class PidaWindow(Window): """Main PIDA Window""" def create_all(self): self.set_title(_('PIDA Loves You!')) self._fix_paneds() self._create_ui() self.resize(800, 600) def start(self): self._start_ui() def _create_ui(self): self._uim = PidaUIManager() self.main_box = gtk.VBox() self.top_box = gtk.VBox() self.bottom_box = gtk.VBox() self._create_statusbar() self.main_box.pack_start(self.top_box, expand=False) self.main_box.pack_start(self._paned) self.main_box.pack_start(self.bottom_box, expand=False) self.main_box.pack_start(self._status_holder, expand=False) self.add(self.main_box) def _create_statusbar(self): self._statusbar = gtk.HBox() self._status_holder = gtk.Statusbar() # OMG frame = self._status_holder.get_children()[0] frame.remove(frame.get_children()[0]) frame.add(self._statusbar) def _start_ui(self): self._menubar = self._uim.get_menubar() self._toolbar = self._uim.get_toolbar() self._toolbar.set_style(gtk.TOOLBAR_ICONS) self.top_box.pack_start(self._menubar, expand=False) self.top_box.pack_start(self._toolbar, expand=False) self.top_box.show_all() self.main_box.show_all() self._statusbar.show_all() def _fix_paneds(self): self._paned = PidaPaned() # Action group API def add_action_group(self, actiongroup): self._uim.add_action_group(actiongroup) def add_uidef(self, filename): try: uifile = get_uidef_path(filename) return self._uim.add_ui_from_file(uifile) except Exception, e: self._boss.log.debug('unable to get %s resource: %s' % (filename, e)) def remove_action_group(self, actiongroup): self._uim.remove_action_group(actiongroup) def remove_uidef(self, ui_merge_id): if ui_merge_id is not None: self._uim.remove_ui(ui_merge_id) # View API def add_view(self, bookname, view, removable=True, present=False): self._paned.add_view(bookname, view, removable, present) def remove_view(self, view): self._paned.remove_view(view) def detach_view(self, view, size): self._paned.detach_view(view, size) def present_view(self, view): self._paned.present_view(view) def present_paned(self, bookname): self._paned.present_paned(bookname) def switch_next_view(self, bookname): self._paned.switch_next_pane(bookname) def switch_prev_view(self, bookname): self._paned.switch_prev_pane(bookname) def get_statusbar(self): return self._statusbar # UI hiding API def set_toolbar_visibility(self, visibility): if visibility: self._toolbar.show_all() else: self._toolbar.hide_all() def set_menubar_visibility(self, visibility): if visibility: self._menubar.show_all() else: self._menubar.hide_all() def set_statusbar_visibility(self, visibility): if visibility: self._statusbar.show_all() else: self._statusbar.hide_all() PIDA-0.5.1/pida/utils/0002755000175000017500000000000010652671502012411 5ustar alialiPIDA-0.5.1/pida/utils/anyvc/0002755000175000017500000000000010652671502013531 5ustar alialiPIDA-0.5.1/pida/utils/anyvc/__init__.py0000644000175000017500000000247110652670576015656 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2006 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. __all__ = ["all_known"] from cmdbased import Monotone, Bazaar, SubVersion, Mercurial, Darcs, Git all_known = [ Monotone, Bazaar, SubVersion, Mercurial, Darcs, Git] PIDA-0.5.1/pida/utils/anyvc/bases.py0000644000175000017500000000735510652670576015222 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2006 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. __all__ = ["VCSBase","DVCSMixin"] class VCSBase(object): """ Base class for all vcs's remember not to use super in subclasses """ def __init__(self, path): self.setup() self.path = path def parse_list_items(self, items, cache): """ redirect to parse_list_item a more complex parser might need to overwrite """ for item in items: yield self.parse_list_item(item, cache) def parse_list_item(self, item, cache): """ parse a single listing item """ raise NotImplementedError def parse_cache_items(self, items): """ parses vcs specific cache items to a list of (name, state) tuples """ return [] def cache_impl(self, paths=False, recursive=False): """ creates a list of vcs specific cache items only necessary by messed up vcs's in case of doubt - dont touch ^^ """ return [] def list_impl(self, paths=False, recursive=False): """ yield a list of vcs specific listing items """ raise NotImplementedError def cache(self, paths=(), recursive=False): """ return a mapping of name to cached states only necessary for messed up vcs's """ return dict( self.parse_cache_items( self.cache_impl( paths = paths, recursive=recursive ))) def list(self, paths=(), recursive=False): """ yield a list of Path instances tagged with status informations """ cache = self.cache(paths = paths,recursive=recursive) return self.parse_list_items( self.list_impl( paths = paths, recursive=recursive, ), cache) def diff(self, paths=()): raise NotImplementedError def update(self, revision=None): raise NotImplementedError def commit(self, paths=None, message=None): raise NotImplementedError def revert(self, paths=None, missing=False): raise NotImplementedError def add(self, paths=None, recursive=False): raise NotImplementedError def remove(self, paths=None, execute=False, recursive=False): raise NotImplementedError class DVCSMixin(object): def pull(self, locations=None): raise NotImplementedError def sync(self, locations=None): raise NotImplementedError def push(self, locations=None): raise NotImplementedError PIDA-0.5.1/pida/utils/anyvc/cmdbased.py0000644000175000017500000003477210652670576015672 0ustar aliali""" Classes for Command based vcs's ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :license: BSD """ from subprocess import Popen, PIPE, STDOUT import os.path #TODO: more reviews from bases import VCSBase, DVCSMixin from file import StatedPath as Path def relative_to(base_path): """ will turn absolute paths to paths relative to the base_path .. warning: will only work on paths below the base_path other paths will be unchanged """ base_path = os.path.normpath(base_path) l = len(base_path) def process_path(path): if path.startswith(base_path): return "." + path[l:] else: return path return process_path class CommandBased(VCSBase): """ Base class for all command based rcs's """ #TODO: set up the missing actions def __init__(self, versioned_path): self.path = os.path.normpath( os.path.abspath(versioned_path) ) self.base_path = self.find_basepath() def find_basepath(self): act_path = self.path detected_path = None detected_sd = None op = None while act_path != op: if os.path.exists( os.path.join(act_path, self.detect_subdir)): detected_path = act_path # continue cause some vcs's # got the subdir in every path op = act_path act_path = os.path.dirname(act_path) if not detected_path: raise ValueError( 'VC Basepath for vc class %r' 'not found above %s'%( type(self), self.path) ) return detected_path def process_paths(self, paths): """ process paths for vcs's usefull for "relpath-bitches" """ return paths def execute_command(self, args, result_type=str, **kw): if not args: raise ValueError('need a valid command') ret = Popen( [self.cmd] + args, stdout=PIPE, stderr=STDOUT, cwd=self.base_path, close_fds=True) if result_type is str: return ret.communicate()[0] elif result_type is iter: return iter(ret.stdout) elif result_type is file: return ret.stdout def get_commit_args(self, message, paths=(), **kw): """ creates a argument list for commiting :param message: the commit message :param paths: the paths to commit """ return ['commit','-m', message] + self.process_paths(paths) def get_diff_args(self, paths=(), **kw): return ['diff'] + self.process_paths(paths) def get_update_args(self, revision=None, **kw): if revision: return ['update', '-r', revision] else: return ['update'] def get_add_args(self, paths=(), recursive=False, **kw): return ['add'] + self.process_paths(paths) def get_remove_args(self, paths=(), recursive=False, execute=False, **kw): return ['remove'] + self.process_paths(paths) def get_revert_args(self, paths=(), recursive=False, **kw): return ['revert'] + self.process_paths(paths) def get_status_args(self,**kw): return ['status'] def get_list_args(self, **kw): raise NotImplementedError("%s doesnt implement list"%self.__class__.__name__) def commit(self, **kw): args = self.get_commit_args(**kw) return self.execute_command(args, **kw) def diff(self, **kw): args = self.get_diff_args(**kw) return self.execute_command(args, **kw) def update(self, **kw): args = self.get_update_args(**kw) return self.execute_command(args, **kw) def status(self, **kw): args = self.get_status_args(**kw) return self.execute_command(args, **kw) def add(self, **kw): args = self.get_add_args(**kw) return self.execute_command(args, **kw) def remove(self, **kw): args = self.get_remove_args(**kw) return self.execute_command(args, **kw) def revert(self, **kw): args = self.get_revert_args(**kw) return self.execute_command(args, **kw) def list_impl(self, **kw): """ the default implementation is only cappable of recursive operation on the complete workdir rcs-specific implementations might support non-recursive and path-specific listing """ args = self.get_list_args(**kw) return self.execute_command(args, result_type=iter, **kw) def cache_impl(self, recursive, **kw): """ only runs caching if it knows, how """ args = self.get_cache_args(**kw) if args: return self.execute_command(args, result_type=iter, **kw) else: return [] def get_cache_args(self, **kw): return None class DCommandBased(CommandBased,DVCSMixin): """ base class for all distributed command based rcs's """ def sync(self, **kw): args = self.get_sync_args(**kw) return self._execute_command(args, **kw) def pull(self, **kw): args = self.get_pull_args(**kw) return self._execute_command(args, **kw) def push(self, **kw): args = self.get_push_args(**kw) return self._execute_command(args, **kw) class Monotone(DCommandBased): cmd = 'mtn' detect_subdir = '_MTN' statemap = { ' ': 'normal', # unchanged ' P': 'modified', # patched (contents changed) ' U': 'none', # unknown (exists on the filesystem but not tracked) ' I': 'ignored', # ignored (exists on the filesystem but excluded by lua hook) ' M': 'missing', # missing (exists in the manifest but not on the filesystem) ' A ': 'error', # added (invalid, add should have associated patch) ' AP': 'new', # added and patched ' AU': 'error', # added but unknown (invalid) ' AI': 'error', # added but ignored (seems invalid, but may be possible) ' AM': 'empty', # added but missing from the filesystem ' R ': 'normal', # rename target ' RP': 'modified', # rename target and patched ' RU': 'error', # rename target but unknown (invalid) ' RI': 'error', # rename target but ignored (seems invalid, but may be possible?) ' RM': 'missing', # rename target but missing from the filesystem 'D ': 'removed', # dropped 'D P': 'error', # dropped and patched (invalid) 'D U': 'error', # dropped and unknown (still exists on the filesystem) 'D I': 'error', # dropped and ignored (seems invalid, but may be possible?) 'D M': 'error', # dropped and missing (invalid) 'DA ': 'error', # dropped and added (invalid, add should have associated patch) 'DAP': 'new', # dropped and added and patched 'DAU': 'error', # dropped and added but unknown (invalid) 'DAI': 'error', # dropped and added but ignored (seems invalid, but may be possible?) 'DAM': 'missing', # dropped and added but missing from the filesystem 'DR ': 'normal', # dropped and rename target 'DRP': 'modified', # dropped and rename target and patched 'DRU': 'error', # dropped and rename target but unknown (invalid) 'DRI': 'error', # dropped and rename target but ignored (invalid) 'DRM': 'missing', # dropped and rename target but missing from the filesystem 'R ': 'missing', # rename source 'R P': 'error', # rename source and patched (invalid) 'R U': 'removed', # rename source and unknown (still exists on the filesystem) 'R I': 'error', # rename source and ignored (seems invalid, but may be possible?) 'R M': 'error', # rename source and missing (invalid) 'RA ': 'error', # rename source and added (invalid, add should have associated patch) 'RAP': 'new', # rename source and added and patched 'RAU': 'error', # rename source and added but unknown (invalid) 'RAI': 'error', # rename source and added but ignored (seems invalid, but may be possible?) 'RAM': 'missing', # rename source and added but missing from the filesystem 'RR ': 'new', # rename source and target 'RRP': 'modified', # rename source and target and target patched 'RRU': 'error', # rename source and target and target unknown (invalid) 'RRI': 'error', # rename source and target and target ignored (seems invalid, but may be possible?) 'RRM': 'missing', # rename source and target and target missing } def process_paths(self, paths): return map(relative_to(self.base_path), paths) def get_remove_args(self, paths, **kw): return ["drop"] + self.process_paths(paths) def get_list_args(self, **kw): return ["automate", "inventory"] def parse_list_item(self, item, cache): state = self.statemap.get(item[:3], "none") return Path(os.path.normpath(item[8:].rstrip()), state, self.base_path) class Bazaar(DCommandBased): """ .. warning: badly broken """ #TODO: fix caching cmd = "bzr" detect_subdir = ".bzr" def process_paths(self, paths): return map(relative_to(self.base_path), paths) def get_list_args(self, recursive=True, paths=(),**kw): ret = ["ls","-v"] if not recursive: ret.append("--non-recursive") return ret def get_cache_args(self, **kw): return ["st"] statemap = { "unknown:": 'none', "added:": 'new', "unchanged:": 'normal', "removed:": 'removed', "ignored:": 'ignored', "modified:": 'modified', "conflicts:": 'conflict', "pending merges:": None, } def parse_cache_items(self, items): state = 'none' for item in items: item = item.rstrip() state = self.statemap.get(item.rstrip(), state) if item.startswith(" ") and state: yield item.strip(), state def parse_list_items(self, items, cache): for item in items: if item.startswith('I'): yield Path(item[1:].strip(), 'ignored', self.base_path) else: fn = item[1:].strip() yield Path( fn, cache.get(fn, 'normal'), self.base_path) class SubVersion(CommandBased): cmd = "svn" detect_subdir = ".svn" def get_list_args(self, recursive=True, paths=(), **kw): #TODO: figure a good way to deal with changes in external # (maybe use the svn python api to do that) ret = ["st", "--no-ignore", "--ignore-externals", "--verbose"] if not recursive: ret.append("--non-recursive") return ret + paths state_map = { "?": 'none', "A": 'new', " ": 'normal', "!": 'missing', "I": 'ignored', "M": 'modified', "D": 'removed', "C": 'conflict', 'X': 'external', 'R': 'modified', '~': 'external', } def parse_list_item(self, item, cache): state = item[0] file = item.split()[-1] #TODO: handle paths with whitespace if ppl fall in that one return Path(file, self.state_map[state], self.base_path) class Mercurial(DCommandBased): cmd = "hg" detect_subdir = ".hg" def get_list_args(self, **kw): return ["status", "-A"] state_map = { "?": 'none', "A": 'new', " ": 'normal', "C": 'normal', #Clean "!": 'missing', "I": 'ignored', "M": 'modified', "R": 'removed', } def parse_list_item(self, item, cache): state = self.state_map[item[0]] file = item[2:].strip() return Path(file, state, self.base_path) class Darcs(DCommandBased): #TODO: ensure this really works in most cases cmd = 'darcs' detect_subdir = '_darcs' def get_list_args(self, **kw): return ['whatsnew', '--boring', '--summary'] state_map = { "a": 'none', "A": 'new', "M": 'modified', "C": 'conflict', "R": 'removed' } def parse_list_item(self, item, cache): if item.startswith('What') or item.startswith('No') or not item.strip(): return None elements = item.split(None, 2)[:2] #TODO: handle filenames with spaces state = self.state_map[elements[0]] file = os.path.normpath(elements[1]) return Path(file, state, self.base_path) class Git(CommandBased): """ experimental copyed processing from http://www.geekfire.com/~alex/pida-git.py by alex """ cmd = 'git' detect_subdir = '.git' statemap = { None: 'normal', "new file": 'new', "": 'normal', "modified": 'modified', "unmerged": 'conflict', "deleted": 'removed' } def process_paths(self, paths): return map(relative_to(self.base_path), paths) def get_commit_args(self, message, paths=()): if paths: # commit only for the supplied paths return ['commit', '-m', message, '--'] + self.process_paths(paths) else: # commit all found changes # this also commits deletes return ['commit', '-a', '-m', message] def get_list_args(self, **kw): return ['ls-tree', '-r', 'HEAD'] def get_cache_args(self, **kw): return ['status'] def parse_list_items(self, items, cache): for item in items: item = item.split()[-1] path = Path(item, cache.get(item, 'normal'), self.base_path) yield path def parse_cache_items(self, items): #TODO: fix the mess for a in items: if not a:continue ev, date, options, tag = [""]*4 if a.startswith('#\t'): a = a.strip("#\t") if a.startswith('('): continue # ignore some odd lines state_and_name = a.split(':') if len(state_and_name) < 2: yield state_and_name[0].strip(), 'normal' else: yield state_and_name[1].strip(), self.statemap.get(state_and_name[0].strip()) PIDA-0.5.1/pida/utils/anyvc/file.py0000644000175000017500000000224710652670576015037 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: """ anyvcs file helpers ~~~~~~~~~~~~~~~~~~~ :copyright: 2006 by Ronny Pfannschmidt :license: BSD License """ from os.path import dirname, basename, join class StatedPath(object): """ stores status informations about files >>> StatedPath("a.txt", "normal") """ __slots__ = "name relpath path base state".split() def __init__(self, name, state, base=None): self.relpath = name self.path = dirname(name) self.name = basename(name) self.base = base self.state = intern(state) def __repr__(self): return "<%s %r>"%( self.state, self.relpath, ) def __str__(self): return self.relpath @property def abspath(self): """ returns the absolute path if the base is known else it returns None >>> StatedPath("a",None,"b").abspath "a/b" >>> StatedPath("a",None).abspath None """ if self.base is not None: return join(self.base, self.relpath) PIDA-0.5.1/pida/utils/anyvc/interfaces.py0000644000175000017500000000621710652670576016244 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2006 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from zope.interface import Interface, Attribute class ILocalVCS(Interface): """a high level interface to local vcs operations on a revision controlled directory """ def configure():## do in init """Set up the instace for the actual vcs""" def is_in(dir): """determine if a directory is managed by the implementing vcs""" def parse_list_item(item, cache): """item is a rcs-specidic file entry, returns a stated file""" def _list_impl(paths=None, recursive=None): """ iterate over vcs specifiv file entries""" def list(paths=None, recursive=False): """return an iterator over stated files""" def diff(paths=None): """differences of all files below the paths""" def update(): """update to the most recent revision""" def commit(paths=None): """commit all changes of files below the paths - might be a network operation""" def revert(paths=None, missing=False): """revert all changes in files below the paths - if missing is True only restore deleted files wich are still in revision controll""" def add(paths=None, recursive=False): """adds all paths to version controll - if recursive is True add all files/directories below paths to the rcs""" def drop(paths=None, execute=False, recursive=False): """removes a path from version-controll if its not recursive it will fail on directories wich contain versioned files params: execute -- also remove the files from the local filesystem recursive -- recurse a directory tree - use with caution """ class IDistributedVCS(ILocalVCS): """high level operations on distributed vcs's""" def pull(locations=None): """pulls from locations or default""" def sync(locations=None): """sync with locations or default""" def push(locations=None): """push to loations or default""" PIDA-0.5.1/pida/utils/debugger/0002755000175000017500000000000010652671502014175 5ustar alialiPIDA-0.5.1/pida/utils/debugger/uidef/0002755000175000017500000000000010652671502015271 5ustar alialiPIDA-0.5.1/pida/utils/debugger/uidef/debugger.xml0000644000175000017500000000273510652670577017617 0ustar aliali PIDA-0.5.1/pida/utils/debugger/uidef/stackview-toolbar.xml0000644000175000017500000000073010652670577021464 0ustar aliali PIDA-0.5.1/pida/utils/debugger/__init__.py0000644000175000017500000000222410652670577016317 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2006 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. PIDA-0.5.1/pida/utils/debugger/debugger.py0000644000175000017500000005313310652670577016351 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig from pida.core.options import OTypeString, OTypeFile from pida.core.actions import TYPE_NORMAL, TYPE_TOGGLE from pida.core.environment import get_pixmap_path from pida.core.projects import ProjectController, \ ProjectKeyDefinition from pida.core.interfaces import IProjectController from pida.utils.debugger.views import DebuggerBreakPointsView, DebuggerStackView # Breakpoints class Breakpoint(object): def __init__(self, file, line, enabled=False, ident=None): self.file = file self.line = line self.enabled = enabled self.ident = ident def get_location(self): return (self.file, self.line) location = property(get_location) class BreakpointHandlerInterface(object): __breakpoints = {} def on_add_breakpoint(self, b, enabled=True): """ if enabled if b in breakpoints: if b is disabled: enable b else if b in breakpoi add b else """ if b.enabled is True: if b.location not in self.__breakpoints: self.__breakpoints[b.location] = b else: self.__breakpoints[b.location].enabled = True def on_del_breakpoint(self, b, enabled=True): if b.enabled is True: if b.location in self.__breakpoints: self.__breakpoints[b.location].enabled = False else: if b.location in self.__breakpoints: self.__breakpoints.remove(b.location) def on_toggle(self, b): if (file, line) in self.__breakpoints: self.on_del_breakpoint(file, line, False) else: self.on_add_breakpoint(file, line, False) def add_breakpoint(self, b): raise NotImplementedError def del_breakpoint(self, b): raise NotImplementedError # Actions class DebuggerActionsConfig(ActionsConfig): def create_actions(self): # Menu self.create_action( 'debug_show_breakpoints_view', TYPE_TOGGLE, 'Debugger breakpoints list', 'Show the breakpoints list', 'accessories-text-editor', self.on_show_breakpoints_view, 'b', ) self.create_action( 'debug_show_stack_view', TYPE_TOGGLE, "Debugger's stack view", 'Show the stack of current debugger', 'accessories-text-editor', self.on_show_stack_view, 's', ) # Toolbar self.create_action( 'debug_start', TYPE_NORMAL, 'Continue', 'Start debugger or Continue debbuging', 'gdb-go', self.on_start, '', ) self.create_action( 'debug_stop', TYPE_NORMAL, 'Break', 'Stop debbuging', 'gdb-break', self.on_stop, '', ) self.create_action( 'debug_next', TYPE_NORMAL, 'Step Over', 'Step over highlighted statement', 'gdb-next', self.on_step_over, '', ) self.create_action( 'debug_step', TYPE_NORMAL, 'Step In', 'Step in highlighted statement', 'gdb-step', self.on_step_in, '', ) self.create_action( 'debug_return', TYPE_NORMAL, 'Finish function', 'Step until end of current function', 'gdb-return', self.on_return, '', ) self.create_action( 'debug_toggle_breakpoint', TYPE_NORMAL, 'Toggle breakpoint', 'Toggle breakpoint on selected line', 'gdb-toggle-bp', self.on_toggle_breakpoint, '', ) # Buttonbar def on_step_over(self, action): self.svc.step_over() def on_step_in(self, action): self.svc.step_in() def on_start(self, action): self.svc.cont() def on_stop(self, action): self.svc.end() def on_return(self, action): self.svc.finish() # Menu def on_show_breakpoints_view(self, action): if action.get_active(): self.svc.boss.cmd('window', 'add_view', paned='Plugin', view=self.svc._breakpoints_view) else: self.svc.boss.cmd('window', 'remove_view', view=self.svc._breakpoints_view) def on_show_stack_view(self, action): if not self.svc._stack_view: self.svc._stack_view = DebuggerStackView(self.svc) if action.get_active(): self.svc.boss.cmd('window', 'add_view', paned='Terminal', view=self.svc._stack_view) else: self.svc.boss.cmd('window', 'remove_view', view=self.svc._stack_view) # def on_toggle_breakpoint(self, action): # print 'act.on_toggle_breakpoint' self.svc.emit('toggle_breakpoint', file=self.svc._current.get_filename(), line=self.svc.boss.editor.cmd('get_current_line_number')) # Events class DebuggerEventsConfig(EventsConfig): _breakpoints = {} def create_events(self): # UI events self.create_event('toggle_breakpoint') # Debugger events self.create_event('debugger_started') self.create_event('reset') self.create_event('step') self.create_event('thread') self.create_event('function_call') self.create_event('function_return') self.create_event('add_breakpoint') self.create_event('del_breakpoint') self.create_event('debugger_ended') self.subscribe_event('toggle_breakpoint', self.on_toggle_breakpoint) self.subscribe_event('add_breakpoint', self.on_add_breakpoint) self.subscribe_event('del_breakpoint', self.on_del_breakpoint) self.subscribe_event('debugger_started', self.on_start_debugging) self.subscribe_event('debugger_ended', self.on_end_debugging) self.subscribe_event('step', self.on_step) def on_step(self, file, line, function): self.svc.boss.cmd('buffer', 'open_file', file_name=file) if self.svc._current is not None and file == self.svc._current.get_filename(): for (oldfile, oldline) in self.svc._step: if oldfile == self.svc._current.get_filename(): self.svc.boss.editor.cmd('hide_sign', type='step', file_name=oldfile, line=oldline) self.svc._step.remove((oldfile, oldline)) self.svc.boss.editor.cmd('goto_line', line=line) self.svc.boss.editor.cmd('show_sign', type='step', file_name=file, line=line) self.svc._step.append((file, line)) def on_toggle_breakpoint(self, file, line): """ Toggles a breakpoint in a file on line Sends the command to the debugger if it is active or update the interfaces """ # print 'evt.on_toggle_breakpoint', file, line if self.svc._is_running: # print 'is running:', self._breakpoints if not (file, str(line)) in self._breakpoints.values(): self.svc.add_breakpoint(file, line) else: self.svc.del_breakpoint(file, line) else: self.svc._toggle_breakpoint(file, line) def on_add_breakpoint(self, ident, file, line): """ Add a breakpoint on line of file Store it with the current controller """ # print 'evt.on_add_breakpoint', ident, file, line self._breakpoints[ident] = (file, line) self.svc._controller.store_breakpoint(file, line) if self.svc._is_running: # print 'show' self.svc.boss.editor.cmd('show_sign', type='breakpoint', file_name=file, line=line) def on_del_breakpoint(self, ident): """ Deletes a breakpoint on line of file Store it with the current controller """ # print 'evt.on_del_breakpoint', ident if ident in self._breakpoints: (file, line) = self._breakpoints[str(ident)] del(self._breakpoints[str(ident)]) if self.svc._is_running and file == self.svc._current.get_filename(): # print 'hide' self.svc.boss.editor.cmd('hide_sign', type='breakpoint', file_name=file, line=line) self.svc._controller.flush_breakpoint(file, line) def on_end_debugging(self): # print 'evt.on_end_debugging' self._breakpoints = {} def on_start_debugging(self): # print 'evt.on_start_debugging' self._breakpoints = {} self.svc.get_action('debug_start').set_sensitive(True) self.svc.get_action('debug_stop').set_sensitive(True) self.svc.get_action('debug_return').set_sensitive(True) self.svc.get_action('debug_step').set_sensitive(True) self.svc.get_action('debug_next').set_sensitive(True) def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.on_document_changed) self.subscribe_foreign_event('editor', 'started', self.on_editor_startup) def on_editor_startup(self): """ Set the highlights in vim """ self.svc.boss.editor.cmd('define_sign_type', type="breakpoint", icon=get_pixmap_path("stop.svg"), linehl="", text="X", texthl="Search") self.svc.boss.editor.cmd('define_sign_type', type="step", icon=get_pixmap_path("forward.svg"), linehl="lCursor", text=">", texthl="lCursor") def on_document_changed(self, document): if document is not None: self.svc.get_action('debug_toggle_breakpoint').set_sensitive(True) self.svc.update_editor(document) else: self.svc.get_action('debug_toggle_breakpoint').set_sensitive(False) # Controller class GenericDebuggerController(ProjectController): name = 'GENERIC_DEBUGGER' label = 'Generic Debugger' svcname = '' debugger = None # parameters of the debugger attributes = [ ProjectKeyDefinition('executable', 'Path to the executable', True), ProjectKeyDefinition('parameters', 'Parameters to give to the executable', True), ] + ProjectController.attributes def execute(self): """ Execute debugger """ self.svc = self.boss.get_service(self.svcname) self.svc._executable = self.get_option('executable') self.svc._parameters = self.get_option('parameters') self.svc._controller = self if not self.svc._executable: self.boss.get_window().error_dlg( 'Debug controller is not fully configured.') else: if self.svc._is_running: self.boss.get_window().error_dlg( 'Debugger already running.') else: if self.svc._init(): self.svc.emit('debugger_started') else: self.boss.get_window().error_dlg( 'Debug session failed to launch.') # breakpoint recording management _breakpoints = {} def init_breakpoint(self): """ init breakpoint storage """ # print 'controller.init_breakpoint' if self.get_option('breakpoint') is None: self.set_option('breakpoint', dict()) def store_breakpoint(self, file, line): """ Store breakpoint """ # print 'controller.store_breakpoint', file, line if file not in self.get_option('breakpoint'): self.get_option('breakpoint')[file] = [] if line not in self.get_option('breakpoint')[file]: self.get_option('breakpoint')[file].append(line) self.project.options.write() return True else: return False def flush_breakpoint(self, file, line): """ Remove breakpoint from recorded list """ # print 'controller.flush_breakpoint', file, line if file in self.get_option('breakpoint'): if line in self.get_option('breakpoint')[file]: self.get_option('breakpoint')[file].remove(line) if self.get_option('breakpoint')[file] == []: del(self.get_option('breakpoint')[file]) self.project.options.write() def list_breakpoints(self): l = self.get_option('breakpoint') # print 'controller.list_breakpoints', l if l is None: return {} return l class DebuggerFeaturesConfig(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('project', IProjectController, self.svc.controller_config) class DebuggerOptionsConfig(OptionsConfig): name = 'debugger' def create_options(self): self.create_option( self.name+'breakpoint_pixmap', "Breakpoint's pixmap", OTypeFile, get_pixmap_path("stop.svg"), 'Path to a pixmap for the breakpoints to be displayed at \ beginning of line', ) self.create_option( self.name+'_cursor_pixmap', "Debugging cursor's pixmap", OTypeFile, get_pixmap_path("forward.svg"), 'Path to a pixmap for the cursor to be displayed at \ beginning of line where debugger is halted') self.create_option( self.name+'_executable_path', 'Pathes to the GDB-compatible debugger : ', OTypeString, self.svc.DEFAULT_DEBUGGER_PATH_OPTION, ('Set the path to the debugger executable.') ) # Service class class Debugger(Service): """Debugging a project service""" DEFAULT_DEBUGGER_PATH_OPTION = None actions_config = DebuggerActionsConfig events_config = DebuggerEventsConfig features_config = DebuggerFeaturesConfig options_config = DebuggerOptionsConfig controller_config = GenericDebuggerController _controller = None def _toggle_breakpoint(self, file, line): if self._controller.store_breakpoint(file, line): self.boss.editor.cmd('show_sign', type='breakpoint', file_name=file, line=line) else: self._controller.flush_breakpoint(file, line) self.boss.editor.cmd('hide_sign', type='breakpoint', file_name=file, line=line) def _list_breakpoints(self, file=None): """ Lists all breakpoints choosed by the user """ # print 'svc._list_breakpoints' if self._controller is not None: if file is not None: for line in self._controller.list_breakpoints()[file]: yield (file, line) else: for file in self._controller.list_breakpoints(): for line in self._controller.list_breakpoints()[file]: yield (file, line) def _flush_breakpoints(self): """ Removes all breakpoints from the debugger """ # print 'svc._flush_breakpoints' for (file, line) in self._list_breakpoints(): self._controller.flush_breakpoint(file, line) self._breakpoints_view.clear_items() def _load_breakpoints(self): """ Load all breakpoints in the debugger """ # print 'svc._load_breakpoints' lb = self._list_breakpoints() self._flush_breakpoints() for (file, line) in lb: self.emit('toggle_breakpoint', file=file, line=line) def _init(self): """ Initialisation of the debugging session """ # print 'svc._init' # set session state self._is_running = True if not self.init(): return False # load breakpoints self._controller.init_breakpoint() self._load_breakpoints() # open views self.get_action('debug_show_breakpoints_view').set_active(True) self.get_action('debug_show_stack_view').set_active(True) return True def _end(self): """ Termination of the debugging session """ # print 'svc._end' self._is_running = False self.emit('debugger_ended') self.get_action('debug_start').set_sensitive(False) self.get_action('debug_stop').set_sensitive(False) self.get_action('debug_step').set_sensitive(False) self.get_action('debug_next').set_sensitive(False) self.get_action('debug_return').set_sensitive(False) # removing old cursor for (oldfile, oldline) in self._step: self.boss.editor.cmd('hide_sign', type='step', file_name=oldfile, line=oldline) self._step.remove((oldfile, oldline)) return self.end() def start(self): """ Starts the service """ self._step = [] self._current = None self._controller = None self._is_running = False # initiate the views self._breakpoints_view = DebuggerBreakPointsView(self) self._stack_view = DebuggerStackView(self) # Sets default sensitivity for button bar self.get_action('debug_step').set_sensitive(False) self.get_action('debug_next').set_sensitive(False) self.get_action('debug_return').set_sensitive(False) self.get_action('debug_start').set_sensitive(False) self.get_action('debug_stop').set_sensitive(False) self.get_action('debug_toggle_breakpoint').set_sensitive(False) # load cached breakpoints self._load_breakpoints() def update_editor(self, document): """ Updates the editor with current's document breakpoints """ # update the editor self._current = document if document.get_filename() in self._list_breakpoints(): line = self._list_breakpoints(document.get_filename()) self.boss.editor.cmd('show_sign', type='breakpoint', file_name=document.get_filename(), line=line) if self._is_running and self._step != []: (file, line) = self._step[-1] if file is document.get_filename(): self.boss.editor.cmd('show_sign', type='step', file_name=file, line=line) # abstract interface to be implemented by services def init(self, executable, parameters, controller): """ Initiates the debugging session """ raise NotImplementedError def end(self): """ Ends the debugging session """ raise NotImplementedError def step_in(self): """ step in instruction """ raise NotImplementedError def step_over(self): """ Step over instruction """ raise NotImplementedError def cont(self): """ Continue execution """ raise NotImplementedError def finish(self): """ Finish current function call """ raise NotImplementedError def add_breakpoint(self, file, line): raise NotImplementedError def del_breakpoint(self, file, line): raise NotImplementedError # Required Service attribute for service loading Service = Debugger # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/debugger/views.py0000644000175000017500000002216310652670577015721 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from kiwi.ui.objectlist import ObjectTree, ObjectList, Column # PIDA Imports from pida.core.environment import get_uidef_path from pida.utils.gthreads import gcall from pida.ui.views import PidaView # --- Breakpoint list view class DebuggerBreakPointItem(object): def __init__(self, file, line, status='enabled'): self.file = file self.line = line self.status = status class DebuggerBreakPointsView(PidaView): label_text = 'Debug Breakpoints' icon_name = 'accessories-text-editor' def create_ui(self): self._prebreakpoints = {} self._breakpoints = {} self._breakpoint_list = ObjectList( [ Column('line'), Column('file', sorted=True), Column('status') ] ) self._breakpoint_list.connect('double-click', self._on_breakpoint_double_click) self.add_main_widget(self._breakpoint_list) self._breakpoint_list.show_all() for (file, line) in self.svc._list_breakpoints(): self.add_breakpoint(None, file, line) self.svc.subscribe_event('add_breakpoint', self.add_breakpoint) self.svc.subscribe_event('del_breakpoint', self.del_breakpoint) self.svc.subscribe_event('toggle_breakpoint', self.toggle_breakpoint) self.svc.subscribe_event('debugger_started', self.start_debug_session) self.svc.subscribe_event('debugger_ended', self.end_debug_session) def start_debug_session(self): pass def end_debug_session(self): self._prebreakpoints = self._breakpoints self._breakpoints = {} for item in self._breakpoint_list: if item.status == 'disabled': self._breakpoint_list.remove(item) else: item.status = 'disabled' def clear_items(self): self._prebreakpoints = {} self._breakpoints = {} gcall(self._breakpoint_list.clear) def toggle_breakpoint(self, file, line): if self.svc._is_running: return breakpoint = DebuggerBreakPointItem(file, line, 'disabled') if (file, line) not in self._prebreakpoints: self._prebreakpoints[(file,line)] = breakpoint self._breakpoint_list.append(breakpoint) else: oldbp = self._prebreakpoints.pop((file,line)) self._breakpoint_list.remove(oldbp) def add_breakpoint(self, ident, file, line): # print 'view.add_breakpoint', ident, file, line if (file, int(line)) in self._prebreakpoints: breakpoint = self._prebreakpoints.pop((file, int(line))) breakpoint.status = 'enabled' self._breakpoint_list.remove(breakpoint) else: breakpoint = DebuggerBreakPointItem(file, int(line)) if ident not in self._breakpoints: self._breakpoints[ident] = breakpoint self._breakpoint_list.append(breakpoint) return True else: return False def del_breakpoint(self, ident): # print 'view.del_breakpoint', ident if ident in self._breakpoints: self._breakpoint_list.remove(self._breakpoints[ident]) del(self._breakpoints[ident]) return True else: return False def get_breakpoint_list(self): # print 'view.get_breakpoint_list' return self._breakpoints def _on_breakpoint_double_click(self, olist, item): self.svc.boss.editor.cmd('goto_line', line=item.line) self.svc.boss.cmd('buffer', 'open_file', file_name=item.file) def can_be_closed(self): self.svc.get_action('debug_show_breakpoints_view').set_active(False) return True # --- Function stack view class DebuggerStackItem(object): def __init__(self, frame, function, file, line): self.thread = "" self.frame = frame self.function = function self.file = file self.line = line self.parent = None class AnyDbgStackThreadItem(object): def __init__(self,thread): self.thread = thread self.frame = "" self.function = "" self.file = "" self.line = "" self.parent = None class DebuggerStackView(PidaView): label_text = 'Debug Function Stack' icon_name = 'accessories-text-editor' def create_ui(self): self.__last = None self.__call = False self.__return = False self.__cnt = 0 # Toolbar self.create_toolbar() # Tree self._stack_list = ObjectTree( [ Column('thread'), Column('frame'), Column('line'), Column('function'), Column('file'), ] ) self._stack_list.connect('double-click', self._on_frame_double_click) # Arrange the UI self._vbox = gtk.VBox() self._vbox.pack_start(self._toolbar, expand=False) self._vbox.pack_start(self._stack_list, expand=True) self.add_main_widget(self._vbox) self._vbox.show_all() self.svc.subscribe_event('function_call', self.on_function_call) self.svc.subscribe_event('function_return', self.on_function_return) self.svc.subscribe_event('step', self.on_step) self.svc.subscribe_event('thread', self.on_thread_stmt) self.svc.subscribe_event('debugger_ended', self.end_debug_session) self._thread = { None:None } self.__current_thread = None def end_debug_session(self): self.clear_items() def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('debugger_stackview_toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) self._toolbar.show_all() def on_thread_stmt(self, thread): self.__current_thread = thread thread_item = AnyDbgStackThreadItem(thread) if thread not in self._thread: self._thread[thread] = thread_item self._stack_list.prepend(None, thread_item) def on_function_call(self): self.__call = True def on_function_return(self): self.__return = True def on_step(self, file, line, function): if self.__return is True: self.pop_function() self.__return = False if self.__call is True: self.push_function(function, file, line, self.__current_thread) self.__call = False if self.__call is False and self.__return is False: if self.__last is None: self.push_function(function, file, line, self.__current_thread) else: self.pop_function() self.push_function(function, file, line, self.__current_thread) return True def clear_items(self): self.__last = None self.__call = False self.__return = False self.__cnt = 0 self._thread = { None:None } self.__current_thread = None gcall(self._stack_list.clear) def push_function(self, function, file, line, thread=None): self.__cnt = self.__cnt + 1 func = DebuggerStackItem(self.__cnt, function, file, line) func.parent = self.__last self._stack_list.prepend(self._thread[thread],func) self.__last = func def pop_function(self): if self.__last is not None: self._stack_list.remove(self.__last) self.__last = self.__last.parent self.__cnt = self.__cnt - 1 def _on_frame_double_click(self, olist, item): self.svc.boss.editor.cmd('goto_line', line=item.line) self.svc.boss.cmd('buffer', 'open_file', file_name=item.file) def can_be_closed(self): self.svc.get_action('debug_show_stack_view').set_active(False) return True # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/emacs/0002755000175000017500000000000010652671502013501 5ustar alialiPIDA-0.5.1/pida/utils/emacs/__init__.py0000644000175000017500000000226310652670601015612 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """Utilities for Pida Emacs service.""" # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/emacs/emacscom.py0000644000175000017500000002131510652670601015641 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """Classes for both way communication wih Emacs. Communication with Emacs process is handled by EmacsClient in the pIDA->Emacs way, and EmacsServer the other way. On occurence of a message, EmacsServer extracts a request name and arguments, and then tries to invoke the matching method on a EmacsCallback object (see editor/emacs/emacs.py). """ import logging import socket import subprocess import gobject from pida.core.log import build_logger class EmacsClient(object): """Tool for sending orders to emacs. EmacsClient class relies on the emacsclient binary. Note that this utility works with a server started from inside a running emacs instance. We assume for now that the emacs instance running in pida is the only one having a running server. """ def __init__(self): """Constructor.""" #TODO: I would like to use something like here, # but then the log will be printed three times. self._log = logging.getLogger('emacs') self._active = True def activate(self): """Allow communication. An EmacsClient object is activated by default. """ self._active = True def inactivate(self): """Prevents sending any message to Emacs. This can be useful if Pida knows emacs has quit for example. """ self._active = False def set_directory(self, path): self._send('(cd "%s")' % path) def open_file(self, filename): self._send('(find-file "%s")' % filename) def change_buffer(self, filename): self._send('(switch-to-buffer (get-file-buffer "%s"))' % filename) def save_buffer(self): self._send('(save-buffer)') def save_buffer_as(self, filename): self._send('(write-file "%s"))' % filename) def close_buffer(self, buffer): self._send('(kill-buffer (get-file-buffer "%s"))' % buffer) def cut(self): self._send('(kill-region (region-beginning) (region-end))') def copy(self): self._send('(kill-ring-save (region-beginning) (region-end))') def paste(self): self._send('(yank)') def ping(self): self._send('(pida-ping)') def goto_line(self, line): self._send('(goto-line %s)' % line) def revert_buffer(self): self._send('(revert-buffer)') def undo(self): self._send('(undo-only)') def redo(self): # Well... I'm still a bit disturbed with the undo / redo of Emacs self._send('(undo)') def quit(self): self._send('(kill-emacs)') def _send(self, command): """Invokes emacsclient to send a message to Emacs. The message is only sent is this object is not inactivated. """ if self._active: self._log.debug('sending "%s"' % command) try: subprocess.call( ['emacsclient', '-e', command], stdout=subprocess.PIPE) except OSError, e: self._log.debug('%s"' % e) class EmacsServer(object): """Listener for Emacs notifications. When started by the EmacsEmbed object, the EMACS_SCRIPT is provided to Emacs to register some callbacks and create a link with Pida. EmacsServer is the server part of this link. """ def __init__(self, cb): """Constructor.""" self._log = logging.getLogger('emacs') self._cb = cb self._socket = None def connect(self): """Install the link between Pida and Emacs.""" result = self._socket is not None if not result: try: self._socket = self._wait_connection() gobject.io_add_watch(self._socket, gobject.IO_IN | gobject.IO_HUP, self._cb_socket_event) result = True except socket.error, e: if e.args[0] == 111: self._log.warn('emacs disconnected') return result def _wait_connection(self): """Wait for connection from Emacs.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('', 5001)) s.listen(1) conn, addr = s.accept() self._log.debug('connection from "%s:%d"' % addr) return conn def _cb_socket_event(self, sock, condition): """Wait for Pida events. Called by GTK main loop as soon as Emacs notifies an event. This method also monitors the link with Emacs. Return True as long as Emacs is still alive. """ cont = True if condition == gobject.IO_IN: data = sock.recv(1024) events = data.split('\n') for event in events: if event: cont = self._cb_socket_read(event) elif condition == gobject.IO_HUP: self._log.warn('event: emacs disconnected') cont = False elif condition == gobject.IO_ERR: self._log.warn('event: io error') return cont def _cb_socket_read(self, data): """Analyse Emacs notifications and forward events to Pida. All Emacs notifications are composed of a message name, and possibly an argument. The EmacsServer object build the name of a related callback in the EmacsCallback object by prefixing the message name with 'cb_'. Return True as long as the link with Emacs should be maintained. """ cont = True hook, args = data, '' sep = data.find(' ') if sep != -1: hook, args = data[0:sep], data[sep + 1:] name = 'cb_' + hook.replace('-', '_') if args.endswith('\n'): args = args[:-1] cb = getattr(self._cb.__class__, name, None) if callable(cb): cont = cb(self._cb, args) else: self._log.warn('unknown hook "%s"' % hook) return cont EMACS_SCRIPT = """;; Emacs client script for Pida. ;; David Soulayrol ;; ;; This script is automatically generated by pida when destroyed. ;; Leave untouched or modify it at your own risk. (defconst pida-connection-terminator "\n" "The terminator used to send notifications to pida") (defconst pida-connection-port 5001 "The port used to communicate with pida") (defvar pida-connection nil "The socket to comunicate with pida") ;; open-network-stream name buffer-or-name host service (setq pida-connection (open-network-stream "pida-connection" nil "localhost" pida-connection-port)) (process-kill-without-query pida-connection nil) (defun pida-send-message (message) (process-send-string "pida-connection" (concat message pida-connection-terminator))) (defun pida-ping () (pida-send-message "pida-pong ready")) ;; hook to the events pida is interested in. (add-hook 'find-file-hooks '(lambda () (pida-send-message (concat "find-file-hooks " buffer-file-name)))) (add-hook 'after-save-hook '(lambda () (pida-send-message (concat "after-save-hook " buffer-file-name)))) (add-hook 'kill-buffer-hook '(lambda () (pida-send-message (concat "kill-buffer-hook " buffer-file-name)))) (add-hook 'window-configuration-change-hook '(lambda () (pida-send-message (concat "window-configuration-change-hook " buffer-file-name)))) (add-hook 'kill-emacs-hook '(lambda () (pida-send-message "kill-emacs-hook") (delete-process pida-connection))) ;; is there a way to prevent emacs from showing the "GNU Emacs" ;; first buffer ? ;; d_rol: M-x customize-variable RET inhibit-splash-screen RET (setq inhibit-splash-screen 1) """ # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/emacs/emacsembed.py0000644000175000017500000000545210652670601016143 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """This module provides the widget which is responsible to embed Emacs for Pida. This work was made possible thanks to the Emacs patches written by Timo Savola for his own embedding of Emacs with his Encode project (http://encode.sourceforge.net/). """ import subprocess import gtk class EmacsEmbedWidget(gtk.EventBox): """A widget embedding Emacs. The EmacsEmbedWidget makes use of a GTK socket to embed an Emacs frame inside a GTK application. The widget is also a GTK Event Box, so key events are available. """ def __init__(self, command, script_path, args=[]): """Constructor.""" gtk.EventBox.__init__(self) self._command = command self._init_script = script_path self._pid = None self._args = args def run(self): """Start the Emacs process.""" if not self._pid: xid = self._create_ui() if xid: args = self._args[:] # a copy args.extend(['--parent-id', '%s' % xid, '-f', 'server-start', '-l', '%s' % self._init_script]) popen = subprocess.Popen([self._command] + args, close_fds=True) self._pid = popen.pid self.show_all() def grab_input_focus(self): self.child_focus(gtk.DIR_TAB_FORWARD) def _create_ui(self): """Instantiate the GTK socket. Called by the run method before the widget is realized. """ socket = gtk.Socket() self.add_events(gtk.gdk.KEY_PRESS_MASK) self.add(socket) self.show_all() return socket.get_id() # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/launchpadder/0002755000175000017500000000000010652671502015043 5ustar alialiPIDA-0.5.1/pida/utils/launchpadder/LICENSE0000644000175000017500000000044010652670601016043 0ustar alialiCopyright Ali Afshar aafshar@gmail.com MIT-style -- 1. Do what you like with the software. 2. Don't blame me for any breakages to anything ever for any reason, this software may well fry your system and eat your cat. 3. Leave this notice here. 4. Remember, I am just the little guy. PIDA-0.5.1/pida/utils/launchpadder/README0000644000175000017500000000012210652670601015713 0ustar aliali= A script to report bugs to launchpad = ./lp --help for help. (More to follow) PIDA-0.5.1/pida/utils/launchpadder/__init__.py0000644000175000017500000000000010652670601017137 0ustar alialiPIDA-0.5.1/pida/utils/launchpadder/gtkgui.py0000644000175000017500000001137710652670601016715 0ustar aliali import threading import gtk import gobject from cgi import escape from gtk import gdk gdk.threads_init() import lplib as launchpadlib busy_cursor = gtk.gdk.Cursor(gtk.gdk.WATCH) normal_cursor = gtk.gdk.Cursor(gtk.gdk.RIGHT_PTR) TITLE_MARKUP = 'Make a launchpad bug report' def label_widget(widget, label): vb = gtk.VBox() vb.set_border_width(6) label = gtk.Label(label) label.set_alignment(0, 0.5) vb.pack_start(label, expand=False) exp = isinstance(widget, gtk.ScrolledWindow) vb.pack_start(widget, expand=exp) return vb class PasswordDialog(gtk.Dialog): def __init__(self): super(PasswordDialog, self).__init__('Enter User Details', parent = None, flags = 0, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) self.email = gtk.Entry() self.password = gtk.Entry() self.password.set_visibility(False) self.vbox.pack_start(label_widget(self.email, 'Email Address')) self.vbox.pack_start(label_widget(self.password, 'Password')) self.save_details = gtk.CheckButton() self.save_details.set_label('Save across sessions?') self.vbox.pack_start(self.save_details) self.show_all() def get_user_details(self): return (self.email.get_text(), self.password.get_text(), self.save_details.get_active()) class ReportWidget(gtk.VBox): def __init__(self, opts): super(ReportWidget, self).__init__() self.product = gtk.Entry() exp = gtk.Expander('Details') vb = gtk.VBox() exp.add(vb) if opts.show_product: prod_container = self else: prod_container = vb prod_container.pack_start(label_widget(self.product, 'Product'), False) self.title = gtk.Entry() self.pack_start(label_widget(self.title, 'Title'), False) self.comment = gtk.TextView() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self.comment) self.pack_start(label_widget(sw, 'Comment')) self.pack_start(exp, expand=False) self.baseurl = gtk.Entry() #vb.pack_start(label_widget(self.baseurl, 'Launchpad URL'), False) self.pulser = gtk.ProgressBar() self.pulser.set_no_show_all(True) self.pack_start(self.pulser, expand = False) self.baseurl.set_text(opts.root_url) self.product.set_text(opts.product) buf = self.comment.get_buffer() buf.insert(buf.get_start_iter(), opts.comment) self.title.set_text(opts.title) self.email, self.password = launchpadlib.get_local_config() self._pulsing = False def start_pulsing(self): self._pulsing = True self.pulser.show() def _pulse(): self.pulser.pulse() return self._pulsing gobject.timeout_add(100, _pulse) def stop_pulsing(self): self._pulsing = False self.pulser.hide() def report(self, finished_callback=lambda r: None): if self.email is None: self.get_pass() if self.email is None: return buf = self.comment.get_buffer() self.start_pulsing() def report(): product = self.product.get_text() results = launchpadlib.report( self.baseurl.get_text(), self.email, self.password, product, self.title.get_text(), buf.get_text(buf.get_start_iter(), buf.get_end_iter())) gobject.idle_add(self.stop_pulsing) gobject.idle_add(finished_callback, results) threading.Thread(target=report).start() def get_pass(self): pass_dlg = PasswordDialog() def pass_response(dlg, resp): dlg.hide() if resp == gtk.RESPONSE_ACCEPT: self.email, self.password, save = dlg.get_user_details() if save: launchpadlib.save_local_config(self.email, self.password) dlg.destroy() pass_dlg.connect('response', pass_response) pass_dlg.run() class ReportWindow(gtk.Dialog): def __init__(self, opts): super(ReportWindow, self).__init__('Launchpad Bug Report', parent = None, flags = 0, buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) self._reporter = ReportWidget(opts) self.vbox.pack_start(self._reporter) self.resize(400, 300) gobject.idle_add(self._reporter.title.grab_focus) PIDA-0.5.1/pida/utils/launchpadder/lplib.py0000644000175000017500000000622310652670601016517 0ustar aliali import urllib, urllib2, cookielib, os, getpass ROOT_URL = 'https://launchpad.net/' def fake_opts(root_url=ROOT_URL, product='', show_product=False, title='', no_gui=False, comment=''): class Opts: pass o = Opts() o.root_url = root_url o.product = product o.show_product = show_product o.title = title o.no_gui = no_gui o.comment = comment return o, [] class LaunchpadClient(object): def __init__(self, launchpad_base_url): self._base_url = launchpad_base_url self._cookies = cookielib.CookieJar() req = urllib2.Request(self._base_url) resp = urllib2.urlopen(req) self._cookies.extract_cookies(resp, req) def login(self, username, password): data = {'form': 'login', 'loginpage_email': username, 'loginpage_password': password, 'loginpage_submit_login': 'Log In'} url = self._base_url + 'products/+login' self._fetch(url, data) def submit_report(self, productpath, title, comment): data = { 'field.title': title, 'field.comment': comment, 'field.private': '', 'field.security_related': '', 'field.security_related.used': '', 'FORM_SUBMIT': 'Submit Bug Report', } url = self._base_url + productpath + '/+filebug' return self._fetch(url, data) def _fetch(self, url, data): postdata = urllib.urlencode(data) req = urllib2.Request(url, postdata) c = self._cookies._cookies_for_request(req)[0] req.add_header('Cookie', '%s=%s' % (c.name, c.value)) return urllib2.urlopen(req).read() def report_html(root_url, email, password, productpath, title, comment): client = LaunchpadClient(root_url) login_result = client.login(email, password) submit_result = client.submit_report(productpath, title, comment) return login_result, submit_result from xmlrpclib import ServerProxy def report(root_url, email, password, productpath, title, comment): s = ServerProxy('https://%s:%s@xmlrpc.launchpad.net/bugs/' % (email, password)) d = dict( product=productpath, summary=title, comment=comment, ) try: return True, s.filebug(d) except Exception, e: return False, e def get_local_config(): configfile = os.path.expanduser('~/.launchpad-client') try: f = open(configfile) name = f.readline().strip() password = f.readline().strip() return (name, password) except (OSError, IOError): return (None, None) def save_local_config(email, password): configfile = os.path.expanduser('~/.launchpad-client') f = open(configfile, 'w') f.write('%s\n%s\n\n' % (email, password)) f.close() def console_report(opts): email, password = get_local_config() if not email: email = raw_input('Email address: ') if not password: password = getpass.getpass('Password: ') product = 'products/%s/' % opts.product report(opts.root_url, email, password, product, opts.title, opts.comment) PIDA-0.5.1/pida/utils/launchpadder/lpreport.py0000755000175000017500000000541510652670601017271 0ustar aliali#! /usr/bin/env python import optparse, sys import lplib as launchpadlib def get_opts(): usage = """ Console\t%prog -n [-r ROOT_URL] -p PRODUCT -t TITLE -c COMMENT GUI\t%prog [-r ROOT_URL] [-p PRODUCT] [-t TITLE] [-c COMMENT] Details\t%prog --help""" parser = optparse.OptionParser(usage=usage) parser.add_option('-r', '--root-url', dest='root_url', action='store', type='string', help="""The base Launchpad URL. If not given defaults to the main launchpad at %s.""" % launchpadlib.ROOT_URL) parser.add_option('-p', '--product-name', dest='product', action='store', type='string', help="""The product name to report. This option is compulsory for console reports, where it will be the actual value. For GUI reports, it is the prefilled name and can be changed by the user""") parser.add_option('-t', '--title', dest='title', action='store', type='string', help="""The bug report title. This option is compulsory for console reports, where it will be the actual value. For GUI reports, it is the prefilled name and can be changed by the user""") parser.add_option('-c', '--comment', dest='comment', action='store', type='string', help="""The bug report comment. This option is compulsory for console reports, where it will be the actual value. For GUI reports, it is the prefilled name and can be changed by the user""") parser.add_option('-s', '--stdin-comment', dest='stdin_comment', action='store_true', help = """Read the comment text from stdin (overrides -c)""") parser.add_option('-n', '--no-gui', dest='no_gui', action='store_true', help="""Run in console (non-GUI mode)""") parser.add_option('-S', '--show-product', dest='show_product', action='store_true', help="""Show the product option (GUI only). The default behaviour without this option is to show the product field if it has not been priveded on the command line.""") opts, args = parser.parse_args() opts.product = opts.product or '' opts.title = opts.title or '' opts.comment = opts.comment or '' opts.root_url = opts.root_url or launchpadlib.ROOT_URL if opts.stdin_comment: opts.comment = sys.stdin.read() if not opts.product: opts.show_product = True if opts.no_gui: def _error(msg): parser.error('Must provide a %s for console reports' % msg) if not opts.product: _error('product') if not opts.title: _error('title') if not opts.comment: _error('comment') return opts, args if __name__ == '__main__': opts, args = get_opts() if opts.no_gui: launchpadlib.console_report(opts) else: import gtkgui gtkgui.gui_report(opts) sys.exit(0) PIDA-0.5.1/pida/utils/pyflakes/0002755000175000017500000000000010652671502014227 5ustar alialiPIDA-0.5.1/pida/utils/pyflakes/__init__.py0000644000175000017500000002034610652670605016346 0ustar aliali# (c) 2005 Divmod, Inc. See LICENSE file for details import __builtin__ import messages class Binding(object): def __init__(self, name, source): self.name = name self.source = source self.used = False def __str__(self): return self.name def __repr__(self): return '' % (self.name, self.source.lineno, id(self)) class UnBinding(Binding): '''Created by the 'del' operator.''' class Importation(Binding): def __init__(self, name, source): name = name.split('.')[0] super(Importation, self).__init__(name, source) class Assignment(Binding): pass class Scope(dict): importStarred = False # set to True when import * is found def __repr__(self): return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self)) def __init__(self): super(Scope, self).__init__() class ClassScope(Scope): pass class FunctionScope(Scope): pass class ModuleScope(Scope): pass class Checker(object): nodeDepth = 0 traceTree = False def __init__(self, tree, filename='(none)'): self.deferred = [] self.dead_scopes = [] self.messages = [] self.filename = filename self.scopeStack = [ModuleScope()] self.handleChildren(tree) for handler, scope in self.deferred: self.scopeStack = scope handler() del self.scopeStack[1:] self.popScope() self.check_dead_scopes() def defer(self, callable): '''Schedule something to be called after just before completion. This is used for handling function bodies, which must be deferred because code later in the file might modify the global scope. When `callable` is called, the scope at the time this is called will be restored, however it will contain any new bindings added to it. ''' self.deferred.append( (callable, self.scopeStack[:]) ) def scope(self): return self.scopeStack[-1] scope = property(scope) def popScope(self): self.dead_scopes.append(self.scopeStack.pop()) def check_dead_scopes(self): for scope in self.dead_scopes: for importation in scope.itervalues(): if isinstance(importation, Importation) and not importation.used: self.report(messages.UnusedImport, importation.source.lineno, importation.name) def pushFunctionScope(self): self.scopeStack.append(FunctionScope()) def pushClassScope(self): self.scopeStack.append(ClassScope()) def report(self, messageClass, *args, **kwargs): self.messages.append(messageClass(self.filename, *args, **kwargs)) def handleChildren(self, tree): for node in tree.getChildNodes(): self.handleNode(node) def handleNode(self, node): if self.traceTree: print ' ' * self.nodeDepth + node.__class__.__name__ self.nodeDepth += 1 try: handler = getattr(self, node.__class__.__name__.upper()) handler(node) finally: self.nodeDepth -= 1 if self.traceTree: print ' ' * self.nodeDepth + 'end ' + node.__class__.__name__ def ignore(self, node): pass STMT = PRINT = PRINTNL = TUPLE = LIST = ASSTUPLE = ASSATTR = \ ASSLIST = GETATTR = SLICE = SLICEOBJ = IF = CALLFUNC = DISCARD = FOR = \ RETURN = ADD = MOD = SUB = NOT = UNARYSUB = INVERT = ASSERT = COMPARE = \ SUBSCRIPT = AND = OR = TRYEXCEPT = RAISE = YIELD = DICT = LEFTSHIFT = \ RIGHTSHIFT = KEYWORD = TRYFINALLY = WHILE = EXEC = MUL = DIV = POWER = \ FLOORDIV = BITAND = BITOR = BITXOR = LISTCOMPFOR = LISTCOMPIF = \ AUGASSIGN = BACKQUOTE = UNARYADD = GENEXPR = GENEXPRFOR = GENEXPRIF = handleChildren CONST = PASS = CONTINUE = BREAK = GLOBAL = ELLIPSIS = ignore def addBinding(self, lineno, value, reportRedef=True): '''Called when a binding is altered. - `lineno` is the line of the statement responsible for the change - `value` is the optional new value, a Binding instance, associated with the binding; if None, the binding is deleted if it exists. - iff `reportRedef` is True (default), rebinding while unused will be reported. ''' if isinstance(self.scope.get(value.name), Importation) \ and not self.scope[value.name].used \ and reportRedef: self.report(messages.RedefinedWhileUnused, lineno, value.name, self.scope[value.name].source.lineno) if isinstance(value, UnBinding): try: del self.scope[value.name] except KeyError: self.report(messages.UndefinedName, lineno, value.name) else: self.scope[value.name] = value def LISTCOMP(self, node): for qual in node.quals: self.handleNode(qual) self.handleNode(node.expr) GENEXPRINNER = LISTCOMP def NAME(self, node): # try local scope importStarred = self.scope.importStarred try: self.scope[node.name].used = True except KeyError: pass else: return # try enclosing function scopes for scope in self.scopeStack[-2:0:-1]: importStarred = importStarred or scope.importStarred if not isinstance(scope, FunctionScope): continue try: scope[node.name].used = True except KeyError: pass else: return # try global scope importStarred = importStarred or self.scopeStack[0].importStarred try: self.scopeStack[0][node.name].used = True except KeyError: if (not hasattr(__builtin__, node.name)) \ and node.name not in ['__file__'] \ and not importStarred: self.report(messages.UndefinedName, node.lineno, node.name) def FUNCTION(self, node): if getattr(node, "decorators", None) is not None: self.handleChildren(node.decorators) self.addBinding(node.lineno, Assignment(node.name, node)) self.LAMBDA(node) def LAMBDA(self, node): for default in node.defaults: self.handleNode(default) def runFunction(): args = [] def addArgs(arglist): for arg in arglist: if isinstance(arg, tuple): addArgs(arg) else: if arg in args: self.report(messages.DuplicateArgument, node.lineno, arg) args.append(arg) self.pushFunctionScope() addArgs(node.argnames) for name in args: self.addBinding(node.lineno, Assignment(name, node), reportRedef=False) self.handleNode(node.code) self.popScope() self.defer(runFunction) def CLASS(self, node): self.addBinding(node.lineno, Assignment(node.name, node)) for baseNode in node.bases: self.handleNode(baseNode) self.pushClassScope() self.handleChildren(node.code) self.popScope() def ASSNAME(self, node): if node.flags == 'OP_DELETE': self.addBinding(node.lineno, UnBinding(node.name, node)) else: self.addBinding(node.lineno, Assignment(node.name, node)) def ASSIGN(self, node): self.handleNode(node.expr) for subnode in node.nodes[::-1]: self.handleNode(subnode) def IMPORT(self, node): for name, alias in node.names: name = alias or name importation = Importation(name, node) self.addBinding(node.lineno, importation) def FROM(self, node): for name, alias in node.names: if name == '*': self.scope.importStarred = True self.report(messages.ImportStarUsed, node.lineno, node.modname) continue name = alias or name importation = Importation(name, node) if node.modname == '__future__': importation.used = True self.addBinding(node.lineno, importation) PIDA-0.5.1/pida/utils/pyflakes/messages.py0000644000175000017500000000326410652670605016416 0ustar aliali# (c) 2005 Divmod, Inc. See LICENSE file for details class Message(object): message = '' message_args = () def __init__(self, filename, lineno): self.filename = filename self.lineno = lineno def __str__(self): return '%s:%s: %s' % (self.filename, self.lineno, self.message % self.message_args) class UnusedImport(Message): message = '%r imported but unused' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedWhileUnused(Message): message = 'redefinition of unused %r from line %r' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) class ImportStarUsed(Message): message = "'from %s import *' used; unable to detect undefined names" def __init__(self, filename, lineno, modname): Message.__init__(self, filename, lineno) self.message_args = (modname,) class UndefinedName(Message): message = 'undefined name %r' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class DuplicateArgument(Message): message = 'duplicate argument %r in function definition' def __init__(self, filename, lineno, name): Message.__init__(self, filename, lineno) self.message_args = (name,) class RedefinedFunction(Message): message = 'redefinition of fuction %r from line %r' def __init__(self, filename, lineno, name, orig_lineno): Message.__init__(self, filename, lineno) self.message_args = (name, orig_lineno) PIDA-0.5.1/pida/utils/rat/0002755000175000017500000000000010652671502013177 5ustar alialiPIDA-0.5.1/pida/utils/rat/__init__.py0000644000175000017500000000064710652670600015313 0ustar aliali""" Rat is a bunch of modules for agilizing PyGtk development. It is not a framework, therefore it does not imposes a certain arquitecture. Rat is food for pythons therefore it's easy to swallow, which means it's also easy to learn! """ __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" __license__ = "MIT " PIDA-0.5.1/pida/utils/rat/gwp.py0000644000175000017500000003053310652670600014346 0ustar aliali__doc__ = """ GConf Widget Persistency is a module for maintaining persistency between your existing widgets and the GConf keys. Not only it forces the schema you've defined for the key but also preserves the widget state, for example making it insensitive when the GConf key is insensitive. It also implements a representation of a gconf key(GConfValue) that handles the repetitive hassles of a maintaining its integrity. Use the L{create_persistency_link} function to create persistency links between your widget and a gconf value. The signature of the function changes according the first argument: * gtk.FileChooserButton:(button, key, use_directory=False, use_uri=True, *args, **kwargs) * gtk.Entry:(entry, key, data_spec = Spec.STRING, *args, **kwargs) * gtk.SpinButton:(spinbutton, key, use_int = True, *args, **kwargs) * gtk.ToggleButton:(toggle, key, *args, **kwargs) You can add new handlers to the L{create_persistency_link} function like this:: import gwp gwp.create_persistency_link.append_handler(SomeClass, my_handler) C{my_handler} should be a function that returns a L{PersistencyLink} Here's a simple example on how to use it:: from rat import gwp import gtk import gconf # Monitor the key, so gaw can listen for gconf events gconf.client_get_default().add_dir("/apps/gaw", gconf.CLIENT_PRELOAD_NONE) win = gtk.Window(gtk.WINDOW_TOPLEVEL) entry = gtk.Entry() entry.show() # bind the key with the widget link = gwp.create_persistency_link(entry, "/apps/gaw/str_key") win.add(entry) win.show() gtk.main() """ __license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import gconf import gobject import gtk from swp import * class Spec: """ The spec is an adapter between a GConfValue and a Python value, simplifying the conversion and the integrity. You should use L{Spec.STRING}, L{Spec.FLOAT}, L{Spec.INT} and L{Spec.BOOL} instead. """ def __init__(self, name, gconf_type, py_type, default): self.gconf_type = gconf_type self.py_type = py_type self.default = default self.name = name Spec.STRING = Spec("string", gconf.VALUE_STRING, str, '') Spec.FLOAT = Spec("float", gconf.VALUE_FLOAT, float, 0.0) Spec.INT = Spec("int", gconf.VALUE_INT, int, 0) Spec.BOOL = Spec("bool", gconf.VALUE_BOOL, bool, True) class GConfValue(object): """ The GConfValue represents the GConf key's data. You define a certain schema (or type of data) and GConfValue keeps track of its integrity. It adds the possibility to define a default value to be used when the key is inexistent or contains an invalid data type. You can also define callbacks that notify you when the key is altered. Taken from U{GAW Introduction }:: import gwp, gconf, gtk gconf.client_get_default().add_dir("/apps/gwp", gconf.CLIENT_PRELOAD_NONE) key_str = gwp.GConfValue( key = "/apps/gwp/key_str", data_spec = gwp.Spec.STRING ) def on_changed(*args): global key_str print key_str.key, "=", key_str.data gtk.main_quit() tmp.set_callback(on_changed) tmp.data = "Hello world" gtk.main() """ _notify_id = None def __init__(self, key, data_spec, client = None, **kwargs): if not client: client = gconf.client_get_default() self.client = client self.key = key self.data_spec = data_spec if "default" in kwargs: self.default = kwargs["default"] ############ # data_spec def get_data_spec(self): return self._data_spec def set_data_spec(self, data_spec): self._data_spec = data_spec self._setter = getattr(self.client, "set_" + data_spec.name) self._getter = getattr(self.client, "get_" + self.data_spec.name) data_spec = property(get_data_spec, set_data_spec) ####### # data def get_data(self): try: val = self._getter(self.key) except gobject.GError: return self.default if val is None: return self.default return val def set_data(self, value): assert isinstance(value, self.data_spec.py_type) val = self.get_data() if val != value: self._setter(self.key, value) data = property(get_data, set_data) ########## # default def get_default(self): return getattr(self, "_default", self.data_spec.default) def set_default(self, default): self._default = default default = property(get_default, set_default) ############### # is writable def get_is_writable(self): return self.client.key_is_writable(self.key) is_writable = property(get_is_writable) ################ # Other methods def set_callback(self, on_changed): assert on_changed is None or callable(on_changed) if self._notify_id is not None: self.client_notify_remove(self._notify_id) self._notify_id = None self._on_changed_cb = None if on_changed is not None: self._on_changed_cb = on_changed self._notify_id = self.client.notify_add( self.key, self._on_changed ) def _on_changed(self, *args): self._on_changed_cb(self) def __del__(self): self.set_callback(None) def reset_default(self): """ Resets the default value to the one present in the Spec """ if hasattr(self, "_default"): del self._default class RadioButtonPersistencyLink: """ A radio_group is a dictionary that associates a gconf boolean key with a radio button:: data = RadioButtonPersistency( { 'cheese': cheese_btn, 'ham': ham_btn, 'fish': fish_btn }, ) data.selected_by_default = 'ham' selected_value = data.data data.data = 'fish' """ selected_by_default = None def __init__(self, widgets, key, client = None): self.widgets = widgets self.keys = {} self.gconf_value = GConfValue(key, Spec.STRING, client) self.gconf_value.set_callback(self._on_gconf_changed) notify_widget = False for key, widget in widgets.iteritems(): if not notify_widget: widget.connect("toggled", self._on_widget_changed) notify_widget = True widget.connect("destroy", self._on_destroy) self.keys[widget] = key self.sync_widget() def _on_destroy(self, widget): key = self.keys[widget] del self.widgets[key] # Set the widget to none so that the key still exists self.keys[widget] = None def _get_active(self): for radio in self.keys: if radio is not None and radio.get_active(): return radio return None def _on_widget_changed(self, radio_button): # Update gconf entries self.sync_gconf() def _on_gconf_changed(self, data): data_spec = self.gconf_value.data_spec for widget in self.keys: widget.set_sensitive(self.gconf_value.is_writable) self.sync_widget() self.sync_gconf() def sync_widget(self): key = self.gconf_value.data if key in self.widgets: # value is in radio group self.widgets[key].set_active(True) else: # When there is a default value, set it if self.selected_by_default is not None: self.data = self.selected_by_default # Otherwise deselect all entries active = self._get_active() if active is not None: # Unset the active radio button active.set_active(False) self.sync_gconf() def sync_gconf(self): active = self._get_active() if active is not None: self.gconf_value.data = self.keys[active] else: self.gconf_value.reset_default() def set_data(self, value): self.sync_gconf() self.gconf_value = value def get_data(self): self.sync_gconf() return self.gconf_value.data data = property(get_data, set_data) def cmp_func(cls, obj): try: obj_iter = iter(obj) except TypeError: return False for item in obj_iter: if not isinstance(item, gtk.RadioButton): return False return True cmp_func = classmethod(cmp_func) create_persistency_link = PersistencyLinkFactory() def _persistency_link_file_chooser(button, key, use_directory=False, use_uri=True, *args, **kwargs): """ Associates a L{gwp.PersistencyLink} to a gtk.FileChooserButton. This is an utility function that wrapps around L{gwp.PersistencyLink}. @param button: the file chooser button @param key: the gconf key @param use_directory: boolean variable setting if it's we're using files or directories. @param use_uri: boolean variable setting if we're using URI's or normal filenames. @param default: the default value that L{gwp.GConfValue} falls back to. @param client: The GConfClient @type button: U{gtk.FileChooserButton } @rtype: L{gwp.PersistencyLink} """ if not use_directory and not use_uri: getter = button.get_filename setter = button.set_filename elif not use_directory and use_uri: getter = button.get_uri setter = button.set_uri elif use_directory and not use_uri: getter = button.get_current_folder setter = button.set_current_folder elif use_directory and use_uri: getter = button.get_current_folder_uri setter = button.set_current_folder_uri return PersistencyLink(button, getter, setter, "selection-changed", GConfValue(key, Spec.STRING, default=default, client=client, *args, **kwargs), is_lazy=True) create_persistency_link.append_handler(gtk.FileChooserButton, _persistency_link_file_chooser) def _persistency_link_entry(entry, key, data_spec = Spec.STRING, *args, **kwargs): """ Associates to a U{gtk.Entry } @rtype: L{gwp.PersistencyLink} """ return PersistencyLink(entry, entry.get_text, entry.set_text, "changed", GConfValue(key, data_spec, *args, **kwargs)) create_persistency_link.append_handler(gtk.Entry, _persistency_link_entry) def _persistency_link_spin_button(spinbutton, key, use_int = True, *args, **kwargs): """ Associates to a U{gtk.SpinButton } @param use_int: when set to False it uses floats instead. @rtype: L{gwp.PersistencyLink} """ if use_int: return PersistencyLink(spinbutton, spinbutton.get_value_as_int, spinbutton.set_value, "value-changed", GConfValue(key, Spec.INT, *args, **kwargs)) else: return PersistencyLink(spinbutton, spinbutton.get_value, spinbutton.set_value, "value-changed", GConfValue(key, Spec.FLOAT, *args, **kwargs)) create_persistency_link.append_handler(gtk.SpinButton, _persistency_link_spin_button) def _persistency_link_toggle_button(toggle, key, *args, **kwargs): """ This is to be used with a U{gtk.ToggleButton } @rtype: L{gwp.PersistencyLink} """ return PersistencyLink(toggle, toggle.get_active, toggle.set_active, "toggled", GConfValue(key, Spec.BOOL, *args, **kwargs)) create_persistency_link.append_handler(gtk.ToggleButton, _persistency_link_toggle_button) create_persistency_link.append_handler_full(RadioButtonPersistencyLink.cmp_func, RadioButtonPersistencyLink)PIDA-0.5.1/pida/utils/rat/hig.py0000644000175000017500000007256110652670600014327 0ustar aliali__doc__ = """ The HIG module offers a set of utility functions and widgets to implement HIG compliant interfaces. This module defines a pipeline of costumizing widgets, it follows the simple procedure that creating a HIG compliant application can be thought of a work that has patterns of costumizations that act in different levels with different widgets, we'll address them as Costumizers. To call a dialog for asking the user to save changes on a certain file use this:: >>> import rat.hig >>> rat.hig.save_changes(["foo.bar", "foo"], title = "Save changes?") (['foo.bar'], -6) There are also utility functions for calling dialogs, like warning messages:: >>> rat.hig.dialog_warn("Rat will simplify your code", ... "By putting common utilities in one place makes all " ... "benefit and get nicer apps.") -5 Sometimes you want to manipulate the dialog before running it:: >>> rat.hig.dialog_warn("Rat will simplify your code", ... "By putting common utilities in one place makes all " ... "benefit and get nicer apps.", run = False) """ __license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import gobject import gtk import datetime from util import find_child_widget from gettext import gettext as _ from gettext import ngettext as N_ class WidgetCostumizer: """ The WidgetCostumizer is a class template for defining chaining of asseblies of interfaces. For example you can create a dialog with this simple lines of code:: creator.bind(SetupDialog()).bind(SetupAlert(primary_text, secondary_text, **kwargs)) dlg = creator() The costumization of a widget is like a pipeline of filters that act on a certain widget and on a toplevel container. """ _to_attrs = True _defaults = {} _next_values = None _next_iter = None def __init__(self, *args, **kwargs): self._args = args self._kwargs = dict(self._defaults) self._kwargs.update(kwargs) self._next_values = [] def _get_next(self): return self._next_iter.next() def update(self, **kwargs): self._kwargs.update(kwargs) def _run(self, *args, **kwargs): pass def bind(self, *others): for other in others: if not isinstance(other, WidgetCostumizer): raise TypeError(type(other)) self._next_values.append(other) return self def _call(self, widget, container): if self._to_attrs: for key, value in self._kwargs.iteritems(): setattr(self, key, value) widget, container = self._run(widget, container) for costum in self._next_values: widget, container = costum._call(widget, container) for key in self._kwargs: delattr(self, key) return widget, container def __call__(self, widget = None, container = None): """This method is only called once""" return self._call(widget, container)[0] class SetupScrolledWindow(WidgetCostumizer): def _run(self, scrolled, container): assert container is None if scrolled is None: scrolled = gtk.ScrolledWindow() scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) return scrolled, None class SetupLabel(WidgetCostumizer): """ Usage:: lbl = SetupLabel("foo")(gtk.Label()) lbl.show() Or:: lbl = SetupLabel("foo")() lbl.show() """ def _run(self, lbl, container): assert container is None assert len(self._args) == 0 or len(self._args) == 1 if lbl is None: lbl = gtk.Label() lbl.set_alignment(0, 0) if len(self._args) == 1: lbl.set_markup(self._args[0]) lbl.set_selectable(True) lbl.set_line_wrap(True) return lbl, container def _dialog_decorator(func): def wrapper(self, dialog, container): if container is None: container = dialog.get_child() return func(self, dialog, container) return wrapper class SetupDialog(WidgetCostumizer): def _run(self, dialog, container): dialog.set_border_width(4) dialog.set_has_separator(False) dialog.set_title("") dialog.set_resizable(False) align = gtk.Alignment() align.set_padding( padding_top = 0, padding_bottom = 7, padding_left = 0, padding_right = 0 ) align.set_border_width(5) align.show() container.add(align) return dialog, align _run = _dialog_decorator(_run) class SetupAlert(WidgetCostumizer): class _PrimaryTextDecorator: def __init__(self, label): self.label = label def __call__(self, primary_text): self.label.set_markup( ''+primary_text+'' ) _defaults = { "title": "", "stock": gtk.STOCK_DIALOG_INFO } def _before_text(self, dialog, vbox): pass def _after_text(self, dialog, vbox): pass def _run(self, dialog, container): primary_text, secondary_text = self._args dialog.set_title(self.title) hbox = gtk.HBox(spacing = 12) hbox.set_border_width(0) hbox.show() container.add(hbox) img = gtk.Image() img.set_from_stock(self.stock, gtk.ICON_SIZE_DIALOG) img.set_alignment(img.get_alignment()[0], 0.0) img.show() hbox.pack_start(img, False, False) vbox = gtk.VBox(spacing = 6) vbox.show() hbox.pack_start(vbox) lbl = SetupLabel( ''+primary_text+'' )() lbl.show() dialog.set_primary_text = self._PrimaryTextDecorator(lbl) vbox.pack_start(lbl, False, False) lbl = SetupLabel(secondary_text)() lbl.show() dialog.set_secondary_text = lbl.set_text def on_destroy(widget): delattr(widget, "set_secondary_text") delattr(widget, "set_primary_text") dialog.connect("destroy", on_destroy) self._before_text(dialog, vbox) vbox.pack_start(lbl, False, False) self._after_text(dialog, vbox) return dialog, vbox _run = _dialog_decorator(_run) class _SetupRadioChoiceList(SetupAlert): def _after_text(self, dialog, container): vbox = gtk.VBox(spacing=6) vbox.show() vbox.set_name("items") container.pack_start(vbox) group = None for item in self.items: radio = gtk.RadioButton(group, item) radio.show() if group is None: group = radio vbox.pack_start(radio, False, False) class SetupListAlertTemplate(SetupAlert): def get_list_title(self): raise NotImplementedError def configure_widgets(self, dialog, tree): raise NotImplementedError def create_store(self): raise NotImplementedError def _before_text(self, dialog, vbox): store = self.create_store() title = self.get_list_title() if title is not None: lbl = SetupLabel(title)() lbl.show() vbox.pack_start(lbl, False, False) tree = gtk.TreeView() tree.set_name("list_view") tree.set_model(store) tree.set_headers_visible(False) tree.show() scroll = SetupScrolledWindow()() scroll.add(tree) scroll.show() scroll.set_name("scrolled_window") vbox.add(scroll) self.configure_widgets(dialog, tree) count = len(tree.get_model()) # Update the size according to the number of elements on the list if count > 5: scroll.set_size_request(-1, min(154, 30 * count / 2)) return dialog, vbox class _SetupMultipleChoiceList(SetupListAlertTemplate): _defaults = { "list_title": None } _defaults.update(SetupAlert._defaults) def get_list_title(self): return self.list_title def configure_widgets(self, dialog, tree): store = tree.get_model() # Create the callback def on_toggle(render, path, args): dialog, model, min_sel, max_sel = args tree_iter = model.get_iter(path) row = model[tree_iter] row[0] = not row[0] if row[0]: model.enabled_rows += 1 else: model.enabled_rows -= 1 if model.enabled_rows == 0: is_sensitive = False elif max_sel >= 0: is_sensitive = min_sel <= model.enabled_rows <= max_sel else: is_sensitive = min_sel <= model.enabled_rows dialog.set_response_sensitive(gtk.RESPONSE_OK, is_sensitive) args = (dialog, store, self.min_select, self.max_select) rend = gtk.CellRendererToggle() rend.connect("toggled", on_toggle, args) col = gtk.TreeViewColumn("", rend, active = 0) tree.append_column(col) rend = gtk.CellRendererText() col = gtk.TreeViewColumn("", rend, text = 1) tree.append_column(col) dialog.set_response_sensitive(gtk.RESPONSE_OK, False) def create_store(self): store = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING) store.enabled_rows = 0 for item in self.items: store.append((False, item)) return store class _SetupListAlert(SetupListAlertTemplate): _defaults = { "list_title": None } _defaults.update(SetupAlert._defaults) def get_list_title(self): return self.list_title def configure_widgets(self, dialog, tree): rend = gtk.CellRendererText() col = gtk.TreeViewColumn("", rend, text = 0) tree.append_column(col) tree.get_selection().set_mode(gtk.SELECTION_NONE) def create_store(self): store = gtk.ListStore(gobject.TYPE_STRING) for item in self.items: store.append((item,)) return store class _SetupSingleChoiceList(_SetupListAlert): _defaults = { "min_select": 1, } _defaults.update(_SetupListAlert._defaults) def configure_widgets(self, dialog, tree): assert self.min_select in (0, 1) _SetupListAlert.configure_widgets(self, dialog, tree) selection = tree.get_selection() if self.min_select == 0: selection_mode = gtk.SELECTION_SINGLE def on_selection_changed(selection, dialog): is_sensitive = selection.count_selected_rows() > 0 dialog.set_response_sensitive(gtk.RESPONSE_OK, is_sensitive) selection.connect("changed", on_selection_changed, dialog) else: selection_mode = gtk.SELECTION_BROWSE selection.set_mode(selection_mode) class RunDialog(WidgetCostumizer): """ This is a terminal costumizer because it swaps the gtk.Dialog recieved by argument for its `gtk.Dialog.run`'s result. """ def _run(self, dialog, container): response = dialog.run() dialog.destroy() return response, None def hig_alert(primary_text, secondary_text, parent = None, flags = 0, \ buttons =(gtk.STOCK_OK, gtk.RESPONSE_OK), run = True, \ _setup_alert = SetupAlert, **kwargs): if parent is None and "title" not in kwargs: raise TypeError("When you don't define a parent you must define a " "title") dlg = gtk.Dialog(parent = parent, flags = flags, buttons = buttons) costumizer = SetupDialog() costumizer.bind(_setup_alert(primary_text, secondary_text, **kwargs)) if run: costumizer.bind(RunDialog()) return costumizer(dlg) ################################# # choice_dialog class _OneStrategy: accepts = lambda self, choices, min_select, max_select: choices == 1 def before(self, kwargs): pass def get_items(self, data): return (0,) class _BaseStrategy: def before(self, kwargs): kwargs["_setup_alert"] = self.setup_factory class _MultipleStrategy(_BaseStrategy): accepts = lambda self, choices, min_select, max_select: max_select == -1 or\ max_select > 1 setup_factory = _SetupMultipleChoiceList def get_items(self, dlg): # Multiple selection store = find_child_widget(dlg, "list_view").get_model() return tuple(row.path[0] for row in store if row[0]) class _RadioStrategy(_BaseStrategy): accepts = lambda self, choices, min_select, max_select: choices < 5 setup_factory = _SetupRadioChoiceList def get_items(self, dlg): vbox = find_child_widget(dlg, "items") counter = 0 for radio in vbox.get_children(): if radio.get_active(): break counter += 1 assert radio.get_active() for radio in vbox.get_children(): vbox.remove(radio) radio.destroy() return (counter,) class _SingleListStrategy(_BaseStrategy): accepts = lambda self, a, b, c: True setup_factory = _SetupSingleChoiceList def get_items(self, dlg): list_view = find_child_widget(dlg, "list_view") rows = list_view.get_selection().get_selected_rows()[1] get_element = lambda row: row[0] items = tuple(map(get_element, rows)) _STRATEGIES = (_OneStrategy, _MultipleStrategy, _RadioStrategy, _SingleListStrategy) _STRATEGIES = tuple(factory() for factory in _STRATEGIES) def choice(primary_text, secondary_text, parent=None, allow_cancel=True, \ **kwargs): """ @param items: the items you want to choose from @param list_title: the title of the list. Optional. @param allow_cancel: If the user can cancel/close the dialog. @param min_select: The minimum number of elements to be selected. @param max_select: The maximum number of elements to be selected. -1 Means no limit. @param dialog_callback: This is a callback function that is going to be called when the dialog is created. The argument is the dialog object. @param one_item_text: when specified and if the number of `items` is one this text will be the primary text. This string must contain a '%s' which will be replaced by the item value. Optional. """ if "run" in kwargs: del kwargs["run"] choices = len(kwargs["items"]) min_select = kwargs.get("min_select", 1) max_select = kwargs.get("max_select", -1) # Make sure the arguments are correct assert choices > 0 assert (max_select == -1) ^ (min_select <= max_select <= choices) assert 0 <= min_select <= choices buttons = (kwargs.get("ok_button", gtk.STOCK_OK), gtk.RESPONSE_OK) if allow_cancel: buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + buttons else: # TODO: make closing the window impossible pass if min_select == 0: txt = N_("Don't select it", "Don't select any items", choices) txt = kwargs.get("skip_button", txt) buttons = (txt, gtk.RESPONSE_CLOSE) + buttons for strategy in _STRATEGIES: if strategy.accepts(choices, min_select, max_select): break assert strategy.accepts(choices, min_select, max_select) if choices == 1: if "one_item_text" in kwargs: primary_text = kwargs["one_item_text"] % kwargs["items"][0] data = strategy.before(kwargs) if data is not None: primary_text = data dlg = hig_alert( primary_text, secondary_text, parent = parent, run = False, buttons = buttons, **kwargs ) kwargs.get("dialog_callback", lambda foo: None)(dlg) response = dlg.run() if response != gtk.RESPONSE_OK: dlg.destroy() return (), response items = strategy.get_items(dlg) dlg.destroy() return items, response ############################# # save_changes _MIN_FRACTION = 60 _HOUR_FRACTION = 60 * _MIN_FRACTION _DAY_FRACTION = 24 * _HOUR_FRACTION def _humanize_seconds(elapsed_seconds, use_hours = True, use_days = True): """ Turns a number of seconds into to a human readable string, example 125 seconds is: '2 minutes and 5 seconds'. @param elapsed_seconds: number of seconds you want to humanize @param use_hours: wether or not to render the hours(if hours > 0) @param use_days: wether or not to render the days(if days > 0) """ text = [] duration = elapsed_seconds if duration == 0: return _("0 seconds") days = duration / _DAY_FRACTION if use_days and days > 0: text.append(N_("%d day", "%d days", days) % days) duration %= _DAY_FRACTION hours = duration / _HOUR_FRACTION if use_hours and hours > 0: text.append(N_("%d hour", "%d hours", hours) % hours) duration %= _HOUR_FRACTION minutes = duration / _MIN_FRACTION if minutes > 0: text.append(N_("%d minute", "%d minutes", minutes) % minutes) duration %= _MIN_FRACTION seconds = duration % 60 if seconds > 0: text.append(N_("%d second", "%d seconds", seconds) % seconds) if len(text) > 2: # To translators: this joins 3 or more time fractions return _(", ").join(text[:-1]) + _(" and ") + text[-1] else: # To translators: this joins 2 or 1 time fractions return _(" and ").join(text) class _TimeUpdater: def __init__(self, initial_time): self.initial_time = initial_time def set_dialog(self, dialog): self.dialog = dialog self.dialog.connect("response", self.on_response) self.source = gobject.timeout_add(500, self.on_tick) def on_response(self, *args): gobject.source_remove(self.source) def get_text(self): last_changes = datetime.datetime.now() - self.initial_time # To translators %s is the time secondary_text = _("If you don't save, changes from the last %s " "will be permanently lost.") return secondary_text % _humanize_seconds(last_changes.seconds) def on_tick(self): self.dialog.set_secondary_text(self.get_text()) return True def save_changes(files, last_save=None, parent=None, **kwargs): """ Shows up a Save changes dialog to a certain list of documents and returns a tuple with two values, the first is a list of files that are to be saved the second is the value of the response, which can be one of: - gtk.RESPONSE_OK - the user wants to save - gtk.RESPONSE_CANCEL - the user canceled the dialog - gtk.RESPONSE_CLOSE - the user wants to close without saving - gtk.RESPONSE_DELETE_EVENT - the user closed the window So if you want to check if the user canceled just check if the response is equal to gtk.RESPONSE_CANCEL or gtk.RESPONSE_DELETE_EVENT When the `elapsed_time` argument is not `None` it should be a list of the elapsed time since each was modified. It must be in the same order of the `files` argument. This function also accepts every argument that a hig_alert function accepts, which means it accepts `title`, etc. Note that this function overrides the `run` argument and sets it to True, because it's not possible for a user to know which files were saved since the dialog changes is structure depending on the arguments. Simple usage example:: files_to_save, response = save_changes(["foo.bar"], title="Rat Demo") @param files: a list of filenames to be saved @param last_save: when you only want to save one file you can optionally send the date of when the user saved the file most recently. @type last_save: datetime.datetime @param parent: the window that will be parent of this window. @param primary_text: optional, see hig_alert. @param secondary_text: optional, see hig_alert. @param one_item_text: optional, see choice_alert. @param list_title: optional, see choice_alert. @param kwargs: the remaining keyword arguments are the same as used on the function hig_alert. @return: a tuple with a list of entries the user chose to save and a gtk.RESPONSE_* from the dialog """ primary_text = N_("There is %d file with unsaved changes. " "Save changes before closing?", "There are %d files with unsaved " "changes. Save changes before closing?", len(files)) primary_text %= len(files) primary_text = kwargs.get("primary_text", primary_text) secondary_text = _("If you don't save, all your changes will be " "permanently lost.") secondary_text = kwargs.get("secondary_text", secondary_text) one_item_text = _("Save the changes to %s before closing?") one_item_text = kwargs.get("one_item_text", one_item_text) list_title = _("Select the files you want to save:") list_title = kwargs.get("list_title", list_title) if len(files) == 1 and last_save is not None: updater = _TimeUpdater(last_save) secondary_text = updater.get_text() kwargs["dialog_callback"] = updater.set_dialog indexes, response = choice( primary_text, secondary_text, parent = parent, min_select = 0, max_select = -1, skip_button = _("Close without saving"), ok_button = gtk.STOCK_SAVE, list_title = list_title, items = files, one_item_text = one_item_text, **kwargs ) return map(files.__getitem__, indexes), response ################# # Common dialogs def error(primary_text, secondary_text, **kwargs): return hig_alert( primary_text, secondary_text, stock = gtk.STOCK_DIALOG_ERROR, buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK), **kwargs ) def warning(primary_text, secondary_text, **kwargs): return hig_alert( primary_text, secondary_text, stock = gtk.STOCK_DIALOG_WARNING, buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK), **kwargs ) def ok_cancel(primary_text, secondary_text, ok_button=gtk.STOCK_OK, **kwargs): return hig_alert( primary_text, secondary_text, stock = gtk.STOCK_DIALOG_WARNING, buttons =( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, ok_button, gtk.RESPONSE_OK ), **kwargs ) def info(primary_text, secondary_text, **kwargs): return hig_alert( primary_text, secondary_text, stock = gtk.STOCK_DIALOG_INFO, buttons =(gtk.STOCK_CLOSE, gtk.RESPONSE_OK), **kwargs ) def listing(primary_text, secondary_text, parent=None, items=(), **kwargs): """ @param list_title: A label will be placed above the list of items describing what's the content of the list. Optional. Every other argument that L{hig_alert} function does. Example:: listing( "Listing cool stuff", "To select more of that stuff eat alot of cheese!", items=["foo", "bar"] * 10, # Some random 20 elements title="Rat Demo", list_title="Your cool stuff:" ) """ return hig_alert( primary_text, secondary_text, parent = parent, _setup_alert = _SetupListAlert, items = items, **kwargs ) ######## class HigProgress(gtk.Window): """ HigProgress returns a window that contains a number of properties to access what a common Progress window should have. """ def __init__(self): gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) self.set_border_width(6) self.set_resizable(False) self.set_title('') # defaults to center location self.set_position(gtk.WIN_POS_CENTER) self.connect("delete-event", self._on_close) # main container main = gtk.VBox(spacing = 12) main.set_spacing(12) main.set_border_width(6) main.show() self.add(main) # primary text alg = gtk.Alignment() alg.set_padding(0, 6, 0, 0) alg.show() main.pack_start(alg, False, False) lbl = SetupLabel()() lbl.set_selectable(False) lbl.show() self._primary_label = lbl alg.add(lbl) # secondary text lbl = SetupLabel()() lbl.set_selectable(False) lbl.show() main.pack_start(lbl, False, False) self._secondary_label = lbl # Progress bar vbox = gtk.VBox() vbox.show() main.pack_start(vbox, False, False) prog = gtk.ProgressBar() prog.show() self._progress_bar = prog vbox.pack_start(prog, expand = False) lbl = SetupLabel()() lbl.set_selectable(False) lbl.show() self._sub_progress_label = lbl vbox.pack_start(lbl, False, False) # Buttons box bbox = gtk.HButtonBox() bbox.set_layout(gtk.BUTTONBOX_END) bbox.show() # Cancel Button cancel = gtk.Button(gtk.STOCK_CANCEL) cancel.set_use_stock(True) cancel.show() self._cancel = cancel bbox.add(cancel) main.add(bbox) # Close button, which is hidden by default close = gtk.Button(gtk.STOCK_CLOSE) close.set_use_stock(True) close.hide() bbox.add(close) self._close = close primary_label = property(lambda self: self._primary_label) secondary_label = property(lambda self: self._secondary_label) progress_bar = property(lambda self: self._progress_bar) sub_progress_label = property(lambda self: self._sub_progress_label) cancel_button = property(lambda self: self._cancel) close_button = property(lambda self: self._close) def set_primary_text(self, text): self.primary_label.set_markup( ''+text+'' ) self.set_title(text) primary_text = property(fset = set_primary_text) def set_secondary_text(self, text): self.secondary_label.set_markup(text) secondary_text = property(fset = set_secondary_text) def set_progress_fraction(self, fraction): self.progress_bar.set_fraction(fraction) def get_progress_fraction(self): return self.progress_bar.get_fraction() progress_fraction = property(get_progress_fraction, set_progress_fraction) def set_progress_text(self, text): self.progress_bar.set_text(text) progress_text = property(fset = set_progress_text) def set_sub_progress_text(self, text): self.sub_progress_label.set_markup(''+text+'') sub_progress_text = property(fset = set_sub_progress_text) def _on_close(self, *args): if not self.cancel_button.get_property("sensitive"): return True # click on the cancel button self.cancel_button.clicked() # let the clicked event close the window if it likes too return True if __name__ == '__main__': items = ["foo", "bar"] * 10 #items = ["foo", "bar"] * 2 #items = ["foo"] primary_text = "Select an item" if len(items) != 1: secondary_text = "These items will aid you to do cool stuff" else: secondary_text = "This item will aid you to do cool stuff" list_title = "Items:" window_title = "Rat Demo" # Simple single selection choice #choice(primary_text, secondary_text, items=items, title=window_title, list_title=list_title) # Allows the user to not select any element #choice_dialog(primary_text, secondary_text, items=items, title=window_title, list_title=list_title, min_select=0) # The user must choose at least 2 elements listing("This is a nice primary text", "bar", title="Rat", items=("foo", "bar")*20) # print choice( # primary_text, # secondary_text, # one_item_text = "Do you want to choose %s?", # items=items, # title=window_title, # list_title=list_title, # min_select=0, # max_select=1, # allow_cancel=False, # ) # print save_changes(["foo"], title="goo", last_save=datetime.datetime.now()) # dlg = ok_cancel("Rat will simplify your code", # ("By putting common utilities in one place all " # "benefit and get nicer apps."), title="foo", run=False) PIDA-0.5.1/pida/utils/rat/sensitive.py0000644000175000017500000001443010652670600015560 0ustar aliali""" This module exposes a common pattern on developing HIG applications, you often have more then one condtions affecting a widget's sensitive state, worst sometimes these conditions are not centralised and may even be created by plugins. To help solve this problem you can use a L{SensitiveController} or a L{SignalBind}. """ __license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import gobject import weakref class SensitiveClient: """ The L{SensitiveClient} can affect the widget's target state by changing the method L{SensitiveClient.set_sensitive}. When no references exist to this client it's unregistred from its controller. """ def __init__(self, counter): self.counter = counter self._sensitive = True def set_sensitive(self, sensitive): """ Set this client's sensitive state. """ if self._sensitive == sensitive: return self._sensitive = sensitive if sensitive: self.counter.dec() else: self.counter.inc() def __del__(self): # unregister from parent self.counter.dec() class _Counter: """ The Counter object uses a weakref to the callback, so if you by any chance loose its reference the Counter object will no longer use it. """ def __init__(self, callback = None): self.__count = 0 if self.__callback is not None: self.__callback = callback def __callback(self, amount): """This is a no-op""" def inc(self): self.__count += 1 self.__callback(self.__count) def dec(self): self.__count -= 1 self.__callback(self.__count) Counter = _Counter class SensitiveController: """ The L{SensitiveController} is the class responsible for maintaining the widget sensitive state. Whenever you want to add a new condition that affects your widget you create a new client and then use that client as if it was your condition:: lbl = gtk.Label("My widget") cnt = SensitiveController(lbl) client = cnt.create_client() client.set_sensitive(len(lbl.get_text()) > 0) If you create more clients in your controller your widget will only be sensitive when B{all} its clients are set to C{True}, if one is set to insensitive the widget will be insensitive as well. When this object has no references back it will make the widget sensitive. """ class _Callback: def __init__(self, widget): self.widget = widget def callback(self, counter): assert counter >= 0 self.widget.set_sensitive(counter <= 0) def __init__(self, widget): self.widget = widget cb = self._Callback(widget) self.__counter = _Counter(cb.callback) widget.set_sensitive(True) def __on_change(self, counter): assert counter >= 0 self.widget.set_sensitive(counter <= 0) def create_client(self): """ It will create one more client to the controller. @rtype: L{SensitiveClient} """ return SensitiveClient(self.__counter) def __del__(self): self.widget.set_sensitive(True) class SignalBind(object): """ The L{SignalBind} helps you connect a signal from a widget that will affect the sensitive state of your target widget. For example if we want a button the be sensitive only when a text entry has some text in it we do the following:: btn = gtk.Button() cnt = rat.sensitive.SensitiveController(btn) entry = gtk.Entry() sig_bind = rat.sensitive.SignalBind(cnt) sig_bind.bind(entry, "text", "changed", lambda txt: len(txt) > 0) Summing it up, the L{SignalBind} connects a property and a signal of a certain widget to a controller. Reference counting is thought of, this means that if the L{SignalBind} has not references back to it it will call the L{SignalBind.unbind} method. """ class _Callback: """ The callback object is used to remove circular dependencies. It exists for private use only. """ def __init__(self, affecter, property, condition, client): self.property = property self.condition = condition self.client = weakref.ref(client) self.__call__(affecter) def __call__(self, src, *args): value = self.condition(src.get_property(self.property)) self.client().set_sensitive(value) def __init__(self, controller): self.controller = controller self.source = None self.client = None def bind(self, affecter, property, signal, condition): """ Connects the affecter through a certain signal to the sensitive binder. @param affecter: the widget that has a certain property, which will be triggered by a certain signal. @param property: the property which will be evaluated by the condition when the signal is called @param signal: the signal that is triggered when the property is changed @param condition: the condition is a function that accepts one value which is the property's value. """ assert self.source is None, "call unbind() before bind()" self.client = self.controller.create_client() cb = self._Callback(affecter, property, condition, self.client) self.source = affecter.connect(signal, cb.__call__) def unbind(self): """ Calling the unbind method will remove the L{SensitiveController} registration and the source associated with the signal it's listening to. """ assert self.source is not None, "bind() must be called before unbind()" assert self.client is not None, "There's a bug in this program" gobject.source_remove(self.source) self.source = None self.client = None def __del__(self): if self.source is not None: self.unbind() PIDA-0.5.1/pida/utils/rat/shiftpaned.py0000644000175000017500000002156510652670600015703 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2006, Tiago Cogumbreiro" import gtk HIDE_BOTH = 0 SHOW_CHILD1 = 1 SHOW_CHILD2 = 2 SHOW_BOTH = 3 def _state_to_operation(old_state, new_state): result = new_state - old_state if result == 1: return "_add_child" if result == -1: return "_remove_child" def _get_operations(old_state, new_state): """ how get operation works is really simple, there are two booleans packed in an integer, namely 'state'. The first bit is child 1, the second bit is referring to child 2. So, using the binary notation, the state is like this: * 00 then it means that child 1 is hidden and so is child 2. * 01 means that child 1 is visible and child 2 is not. * 10 means that child 2 is visible and child 1 is not. Now the shift of states means that some operations will performed, like '_add_child1' or '_remove_child2', these are mappend to ShiftPaned's methods. This means that if I'm changing from SHOW_CHILD1 to SHOW_CHILD2 I will have to remove child 2 ('_remove_child2') and add child 1 to the pane ('_add_child1'). Lets analize this mathmatically, the latter example means that we are changing from 01 to 10. Now for another example, from SHOW_CHILD2 to SHOW_BOTH, the change was from 10 to 11 and we'll have to '_add_child1'. The conclusion about this is that when we shift from a 0 to a 1 we are infact adding the child on a given location, if it's the first bit we're adding child 1 if it's the second bit then we're adding child 2. But if we shift from a 1 to a 0 then we're removing a child from the pane. If the numbers are equal we do nothing. Now the implementation needs two things: the first is to get the visibility state from each child, these are the 'child1_oper' and 'child2_oper' lambda functions; the second function is something to convert the bit to an operation, this is fairly easy, subtracting a bit from the other we'll have three possible values: * 1 - 0: positive * 0 - 1: negative * 1 - 1 or 0 - 0: equal If we subtract the new bit from the old bit having a positive number means that we want to add something and a negative means that we want to remove it. """ operations = [] child1_oper = lambda state: state & SHOW_CHILD1 child2_oper = lambda state: (state & SHOW_CHILD2) >> 1 result = _state_to_operation(child1_oper(old_state), child1_oper(new_state)) if result != None: operations.append(result + "1") result = _state_to_operation(child2_oper(old_state), child2_oper(new_state)) if result != None: operations.append(result + "2") return operations # fill _STATE_TO_WIDGET, it's faster to lookup then it is to calculate _STATE_TO_WIDGET = {} for old_state in range(4): for new_state in range(4): if old_state == new_state: continue _STATE_TO_WIDGET[(old_state, new_state)] = tuple(_get_operations(old_state, new_state)) # this is a forced memoize ;) def _get_operations(old_state, new_state): global _STATE_TO_WIDGET return _STATE_TO_WIDGET[(old_state, new_state)] class ShiftPaned(gtk.EventBox): """ A ShiftPaned is a gtk.Paned that can hide one of its child widgets, therefore hiding the pane division. """ _state = SHOW_BOTH _child1_args = () _child1_kwargs = {} _child2_args = () _child2_kwargs = {} child1_widget = None child2_widget = None def has_both_widgets(self): return self.child2_widget is not None and self.child1_widget is not None def __init__(self, paned_factory=gtk.HPaned): self.paned = paned_factory() self.paned.show() super(ShiftPaned, self).__init__() super(ShiftPaned, self).add(self.paned) def _add_child1(self): if self.child1_widget is not None and self.paned.get_child1() is None: self.paned.pack1( self.child1_widget, *self._child1_args, **self._child1_kwargs ) def _add_child2(self): if self.child2_widget is not None and self.paned.get_child2() is None: self.paned.pack2( self.child2_widget, *self._child2_args, **self._child2_kwargs ) def _remove_child1(self): if self.child1_widget is not None: self.paned.remove(self.child1_widget) def _remove_child2(self): if self.child2_widget is not None: self.paned.remove(self.child2_widget) def pack1(self, widget, *args, **kwargs): assert widget is not None self._child1_args = args self._child1_kwargs = kwargs self.child1_widget = widget if self._state & SHOW_CHILD1: self._add_child1() def pack2(self, widget, *args, **kwargs): assert widget is not None self._child2_args = args self._child2_kwargs = kwargs self.child2_widget = widget if self._state & SHOW_CHILD2: self._add_child2() def set_state(self, state): """This pane uses a number of states to act more effeciently the change of visibility of their children""" if state == self._state: return actions = _get_operations(self._state, state) get_method = lambda name: getattr(self, name) actions = map(get_method, actions) self._state = state for action in actions: action() def get_state(self): return self._state def show_child1(self): self.set_state(self._state | SHOW_CHILD1) def show_child2(self): self.set_state(self._state | SHOW_CHILD2) def hide_child1(self): self.set_state(self._state & ~SHOW_CHILD1) def hide_child2(self): self.set_state(self._state & ~SHOW_CHILD2) def get_child1_visibility(self): return (self._state & SHOW_CHILD1) == SHOW_CHILD1 def get_child2_visibility(self): return (self._state & SHOW_CHILD2) == SHOW_CHILD2 def set_position(self, position): self.paned.set_position(position) def get_position(self): return self.paned.get_position() def get_children(self): return self.paned.get_children() def remove(self, widget): if self.child1_widget is widget: self.child1_widget = None elif self.child2_widget is widget: self.child2_widget = None self.paned.remove(widget) def get_child1(self): return self.child1_widget def get_child2(self): return self.child2_widget def compute_position(self, *args, **kwargs): return self.paned.compute_position(*args, **kwargs) def add1(self, child): self.pack1(child) def add2(self, child): self.pack2(child) def add(self, widget): raise AttributeError("Use add1 and add2 instead.") class SidebarPaned(gtk.EventBox): def __init__(self, paned_factory=gtk.HPaned, main_first=True): super(SidebarPaned, self).__init__() self.paned = ShiftPaned(paned_factory) self.main_first = main_first self.add(self.paned) self.paned.show() def pack_main(self, main_widget, *args, **kwargs): if self.main_first: self.paned.pack1(main_widget, *args, **kwargs) else: self.paned.pack2(main_widget, *args, **kwargs) def pack_sub(self, sub_widget, *args, **kwargs): if not self.main_first: self.paned.pack1(sub_widget, *args, **kwargs) else: self.paned.pack2(sub_widget, *args, **kwargs) def show_sub(self): # Faster this way self.paned.set_state(SHOW_BOTH) def hide_sub(self): if not self.main_first: self.paned.hide_child1() else: self.paned.hide_child2() def set_position(self, position): self.paned.set_position(position) if __name__ == '__main__': #p = ShiftPaned(gtk.VPaned) # p = ShiftPaned(gtk.HPaned) p = SidebarPaned() btn1 = gtk.Button("Show sidebar") btn2 = gtk.Button("Hide sidebar") p.pack_main(btn1) p.pack_sub(btn2) # p.pack1(btn1) # p.pack2(btn2) def on_click(btn): # p.show_child2() # p.hide_child1() p.show_sub() btn1.connect("clicked", on_click) def on_click(btn): # p.show_child1() # p.hide_child2() p.hide_sub() btn2.connect("clicked", on_click) btn1.show() btn2.show() w = gtk.Window() vbox = gtk.VBox() vbox.add(p) w.add(vbox) w.show_all() w.connect("delete-event", gtk.main_quit) gtk.main() PIDA-0.5.1/pida/utils/rat/swp.py0000644000175000017500000001360010652670600014356 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" __doc__ = """ Storage-Widget Persistency defines a set of objects to implement a persistency link between a widget and a certain data store. The data store must implement the interface IValueHolder. """ import gobject class OutOfSyncError(StandardError): """ This error is thrown when there's a synchronization problem between the L{GConfValue} and the widget. """ class IStorage: """The 'PersistencyLink' object expects an IValueHolder class""" def get_data(): pass def set_data(value): pass data = property(get_data, set_data) def get_is_writable(): """Returns whether or not the value can be set""" is_writable = property(get_is_writable) def set_callback(callback): """ Defines a callback for when the value has changed The callback has the following signature: def callback(val) 'val' is self. """ class PersistencyLink(object): """ This utility class acts as a synchronizer between a widget and storage value. This data is considered to have problematic backends, since widgets can be destroyed and storage can have integrity problems (for example permissions or schema change). """ def __init__(self, widget, widget_getter, widget_setter, changed_signal, storage, is_lazy=False): """ @param widget: This is the widget this is observing. @type widget: gtk.Widget @param widget_getter: The function that gets the widget's data @param widget_setter: The function that sets the widget's data @param changed_signal: The name of the signal this observer should be connecting too. @param storage: The value contained in the data storage @type storage: IStorage """ self._widget = widget self._widget_setter = widget_setter self._widget_getter = widget_getter self.storage = storage self.is_lazy = is_lazy storage.set_callback(self._on_storage_changed) widget.connect(changed_signal, self._on_widget_changed) widget.connect("destroy", self._on_destroy) if self.widget is not None: self.sync_widget() ####### # data def get_data(self, sync_storage=True): if sync_storage: self.sync_storage() return self.storage.data def set_data(self, data): self.storage.data = data self._widget_setter(data) data = property(get_data, set_data, doc="The data contained in this component.") ######## # widget def get_widget(self): return self._widget widget = property(get_widget) ########## # Methods def _on_destroy(self, widget): self._widget = None def _on_widget_changed(self, *args): if self.widget is None: return # Widget has changed its value, we need to update the GConfValue self.sync_storage() def _on_storage_changed(self, storage): # Something was updated on gconf if self.widget is None: return self.widget.set_sensitive(storage.is_writable) # Because widgets can validate data, sync the gconf entry again self.sync_widget() self.sync_storage() def sync_widget(self): """ Synchronizes the widget in favour of the gconf key. You must check if there is a valid widget before calling this method. """ assert self.widget, "Checking if there's a valid widget is a prerequisite." # Set the data from the storage val = self.storage.data if val is not None: self._widget_setter(val) if self.is_lazy: gobject.idle_add(self._check_sync, val) else: self._check_sync(val) def _check_sync(self, value): # Because some widgets change the value, update it to gconf again new_val = self._widget_getter() if new_val is None: raise OutOfSyncError("Widget getter returned 'None' after a value was set.") # The value was changed by the widget, we updated it back to GConfValue if value != new_val: self.sync_storage() def sync_storage(self): """ Synchronizes the gconf key in favour of the widget. You must check if there is a valid widget before calling this method. """ assert self.widget, "Checking if there's a valid widget is a prerequisite." # First we val = self._widget_getter() if val is None: return self.storage.data = val class PersistencyLinkFactory: # This class implements a _very_ basic dispatch.on matching the first argument def __init__(self): self._classes = [] def _generate_cmp_func(self, widget_class): return lambda obj: isinstance(obj, widget_class) def append_handler_full(self, cmp_func, handler): self._classes.append((cmp_func, handler)) def insert_handler(self, index, cmp_func, handler): self._classes.insert(index, (cmp_func, handler)) def append_handler(self, widget_class, handler): self.append_handler_full(self._generate_cmp_func(widget_class), handler) def insert_handler(self, index, widget_class, handler): self.insert_handler_full(self._generate_cmp_func(widget_class), handler) def __call__(self, widget, *args, **kwargs): for cmp_func, handler in self._classes: if cmp_func(widget): return handler(widget, *args, **kwargs) PIDA-0.5.1/pida/utils/rat/text.py0000644000175000017500000001321110652670600014527 0ustar aliali""" This module contains to, very usefull utility functions, one for grabbing the text selected on a certain `gtk.TreeBuffer`, the other creates an iterator for manipulating searches to a `gtk.TreeBuffer`. """ __license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import gtk def get_buffer_selection(buffer): """Returns the selected text, when nothing is selected it returns the empty string.""" bounds = buffer.get_selection_bounds() if len(bounds) == 0: return "" else: return buffer.get_slice(*bounds) class SearchIterator: def __init__(self, text_buffer, search_text, find_forward=True, start_in_cursor=True, next_iter=None): self.search_text = search_text self.text_buffer = text_buffer self.find_forward = find_forward if next_iter is not None: self.next_iter = next_iter elif start_in_cursor: bounds = text_buffer.get_selection_bounds() if len(bounds) == 0: self.next_iter = text_buffer.get_iter_at_mark(text_buffer.get_insert()) else: self.next_iter = find_forward and bounds[1] or bounds[0] else: if find_forward: self.next_iter = text_buffer.get_start_iter() else: self.next_iter = text_buffer.get_end_iter() def next(self): if self.next_iter is None: raise StopIteration find_forward = self.find_forward if find_forward: search = self.next_iter.forward_search else: search = self.next_iter.backward_search bounds = search(self.search_text, gtk.TEXT_SEARCH_TEXT_ONLY, limit=None) if bounds is None: self.next_iter = None raise StopIteration if find_forward: self.next_iter = bounds[1] else: self.next_iter = bounds[0] return bounds def __iter__(self): return self search_iterator = SearchIterator def line_iterator(buff, start_iter, end_iter): begin_line = start_iter.get_line() end_line = end_iter.get_line() assert begin_line <= end_line for line_num in range(begin_line, end_line+1): yield buff.get_iter_at_line(line_num) def selected_line_iterator(buff): """ Iterates over selected lines """ bounds = buff.get_selection_bounds() if len(bounds) == 0: return last_iter = bounds[1] for start_iter in line_iterator(buff, *bounds): # Skip empty lines if start_iter.equal(last_iter) or start_iter.ends_line(): continue yield start_iter def indent_selected(buff, indent): """ Indents selected text of a gtk.TextBuffer """ bounds = buff.get_selection_bounds() if len(bounds) == 0: return move_home = bounds[0].starts_line() insert = buff.insert insert_indent = lambda start_iter: insert(start_iter, indent) map(insert_indent, selected_line_iterator(buff)) if move_home: start_iter, end_iter = buff.get_selection_bounds() start_iter.set_line_offset(0) buff.select_range(end_iter, start_iter) def _unindent_iter(buff, start_iter, indent, use_subset): # Get the iterator of the end of the text end_iter = start_iter.copy() end_iter.forward_to_line_end() total = len(indent) # Now get the selected text text = buff.get_text(start_iter, end_iter) # Check if the text starts with indent: if text.startswith(indent): count = total # Delete 'count' characters end_iter = start_iter.copy() end_iter.forward_chars(count) buff.delete(start_iter, end_iter) elif use_subset: for count in range(1, total): if text.startswith(indent[:-count]): # Delete 'count' characters offset = total - count end_iter = start_iter.copy() end_iter.forward_chars(offset) buff.delete(start_iter, end_iter) return def unindent_selected(buff, indent, use_subset=True): """ Unindents selected text of a `gtk.TextBuffer` """ if len(buff.get_selection_bounds()) == 0: start_iter = buff.get_iter_at_mark(buff.get_insert()) # Move the offset to the start of the line start_iter.set_line_offset(0) _unindent_iter(buff, start_iter, indent, use_subset) end_iter = start_iter.copy() end_iter.forward_to_line_end() unindent_iter = lambda start_iter: _unindent_iter(buff, start_iter, indent, use_subset) map(unindent_iter, selected_line_iterator(buff)) # This function belongs to make_soruce_view_indentable def _on_key_press(view, event): keyname = gtk.gdk.keyval_name(event.keyval) buff = view.get_buffer() if view.get_insert_spaces_instead_of_tabs(): tab = " " * view.get_tabs_width() else: tab = "\t" if event.state & gtk.gdk.SHIFT_MASK and keyname == "ISO_Left_Tab": unindent_selected(buff, tab) return True elif event.keyval == gtk.keysyms.Tab: if len(buff.get_selection_bounds()) == 0: return False indent_selected(buff, tab) return True def make_source_view_indentable(source_view): # TODO: make the selection carret move to the end of the selection # and not the start return source_view.connect("key-press-event", _on_key_press) PIDA-0.5.1/pida/utils/rat/util.py0000644000175000017500000001431410652670600014525 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" __doc__ = """ This module contains usefull functions like widget navigation and list store creation. """ import gtk class NotFoundError(KeyError): """This is raised when an element is not found""" class ListSpec: """ This class is used to help the manipulation of C{gtk.ListStore}s. Here's an example on how to create one:: my_spec = ListSpec( ("STATE", gobject.TYPE_INT), ("FILENAME", gobject.TYPE_STRING), ) To create a ListStore, just do the following:: store = my_spec.create_list_store() To add data to a store you can access it directly:: store.append((1, "fooo")) Or by creating a dict object and converting it:: row = { my_spec.STATE: 2, my_spec.FILENAME: "bar" } store.append(my_spec.to_tree_row(row)) To access a column on a given row:: for row in store: print "State:", row[my_spec.STATE] print "Filename:", row[my_spec.FILENAME] So here are its features: - helps you centralize the specs of a given C{gtk.ListStore} - makes your code more readable and less error-prone thanks to the created constants """ def __init__(self, *columns): names = [] gtypes = [] for(index,(name, gtype)) in enumerate(columns): assert name != "create_list_store" and name != "to_tree_row" setattr(self, name, index) gtypes.append(gtype) self.__gtypes = tuple(gtypes) def create_list_store(self): """Creates a new C{gtk.ListStore} @rtype: C{gtk.ListStore} """ return gtk.ListStore(*self.__gtypes) def to_tree_row(self, mapping): """ Converts a L{dict} like object to a list suitable for adding to a C{gtk.ListStore}. @rtype: C{ListType} """ keys = mapping.keys() keys.sort() return [mapping[key] for key in keys] ################################################################################ # widget iterators def _simple_iterate_widget_children(widget): """This function iterates all over the widget children. """ get_children = getattr(widget, "get_children", None) if get_children is None: return for child in get_children(): yield child get_submenu = getattr(widget, "get_submenu", None) if get_submenu is None: return sub_menu = get_submenu() if sub_menu is not None: yield sub_menu class _IterateWidgetChildren: """This iterator class is used to recurse to child widgets, it uses the _simple_iterate_widget_children function """ def __init__(self, widget): self.widget = widget self.children_widgets = iter(_simple_iterate_widget_children(self.widget)) self.next_iter = None def next(self): if self.next_iter is None: widget = self.children_widgets.next() self.next_iter = _IterateWidgetChildren(widget) return widget else: try: return self.next_iter.next() except StopIteration: self.next_iter = None return self.next() def __iter__(self): return self def iterate_widget_children(widget, recurse_children = False): """ This function is used to iterate over the children of a given widget. You can recurse to all the widgets contained in a certain widget. @param widget: The base widget of iteration @param recurse_children: Wether or not to iterate recursively, by iterating over the children's children. @return: an iterator @rtype: C{GeneratorType} """ if recurse_children: return _IterateWidgetChildren(widget) else: return iter(_simple_iterate_widget_children(widget)) def iterate_widget_parents(widget): """Iterate over the widget's parents. @param widget: The base widget of iteration @return: an iterator @rtype: C{GeneratorType} """ widget = widget.get_parent() while widget is not None: yield widget widget = widget.get_parent() def find_parent_widget(widget, name, find_self=True): """ Finds a widget by name upwards the tree, by searching self and its parents @return: C{None} when it didn't find it, otherwise a C{gtk.Container} @rtype: C{gtk.Container} @param find_self: Set this to C{False} if you want to only find on the parents @param name: The name of the widget @param widget: The widget where this function will start searching """ assert widget is not None if find_self and widget.get_name() == name: return widget for w in iterate_widget_parents(widget): if w.get_name() == name: return w raise NotFoundError(name) def find_child_widget(widget, name, find_self=True): """ Finds the widget by name downwards the tree, by searching self and its children. @return: C{None} when it didn't find it, otherwise a C{gtk.Widget} @rtype: C{gtk.Widget} @param find_self: Set this to L{False} if you want to only find on the children @param name: The name of the widget @param widget: The widget where this function will start searching """ assert widget is not None if find_self and widget.get_name() == name: return widget for w in iterate_widget_children(widget, True): if name == w.get_name(): return w raise NotFoundError(name) def get_root_parent(widget): """Returns the first widget of a tree. If this widget has no children it will return C{None} @return: C{None} when there is no parent widget, otherwise a C{gtk.Container} @rtype: C{gtk.Container} """ parents = list(iterate_widget_parents(widget)) if len(parents) == 0: return None else: return parents[-1] PIDA-0.5.1/pida/utils/testing/0002755000175000017500000000000010652671502014066 5ustar alialiPIDA-0.5.1/pida/utils/testing/__init__.py0000644000175000017500000000036110652670605016200 0ustar aliali""" Some things to make testing the UI a bit nicer """ # stlib import time # gtk import gtk # Stolen from Kiwi def refresh_gui(delay=0): while gtk.events_pending(): gtk.main_iteration_do(block=False) time.sleep(delay) PIDA-0.5.1/pida/utils/testing/mock.py0000644000175000017500000003742510652670605015405 0ustar aliali# # (c) Dave Kirby 2001 - 2005 # mock@thedeveloperscoach.com # # Original call interceptor and call assertion code by Phil Dawes (pdawes@users.sourceforge.net) # Call interceptor code enhanced by Bruce Cropley (cropleyb@yahoo.com.au) # # This Python module and associated files are released under the FreeBSD # license. Essentially, you can do what you like with it except pretend you wrote # it yourself. # # # Copyright (c) 2005, Dave Kirby # # 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 this library 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. # # mock@thedeveloperscoach.com """ Mock object library for Python. Mock objects can be used when unit testing to remove a dependency on another production class. They are typically used when the dependency would either pull in lots of other classes, or significantly slow down the execution of the test. They are also used to create exceptional conditions that cannot otherwise be easily triggered in the class under test. """ __version__ = "0.1.0" # Added in Python 2.1 import inspect import re class MockInterfaceError(Exception): pass class Mock: """ The Mock class emulates any other class for testing purposes. All method calls are stored for later examination. """ def __init__(self, returnValues=None, realClass=None): """ The Mock class constructor takes a dictionary of method names and the values they return. Methods that are not in the returnValues dictionary will return None. You may also supply a class whose interface is being mocked. All calls will be checked to see if they appear in the original interface. Any calls to methods not appearing in the real class will raise a MockInterfaceError. Any calls that would fail due to non-matching parameter lists will also raise a MockInterfaceError. Both of these help to prevent the Mock class getting out of sync with the class it is Mocking. """ self.mockCalledMethods = {} self.mockAllCalledMethods = [] self.mockReturnValues = returnValues or {} self.mockExpectations = {} self.realClassMethods = None if realClass: self.realClassMethods = dict(inspect.getmembers(realClass, inspect.isroutine)) for retMethod in self.mockReturnValues.keys(): if not self.realClassMethods.has_key(retMethod): raise MockInterfaceError("Return value supplied for method '%s' that was not in the original class" % retMethod) self._setupSubclassMethodInterceptors() def _setupSubclassMethodInterceptors(self): methods = inspect.getmembers(self.__class__,inspect.isroutine) baseMethods = dict(inspect.getmembers(Mock, inspect.ismethod)) for m in methods: name = m[0] # Don't record calls to methods of Mock base class. if not name in baseMethods: self.__dict__[name] = MockCallable(name, self, handcrafted=True) def __getattr__(self, name): return MockCallable(name, self) def mockAddReturnValues(self, **methodReturnValues ): self.mockReturnValues.update(methodReturnValues) def mockSetExpectation(self, name, testFn, after=0, until=0): self.mockExpectations.setdefault(name, []).append((testFn,after,until)) def _checkInterfaceCall(self, name, callParams, callKwParams): """ Check that a call to a method of the given name to the original class with the given parameters would not fail. If it would fail, raise a MockInterfaceError. Based on the Python 2.3.3 Reference Manual section 5.3.4: Calls. """ if self.realClassMethods == None: return if not self.realClassMethods.has_key(name): raise MockInterfaceError("Calling mock method '%s' that was not found in the original class" % name) func = self.realClassMethods[name] try: args, varargs, varkw, defaults = inspect.getargspec(func) except TypeError: # func is not a Python function. It is probably a builtin, # such as __repr__ or __coerce__. TODO: Checking? # For now assume params are OK. return # callParams doesn't include self; args does include self. numPosCallParams = 1 + len(callParams) if numPosCallParams > len(args) and not varargs: raise MockInterfaceError("Original %s() takes at most %s arguments (%s given)" % (name, len(args), numPosCallParams)) # Get the number of positional arguments that appear in the call, # also check for duplicate parameters and unknown parameters numPosSeen = _getNumPosSeenAndCheck(numPosCallParams, callKwParams, args, varkw) lenArgsNoDefaults = len(args) - len(defaults or []) if numPosSeen < lenArgsNoDefaults: raise MockInterfaceError("Original %s() takes at least %s arguments (%s given)" % (name, lenArgsNoDefaults, numPosSeen)) def mockGetAllCalls(self): """ Return a list of MockCall objects, representing all the methods in the order they were called. """ return self.mockAllCalledMethods getAllCalls = mockGetAllCalls # deprecated - kept for backward compatibility def mockGetNamedCalls(self, methodName): """ Return a list of MockCall objects, representing all the calls to the named method in the order they were called. """ return self.mockCalledMethods.get(methodName, []) getNamedCalls = mockGetNamedCalls # deprecated - kept for backward compatibility def mockCheckCall(self, index, name, *args, **kwargs): '''test that the index-th call had the specified name and parameters''' call = self.mockAllCalledMethods[index] assert name == call.getName(), "%r != %r" % (name, call.getName()) call.checkArgs(*args, **kwargs) def _getNumPosSeenAndCheck(numPosCallParams, callKwParams, args, varkw): """ Positional arguments can appear as call parameters either named as a named (keyword) parameter, or just as a value to be matched by position. Count the positional arguments that are given by either keyword or position, and check for duplicate specifications. Also check for arguments specified by keyword that do not appear in the method's parameter list. """ posSeen = {} for arg in args[:numPosCallParams]: posSeen[arg] = True for kwp in callKwParams: if posSeen.has_key(kwp): raise MockInterfaceError("%s appears as both a positional and named parameter." % kwp) if kwp in args: posSeen[kwp] = True elif not varkw: raise MockInterfaceError("Original method does not have a parameter '%s'" % kwp) return len(posSeen) class MockCall: """ MockCall records the name and parameters of a call to an instance of a Mock class. Instances of MockCall are created by the Mock class, but can be inspected later as part of the test. """ def __init__(self, name, params, kwparams ): self.name = name self.params = params self.kwparams = kwparams def checkArgs(self, *args, **kwargs): assert args == self.params, "%r != %r" % (args, self.params) assert kwargs == self.kwparams, "%r != %r" % (kwargs, self.kwparams) def getParam( self, n ): if isinstance(n, int): return self.params[n] elif isinstance(n, str): return self.kwparams[n] else: raise IndexError, 'illegal index type for getParam' def getNumParams(self): return len(self.params) def getNumKwParams(self): return len(self.kwparams) def getName(self): return self.name #pretty-print the method call def __str__(self): s = self.name + "(" sep = '' for p in self.params: s = s + sep + repr(p) sep = ', ' items = self.kwparams.items() items.sort() for k,v in items: s = s + sep + k + '=' + repr(v) sep = ', ' s = s + ')' return s def __repr__(self): return self.__str__() class MockCallable: """ Intercepts the call and records it, then delegates to either the mock's dictionary of mock return values that was passed in to the constructor, or a handcrafted method of a Mock subclass. """ def __init__(self, name, mock, handcrafted=False): self.name = name self.mock = mock self.handcrafted = handcrafted def __call__(self, *params, **kwparams): self.mock._checkInterfaceCall(self.name, params, kwparams) thisCall = self.recordCall(params,kwparams) self.checkExpectations(thisCall, params, kwparams) return self.makeCall(params, kwparams) def recordCall(self, params, kwparams): """ Record the MockCall in an ordered list of all calls, and an ordered list of calls for that method name. """ thisCall = MockCall(self.name, params, kwparams) calls = self.mock.mockCalledMethods.setdefault(self.name, []) calls.append(thisCall) self.mock.mockAllCalledMethods.append(thisCall) return thisCall def makeCall(self, params, kwparams): if self.handcrafted: allPosParams = (self.mock,) + params func = _findFunc(self.mock.__class__, self.name) if not func: raise NotImplementedError return func(*allPosParams, **kwparams) else: returnVal = self.mock.mockReturnValues.get(self.name) if isinstance(returnVal, ReturnValuesBase): returnVal = returnVal.next() return returnVal def checkExpectations(self, thisCall, params, kwparams): if self.name in self.mock.mockExpectations: callsMade = len(self.mock.mockCalledMethods[self.name]) for (expectation, after, until) in self.mock.mockExpectations[self.name]: if callsMade > after and (until==0 or callsMade < until): assert expectation(self.mock, thisCall, len(self.mock.mockAllCalledMethods)-1), 'Expectation failed: '+str(thisCall) def _findFunc(cl, name): """ Depth first search for a method with a given name. """ if cl.__dict__.has_key(name): return cl.__dict__[name] for base in cl.__bases__: func = _findFunc(base, name) if func: return func return None class ReturnValuesBase: def next(self): try: return self.iter.next() except StopIteration: raise AssertionError("No more return values") def __iter__(self): return self class ReturnValues(ReturnValuesBase): def __init__(self, *values): self.iter = iter(values) class ReturnIterator(ReturnValuesBase): def __init__(self, iterator): self.iter = iter(iterator) def expectParams(*params, **keywords): '''check that the callObj is called with specified params and keywords ''' def fn(mockObj, callObj, idx): return callObj.params == params and callObj.kwparams == keywords return fn def expectAfter(*methods): '''check that the function is only called after all the functions in 'methods' ''' def fn(mockObj, callObj, idx): calledMethods = [method.getName() for method in mockObj.mockGetAllCalls()] #skip last entry, since that is the current call calledMethods = calledMethods[:-1] for method in methods: if method not in calledMethods: return False return True return fn def expectException(exception, *args, **kwargs): ''' raise an exception when the method is called ''' def fn(mockObj, callObj, idx): raise exception(*args, **kwargs) return fn def expectParam(paramIdx, cond): '''check that the callObj is called with parameter specified by paramIdx (a position index or keyword) fulfills the condition specified by cond. cond is a function that takes a single argument, the value to test. ''' def fn(mockObj, callObj, idx): param = callObj.getParam(paramIdx) return cond(param) return fn def EQ(value): def testFn(param): return param == value return testFn def NE(value): def testFn(param): return param != value return testFn def GT(value): def testFn(param): return param > value return testFn def LT(value): def testFn(param): return param < value return testFn def GE(value): def testFn(param): return param >= value return testFn def LE(value): def testFn(param): return param <= value return testFn def AND(*condlist): def testFn(param): for cond in condlist: if not cond(param): return False return True return testFn def OR(*condlist): def testFn(param): for cond in condlist: if cond(param): return True return False return testFn def NOT(cond): def testFn(param): return not cond(param) return testFn def MATCHES(regex, *args, **kwargs): compiled_regex = re.compile(regex, *args, **kwargs) def testFn(param): return compiled_regex.match(param) != None return testFn def SEQ(*sequence): iterator = iter(sequence) def testFn(param): try: cond = iterator.next() except StopIteration: raise AssertionError('SEQ exhausted') return cond(param) return testFn def IS(instance): def testFn(param): return param is instance return testFn def ISINSTANCE(class_): def testFn(param): return isinstance(param, class_) return testFn def ISSUBCLASS(class_): def testFn(param): return issubclass(param, class_) return testFn def CONTAINS(val): def testFn(param): return val in param return testFn def IN(container): def testFn(param): return param in container return testFn def HASATTR(attr): def testFn(param): return hasattr(param, attr) return testFn def HASMETHOD(method): def testFn(param): return hasattr(param, method) and callable(getattr(param, method)) return testFn CALLABLE = callable PIDA-0.5.1/pida/utils/vim/0002755000175000017500000000000010652671502013204 5ustar alialiPIDA-0.5.1/pida/utils/vim/__init__.py0000644000175000017500000000232110652670601015310 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """ PIDA integration for Vim_. .. _Vim: http://vim.org/ """ PIDA-0.5.1/pida/utils/vim/vimcom.py0000644000175000017500000007165610652670601015064 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. """ A library to control vim -g using its X protocol interface (with gdk). ============ How it works ============ === General Communication === The Vim client/server protocol communicates by sending messages to and from an X communication window. The details are explained in the Vim source. Essentially, Vim understands two sorts of messages over this interface. ;asynchronous key sends : that are exactly equivalent to to the user of the remote Vim typing commands. ;synchronous expression evaluations : these are Vim expressions that are evaluated by the remote Vim, and an answer is replied with the result over the same protocol. Although the synchronous messages are called synchronous, the reply itself, in programming terms is entirely asynchronous, in that there is no way of knowing when a reply will be received, and one cannot block for it. Thus, this library allows you to make both of these calls to remote Vims. Synchronous expressions must provide a call back function that will be called when the message is replied to. === The Server List === (It has been an utter nightmare.) The primary problem is that GTK does not actually know accurately whether a window with a given window ID has been destroyed. This is how Vim does it (using the X libraries) after checking an attribute for registered Vim sessions with the X root window. This way each Vim doesn't need to unregister itself with the X root window on dying, it just assumes that any other client attempting to connect to it will know that the window has been destroyed. As mentioned, GTK totally fails to do what the X library does, and ascertain whether the window is alive. It succeeds sometimes, but not at others. The result is a GDK window that appears alive, and ready to communicate with, but which causes an uncatchable and fatal application error. Step in other potential methods of getting an accurate list of servers. Firstly, and most obviously, one can call the command 'vim --serverlist' on a simple system pipe and read the list off. This is entirely reliable, and effective, but the cost of forking a process and starting Vim each time is not fun, and effectively blocks. Another option is to force users to start Vim through Pida and keep an account of the child processes. This would work very effectively, but it restricts the user, and the entire system. The final, and current solution is to start Vim itself on a pseudoterminal as a hidden instance, and then communicate with that over the Vim protocol. The reason this can be reliably done, is that since the process is a child, it can be polled to check whether it is alive. This is performed each time the serverlist is requested, and if the hidden instance has been destroyed (eg by the user) a new one is spawned, thus preventing an attempt to communicate with an already-destroyed GDK window. The cost of this solution is that we spawn an extra Vim process. I believe that the added solidity it brings to the entire system is easily worth it, and it ensures that Pida can communicate with Vim it started and Vim it didn't start. """ # Gtk imports import gtk import gtk.gdk as gdk import gobject # System imports import os import pty import time import tempfile class poller(object): """ DEPRECATED: WE DO NOT USE THIS ANYMORE An instance of Vim on a pseudoterminal which can be reliably polled. This class is used to provide an instance of Vim which can be communicated with using the Vim client/server protocol, in order to retrieve an accurate and current server list, and also which can be polled accurately as to whether it is alive before communicating with it. This method is much cheaper in resources than running vim --serverlist each time, and much more accurate than using the root window's VimRegistry property, and also more accurate than using GDK methods for assessing whether a window is alive. """ def __init__(self): """ Constructor. Create a temporary and unique name for use as the servername, and initialise the instance variables. @param cb: An instance of the main application class. @type cb: pida.main.Application. """ # Prefacing with '__' means it will be ignored in the internal server # list. self.name = '__%s_PIDA_HIDDEN' % time.time() # Checked to evaluate False on starting. self.pid = None def start(self): """ Start the Vim instance if it is not already running. This command forks in a pseudoterminal, and starts Vim, if Vim is not already running. The pid is stored for later use. """ if not self.pid: # Get the console vim executable path #command = self.prop_main_registry.commands.vim.value() command = 'gvim' # Fork using pty.fork to prevent Vim taking the terminal sock = gtk.Socket() w = gtk.Window() w.realize() w.add(sock) xid = sock.get_id() pid, fd = pty.fork() if pid == 0: # Child, execute Vim with the correct servername argument os.execvp(command, ['gvim', '-f', '--servername', self.name, '--socketid', '%s' % xid]) #'-v']) # os.system('%s -v --servername %s' % (command, self.name)) else: # Parent, store the pid, and file descriptor for later. self.pid = pid self.childfd = fd #self.do_action('accountfork', self.pid) def is_alive(self): """ Check if the Vim instance is alive. This method uses os.waitpid, with no blocking to determine whether the process is still alive. If it is not, it sets the internal pid attribute to None, so that it may be restarted. @returns: alive @rtype alive: boolean """ if self.pid: try: # call os.waitpid, returns 0 if the pid is alive pid, sts = os.waitpid(self.pid, os.WNOHANG) except OSError: # might still be starting up return False if pid == self.pid: # has shut down self.pid = None return False else: # is still alive return True else: # Not started yet return False class communication_window(gtk.Window): """ A GTK window that can communicate with any number Vim instances. This is an actual GTK window (which it must be to accurately detect property events inside the GTK main loop) but has its GDK window correctly set to receive such events. This is notably the "Vim" property which must be present and set to a version string, in this case "6.0" is used. """ def __init__(self, cb): """ Constructor. The Window is instantiated, the properties are correctly set, the event mask is modified, and the instance variables are initialized. @param cb: An instance of the main Application class. @type cb: pida.main.Application. """ gtk.Window.__init__(self) self.cb = cb # Window needs to be realized to do anything useful with it. Realizing # does not show the window to the user, so we can use it, but not have # an ugly blank frame while it loads. self.realize() # The "Vim" property self.window.property_change("Vim", gdk.SELECTION_TYPE_STRING, 8, gdk.PROP_MODE_REPLACE, "6.0") # Set the correct event mask and connect the notify event self.add_events(gtk.gdk.PROPERTY_CHANGE_MASK) self.connect('property-notify-event', self.cb_notify) # The serial number used for sending synchronous messages self.serial = 1 # A dictionary of callbacks for synchronous messages. The key is the # serial number, and the value is a callable that will be called with # the result of the synchronous evaluation. self.callbacks = {} # A dictionary to store the working directories for each Vim so they # only have to be fetched once. self.server_cwds = {} # An instance of the root window, so it only has to be fetched once. dpy = gdk.display_get_default() if not dpy: raise Exception('Unable to get default display') screen = dpy.get_screen(0) self.root_window = screen.get_root_window() # fetch the serverlist to begin with to know when we are started self.oldservers = None self.keep_fetching_serverlist = True gobject.timeout_add(250, self.fetch_serverlist) def fetch_serverlist(self): """ Fetch the serverlist, and if it has changed, feed it to the client. The serverlist is requested asynchrnously, and passed the gotservers function as a callback. The gotservers function is then called with the server list, gets the appropriate working directory (if required) and feeds the new server list to the client if it has changed. """ def gotservers(serverlist): """ Called back on receiving the serverlist. Fetch working directories for new Vim instances, and feed the server list to the client if it has changed. """ for server in serverlist: # Check if we already have the working directory. if server not in self.server_cwds: # We don't, fetch it self.fetch_cwd(server) # Check if the server list has changed if serverlist != self.oldservers: self.oldservers = serverlist # A ew serverlist to feed to the client. self.feed_serverlist(serverlist) gotservers(self.get_rootwindow_serverlist()) # decide whether to keep fetching server list return self.keep_fetching_serverlist def stop_fetching_serverlist(self): self.keep_fetching_serverlist = False def get_rootwindow_serverlist(self): """ Get the X root window's version of the current Vim serverlist. On starting with the client-server feature, GVim or Vim with the --servername option registers its server name and X window id as part of the "VimRegistry" parameter on the X root window. This method extracts and parses that property, and returns the server list. Note: Vim does not actually unregister itself with the root window on dying, so the presence of a server in the root window list is no gurantee that it is alive. @return: servers @rtype servers: dict of ("server", "window id") key, value """ servers = {} # Read the property vimregistry = self.root_window.property_get("VimRegistry") # If it exists if vimregistry: # Get the list of servers by splitting with '\0' vimservers = vimregistry[-1].split('\0') # Parse each individual server and add to the results list for rawserver in vimservers: # Sometimes blank servers exist in the list if rawserver: # split the raw value for the name and id name_id = rawserver.split() # Set the value in the results dict, remembering to convert # the window id to a long int. servers[name_id[1]] = long(int(name_id[0], 16)) # return the list of resuts return servers def get_shell_serverlist(self): """ DEPRACATED: WE NEVER NEED A SERVERLIST (This is here for educative purposes) Get the server list by starting console Vim on a Pipe. This blocks, so we don't use it. It is one of the alternative methods of retrieving an accurate serverlist. It is slow, and expensive. """ vimcom = 'gvim' p = os.popen('%s --serverlist' % vimcom) servers = p.read() p.close() return servers.splitlines() def get_hidden_serverlist(self, callbackfunc): """ DEPRACATED: WE NEVER NEED A SERVERLIST (This is here for educative purposes) Get the serverlist from the hidden Vim instance and call the callback function with the results. This method checks first whther the Vim instance is alive, and then evaluates the serverlist() function remotely in it, with a local call back function which parses the result and calls the user-provided callback function. @param callbackfunc: The call back function to be called with the server list. @type callbackfunc: callable """ def cb(serverstring): """ Called back with the raw server list. Parse the lines and call the call back function, ignoring any instances starting with "__" which represent hidden instances. If the hidden Vim instance is not alive, it is restarted. """ servers = serverstring.splitlines() # Call the callback function callbackfunc([svr for svr in servers if not svr.startswith('__')]) # Check if the hidden Vim is alive. if self.vim_hidden.is_alive(): # It is alive, get the serverlist. self.send_expr(self.vim_hidden.name, 'serverlist()', cb) else: # It is not alive, restart it. self.vim_hidden.start() def get_server_wid(self, servername): """ Get the X Window id for a named Vim server. This function returns the id from the root window server list, if it exists, or None if it does not. @param servername: The name of the server @type servername: str @return: wid @rtype wid: long """ try: # get the window id from the root window wid = self.get_rootwindow_serverlist()[servername] except KeyError: # The server is not registered in the root window so return None wid = None # Return wid if it is not none, or None return wid and long(wid) or None def get_server_window(self, wid): """ Create and return a GDK window for a given window ID. This method simply calls gdk.window_foreign_new, which should return None if the window has been destroyed, but does not, in some cases. @param wid: The window ID. @type wid: long """ return gtk.gdk.window_foreign_new(wid) def feed_serverlist(self, serverlist): """ Feed the given list of servers to the client. This is achieved by calling the clients serverlist event. In Pida, this event is passed on to all the plugins. @param serverlist: The list of servers. @type serverlist: list """ # Call the event. #self.do_evt('serverlist', serverlist) self.cb.vim_new_serverlist(serverlist) def fetch_cwd(self, servername): """ Fetch the working directory for a named server and store the result. """ def gotcwd(cwd): """ Called back on receiving the working directory, store it for later use. """ self.server_cwds[servername] = cwd # Evaluate the expression with the gotcwd callback self.send_expr(servername, "getcwd()", gotcwd) def get_cwd(self, server): if server in self.server_cwds: return self.server_cwds[server] def abspath(self, servername, filename): """ Return the absolute path of a buffer name in the context of the named server. """ # Only alter non-absolute paths if not filename.startswith('/'): try: # Try to find the current working directory cwd = self.server_cwds[servername] except KeyError: # The working directory is not set # Use a sane default, and fetch it cwd = os.path.expanduser('~') self.fetch_cwd(servername) filename = os.path.join(cwd, filename) return filename def generate_message(self, server, cork, message, sourceid): """ Generate a message. """ # Increment the serial number used for synchronous messages if cork: self.serial = self.serial + 1 # Pick an arbitrary number where we recycle. if self.serial > 65530: self.serial = 1 # return the generated string return '\0%s\0-n %s\0-s %s\0-r %x %s\0' % (cork, server, message, sourceid, self.serial) def parse_message(self, message): """ Parse a received message and return the message atributes as a dictionary. """ messageattrs = {} for t in [s.split(' ') for s in message.split('\0')]: if t and len(t[0]): name = t[0] value = ' '.join(t[1:]) if name.startswith('-'): #attributes start with a '-', strip it and set the value name = name[1:] messageattrs[name] = value else: # Otherwise set the t attribute messageattrs['t'] = name return messageattrs def send_message(self, servername, message, asexpr, callback): wid = self.get_server_wid(servername) if wid: cork = (asexpr and 'c') or 'k' sw = self.get_server_window(wid) if sw and sw.property_get("Vim"): mp = self.generate_message(servername, cork, message, self.window.xid) sw.property_change("Comm", gdk.TARGET_STRING, 8, gdk.PROP_MODE_APPEND, mp) if asexpr and callback: self.callbacks['%s' % (self.serial)] = callback def send_expr(self, server, message, callback): self.send_message(server, message, True, callback) def send_keys(self, server, message): self.send_message(server, message, False, False) def send_esc(self, server): self.send_keys(server, '') def send_ret(self, server): self.send_keys(server, '') def send_ex(self, server, message): self.send_esc(server) self.send_keys(server, ':%s' % message) self.send_ret(server) def send_ex_via_tempfile(self, server, message): """For really long ugly messages""" tf, tp = tempfile.mkstemp() os.write(tf, '%s\n' % message) os.close(tf) self.load_script(server, tp) # delay removing the temporary file to make sure it is loaded gobject.timeout_add(6000, os.unlink, tp) def get_option(self, server, option, callbackfunc): self.send_expr(server, '&%s' % option, callbackfunc) def foreground(self, server): def cb(*args): pass self.send_expr(server, 'foreground()', cb) def change_buffer(self, server, filename): self.send_ex(server, "exe 'b!'.bufnr('%s')" % filename) def change_buffer_number(self, server, number): self.send_ex(server, "b!%s" % number) def close_buffer(self, server, buffername): self.send_ex(server, "exe 'confirm bw'.bufnr('%s')" % buffername) def close_current_buffer(self, server): self.send_ex(server, 'confirm bw') def change_cursor(self, server, x, y): self.send_message(server, 'cursor(%s, %s)' % (y, x), True, False) self.send_esc(server) def save_session(self, server, file_name): self.send_ex(server, 'mks %s' % file_name) def load_session(self, server, file_name): self.load_script(server, file_name) def escape_filename(self, name): for s in ['\\', '?', '*', ' ', "'", '"', '[', ' ', '$', '{', '}']: name = name.replace (s, '\\%s' % s) return name def open_file(self, server, name): self.send_ex(server, 'confirm e %s' % self.escape_filename(name)) def new_file(self, server): f, path = tempfile.mkstemp() self.open_file(server, path) return path def goto_line(self, server, linenumber): self.send_ex(server, '%s' % linenumber) self.send_esc(server) self.send_keys(server, 'zz') self.send_keys(server, 'zv') def revert(self, server): self.send_ex(server, 'e') def load_script(self, server, scriptpath): self.send_ex(server, 'so %s' % scriptpath) def preview_file(self, server, fn): self.send_ex(server, 'pc') self.send_ex(server, 'set nopreviewwindow') self.send_ex(server, 'pedit %s' % fn) def get_bufferlist(self, server): def cb(bl): if bl: l = [i.split(':') for i in bl.strip(';').split(';')] L = [] for n in l: if not n[0].startswith('E'): L.append([n[0], self.abspath(server, n[1])]) self.do_evt('bufferlist', L) #self.get_cwd(server) self.send_expr(server, 'Bufferlist()', cb) def get_current_buffer(self, server): def cb(bs): bn = bs.split(chr(5)) bn[1] = self.abspath(server, bn[1]) self.do_evt('bufferchange', *bn) #self.get_cwd(server) self.send_expr(server, "bufnr('%').'\\5'.bufname('%')", cb) def save(self, server): self.send_ex(server, 'w') def save_as(self, server, filename): print filename self.send_ex(server, 'saveas %s' % filename) def undo(self, server): self.send_esc(server) self.send_keys(server, 'u') def redo(self, server): self.send_esc(server) self.send_keys(server, '') def cut(self, server): self.send_keys(server, '"+x') def copy(self, server): self.send_keys(server, '"+y') def paste(self, server): self.send_esc(server) self.send_keys(server, 'p') def set_colorscheme(self, server, colorscheme): self.send_ex(server, 'colorscheme %s' % colorscheme) def set_menu_visible(self, server, visible): if visible: op = '+' else: op = '-' self.send_ex(server, 'set guioptions%s=m' % op) def quit(self, server): self.send_ex(server, 'q!') def define_sign(self, server, name, icon, linehl, text, texthl, direct=False): cmd = ('sign define %s icon=%s linehl=%s text=%s texthl=%s '% (name, icon, linehl, text, texthl)) if direct: self.send_ex(server, cmd) else: self.send_ex_via_tempfile(server, cmd) def undefine_sign(self, server, name): self.send_ex(server, 'sign undefine %s' % name) def show_sign(self, server, index, type, filename, line): self.send_ex(server, 'sign place %s line=%s name=%s file=%s' % (index + 1, line, type, filename)) def hide_sign(self, server, index, filename): self.send_ex(server, 'sign unplace %s' % (index + 1)) def get_cword(self, server, callback): self.send_esc(server) self.send_expr(server, 'expand("")', callback) def get_selection(self, server, callback): self.send_expr(server, 'getreg("*")', callback) def delete_cword(self, server): self.send_esc(server) self.send_keys(server, 'ciw') def insert_text(self, server, text): self.send_esc(server) self.send_keys(server, 'a') self.send_keys(server, text) def set_path(self, server, path): self.send_ex(server, 'cd %s' % path) def add_completion(self, server, s): self.send_expr(server, 'complete_add("%s")' % s, lambda *a: None) def finish_completion(self, server): self.send_keys(server, chr(3)) def cb_notify(self, *a): win, ev = a if hasattr(ev, 'atom'): if ev.atom == 'Comm': message = self.window.property_get('Comm', pdelete=True) if message: self.cb_reply(message[-1]) return True def cb_reply(self, data): mdict = self.parse_message(data) if mdict['t'] == 'r': if mdict['s'] in self.callbacks: self.callbacks[mdict['s']](mdict['r']) else: s = [t for t in data.split('\0') if t.startswith('-n')].pop()[3:] self.cb_reply_async(s) def cb_reply_async(self, data): if data.count(':'): server, data = data.split(':', 1) else: server = None sep = chr(4) if data.count(sep): evt, d = data.split(sep, 1) self.vim_event(server, evt, d) else: print 'bad async reply', data def vim_event(self, server, evt, d): funcname = 'vim_%s' % evt if hasattr(self.cb, funcname): getattr(self.cb, funcname)(server, *d.split(chr(4))) else: print 'unhandled event', evt VimCom = communication_window NMAP_COM = '%smap %s :call %sCR>' UNMAP_COM = '%sun %s' VIMSCRIPT = ''':silent function! Bufferlist() let i = 1 let max = bufnr('$') + 1 let lis = "" while i < max if bufexists(i) let lis = lis.";".i.":".bufname(i) endif let i = i + 1 endwhile return lis :endfunction :silent function! BreakPoint(l) call Async_event(v:servername.":set_breakpoint,".a:l) :endfunction :silent function! Yank_visual() y return @" :endfunction :silent function! Async_event(e) let args = substitute(a:e, ",", "\4", "g") let c = "silent call server2client('".expand('')."', '".args."')" try exec c catch /.*/ echo c endtry :endfunction :silent function! Pida_Started() silent call Async_event(v:servername.":filesave,") echo "PIDA connected" :endfunction :silent sign define break text=!B :silent augroup pida :silent set guioptions-=T :silent set guioptions-=m :silent au! pida :silent au pida BufEnter * silent call Async_event(v:servername.":bufferchange,".getcwd().",".bufname('%').",".bufnr('%')) :silent au pida BufDelete * silent call Async_event(v:servername.":bufferunload,".expand('')) :silent au pida VimLeave * silent call Async_event(v:servername.":shutdown,") :silent au pida VimEnter * silent call Pida_Started() :silent au pida BufWritePost * silent call Async_event(v:servername.":filesave,") :silent au pida CursorMovedI * silent call Async_event(v:servername.":cursor_move,".line('.')) :silent au pida CursorMoved * silent call Async_event(v:servername.":cursor_move,".line('.')) :silent function! Pida_Complete(findstart, base) " locate the start of the word let line = getline('.') let start = col('.') - 1 while start > 0 && line[start - 1] =~ '\a' let start -= 1 endwhile if a:findstart let g:completing = 1 return start else call Async_event(v:servername.":complete".a:findstart."".a:base."".line."".start) let completion_time = 0 while g:completing && completion_time < 500 sleep 100m let completion_time = completion_time + 100 "if complete_check() " break "endif endwhile return [] endif :endfunction :silent function! Pida_Stop_Completing() let g:completing = 1 :endfunction set completefunc=Pida_Complete ''' PIDA-0.5.1/pida/utils/vim/vimeditor.py0000644000175000017500000002310610652670601015557 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005-2006 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gobject import pida.core.service as service from pida.core import errors defs = service.definitions types = service.types import vimcom class vim_editor(object): single_view = None class display(defs.optiongroup): class colour_scheme(defs.option): """The colour scheme to use in vim (Empty will be ignored).""" rtype = types.string default = '' class hide_vim_menu(defs.option): """Whether the vim menu will be hidden.""" rtype = types.boolean default = False def init(self): self.__servers = {} self.__documents = {} self.__old_shortcuts = {'n':{}, 'v':{}} self.__currentdocument = None self._create_initscript() self.__cw = vimcom.communication_window(self) self.__newdocs = {} def _create_initscript(self): script_path = os.path.join(self.boss.get_pida_home(), 'pida_vim_init.vim') if not os.path.exists(script_path): f = open(script_path, 'w') f.write(vimcom.VIMSCRIPT) f.close() def vim_init_server(self): self.__cw.load_script(self.server, os.path.join(self.boss.get_pida_home(), 'pida_vim_init.vim')) def stop_fetching_serverlist(self): self.__cw.keep_fetching_serverlist = False def get_server(self): raise NotImplementedError def vim_start(self): raise NotImplementedError def vim_new_serverlist(self, serverlist): raise NotImplementedError def cmd_start(self): self.vim_start() def cmd_revert(self): self.__cw.revert(self.server) def cmd_close(self, document): if document.unique_id in self.__newdocs: fn = self.__newdocs[document.unique_id] else: fn = document.filename self.__cw.close_buffer(self.server, fn) def cmd_edit(self, document): """Open and edit.""" if document is not self.__currentdocument: if (document.unique_id in self.__servers.setdefault(self.server, [])): if document.unique_id in self.__newdocs: fn = self.__newdocs[document.unique_id] else: fn = document.filename self.__cw.change_buffer(self.server, fn) self.__cw.foreground(self.server) else: found = False for server in self.__servers: serverdocs = self.__servers[server] if document.unique_id in serverdocs: self.__cw.change_buffer(server, document.filename) self.__cw.foreground(server) found = True break if not found: if document.filename is None: newname = self.__cw.new_file(self.server) self.__newdocs[document.unique_id] = newname else: self.__cw.open_file(self.server, document.filename) self.__servers[self.server].append(document.unique_id) self.__documents[document.unique_id] = document self.__currentdocument = document if self.single_view is not None: self.single_view.raise_page() if document.filename is None: title = 'New File' else: title = document.filename self.single_view.long_title = title def cmd_undo(self): self.__cw.undo(self.server) def cmd_redo(self): self.__cw.redo(self.server) def cmd_cut(self): self.__cw.cut(self.server) def cmd_copy(self): self.__cw.copy(self.server) def cmd_paste(self): self.__cw.paste(self.server) def cmd_save(self): self.__cw.save(self.server) def cmd_save_as(self, filename): del self.__newdocs[self.__currentdocument.unique_id] self.__cw.save_as(self.server, filename) def cmd_goto_line(self, linenumber): self.__cw.goto_line(self.server, linenumber + 1) def cmd_show_mark(self, index, filename, line): self.__cw.show_sign(self.server, index, filename, line) def cmd_hide_mark(self, index): pass def reset(self): colorscheme = self.opts.display__colour_scheme if colorscheme: self.__cw.set_colorscheme(self.server, colorscheme) if self.opts.display__hide_vim_menu: self.__cw.set_menu_visible(self.server, False) #self.__load_shortcuts() def open_file_line(self, filename, linenumber): if self.__currentfile != filename: self.open_file(filename) self.__bufferevents.append([self.goto_line, (linenumber, )]) else: self.goto_line(linenumber) def goto_line(self, linenumber): self.__cw.change_cursor(self.server, 1, linenumber) def vim_bufferchange(self, server, cwd, filename, bufnr): self.log.debug('vim buffer change "%s"', filename) if not filename or filename in '-MiniBufExplorer-': return if os.path.abspath(filename) != filename: filename = os.path.join(cwd, filename) if os.path.isdir(filename): if self.opts.behaviour__open_directories_in_pida: self.boss.call_command('filemanager', 'browse', directory=filename) self.__cw.close_buffer(self.server, filename) return if self.__currentdocument is None or filename != self.__currentdocument.filename: for uid, fn in self.__newdocs.iteritems(): if fn == filename: doc = self.__documents[uid] self.__current_doc_set(doc) return for doc in self.__documents.values(): if doc.filename == filename: self.__current_doc_set(doc) return self.boss.call_command('buffermanager', 'open_file', filename=filename) def __current_doc_set(self, doc): self.__currentdocument = doc self.boss.call_command('buffermanager', 'open_document', document=doc) def vim_bufferunload(self, server, filename, *args): self.log.debug('vim unloaded "%s"', filename) if filename != '': doc = None for uid, fn in self.__newdocs.iteritems(): if fn == filename: doc = self.__documents[uid] break if doc is None: for uid, document in self.__documents.iteritems(): if document.filename == filename: doc = document break if doc is not None: self.__servers[server].remove(doc.unique_id) del self.__documents[uid] self.__currentdocument = None self.boss.call_command('buffermanager', 'document_closed', document=doc, dorefresh=True) def vim_started(self, server): print 'started' def vim_filesave(self, server, *args): self.boss.call_command('buffermanager', 'reset_current_document') def vim_globalkp(self, server, name): self.boss.command('keyboardshortcuts', 'keypress-by-name', kpname=name) def vim_shutdown(self, server, *args): #self.clean_after_shutdown(server) self.after_shutdown(server) def vim_set_breakpoint(self, server, line): self.boss.call_command('pythondebugger', 'set_breakpoint', filename=self.__currentdocument.filename, line=int(line)) def clean_after_shutdown(self, server): for docid in self.__servers.setdefault(server, []): doc = self.__documents[docid] del self.__documents[docid] self.boss.call_command('buffermanager', 'document_closed', document=doc) self.__servers[server] = [] self.__currentdocument = None def after_shutdown(self, server): pass def get_vim_window(self): return self.__cw vim_window= property(get_vim_window) def get_current_document(self): return self.__currentdocument current_document = property(get_current_document) def stop(self): self.__cw.quit(self.server) PIDA-0.5.1/pida/utils/vim/vimembed.py0000644000175000017500000001041710652670601015346 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2005-2006 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. ''' A library to embed vim in a gtk socket ''' import gtk import os import time import subprocess class vim_embed(object): HAS_CONTROL_BOX = False HAS_TITLE = False def init(self, command='gvim', args=[]): self.__servername = self.__generate_servername() self.pid = None self.args = args self.r_cb_plugged = None self.r_cb_unplugged = None self.__eb = None def __pack(self): socket = gtk.Socket() eb = gtk.EventBox() self.widget.pack_start(eb) eb.add_events(gtk.gdk.KEY_PRESS_MASK) eb.add(socket) self.show_all() self.__eb = eb return socket.get_id() def __generate_servername(self): return 'PIDA_EMBEDDED_%s' % time.time() def get_servername(self): return self.__servername servername = property(get_servername) def should_remove(self): self.service.remove_attempt() return False def run(self, command): self.command = command xid = self.__pack() args = self.args[:] # a copy args.extend(['--socketid', '%s' % xid]) if not xid: return if not self.pid: popen = subprocess.Popen([self.command, '--servername', self.servername, '--cmd', 'let PIDA_EMBEDDED=1'] + args, close_fds=True) self.pid = popen.pid self.show_all() def grab_input_focus(self): self.__eb.child_focus(gtk.DIR_TAB_FORWARD) class VimEmbedWidget(gtk.EventBox): def __init__(self, command, script_path, args=[]): gtk.EventBox.__init__(self) self._servername = self._generate_servername() self._command = command self._init_script = script_path self.pid = None self.args = args self.r_cb_plugged = None self.r_cb_unplugged = None self.__eb = None def _create_ui(self): socket = gtk.Socket() self.add_events(gtk.gdk.KEY_PRESS_MASK) self.add(socket) self.show_all() return socket.get_id() def _generate_servername(self): return 'PIDA_EMBEDDED_%s' % time.time() def get_server_name(self): return self._servername def should_remove(self): self.service.remove_attempt() return False def run(self): xid = self._create_ui() args = self.args[:] # a copy args.extend(['--socketid', '%s' % xid]) if not xid: return if not self.pid: try: popen = subprocess.Popen( [self._command, '--servername', self.get_server_name(), '--cmd', 'let PIDA_EMBEDDED=1', '--cmd', 'so %s' % self._init_script ] + args, close_fds=True ) self.pid = popen.pid except OSError: return False self.show_all() return True def grab_input_focus(self): self.child_focus(gtk.DIR_TAB_FORWARD) PIDA-0.5.1/pida/utils/__init__.py0000644000175000017500000000000010652670605014511 0ustar alialiPIDA-0.5.1/pida/utils/configobj.py0000644000175000017500000024321110652670605014727 0ustar aliali# configobj.py # A config file reader/writer that supports nested sections in config files. # Copyright (C) 2005-2006 Michael Foord, Nicola Larosa # E-mail: fuzzyman AT voidspace DOT org DOT uk # nico AT tekNico DOT net # ConfigObj 4 # http://www.voidspace.org.uk/python/configobj.html # Released subject to the BSD License # Please see http://www.voidspace.org.uk/python/license.shtml # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml # For information about bugfixes, updates and support, please join the # ConfigObj mailing list: # http://lists.sourceforge.net/lists/listinfo/configobj-develop # Comments, suggestions and bug reports welcome. from __future__ import generators import sys INTP_VER = sys.version_info[:2] if INTP_VER < (2, 2): raise RuntimeError("Python v.2.2 or later needed") import os, re compiler = None try: import compiler except ImportError: # for IronPython pass from types import StringTypes from warnings import warn try: from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE except ImportError: # Python 2.2 does not have these # UTF-8 BOM_UTF8 = '\xef\xbb\xbf' # UTF-16, little endian BOM_UTF16_LE = '\xff\xfe' # UTF-16, big endian BOM_UTF16_BE = '\xfe\xff' if sys.byteorder == 'little': # UTF-16, native endianness BOM_UTF16 = BOM_UTF16_LE else: # UTF-16, native endianness BOM_UTF16 = BOM_UTF16_BE # A dictionary mapping BOM to # the encoding to decode with, and what to set the # encoding attribute to. BOMS = { BOM_UTF8: ('utf_8', None), BOM_UTF16_BE: ('utf16_be', 'utf_16'), BOM_UTF16_LE: ('utf16_le', 'utf_16'), BOM_UTF16: ('utf_16', 'utf_16'), } # All legal variants of the BOM codecs. # TODO: the list of aliases is not meant to be exhaustive, is there a # better way ? BOM_LIST = { 'utf_16': 'utf_16', 'u16': 'utf_16', 'utf16': 'utf_16', 'utf-16': 'utf_16', 'utf16_be': 'utf16_be', 'utf_16_be': 'utf16_be', 'utf-16be': 'utf16_be', 'utf16_le': 'utf16_le', 'utf_16_le': 'utf16_le', 'utf-16le': 'utf16_le', 'utf_8': 'utf_8', 'u8': 'utf_8', 'utf': 'utf_8', 'utf8': 'utf_8', 'utf-8': 'utf_8', } # Map of encodings to the BOM to write. BOM_SET = { 'utf_8': BOM_UTF8, 'utf_16': BOM_UTF16, 'utf16_be': BOM_UTF16_BE, 'utf16_le': BOM_UTF16_LE, None: BOM_UTF8 } try: from validate import VdtMissingValue except ImportError: VdtMissingValue = None try: enumerate except NameError: def enumerate(obj): """enumerate for Python 2.2.""" i = -1 for item in obj: i += 1 yield i, item try: True, False except NameError: True, False = 1, 0 __version__ = '4.4.0' __revision__ = '$Id: configobj.py 156 2006-01-31 14:57:08Z fuzzyman $' __docformat__ = "restructuredtext en" __all__ = ( '__version__', 'DEFAULT_INDENT_TYPE', 'DEFAULT_INTERPOLATION', 'ConfigObjError', 'NestingError', 'ParseError', 'DuplicateError', 'ConfigspecError', 'ConfigObj', 'SimpleVal', 'InterpolationError', 'InterpolationLoopError', 'MissingInterpolationOption', 'RepeatSectionError', 'UnreprError', 'UnknownType', '__docformat__', 'flatten_errors', ) DEFAULT_INTERPOLATION = 'configparser' DEFAULT_INDENT_TYPE = ' ' MAX_INTERPOL_DEPTH = 10 OPTION_DEFAULTS = { 'interpolation': True, 'raise_errors': False, 'list_values': True, 'create_empty': False, 'file_error': False, 'configspec': None, 'stringify': True, # option may be set to one of ('', ' ', '\t') 'indent_type': None, 'encoding': None, 'default_encoding': None, 'unrepr': False, 'write_empty_values': False, } def getObj(s): s = "a=" + s if compiler is None: raise ImportError('compiler module not available') p = compiler.parse(s) return p.getChildren()[1].getChildren()[0].getChildren()[1] class UnknownType(Exception): pass class Builder: def build(self, o): m = getattr(self, 'build_' + o.__class__.__name__, None) if m is None: raise UnknownType(o.__class__.__name__) return m(o) def build_List(self, o): return map(self.build, o.getChildren()) def build_Const(self, o): return o.value def build_Dict(self, o): d = {} i = iter(map(self.build, o.getChildren())) for el in i: d[el] = i.next() return d def build_Tuple(self, o): return tuple(self.build_List(o)) def build_Name(self, o): if o.name == 'None': return None if o.name == 'True': return True if o.name == 'False': return False # An undefinted Name raise UnknownType('Undefined Name') def build_Add(self, o): real, imag = map(self.build_Const, o.getChildren()) try: real = float(real) except TypeError: raise UnknownType('Add') if not isinstance(imag, complex) or imag.real != 0.0: raise UnknownType('Add') return real+imag def build_Getattr(self, o): parent = self.build(o.expr) return getattr(parent, o.attrname) def build_UnarySub(self, o): return -self.build_Const(o.getChildren()[0]) def build_UnaryAdd(self, o): return self.build_Const(o.getChildren()[0]) def unrepr(s): if not s: return s return Builder().build(getObj(s)) def _splitlines(instring): """Split a string on lines, without losing line endings or truncating.""" class ConfigObjError(SyntaxError): """ This is the base class for all errors that ConfigObj raises. It is a subclass of SyntaxError. """ def __init__(self, message='', line_number=None, line=''): self.line = line self.line_number = line_number self.message = message SyntaxError.__init__(self, message) class NestingError(ConfigObjError): """ This error indicates a level of nesting that doesn't match. """ class ParseError(ConfigObjError): """ This error indicates that a line is badly written. It is neither a valid ``key = value`` line, nor a valid section marker line. """ class DuplicateError(ConfigObjError): """ The keyword or section specified already exists. """ class ConfigspecError(ConfigObjError): """ An error occured whilst parsing a configspec. """ class InterpolationError(ConfigObjError): """Base class for the two interpolation errors.""" class InterpolationLoopError(InterpolationError): """Maximum interpolation depth exceeded in string interpolation.""" def __init__(self, option): InterpolationError.__init__( self, 'interpolation loop detected in value "%s".' % option) class RepeatSectionError(ConfigObjError): """ This error indicates additional sections in a section with a ``__many__`` (repeated) section. """ class MissingInterpolationOption(InterpolationError): """A value specified for interpolation was missing.""" def __init__(self, option): InterpolationError.__init__( self, 'missing option "%s" in interpolation.' % option) class UnreprError(ConfigObjError): """An error parsing in unrepr mode.""" class InterpolationEngine(object): """ A helper class to help perform string interpolation. This class is an abstract base class; its descendants perform the actual work. """ # compiled regexp to use in self.interpolate() _KEYCRE = re.compile(r"%\(([^)]*)\)s") def __init__(self, section): # the Section instance that "owns" this engine self.section = section def interpolate(self, key, value): def recursive_interpolate(key, value, section, backtrail): """The function that does the actual work. ``value``: the string we're trying to interpolate. ``section``: the section in which that string was found ``backtrail``: a dict to keep track of where we've been, to detect and prevent infinite recursion loops This is similar to a depth-first-search algorithm. """ # Have we been here already? if backtrail.has_key((key, section.name)): # Yes - infinite loop detected raise InterpolationLoopError(key) # Place a marker on our backtrail so we won't come back here again backtrail[(key, section.name)] = 1 # Now start the actual work match = self._KEYCRE.search(value) while match: # The actual parsing of the match is implementation-dependent, # so delegate to our helper function k, v, s = self._parse_match(match) if k is None: # That's the signal that no further interpolation is needed replacement = v else: # Further interpolation may be needed to obtain final value replacement = recursive_interpolate(k, v, s, backtrail) # Replace the matched string with its final value start, end = match.span() value = ''.join((value[:start], replacement, value[end:])) new_search_start = start + len(replacement) # Pick up the next interpolation key, if any, for next time # through the while loop match = self._KEYCRE.search(value, new_search_start) # Now safe to come back here again; remove marker from backtrail del backtrail[(key, section.name)] return value # Back in interpolate(), all we have to do is kick off the recursive # function with appropriate starting values value = recursive_interpolate(key, value, self.section, {}) return value def _fetch(self, key): """Helper function to fetch values from owning section. Returns a 2-tuple: the value, and the section where it was found. """ # switch off interpolation before we try and fetch anything ! save_interp = self.section.main.interpolation self.section.main.interpolation = False # Start at section that "owns" this InterpolationEngine current_section = self.section while True: # try the current section first val = current_section.get(key) if val is not None: break # try "DEFAULT" next val = current_section.get('DEFAULT', {}).get(key) if val is not None: break # move up to parent and try again # top-level's parent is itself if current_section.parent is current_section: # reached top level, time to give up break current_section = current_section.parent # restore interpolation to previous value before returning self.section.main.interpolation = save_interp if val is None: raise MissingInterpolationOption(key) return val, current_section def _parse_match(self, match): """Implementation-dependent helper function. Will be passed a match object corresponding to the interpolation key we just found (e.g., "%(foo)s" or "$foo"). Should look up that key in the appropriate config file section (using the ``_fetch()`` helper function) and return a 3-tuple: (key, value, section) ``key`` is the name of the key we're looking for ``value`` is the value found for that key ``section`` is a reference to the section where it was found ``key`` and ``section`` should be None if no further interpolation should be performed on the resulting value (e.g., if we interpolated "$$" and returned "$"). """ raise NotImplementedError class ConfigParserInterpolation(InterpolationEngine): """Behaves like ConfigParser.""" _KEYCRE = re.compile(r"%\(([^)]*)\)s") def _parse_match(self, match): key = match.group(1) value, section = self._fetch(key) return key, value, section class TemplateInterpolation(InterpolationEngine): """Behaves like string.Template.""" _delimiter = '$' _KEYCRE = re.compile(r""" \$(?: (?P\$) | # Two $ signs (?P[_a-z][_a-z0-9]*) | # $name format {(?P[^}]*)} # ${name} format ) """, re.IGNORECASE | re.VERBOSE) def _parse_match(self, match): # Valid name (in or out of braces): fetch value from section key = match.group('named') or match.group('braced') if key is not None: value, section = self._fetch(key) return key, value, section # Escaped delimiter (e.g., $$): return single delimiter if match.group('escaped') is not None: # Return None for key and section to indicate it's time to stop return None, self._delimiter, None # Anything else: ignore completely, just return it unchanged return None, match.group(), None interpolation_engines = { 'configparser': ConfigParserInterpolation, 'template': TemplateInterpolation, } class Section(dict): """ A dictionary-like object that represents a section in a config file. It does string interpolation if the 'interpolation' attribute of the 'main' object is set to True. Interpolation is tried first from this object, then from the 'DEFAULT' section of this object, next from the parent and its 'DEFAULT' section, and so on until the main object is reached. A Section will behave like an ordered dictionary - following the order of the ``scalars`` and ``sections`` attributes. You can use this to change the order of members. Iteration follows the order: scalars, then sections. """ def __init__(self, parent, depth, main, indict=None, name=None): """ * parent is the section above * depth is the depth level of this section * main is the main ConfigObj * indict is a dictionary to initialise the section with """ if indict is None: indict = {} dict.__init__(self) # used for nesting level *and* interpolation self.parent = parent # used for the interpolation attribute self.main = main # level of nesting depth of this Section self.depth = depth # the sequence of scalar values in this Section self.scalars = [] # the sequence of sections in this Section self.sections = [] # purely for information self.name = name # for comments :-) self.comments = {} self.inline_comments = {} # for the configspec self.configspec = {} self._order = [] self._configspec_comments = {} self._configspec_inline_comments = {} self._cs_section_comments = {} self._cs_section_inline_comments = {} # for defaults self.defaults = [] # # we do this explicitly so that __setitem__ is used properly # (rather than just passing to ``dict.__init__``) for entry in indict: self[entry] = indict[entry] def _interpolate(self, key, value): try: # do we already have an interpolation engine? engine = self._interpolation_engine except AttributeError: # not yet: first time running _interpolate(), so pick the engine name = self.main.interpolation if name == True: # note that "if name:" would be incorrect here # backwards-compatibility: interpolation=True means use default name = DEFAULT_INTERPOLATION name = name.lower() # so that "Template", "template", etc. all work class_ = interpolation_engines.get(name, None) if class_ is None: # invalid value for self.main.interpolation self.main.interpolation = False return value else: # save reference to engine so we don't have to do this again engine = self._interpolation_engine = class_(self) # let the engine do the actual work return engine.interpolate(key, value) def __getitem__(self, key): """Fetch the item and do string interpolation.""" val = dict.__getitem__(self, key) if self.main.interpolation and isinstance(val, StringTypes): return self._interpolate(key, val) return val def __setitem__(self, key, value, unrepr=False): """ Correctly set a value. Making dictionary values Section instances. (We have to special case 'Section' instances - which are also dicts) Keys must be strings. Values need only be strings (or lists of strings) if ``main.stringify`` is set. `unrepr`` must be set when setting a value to a dictionary, without creating a new sub-section. """ if not isinstance(key, StringTypes): raise ValueError, 'The key "%s" is not a string.' % key # add the comment if not self.comments.has_key(key): self.comments[key] = [] self.inline_comments[key] = '' # remove the entry from defaults if key in self.defaults: self.defaults.remove(key) # if isinstance(value, Section): if not self.has_key(key): self.sections.append(key) dict.__setitem__(self, key, value) elif isinstance(value, dict) and not unrepr: # First create the new depth level, # then create the section if not self.has_key(key): self.sections.append(key) new_depth = self.depth + 1 dict.__setitem__( self, key, Section( self, new_depth, self.main, indict=value, name=key)) else: if not self.has_key(key): self.scalars.append(key) if not self.main.stringify: if isinstance(value, StringTypes): pass elif isinstance(value, (list, tuple)): for entry in value: if not isinstance(entry, StringTypes): raise TypeError, ( 'Value is not a string "%s".' % entry) else: raise TypeError, 'Value is not a string "%s".' % value dict.__setitem__(self, key, value) def __delitem__(self, key): """Remove items from the sequence when deleting.""" dict. __delitem__(self, key) if key in self.scalars: self.scalars.remove(key) else: self.sections.remove(key) del self.comments[key] del self.inline_comments[key] def get(self, key, default=None): """A version of ``get`` that doesn't bypass string interpolation.""" try: return self[key] except KeyError: return default def update(self, indict): """ A version of update that uses our ``__setitem__``. """ for entry in indict: self[entry] = indict[entry] def pop(self, key, *args): """ """ val = dict.pop(self, key, *args) if key in self.scalars: del self.comments[key] del self.inline_comments[key] self.scalars.remove(key) elif key in self.sections: del self.comments[key] del self.inline_comments[key] self.sections.remove(key) if self.main.interpolation and isinstance(val, StringTypes): return self._interpolate(key, val) return val def popitem(self): """Pops the first (key,val)""" sequence = (self.scalars + self.sections) if not sequence: raise KeyError, ": 'popitem(): dictionary is empty'" key = sequence[0] val = self[key] del self[key] return key, val def clear(self): """ A version of clear that also affects scalars/sections Also clears comments and configspec. Leaves other attributes alone : depth/main/parent are not affected """ dict.clear(self) self.scalars = [] self.sections = [] self.comments = {} self.inline_comments = {} self.configspec = {} def setdefault(self, key, default=None): """A version of setdefault that sets sequence if appropriate.""" try: return self[key] except KeyError: self[key] = default return self[key] def items(self): """ """ return zip((self.scalars + self.sections), self.values()) def keys(self): """ """ return (self.scalars + self.sections) def values(self): """ """ return [self[key] for key in (self.scalars + self.sections)] def iteritems(self): """ """ return iter(self.items()) def iterkeys(self): """ """ return iter((self.scalars + self.sections)) __iter__ = iterkeys def itervalues(self): """ """ return iter(self.values()) def __repr__(self): return '{%s}' % ', '.join([('%s: %s' % (repr(key), repr(self[key]))) for key in (self.scalars + self.sections)]) __str__ = __repr__ # Extra methods - not in a normal dictionary def dict(self): """ Return a deepcopy of self as a dictionary. All members that are ``Section`` instances are recursively turned to ordinary dictionaries - by calling their ``dict`` method. >>> n = a.dict() >>> n == a 1 >>> n is a 0 """ newdict = {} for entry in self: this_entry = self[entry] if isinstance(this_entry, Section): this_entry = this_entry.dict() elif isinstance(this_entry, list): # create a copy rather than a reference this_entry = list(this_entry) elif isinstance(this_entry, tuple): # create a copy rather than a reference this_entry = tuple(this_entry) newdict[entry] = this_entry return newdict def merge(self, indict): """ A recursive update - useful for merging config files. >>> a = '''[section1] ... option1 = True ... [[subsection]] ... more_options = False ... # end of file'''.splitlines() >>> b = '''# File is user.ini ... [section1] ... option1 = False ... # end of file'''.splitlines() >>> c1 = ConfigObj(b) >>> c2 = ConfigObj(a) >>> c2.merge(c1) >>> c2 {'section1': {'option1': 'False', 'subsection': {'more_options': 'False'}}} """ for key, val in indict.items(): if (key in self and isinstance(self[key], dict) and isinstance(val, dict)): self[key].merge(val) else: self[key] = val def rename(self, oldkey, newkey): """ Change a keyname to another, without changing position in sequence. Implemented so that transformations can be made on keys, as well as on values. (used by encode and decode) Also renames comments. """ if oldkey in self.scalars: the_list = self.scalars elif oldkey in self.sections: the_list = self.sections else: raise KeyError, 'Key "%s" not found.' % oldkey pos = the_list.index(oldkey) # val = self[oldkey] dict.__delitem__(self, oldkey) dict.__setitem__(self, newkey, val) the_list.remove(oldkey) the_list.insert(pos, newkey) comm = self.comments[oldkey] inline_comment = self.inline_comments[oldkey] del self.comments[oldkey] del self.inline_comments[oldkey] self.comments[newkey] = comm self.inline_comments[newkey] = inline_comment def walk(self, function, raise_errors=True, call_on_sections=False, **keywargs): """ Walk every member and call a function on the keyword and value. Return a dictionary of the return values If the function raises an exception, raise the errror unless ``raise_errors=False``, in which case set the return value to ``False``. Any unrecognised keyword arguments you pass to walk, will be pased on to the function you pass in. Note: if ``call_on_sections`` is ``True`` then - on encountering a subsection, *first* the function is called for the *whole* subsection, and then recurses into it's members. This means your function must be able to handle strings, dictionaries and lists. This allows you to change the key of subsections as well as for ordinary members. The return value when called on the whole subsection has to be discarded. See the encode and decode methods for examples, including functions. .. caution:: You can use ``walk`` to transform the names of members of a section but you mustn't add or delete members. >>> config = '''[XXXXsection] ... XXXXkey = XXXXvalue'''.splitlines() >>> cfg = ConfigObj(config) >>> cfg {'XXXXsection': {'XXXXkey': 'XXXXvalue'}} >>> def transform(section, key): ... val = section[key] ... newkey = key.replace('XXXX', 'CLIENT1') ... section.rename(key, newkey) ... if isinstance(val, (tuple, list, dict)): ... pass ... else: ... val = val.replace('XXXX', 'CLIENT1') ... section[newkey] = val >>> cfg.walk(transform, call_on_sections=True) {'CLIENT1section': {'CLIENT1key': None}} >>> cfg {'CLIENT1section': {'CLIENT1key': 'CLIENT1value'}} """ out = {} # scalars first for i in range(len(self.scalars)): entry = self.scalars[i] try: val = function(self, entry, **keywargs) # bound again in case name has changed entry = self.scalars[i] out[entry] = val except Exception: if raise_errors: raise else: entry = self.scalars[i] out[entry] = False # then sections for i in range(len(self.sections)): entry = self.sections[i] if call_on_sections: try: function(self, entry, **keywargs) except Exception: if raise_errors: raise else: entry = self.sections[i] out[entry] = False # bound again in case name has changed entry = self.sections[i] # previous result is discarded out[entry] = self[entry].walk( function, raise_errors=raise_errors, call_on_sections=call_on_sections, **keywargs) return out def decode(self, encoding): """ Decode all strings and values to unicode, using the specified encoding. Works with subsections and list values. Uses the ``walk`` method. Testing ``encode`` and ``decode``. >>> m = ConfigObj(a) >>> m.decode('ascii') >>> def testuni(val): ... for entry in val: ... if not isinstance(entry, unicode): ... print >> sys.stderr, type(entry) ... raise AssertionError, 'decode failed.' ... if isinstance(val[entry], dict): ... testuni(val[entry]) ... elif not isinstance(val[entry], unicode): ... raise AssertionError, 'decode failed.' >>> testuni(m) >>> m.encode('ascii') >>> a == m 1 """ warn('use of ``decode`` is deprecated.', DeprecationWarning) def decode(section, key, encoding=encoding, warn=True): """ """ val = section[key] if isinstance(val, (list, tuple)): newval = [] for entry in val: newval.append(entry.decode(encoding)) elif isinstance(val, dict): newval = val else: newval = val.decode(encoding) newkey = key.decode(encoding) section.rename(key, newkey) section[newkey] = newval # using ``call_on_sections`` allows us to modify section names self.walk(decode, call_on_sections=True) def encode(self, encoding): """ Encode all strings and values from unicode, using the specified encoding. Works with subsections and list values. Uses the ``walk`` method. """ warn('use of ``encode`` is deprecated.', DeprecationWarning) def encode(section, key, encoding=encoding): """ """ val = section[key] if isinstance(val, (list, tuple)): newval = [] for entry in val: newval.append(entry.encode(encoding)) elif isinstance(val, dict): newval = val else: newval = val.encode(encoding) newkey = key.encode(encoding) section.rename(key, newkey) section[newkey] = newval self.walk(encode, call_on_sections=True) def istrue(self, key): """A deprecated version of ``as_bool``.""" warn('use of ``istrue`` is deprecated. Use ``as_bool`` method ' 'instead.', DeprecationWarning) return self.as_bool(key) def as_bool(self, key): """ Accepts a key as input. The corresponding value must be a string or the objects (``True`` or 1) or (``False`` or 0). We allow 0 and 1 to retain compatibility with Python 2.2. If the string is one of ``True``, ``On``, ``Yes``, or ``1`` it returns ``True``. If the string is one of ``False``, ``Off``, ``No``, or ``0`` it returns ``False``. ``as_bool`` is not case sensitive. Any other input will raise a ``ValueError``. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_bool('a') Traceback (most recent call last): ValueError: Value "fish" is neither True nor False >>> a['b'] = 'True' >>> a.as_bool('b') 1 >>> a['b'] = 'off' >>> a.as_bool('b') 0 """ val = self[key] if val == True: return True elif val == False: return False else: try: if not isinstance(val, StringTypes): raise KeyError else: return self.main._bools[val.lower()] except KeyError: raise ValueError('Value "%s" is neither True nor False' % val) def as_int(self, key): """ A convenience method which coerces the specified value to an integer. If the value is an invalid literal for ``int``, a ``ValueError`` will be raised. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_int('a') Traceback (most recent call last): ValueError: invalid literal for int(): fish >>> a['b'] = '1' >>> a.as_int('b') 1 >>> a['b'] = '3.2' >>> a.as_int('b') Traceback (most recent call last): ValueError: invalid literal for int(): 3.2 """ return int(self[key]) def as_float(self, key): """ A convenience method which coerces the specified value to a float. If the value is an invalid literal for ``float``, a ``ValueError`` will be raised. >>> a = ConfigObj() >>> a['a'] = 'fish' >>> a.as_float('a') Traceback (most recent call last): ValueError: invalid literal for float(): fish >>> a['b'] = '1' >>> a.as_float('b') 1.0 >>> a['b'] = '3.2' >>> a.as_float('b') 3.2000000000000002 """ return float(self[key]) class ConfigObj(Section): """An object to read, create, and write config files.""" _keyword = re.compile(r'''^ # line start (\s*) # indentation ( # keyword (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'"=].*?) # no quotes ) \s*=\s* # divider (.*) # value (including list values and comments) $ # line end ''', re.VERBOSE) _sectionmarker = re.compile(r'''^ (\s*) # 1: indentation ((?:\[\s*)+) # 2: section marker open ( # 3: section name open (?:"\s*\S.*?\s*")| # at least one non-space with double quotes (?:'\s*\S.*?\s*')| # at least one non-space with single quotes (?:[^'"\s].*?) # at least one non-space unquoted ) # section name close ((?:\s*\])+) # 4: section marker close \s*(\#.*)? # 5: optional comment $''', re.VERBOSE) # this regexp pulls list values out as a single string # or single values and comments # FIXME: this regex adds a '' to the end of comma terminated lists # workaround in ``_handle_value`` _valueexp = re.compile(r'''^ (?: (?: ( (?: (?: (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\#][^,\#]*?) # unquoted ) \s*,\s* # comma )* # match all list items ending in a comma (if any) ) ( (?:".*?")| # double quotes (?:'.*?')| # single quotes (?:[^'",\#\s][^,]*?)| # unquoted (?:(? 1: msg = ("Parsing failed with several errors.\nFirst error %s" % info) error = ConfigObjError(msg) else: error = self._errors[0] # set the errors attribute; it's a list of tuples: # (error_type, message, line_number) error.errors = self._errors # set the config attribute error.config = self raise error # delete private attributes del self._errors # if defaults['configspec'] is None: self.configspec = None else: self._handle_configspec(defaults['configspec']) def __repr__(self): return 'ConfigObj({%s})' % ', '.join( [('%s: %s' % (repr(key), repr(self[key]))) for key in (self.scalars + self.sections)]) def _handle_bom(self, infile): """ Handle any BOM, and decode if necessary. If an encoding is specified, that *must* be used - but the BOM should still be removed (and the BOM attribute set). (If the encoding is wrongly specified, then a BOM for an alternative encoding won't be discovered or removed.) If an encoding is not specified, UTF8 or UTF16 BOM will be detected and removed. The BOM attribute will be set. UTF16 will be decoded to unicode. NOTE: This method must not be called with an empty ``infile``. Specifying the *wrong* encoding is likely to cause a ``UnicodeDecodeError``. ``infile`` must always be returned as a list of lines, but may be passed in as a single string. """ if ((self.encoding is not None) and (self.encoding.lower() not in BOM_LIST)): # No need to check for a BOM # the encoding specified doesn't have one # just decode return self._decode(infile, self.encoding) # if isinstance(infile, (list, tuple)): line = infile[0] else: line = infile if self.encoding is not None: # encoding explicitly supplied # And it could have an associated BOM # TODO: if encoding is just UTF16 - we ought to check for both # TODO: big endian and little endian versions. enc = BOM_LIST[self.encoding.lower()] if enc == 'utf_16': # For UTF16 we try big endian and little endian for BOM, (encoding, final_encoding) in BOMS.items(): if not final_encoding: # skip UTF8 continue if infile.startswith(BOM): ### BOM discovered ##self.BOM = True # Don't need to remove BOM return self._decode(infile, encoding) # # If we get this far, will *probably* raise a DecodeError # As it doesn't appear to start with a BOM return self._decode(infile, self.encoding) # # Must be UTF8 BOM = BOM_SET[enc] if not line.startswith(BOM): return self._decode(infile, self.encoding) # newline = line[len(BOM):] # # BOM removed if isinstance(infile, (list, tuple)): infile[0] = newline else: infile = newline self.BOM = True return self._decode(infile, self.encoding) # # No encoding specified - so we need to check for UTF8/UTF16 for BOM, (encoding, final_encoding) in BOMS.items(): if not line.startswith(BOM): continue else: # BOM discovered self.encoding = final_encoding if not final_encoding: self.BOM = True # UTF8 # remove BOM newline = line[len(BOM):] if isinstance(infile, (list, tuple)): infile[0] = newline else: infile = newline # UTF8 - don't decode if isinstance(infile, StringTypes): return infile.splitlines(True) else: return infile # UTF16 - have to decode return self._decode(infile, encoding) # # No BOM discovered and no encoding specified, just return if isinstance(infile, StringTypes): # infile read from a file will be a single string return infile.splitlines(True) else: return infile def _a_to_u(self, aString): """Decode ASCII strings to unicode if a self.encoding is specified.""" if self.encoding: return aString.decode('ascii') else: return aString def _decode(self, infile, encoding): """ Decode infile to unicode. Using the specified encoding. if is a string, it also needs converting to a list. """ if isinstance(infile, StringTypes): # can't be unicode # NOTE: Could raise a ``UnicodeDecodeError`` return infile.decode(encoding).splitlines(True) for i, line in enumerate(infile): if not isinstance(line, unicode): # NOTE: The isinstance test here handles mixed lists of unicode/string # NOTE: But the decode will break on any non-string values # NOTE: Or could raise a ``UnicodeDecodeError`` infile[i] = line.decode(encoding) return infile def _decode_element(self, line): """Decode element to unicode if necessary.""" if not self.encoding: return line if isinstance(line, str) and self.default_encoding: return line.decode(self.default_encoding) return line def _str(self, value): """ Used by ``stringify`` within validate, to turn non-string values into strings. """ if not isinstance(value, StringTypes): return str(value) else: return value def _parse(self, infile): """Actually parse the config file.""" temp_list_values = self.list_values if self.unrepr: self.list_values = False comment_list = [] done_start = False this_section = self maxline = len(infile) - 1 cur_index = -1 reset_comment = False while cur_index < maxline: if reset_comment: comment_list = [] cur_index += 1 line = infile[cur_index] sline = line.strip() # do we have anything on the line ? if not sline or sline.startswith('#'): reset_comment = False comment_list.append(line) continue if not done_start: # preserve initial comment self.initial_comment = comment_list comment_list = [] done_start = True reset_comment = True # first we check if it's a section marker mat = self._sectionmarker.match(line) if mat is not None: # is a section line (indent, sect_open, sect_name, sect_close, comment) = ( mat.groups()) if indent and (self.indent_type is None): self.indent_type = indent cur_depth = sect_open.count('[') if cur_depth != sect_close.count(']'): self._handle_error( "Cannot compute the section depth at line %s.", NestingError, infile, cur_index) continue # if cur_depth < this_section.depth: # the new section is dropping back to a previous level try: parent = self._match_depth( this_section, cur_depth).parent except SyntaxError: self._handle_error( "Cannot compute nesting level at line %s.", NestingError, infile, cur_index) continue elif cur_depth == this_section.depth: # the new section is a sibling of the current section parent = this_section.parent elif cur_depth == this_section.depth + 1: # the new section is a child the current section parent = this_section else: self._handle_error( "Section too nested at line %s.", NestingError, infile, cur_index) # sect_name = self._unquote(sect_name) if parent.has_key(sect_name): self._handle_error( 'Duplicate section name at line %s.', DuplicateError, infile, cur_index) continue # create the new section this_section = Section( parent, cur_depth, self, name=sect_name) parent[sect_name] = this_section parent.inline_comments[sect_name] = comment parent.comments[sect_name] = comment_list continue # # it's not a section marker, # so it should be a valid ``key = value`` line mat = self._keyword.match(line) if mat is None: # it neither matched as a keyword # or a section marker self._handle_error( 'Invalid line at line "%s".', ParseError, infile, cur_index) else: # is a keyword value # value will include any inline comment (indent, key, value) = mat.groups() if indent and (self.indent_type is None): self.indent_type = indent # check for a multiline value if value[:3] in ['"""', "'''"]: try: (value, comment, cur_index) = self._multiline( value, infile, cur_index, maxline) except SyntaxError: self._handle_error( 'Parse error in value at line %s.', ParseError, infile, cur_index) continue else: if self.unrepr: comment = '' try: value = unrepr(value) except Exception, e: if type(e) == UnknownType: msg = 'Unknown name or type in value at line %s.' else: msg = 'Parse error in value at line %s.' self._handle_error(msg, UnreprError, infile, cur_index) continue else: if self.unrepr: comment = '' try: value = unrepr(value) except Exception, e: if isinstance(e, UnknownType): msg = 'Unknown name or type in value at line %s.' else: msg = 'Parse error in value at line %s.' self._handle_error(msg, UnreprError, infile, cur_index) continue else: # extract comment and lists try: (value, comment) = self._handle_value(value) except SyntaxError: self._handle_error( 'Parse error in value at line %s.', ParseError, infile, cur_index) continue # key = self._unquote(key) if this_section.has_key(key): self._handle_error( 'Duplicate keyword name at line %s.', DuplicateError, infile, cur_index) continue # add the key. # we set unrepr because if we have got this far we will never # be creating a new section this_section.__setitem__(key, value, unrepr=True) this_section.inline_comments[key] = comment this_section.comments[key] = comment_list continue # if self.indent_type is None: # no indentation used, set the type accordingly self.indent_type = '' # if self._terminated: comment_list.append('') # preserve the final comment if not self and not self.initial_comment: self.initial_comment = comment_list elif not reset_comment: self.final_comment = comment_list self.list_values = temp_list_values def _match_depth(self, sect, depth): """ Given a section and a depth level, walk back through the sections parents to see if the depth level matches a previous section. Return a reference to the right section, or raise a SyntaxError. """ while depth < sect.depth: if sect is sect.parent: # we've reached the top level already raise SyntaxError sect = sect.parent if sect.depth == depth: return sect # shouldn't get here raise SyntaxError def _handle_error(self, text, ErrorClass, infile, cur_index): """ Handle an error according to the error settings. Either raise the error or store it. The error will have occured at ``cur_index`` """ line = infile[cur_index] cur_index += 1 message = text % cur_index error = ErrorClass(message, cur_index, line) if self.raise_errors: # raise the error - parsing stops here raise error # store the error # reraise when parsing has finished self._errors.append(error) def _unquote(self, value): """Return an unquoted version of a value""" if (value[0] == value[-1]) and (value[0] in ('"', "'")): value = value[1:-1] return value def _quote(self, value, multiline=True): """ Return a safely quoted version of a value. Raise a ConfigObjError if the value cannot be safely quoted. If multiline is ``True`` (default) then use triple quotes if necessary. Don't quote values that don't need it. Recursively quote members of a list and return a comma joined list. Multiline is ``False`` for lists. Obey list syntax for empty and single member lists. If ``list_values=False`` then the value is only quoted if it contains a ``\n`` (is multiline). If ``write_empty_values`` is set, and the value is an empty string, it won't be quoted. """ if multiline and self.write_empty_values and value == '': # Only if multiline is set, so that it is used for values not # keys, and not values that are part of a list return '' if multiline and isinstance(value, (list, tuple)): if not value: return ',' elif len(value) == 1: return self._quote(value[0], multiline=False) + ',' return ', '.join([self._quote(val, multiline=False) for val in value]) if not isinstance(value, StringTypes): if self.stringify: value = str(value) else: raise TypeError, 'Value "%s" is not a string.' % value squot = "'%s'" dquot = '"%s"' noquot = "%s" wspace_plus = ' \r\t\n\v\t\'"' tsquot = '"""%s"""' tdquot = "'''%s'''" if not value: return '""' if (not self.list_values and '\n' not in value) or not (multiline and ((("'" in value) and ('"' in value)) or ('\n' in value))): if not self.list_values: # we don't quote if ``list_values=False`` quot = noquot # for normal values either single or double quotes will do elif '\n' in value: # will only happen if multiline is off - e.g. '\n' in key raise ConfigObjError, ('Value "%s" cannot be safely quoted.' % value) elif ((value[0] not in wspace_plus) and (value[-1] not in wspace_plus) and (',' not in value)): quot = noquot else: if ("'" in value) and ('"' in value): raise ConfigObjError, ( 'Value "%s" cannot be safely quoted.' % value) elif '"' in value: quot = squot else: quot = dquot else: # if value has '\n' or "'" *and* '"', it will need triple quotes if (value.find('"""') != -1) and (value.find("'''") != -1): raise ConfigObjError, ( 'Value "%s" cannot be safely quoted.' % value) if value.find('"""') == -1: quot = tdquot else: quot = tsquot return quot % value def _handle_value(self, value): """ Given a value string, unquote, remove comment, handle lists. (including empty and single member lists) """ # do we look for lists in values ? if not self.list_values: mat = self._nolistvalue.match(value) if mat is None: raise SyntaxError # NOTE: we don't unquote here return mat.groups() # mat = self._valueexp.match(value) if mat is None: # the value is badly constructed, probably badly quoted, # or an invalid list raise SyntaxError (list_values, single, empty_list, comment) = mat.groups() if (list_values == '') and (single is None): # change this if you want to accept empty values raise SyntaxError # NOTE: note there is no error handling from here if the regex # is wrong: then incorrect values will slip through if empty_list is not None: # the single comma - meaning an empty list return ([], comment) if single is not None: # handle empty values if list_values and not single: # FIXME: the '' is a workaround because our regex now matches # '' at the end of a list if it has a trailing comma single = None else: single = single or '""' single = self._unquote(single) if list_values == '': # not a list value return (single, comment) the_list = self._listvalueexp.findall(list_values) the_list = [self._unquote(val) for val in the_list] if single is not None: the_list += [single] return (the_list, comment) def _multiline(self, value, infile, cur_index, maxline): """Extract the value, where we are in a multiline situation.""" quot = value[:3] newvalue = value[3:] single_line = self._triple_quote[quot][0] multi_line = self._triple_quote[quot][1] mat = single_line.match(value) if mat is not None: retval = list(mat.groups()) retval.append(cur_index) return retval elif newvalue.find(quot) != -1: # somehow the triple quote is missing raise SyntaxError # while cur_index < maxline: cur_index += 1 newvalue += '\n' line = infile[cur_index] if line.find(quot) == -1: newvalue += line else: # end of multiline, process it break else: # we've got to the end of the config, oops... raise SyntaxError mat = multi_line.match(line) if mat is None: # a badly formed line raise SyntaxError (value, comment) = mat.groups() return (newvalue + value, comment, cur_index) def _handle_configspec(self, configspec): """Parse the configspec.""" # FIXME: Should we check that the configspec was created with the # correct settings ? (i.e. ``list_values=False``) if not isinstance(configspec, ConfigObj): try: configspec = ConfigObj( configspec, raise_errors=True, file_error=True, list_values=False) except ConfigObjError, e: # FIXME: Should these errors have a reference # to the already parsed ConfigObj ? raise ConfigspecError('Parsing configspec failed: %s' % e) except IOError, e: raise IOError('Reading configspec failed: %s' % e) self._set_configspec_value(configspec, self) def _set_configspec_value(self, configspec, section): """Used to recursively set configspec values.""" if '__many__' in configspec.sections: section.configspec['__many__'] = configspec['__many__'] if len(configspec.sections) > 1: # FIXME: can we supply any useful information here ? raise RepeatSectionError if hasattr(configspec, 'initial_comment'): section._configspec_initial_comment = configspec.initial_comment section._configspec_final_comment = configspec.final_comment section._configspec_encoding = configspec.encoding section._configspec_BOM = configspec.BOM section._configspec_newlines = configspec.newlines section._configspec_indent_type = configspec.indent_type for entry in configspec.scalars: section._configspec_comments[entry] = configspec.comments[entry] section._configspec_inline_comments[entry] = ( configspec.inline_comments[entry]) section.configspec[entry] = configspec[entry] section._order.append(entry) for entry in configspec.sections: if entry == '__many__': continue section._cs_section_comments[entry] = configspec.comments[entry] section._cs_section_inline_comments[entry] = ( configspec.inline_comments[entry]) if not section.has_key(entry): section[entry] = {} self._set_configspec_value(configspec[entry], section[entry]) def _handle_repeat(self, section, configspec): """Dynamically assign configspec for repeated section.""" try: section_keys = configspec.sections scalar_keys = configspec.scalars except AttributeError: section_keys = [entry for entry in configspec if isinstance(configspec[entry], dict)] scalar_keys = [entry for entry in configspec if not isinstance(configspec[entry], dict)] if '__many__' in section_keys and len(section_keys) > 1: # FIXME: can we supply any useful information here ? raise RepeatSectionError scalars = {} sections = {} for entry in scalar_keys: val = configspec[entry] scalars[entry] = val for entry in section_keys: val = configspec[entry] if entry == '__many__': scalars[entry] = val continue sections[entry] = val # section.configspec = scalars for entry in sections: if not section.has_key(entry): section[entry] = {} self._handle_repeat(section[entry], sections[entry]) def _write_line(self, indent_string, entry, this_entry, comment): """Write an individual line, for the write method""" # NOTE: the calls to self._quote here handles non-StringType values. if not self.unrepr: val = self._decode_element(self._quote(this_entry)) else: val = repr(this_entry) return '%s%s%s%s%s' % ( indent_string, self._decode_element(self._quote(entry, multiline=False)), self._a_to_u(' = '), val, self._decode_element(comment)) def _write_marker(self, indent_string, depth, entry, comment): """Write a section marker line""" return '%s%s%s%s%s' % ( indent_string, self._a_to_u('[' * depth), self._quote(self._decode_element(entry), multiline=False), self._a_to_u(']' * depth), self._decode_element(comment)) def _handle_comment(self, comment): """Deal with a comment.""" if not comment: return '' start = self.indent_type if not comment.startswith('#'): start += self._a_to_u(' # ') return (start + comment) # Public methods def write(self, outfile=None, section=None): """ Write the current ConfigObj as a file tekNico: FIXME: use StringIO instead of real files >>> filename = a.filename >>> a.filename = 'test.ini' >>> a.write() >>> a.filename = filename >>> a == ConfigObj('test.ini', raise_errors=True) 1 """ if self.indent_type is None: # this can be true if initialised from a dictionary self.indent_type = DEFAULT_INDENT_TYPE # out = [] cs = self._a_to_u('#') csp = self._a_to_u('# ') if section is None: int_val = self.interpolation self.interpolation = False section = self for line in self.initial_comment: line = self._decode_element(line) stripped_line = line.strip() if stripped_line and not stripped_line.startswith(cs): line = csp + line out.append(line) # indent_string = self.indent_type * section.depth for entry in (section.scalars + section.sections): if entry in section.defaults: # don't write out default values continue for comment_line in section.comments[entry]: comment_line = self._decode_element(comment_line.lstrip()) if comment_line and not comment_line.startswith(cs): comment_line = csp + comment_line out.append(indent_string + comment_line) this_entry = section[entry] comment = self._handle_comment(section.inline_comments[entry]) # if isinstance(this_entry, dict): # a section out.append(self._write_marker( indent_string, this_entry.depth, entry, comment)) out.extend(self.write(section=this_entry)) else: out.append(self._write_line( indent_string, entry, this_entry, comment)) # if section is self: for line in self.final_comment: line = self._decode_element(line) stripped_line = line.strip() if stripped_line and not stripped_line.startswith(cs): line = csp + line out.append(line) self.interpolation = int_val # if section is not self: return out # if (self.filename is None) and (outfile is None): # output a list of lines # might need to encode # NOTE: This will *screw* UTF16, each line will start with the BOM if self.encoding: out = [l.encode(self.encoding) for l in out] if (self.BOM and ((self.encoding is None) or (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): # Add the UTF8 BOM if not out: out.append('') out[0] = BOM_UTF8 + out[0] return out # # Turn the list to a string, joined with correct newlines output = (self._a_to_u(self.newlines or os.linesep) ).join(out) if self.encoding: output = output.encode(self.encoding) if (self.BOM and ((self.encoding is None) or (BOM_LIST.get(self.encoding.lower()) == 'utf_8'))): # Add the UTF8 BOM output = BOM_UTF8 + output if outfile is not None: outfile.write(output) else: h = open(self.filename, 'wb') h.write(output) h.close() def validate(self, validator, preserve_errors=False, copy=False, section=None): """ Test the ConfigObj against a configspec. It uses the ``validator`` object from *validate.py*. To run ``validate`` on the current ConfigObj, call: :: test = config.validate(validator) (Normally having previously passed in the configspec when the ConfigObj was created - you can dynamically assign a dictionary of checks to the ``configspec`` attribute of a section though). It returns ``True`` if everything passes, or a dictionary of pass/fails (True/False). If every member of a subsection passes, it will just have the value ``True``. (It also returns ``False`` if all members fail). In addition, it converts the values from strings to their native types if their checks pass (and ``stringify`` is set). If ``preserve_errors`` is ``True`` (``False`` is default) then instead of a marking a fail with a ``False``, it will preserve the actual exception object. This can contain info about the reason for failure. For example the ``VdtValueTooSmallError`` indeicates that the value supplied was too small. If a value (or section) is missing it will still be marked as ``False``. You must have the validate module to use ``preserve_errors=True``. You can then use the ``flatten_errors`` function to turn your nested results dictionary into a flattened list of failures - useful for displaying meaningful error messages. """ if section is None: if self.configspec is None: raise ValueError, 'No configspec supplied.' if preserve_errors: if VdtMissingValue is None: raise ImportError('Missing validate module.') section = self # spec_section = section.configspec if copy and hasattr(section, '_configspec_initial_comment'): section.initial_comment = section._configspec_initial_comment section.final_comment = section._configspec_final_comment section.encoding = section._configspec_encoding section.BOM = section._configspec_BOM section.newlines = section._configspec_newlines section.indent_type = section._configspec_indent_type if '__many__' in section.configspec: many = spec_section['__many__'] # dynamically assign the configspecs # for the sections below for entry in section.sections: self._handle_repeat(section[entry], many) # out = {} ret_true = True ret_false = True order = [k for k in section._order if k in spec_section] order += [k for k in spec_section if k not in order] for entry in order: if entry == '__many__': continue if (not entry in section.scalars) or (entry in section.defaults): # missing entries # or entries from defaults missing = True val = None if copy and not entry in section.scalars: # copy comments section.comments[entry] = ( section._configspec_comments.get(entry, [])) section.inline_comments[entry] = ( section._configspec_inline_comments.get(entry, '')) # else: missing = False val = section[entry] try: check = validator.check(spec_section[entry], val, missing=missing ) except validator.baseErrorClass, e: if not preserve_errors or isinstance(e, VdtMissingValue): out[entry] = False else: # preserve the error out[entry] = e ret_false = False ret_true = False else: ret_false = False out[entry] = True if self.stringify or missing: # if we are doing type conversion # or the value is a supplied default if not self.stringify: if isinstance(check, (list, tuple)): # preserve lists check = [self._str(item) for item in check] elif missing and check is None: # convert the None from a default to a '' check = '' else: check = self._str(check) if (check != val) or missing: section[entry] = check if not copy and missing and entry not in section.defaults: section.defaults.append(entry) # # Missing sections will have been created as empty ones when the # configspec was read. for entry in section.sections: # FIXME: this means DEFAULT is not copied in copy mode if section is self and entry == 'DEFAULT': continue if copy: section.comments[entry] = section._cs_section_comments[entry] section.inline_comments[entry] = ( section._cs_section_inline_comments[entry]) check = self.validate(validator, preserve_errors=preserve_errors, copy=copy, section=section[entry]) out[entry] = check if check == False: ret_true = False elif check == True: ret_false = False else: ret_true = False ret_false = False # if ret_true: return True elif ret_false: return False else: return out class SimpleVal(object): """ A simple validator. Can be used to check that all members expected are present. To use it, provide a configspec with all your members in (the value given will be ignored). Pass an instance of ``SimpleVal`` to the ``validate`` method of your ``ConfigObj``. ``validate`` will return ``True`` if all members are present, or a dictionary with True/False meaning present/missing. (Whole missing sections will be replaced with ``False``) """ def __init__(self): self.baseErrorClass = ConfigObjError def check(self, check, member, missing=False): """A dummy check method, always returns the value unchanged.""" if missing: raise self.baseErrorClass return member # Check / processing functions for options def flatten_errors(cfg, res, levels=None, results=None): """ An example function that will turn a nested dictionary of results (as returned by ``ConfigObj.validate``) into a flat list. ``cfg`` is the ConfigObj instance being checked, ``res`` is the results dictionary returned by ``validate``. (This is a recursive function, so you shouldn't use the ``levels`` or ``results`` arguments - they are used by the function. Returns a list of keys that failed. Each member of the list is a tuple : :: ([list of sections...], key, result) If ``validate`` was called with ``preserve_errors=False`` (the default) then ``result`` will always be ``False``. *list of sections* is a flattened list of sections that the key was found in. If the section was missing then key will be ``None``. If the value (or section) was missing then ``result`` will be ``False``. If ``validate`` was called with ``preserve_errors=True`` and a value was present, but failed the check, then ``result`` will be the exception object returned. You can use this as a string that describes the failure. For example *The value "3" is of the wrong type*. >>> import validate >>> vtor = validate.Validator() >>> my_ini = ''' ... option1 = True ... [section1] ... option1 = True ... [section2] ... another_option = Probably ... [section3] ... another_option = True ... [[section3b]] ... value = 3 ... value2 = a ... value3 = 11 ... ''' >>> my_cfg = ''' ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section1] ... option1 = boolean() ... option2 = boolean() ... option3 = boolean(default=Bad_value) ... [section2] ... another_option = boolean() ... [section3] ... another_option = boolean() ... [[section3b]] ... value = integer ... value2 = integer ... value3 = integer(0, 10) ... [[[section3b-sub]]] ... value = string ... [section4] ... another_option = boolean() ... ''' >>> cs = my_cfg.split('\\n') >>> ini = my_ini.split('\\n') >>> cfg = ConfigObj(ini, configspec=cs) >>> res = cfg.validate(vtor, preserve_errors=True) >>> errors = [] >>> for entry in flatten_errors(cfg, res): ... section_list, key, error = entry ... section_list.insert(0, '[root]') ... if key is not None: ... section_list.append(key) ... else: ... section_list.append('[missing]') ... section_string = ', '.join(section_list) ... errors.append((section_string, ' = ', error)) >>> errors.sort() >>> for entry in errors: ... print entry[0], entry[1], (entry[2] or 0) [root], option2 = 0 [root], option3 = the value "Bad_value" is of the wrong type. [root], section1, option2 = 0 [root], section1, option3 = the value "Bad_value" is of the wrong type. [root], section2, another_option = the value "Probably" is of the wrong type. [root], section3, section3b, section3b-sub, [missing] = 0 [root], section3, section3b, value2 = the value "a" is of the wrong type. [root], section3, section3b, value3 = the value "11" is too big. [root], section4, [missing] = 0 """ if levels is None: # first time called levels = [] results = [] if res is True: return results if res is False: results.append((levels[:], None, False)) if levels: levels.pop() return results for (key, val) in res.items(): if val == True: continue if isinstance(cfg.get(key), dict): # Go down one level levels.append(key) flatten_errors(cfg[key], val, levels, results) continue results.append((levels[:], key, val)) # # Go up one level if levels: levels.pop() # return results """*A programming language is a medium of expression.* - Paul Graham""" PIDA-0.5.1/pida/utils/feedparser.py0000755000175000017500000036006510652670605015121 0ustar aliali#!/usr/bin/env python """Universal feed parser Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds Visit http://feedparser.org/ for the latest version Visit http://feedparser.org/docs/ for the latest documentation Required: Python 2.1 or later Recommended: Python 2.3 or later Recommended: CJKCodecs and iconv_codec """ __version__ = "4.1"# + "$Revision: 1.92 $"[11:15] + "-cvs" __license__ = """Copyright (c) 2002-2006, Mark Pilgrim, 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. 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.""" __author__ = "Mark Pilgrim " __contributors__ = ["Jason Diamond ", "John Beimler ", "Fazal Majid ", "Aaron Swartz ", "Kevin Marks "] _debug = 0 # HTTP "User-Agent" header to send to servers when downloading feeds. # If you are embedding feedparser in a larger application, you should # change this to your application name and URL. USER_AGENT = "UniversalFeedParser/%s +http://feedparser.org/" % __version__ # HTTP "Accept" header to send to servers when downloading feeds. If you don't # want to send an Accept header, set this to None. ACCEPT_HEADER = "application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1" # List of preferred XML parsers, by SAX driver name. These will be tried first, # but if they're not installed, Python will keep searching through its own list # of pre-installed parsers until it finds one that supports everything we need. PREFERRED_XML_PARSERS = ["drv_libxml2"] # If you want feedparser to automatically run HTML markup through HTML Tidy, set # this to 1. Requires mxTidy # or utidylib . TIDY_MARKUP = 0 # List of Python interfaces for HTML Tidy, in order of preference. Only useful # if TIDY_MARKUP = 1 PREFERRED_TIDY_INTERFACES = ["uTidy", "mxTidy"] # ---------- required modules (should come with any Python distribution) ---------- import sgmllib, re, sys, copy, urlparse, time, rfc822, types, cgi, urllib, urllib2 try: from cStringIO import StringIO as _StringIO except: from StringIO import StringIO as _StringIO # ---------- optional modules (feedparser will work without these, but with reduced functionality) ---------- # gzip is included with most Python distributions, but may not be available if you compiled your own try: import gzip except: gzip = None try: import zlib except: zlib = None # If a real XML parser is available, feedparser will attempt to use it. feedparser has # been tested with the built-in SAX parser, PyXML, and libxml2. On platforms where the # Python distribution does not come with an XML parser (such as Mac OS X 10.2 and some # versions of FreeBSD), feedparser will quietly fall back on regex-based parsing. try: import xml.sax xml.sax.make_parser(PREFERRED_XML_PARSERS) # test for valid parsers from xml.sax.saxutils import escape as _xmlescape _XML_AVAILABLE = 1 except: _XML_AVAILABLE = 0 def _xmlescape(data): data = data.replace('&', '&') data = data.replace('>', '>') data = data.replace('<', '<') return data # base64 support for Atom feeds that contain embedded binary data try: import base64, binascii except: base64 = binascii = None # cjkcodecs and iconv_codec provide support for more character encodings. # Both are available from http://cjkpython.i18n.org/ try: import cjkcodecs.aliases except: pass try: import iconv_codec except: pass # chardet library auto-detects character encodings # Download from http://chardet.feedparser.org/ try: import chardet if _debug: import chardet.constants chardet.constants._debug = 1 except: chardet = None # ---------- don't touch these ---------- class ThingsNobodyCaresAboutButMe(Exception): pass class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe): pass class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe): pass class NonXMLContentType(ThingsNobodyCaresAboutButMe): pass class UndeclaredNamespace(Exception): pass sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*') sgmllib.special = re.compile('' % (tag, ''.join([' %s="%s"' % t for t in attrs])), escape=0) # match namespaces if tag.find(':') <> -1: prefix, suffix = tag.split(':', 1) else: prefix, suffix = '', tag prefix = self.namespacemap.get(prefix, prefix) if prefix: prefix = prefix + '_' # special hack for better tracking of empty textinput/image elements in illformed feeds if (not prefix) and tag not in ('title', 'link', 'description', 'name'): self.intextinput = 0 if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'): self.inimage = 0 # call special handler (if defined) or default handler methodname = '_start_' + prefix + suffix try: method = getattr(self, methodname) return method(attrsD) except AttributeError: return self.push(prefix + suffix, 1) def unknown_endtag(self, tag): if _debug: sys.stderr.write('end %s\n' % tag) # match namespaces if tag.find(':') <> -1: prefix, suffix = tag.split(':', 1) else: prefix, suffix = '', tag prefix = self.namespacemap.get(prefix, prefix) if prefix: prefix = prefix + '_' # call special handler (if defined) or default handler methodname = '_end_' + prefix + suffix try: method = getattr(self, methodname) method() except AttributeError: self.pop(prefix + suffix) # track inline content if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'): # element declared itself as escaped markup, but it isn't really self.contentparams['type'] = 'application/xhtml+xml' if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml': tag = tag.split(':')[-1] self.handle_data('' % tag, escape=0) # track xml:base and xml:lang going out of scope if self.basestack: self.basestack.pop() if self.basestack and self.basestack[-1]: self.baseuri = self.basestack[-1] if self.langstack: self.langstack.pop() if self.langstack: # and (self.langstack[-1] is not None): self.lang = self.langstack[-1] def handle_charref(self, ref): # called for each character reference, e.g. for ' ', ref will be '160' if not self.elementstack: return ref = ref.lower() if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'): text = '&#%s;' % ref else: if ref[0] == 'x': c = int(ref[1:], 16) else: c = int(ref) text = unichr(c).encode('utf-8') self.elementstack[-1][2].append(text) def handle_entityref(self, ref): # called for each entity reference, e.g. for '©', ref will be 'copy' if not self.elementstack: return if _debug: sys.stderr.write('entering handle_entityref with %s\n' % ref) if ref in ('lt', 'gt', 'quot', 'amp', 'apos'): text = '&%s;' % ref else: # entity resolution graciously donated by Aaron Swartz def name2cp(k): import htmlentitydefs if hasattr(htmlentitydefs, 'name2codepoint'): # requires Python 2.3 return htmlentitydefs.name2codepoint[k] k = htmlentitydefs.entitydefs[k] if k.startswith('&#') and k.endswith(';'): return int(k[2:-1]) # not in latin-1 return ord(k) try: name2cp(ref) except KeyError: text = '&%s;' % ref else: text = unichr(name2cp(ref)).encode('utf-8') self.elementstack[-1][2].append(text) def handle_data(self, text, escape=1): # called for each block of plain text, i.e. outside of any tag and # not containing any character or entity references if not self.elementstack: return if escape and self.contentparams.get('type') == 'application/xhtml+xml': text = _xmlescape(text) self.elementstack[-1][2].append(text) def handle_comment(self, text): # called for each comment, e.g. pass def handle_pi(self, text): # called for each processing instruction, e.g. pass def handle_decl(self, text): pass def parse_declaration(self, i): # override internal declaration handler to handle CDATA blocks if _debug: sys.stderr.write('entering parse_declaration\n') if self.rawdata[i:i+9] == '', i) if k == -1: k = len(self.rawdata) self.handle_data(_xmlescape(self.rawdata[i+9:k]), 0) return k+3 else: k = self.rawdata.find('>', i) return k+1 def mapContentType(self, contentType): contentType = contentType.lower() if contentType == 'text': contentType = 'text/plain' elif contentType == 'html': contentType = 'text/html' elif contentType == 'xhtml': contentType = 'application/xhtml+xml' return contentType def trackNamespace(self, prefix, uri): loweruri = uri.lower() if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not self.version: self.version = 'rss090' if loweruri == 'http://purl.org/rss/1.0/' and not self.version: self.version = 'rss10' if loweruri == 'http://www.w3.org/2005/atom' and not self.version: self.version = 'atom10' if loweruri.find('backend.userland.com/rss') <> -1: # match any backend.userland.com namespace uri = 'http://backend.userland.com/rss' loweruri = uri if self._matchnamespaces.has_key(loweruri): self.namespacemap[prefix] = self._matchnamespaces[loweruri] self.namespacesInUse[self._matchnamespaces[loweruri]] = uri else: self.namespacesInUse[prefix or ''] = uri def resolveURI(self, uri): return _urljoin(self.baseuri or '', uri) def decodeEntities(self, element, data): return data def push(self, element, expectingText): self.elementstack.append([element, expectingText, []]) def pop(self, element, stripWhitespace=1): if not self.elementstack: return if self.elementstack[-1][0] != element: return element, expectingText, pieces = self.elementstack.pop() output = ''.join(pieces) if stripWhitespace: output = output.strip() if not expectingText: return output # decode base64 content if base64 and self.contentparams.get('base64', 0): try: output = base64.decodestring(output) except binascii.Error: pass except binascii.Incomplete: pass # resolve relative URIs if (element in self.can_be_relative_uri) and output: output = self.resolveURI(output) # decode entities within embedded markup if not self.contentparams.get('base64', 0): output = self.decodeEntities(element, output) # remove temporary cruft from contentparams try: del self.contentparams['mode'] except KeyError: pass try: del self.contentparams['base64'] except KeyError: pass # resolve relative URIs within embedded markup if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types: if element in self.can_contain_relative_uris: output = _resolveRelativeURIs(output, self.baseuri, self.encoding) # sanitize embedded markup if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types: if element in self.can_contain_dangerous_markup: output = _sanitizeHTML(output, self.encoding) if self.encoding and type(output) != type(u''): try: output = unicode(output, self.encoding) except: pass # categories/tags/keywords/whatever are handled in _end_category if element == 'category': return output # store output in appropriate place(s) if self.inentry and not self.insource: if element == 'content': self.entries[-1].setdefault(element, []) contentparams = copy.deepcopy(self.contentparams) contentparams['value'] = output self.entries[-1][element].append(contentparams) elif element == 'link': self.entries[-1][element] = output if output: self.entries[-1]['links'][-1]['href'] = output else: if element == 'description': element = 'summary' self.entries[-1][element] = output if self.incontent: contentparams = copy.deepcopy(self.contentparams) contentparams['value'] = output self.entries[-1][element + '_detail'] = contentparams elif (self.infeed or self.insource) and (not self.intextinput) and (not self.inimage): context = self._getContext() if element == 'description': element = 'subtitle' context[element] = output if element == 'link': context['links'][-1]['href'] = output elif self.incontent: contentparams = copy.deepcopy(self.contentparams) contentparams['value'] = output context[element + '_detail'] = contentparams return output def pushContent(self, tag, attrsD, defaultContentType, expectingText): self.incontent += 1 self.contentparams = FeedParserDict({ 'type': self.mapContentType(attrsD.get('type', defaultContentType)), 'language': self.lang, 'base': self.baseuri}) self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams) self.push(tag, expectingText) def popContent(self, tag): value = self.pop(tag) self.incontent -= 1 self.contentparams.clear() return value def _mapToStandardPrefix(self, name): colonpos = name.find(':') if colonpos <> -1: prefix = name[:colonpos] suffix = name[colonpos+1:] prefix = self.namespacemap.get(prefix, prefix) name = prefix + ':' + suffix return name def _getAttribute(self, attrsD, name): return attrsD.get(self._mapToStandardPrefix(name)) def _isBase64(self, attrsD, contentparams): if attrsD.get('mode', '') == 'base64': return 1 if self.contentparams['type'].startswith('text/'): return 0 if self.contentparams['type'].endswith('+xml'): return 0 if self.contentparams['type'].endswith('/xml'): return 0 return 1 def _itsAnHrefDamnIt(self, attrsD): href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None))) if href: try: del attrsD['url'] except KeyError: pass try: del attrsD['uri'] except KeyError: pass attrsD['href'] = href return attrsD def _save(self, key, value): context = self._getContext() context.setdefault(key, value) def _start_rss(self, attrsD): versionmap = {'0.91': 'rss091u', '0.92': 'rss092', '0.93': 'rss093', '0.94': 'rss094'} if not self.version: attr_version = attrsD.get('version', '') version = versionmap.get(attr_version) if version: self.version = version elif attr_version.startswith('2.'): self.version = 'rss20' else: self.version = 'rss' def _start_dlhottitles(self, attrsD): self.version = 'hotrss' def _start_channel(self, attrsD): self.infeed = 1 self._cdf_common(attrsD) _start_feedinfo = _start_channel def _cdf_common(self, attrsD): if attrsD.has_key('lastmod'): self._start_modified({}) self.elementstack[-1][-1] = attrsD['lastmod'] self._end_modified() if attrsD.has_key('href'): self._start_link({}) self.elementstack[-1][-1] = attrsD['href'] self._end_link() def _start_feed(self, attrsD): self.infeed = 1 versionmap = {'0.1': 'atom01', '0.2': 'atom02', '0.3': 'atom03'} if not self.version: attr_version = attrsD.get('version') version = versionmap.get(attr_version) if version: self.version = version else: self.version = 'atom' def _end_channel(self): self.infeed = 0 _end_feed = _end_channel def _start_image(self, attrsD): self.inimage = 1 self.push('image', 0) context = self._getContext() context.setdefault('image', FeedParserDict()) def _end_image(self): self.pop('image') self.inimage = 0 def _start_textinput(self, attrsD): self.intextinput = 1 self.push('textinput', 0) context = self._getContext() context.setdefault('textinput', FeedParserDict()) _start_textInput = _start_textinput def _end_textinput(self): self.pop('textinput') self.intextinput = 0 _end_textInput = _end_textinput def _start_author(self, attrsD): self.inauthor = 1 self.push('author', 1) _start_managingeditor = _start_author _start_dc_author = _start_author _start_dc_creator = _start_author _start_itunes_author = _start_author def _end_author(self): self.pop('author') self.inauthor = 0 self._sync_author_detail() _end_managingeditor = _end_author _end_dc_author = _end_author _end_dc_creator = _end_author _end_itunes_author = _end_author def _start_itunes_owner(self, attrsD): self.inpublisher = 1 self.push('publisher', 0) def _end_itunes_owner(self): self.pop('publisher') self.inpublisher = 0 self._sync_author_detail('publisher') def _start_contributor(self, attrsD): self.incontributor = 1 context = self._getContext() context.setdefault('contributors', []) context['contributors'].append(FeedParserDict()) self.push('contributor', 0) def _end_contributor(self): self.pop('contributor') self.incontributor = 0 def _start_dc_contributor(self, attrsD): self.incontributor = 1 context = self._getContext() context.setdefault('contributors', []) context['contributors'].append(FeedParserDict()) self.push('name', 0) def _end_dc_contributor(self): self._end_name() self.incontributor = 0 def _start_name(self, attrsD): self.push('name', 0) _start_itunes_name = _start_name def _end_name(self): value = self.pop('name') if self.inpublisher: self._save_author('name', value, 'publisher') elif self.inauthor: self._save_author('name', value) elif self.incontributor: self._save_contributor('name', value) elif self.intextinput: context = self._getContext() context['textinput']['name'] = value _end_itunes_name = _end_name def _start_width(self, attrsD): self.push('width', 0) def _end_width(self): value = self.pop('width') try: value = int(value) except: value = 0 if self.inimage: context = self._getContext() context['image']['width'] = value def _start_height(self, attrsD): self.push('height', 0) def _end_height(self): value = self.pop('height') try: value = int(value) except: value = 0 if self.inimage: context = self._getContext() context['image']['height'] = value def _start_url(self, attrsD): self.push('href', 1) _start_homepage = _start_url _start_uri = _start_url def _end_url(self): value = self.pop('href') if self.inauthor: self._save_author('href', value) elif self.incontributor: self._save_contributor('href', value) elif self.inimage: context = self._getContext() context['image']['href'] = value elif self.intextinput: context = self._getContext() context['textinput']['link'] = value _end_homepage = _end_url _end_uri = _end_url def _start_email(self, attrsD): self.push('email', 0) _start_itunes_email = _start_email def _end_email(self): value = self.pop('email') if self.inpublisher: self._save_author('email', value, 'publisher') elif self.inauthor: self._save_author('email', value) elif self.incontributor: self._save_contributor('email', value) _end_itunes_email = _end_email def _getContext(self): if self.insource: context = self.sourcedata elif self.inentry: context = self.entries[-1] else: context = self.feeddata return context def _save_author(self, key, value, prefix='author'): context = self._getContext() context.setdefault(prefix + '_detail', FeedParserDict()) context[prefix + '_detail'][key] = value self._sync_author_detail() def _save_contributor(self, key, value): context = self._getContext() context.setdefault('contributors', [FeedParserDict()]) context['contributors'][-1][key] = value def _sync_author_detail(self, key='author'): context = self._getContext() detail = context.get('%s_detail' % key) if detail: name = detail.get('name') email = detail.get('email') if name and email: context[key] = '%s (%s)' % (name, email) elif name: context[key] = name elif email: context[key] = email else: author = context.get(key) if not author: return emailmatch = re.search(r'''(([a-zA-Z0-9\_\-\.\+]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?))''', author) if not emailmatch: return email = emailmatch.group(0) # probably a better way to do the following, but it passes all the tests author = author.replace(email, '') author = author.replace('()', '') author = author.strip() if author and (author[0] == '('): author = author[1:] if author and (author[-1] == ')'): author = author[:-1] author = author.strip() context.setdefault('%s_detail' % key, FeedParserDict()) context['%s_detail' % key]['name'] = author context['%s_detail' % key]['email'] = email def _start_subtitle(self, attrsD): self.pushContent('subtitle', attrsD, 'text/plain', 1) _start_tagline = _start_subtitle _start_itunes_subtitle = _start_subtitle def _end_subtitle(self): self.popContent('subtitle') _end_tagline = _end_subtitle _end_itunes_subtitle = _end_subtitle def _start_rights(self, attrsD): self.pushContent('rights', attrsD, 'text/plain', 1) _start_dc_rights = _start_rights _start_copyright = _start_rights def _end_rights(self): self.popContent('rights') _end_dc_rights = _end_rights _end_copyright = _end_rights def _start_item(self, attrsD): self.entries.append(FeedParserDict()) self.push('item', 0) self.inentry = 1 self.guidislink = 0 id = self._getAttribute(attrsD, 'rdf:about') if id: context = self._getContext() context['id'] = id self._cdf_common(attrsD) _start_entry = _start_item _start_product = _start_item def _end_item(self): self.pop('item') self.inentry = 0 _end_entry = _end_item def _start_dc_language(self, attrsD): self.push('language', 1) _start_language = _start_dc_language def _end_dc_language(self): self.lang = self.pop('language') _end_language = _end_dc_language def _start_dc_publisher(self, attrsD): self.push('publisher', 1) _start_webmaster = _start_dc_publisher def _end_dc_publisher(self): self.pop('publisher') self._sync_author_detail('publisher') _end_webmaster = _end_dc_publisher def _start_published(self, attrsD): self.push('published', 1) _start_dcterms_issued = _start_published _start_issued = _start_published def _end_published(self): value = self.pop('published') self._save('published_parsed', _parse_date(value)) _end_dcterms_issued = _end_published _end_issued = _end_published def _start_updated(self, attrsD): self.push('updated', 1) _start_modified = _start_updated _start_dcterms_modified = _start_updated _start_pubdate = _start_updated _start_dc_date = _start_updated def _end_updated(self): value = self.pop('updated') parsed_value = _parse_date(value) self._save('updated_parsed', parsed_value) _end_modified = _end_updated _end_dcterms_modified = _end_updated _end_pubdate = _end_updated _end_dc_date = _end_updated def _start_created(self, attrsD): self.push('created', 1) _start_dcterms_created = _start_created def _end_created(self): value = self.pop('created') self._save('created_parsed', _parse_date(value)) _end_dcterms_created = _end_created def _start_expirationdate(self, attrsD): self.push('expired', 1) def _end_expirationdate(self): self._save('expired_parsed', _parse_date(self.pop('expired'))) def _start_cc_license(self, attrsD): self.push('license', 1) value = self._getAttribute(attrsD, 'rdf:resource') if value: self.elementstack[-1][2].append(value) self.pop('license') def _start_creativecommons_license(self, attrsD): self.push('license', 1) def _end_creativecommons_license(self): self.pop('license') def _addTag(self, term, scheme, label): context = self._getContext() tags = context.setdefault('tags', []) if (not term) and (not scheme) and (not label): return value = FeedParserDict({'term': term, 'scheme': scheme, 'label': label}) if value not in tags: tags.append(FeedParserDict({'term': term, 'scheme': scheme, 'label': label})) def _start_category(self, attrsD): if _debug: sys.stderr.write('entering _start_category with %s\n' % repr(attrsD)) term = attrsD.get('term') scheme = attrsD.get('scheme', attrsD.get('domain')) label = attrsD.get('label') self._addTag(term, scheme, label) self.push('category', 1) _start_dc_subject = _start_category _start_keywords = _start_category def _end_itunes_keywords(self): for term in self.pop('itunes_keywords').split(): self._addTag(term, 'http://www.itunes.com/', None) def _start_itunes_category(self, attrsD): self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None) self.push('category', 1) def _end_category(self): value = self.pop('category') if not value: return context = self._getContext() tags = context['tags'] if value and len(tags) and not tags[-1]['term']: tags[-1]['term'] = value else: self._addTag(value, None, None) _end_dc_subject = _end_category _end_keywords = _end_category _end_itunes_category = _end_category def _start_cloud(self, attrsD): self._getContext()['cloud'] = FeedParserDict(attrsD) def _start_link(self, attrsD): attrsD.setdefault('rel', 'alternate') attrsD.setdefault('type', 'text/html') attrsD = self._itsAnHrefDamnIt(attrsD) if attrsD.has_key('href'): attrsD['href'] = self.resolveURI(attrsD['href']) expectingText = self.infeed or self.inentry or self.insource context = self._getContext() context.setdefault('links', []) context['links'].append(FeedParserDict(attrsD)) if attrsD['rel'] == 'enclosure': self._start_enclosure(attrsD) if attrsD.has_key('href'): expectingText = 0 if (attrsD.get('rel') == 'alternate') and (self.mapContentType(attrsD.get('type')) in self.html_types): context['link'] = attrsD['href'] else: self.push('link', expectingText) _start_producturl = _start_link def _end_link(self): value = self.pop('link') context = self._getContext() if self.intextinput: context['textinput']['link'] = value if self.inimage: context['image']['link'] = value _end_producturl = _end_link def _start_guid(self, attrsD): self.guidislink = (attrsD.get('ispermalink', 'true') == 'true') self.push('id', 1) def _end_guid(self): value = self.pop('id') self._save('guidislink', self.guidislink and not self._getContext().has_key('link')) if self.guidislink: # guid acts as link, but only if 'ispermalink' is not present or is 'true', # and only if the item doesn't already have a link element self._save('link', value) def _start_title(self, attrsD): self.pushContent('title', attrsD, 'text/plain', self.infeed or self.inentry or self.insource) _start_dc_title = _start_title _start_media_title = _start_title def _end_title(self): value = self.popContent('title') context = self._getContext() if self.intextinput: context['textinput']['title'] = value elif self.inimage: context['image']['title'] = value _end_dc_title = _end_title _end_media_title = _end_title def _start_description(self, attrsD): context = self._getContext() if context.has_key('summary'): self._summaryKey = 'content' self._start_content(attrsD) else: self.pushContent('description', attrsD, 'text/html', self.infeed or self.inentry or self.insource) def _start_abstract(self, attrsD): self.pushContent('description', attrsD, 'text/plain', self.infeed or self.inentry or self.insource) def _end_description(self): if self._summaryKey == 'content': self._end_content() else: value = self.popContent('description') context = self._getContext() if self.intextinput: context['textinput']['description'] = value elif self.inimage: context['image']['description'] = value self._summaryKey = None _end_abstract = _end_description def _start_info(self, attrsD): self.pushContent('info', attrsD, 'text/plain', 1) _start_feedburner_browserfriendly = _start_info def _end_info(self): self.popContent('info') _end_feedburner_browserfriendly = _end_info def _start_generator(self, attrsD): if attrsD: attrsD = self._itsAnHrefDamnIt(attrsD) if attrsD.has_key('href'): attrsD['href'] = self.resolveURI(attrsD['href']) self._getContext()['generator_detail'] = FeedParserDict(attrsD) self.push('generator', 1) def _end_generator(self): value = self.pop('generator') context = self._getContext() if context.has_key('generator_detail'): context['generator_detail']['name'] = value def _start_admin_generatoragent(self, attrsD): self.push('generator', 1) value = self._getAttribute(attrsD, 'rdf:resource') if value: self.elementstack[-1][2].append(value) self.pop('generator') self._getContext()['generator_detail'] = FeedParserDict({'href': value}) def _start_admin_errorreportsto(self, attrsD): self.push('errorreportsto', 1) value = self._getAttribute(attrsD, 'rdf:resource') if value: self.elementstack[-1][2].append(value) self.pop('errorreportsto') def _start_summary(self, attrsD): context = self._getContext() if context.has_key('summary'): self._summaryKey = 'content' self._start_content(attrsD) else: self._summaryKey = 'summary' self.pushContent(self._summaryKey, attrsD, 'text/plain', 1) _start_itunes_summary = _start_summary def _end_summary(self): if self._summaryKey == 'content': self._end_content() else: self.popContent(self._summaryKey or 'summary') self._summaryKey = None _end_itunes_summary = _end_summary def _start_enclosure(self, attrsD): attrsD = self._itsAnHrefDamnIt(attrsD) self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD)) href = attrsD.get('href') if href: context = self._getContext() if not context.get('id'): context['id'] = href def _start_source(self, attrsD): self.insource = 1 def _end_source(self): self.insource = 0 self._getContext()['source'] = copy.deepcopy(self.sourcedata) self.sourcedata.clear() def _start_content(self, attrsD): self.pushContent('content', attrsD, 'text/plain', 1) src = attrsD.get('src') if src: self.contentparams['src'] = src self.push('content', 1) def _start_prodlink(self, attrsD): self.pushContent('content', attrsD, 'text/html', 1) def _start_body(self, attrsD): self.pushContent('content', attrsD, 'application/xhtml+xml', 1) _start_xhtml_body = _start_body def _start_content_encoded(self, attrsD): self.pushContent('content', attrsD, 'text/html', 1) _start_fullitem = _start_content_encoded def _end_content(self): copyToDescription = self.mapContentType(self.contentparams.get('type')) in (['text/plain'] + self.html_types) value = self.popContent('content') if copyToDescription: self._save('description', value) _end_body = _end_content _end_xhtml_body = _end_content _end_content_encoded = _end_content _end_fullitem = _end_content _end_prodlink = _end_content def _start_itunes_image(self, attrsD): self.push('itunes_image', 0) self._getContext()['image'] = FeedParserDict({'href': attrsD.get('href')}) _start_itunes_link = _start_itunes_image def _end_itunes_block(self): value = self.pop('itunes_block', 0) self._getContext()['itunes_block'] = (value == 'yes') and 1 or 0 def _end_itunes_explicit(self): value = self.pop('itunes_explicit', 0) self._getContext()['itunes_explicit'] = (value == 'yes') and 1 or 0 if _XML_AVAILABLE: class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler): def __init__(self, baseuri, baselang, encoding): if _debug: sys.stderr.write('trying StrictFeedParser\n') xml.sax.handler.ContentHandler.__init__(self) _FeedParserMixin.__init__(self, baseuri, baselang, encoding) self.bozo = 0 self.exc = None def startPrefixMapping(self, prefix, uri): self.trackNamespace(prefix, uri) def startElementNS(self, name, qname, attrs): namespace, localname = name lowernamespace = str(namespace or '').lower() if lowernamespace.find('backend.userland.com/rss') <> -1: # match any backend.userland.com namespace namespace = 'http://backend.userland.com/rss' lowernamespace = namespace if qname and qname.find(':') > 0: givenprefix = qname.split(':')[0] else: givenprefix = None prefix = self._matchnamespaces.get(lowernamespace, givenprefix) if givenprefix and (prefix == None or (prefix == '' and lowernamespace == '')) and not self.namespacesInUse.has_key(givenprefix): raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix if prefix: localname = prefix + ':' + localname localname = str(localname).lower() if _debug: sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname)) # qname implementation is horribly broken in Python 2.1 (it # doesn't report any), and slightly broken in Python 2.2 (it # doesn't report the xml: namespace). So we match up namespaces # with a known list first, and then possibly override them with # the qnames the SAX parser gives us (if indeed it gives us any # at all). Thanks to MatejC for helping me test this and # tirelessly telling me that it didn't work yet. attrsD = {} for (namespace, attrlocalname), attrvalue in attrs._attrs.items(): lowernamespace = (namespace or '').lower() prefix = self._matchnamespaces.get(lowernamespace, '') if prefix: attrlocalname = prefix + ':' + attrlocalname attrsD[str(attrlocalname).lower()] = attrvalue for qname in attrs.getQNames(): attrsD[str(qname).lower()] = attrs.getValueByQName(qname) self.unknown_starttag(localname, attrsD.items()) def characters(self, text): self.handle_data(text) def endElementNS(self, name, qname): namespace, localname = name lowernamespace = str(namespace or '').lower() if qname and qname.find(':') > 0: givenprefix = qname.split(':')[0] else: givenprefix = '' prefix = self._matchnamespaces.get(lowernamespace, givenprefix) if prefix: localname = prefix + ':' + localname localname = str(localname).lower() self.unknown_endtag(localname) def error(self, exc): self.bozo = 1 self.exc = exc def fatalError(self, exc): self.error(exc) raise exc class _BaseHTMLProcessor(sgmllib.SGMLParser): elements_no_end_tag = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param'] def __init__(self, encoding): self.encoding = encoding if _debug: sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding) sgmllib.SGMLParser.__init__(self) def reset(self): self.pieces = [] sgmllib.SGMLParser.reset(self) def _shorttag_replace(self, match): tag = match.group(1) if tag in self.elements_no_end_tag: return '<' + tag + ' />' else: return '<' + tag + '>' def feed(self, data): data = re.compile(r'', self._shorttag_replace, data) # bug [ 1399464 ] Bad regexp for _shorttag_replace data = re.sub(r'<([^<\s]+?)\s*/>', self._shorttag_replace, data) data = data.replace(''', "'") data = data.replace('"', '"') if self.encoding and type(data) == type(u''): data = data.encode(self.encoding) sgmllib.SGMLParser.feed(self, data) def normalize_attrs(self, attrs): # utility method to be called by descendants attrs = [(k.lower(), v) for k, v in attrs] attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs] return attrs def unknown_starttag(self, tag, attrs): # called for each start tag # attrs is a list of (attr, value) tuples # e.g. for
, tag='pre', attrs=[('class', 'screen')]
        if _debug: sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
        uattrs = []
        # thanks to Kevin Marks for this breathtaking hack to deal with (valid) high-bit attribute values in UTF-8 feeds
        for key, value in attrs:
            if type(value) != type(u''):
                value = unicode(value, self.encoding)
            uattrs.append((unicode(key, self.encoding), value))
        strattrs = u''.join([u' %s="%s"' % (key, value) for key, value in uattrs]).encode(self.encoding)
        if tag in self.elements_no_end_tag:
            self.pieces.append('<%(tag)s%(strattrs)s />' % locals())
        else:
            self.pieces.append('<%(tag)s%(strattrs)s>' % locals())

    def unknown_endtag(self, tag):
        # called for each end tag, e.g. for 
, tag will be 'pre' # Reconstruct the original end tag. if tag not in self.elements_no_end_tag: self.pieces.append("" % locals()) def handle_charref(self, ref): # called for each character reference, e.g. for ' ', ref will be '160' # Reconstruct the original character reference. self.pieces.append('&#%(ref)s;' % locals()) def handle_entityref(self, ref): # called for each entity reference, e.g. for '©', ref will be 'copy' # Reconstruct the original entity reference. self.pieces.append('&%(ref)s;' % locals()) def handle_data(self, text): # called for each block of plain text, i.e. outside of any tag and # not containing any character or entity references # Store the original text verbatim. if _debug: sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text) self.pieces.append(text) def handle_comment(self, text): # called for each HTML comment, e.g. # Reconstruct the original comment. self.pieces.append('' % locals()) def handle_pi(self, text): # called for each processing instruction, e.g. # Reconstruct original processing instruction. self.pieces.append('' % locals()) def handle_decl(self, text): # called for the DOCTYPE, if present, e.g. # # Reconstruct original DOCTYPE self.pieces.append('' % locals()) _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match def _scan_name(self, i, declstartpos): rawdata = self.rawdata n = len(rawdata) if i == n: return None, -1 m = self._new_declname_match(rawdata, i) if m: s = m.group() name = s.strip() if (i + len(s)) == n: return None, -1 # end of buffer return name.lower(), m.end() else: self.handle_data(rawdata) # self.updatepos(declstartpos, i) return None, -1 def output(self): '''Return processed HTML as a single string''' return ''.join([str(p) for p in self.pieces]) class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor): def __init__(self, baseuri, baselang, encoding): sgmllib.SGMLParser.__init__(self) _FeedParserMixin.__init__(self, baseuri, baselang, encoding) def decodeEntities(self, element, data): data = data.replace('<', '<') data = data.replace('<', '<') data = data.replace('>', '>') data = data.replace('>', '>') data = data.replace('&', '&') data = data.replace('&', '&') data = data.replace('"', '"') data = data.replace('"', '"') data = data.replace(''', ''') data = data.replace(''', ''') if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'): data = data.replace('<', '<') data = data.replace('>', '>') data = data.replace('&', '&') data = data.replace('"', '"') data = data.replace(''', "'") return data class _RelativeURIResolver(_BaseHTMLProcessor): relative_uris = [('a', 'href'), ('applet', 'codebase'), ('area', 'href'), ('blockquote', 'cite'), ('body', 'background'), ('del', 'cite'), ('form', 'action'), ('frame', 'longdesc'), ('frame', 'src'), ('iframe', 'longdesc'), ('iframe', 'src'), ('head', 'profile'), ('img', 'longdesc'), ('img', 'src'), ('img', 'usemap'), ('input', 'src'), ('input', 'usemap'), ('ins', 'cite'), ('link', 'href'), ('object', 'classid'), ('object', 'codebase'), ('object', 'data'), ('object', 'usemap'), ('q', 'cite'), ('script', 'src')] def __init__(self, baseuri, encoding): _BaseHTMLProcessor.__init__(self, encoding) self.baseuri = baseuri def resolveURI(self, uri): return _urljoin(self.baseuri, uri) def unknown_starttag(self, tag, attrs): attrs = self.normalize_attrs(attrs) attrs = [(key, ((tag, key) in self.relative_uris) and self.resolveURI(value) or value) for key, value in attrs] _BaseHTMLProcessor.unknown_starttag(self, tag, attrs) def _resolveRelativeURIs(htmlSource, baseURI, encoding): if _debug: sys.stderr.write('entering _resolveRelativeURIs\n') p = _RelativeURIResolver(baseURI, encoding) p.feed(htmlSource) return p.output() class _HTMLSanitizer(_BaseHTMLProcessor): acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area', 'b', 'big', 'blockquote', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu', 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var'] acceptable_attributes = ['abbr', 'accept', 'accept-charset', 'accesskey', 'action', 'align', 'alt', 'axis', 'border', 'cellpadding', 'cellspacing', 'char', 'charoff', 'charset', 'checked', 'cite', 'class', 'clear', 'cols', 'colspan', 'color', 'compact', 'coords', 'datetime', 'dir', 'disabled', 'enctype', 'for', 'frame', 'headers', 'height', 'href', 'hreflang', 'hspace', 'id', 'ismap', 'label', 'lang', 'longdesc', 'maxlength', 'media', 'method', 'multiple', 'name', 'nohref', 'noshade', 'nowrap', 'prompt', 'readonly', 'rel', 'rev', 'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src', 'start', 'summary', 'tabindex', 'target', 'title', 'type', 'usemap', 'valign', 'value', 'vspace', 'width'] unacceptable_elements_with_end_tag = ['script', 'applet'] def reset(self): _BaseHTMLProcessor.reset(self) self.unacceptablestack = 0 def unknown_starttag(self, tag, attrs): if not tag in self.acceptable_elements: if tag in self.unacceptable_elements_with_end_tag: self.unacceptablestack += 1 return attrs = self.normalize_attrs(attrs) attrs = [(key, value) for key, value in attrs if key in self.acceptable_attributes] _BaseHTMLProcessor.unknown_starttag(self, tag, attrs) def unknown_endtag(self, tag): if not tag in self.acceptable_elements: if tag in self.unacceptable_elements_with_end_tag: self.unacceptablestack -= 1 return _BaseHTMLProcessor.unknown_endtag(self, tag) def handle_pi(self, text): pass def handle_decl(self, text): pass def handle_data(self, text): if not self.unacceptablestack: _BaseHTMLProcessor.handle_data(self, text) def _sanitizeHTML(htmlSource, encoding): p = _HTMLSanitizer(encoding) p.feed(htmlSource) data = p.output() if TIDY_MARKUP: # loop through list of preferred Tidy interfaces looking for one that's installed, # then set up a common _tidy function to wrap the interface-specific API. _tidy = None for tidy_interface in PREFERRED_TIDY_INTERFACES: try: if tidy_interface == "uTidy": from tidy import parseString as _utidy def _tidy(data, **kwargs): return str(_utidy(data, **kwargs)) break elif tidy_interface == "mxTidy": from mx.Tidy import Tidy as _mxtidy def _tidy(data, **kwargs): nerrors, nwarnings, data, errordata = _mxtidy.tidy(data, **kwargs) return data break except: pass if _tidy: utf8 = type(data) == type(u'') if utf8: data = data.encode('utf-8') data = _tidy(data, output_xhtml=1, numeric_entities=1, wrap=0, char_encoding="utf8") if utf8: data = unicode(data, 'utf-8') if data.count(''): data = data.split('>', 1)[1] if data.count('= '2.3.3' assert base64 != None user, passw = base64.decodestring(req.headers['Authorization'].split(' ')[1]).split(':') realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0] self.add_password(realm, host, user, passw) retry = self.http_error_auth_reqed('www-authenticate', host, req, headers) self.reset_retry_count() return retry except: return self.http_error_default(req, fp, code, msg, headers) def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers): """URL, filename, or string --> stream This function lets you define parsers that take any input source (URL, pathname to local or network file, or actual data as a string) and deal with it in a uniform manner. Returned object is guaranteed to have all the basic stdio read methods (read, readline, readlines). Just .close() the object when you're done with it. If the etag argument is supplied, it will be used as the value of an If-None-Match request header. If the modified argument is supplied, it must be a tuple of 9 integers as returned by gmtime() in the standard Python time module. This MUST be in GMT (Greenwich Mean Time). The formatted date/time will be used as the value of an If-Modified-Since request header. If the agent argument is supplied, it will be used as the value of a User-Agent request header. If the referrer argument is supplied, it will be used as the value of a Referer[sic] request header. If handlers is supplied, it is a list of handlers used to build a urllib2 opener. """ if hasattr(url_file_stream_or_string, 'read'): return url_file_stream_or_string if url_file_stream_or_string == '-': return sys.stdin if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp'): if not agent: agent = USER_AGENT # test for inline user:password for basic auth auth = None if base64: urltype, rest = urllib.splittype(url_file_stream_or_string) realhost, rest = urllib.splithost(rest) if realhost: user_passwd, realhost = urllib.splituser(realhost) if user_passwd: url_file_stream_or_string = '%s://%s%s' % (urltype, realhost, rest) auth = base64.encodestring(user_passwd).strip() # try to open with urllib2 (to use optional headers) request = urllib2.Request(url_file_stream_or_string) request.add_header('User-Agent', agent) if etag: request.add_header('If-None-Match', etag) if modified: # format into an RFC 1123-compliant timestamp. We can't use # time.strftime() since the %a and %b directives can be affected # by the current locale, but RFC 2616 states that dates must be # in English. short_weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] request.add_header('If-Modified-Since', '%s, %02d %s %04d %02d:%02d:%02d GMT' % (short_weekdays[modified[6]], modified[2], months[modified[1] - 1], modified[0], modified[3], modified[4], modified[5])) if referrer: request.add_header('Referer', referrer) if gzip and zlib: request.add_header('Accept-encoding', 'gzip, deflate') elif gzip: request.add_header('Accept-encoding', 'gzip') elif zlib: request.add_header('Accept-encoding', 'deflate') else: request.add_header('Accept-encoding', '') if auth: request.add_header('Authorization', 'Basic %s' % auth) if ACCEPT_HEADER: request.add_header('Accept', ACCEPT_HEADER) request.add_header('A-IM', 'feed') # RFC 3229 support opener = apply(urllib2.build_opener, tuple([_FeedURLHandler()] + handlers)) opener.addheaders = [] # RMK - must clear so we only send our custom User-Agent try: return opener.open(request) finally: opener.close() # JohnD # try to open with native open function (if url_file_stream_or_string is a filename) try: return open(url_file_stream_or_string) except: pass # treat url_file_stream_or_string as string return _StringIO(str(url_file_stream_or_string)) _date_handlers = [] def registerDateHandler(func): '''Register a date handler function (takes string, returns 9-tuple date in GMT)''' _date_handlers.insert(0, func) # ISO-8601 date parsing routines written by Fazal Majid. # The ISO 8601 standard is very convoluted and irregular - a full ISO 8601 # parser is beyond the scope of feedparser and would be a worthwhile addition # to the Python library. # A single regular expression cannot parse ISO 8601 date formats into groups # as the standard is highly irregular (for instance is 030104 2003-01-04 or # 0301-04-01), so we use templates instead. # Please note the order in templates is significant because we need a # greedy match. _iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-MM', 'YYYY-?OOO', 'YY-?MM-?DD', 'YY-?OOO', 'YYYY', '-YY-?MM', '-OOO', '-YY', '--MM-?DD', '--MM', '---DD', 'CC', ''] _iso8601_re = [ tmpl.replace( 'YYYY', r'(?P\d{4})').replace( 'YY', r'(?P\d\d)').replace( 'MM', r'(?P[01]\d)').replace( 'DD', r'(?P[0123]\d)').replace( 'OOO', r'(?P[0123]\d\d)').replace( 'CC', r'(?P\d\d$)') + r'(T?(?P\d{2}):(?P\d{2})' + r'(:(?P\d{2}))?' + r'(?P[+-](?P\d{2})(:(?P\d{2}))?|Z)?)?' for tmpl in _iso8601_tmpl] del tmpl _iso8601_matches = [re.compile(regex).match for regex in _iso8601_re] del regex def _parse_date_iso8601(dateString): '''Parse a variety of ISO-8601-compatible formats like 20040105''' m = None for _iso8601_match in _iso8601_matches: m = _iso8601_match(dateString) if m: break if not m: return if m.span() == (0, 0): return params = m.groupdict() ordinal = params.get('ordinal', 0) if ordinal: ordinal = int(ordinal) else: ordinal = 0 year = params.get('year', '--') if not year or year == '--': year = time.gmtime()[0] elif len(year) == 2: # ISO 8601 assumes current century, i.e. 93 -> 2093, NOT 1993 year = 100 * int(time.gmtime()[0] / 100) + int(year) else: year = int(year) month = params.get('month', '-') if not month or month == '-': # ordinals are NOT normalized by mktime, we simulate them # by setting month=1, day=ordinal if ordinal: month = 1 else: month = time.gmtime()[1] month = int(month) day = params.get('day', 0) if not day: # see above if ordinal: day = ordinal elif params.get('century', 0) or \ params.get('year', 0) or params.get('month', 0): day = 1 else: day = time.gmtime()[2] else: day = int(day) # special case of the century - is the first year of the 21st century # 2000 or 2001 ? The debate goes on... if 'century' in params.keys(): year = (int(params['century']) - 1) * 100 + 1 # in ISO 8601 most fields are optional for field in ['hour', 'minute', 'second', 'tzhour', 'tzmin']: if not params.get(field, None): params[field] = 0 hour = int(params.get('hour', 0)) minute = int(params.get('minute', 0)) second = int(params.get('second', 0)) # weekday is normalized by mktime(), we can ignore it weekday = 0 # daylight savings is complex, but not needed for feedparser's purposes # as time zones, if specified, include mention of whether it is active # (e.g. PST vs. PDT, CET). Using -1 is implementation-dependent and # and most implementations have DST bugs daylight_savings_flag = 0 tm = [year, month, day, hour, minute, second, weekday, ordinal, daylight_savings_flag] # ISO 8601 time zone adjustments tz = params.get('tz') if tz and tz != 'Z': if tz[0] == '-': tm[3] += int(params.get('tzhour', 0)) tm[4] += int(params.get('tzmin', 0)) elif tz[0] == '+': tm[3] -= int(params.get('tzhour', 0)) tm[4] -= int(params.get('tzmin', 0)) else: return None # Python's time.mktime() is a wrapper around the ANSI C mktime(3c) # which is guaranteed to normalize d/m/y/h/m/s. # Many implementations have bugs, but we'll pretend they don't. return time.localtime(time.mktime(tm)) registerDateHandler(_parse_date_iso8601) # 8-bit date handling routines written by ytrewq1. _korean_year = u'\ub144' # b3e2 in euc-kr _korean_month = u'\uc6d4' # bff9 in euc-kr _korean_day = u'\uc77c' # c0cf in euc-kr _korean_am = u'\uc624\uc804' # bfc0 c0fc in euc-kr _korean_pm = u'\uc624\ud6c4' # bfc0 c8c4 in euc-kr _korean_onblog_date_re = \ re.compile('(\d{4})%s\s+(\d{2})%s\s+(\d{2})%s\s+(\d{2}):(\d{2}):(\d{2})' % \ (_korean_year, _korean_month, _korean_day)) _korean_nate_date_re = \ re.compile(u'(\d{4})-(\d{2})-(\d{2})\s+(%s|%s)\s+(\d{,2}):(\d{,2}):(\d{,2})' % \ (_korean_am, _korean_pm)) def _parse_date_onblog(dateString): '''Parse a string according to the OnBlog 8-bit date format''' m = _korean_onblog_date_re.match(dateString) if not m: return w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\ 'zonediff': '+09:00'} if _debug: sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate) return _parse_date_w3dtf(w3dtfdate) registerDateHandler(_parse_date_onblog) def _parse_date_nate(dateString): '''Parse a string according to the Nate 8-bit date format''' m = _korean_nate_date_re.match(dateString) if not m: return hour = int(m.group(5)) ampm = m.group(4) if (ampm == _korean_pm): hour += 12 hour = str(hour) if len(hour) == 1: hour = '0' + hour w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ 'hour': hour, 'minute': m.group(6), 'second': m.group(7),\ 'zonediff': '+09:00'} if _debug: sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate) return _parse_date_w3dtf(w3dtfdate) registerDateHandler(_parse_date_nate) _mssql_date_re = \ re.compile('(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})(\.\d+)?') def _parse_date_mssql(dateString): '''Parse a string according to the MS SQL date format''' m = _mssql_date_re.match(dateString) if not m: return w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % \ {'year': m.group(1), 'month': m.group(2), 'day': m.group(3),\ 'hour': m.group(4), 'minute': m.group(5), 'second': m.group(6),\ 'zonediff': '+09:00'} if _debug: sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate) return _parse_date_w3dtf(w3dtfdate) registerDateHandler(_parse_date_mssql) # Unicode strings for Greek date strings _greek_months = \ { \ u'\u0399\u03b1\u03bd': u'Jan', # c9e1ed in iso-8859-7 u'\u03a6\u03b5\u03b2': u'Feb', # d6e5e2 in iso-8859-7 u'\u039c\u03ac\u03ce': u'Mar', # ccdcfe in iso-8859-7 u'\u039c\u03b1\u03ce': u'Mar', # cce1fe in iso-8859-7 u'\u0391\u03c0\u03c1': u'Apr', # c1f0f1 in iso-8859-7 u'\u039c\u03ac\u03b9': u'May', # ccdce9 in iso-8859-7 u'\u039c\u03b1\u03ca': u'May', # cce1fa in iso-8859-7 u'\u039c\u03b1\u03b9': u'May', # cce1e9 in iso-8859-7 u'\u0399\u03bf\u03cd\u03bd': u'Jun', # c9effded in iso-8859-7 u'\u0399\u03bf\u03bd': u'Jun', # c9efed in iso-8859-7 u'\u0399\u03bf\u03cd\u03bb': u'Jul', # c9effdeb in iso-8859-7 u'\u0399\u03bf\u03bb': u'Jul', # c9f9eb in iso-8859-7 u'\u0391\u03cd\u03b3': u'Aug', # c1fde3 in iso-8859-7 u'\u0391\u03c5\u03b3': u'Aug', # c1f5e3 in iso-8859-7 u'\u03a3\u03b5\u03c0': u'Sep', # d3e5f0 in iso-8859-7 u'\u039f\u03ba\u03c4': u'Oct', # cfeaf4 in iso-8859-7 u'\u039d\u03bf\u03ad': u'Nov', # cdefdd in iso-8859-7 u'\u039d\u03bf\u03b5': u'Nov', # cdefe5 in iso-8859-7 u'\u0394\u03b5\u03ba': u'Dec', # c4e5ea in iso-8859-7 } _greek_wdays = \ { \ u'\u039a\u03c5\u03c1': u'Sun', # caf5f1 in iso-8859-7 u'\u0394\u03b5\u03c5': u'Mon', # c4e5f5 in iso-8859-7 u'\u03a4\u03c1\u03b9': u'Tue', # d4f1e9 in iso-8859-7 u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7 u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7 u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7 u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7 } _greek_date_format_re = \ re.compile(u'([^,]+),\s+(\d{2})\s+([^\s]+)\s+(\d{4})\s+(\d{2}):(\d{2}):(\d{2})\s+([^\s]+)') def _parse_date_greek(dateString): '''Parse a string according to a Greek 8-bit date format.''' m = _greek_date_format_re.match(dateString) if not m: return try: wday = _greek_wdays[m.group(1)] month = _greek_months[m.group(3)] except: return rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % \ {'wday': wday, 'day': m.group(2), 'month': month, 'year': m.group(4),\ 'hour': m.group(5), 'minute': m.group(6), 'second': m.group(7),\ 'zonediff': m.group(8)} if _debug: sys.stderr.write('Greek date parsed as: %s\n' % rfc822date) return _parse_date_rfc822(rfc822date) registerDateHandler(_parse_date_greek) # Unicode strings for Hungarian date strings _hungarian_months = \ { \ u'janu\u00e1r': u'01', # e1 in iso-8859-2 u'febru\u00e1ri': u'02', # e1 in iso-8859-2 u'm\u00e1rcius': u'03', # e1 in iso-8859-2 u'\u00e1prilis': u'04', # e1 in iso-8859-2 u'm\u00e1ujus': u'05', # e1 in iso-8859-2 u'j\u00fanius': u'06', # fa in iso-8859-2 u'j\u00falius': u'07', # fa in iso-8859-2 u'augusztus': u'08', u'szeptember': u'09', u'okt\u00f3ber': u'10', # f3 in iso-8859-2 u'november': u'11', u'december': u'12', } _hungarian_date_format_re = \ re.compile(u'(\d{4})-([^-]+)-(\d{,2})T(\d{,2}):(\d{2})((\+|-)(\d{,2}:\d{2}))') def _parse_date_hungarian(dateString): '''Parse a string according to a Hungarian 8-bit date format.''' m = _hungarian_date_format_re.match(dateString) if not m: return try: month = _hungarian_months[m.group(2)] day = m.group(3) if len(day) == 1: day = '0' + day hour = m.group(4) if len(hour) == 1: hour = '0' + hour except: return w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % \ {'year': m.group(1), 'month': month, 'day': day,\ 'hour': hour, 'minute': m.group(5),\ 'zonediff': m.group(6)} if _debug: sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate) return _parse_date_w3dtf(w3dtfdate) registerDateHandler(_parse_date_hungarian) # W3DTF-style date parsing adapted from PyXML xml.utils.iso8601, written by # Drake and licensed under the Python license. Removed all range checking # for month, day, hour, minute, and second, since mktime will normalize # these later def _parse_date_w3dtf(dateString): def __extract_date(m): year = int(m.group('year')) if year < 100: year = 100 * int(time.gmtime()[0] / 100) + int(year) if year < 1000: return 0, 0, 0 julian = m.group('julian') if julian: julian = int(julian) month = julian / 30 + 1 day = julian % 30 + 1 jday = None while jday != julian: t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0)) jday = time.gmtime(t)[-2] diff = abs(jday - julian) if jday > julian: if diff < day: day = day - diff else: month = month - 1 day = 31 elif jday < julian: if day + diff < 28: day = day + diff else: month = month + 1 return year, month, day month = m.group('month') day = 1 if month is None: month = 1 else: month = int(month) day = m.group('day') if day: day = int(day) else: day = 1 return year, month, day def __extract_time(m): if not m: return 0, 0, 0 hours = m.group('hours') if not hours: return 0, 0, 0 hours = int(hours) minutes = int(m.group('minutes')) seconds = m.group('seconds') if seconds: seconds = int(seconds) else: seconds = 0 return hours, minutes, seconds def __extract_tzd(m): '''Return the Time Zone Designator as an offset in seconds from UTC.''' if not m: return 0 tzd = m.group('tzd') if not tzd: return 0 if tzd == 'Z': return 0 hours = int(m.group('tzdhours')) minutes = m.group('tzdminutes') if minutes: minutes = int(minutes) else: minutes = 0 offset = (hours*60 + minutes) * 60 if tzd[0] == '+': return -offset return offset __date_re = ('(?P\d\d\d\d)' '(?:(?P-|)' '(?:(?P\d\d\d)' '|(?P\d\d)(?:(?P=dsep)(?P\d\d))?))?') __tzd_re = '(?P[-+](?P\d\d)(?::?(?P\d\d))|Z)' __tzd_rx = re.compile(__tzd_re) __time_re = ('(?P\d\d)(?P:|)(?P\d\d)' '(?:(?P=tsep)(?P\d\d(?:[.,]\d+)?))?' + __tzd_re) __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re) __datetime_rx = re.compile(__datetime_re) m = __datetime_rx.match(dateString) if (m is None) or (m.group() != dateString): return gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0) if gmt[0] == 0: return return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone) registerDateHandler(_parse_date_w3dtf) def _parse_date_rfc822(dateString): '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date''' data = dateString.split() if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames: del data[0] if len(data) == 4: s = data[3] i = s.find('+') if i > 0: data[3:] = [s[:i], s[i+1:]] else: data.append('') dateString = " ".join(data) if len(data) < 5: dateString += ' 00:00:00 GMT' tm = rfc822.parsedate_tz(dateString) if tm: return time.gmtime(rfc822.mktime_tz(tm)) # rfc822.py defines several time zones, but we define some extra ones. # 'ET' is equivalent to 'EST', etc. _additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800} rfc822._timezones.update(_additional_timezones) registerDateHandler(_parse_date_rfc822) def _parse_date(dateString): '''Parses a variety of date formats into a 9-tuple in GMT''' for handler in _date_handlers: try: date9tuple = handler(dateString) if not date9tuple: continue if len(date9tuple) != 9: if _debug: sys.stderr.write('date handler function must return 9-tuple\n') raise ValueError map(int, date9tuple) return date9tuple except Exception, e: if _debug: sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e))) pass return None def _getCharacterEncoding(http_headers, xml_data): '''Get the character encoding of the XML document http_headers is a dictionary xml_data is a raw string (not Unicode) This is so much trickier than it sounds, it's not even funny. According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type is application/xml, application/*+xml, application/xml-external-parsed-entity, or application/xml-dtd, the encoding given in the charset parameter of the HTTP Content-Type takes precedence over the encoding given in the XML prefix within the document, and defaults to 'utf-8' if neither are specified. But, if the HTTP Content-Type is text/xml, text/*+xml, or text/xml-external-parsed-entity, the encoding given in the XML prefix within the document is ALWAYS IGNORED and only the encoding given in the charset parameter of the HTTP Content-Type header should be respected, and it defaults to 'us-ascii' if not specified. Furthermore, discussion on the atom-syntax mailing list with the author of RFC 3023 leads me to the conclusion that any document served with a Content-Type of text/* and no charset parameter must be treated as us-ascii. (We now do this.) And also that it must always be flagged as non-well-formed. (We now do this too.) If Content-Type is unspecified (input was local file or non-HTTP source) or unrecognized (server just got it totally wrong), then go by the encoding given in the XML prefix of the document and default to 'iso-8859-1' as per the HTTP specification (RFC 2616). Then, assuming we didn't find a character encoding in the HTTP headers (and the HTTP Content-type allowed us to look in the body), we need to sniff the first few bytes of the XML data and try to determine whether the encoding is ASCII-compatible. Section F of the XML specification shows the way here: http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info If the sniffed encoding is not ASCII-compatible, we need to make it ASCII compatible so that we can sniff further into the XML declaration to find the encoding attribute, which will tell us the true encoding. Of course, none of this guarantees that we will be able to parse the feed in the declared character encoding (assuming it was declared correctly, which many are not). CJKCodecs and iconv_codec help a lot; you should definitely install them if you can. http://cjkpython.i18n.org/ ''' def _parseHTTPContentType(content_type): '''takes HTTP Content-Type header and returns (content type, charset) If no charset is specified, returns (content type, '') If no content type is specified, returns ('', '') Both return parameters are guaranteed to be lowercase strings ''' content_type = content_type or '' content_type, params = cgi.parse_header(content_type) return content_type, params.get('charset', '').replace("'", '') sniffed_xml_encoding = '' xml_encoding = '' true_encoding = '' http_content_type, http_encoding = _parseHTTPContentType(http_headers.get('content-type')) # Must sniff for non-ASCII-compatible character encodings before # searching for XML declaration. This heuristic is defined in # section F of the XML specification: # http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info try: if xml_data[:4] == '\x4c\x6f\xa7\x94': # EBCDIC xml_data = _ebcdic_to_ascii(xml_data) elif xml_data[:4] == '\x00\x3c\x00\x3f': # UTF-16BE sniffed_xml_encoding = 'utf-16be' xml_data = unicode(xml_data, 'utf-16be').encode('utf-8') elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') and (xml_data[2:4] != '\x00\x00'): # UTF-16BE with BOM sniffed_xml_encoding = 'utf-16be' xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8') elif xml_data[:4] == '\x3c\x00\x3f\x00': # UTF-16LE sniffed_xml_encoding = 'utf-16le' xml_data = unicode(xml_data, 'utf-16le').encode('utf-8') elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and (xml_data[2:4] != '\x00\x00'): # UTF-16LE with BOM sniffed_xml_encoding = 'utf-16le' xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8') elif xml_data[:4] == '\x00\x00\x00\x3c': # UTF-32BE sniffed_xml_encoding = 'utf-32be' xml_data = unicode(xml_data, 'utf-32be').encode('utf-8') elif xml_data[:4] == '\x3c\x00\x00\x00': # UTF-32LE sniffed_xml_encoding = 'utf-32le' xml_data = unicode(xml_data, 'utf-32le').encode('utf-8') elif xml_data[:4] == '\x00\x00\xfe\xff': # UTF-32BE with BOM sniffed_xml_encoding = 'utf-32be' xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8') elif xml_data[:4] == '\xff\xfe\x00\x00': # UTF-32LE with BOM sniffed_xml_encoding = 'utf-32le' xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8') elif xml_data[:3] == '\xef\xbb\xbf': # UTF-8 with BOM sniffed_xml_encoding = 'utf-8' xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8') else: # ASCII-compatible pass xml_encoding_match = re.compile('^<\?.*encoding=[\'"](.*?)[\'"].*\?>').match(xml_data) except: xml_encoding_match = None if xml_encoding_match: xml_encoding = xml_encoding_match.groups()[0].lower() if sniffed_xml_encoding and (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16')): xml_encoding = sniffed_xml_encoding acceptable_content_type = 0 application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity') text_content_types = ('text/xml', 'text/xml-external-parsed-entity') if (http_content_type in application_content_types) or \ (http_content_type.startswith('application/') and http_content_type.endswith('+xml')): acceptable_content_type = 1 true_encoding = http_encoding or xml_encoding or 'utf-8' elif (http_content_type in text_content_types) or \ (http_content_type.startswith('text/')) and http_content_type.endswith('+xml'): acceptable_content_type = 1 true_encoding = http_encoding or 'us-ascii' elif http_content_type.startswith('text/'): true_encoding = http_encoding or 'us-ascii' elif http_headers and (not http_headers.has_key('content-type')): true_encoding = xml_encoding or 'iso-8859-1' else: true_encoding = xml_encoding or 'utf-8' return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type def _toUTF8(data, encoding): '''Changes an XML data stream on the fly to specify a new encoding data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already encoding is a string recognized by encodings.aliases ''' if _debug: sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding) # strip Byte Order Mark (if present) if (len(data) >= 4) and (data[:2] == '\xfe\xff') and (data[2:4] != '\x00\x00'): if _debug: sys.stderr.write('stripping BOM\n') if encoding != 'utf-16be': sys.stderr.write('trying utf-16be instead\n') encoding = 'utf-16be' data = data[2:] elif (len(data) >= 4) and (data[:2] == '\xff\xfe') and (data[2:4] != '\x00\x00'): if _debug: sys.stderr.write('stripping BOM\n') if encoding != 'utf-16le': sys.stderr.write('trying utf-16le instead\n') encoding = 'utf-16le' data = data[2:] elif data[:3] == '\xef\xbb\xbf': if _debug: sys.stderr.write('stripping BOM\n') if encoding != 'utf-8': sys.stderr.write('trying utf-8 instead\n') encoding = 'utf-8' data = data[3:] elif data[:4] == '\x00\x00\xfe\xff': if _debug: sys.stderr.write('stripping BOM\n') if encoding != 'utf-32be': sys.stderr.write('trying utf-32be instead\n') encoding = 'utf-32be' data = data[4:] elif data[:4] == '\xff\xfe\x00\x00': if _debug: sys.stderr.write('stripping BOM\n') if encoding != 'utf-32le': sys.stderr.write('trying utf-32le instead\n') encoding = 'utf-32le' data = data[4:] newdata = unicode(data, encoding) if _debug: sys.stderr.write('successfully converted %s data to unicode\n' % encoding) declmatch = re.compile('^<\?xml[^>]*?>') newdecl = '''''' if declmatch.search(newdata): newdata = declmatch.sub(newdecl, newdata) else: newdata = newdecl + u'\n' + newdata return newdata.encode('utf-8') def _stripDoctype(data): '''Strips DOCTYPE from XML document, returns (rss_version, stripped_data) rss_version may be 'rss091n' or None stripped_data is the same XML document, minus the DOCTYPE ''' entity_pattern = re.compile(r']*?)>', re.MULTILINE) data = entity_pattern.sub('', data) doctype_pattern = re.compile(r']*?)>', re.MULTILINE) doctype_results = doctype_pattern.findall(data) doctype = doctype_results and doctype_results[0] or '' if doctype.lower().count('netscape'): version = 'rss091n' else: version = None data = doctype_pattern.sub('', data) return version, data def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[]): '''Parse a feed from a URL, file, stream, or string''' result = FeedParserDict() result['feed'] = FeedParserDict() result['entries'] = [] if _XML_AVAILABLE: result['bozo'] = 0 if type(handlers) == types.InstanceType: handlers = [handlers] try: f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers) data = f.read() except Exception, e: result['bozo'] = 1 result['bozo_exception'] = e data = '' f = None # if feed is gzip-compressed, decompress it if f and data and hasattr(f, 'headers'): if gzip and f.headers.get('content-encoding', '') == 'gzip': try: data = gzip.GzipFile(fileobj=_StringIO(data)).read() except Exception, e: # Some feeds claim to be gzipped but they're not, so # we get garbage. Ideally, we should re-request the # feed without the 'Accept-encoding: gzip' header, # but we don't. result['bozo'] = 1 result['bozo_exception'] = e data = '' elif zlib and f.headers.get('content-encoding', '') == 'deflate': try: data = zlib.decompress(data, -zlib.MAX_WBITS) except Exception, e: result['bozo'] = 1 result['bozo_exception'] = e data = '' # save HTTP headers if hasattr(f, 'info'): info = f.info() result['etag'] = info.getheader('ETag') last_modified = info.getheader('Last-Modified') if last_modified: result['modified'] = _parse_date(last_modified) if hasattr(f, 'url'): result['href'] = f.url result['status'] = 200 if hasattr(f, 'status'): result['status'] = f.status if hasattr(f, 'headers'): result['headers'] = f.headers.dict if hasattr(f, 'close'): f.close() # there are four encodings to keep track of: # - http_encoding is the encoding declared in the Content-Type HTTP header # - xml_encoding is the encoding declared in the ; changed # project name #2.5 - 7/25/2003 - MAP - changed to Python license (all contributors agree); # removed unnecessary urllib code -- urllib2 should always be available anyway; # return actual url, status, and full HTTP headers (as result['url'], # result['status'], and result['headers']) if parsing a remote feed over HTTP -- # this should pass all the HTTP tests at ; # added the latest namespace-of-the-week for RSS 2.0 #2.5.1 - 7/26/2003 - RMK - clear opener.addheaders so we only send our custom # User-Agent (otherwise urllib2 sends two, which confuses some servers) #2.5.2 - 7/28/2003 - MAP - entity-decode inline xml properly; added support for # inline and as used in some RSS 2.0 feeds #2.5.3 - 8/6/2003 - TvdV - patch to track whether we're inside an image or # textInput, and also to return the character encoding (if specified) #2.6 - 1/1/2004 - MAP - dc:author support (MarekK); fixed bug tracking # nested divs within content (JohnD); fixed missing sys import (JohanS); # fixed regular expression to capture XML character encoding (Andrei); # added support for Atom 0.3-style links; fixed bug with textInput tracking; # added support for cloud (MartijnP); added support for multiple # category/dc:subject (MartijnP); normalize content model: 'description' gets # description (which can come from description, summary, or full content if no # description), 'content' gets dict of base/language/type/value (which can come # from content:encoded, xhtml:body, content, or fullitem); # fixed bug matching arbitrary Userland namespaces; added xml:base and xml:lang # tracking; fixed bug tracking unknown tags; fixed bug tracking content when # element is not in default namespace (like Pocketsoap feed); # resolve relative URLs in link, guid, docs, url, comments, wfw:comment, # wfw:commentRSS; resolve relative URLs within embedded HTML markup in # description, xhtml:body, content, content:encoded, title, subtitle, # summary, info, tagline, and copyright; added support for pingback and # trackback namespaces #2.7 - 1/5/2004 - MAP - really added support for trackback and pingback # namespaces, as opposed to 2.6 when I said I did but didn't really; # sanitize HTML markup within some elements; added mxTidy support (if # installed) to tidy HTML markup within some elements; fixed indentation # bug in _parse_date (FazalM); use socket.setdefaulttimeout if available # (FazalM); universal date parsing and normalization (FazalM): 'created', modified', # 'issued' are parsed into 9-tuple date format and stored in 'created_parsed', # 'modified_parsed', and 'issued_parsed'; 'date' is duplicated in 'modified' # and vice-versa; 'date_parsed' is duplicated in 'modified_parsed' and vice-versa #2.7.1 - 1/9/2004 - MAP - fixed bug handling " and '. fixed memory # leak not closing url opener (JohnD); added dc:publisher support (MarekK); # added admin:errorReportsTo support (MarekK); Python 2.1 dict support (MarekK) #2.7.4 - 1/14/2004 - MAP - added workaround for improperly formed
tags in # encoded HTML (skadz); fixed unicode handling in normalize_attrs (ChrisL); # fixed relative URI processing for guid (skadz); added ICBM support; added # base64 support #2.7.5 - 1/15/2004 - MAP - added workaround for malformed DOCTYPE (seen on many # blogspot.com sites); added _debug variable #2.7.6 - 1/16/2004 - MAP - fixed bug with StringIO importing #3.0b3 - 1/23/2004 - MAP - parse entire feed with real XML parser (if available); # added several new supported namespaces; fixed bug tracking naked markup in # description; added support for enclosure; added support for source; re-added # support for cloud which got dropped somehow; added support for expirationDate #3.0b4 - 1/26/2004 - MAP - fixed xml:lang inheritance; fixed multiple bugs tracking # xml:base URI, one for documents that don't define one explicitly and one for # documents that define an outer and an inner xml:base that goes out of scope # before the end of the document #3.0b5 - 1/26/2004 - MAP - fixed bug parsing multiple links at feed level #3.0b6 - 1/27/2004 - MAP - added feed type and version detection, result['version'] # will be one of SUPPORTED_VERSIONS.keys() or empty string if unrecognized; # added support for creativeCommons:license and cc:license; added support for # full Atom content model in title, tagline, info, copyright, summary; fixed bug # with gzip encoding (not always telling server we support it when we do) #3.0b7 - 1/28/2004 - MAP - support Atom-style author element in author_detail # (dictionary of 'name', 'url', 'email'); map author to author_detail if author # contains name + email address #3.0b8 - 1/28/2004 - MAP - added support for contributor #3.0b9 - 1/29/2004 - MAP - fixed check for presence of dict function; added # support for summary #3.0b10 - 1/31/2004 - MAP - incorporated ISO-8601 date parsing routines from # xml.util.iso8601 #3.0b11 - 2/2/2004 - MAP - added 'rights' to list of elements that can contain # dangerous markup; fiddled with decodeEntities (not right); liberalized # date parsing even further #3.0b12 - 2/6/2004 - MAP - fiddled with decodeEntities (still not right); # added support to Atom 0.2 subtitle; added support for Atom content model # in copyright; better sanitizing of dangerous HTML elements with end tags # (script, frameset) #3.0b13 - 2/8/2004 - MAP - better handling of empty HTML tags (br, hr, img, # etc.) in embedded markup, in either HTML or XHTML form (
,
,
) #3.0b14 - 2/8/2004 - MAP - fixed CDATA handling in non-wellformed feeds under # Python 2.1 #3.0b15 - 2/11/2004 - MAP - fixed bug resolving relative links in wfw:commentRSS; # fixed bug capturing author and contributor URL; fixed bug resolving relative # links in author and contributor URL; fixed bug resolvin relative links in # generator URL; added support for recognizing RSS 1.0; passed Simon Fell's # namespace tests, and included them permanently in the test suite with his # permission; fixed namespace handling under Python 2.1 #3.0b16 - 2/12/2004 - MAP - fixed support for RSS 0.90 (broken in b15) #3.0b17 - 2/13/2004 - MAP - determine character encoding as per RFC 3023 #3.0b18 - 2/17/2004 - MAP - always map description to summary_detail (Andrei); # use libxml2 (if available) #3.0b19 - 3/15/2004 - MAP - fixed bug exploding author information when author # name was in parentheses; removed ultra-problematic mxTidy support; patch to # workaround crash in PyXML/expat when encountering invalid entities # (MarkMoraes); support for textinput/textInput #3.0b20 - 4/7/2004 - MAP - added CDF support #3.0b21 - 4/14/2004 - MAP - added Hot RSS support #3.0b22 - 4/19/2004 - MAP - changed 'channel' to 'feed', 'item' to 'entries' in # results dict; changed results dict to allow getting values with results.key # as well as results[key]; work around embedded illformed HTML with half # a DOCTYPE; work around malformed Content-Type header; if character encoding # is wrong, try several common ones before falling back to regexes (if this # works, bozo_exception is set to CharacterEncodingOverride); fixed character # encoding issues in BaseHTMLProcessor by tracking encoding and converting # from Unicode to raw strings before feeding data to sgmllib.SGMLParser; # convert each value in results to Unicode (if possible), even if using # regex-based parsing #3.0b23 - 4/21/2004 - MAP - fixed UnicodeDecodeError for feeds that contain # high-bit characters in attributes in embedded HTML in description (thanks # Thijs van de Vossen); moved guid, date, and date_parsed to mapped keys in # FeedParserDict; tweaked FeedParserDict.has_key to return True if asking # about a mapped key #3.0fc1 - 4/23/2004 - MAP - made results.entries[0].links[0] and # results.entries[0].enclosures[0] into FeedParserDict; fixed typo that could # cause the same encoding to be tried twice (even if it failed the first time); # fixed DOCTYPE stripping when DOCTYPE contained entity declarations; # better textinput and image tracking in illformed RSS 1.0 feeds #3.0fc2 - 5/10/2004 - MAP - added and passed Sam's amp tests; added and passed # my blink tag tests #3.0fc3 - 6/18/2004 - MAP - fixed bug in _changeEncodingDeclaration that # failed to parse utf-16 encoded feeds; made source into a FeedParserDict; # duplicate admin:generatorAgent/@rdf:resource in generator_detail.url; # added support for image; refactored parse() fallback logic to try other # encodings if SAX parsing fails (previously it would only try other encodings # if re-encoding failed); remove unichr madness in normalize_attrs now that # we're properly tracking encoding in and out of BaseHTMLProcessor; set # feed.language from root-level xml:lang; set entry.id from rdf:about; # send Accept header #3.0 - 6/21/2004 - MAP - don't try iso-8859-1 (can't distinguish between # iso-8859-1 and windows-1252 anyway, and most incorrectly marked feeds are # windows-1252); fixed regression that could cause the same encoding to be # tried twice (even if it failed the first time) #3.0.1 - 6/22/2004 - MAP - default to us-ascii for all text/* content types; # recover from malformed content-type header parameter with no equals sign # ('text/xml; charset:iso-8859-1') #3.1 - 6/28/2004 - MAP - added and passed tests for converting HTML entities # to Unicode equivalents in illformed feeds (aaronsw); added and # passed tests for converting character entities to Unicode equivalents # in illformed feeds (aaronsw); test for valid parsers when setting # XML_AVAILABLE; make version and encoding available when server returns # a 304; add handlers parameter to pass arbitrary urllib2 handlers (like # digest auth or proxy support); add code to parse username/password # out of url and send as basic authentication; expose downloading-related # exceptions in bozo_exception (aaronsw); added __contains__ method to # FeedParserDict (aaronsw); added publisher_detail (aaronsw) #3.2 - 7/3/2004 - MAP - use cjkcodecs and iconv_codec if available; always # convert feed to UTF-8 before passing to XML parser; completely revamped # logic for determining character encoding and attempting XML parsing # (much faster); increased default timeout to 20 seconds; test for presence # of Location header on redirects; added tests for many alternate character # encodings; support various EBCDIC encodings; support UTF-16BE and # UTF16-LE with or without a BOM; support UTF-8 with a BOM; support # UTF-32BE and UTF-32LE with or without a BOM; fixed crashing bug if no # XML parsers are available; added support for 'Content-encoding: deflate'; # send blank 'Accept-encoding: ' header if neither gzip nor zlib modules # are available #3.3 - 7/15/2004 - MAP - optimize EBCDIC to ASCII conversion; fix obscure # problem tracking xml:base and xml:lang if element declares it, child # doesn't, first grandchild redeclares it, and second grandchild doesn't; # refactored date parsing; defined public registerDateHandler so callers # can add support for additional date formats at runtime; added support # for OnBlog, Nate, MSSQL, Greek, and Hungarian dates (ytrewq1); added # zopeCompatibilityHack() which turns FeedParserDict into a regular # dictionary, required for Zope compatibility, and also makes command- # line debugging easier because pprint module formats real dictionaries # better than dictionary-like objects; added NonXMLContentType exception, # which is stored in bozo_exception when a feed is served with a non-XML # media type such as 'text/plain'; respect Content-Language as default # language if not xml:lang is present; cloud dict is now FeedParserDict; # generator dict is now FeedParserDict; better tracking of xml:lang, # including support for xml:lang='' to unset the current language; # recognize RSS 1.0 feeds even when RSS 1.0 namespace is not the default # namespace; don't overwrite final status on redirects (scenarios: # redirecting to a URL that returns 304, redirecting to a URL that # redirects to another URL with a different type of redirect); add # support for HTTP 303 redirects #4.0 - MAP - support for relative URIs in xml:base attribute; fixed # encoding issue with mxTidy (phopkins); preliminary support for RFC 3229; # support for Atom 1.0; support for iTunes extensions; new 'tags' for # categories/keywords/etc. as array of dict # {'term': term, 'scheme': scheme, 'label': label} to match Atom 1.0 # terminology; parse RFC 822-style dates with no time; lots of other # bug fixes #4.1 - MAP - removed socket timeout; added support for chardet library PIDA-0.5.1/pida/utils/firstrun.py0000644000175000017500000001100110652670605014631 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from pida.core.environment import get_pixmap_path pida_icon = gtk.Image() pida_icon.set_from_file(get_pixmap_path('pida-icon.png')) class FirstTimeWindow(object): def __init__(self, editors): self.win = gtk.Dialog(parent=None, title='PIDA First Run Wizard', buttons=(gtk.STOCK_QUIT, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) hbox = gtk.HBox(spacing=12) hbox.set_border_width(12) self.win.vbox.pack_start(hbox) logo = gtk.Image() logofrm = gtk.Alignment(0, 0, 0.1, 0.1) logofrm.add(pida_icon) hbox.pack_start(logofrm, padding=8) box = gtk.VBox() hbox.pack_start(box, padding=8) s = ('It seems this is the first time ' 'you are running Pida.\n\nPlease select an editor:') l = gtk.Label() l.set_markup(s) box.pack_start(l, expand=False, padding=8) self.radio = gtk.RadioButton() self.editors = {} for editor in editors: self.editors[editor.get_label_cls()] = editor.get_name_cls() ebox = gtk.HBox(spacing=6) box.pack_start(ebox, expand=False, padding=4) radio = gtk.RadioButton(self.radio, label=editor.get_label_cls()) ebox.pack_start(radio) cbox = gtk.VBox(spacing=3) label = gtk.Label() label.set_alignment(1, 0.5) cbox.pack_start(label) ebox.pack_start(cbox, padding=4, expand=False) sanitybut = gtk.Button(label='Check') ebox.pack_start(sanitybut, expand=False, padding=1) sanitybut.connect('clicked', self.cb_sanity, editor, radio, label) self.cb_sanity(sanitybut, editor, radio, label) self.radio = radio bbox = gtk.HBox() box.pack_start(bbox, expand=False, padding=4) def run(self, filename): self.win.show_all() response = self.win.run() self.win.hide_all() editor_name = self.get_editor_option() self.win.destroy() # Only write the token file if we want the user chose something success = (response == gtk.RESPONSE_ACCEPT) if success: self.write_file(filename) return (success, editor_name) def cb_sanity(self, button, component, radio, label): errs = component.get_sanity_errors() if errs: radio.set_sensitive(False) radio.set_active(False) s = '\n'.join(errs) label.set_markup('' '%s' % s) else: radio.set_sensitive(True) radio.set_active(True) label.set_markup('' 'Okay to use') button.set_sensitive(False) def get_editor_option(self, *args): for radio in self.radio.get_group(): if radio.get_active(): editor = radio.get_label() return self.editors[editor] def write_file(self, filename): f = open(filename, 'w') f. write('#Remove this to rerun the start wizard\n\n') f.close() if __name__ == '__main__': ftw = FirstTimeWindow([]) print ftw.run('/home/ali/firstrun') # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/grpc.py0000644000175000017500000001213210652670605013716 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os, socket, cPickle import gobject from kiwi.utils import gsignal class NotStartedError(RuntimeError): """Reactor has not yet been started""" class Reactor(gobject.GObject): gsignal('received', str, int, str, object) def __init__(self, port, host=''): gobject.GObject.__init__(self) self.host = host self.port = port self.socket = None def start(self): self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: self.socket.bind((self.host, self.port)) gobject.io_add_watch(self.socket, gobject.IO_IN, self._on_socket_read) except socket.error: self.port += 1 self.start() def stop(self): pass #os.unlink(self.socketfile) def call_local(self, host, port, command, args): self.emit('received', host, port, command, args) def call_remote(self, host, port, command, args): self._send(host, port, self._encode_msg(command, args)) def _on_socket_read(self, socket, condition): if condition == gobject.IO_IN: data, (host, port) = socket.recvfrom(6024) self._received_data(data, host, port) return True def _send(self, host, port, data): if self.socket is None: raise NotStartedError self.socket.sendto(data, (host, port)) def _encode_msg(self, command, args): return cPickle.dumps((command, args)) def _decode_msg(self, msg): return cPickle.loads(msg) def _received_data(self, data, host, port): self.call_local(host, port, *self._decode_msg(data)) class ServerReactor(Reactor): """A server""" def __init__(self, port, host=''): Reactor.__init__(self, port, host) self.start() class LocalServerReactor(ServerReactor): """A local only server""" def __init__(self, port): ServerReactor.__init__(self, port, '127.0.0.1') class ClientReactor(Reactor): """A Client""" def __init__(self, remote_port, remote_host): Reactor.__init__(self, 0) self.r_host = remote_host self.r_port = remote_port self.start() def call_server(self, command, args): Reactor.call_remote(self, self.r_host, self.r_port, command, args) class LocalClientReactor(ClientReactor): """A local only client""" def __init__(self, remote_port): ClientReactor.__init__(self, remote_port, '127.0.0.1') class Dispatcher(object): def __init__(self, reactor): self.reactor = reactor self.reactor.connect('received', self._on_received) def _on_received(self, reactor, host, port, command, args): command_name = 'remote_%s' % command command_call = getattr(self, command_name, None) if command_call is not None: command_call(*args) if command not in ['error', 'ok']: self.call_remote(host, port, 'ok', (command,)) else: self.call_remote(host, port, 'error', (command,)) def call_remote(self, host, port, command, args): self.reactor.call_remote(host, port, command, args) def remote_ok(self, command): print 'OK', command def remote_error(self, command): print 'ERROR', command class LocalServerDispatcher(Dispatcher): def __init__(self, port): Dispatcher.__init__(self, LocalServerReactor(port)) class LocalClientDispatcher(Dispatcher): def __init__(self, remote_port): Dispatcher.__init__(self, LocalClientReactor(remote_port)) def call_server(self, command, args): self.reactor.call_server(command, args) gobject.type_register(Reactor) if __name__ == '__main__': import sys if sys.argv[-1] == 'c': r = LocalClientDispatcher(9000) print 'client' r.call_server('hello', ()) import gtk gtk.main() else: r = LocalServerDispatcher(9000) import gtk gtk.main() # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/gthreads.py0000644000175000017500000001645510652670605014600 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Copyright (c) 2006 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import threading, thread import subprocess import gobject class AsyncTask(object): """ AsyncTask is used to help you perform lengthy tasks without delaying the UI loop cycle, causing the app to look frozen. It is also assumed that each action that the async worker performs cancels the old one (if it's still working), thus there's no problem when the task takes too long. You can either extend this class or pass two callable objects through its constructor. The first on is the 'work_callback' this is where the lengthy operation must be performed. This object may return an object or a group of objects, these will be passed onto the second callback 'loop_callback'. You must be aware on how the argument passing is done. If you return an object that is not a tuple then it's passed directly to the loop callback. If you return `None` no arguments are supplied. If you return a tuple object then these will be the arguments sent to the loop callback. The loop callback is called inside Gtk+'s main loop and it's where you should stick code that affects the UI. """ def __init__(self, work_callback=None, loop_callback=None): self.counter = 0 if work_callback is not None: self.work_callback = work_callback if loop_callback is not None: self.loop_callback = loop_callback def start(self, *args, **kwargs): """ Please note that start is not thread safe. It is assumed that this method is called inside gtk's main loop there for the lock is taken care there. """ args = (self.counter,) + args threading.Thread(target=self._work_callback, args=args, kwargs=kwargs).start() def work_callback(self): pass def loop_callback(self): pass def _work_callback(self, counter, *args, **kwargs): ret = self.work_callback(*args, **kwargs) gobject.idle_add(self._loop_callback, (counter, ret)) def _loop_callback(self, vargs): counter, ret = vargs if counter != self.counter: return if ret is None: ret = () if not isinstance(ret, tuple): ret = (ret,) self.loop_callback(*ret) class GeneratorTask(AsyncTask): """ The diference between this task and AsyncTask is that the 'work_callback' returns a generator. For each value the generator yields the loop_callback is called inside Gtk+'s main loop. A simple example:: def work(): for i in range(10000): yield i def loop(val): print val gt = GeneratorTask(work, loop) gt.start() import gtk gtk.main() """ def __init__(self, work_callback, loop_callback, complete_callback=None): AsyncTask.__init__(self, work_callback, loop_callback) self._complete_callback = complete_callback def _work_callback(self, counter, *args, **kwargs): self._stopped = False for ret in self.work_callback(*args, **kwargs): if self._stopped: thread.exit() gobject.idle_add(self._loop_callback, (counter, ret)) if self._complete_callback is not None: gobject.idle_add(self._complete_callback) def stop(self): self._stopped = True class GeneratorSubprocessTask(GeneratorTask): """ A Generator Task for launching a subprocess An example (inside thread_inited gtk main loop): def output(line): print line task = GeneratorSubprocessTask(output) task.start(['ls', '-al']) """ def __init__(self, stdout_callback, complete_callback=None): GeneratorTask.__init__(self, self.start_process, stdout_callback, complete_callback) def start_process(self, commandargs, **spargs): self._process = subprocess.Popen( commandargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, **spargs ) for line in self._process.stdout: yield line.strip() def stop(self): GeneratorTask.stop(self) try: if hasattr(self, '_process'): os.kill(self._process.pid, 9) except OSError: pass def locked(lockname): ''' Call this decorator with the name of the lock. The decorated method will be wrapped with an acquire()/lock(). Example of usage:: import threading class Foo(object): def __init__(self): self.lock = threading.Lock() @locked("lock") def meth1(self): self.critical_value = 1 @locked("lock") def meth2(self): self.critical_value = 2 return self.critical_value Both 'meth1' and 'meth2' will be wrapped with a 'lock.acquire()' and a 'lock.release()'. ''' def locked(lock_name): """This is a factory of decorators. The decorator wraps an acquire() and release() around the decorated method. The lock name is the name of the attribute containing the lock.""" if not isinstance(lock_name, basestring): raise TypeError("'lock_name' must be a string") def decorator(func): def wrapper(self, *args, **kwargs): lock = getattr(self, lock_name) lock.acquire() # Make sure an exception does not break # the lock try: ret = func(self, *args, **kwargs) except: lock.release() raise lock.release() return ret return wrapper return decorator def gcall(func, *args, **kwargs): """ Calls a function, with the given arguments inside Gtk's main loop. Example:: gcall(lbl.set_text, "foo") If this call would be made in a thread there could be problems, using it inside Gtk's main loop makes it thread safe. """ return gobject.idle_add(lambda: (func(*args, **kwargs) or False)) PIDA-0.5.1/pida/utils/path.py0000644000175000017500000000521210652670605013720 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os def get_relative_path(from_path, to_path): """Get the relative path to to_path from from_path""" from_list = from_path.split(os.sep) to_list = to_path.split(os.sep) final_list = list(to_list) common = [] uncommon = [] if len(to_list) > len(from_list): for i, segment in enumerate(from_list): if to_list[i] == segment: final_list.pop(0) else: return None return final_list else: return None def walktree(top = ".", depthfirst = True, skipped_directory = []): """Walk the directory tree, starting from top. Credit to Noah Spurrier and Doug Fort.""" import os, stat names = os.listdir(top) if not depthfirst: yield top, names for name in names: try: st = os.lstat(os.path.join(top, name)) except os.error: continue if stat.S_ISDIR(st.st_mode): if name in skipped_directory: continue for (newtop, children) in walktree (os.path.join(top, name), depthfirst, skipped_directory): yield newtop, children if depthfirst: names = [name for name in names if name not in skipped_directory] yield top, names if __name__ == '__main__': print get_relative_path('/a/b/c/d', '/a/b/c1/d1') print get_relative_path('/a/b/c/d', '/a/b/c/d/e/f') print get_relative_path('/a/b/c/d', '/a/b/c/d1') print get_relative_path('/a/b/c/d', '/a/b/c') # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida/utils/pyconsole.py0000644000175000017500000004411610652670605015005 0ustar aliali# # pyconsole.py # # Copyright (C) 2004-2005 by Yevgen Muntyan # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # See COPYING file that comes with this distribution. # # This module 'runs' python interpreter in a TextView widget. # The main class is Console, usage is: # Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') - # it creates the widget and 'starts' interactive session; see the end of # this file. If start_script is not empty, it pastes it as it was entered from keyboard. # # This widget is not a replacement for real terminal with python running # inside: GtkTextView is not a terminal. # The use case is: you have a python program, you create this widget, # and inspect your program interiors. import gtk import gtk.gdk as gdk import gobject import pango import gtk.keysyms as keys import code import sys import keyword import re # commonprefix() from posixpath def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" if not m: return '' prefix = m[0] for item in m: for i in range(len(prefix)): if prefix[:i+1] != item[:i+1]: prefix = prefix[:i] if i == 0: return '' break return prefix class _ReadLine(object): class History(object): def __init__(self): object.__init__(self) self.items = [''] self.ptr = 0 self.edited = {} def commit(self, text): if text and self.items[-1] != text: self.items.append(text) self.ptr = 0 self.edited = {} def get(self, dir, text): if len(self.items) == 1: return None if text != self.items[self.ptr]: self.edited[self.ptr] = text elif self.edited.has_key(self.ptr): del self.edited[self.ptr] self.ptr = self.ptr + dir if self.ptr >= len(self.items): self.ptr = 0 elif self.ptr < 0: self.ptr = len(self.items) - 1 try: return self.edited[self.ptr] except KeyError: return self.items[self.ptr] def __init__(self): object.__init__(self) self.set_wrap_mode(gtk.WRAP_CHAR) self.modify_font(pango.FontDescription("Monospace")) self.buffer = self.get_buffer() self.buffer.connect("insert-text", self.on_buf_insert) self.buffer.connect("delete-range", self.on_buf_delete) self.buffer.connect("mark-set", self.on_buf_mark_set) self.do_insert = False self.do_delete = False self.cursor = self.buffer.create_mark("cursor", self.buffer.get_start_iter(), False) insert = self.buffer.get_insert() self.cursor.set_visible(True) insert.set_visible(False) self.ps = '' self.in_raw_input = False self.run_on_raw_input = None self.tab_pressed = 0 self.history = _ReadLine.History() self.nonword_re = re.compile("[^\w\._]") def freeze_undo(self): try: self.begin_not_undoable_action() except: pass def thaw_undo(self): try: self.end_not_undoable_action() except: pass def raw_input(self, ps=None): if ps: self.ps = ps else: self.ps = '' iter = self.buffer.get_iter_at_mark(self.buffer.get_insert()) if ps: self.freeze_undo() self.buffer.insert(iter, self.ps) self.thaw_undo() self.__move_cursor_to(iter) self.scroll_to_mark(self.cursor, 0.2) self.in_raw_input = True if self.run_on_raw_input: run_now = self.run_on_raw_input self.run_on_raw_input = None self.buffer.insert_at_cursor(run_now + '\n') def on_buf_mark_set(self, buffer, iter, mark): if not mark is buffer.get_insert(): return start = self.__get_start() end = self.__get_end() if iter.compare(self.__get_start()) >= 0 and \ iter.compare(self.__get_end()) <= 0: buffer.move_mark_by_name("cursor", iter) self.scroll_to_mark(self.cursor, 0.2) def __insert(self, iter, text): self.do_insert = True self.buffer.insert(iter, text) self.do_insert = False def on_buf_insert(self, buf, iter, text, len): if not self.in_raw_input or self.do_insert or not len: return buf.stop_emission("insert-text") lines = text.splitlines() need_eol = False for l in lines: if need_eol: self.__commit() iter = self.__get_cursor() else: cursor = self.__get_cursor() if iter.compare(self.__get_start()) < 0: iter = cursor elif iter.compare(self.__get_end()) > 0: iter = cursor else: self.__move_cursor_to(iter) need_eol = True self.__insert(iter, l) self.__move_cursor(0) def __delete(self, start, end): self.do_delete = True self.buffer.delete(start, end) self.do_delete = False def on_buf_delete(self, buf, start, end): if not self.in_raw_input or self.do_delete: return buf.stop_emission("delete-range") start.order(end) line_start = self.__get_start() line_end = self.__get_end() if start.compare(line_end) > 0: return if end.compare(line_start) < 0: return self.__move_cursor(0) if start.compare(line_start) < 0: start = line_start if end.compare(line_end) > 0: end = line_end self.__delete(start, end) def do_key_press_event(self, event, parent_type): if not self.in_raw_input: return parent_type.do_key_press_event(self, event) tab_pressed = self.tab_pressed self.tab_pressed = 0 handled = True state = event.state & (gdk.SHIFT_MASK | gdk.CONTROL_MASK | gdk.MOD1_MASK) keyval = event.keyval if not state: if keyval == keys.Return: self.__commit() elif keyval == keys.Up: self.__history(-1) elif keyval == keys.Down: self.__history(1) elif keyval == keys.Left: self.__move_cursor(-1) elif keyval == keys.Right: self.__move_cursor(1) elif keyval == keys.Home: self.__move_cursor(-10000) elif keyval == keys.End: self.__move_cursor(10000) elif keyval == keys.Tab: self.tab_pressed = tab_pressed + 1 self.__complete() else: handled = False elif state == gdk.CONTROL_MASK: if keyval == keys.u: start = self.__get_start() end = self.__get_cursor() self.__delete(start, end) else: handled = False else: handled = False if not handled: return parent_type.do_key_press_event(self, event) else: return True def __history(self, dir): text = self.__get_line() new_text = self.history.get(dir, text) if not new_text is None: self.__replace_line(new_text) self.__move_cursor(0) self.scroll_to_mark(self.cursor, 0.2) def __get_cursor(self): return self.buffer.get_iter_at_mark(self.cursor) def __get_start(self): iter = self.__get_cursor() iter.set_line_offset(len(self.ps)) return iter def __get_end(self): iter = self.__get_cursor() if not iter.ends_line(): iter.forward_to_line_end() return iter def __get_text(self, start, end): return self.buffer.get_text(start, end, False) def __move_cursor_to(self, iter): self.buffer.place_cursor(iter) self.buffer.move_mark_by_name("cursor", iter) def __move_cursor(self, howmany): iter = self.__get_cursor() end = self.__get_cursor() if not end.ends_line(): end.forward_to_line_end() line_len = end.get_line_offset() move_to = iter.get_line_offset() + howmany move_to = min(max(move_to, len(self.ps)), line_len) iter.set_line_offset(move_to) self.__move_cursor_to(iter) def __delete_at_cursor(self, howmany): iter = self.__get_cursor() end = self.__get_cursor() if not end.ends_line(): end.forward_to_line_end() line_len = end.get_line_offset() erase_to = iter.get_line_offset() + howmany if erase_to > line_len: erase_to = line_len elif erase_to < len(self.ps): erase_to = len(self.ps) end.set_line_offset(erase_to) self.__delete(iter, end) def __get_width(self): if not (self.flags() & gtk.REALIZED): return 80 layout = pango.Layout(self.get_pango_context()) letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" layout.set_text(letters) pix_width = layout.get_pixel_size()[0] return self.allocation.width * len(letters) / pix_width def __print_completions(self, completions): line_start = self.__get_text(self.__get_start(), self.__get_cursor()) line_end = self.__get_text(self.__get_cursor(), self.__get_end()) iter = self.buffer.get_end_iter() self.__move_cursor_to(iter) self.__insert(iter, "\n") width = max(self.__get_width(), 4) max_width = max([len(s) for s in completions]) n_columns = max(int(width / (max_width + 1)), 1) col_width = int(width / n_columns) total = len(completions) col_length = total / n_columns if total % n_columns: col_length = col_length + 1 col_length = max(col_length, 1) if col_length == 1: n_columns = total col_width = width / total for i in range(col_length): for j in range(n_columns): ind = i + j*col_length if ind < total: if j == n_columns - 1: n_spaces = 0 else: n_spaces = col_width - len(completions[ind]) self.__insert(iter, completions[ind] + " " * n_spaces) self.__insert(iter, "\n") self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end)) iter.set_line_offset(len(self.ps) + len(line_start)) self.__move_cursor_to(iter) self.scroll_to_mark(self.cursor, 0.2) def __complete(self): text = self.__get_text(self.__get_start(), self.__get_cursor()) start = '' word = text nonwords = self.nonword_re.findall(text) if nonwords: last = text.rfind(nonwords[-1]) + len(nonwords[-1]) start = text[:last] word = text[last:] completions = self.complete(word) if completions: prefix = commonprefix(completions) if prefix != word: start_iter = self.__get_start() start_iter.forward_chars(len(start)) end_iter = start_iter.copy() end_iter.forward_chars(len(word)) self.__delete(start_iter, end_iter) self.__insert(end_iter, prefix) elif self.tab_pressed > 1: self.freeze_undo() self.__print_completions(completions) self.thaw_undo() self.tab_pressed = 0 def complete(self, text): return None def __get_line(self): start = self.__get_start() end = self.__get_end() return self.buffer.get_text(start, end, False) def __replace_line(self, new_text): start = self.__get_start() end = self.__get_end() self.__delete(start, end) self.__insert(end, new_text) def __commit(self): end = self.__get_cursor() if not end.ends_line(): end.forward_to_line_end() text = self.__get_line() self.__move_cursor_to(end) self.freeze_undo() self.__insert(end, "\n") self.in_raw_input = False self.history.commit(text) self.do_raw_input(text) self.thaw_undo() def do_raw_input(self, text): pass def write(self,whatever): self.buffer.insert_at_cursor(whatever) class _Console(_ReadLine, code.InteractiveInterpreter): def __init__(self, locals=None, banner=None, completer=None, use_rlcompleter=True, start_script=None): _ReadLine.__init__(self) code.InteractiveInterpreter.__init__(self, locals) self.locals['__console__'] = self self.start_script = start_script self.completer = completer self.banner = banner if not self.completer and use_rlcompleter: try: import rlcompleter self.completer = rlcompleter.Completer() except ImportError: pass self.ps1 = ">>> " self.ps2 = "... " self.__start() self.run_on_raw_input = start_script self.raw_input(self.ps1) def __start(self): self.cmd_buffer = "" self.freeze_undo() self.thaw_undo() self.buffer.set_text("") if self.banner: iter = self.buffer.get_start_iter() self.buffer.insert(iter, self.banner) if not iter.starts_line(): self.buffer.insert(iter, "\n") def clear(self, start_script=None): if start_script is None: start_script = self.start_script else: self.start_script = start_script self.__start() self.run_on_raw_input = start_script def do_raw_input(self, text): if self.cmd_buffer: cmd = self.cmd_buffer + "\n" + text else: cmd = text if self.runsource(cmd): self.cmd_buffer = cmd ps = self.ps2 else: self.cmd_buffer = '' ps = self.ps1 self.raw_input(ps) def runcode(self, code): saved = sys.stdout sys.stdout = self try: eval(code, self.locals) except SystemExit: raise except: self.showtraceback() sys.stdout = saved def complete_attr(self, start, end): try: obj = eval(start, self.locals) strings = dir(obj) if end: completions = {} for s in strings: if s.startswith(end): completions[s] = None completions = completions.keys() else: completions = strings completions.sort() return [start + "." + s for s in completions] except: return None def complete(self, text): if self.completer: completions = [] i = 0 try: while 1: s = self.completer.complete(text, i) if s: completions.append(s) i = i + 1 else: completions.sort() return completions except NameError: return None dot = text.rfind(".") if dot >= 0: return self.complete_attr(text[:dot], text[dot+1:]) completions = {} strings = keyword.kwlist if self.locals: strings.extend(self.locals.keys()) try: strings.extend(eval("globals()", self.locals).keys()) except: pass try: exec "import __builtin__" in self.locals strings.extend(eval("dir(__builtin__)", self.locals)) except: pass for s in strings: if s.startswith(text): completions[s] = None completions = completions.keys() completions.sort() return completions def ReadLineType(t=gtk.TextView): class readline(t, _ReadLine): def __init__(self, *args, **kwargs): t.__init__(self) _ReadLine.__init__(self, *args, **kwargs) def do_key_press_event(self, event): return _ReadLine.do_key_press_event(self, event, t) gobject.type_register(readline) return readline def ConsoleType(t=gtk.TextView): class console(t, _Console): def __init__(self, *args, **kwargs): t.__init__(self) _Console.__init__(self, *args, **kwargs) def do_key_press_event(self, event): return _Console.do_key_press_event(self, event, t) gobject.type_register(console) return console ReadLine = ReadLineType() Console = ConsoleType() if __name__ == '__main__': window = gtk.Window() swin = gtk.ScrolledWindow() swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS) window.add(swin) swin.add(Console(banner="Hello there!", use_rlcompleter=False, start_script="import gtk\n" + \ "win = gtk.Window()\n" + \ "label = gtk.Label('Hello there!')\n" + \ "win.add(label)\n" + \ "win.show_all()\n")) window.set_default_size(400, 300) window.show_all() if not gtk.main_level(): window.connect("destroy", gtk.main_quit) gtk.main() # kate: space-indent on; indent-width 4; strip on; PIDA-0.5.1/pida/utils/pythonparser.py0000644000175000017500000006024610652670605015532 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: #Bicycle Repair Man - the Python refactoring browser toolkit. # #Copyright (C) 2001-2006 Phil Dawes # #(also see AUTHORS file for other contributors) #Maintainer #---------- #Phil Dawes #The following people have contributed code, bugfixes and patches: #----------------------------------------------------------------- #Shae Erisson - Original Maintainer # (although I don't think any of the original code now remains - sorry Shae!) #Jürgen Hermann #Canis Lupus #Syver Enstad Windows emacs patches #Mathew Yeates VIM support and bug fixes #Marius Gedminas More VIM support #François Pinard Pymacs + help with # emacs integration #Ender #Jonathan #Steve #Peter Astrand #Adam Feuer #Troy Frever #Aaron Bingham #See ChangeLog for more details. #This library is free software; you can redistribute it and/or modify #it under the terms of the GNU Lesser General Public License as #published by the Free Software Foundation; either version 2.1 of the #License, or (at your option) any later version. #This library is distributed in the hope that it will be useful, but #WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #Lesser General Public License for more details. #[1] http://www.gnu.org/licenses/lgpl.txt #Copyright (c) 2005 Ali Afshar aafshar@gmail.com #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # bike.transformer.save outputqueue = {} def resetOutputQueue(): global outputqueue outputqueue = {} # bike.parsing.parserutils #from __future__ import generators import re escapedQuotesRE = re.compile(r"(\\\\|\\\"|\\\')") # changess \" \' and \\ into ** so that text searches # for " and ' won't hit escaped ones def maskEscapedQuotes(src): return escapedQuotesRE.sub("**", src) stringsAndCommentsRE = \ re.compile("(\"\"\".*?\"\"\"|'''.*?'''|\"[^\"]*\"|\'[^\']*\'|#.*?\n)", re.DOTALL) import string #transtable = string.maketrans('classdefifforwhiletry', "*********************") # performs a transformation on all of the comments and strings so that # text searches for python keywords won't accidently find a keyword in # a string or comment def maskPythonKeywordsInStringsAndComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): allstrings[i] = allstrings[i].upper() #allstrings[i] = allstrings[i].translate(transtable) return "".join(allstrings) allchars = string.maketrans("", "") allcharsExceptNewline = allchars[: allchars.index('\n')]+allchars[allchars.index('\n')+1:] allcharsExceptNewlineTranstable = string.maketrans(allcharsExceptNewline, '*'*len(allcharsExceptNewline)) # replaces all chars in a string or a comment with * (except newlines). # this ensures that text searches don't mistake comments for keywords, and that all # matches are in the same line/comment as the original def maskStringsAndComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): if allstrings[i].startswith("'''")or allstrings[i].startswith('"""'): allstrings[i] = allstrings[i][:3]+ \ allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-3:] else: allstrings[i] = allstrings[i][0]+ \ allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-1] return "".join(allstrings) # replaces all chars in a string or a comment with * (except newlines). # this ensures that text searches don't mistake comments for keywords, and that all # matches are in the same line/comment as the original def maskStringsAndRemoveComments(src): src = escapedQuotesRE.sub("**", src) allstrings = stringsAndCommentsRE.split(src) # every odd element is a string or comment for i in xrange(1, len(allstrings), 2): if allstrings[i].startswith("'''")or allstrings[i].startswith('"""'): allstrings[i] = allstrings[i][:3]+ \ allstrings[i][3:-3].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-3:] elif allstrings[i].startswith("#"): allstrings[i] = '\n' else: allstrings[i] = allstrings[i][0]+ \ allstrings[i][1:-1].translate(allcharsExceptNewlineTranstable)+ \ allstrings[i][-1] return "".join(allstrings) implicitContinuationChars = (('(', ')'), ('[', ']'), ('{', '}')) emptyHangingBraces = [0,0,0,0,0] linecontinueRE = re.compile(r"\\\s*(#.*)?$") multiLineStringsRE = \ re.compile("(^.*?\"\"\".*?\"\"\".*?$|^.*?'''.*?'''.*?$)", re.DOTALL) #def splitLogicalLines(src): # src = multiLineStringsRE.split(src) # splits the string into logical lines. This requires the comments to # be removed, and strings masked (see other fns in this module) def splitLogicalLines(src): physicallines = src.splitlines(1) return [x for x in generateLogicalLines(physicallines)] class UnbalancedBracesException: pass # splits the string into logical lines. This requires the strings # masked (see other fns in this module) # Physical Lines *Must* start on a non-continued non-in-a-comment line # (although detects unbalanced braces) def generateLogicalLines(physicallines): tmp = [] hangingBraces = list(emptyHangingBraces) hangingComments = 0 for line in physicallines: # update hanging braces for i in range(len(implicitContinuationChars)): contchar = implicitContinuationChars[i] numHanging = hangingBraces[i] hangingBraces[i] = numHanging+line.count(contchar[0]) - \ line.count(contchar[1]) hangingComments ^= line.count('"""') % 2 hangingComments ^= line.count("'''") % 2 if hangingBraces[0] < 0 or \ hangingBraces[1] < 0 or \ hangingBraces[2] < 0: raise UnbalancedBracesException() if linecontinueRE.search(line): tmp.append(line) elif hangingBraces != emptyHangingBraces: tmp.append(line) elif hangingComments: tmp.append(line) else: tmp.append(line) yield "".join(tmp) tmp = [] # see above but yields (line,linenum) # needs physicallines to have linenum attribute # TODO: refactor with previous function def generateLogicalLinesAndLineNumbers(physicallines): tmp = [] hangingBraces = list(emptyHangingBraces) hangingComments = 0 linenum = None for line in physicallines: if tmp == []: linenum = line.linenum # update hanging braces for i in range(len(implicitContinuationChars)): contchar = implicitContinuationChars[i] numHanging = hangingBraces[i] hangingBraces[i] = numHanging+line.count(contchar[0]) - \ line.count(contchar[1]) hangingComments ^= line.count('"""') % 2 hangingComments ^= line.count("'''") % 2 if linecontinueRE.search(line): tmp.append(line) elif hangingBraces != emptyHangingBraces: tmp.append(line) elif hangingComments: tmp.append(line) else: tmp.append(line) yield "".join(tmp),linenum tmp = [] # takes a line of code, and decorates it with noops so that it can be # parsed by the python compiler. # e.g. "if foo:" -> "if foo: pass" # returns the line, and the adjustment made to the column pos of the first char # line must have strings and comments masked # # N.B. it only inserts keywords whitespace and 0's notSpaceRE = re.compile("\s*(\S)") commentRE = re.compile("#.*$") def makeLineParseable(line): return makeLineParseableWhenCommentsRemoved(commentRE.sub("",line)) def makeLineParseableWhenCommentsRemoved(line): line = line.strip() if ":" in line: if line.endswith(":"): line += " pass" if line.startswith("try"): line += "\nexcept: pass" elif line.startswith("except") or line.startswith("finally"): line = "try: pass\n" + line return line elif line.startswith("else") or line.startswith("elif"): line = "if 0: pass\n" + line return line elif line.startswith("yield"): return ("return"+line[5:]) return line # bike.parsing.parserast #from __future__ import generators #from parserutils import generateLogicalLines, maskStringsAndComments, maskStringsAndRemoveComments import re import os import compiler #from bike.transformer.save import resetOutputQueue TABWIDTH = 4 classNameRE = re.compile("^\s*class\s+(\w+)") fnNameRE = re.compile("^\s*def\s+(\w+)") _root = None def getRoot(): global _root if _root is None: resetRoot() return _root def resetRoot(root = None): global _root _root = root or Root() _root.unittestmode = False resetOutputQueue() def getModule(filename_path): from bike.parsing.load import CantLocateSourceNodeException, getSourceNode try: sourcenode = getSourceNode(filename_path) return sourcenode.fastparseroot except CantLocateSourceNodeException: return None def getPackage(directory_path): from bike.parsing.pathutils import getRootDirectory rootdir = getRootDirectory(directory_path) if rootdir == directory_path: return getRoot() else: return Package(directory_path, os.path.basename(directory_path)) class Root: def __init__(self, pythonpath = None): # singleton hack to allow functions in query package to appear # 'stateless' resetRoot(self) # this is to get round a python optimisation which reuses an # empty list as a default arg. unfortunately the client of # this method may fill that list, so it's not empty if not pythonpath: pythonpath = [] self.pythonpath = pythonpath def __repr__(self): return "Root()" #return "Root(%s)"%(self.getChildNodes()) # dummy method def getChild(self,name): return None class Package: def __init__(self, path, name): self.path = path self.name = name def getChild(self,name): from bike.parsing.newstuff import getModule return getModule(os.path.join(self.path,name+".py")) def __repr__(self): return "Package(%s,%s)"%(self.path, self.name) # used so that linenum can be an attribute class Line(str): pass class StructuralNode: def __init__(self, filename, srclines, modulesrc): self.childNodes = [] self.filename = filename self._parent = None self._modulesrc = modulesrc self._srclines = srclines self._maskedLines = None def addChild(self, node): self.childNodes.append(node) node.setParent(self) def setParent(self, parent): self._parent = parent def getParent(self): return self._parent def getChildNodes(self): return self.childNodes def getChild(self,name): matches = [c for c in self.getChildNodes() if c.name == name] if matches != []: return matches[0] def getLogicalLine(self,physicalLineno): return generateLogicalLines(self._srclines[physicalLineno-1:]).next() # badly named: actually returns line numbers of import statements def getImportLineNumbers(self): try: return self.importlines except AttributeError: return[] def getLinesNotIncludingThoseBelongingToChildScopes(self): srclines = self.getMaskedModuleLines() lines = [] lineno = self.getStartLine() for child in self.getChildNodes(): lines+=srclines[lineno-1: child.getStartLine()-1] lineno = child.getEndLine() lines+=srclines[lineno-1: self.getEndLine()-1] return lines def generateLinesNotIncludingThoseBelongingToChildScopes(self): srclines = self.getMaskedModuleLines() lines = [] lineno = self.getStartLine() for child in self.getChildNodes(): for line in srclines[lineno-1: child.getStartLine()-1]: yield self.attachLinenum(line,lineno) lineno +=1 lineno = child.getEndLine() for line in srclines[lineno-1: self.getEndLine()-1]: yield self.attachLinenum(line,lineno) lineno +=1 def generateLinesWithLineNumbers(self,startline=1): srclines = self.getMaskedModuleLines() for lineno in range(startline,len(srclines)+1): yield self.attachLinenum(srclines[lineno-1],lineno) def attachLinenum(self,line,lineno): line = Line(line) line.linenum = lineno return line def getMaskedModuleLines(self): from bike.parsing.load import Cache try: maskedlines = Cache.instance.maskedlinescache[self.filename] except: # make sure src is actually masked # (could just have keywords masked) maskedsrc = maskStringsAndComments(self._modulesrc) maskedlines = maskedsrc.splitlines(1) Cache.instance.maskedlinescache[self.filename] = maskedlines return maskedlines class Module(StructuralNode): def __init__(self, filename, name, srclines, maskedsrc): StructuralNode.__init__(self, filename, srclines, maskedsrc) self.name = name self.indent = -TABWIDTH self.flattenedNodes = [] self.module = self def getMaskedLines(self): return self.getMaskedModuleLines() def getFlattenedListOfChildNodes(self): return self.flattenedNodes def getStartLine(self): return 1 def getEndLine(self): return len(self.getMaskedModuleLines())+1 def getSourceNode(self): return self.sourcenode def setSourceNode(self, sourcenode): self.sourcenode = sourcenode def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Module) and \ node.name == self.name def getParent(self): if self._parent is not None: return self._parent else: from newstuff import getPackage return getPackage(os.path.dirname(self.filename)) def __str__(self): return "bike:Module:"+self.filename indentRE = re.compile("^(\s*)\S") class Node: # module = the module node # linenum = starting line number def __init__(self, name, module, linenum, indent): self.name = name self.module = module self.linenum = linenum self.endline = None self.indent = indent def getMaskedLines(self): return self.getMaskedModuleLines()[self.getStartLine()-1:self.getEndLine()-1] def getStartLine(self): return self.linenum def getEndLine(self): if self.endline is None: physicallines = self.getMaskedModuleLines() lineno = self.linenum logicallines = generateLogicalLines(physicallines[lineno-1:]) # skip the first line, because it's the declaration line = logicallines.next() lineno+=line.count("\n") # scan to the end of the fn for line in logicallines: #print lineno,":",line, match = indentRE.match(line) if match and match.end()-1 <= self.indent: break lineno+=line.count("\n") self.endline = lineno return self.endline # linenum starts at 0 def getLine(self, linenum): return self._srclines[(self.getStartLine()-1) + linenum] baseClassesRE = re.compile("class\s+[^(]+\(([^)]+)\):") class Class(StructuralNode, Node): def __init__(self, name, filename, module, linenum, indent, srclines, maskedmodulesrc): StructuralNode.__init__(self, filename, srclines, maskedmodulesrc) Node.__init__(self, name, module, linenum, indent) self.type = "Class" def getBaseClassNames(self): #line = self.getLine(0) line = self.getLogicalLine(self.getStartLine()) match = baseClassesRE.search(line) if match: return [s.strip()for s in match.group(1).split(",")] else: return [] def getColumnOfName(self): match = classNameRE.match(self.getLine(0)) return match.start(1) def __repr__(self): return "" % self.name def __str__(self): return "bike:Class:"+self.filename+":"+\ str(self.getStartLine())+":"+self.name def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Class) and \ node.name == self.name def __eq__(self,other): return isinstance(other,Class) and \ self.filename == other.filename and \ self.getStartLine() == other.getStartLine() # describes an instance of a class class Instance: def __init__(self, type): assert type is not None self._type = type def getType(self): return self._type def __str__(self): return "Instance(%s)"%(self.getType()) class Function(StructuralNode, Node): def __init__(self, name, filename, module, linenum, indent, srclines, maskedsrc): StructuralNode.__init__(self, filename, srclines, maskedsrc) Node.__init__(self, name, module, linenum, indent) self.type = "Function" def getColumnOfName(self): match = fnNameRE.match(self.getLine(0)) return match.start(1) def __repr__(self): return "" % self.name def __str__(self): return "bike:Function:"+self.filename+":"+\ str(self.getStartLine())+":"+self.name def matchesCompilerNode(self,node): return isinstance(node,compiler.ast.Function) and \ node.name == self.name # bike.parsing.fastparser #!/usr/bin/env python #from bike.parsing.fastparserast import * #from bike.parsing.parserutils import * from parser import ParserError #import exceptions indentRE = re.compile("^\s*(\w+)") # returns a tree of objects representing nested classes and functions # in the source def fastparser(src,modulename="",filename=""): try: return fastparser_impl(src,modulename,filename) except RuntimeError, ex: # if recursive call exceeds maximum depth if str(ex) == "maximum recursion limit exceeded": raise ParserError,"maximum recursion depth exceeded when fast-parsing src "+filename else: raise def fastparser_impl(src,modulename,filename): lines = src.splitlines(1) maskedSrc = maskPythonKeywordsInStringsAndComments(src) maskedLines = maskedSrc.splitlines(1) root = Module(filename,modulename,lines,maskedSrc) parentnode = root lineno = 0 for line in maskedLines: lineno+=1 #print "line",lineno,":",line m = indentRE.match(line) if m: indent = m.start(1) tokenstr = m.group(1) if tokenstr == "import" or tokenstr == "from": while indent <= parentnode.indent: # root indent is -TABWIDTH parentnode = parentnode.getParent() try: parentnode.importlines.append(lineno) except AttributeError: parentnode.importlines = [lineno] elif tokenstr == "class": m2 = classNameRE.match(line) if m2: n = Class(m2.group(1), filename, root, lineno, indent, lines, maskedSrc) root.flattenedNodes.append(n) while indent <= parentnode.indent: parentnode = parentnode.getParent() parentnode.addChild(n) parentnode = n elif tokenstr == "def": m2 = fnNameRE.match(line) if m2: n = Function(m2.group(1), filename, root, lineno, indent, lines, maskedSrc) root.flattenedNodes.append(n) while indent <= parentnode.indent: parentnode = parentnode.getParent() parentnode.addChild(n) parentnode = n elif indent <= parentnode.indent and \ tokenstr in ['if','for','while','try']: parentnode = parentnode.getParent() while indent <= parentnode.indent: parentnode = parentnode.getParent() return root from cgi import escape class SourceCodeNode(object): def __init__(self, filename, linenumber, nodename, nodetype, additional): self.filename = filename self.linenumber = linenumber self.nodename = nodename self.nodetype = nodetype or 'None' self.additional = additional self.node_colour = self.get_node_colour() self.ctype_markup = self.get_ctype_markup() self.nodename_markup = self.get_nodename_markup() self.children = [] def add_child(self, node): self.children.append(node) def get_recursive_children(self): for node in self.children: yield node, self for child, parent in node.get_recursive_children(): yield child, parent def get_node_colour(self): if self.nodetype == 'Class': return '#c00000' else: return '#0000c0' def get_ctype_markup(self): return '%s' % ( self.node_colour, self.nodetype[0]) def get_nodename_markup(self): return '%s\n%s' % tuple( [escape(i) for i in [self.nodename, self.additional]] ) # PIDA Code def adapt_brm_node(node): firstline = node.getLine(0).strip() argnames = firstline.split(' ', 1)[-1].replace(node.name, '', 1) pida_node = SourceCodeNode(node.filename, node.linenum, node.name, node.type, argnames) return pida_node def adapt_tree(roots, built=None): if built is None: built = SourceCodeNode('', 0, '', 'N', '') for root in roots: pnode = adapt_brm_node(root) built.add_child(pnode) adapt_tree(root.getChildNodes(), pnode) return built def parse(stringdata): return fastparser(stringdata).getChildNodes() def get_nodes_from_string(stringdata): return adapt_tree(parse(stringdata)) PIDA-0.5.1/pida/utils/pywebbrowser.py0000644000175000017500000002605010652670605015521 0ustar aliali"""Interfaces for launching and remotely controlling Web browsers.""" import os import sys __all__ = ["Error", "open", "get", "register"] class Error(Exception): pass _browsers = {} # Dictionary of available browser controllers _tryorder = [] # Preference order of available browsers def register(name, klass, instance=None): """Register a browser connector and, optionally, connection.""" _browsers[name.lower()] = [klass, instance] def get(using=None): """Return a browser launcher instance appropriate for the environment.""" if using is not None: alternatives = [using] else: alternatives = _tryorder for browser in alternatives: if '%s' in browser: # User gave us a command line, don't mess with it. return GenericBrowser(browser) else: # User gave us a browser name. try: command = _browsers[browser.lower()] except KeyError: command = _synthesize(browser) if command[1] is None: return command[0]() else: return command[1] raise Error("could not locate runnable browser") # Please note: the following definition hides a builtin function. def open(url, new=0, autoraise=1): get().open(url, new, autoraise) def open_new(url): get().open(url, 1) def _synthesize(browser): """Attempt to synthesize a controller base on existing controllers. This is useful to create a controller when a user specifies a path to an entry in the BROWSER environment variable -- we can copy a general controller to operate using a specific installation of the desired browser in this way. If we can't create a controller in this way, or if there is no executable for the requested browser, return [None, None]. """ if not os.path.exists(browser): return [None, None] name = os.path.basename(browser) try: command = _browsers[name.lower()] except KeyError: return [None, None] # now attempt to clone to fit the new name: controller = command[1] if controller and name.lower() == controller.basename: import copy controller = copy.copy(controller) controller.name = browser controller.basename = os.path.basename(browser) register(browser, None, controller) return [None, controller] return [None, None] def _iscommand(cmd): """Return True if cmd can be found on the executable search path.""" path = os.environ.get("PATH") if not path: return False for d in path.split(os.pathsep): exe = os.path.join(d, cmd) if os.path.isfile(exe): return True return False PROCESS_CREATION_DELAY = 4 class GenericBrowser: def __init__(self, cmd): self.name, self.args = cmd.split(None, 1) self.basename = os.path.basename(self.name) def open(self, url, new=0, autoraise=1): assert "'" not in url command = "%s %s" % (self.name, self.args) os.system(command % url) def open_new(self, url): self.open(url) class Netscape: "Launcher class for Netscape browsers." def __init__(self, name): self.name = name self.basename = os.path.basename(name) def _remote(self, action, autoraise): raise_opt = ("-noraise", "-raise")[autoraise] cmd = "%s %s -remote '%s' >/dev/null 2>&1" % (self.name, raise_opt, action) rc = os.system(cmd) if rc: import time os.system("%s &" % self.name) time.sleep(PROCESS_CREATION_DELAY) rc = os.system(cmd) return not rc def open(self, url, new=0, autoraise=1): if new: self._remote("openURL(%s,new-window)"%url, autoraise) else: self._remote("openURL(%s)" % url, autoraise) def open_new(self, url): self.open(url, 1) class Galeon: """Launcher class for Galeon browsers.""" def __init__(self, name): self.name = name self.basename = os.path.basename(name) def _remote(self, action, autoraise): raise_opt = ("--noraise", "")[autoraise] cmd = "%s %s %s >/dev/null 2>&1" % (self.name, raise_opt, action) rc = os.system(cmd) if rc: import time os.system("%s >/dev/null 2>&1 &" % self.name) time.sleep(PROCESS_CREATION_DELAY) rc = os.system(cmd) return not rc def open(self, url, new=0, autoraise=1): if new: self._remote("-w '%s'" % url, autoraise) else: self._remote("-n '%s'" % url, autoraise) def open_new(self, url): self.open(url, 1) class Konqueror: """Controller for the KDE File Manager (kfm, or Konqueror). See http://developer.kde.org/documentation/other/kfmclient.html for more information on the Konqueror remote-control interface. """ def __init__(self): if _iscommand("konqueror"): self.name = self.basename = "konqueror" else: self.name = self.basename = "kfm" def _remote(self, action): cmd = "kfmclient %s >/dev/null 2>&1" % action rc = os.system(cmd) if rc: import time if self.basename == "konqueror": os.system(self.name + " --silent &") else: os.system(self.name + " -d &") time.sleep(PROCESS_CREATION_DELAY) rc = os.system(cmd) return not rc def open(self, url, new=1, autoraise=1): # XXX Currently I know no way to prevent KFM from # opening a new win. assert "'" not in url self._remote("openURL '%s'" % url) open_new = open class Grail: # There should be a way to maintain a connection to Grail, but the # Grail remote control protocol doesn't really allow that at this # point. It probably neverwill! def _find_grail_rc(self): import glob import pwd import socket import tempfile tempdir = os.path.join(tempfile.gettempdir(), ".grail-unix") user = pwd.getpwuid(os.getuid())[0] filename = os.path.join(tempdir, user + "-*") maybes = glob.glob(filename) if not maybes: return None s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) for fn in maybes: # need to PING each one until we find one that's live try: s.connect(fn) except socket.error: # no good; attempt to clean it out, but don't fail: try: os.unlink(fn) except IOError: pass else: return s def _remote(self, action): s = self._find_grail_rc() if not s: return 0 s.send(action) s.close() return 1 def open(self, url, new=0, autoraise=1): if new: self._remote("LOADNEW " + url) else: self._remote("LOAD " + url) def open_new(self, url): self.open(url, 1) class WindowsDefault: def open(self, url, new=0, autoraise=1): os.startfile(url) def open_new(self, url): self.open(url) # # Platform support for Unix # # This is the right test because all these Unix browsers require either # a console terminal of an X display to run. Note that we cannot split # the TERM and DISPLAY cases, because we might be running Python from inside # an xterm. if os.environ.get("TERM") or os.environ.get("DISPLAY"): _tryorder = ["links", "lynx", "w3m"] # Easy cases first -- register console browsers if we have them. if os.environ.get("TERM"): # The Links browser if _iscommand("links"): register("links", None, GenericBrowser("links '%s'")) # The Lynx browser if _iscommand("lynx"): register("lynx", None, GenericBrowser("lynx '%s'")) # The w3m browser if _iscommand("w3m"): register("w3m", None, GenericBrowser("w3m '%s'")) # X browsers have more in the way of options if os.environ.get("DISPLAY"): _tryorder = ["galeon", "skipstone", "mozilla-firefox", "mozilla-firebird", "mozilla", "netscape", "kfm", "grail"] + _tryorder # First, the Netscape series for browser in ("mozilla-firefox", "mozilla-firebird", "mozilla", "netscape"): if _iscommand(browser): register(browser, None, Netscape(browser)) # Next, Mosaic -- old but still in use. if _iscommand("mosaic"): register("mosaic", None, GenericBrowser( "mosaic '%s' >/dev/null &")) # Gnome's Galeon if _iscommand("galeon"): register("galeon", None, Galeon("galeon")) # Skipstone, another Gtk/Mozilla based browser if _iscommand("skipstone"): register("skipstone", None, GenericBrowser( "skipstone '%s' >/dev/null &")) # Konqueror/kfm, the KDE browser. if _iscommand("kfm") or _iscommand("konqueror"): register("kfm", Konqueror, Konqueror()) # Grail, the Python browser. if _iscommand("grail"): register("grail", Grail, None) class InternetConfig: def open(self, url, new=0, autoraise=1): ic.launchurl(url) def open_new(self, url): self.open(url) # # Platform support for Windows # if sys.platform[:3] == "win": _tryorder = ["netscape", "windows-default"] register("windows-default", WindowsDefault) # # Platform support for MacOS # try: import ic except ImportError: pass else: # internet-config is the only supported controller on MacOS, # so don't mess with the default! _tryorder = ["internet-config"] register("internet-config", InternetConfig) # # Platform support for OS/2 # if sys.platform[:3] == "os2" and _iscommand("netscape.exe"): _tryorder = ["os2netscape"] register("os2netscape", None, GenericBrowser("start netscape.exe %s")) # OK, now that we know what the default preference orders for each # platform are, allow user to override them with the BROWSER variable. # if "BROWSER" in os.environ: # It's the user's responsibility to register handlers for any unknown # browser referenced by this value, before calling open(). _tryorder[0:0] = os.environ["BROWSER"].split(os.pathsep) for cmd in _tryorder: if not cmd.lower() in _browsers: if _iscommand(cmd.lower()): register(cmd.lower(), None, GenericBrowser( "%s '%%s'" % cmd.lower())) cmd = None # to make del work if _tryorder was empty del cmd _tryorder = filter(lambda x: x.lower() in _browsers or x.find("%s") > -1, _tryorder) # what to do if _tryorder is now empty? PIDA-0.5.1/pida/utils/rpdb2.py0000644000175000017500000104350410652670605014004 0ustar aliali#! /usr/bin/env python """ rpdb2.py - version 2.1.4 A remote Python debugger for CPython Copyright (C) 2005-2007 Nir Aides This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ COPYRIGHT_NOTICE = """Copyright (C) 2005-2007 Nir Aides""" CREDITS_NOTICE = """Jurjen N.E. Bos - Compatibility with OS X.""" LICENSE_NOTICE = """ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. A copy of the GPL with the precise terms and conditions for copying, distribution and modification follow: """ COPY_OF_THE_GPL_LICENSE = """ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS """ #if '.' in __name__: # raise ImportError('rpdb2 must not be imported as part of a package!') import SimpleXMLRPCServer import SocketServer import xmlrpclib import threading import linecache import traceback import encodings import compiler import commands import tempfile import __main__ import cPickle import httplib import os.path import socket import getopt import string import thread import random import base64 import atexit import time import copy import hmac import stat import sets import sys import cmd import md5 import imp import os try: from Crypto.Cipher import DES except ImportError: pass # # Pre-Import needed by my_abspath1 # try: from nt import _getfullpathname except ImportError: pass # #-------------------------------- Design Notes ------------------------------- # """ Design: RPDB2 divides the world into two main parts: debugger and debuggee. The debuggee is the script that needs to be debugged. The debugger is another script that attaches to the debuggee for the purpose of debugging. Thus RPDB2 includes two main components: The debuggee-server that runs in the debuggee and the session-manager that runs in the debugger. The session manager and the debuggee-server communicate via XML-RPC. The main classes are: CSessionManager and CDebuggeeServer """ # #--------------------------------- Export functions ------------------------ # TIMEOUT_FIVE_MINUTES = 5 * 60.0 def start_embedded_debugger( pwd, fAllowUnencrypted = True, fAllowRemote = False, timeout = TIMEOUT_FIVE_MINUTES, fDebug = False ): """ Use 'start_embedded_debugger' to invoke the debugger engine in embedded scripts. put the following line as the first line in your script: import rpdb2; rpdb2.start_embedded_debugger(pwd) This will cause the script to freeze until a debugger console attaches. pwd - The password that governs security of client/server communication fAllowUnencrypted - Allow unencrypted communications. Communication will be authenticated but encrypted only if possible. fAllowRemote - Allow debugger consoles from remote machines to connect. timeout - Seconds to wait for attachment before giving up. If None, never give up. Once the timeout period expires, the debuggee will resume execution. fDebug - debug output. IMPORTNAT SECURITY NOTE: USING A HARDCODED PASSWORD MAY BE UNSECURE SINCE ANYONE WITH READ PERMISSION TO THE SCRIPT WILL BE ABLE TO READ THE PASSWORD AND CONNECT TO THE DEBUGGER AND DO WHATEVER THEY WISH VIA THE 'EXEC' DEBUGGER COMMAND. It is safer to use: start_embedded_debugger_interactive_password() """ return __start_embedded_debugger( pwd, fAllowUnencrypted, fAllowRemote, timeout, fDebug ) def start_embedded_debugger_interactive_password( fAllowUnencrypted = True, fAllowRemote = False, timeout = TIMEOUT_FIVE_MINUTES, fDebug = False, stdin = sys.stdin, stdout = sys.stdout ): if g_server is not None: return if stdout is not None: stdout.write('Please type password:') pwd = stdin.readline().rstrip('\n') return __start_embedded_debugger( pwd, fAllowUnencrypted, fAllowRemote, timeout, fDebug ) def settrace(): """ Trace threads that were created with thread.start_new_thread() To trace, call this function from the thread target function. NOTE: The main thread and any threads created with the threading module are automatically traced, and there is no need to invoke this function for them. Note: This call does not pause the script. """ return __settrace() def setbreak(): """ Pause the script for inspection at next script statement. """ return __setbreak() # #----------------------------------- Interfaces ------------------------------ # VERSION = (2, 1, 4, 0, '') RPDB_VERSION = "RPDB_2_1_4" RPDB_COMPATIBILITY_VERSION = "RPDB_2_1_4" def get_version(): return RPDB_VERSION def get_interface_compatibility_version(): return RPDB_COMPATIBILITY_VERSION class CSimpleSessionManager: """ This is a wrapper class that simplifies launching and controlling of a debuggee from within another program. For example, an IDE that launches a script for debugging puposes can use this class to launch, debug and stop a script. """ def __init__(self, fAllowUnencrypted = True): self.__sm = CSessionManagerInternal( pwd = None, fAllowUnencrypted = fAllowUnencrypted, fAllowRemote = False, host = LOCALHOST ) self.m_fRunning = False self.m_termination_lineno = None event_type_dict = {CEventUnhandledException: {}} self.__sm.register_callback(self.__unhandled_exception, event_type_dict, fSingleUse = False) event_type_dict = {CEventState: {}} self.__sm.register_callback(self.__state_calback, event_type_dict, fSingleUse = False) event_type_dict = {CEventExit: {}} self.__sm.register_callback(self.__termination_callback, event_type_dict, fSingleUse = False) def launch(self, fchdir, command_line): self.m_fRunning = False self.m_termination_lineno = None self.__sm.launch(fchdir, command_line, fload_breakpoints = False) def request_go(self): self.__sm.request_go() def detach(self): self.__sm.detach() def stop_debuggee(self): self.__sm.stop_debuggee() def get_session_manager(self): return self.__sm def prepare_attach(self): """ Use this method to attach a debugger to the debuggee after an exception is caught. """ pwd = self.__sm.get_password() si = self.__sm.get_server_info() rid = si.m_rid if os.name == 'posix': # # On posix systems the password is set at the debuggee via # a special temporary file. # create_pwd_file(rid, pwd) pwd = None return (rid, pwd) # # Override these callback to react to the related events. # def unhandled_exception_callback(self): print 'unhandled_exception_callback' self.request_go() def script_about_to_terminate_callback(self): print 'script_about_to_terminate_callback' self.request_go() def script_terminated_callback(self): print 'script_terminated_callback' # # Private Methods # def __unhandled_exception(self, event): self.unhandled_exception_callback() def __state_calback(self, event): """ Handle state change notifications from the debugge. """ if event.m_state != STATE_BROKEN: return if not self.m_fRunning: # # First break comes immediately after launch. # self.m_fRunning = True self.request_go() return sl = self.__sm.get_stack(tid_list = [], fAll = False) if len(sl) == 0: self.request_go() return st = sl[0] s = st.get(DICT_KEY_STACK, []) if len(s) == 0: self.request_go() return e = s[-1] lineno = e[1] path = e[0] filename = os.path.basename(e[0]) if filename == DEBUGGER_FILENAME and lineno == self.__get_termination_lineno(path): self.script_about_to_terminate_callback() def __termination_callback(self, event): self.script_terminated_callback() def __get_termination_lineno(self, path): """ Return the last line number of a file. """ if self.m_termination_lineno is not None: return self.m_termination_lineno f = open(path, 'rb') l = f.read() f.close() _l = l.rstrip() _s = _l.split('\n') self.m_termination_lineno = len(_s) return self.m_termination_lineno class CSessionManager: """ Interface to the session manager. This is the interface through which the debugger controls and communicates with the debuggee. You can study the way it is used in StartClient() """ def __init__(self, pwd, fAllowUnencrypted, fAllowRemote, host): self.__smi = CSessionManagerInternal( pwd, fAllowUnencrypted, fAllowRemote, host ) def set_printer(self, printer): """ 'printer' is a function that takes one argument and prints it. You can study CConsoleInternal.printer() as example for use and rational. """ return self.__smi.set_printer(printer) def report_exception(self, type, value, tb): """ Sends exception information to the printer. """ return self.__smi.report_exception(type, value, tb) def register_callback(self, callback, event_type_dict, fSingleUse): """ Receive events from the session manager. The session manager communicates it state mainly by firing events. You can study CConsoleInternal.__init__() as example for use. For details see CEventDispatcher.register_callback() """ return self.__smi.register_callback( callback, event_type_dict, fSingleUse ) def remove_callback(self, callback): return self.__smi.remove_callback(callback) def refresh(self): """ Fire again all relevant events needed to establish the current state. """ return self.__smi.refresh() def launch(self, fchdir, command_line): """ Launch debuggee in a new process and attach. fchdir - Change current directory to that of the debuggee. command_line - command line arguments pass to the script as a string. """ return self.__smi.launch(fchdir, command_line) def restart(self): """ Restart debug session with same command_line and fchdir arguments which were used in last launch. """ return self.__smi.restart() def get_launch_args(self): """ Return command_line and fchdir arguments which were used in last launch as (last_fchdir, last_command_line). Returns (None, None) if there is no info. """ return self.__smi.get_launch_args() def attach(self, key, name = None): """ Attach to a debuggee (establish communication with the debuggee-server) key - a string specifying part of the filename or PID of the debuggee. """ return self.__smi.attach(key, name) def detach(self): """ Let the debuggee go... """ return self.__smi.detach() def request_break(self): return self.__smi.request_break() def request_go(self): return self.__smi.request_go() def request_go_breakpoint(self, filename, scope, lineno): """ Go (run) until the specified location is reached. """ return self.__smi.request_go_breakpoint(filename, scope, lineno) def request_step(self): """ Go until the next line of code is reached. """ return self.__smi.request_step() def request_next(self): """ Go until the next line of code in the same scope is reached. """ return self.__smi.request_next() def request_return(self): """ Go until end of scope is reached. """ return self.__smi.request_return() def request_jump(self, lineno): """ Jump to the specified line number in the same scope. """ return self.__smi.request_jump(lineno) # # REVIEW: should return breakpoint ID # def set_breakpoint(self, filename, scope, lineno, fEnabled, expr): """ Set a breakpoint. filename - (Optional) can be either a file name or a module name, full path, relative path or no path at all. If filname is None or '', then the current module is used. scope - (Optional) Specifies a dot delimited scope for the breakpoint, such as: foo or myClass.foo lineno - (Optional) Specify a line within the selected file or if a scope is specified, an zero-based offset from the start of the scope. expr - (Optional) A Python expression that will be evaluated locally when the breakpoint is hit. The break will occur only if the expression evaluates to true. """ return self.__smi.set_breakpoint( filename, scope, lineno, fEnabled, expr ) def disable_breakpoint(self, id_list, fAll): """ Disable breakpoints id_list - (Optional) A list of breakpoint ids. fAll - disable all breakpoints regardless of id_list. """ return self.__smi.disable_breakpoint(id_list, fAll) def enable_breakpoint(self, id_list, fAll): """ Enable breakpoints id_list - (Optional) A list of breakpoint ids. fAll - disable all breakpoints regardless of id_list. """ return self.__smi.enable_breakpoint(id_list, fAll) def delete_breakpoint(self, id_list, fAll): """ Delete breakpoints id_list - (Optional) A list of breakpoint ids. fAll - disable all breakpoints regardless of id_list. """ return self.__smi.delete_breakpoint(id_list, fAll) def get_breakpoints(self): """ Return breakpoints in a dictionary of id keys to CBreakPoint values """ return self.__smi.get_breakpoints() def save_breakpoints(self, _filename = ''): """ Save breakpoints to file, locally (on the client side) """ return self.__smi.save_breakpoints(_filename) def load_breakpoints(self, _filename = ''): """ Load breakpoints from file, locally (on the client side) """ return self.__smi.load_breakpoints(_filename) def set_trap_unhandled_exceptions(self, ftrap): """ Set trap-unhandled-exceptions mode. ftrap with a value of False means unhandled exceptions will be ignored. The session manager default is True. """ return self.__smi.set_trap_unhandled_exceptions(ftrap) def get_trap_unhandled_exceptions(self): """ Get trap-unhandled-exceptions mode. """ return self.__smi.get_trap_unhandled_exceptions() def get_stack(self, tid_list, fAll): return self.__smi.get_stack(tid_list, fAll) def get_source_file(self, filename, lineno, nlines): return self.__smi.get_source_file(filename, lineno, nlines) def get_source_lines(self, nlines, fAll): return self.__smi.get_source_lines(nlines, fAll) def set_frame_index(self, frame_index): """ Set frame index. 0 is the current executing frame, and 1, 2, 3, are deeper into the stack. """ return self.__smi.set_frame_index(frame_index) def get_frame_index(self): """ Get frame index. 0 is the current executing frame, and 1, 2, 3, are deeper into the stack. """ return self.__smi.get_frame_index() def set_analyze(self, fAnalyze): """ Toggle analyze mode. In analyze mode the stack switches to the exception stack for examination. """ return self.__smi.set_analyze(fAnalyze) def set_host(self, host): """ Set host to specified host (string) for attaching to debuggies on specified host. host can be a host name or ip address in string form. """ return self.__smi.set_host(host) def get_host(self): return self.__smi.get_host() def calc_server_list(self): """ Calc servers (debuggable scripts) list on specified host. Returns a tuple of a list and a dictionary. The list is a list of CServerInfo objects sorted by their age ordered oldest last. The dictionary is a dictionary of errors that were encountered during the building of the list. The dictionary has error (exception) type as keys and number of occurances as values. """ return self.__smi.calc_server_list() def get_server_info(self): """ Return CServerInfo server info object that corresponds to the server (debugged script) to which the session manager is attached. """ return self.__smi.get_server_info() def get_namespace(self, nl, fFilter): """ get_namespace is designed for locals/globals panes that let the user inspect a namespace tree in GUI debuggers such as Winpdb. You can study the way it is used in Winpdb. nl - List of tuples, where each tuple is made of a python expression as string and a flag that controls whether to "expand" the value, that is, to return its children as well in case it has children e.g. lists, dictionaries, etc... fFilter - Flag. Filter functions and classes out of the globals dictionary, to make it more readable. examples of expression lists: [('x', false), ('y', false)] [('locals()', true)] [('a.b.c', false), ('my_object.foo', false), ('another_object', true)] Return value is a list of dictionaries, where every element in the list corresponds to an element in the input list 'nl'. Each dictionary has the following keys and values: DICT_KEY_EXPR - the original expression string. DICT_KEY_REPR - A repr of the evaluated value of the expression. DICT_KEY_TYPE - A string representing the type of the experession's evaluated value. DICT_KEY_N_SUBNODES - If the evaluated value has children like items in a list or in a dictionary or members of a class, etc, this key will have their number as value. DICT_KEY_SUBNODES - If the evaluated value has children and the "expand" flag was set for this expression, then the value of this key will be a list of dictionaries as described below. DICT_KEY_ERROR - If an error prevented evaluation of this expression the value of this key will be a repr of the exception info: repr(sys.exc_info()) Each dictionary for child items has the following keys and values: DICT_KEY_EXPR - The Python expression that designates this child. e.g. 'my_list[0]' designates the first child of the list 'my_list' DICT_KEY_NAME - a repr of the child name, e.g '0' for the first item in a list. DICT_KEY_REPR - A repr of the evaluated value of the expression. DICT_KEY_TYPE - A string representing the type of the experession's evaluated value. DICT_KEY_N_SUBNODES - If the evaluated value has children like items in a list or in a dictionary or members of a class, etc, this key will have their number as value. """ return self.__smi.get_namespace(nl, fFilter) # # REVIEW: remove warning item. # def evaluate(self, expr): """ Evaluate a python expression in the context of the current thread and frame. Return value is a tuple (v, w, e) where v is a repr of the evaluated expression value, w is always '', and e is an error string if an error occurred. NOTE: This call might not return since debugged script logic can lead to tmporary locking or even deadlocking. """ return self.__smi.evaluate(expr) def execute(self, suite): """ Execute a python statement in the context of the current thread and frame. Return value is a tuple (w, e) where w and e are warning and error strings (respectively) if an error occurred. NOTE: This call might not return since debugged script logic can lead to tmporary locking or even deadlocking. """ return self.__smi.execute(suite) def get_state(self): """ Get the session manager state. Return one of the STATE_* constants defined below, for example STATE_DETACHED, STATE_BROKEN, etc... """ return self.__smi.get_state() # # REVIEW: Improve data strucutre. # def get_thread_list(self): return self.__smi.get_thread_list() def set_thread(self, tid): """ Set the focused thread to the soecified thread. tid - either the OS thread id or the zero based index of the thread in the thread list returned by get_thread_list(). """ return self.__smi.set_thread(tid) def set_password(self, pwd): """ Set the password that will govern the authentication and encryption of client-server communication. """ return self.__smi.set_password(pwd) def get_password(self): """ Get the password that governs the authentication and encryption of client-server communication. """ return self.__smi.get_password() def get_encryption(self): """ Get the encryption mode. Return True if unencrypted connections are not allowed. When launching a new debuggee the debuggee will inherit the encryption mode. The encryption mode can be set via command-line only. """ return self.__smi.get_encryption() def set_remote(self, fAllowRemote): """ Set the remote-connections mode. if True, connections from remote machine are allowed. When launching a new debuggee the debuggee will inherit this mode. This mode is only relevant to the debuggee. """ return self.__smi.set_remote(fAllowRemote) def get_remote(self): """ Get the remote-connections mode. Return True if connections from remote machine are allowed. When launching a new debuggee the debuggee will inherit this mode. This mode is only relevant to the debuggee. """ return self.__smi.get_remote() def stop_debuggee(self): """ Stop the debuggee with the os.abort() command. """ return self.__smi.stop_debuggee() class CConsole: """ Interface to a debugger console. """ def __init__( self, session_manager, stdin = None, stdout = None, fSplit = False ): """ Constructor of CConsole session_manager - session manager object. stdin, stdout - redirection for IO. fsplit - Set flag to True when Input and Ouput belong to different panes. For example take a look at Winpdb. """ self.m_ci = CConsoleInternal( session_manager, stdin, stdout, fSplit ) def start(self): return self.m_ci.start() def join(self): """ Wait until the console ends. """ return self.m_ci.join() def set_filename(self, filename): """ Set current filename for the console. The current filename can change from outside the console when the console is embeded in other components, for example take a look at Winpdb. """ return self.m_ci.set_filename(filename) # # ---------------------------- Exceptions ---------------------------------- # class CException(Exception): """ Base exception class for the debugger. """ def __init__(self, *args): Exception.__init__(self, *args) class InvalidScopeName(CException): """ Invalid scope name. This exception may be thrown when a request was made to set a breakpoint to an unknown scope. """ class BadArgument(CException): """ Bad Argument. """ class ThreadNotFound(CException): """ Thread not found. """ class NoThreads(CException): """ No Threads. """ class ThreadDone(CException): """ Thread Done. """ class DebuggerNotBroken(CException): """ Debugger is not broken. This exception is thrown when an operation that can only be performed while the debuggee is broken, is requested while the debuggee is running. """ class InvalidFrame(CException): """ Invalid Frame. This exception is raised if an operation is requested on a stack frame that does not exist. """ class NoExceptionFound(CException): """ No Exception Found. This exception is raised when exception information is requested, but no exception is found, or has been thrown. """ class CConnectionException(CException): def __init__(self, *args): CException.__init__(self, *args) class BadVersion(CConnectionException): """Bad Version.""" def __init__(self, version): CConnectionException.__init__(self) self.m_version = version def __str__(self): return repr(self.m_version) class UnexpectedData(CConnectionException): """Unexpected data.""" class AlreadyAttached(CConnectionException): """Already Attached.""" class NotAttached(CConnectionException): """Not Attached.""" class SpawnUnsupported(CConnectionException): """Spawn Unsupported.""" class UnknownServer(CConnectionException): """Unknown Server.""" class CSecurityException(CConnectionException): def __init__(self, *args): CConnectionException.__init__(self, *args) class UnsetPassword(CSecurityException): """Unset Password.""" class EncryptionNotSupported(CSecurityException): """Encryption Not Supported.""" class EncryptionExpected(CSecurityException): """Encryption Expected.""" class DecryptionFailure(CSecurityException): """Decryption Failure.""" class AuthenticationBadData(CSecurityException): """Authentication Bad Data.""" class AuthenticationFailure(CSecurityException): """Authentication Failure.""" class AuthenticationBadIndex(CSecurityException): """Authentication Bad Index.""" def __init__(self, max_index = 0, anchor = 0): CSecurityException.__init__(self) self.m_max_index = max_index self.m_anchor = anchor def __str__(self): return repr((self.m_max_index, self.m_anchor)) # #----------------------- Infinite List of Globals --------------------------- # GNOME_DEFAULT_TERM = 'gnome-terminal' NT_DEBUG = 'nt_debug' SCREEN = 'screen' MAC = 'mac' DARWIN = 'darwin' POSIX = 'posix' # # REVIEW: Go over this mechanism # # map between OS type and relvant command to initiate a new OS console. # entries for other OSs can be added here. # '%s' serves as a place holder. # osSpawn = { 'nt': 'start "rpdb2 - Version ' + get_version() + ' - Debuggee Console" cmd /c ""%s" %s"', NT_DEBUG: 'start "rpdb2 - Version ' + get_version() + ' - Debuggee Console" cmd /k ""%s" %s"', POSIX: "%s -e %s %s &", GNOME_DEFAULT_TERM: "gnome-terminal --disable-factory -x %s %s &", MAC: '%s %s', DARWIN: '%s %s', SCREEN: 'screen -t debuggee_console %s %s' } RPDBTERM = 'RPDBTERM' COLORTERM = 'COLORTERM' TERM = 'TERM' KDE_PREFIX = 'KDE' GNOME_PREFIX = 'GNOME' KDE_DEFAULT_TERM_QUERY = "kreadconfig --file kdeglobals --group General --key TerminalApplication --default konsole" XTERM = 'xterm' RXVT = 'rxvt' RPDB_SETTINGS_FOLDER = '.rpdb2_settings' RPDB_PWD_FOLDER = os.path.join(RPDB_SETTINGS_FOLDER, 'passwords') RPDB_BPL_FOLDER = os.path.join(RPDB_SETTINGS_FOLDER, 'breakpoints') RPDB_BPL_FOLDER_NT = 'rpdb2_breakpoints' MAX_BPL_FILES = 100 IDLE_MAX_RATE = 2.0 PING_TIMEOUT = 4.0 LOCAL_TIMEOUT = 1.0 COMMUNICATION_RETRIES = 5 WAIT_FOR_BREAK_TIMEOUT = 3.0 STARTUP_TIMEOUT = 3.0 STARTUP_RETRIES = 3 LOOPBACK = '127.0.0.1' LOCALHOST = 'localhost' SERVER_PORT_RANGE_START = 51000 SERVER_PORT_RANGE_LENGTH = 20 ERROR_SOCKET_ADDRESS_IN_USE_WIN = 10048 ERROR_SOCKET_ADDRESS_IN_USE_UNIX = 98 ERROR_SOCKET_ADDRESS_IN_USE_MAC = 48 SOURCE_EVENT_CALL = 'C' SOURCE_EVENT_LINE = 'L' SOURCE_EVENT_RETURN = 'R' SOURCE_EVENT_EXCEPTION = 'E' SOURCE_STATE_UNBROKEN = '*' SOURCE_BP_ENABLED = 'B' SOURCE_BP_DISABLED = 'D' SYMBOL_MARKER = '>' SYMBOL_ALL = '*' SOURCE_MORE = '+' SOURCE_LESS = '-' SOURCE_ENTIRE_FILE = '^' CONSOLE_PRINTER = '*** ' CONSOLE_WRAP_INDEX = 78 CONSOLE_PROMPT = '\n> ' CONSOLE_PROMPT_ANALYZE = '\nAnalayze> ' CONSOLE_INTRO = ("""RPDB - The Remote Python Debugger, version %s, Copyright (C) 2005-2007 Nir Aides. Type "help", "copyright", "license", "credits" for more information.""" % (RPDB_VERSION)) PRINT_NOTICE_PROMPT = "Hit Return for more, or q (and Return) to quit:" PRINT_NOTICE_LINES_PER_SECTION = 20 STR_NO_THREADS = "Operation failed since no traced threads were found." STR_AUTOMATIC_LAUNCH_UNKNOWN = "RPDB doesn't know how to launch a new terminal on this platform. Please start the debuggee manually with the -d flag on a separate console and then use the 'attach' command to attach to it." STR_STARTUP_NOTICE = "Attaching to debuggee..." STR_SPAWN_UNSUPPORTED = "Launch command supported on 'posix' and 'nt', systems only. Please start the debuggee manually with the -d flag on a separate console and then use the 'attach' command to attach to it." STR_STARTUP_SPAWN_NOTICE = "Spawning debuggee..." STR_KILL_NOTICE = "Stopping debuggee..." STR_STARTUP_FAILURE = "Debuggee failed to start in a timely manner." STR_OUTPUT_WARNING = "Textual output will be done at the debuggee." STR_OUTPUT_WARNING_ASYNC = "The operation will continue to run in the background." STR_ANALYZE_GLOBALS_WARNING = "In analyze mode the globals and locals dictionaries are read only." STR_BREAKPOINTS_LOADED = "Breakpoints were loaded." STR_BREAKPOINTS_SAVED = "Breakpoints were saved." STR_BREAKPOINTS_SAVE_PROBLEM = "A problem occurred while saving the breakpoints." STR_BREAKPOINTS_LOAD_PROBLEM = "A problem occurred while loading the breakpoints." STR_BREAKPOINTS_NOT_SAVED = "Breakpoints were not saved." STR_BREAKPOINTS_NOT_LOADED = "Breakpoints were not loaded." STR_BREAKPOINTS_FILE_NOT_FOUND = "Breakpoints file was not found." STR_BREAKPOINTS_NOT_FOUND = "No Breakpoints were found." STR_BAD_FILENAME = "Bad File Name." STR_SOME_BREAKPOINTS_NOT_LOADED = "Some breakpoints were not loaded, because of an error." STR_BAD_EXPRESSION = "Bad expression '%s'." STR_FILE_NOT_FOUND = "File '%s' not found." STR_EXCEPTION_NOT_FOUND = "No exception was found." STR_SCOPE_NOT_FOUND = "Scope '%s' not found." STR_NO_SUCH_BREAKPOINT = "Breakpoint not found." STR_THREAD_NOT_FOUND = "Thread was not found." STR_NO_THREADS_FOUND = "No threads were found." STR_THREAD_NOT_BROKEN = "Thread is running." STR_THREAD_FOCUS_SET = "Focus was set to chosen thread." STR_ILEGAL_ANALYZE_MODE_ARG = "Argument is not allowed in analyze mode. Type 'help analyze' for more info." STR_ILEGAL_ANALYZE_MODE_CMD = "Command is not allowed in analyze mode. Type 'help analyze' for more info." STR_ANALYZE_MODE_TOGGLE = "Analyze mode was set to: %s." STR_BAD_ARGUMENT = "Bad Argument." STR_DEBUGGEE_TERMINATED = "Debuggee has terminated." STR_DEBUGGEE_NOT_BROKEN = "Debuggee has to be waiting at break point to complete this command." STR_DEBUGGER_HAS_BROKEN = "Debuggee is waiting at break point for further commands." STR_ALREADY_ATTACHED = "Already attached. Detach from debuggee and try again." STR_NOT_ATTACHED = "Not attached to any script. Attach to a script and try again." STR_COMMUNICATION_FAILURE = "Failed to communicate with debugged script." STR_ERROR_OTHER = "Command returned the following error:\n%(type)s, %(value)s.\nPlease check stderr for stack trace and report to support." STR_BAD_VERSION = "A debuggee was found with incompatible debugger version %(value)s." STR_BAD_VERSION2 = "While attempting to find the specified debuggee at least one debuggee was found that uses incompatible version of RPDB2." STR_UNEXPECTED_DATA = "Unexpected data received." STR_ACCESS_DENIED = "While attempting to find debuggee, at least one debuggee denied connection because of mismatched passwords. Please verify your password." STR_ACCESS_DENIED2 = "Communication is denied because of un-matching passwords." STR_ENCRYPTION_EXPECTED = "While attempting to find debuggee, at least one debuggee denied connection since it accepts encrypted connections only." STR_ENCRYPTION_EXPECTED2 = "Debuggee will only talk over an encrypted channel." STR_DECRYPTION_FAILURE = "Bad packet was received by the debuggee." STR_DEBUGGEE_NO_ENCRYPTION = "Debuggee does not support encrypted mode. Either install the python-crypto package on the debuggee machine or allow unencrypted connections." STR_RANDOM_PASSWORD = "Password has been set to a random password." STR_PASSWORD_INPUT = "Please type a password:" STR_PASSWORD_CONFIRM = "Password has been set." STR_PASSWORD_NOT_SUPPORTED = "The --pwd flag is only supported on NT systems." STR_PASSWORD_MUST_BE_SET = "A password should be set to secure debugger client-server communication." STR_BAD_DATA = "Bad data received from debuggee." STR_BAD_FILE_DATA = "Bad data received from file." STR_ATTACH_FAILED = "Failed to attach" STR_ATTACH_FAILED_NAME = "Failed to attach to '%s'." STR_ATTACH_CRYPTO_MODE = "Debug Channel is%s encrypted." STR_ATTACH_CRYPTO_MODE_NOT = "NOT" STR_ATTACH_SUCCEEDED = "Successfully attached to '%s'." STR_ATTEMPTING_TO_STOP = "Requesting script to stop." STR_ATTEMPTING_TO_DETACH = "Detaching from script..." STR_DETACH_SUCCEEDED = "Detached from script." STR_DEBUGGEE_UNKNOWN = "Failed to find script." STR_MULTIPLE_DEBUGGEES = "WARNING: There is more than one debuggee '%s'." MSG_ERROR_HOST_TEXT = """The debugger was not able to set the host to '%s'. The following error was returned: %s""" STR_SOURCE_NOT_FOUND = "Failed to get source from debuggee." STR_SCRIPTS_CONNECTING = "Connecting to '%s'..." STR_SCRIPTS_NO_SCRIPTS = "No scripts to debug on '%s'" STR_SCRIPTS_TO_DEBUG = """Scripts to debug on '%s': pid name --------------------------""" STR_STACK_TRACE = """Stack trace for thread %d: Frame File Name Line Function ------------------------------------------------------------------------------""" STR_SOURCE_LINES = """Source lines for thread %d from file '%s': """ STR_ACTIVE_THREADS = """List of active threads known to the debugger: No Tid Name State -----------------------------------------------""" STR_BREAKPOINTS_LIST = """List of breakpoints: Id State Line Filename-Scope-Condition ------------------------------------------------------------------------------""" STR_BREAKPOINTS_TEMPLATE = """ %2d %-8s %5d %s %s %s""" STR_ENCRYPTION_SUPPORT_ERROR = "Encryption is not supported since the python-crypto package was not found. Either install the python-crypto package or allow unencrypted connections." STR_PASSWORD_NOT_SET = 'Password is not set.' STR_PASSWORD_SET = 'Password is set to: "%s"' STR_ENCRYPT_MODE = 'Force encryption mode: %s' STR_REMOTE_MODE = 'Allow remote machines mode: %s' STR_TRAP_MODE = 'Trap unhandled exceptions mode is: %s' STR_TRAP_MODE_SET = "Trap unhandled exceptions mode was set to: %s." STR_LOCAL_NAMESPACE_WARNING = 'Debugger modifications to the original bindings of the local namespace of this frame will be committed before the execution of the next statement of the frame. Any code using these variables executed before that point will see the original values.' STR_WARNING = 'Warning: %s' STR_MAX_NAMESPACE_WARNING_TITLE = 'Namespace Warning' STR_MAX_NAMESPACE_WARNING_TYPE = '*** WARNING ***' STR_MAX_NAMESPACE_WARNING_MSG = 'Number of items exceeds capacity of namespace browser.' STR_MAX_EVALUATE_LENGTH_WARNING = 'Output length exeeds maximum capacity.' ENCRYPTION_ENABLED = 'encrypted' ENCRYPTION_DISABLED = 'plain-text' ENCRYPTION_PREFIX = 'encrypted' STATE_ENABLED = 'enabled' STATE_DISABLED = 'disabled' BREAKPOINTS_FILE_EXT = '.bpl' PYTHON_FILE_EXTENSION = '.py' PYTHONW_FILE_EXTENSION = '.pyw' PYTHONW_SO_EXTENSION = '.so' PYTHON_EXT_LIST = ['.py', '.pyw', '.pyc', '.pyd', '.pyo', '.so'] MODULE_SCOPE = '?' MODULE_SCOPE2 = '' BLENDER_NO_SOURCE_FILENAME = '' ERROR_NO_BLENDER_SOURCE = 'Blender script source not available.' SCOPE_SEP = '.' BP_FILENAME_SEP = ':' BP_EVAL_SEP = ',' DEBUGGER_FILENAME = 'rpdb2.py' THREADING_FILENAME = 'threading.py' STR_STATE_BROKEN = 'waiting at break point' STATE_BROKEN = 'broken' STATE_RUNNING = 'running' STATE_ANALYZE = 'analyze' STATE_DETACHED = 'detached' STATE_DETACHING = 'detaching' STATE_SPAWNING = 'spawning' STATE_ATTACHING = 'attaching' DEFAULT_NUMBER_OF_LINES = 20 DICT_KEY_TID = 'tid' DICT_KEY_STACK = 'stack' DICT_KEY_CODE_LIST = 'code_list' DICT_KEY_CURRENT_TID = 'current tid' DICT_KEY_BROKEN = 'broken' DICT_KEY_BREAKPOINTS = 'breakpoints' DICT_KEY_LINES = 'lines' DICT_KEY_FILENAME = 'filename' DICT_KEY_FIRST_LINENO = 'first_lineno' DICT_KEY_FRAME_LINENO = 'frame_lineno' DICT_KEY_EVENT = 'event' DICT_KEY_EXPR = 'expr' DICT_KEY_NAME = 'name' DICT_KEY_REPR = 'repr' DICT_KEY_TYPE = 'type' DICT_KEY_SUBNODES = 'subnodes' DICT_KEY_N_SUBNODES = 'n_subnodes' DICT_KEY_ERROR = 'error' RPDB_EXEC_INFO = 'rpdb_exception_info' MODE_ON = 'ON' MODE_OFF = 'OFF' MAX_EVALUATE_LENGTH = 256 * 1024 MAX_NAMESPACE_ITEMS = 1024 MAX_NAMESPACE_WARNING = { DICT_KEY_EXPR: STR_MAX_NAMESPACE_WARNING_TITLE, DICT_KEY_NAME: STR_MAX_NAMESPACE_WARNING_TITLE, DICT_KEY_REPR: STR_MAX_NAMESPACE_WARNING_MSG, DICT_KEY_TYPE: STR_MAX_NAMESPACE_WARNING_TYPE, DICT_KEY_N_SUBNODES: 0 } MAX_EVENT_LIST_LENGTH = 1000 EVENT_EXCLUDE = 'exclude' EVENT_INCLUDE = 'include' INDEX_TABLE_SIZE = 100 DISPACHER_METHOD = 'dispatcher_method' BASIC_TYPES_LIST = ['str', 'unicode', 'int', 'long', 'float', 'bool', 'NoneType'] XML_DATA = """ dispatcher_method RPDB_02_00_04 """ N_WORK_QUEUE_THREADS = 8 DEFAULT_PATH_SUFFIX_LENGTH = 55 g_server_lock = threading.RLock() g_server = None g_debugger = None g_fScreen = False # # In debug mode errors and tracebacks are printed to stdout # g_fDebug = False # # Lock for the traceback module to prevent it from interleaving # output from different threads. # g_traceback_lock = threading.RLock() # # Filename to Source-lines dictionary of blender Python scripts. # g_blender_text = {} g_initial_cwd = [] g_error_mapping = { socket.error: STR_COMMUNICATION_FAILURE, BadVersion: STR_BAD_VERSION, UnexpectedData: STR_UNEXPECTED_DATA, SpawnUnsupported: STR_SPAWN_UNSUPPORTED, UnknownServer: STR_DEBUGGEE_UNKNOWN, UnsetPassword: STR_PASSWORD_MUST_BE_SET, EncryptionNotSupported: STR_DEBUGGEE_NO_ENCRYPTION, EncryptionExpected: STR_ENCRYPTION_EXPECTED, DecryptionFailure: STR_DECRYPTION_FAILURE, AuthenticationBadData: STR_ACCESS_DENIED, AuthenticationFailure: STR_ACCESS_DENIED, AlreadyAttached: STR_ALREADY_ATTACHED, NotAttached: STR_NOT_ATTACHED, DebuggerNotBroken: STR_DEBUGGEE_NOT_BROKEN, NoThreads: STR_NO_THREADS, NoExceptionFound: STR_EXCEPTION_NOT_FOUND, } # # ---------------------------- General Utils ------------------------------ # def class_name(c): s = str(c) if "'" in s: s = s.split("'")[1] assert(s.startswith(__name__ + '.')) return s def clip_filename(path, n = DEFAULT_PATH_SUFFIX_LENGTH): suffix = calc_suffix(path, n) if not suffix.startswith('...'): return suffix index = suffix.find(os.sep) if index == -1: return suffix clip = '...' + suffix[index:] return clip def safe_repr(x): try: y = repr(x) return y except: pass try: y = str(x) return y except: pass return 'N/A' def safe_repr_limited(x): y = safe_repr(x)[0:128] if len(y) == 128: y += '...' return y def print_debug(fForce = False): """ Print exceptions to stdout when in debug mode. """ if not g_fDebug and not fForce: return (t, v, tb) = sys.exc_info() print_exception(t, v, tb, fForce) def print_exception(t, v, tb, fForce = False): """ Print exceptions to stdout when in debug mode. """ if not g_fDebug and not fForce: return try: g_traceback_lock.acquire() traceback.print_exception(t, v, tb, file = sys.stderr) finally: g_traceback_lock.release() def print_stack(): """ Print exceptions to stdout when in debug mode. """ if g_fDebug == True: try: g_traceback_lock.acquire() traceback.print_stack(file = sys.stderr) finally: g_traceback_lock.release() def split_command_line_path_filename_args(command_line): """ Split command line to a 3 elements tuple (path, filename, args) """ command_line = command_line.strip() if len(command_line) == 0: return ('', '', '') if os.path.isfile(command_line): (_path, _filename) = split_path(command_line) return (_path, _filename, '') if command_line[0] in ['"', "'"]: _command_line = command_line[1:] i = _command_line.find(command_line[0]) if i == -1: (_path, filename) = split_path(_command_line) return (_path, filename, '') else: (_path, filename) = split_path(_command_line[: i]) args = _command_line[i + 1:].strip() return (_path, filename, args) else: i = command_line.find(' ') if i == -1: (_path, filename) = split_path(command_line) return (_path, filename, '') else: args = command_line[i + 1:].strip() (_path, filename) = split_path(command_line[: i]) return (_path, filename, args) def split_path(path): (_path, filename) = os.path.split(path) # # Make sure path separator (e.g. '/') ends the splitted path if it was in # the original path. # if (_path[-1:] not in [os.path.sep, os.path.altsep]) and \ (path[len(_path): len(_path) + 1] in [os.path.sep, os.path.altsep]): _path = _path + path[len(_path): len(_path) + 1] return (_path, filename) def calc_frame_path(frame): filename = frame.f_code.co_filename if filename.startswith('<'): return filename if os.path.isabs(filename): abspath = my_abspath(filename) lowered = winlower(abspath) return lowered globals_file = frame.f_globals.get('__file__', None) if globals_file != None and os.path.isabs(globals_file): dirname = os.path.dirname(globals_file) basename = os.path.basename(filename) path = os.path.join(dirname, basename) abspath = my_abspath(path) lowered = winlower(abspath) return lowered try: abspath = FindFile(filename, fModules = True) lowered = winlower(abspath) return lowered except IOError: lowered = winlower(filename) return lowered def my_abspath(path): """ We need our own little version of os.path.abspath since the original code imports modules in the 'nt' code path which can cause our debugger to deadlock in unexpected locations. """ if path[:1] == '<': # # 'path' may also be '' in which case it is left untouched. # return path if os.name == 'nt': return my_abspath1(path) return os.path.abspath(path) def my_abspath1(path): """ Modification of ntpath.abspath() that avoids doing an import. """ if path: try: path = _getfullpathname(path) except WindowsError: pass else: path = os.getcwd() np = os.path.normpath(path) if (len(np) >= 2) and (np[1:2] == ':'): np = np[:1].upper() + np[1:] return np def IsPythonSourceFile(filename): if filename.endswith(PYTHON_FILE_EXTENSION): return True if filename.endswith(PYTHONW_FILE_EXTENSION): return True return False def CalcModuleName(filename): _basename = os.path.basename(filename) (modulename, ext) = os.path.splitext(_basename) if ext in PYTHON_EXT_LIST: return modulename return _basename def CalcScriptName(filename, fAllowAnyExt = True): if filename.endswith(PYTHON_FILE_EXTENSION): return filename if filename.endswith(PYTHONW_FILE_EXTENSION): return filename if filename.endswith(PYTHONW_SO_EXTENSION): scriptname = filename[:-3] + PYTHON_FILE_EXTENSION return scriptname if filename[:-1].endswith(PYTHON_FILE_EXTENSION): scriptname = filename[:-1] return scriptname if fAllowAnyExt: return filename scriptname = filename + PYTHON_FILE_EXTENSION return scriptname def FindModuleDir(module_name): if module_name == '': raise IOError dot_index = module_name.rfind('.') if dot_index != -1: parent = module_name[: dot_index] child = module_name[dot_index + 1:] else: parent = '' child = module_name m = sys.modules[module_name] if not hasattr(m, '__file__') or m.__file__ == None: parent_dir = FindModuleDir(parent) module_dir = os.path.join(parent_dir, winlower(child)) return module_dir if not os.path.isabs(m.__file__): parent_dir = FindModuleDir(parent) module_dir = os.path.join(parent_dir, winlower(child)) return module_dir (root, ext) = os.path.splitext(m.__file__) if root.endswith('__init__'): root = os.path.dirname(root) abspath = my_abspath(root) lowered = winlower(abspath) return lowered def FindFileAsModule(filename): lowered = winlower(filename) (root, ext) = os.path.splitext(lowered) root_dotted = root.replace('\\', '.').replace('/', '.').replace(':', '.') match_list = [] for (module_name, m) in sys.modules.items(): lowered_module_name = winlower(module_name) if (root_dotted + '.').startswith(lowered_module_name + '.'): match_list.append((len(module_name), module_name)) if lowered_module_name == root_dotted: break match_list.sort() match_list.reverse() for (matched_len, matched_module) in match_list: try: module_dir = FindModuleDir(matched_module) except IOError: continue suffix = root[matched_len:] if suffix == '': path = module_dir + ext else: path = os.path.join(module_dir, suffix.strip('\\')) + ext scriptname = CalcScriptName(path, fAllowAnyExt = False) if os.path.isfile(scriptname): return scriptname # # Check .pyw files # scriptname += 'w' if scriptname.endswith(PYTHONW_FILE_EXTENSION) and os.path.isfile(scriptname): return scriptname raise IOError def FindFile( filename, sources_paths = [], fModules = False, fAllowAnyExt = True ): """ FindFile looks for the full path of a script in a rather non-strict and human like behavior. It will always look for .py or .pyw files even if a .pyc or no extension is given. 1. It will check against loaded modules if asked. 1. full path (if exists). 2. sources_paths. 2. current path. 3. PYTHONPATH 4. PATH """ if filename.startswith('<'): raise IOError filename = filename.strip('\'"') if fModules and not (os.path.isabs(filename) or filename.startswith('.')): try: return FindFileAsModule(filename) except IOError: pass if fAllowAnyExt: try: abspath = FindFile( filename, sources_paths, fModules = False, fAllowAnyExt = False ) return abspath except IOError: pass if os.path.isabs(filename) or filename.startswith('.'): abspath = my_abspath(filename) lowered = winlower(abspath) scriptname = CalcScriptName(lowered, fAllowAnyExt) if os.path.isfile(scriptname): return scriptname # # Check .pyw files # scriptname += 'w' if scriptname.endswith(PYTHONW_FILE_EXTENSION) and os.path.isfile(scriptname): return scriptname raise IOError scriptname = CalcScriptName(filename, fAllowAnyExt) cwd = os.getcwd() env_path = os.environ['PATH'] paths = sources_paths + [cwd] + g_initial_cwd + sys.path + env_path.split(os.pathsep) for p in paths: f = os.path.join(p, scriptname) abspath = my_abspath(f) lowered = winlower(abspath) if os.path.isfile(lowered): return lowered # # Check .pyw files # lowered += 'w' if lowered.endswith(PYTHONW_FILE_EXTENSION) and os.path.isfile(lowered): return lowered raise IOError def IsFileInPath(filename): if filename == '': return False try: FindFile(filename) return True except IOError: return False def IsPrefixInEnviron(str): for e in os.environ.keys(): if e.startswith(str): return True return False def CalcTerminalCommand(): """ Calc the unix command to start a new terminal, for example: xterm """ if RPDBTERM in os.environ: term = os.environ[RPDBTERM] if IsFileInPath(term): return term if COLORTERM in os.environ: term = os.environ[COLORTERM] if IsFileInPath(term): return term if IsPrefixInEnviron(KDE_PREFIX): (s, term) = commands.getstatusoutput(KDE_DEFAULT_TERM_QUERY) if (s == 0) and IsFileInPath(term): return term elif IsPrefixInEnviron(GNOME_PREFIX): if IsFileInPath(GNOME_DEFAULT_TERM): return GNOME_DEFAULT_TERM if IsFileInPath(XTERM): return XTERM if IsFileInPath(RXVT): return RXVT raise SpawnUnsupported def CalcMacTerminalCommand(command): """ Calculate what to put in popen to start a given script. Starts a tiny Applescript that performs the script action. """ # # Quoting is a bit tricky; we do it step by step. # Make Applescript string: put backslashes before double quotes and # backslashes. # command = command.replace('\\', '\\\\').replace('"', '\\"') # # Make complete Applescript command. # command = 'tell application "Terminal" to do script "%s"' % command # # Make a shell single quoted string (put backslashed single quotes # outside string). # command = command.replace("'", "'\\''") # # Make complete shell command. # return "osascript -e '%s'" % command def winlower(path): """ return lowercase version of 'path' on NT systems. On NT filenames are case insensitive so lowercase filenames for comparison purposes on NT. """ if os.name == 'nt': return path.lower() return path def is_blender_file(filename): """ Return True if filename refers to a blender file. Support for debugging of Blender Python scripts. Blender scripts are not always saved on disk, and their source has to be queried directly from the Blender API. http://www.blender.org """ if not 'Blender.Text' in sys.modules: return False if filename == BLENDER_NO_SOURCE_FILENAME: return True _filename = os.path.basename(filename) try: sys.modules['Blender.Text'].get(_filename) return True except NameError: f = winlower(_filename) tlist = sys.modules['Blender.Text'].get() for t in tlist: n = winlower(t.getName()) if n == f: return True return False def get_blender_source(filename): """ Return list of lines in the file refered by filename. Support for debugging of Blender Python scripts. Blender scripts are not always saved on disk, and their source has to be queried directly from the Blender API. http://www.blender.org """ if filename.startswith('<'): raise IOError(ERROR_NO_BLENDER_SOURCE) _filename = os.path.basename(filename) lines = g_blender_text.get(_filename, None) if lines is not None: return lines f = winlower(_filename) lines = g_blender_text.get(f, None) if lines is not None: return lines try: t = sys.modules['Blender.Text'].get(_filename) lines = t.asLines() g_blender_text[_filename] = lines return lines except NameError: f = winlower(_filename) tlist = sys.modules['Blender.Text'].get() for _t in tlist: n = winlower(_t.getName()) if n == f: t = _t break lines = t.asLines() g_blender_text[f] = lines return lines def get_source_line(filename, lineno, fBlender): """ Return source line from file. """ if fBlender: lines = get_blender_source(filename) try: line = lines[lineno - 1] + '\n' return line except IndexError: return '' line = linecache.getline(filename, lineno) return line def _getpid(): try: return os.getpid() except: return -1 def calcURL(host, port): """ Form HTTP URL from 'host' and 'port' arguments. """ url = "http://" + str(host) + ":" + str(port) return url def GetSocketError(e): if (not isinstance(e.args, tuple)) or (len(e.args) == 0): return -1 return e.args[0] def ControlRate(t_last_call, max_rate): """ Limits rate at which this function is called by sleeping. Returns the time of invocation. """ p = 1.0 / max_rate t_current = time.time() dt = t_current - t_last_call if dt < p: time.sleep(p - dt) return t_current def generate_rid(): """ Return a 7 digits random id. """ return repr(random.randint(1000000, 9999999)) def generate_random_char(str): """ Return a random character from string argument. """ if str == '': return '' i = random.randint(0, len(str) - 1) return str[i] def generate_random_password(): """ Generate an 8 characters long password. """ s = 'abdefghijmnqrt' + 'ABDEFGHJLMNQRTY' ds = '23456789_' + s pwd = generate_random_char(s) for i in range(0, 7): pwd += generate_random_char(ds) return pwd def is_encryption_supported(): """ Is the Crypto module imported/available. """ return 'DES' in globals() def calc_suffix(str, n): """ Return an n charaters suffix of the argument string of the form '...suffix'. """ if len(str) <= n: return str return '...' + str[-(n - 3):] def calc_prefix(str, n): """ Return an n charaters prefix of the argument string of the form 'prefix...'. """ if len(str) <= n: return str return str[: (n - 3)] + '...' def create_rpdb_settings_folder(): """ Create the settings folder on Posix systems: '~/.rpdb2_settings' with mode 700. """ if os.name != POSIX: return home = os.path.expanduser('~') rsf = os.path.join(home, RPDB_SETTINGS_FOLDER) if not os.path.exists(rsf): os.mkdir(rsf, 0700) pwds = os.path.join(home, RPDB_PWD_FOLDER) if not os.path.exists(pwds): os.mkdir(pwds, 0700) bpl = os.path.join(home, RPDB_BPL_FOLDER) if not os.path.exists(bpl): os.mkdir(bpl, 0700) def cleanup_bpl_folder(path): if random.randint(0, 10) > 0: return l = os.listdir(path) if len(l) < MAX_BPL_FILES: return try: ll = [(os.stat(os.path.join(path, f))[stat.ST_ATIME], f) for f in l] except: return ll.sort() print ll for (t, f) in ll[: -MAX_BPL_FILES]: try: os.remove(os.path.join(path, f)) except: pass def calc_bpl_filename(filename): tmp_filename = md5.new(filename).hexdigest()[:10] if os.name == POSIX: home = os.path.expanduser('~') bpldir = os.path.join(home, RPDB_BPL_FOLDER) cleanup_bpl_folder(bpldir) path = os.path.join(bpldir, tmp_filename) + BREAKPOINTS_FILE_EXT return path # # gettempdir() is used since it works with unicode user names on # Windows. # tmpdir = tempfile.gettempdir() bpldir = os.path.join(tmpdir, RPDB_BPL_FOLDER_NT) if not os.path.exists(bpldir): # # Folder creation is done here since this is a temp folder. # try: os.mkdir(bpldir, 0700) except: print_debug() raise CException else: cleanup_bpl_folder(bpldir) path = os.path.join(bpldir, tmp_filename) + BREAKPOINTS_FILE_EXT return path def calc_pwd_file_path(rid): """ Calc password file path for Posix systems: '~/.rpdb2_settings/' """ home = os.path.expanduser('~') rsf = os.path.join(home, RPDB_PWD_FOLDER) pwd_file_path = os.path.join(rsf, rid) return pwd_file_path def create_pwd_file(rid, pwd): """ Create password file for Posix systems. """ if os.name != POSIX: return path = calc_pwd_file_path(rid) fd = os.open(path, os.O_WRONLY | os.O_CREAT, 0600) os.write(fd, pwd) os.close(fd) def read_pwd_file(rid): """ Read password from password file for Posix systems. """ assert(os.name == POSIX) path = calc_pwd_file_path(rid) p = open(path, 'r') pwd = p.read() p.close() return pwd def delete_pwd_file(rid): """ Delete password file for Posix systems. """ if os.name != POSIX: return path = calc_pwd_file_path(rid) try: os.remove(path) except: pass def ParseLineEncoding(l): if l.startswith('# -*- coding: '): e = l[len('# -*- coding: '):].split()[0] return e if l.startswith('# vim:fileencoding='): e = l[len('# vim:fileencoding='):].strip() return e return None def ParseEncoding(txt): """ Parse document encoding according to: http://docs.python.org/ref/encodings.html """ if txt.startswith('\xef\xbb\xbf'): return 'utf8' l = txt.split('\n', 2) e = ParseLineEncoding(l[0]) if e is not None: return e if len(l) == 1: return None e = ParseLineEncoding(l[1]) return e # #--------------------------------------- Crypto --------------------------------------- # class CCrypto: """ Handle authentication and encryption of data, using password protection. """ m_keys = {} def __init__(self, pwd, fAllowUnencrypted, rid): self.m_pwd = pwd self.m_key = self.__calc_key(pwd) self.m_fAllowUnencrypted = fAllowUnencrypted self.m_rid = rid self.m_failure_lock = threading.RLock() self.m_lock = threading.RLock() self.m_index_anchor_in = random.randint(0, 1000000000) self.m_index_anchor_ex = 0 self.m_index = 0 self.m_index_table = {} self.m_index_table_size = INDEX_TABLE_SIZE self.m_max_index = 0 def __calc_key(self, pwd): """ Create and return a key from a password. A Weak password means a weak key. """ if pwd in CCrypto.m_keys: return CCrypto.m_keys[pwd] d = md5.new() key = pwd # # The following loop takes around a second to complete # and should strengthen the password by ~16 bits. # a good password is ~30 bits strong so we are looking # at ~45 bits strong key # for i in range(2 ** 16): d.update(key * 64) key = d.digest() CCrypto.m_keys[pwd] = key return key def set_index(self, i, anchor): try: self.m_lock.acquire() self.m_index = i self.m_index_anchor_ex = anchor finally: self.m_lock.release() def get_max_index(self): return self.m_max_index def is_encrypted(self, str): return str.startswith(ENCRYPTION_PREFIX) def do_crypto(self, s, fEncryption): """ Sign string s and possibly encrypt it. Return signed/encrypted string. """ _s = self.__sign(s) if not fEncryption: if self.m_fAllowUnencrypted: return _s raise EncryptionExpected if not is_encryption_supported(): raise EncryptionNotSupported s_encrypted = self.__encrypt(_s) return s_encrypted def undo_crypto(self, s_encrypted, fVerifyIndex = True): """ Take crypto string, verify its signature and decrypt it, if needed. """ (_s, fEncryption) = self.__decrypt(s_encrypted) s = self.__verify_signature(_s, fVerifyIndex) return (s, fEncryption) def __encrypt(self, s): s_padded = s + '0' * (DES.block_size - (len(s) % DES.block_size)) key_padded = (self.m_key + '0' * (DES.key_size - (len(self.m_key) % DES.key_size)))[:DES.key_size] iv = '0' * DES.block_size d = DES.new(key_padded, DES.MODE_CBC, iv) r = d.encrypt(s_padded) s_encoded = base64.encodestring(r) return ENCRYPTION_PREFIX + s_encoded def __decrypt(self, s): if not s.startswith(ENCRYPTION_PREFIX): if self.m_fAllowUnencrypted: return (s, False) raise EncryptionExpected if not is_encryption_supported(): raise EncryptionNotSupported s_encoded = s[len(ENCRYPTION_PREFIX):] try: _r = base64.decodestring(s_encoded) key_padded = (self.m_key + '0' * (DES.key_size - (len(self.m_key) % DES.key_size)))[:DES.key_size] iv = '0' * DES.block_size d = DES.new(key_padded, DES.MODE_CBC, iv) _s = d.decrypt(_r) return (_s, True) except: self.__wait_a_little() raise DecryptionFailure def __sign(self, s): i = self.__get_next_index() _s = cPickle.dumps((self.m_index_anchor_ex, i, self.m_rid, s)) h = hmac.new(self.m_key, _s, md5) _d = h.digest() r = (_d, _s) s_signed = cPickle.dumps(r) return s_signed def __get_next_index(self): try: self.m_lock.acquire() self.m_index += 1 return self.m_index finally: self.m_lock.release() def __verify_signature(self, s, fVerifyIndex): try: r = cPickle.loads(s) (_d, _s) = r h = hmac.new(self.m_key, _s, md5) d = h.digest() if _d != d: self.__wait_a_little() raise AuthenticationFailure (anchor, i, id, s_original) = cPickle.loads(_s) except AuthenticationFailure: raise except: print_debug() self.__wait_a_little() raise AuthenticationBadData if fVerifyIndex: self.__verify_index(anchor, i, id) return s_original def __verify_index(self, anchor, i, id): """ Manage messages ids to prevent replay of old messages. """ try: try: self.m_lock.acquire() if anchor != self.m_index_anchor_in: raise AuthenticationBadIndex(self.m_max_index, self.m_index_anchor_in) if i > self.m_max_index + INDEX_TABLE_SIZE / 2: raise AuthenticationBadIndex(self.m_max_index, self.m_index_anchor_in) i_mod = i % INDEX_TABLE_SIZE (iv, idl) = self.m_index_table.get(i_mod, (None, None)) #print >> sys.__stderr__, i, i_mod, iv, self.m_max_index if (iv is None) or (i > iv): idl = [id] elif (iv == i) and (not id in idl): idl.append(id) else: raise AuthenticationBadIndex(self.m_max_index, self.m_index_anchor_in) self.m_index_table[i_mod] = (i, idl) if i > self.m_max_index: self.m_max_index = i return self.m_index finally: self.m_lock.release() except: self.__wait_a_little() raise def __wait_a_little(self): self.m_failure_lock.acquire() time.sleep((1.0 + random.random()) / 2) self.m_failure_lock.release() # # --------------------------------- Events List -------------------------- # class CEvent: """ Base class for events. """ def is_match(self, arg): pass class CEventExit(CEvent): """ Debuggee is about to terminate. """ pass class CEventState(CEvent): """ State of the debugger. Value of m_state can be one of the STATE_* globals. """ def __init__(self, state): self.m_state = state def is_match(self, arg): return self.m_state == arg class CEventTrap(CEvent): """ Mode of "trap unhandled exceptions". Sent when the mode changes. """ def __init__(self, ftrap): self.m_ftrap = ftrap def is_match(self, arg): return self.m_ftrap == arg class CEventUnhandledException(CEvent): """ Unhandled Exception Sent when an unhandled exception is caught. """ class CEventNamespace(CEvent): """ Namespace has changed. This tells the debugger it should query the namespace again. """ pass class CEventNoThreads(CEvent): """ No threads to debug. Debuggee notifies the debugger that it has no threads. This can happen in embedded debugging and in a python interpreter session. """ pass class CEventThreads(CEvent): """ State of threads. """ def __init__(self, current_thread, thread_list): self.m_current_thread = current_thread self.m_thread_list = thread_list class CEventThreadBroken(CEvent): """ A thread has broken. """ def __init__(self, tid, name): self.m_tid = tid self.m_name = name class CEventStack(CEvent): """ Stack of current thread. """ def __init__(self, stack): self.m_stack = stack class CEventStackFrameChange(CEvent): """ Stack frame has changed. This event is sent when the debugger goes up or down the stack. """ def __init__(self, frame_index): self.m_frame_index = frame_index class CEventStackDepth(CEvent): """ Stack depth has changed. """ def __init__(self, stack_depth, stack_depth_exception): self.m_stack_depth = stack_depth self.m_stack_depth_exception = stack_depth_exception class CEventBreakpoint(CEvent): """ A breakpoint or breakpoints changed. """ DISABLE = 'disable' ENABLE = 'enable' REMOVE = 'remove' SET = 'set' def __init__(self, bp, action = SET, id_list = [], fAll = False): self.m_bp = copy.copy(bp) if self.m_bp is not None: self.m_bp.m_code = None self.m_action = action self.m_id_list = id_list self.m_fAll = fAll class CEventSync(CEvent): """ Internal (not sent to the debugger) event that trigers the firing of other events that help the debugger synchronize with the state of the debuggee. """ def __init__(self, fException, fSendUnhandled): self.m_fException = fException self.m_fSendUnhandled = fSendUnhandled # # --------------------------------- Event Manager -------------------------- # class CEventDispatcherRecord: """ Internal structure that binds a callback to particular events. """ def __init__(self, callback, event_type_dict, fSingleUse): self.m_callback = callback self.m_event_type_dict = event_type_dict self.m_fSingleUse = fSingleUse def is_match(self, event): rtl = [t for t in self.m_event_type_dict.keys() if isinstance(event, t)] if len(rtl) == 0: return False # # Examine first match only. # rt = rtl[0] rte = self.m_event_type_dict[rt].get(EVENT_EXCLUDE, []) if len(rte) != 0: for e in rte: if event.is_match(e): return False return True rte = self.m_event_type_dict[rt].get(EVENT_INCLUDE, []) if len(rte) != 0: for e in rte: if event.is_match(e): return True return False return True class CEventDispatcher: """ Events dispatcher. Dispatchers can be chained together. """ def __init__(self, chained_event_dispatcher = None): self.m_chained_event_dispatcher = chained_event_dispatcher self.m_chain_override_types = {} self.m_registrants = {} def shutdown(self): for er in self.m_registrants.keys(): self.__remove_dispatcher_record(er) def register_callback(self, callback, event_type_dict, fSingleUse): er = CEventDispatcherRecord(callback, event_type_dict, fSingleUse) # # If we have a chained dispatcher, register the callback on the # chained dispatcher as well. # if self.m_chained_event_dispatcher is not None: _er = self.__register_callback_on_chain(er, event_type_dict, fSingleUse) self.m_registrants[er] = _er return er self.m_registrants[er] = True return er def remove_callback(self, callback): erl = [er for er in self.m_registrants.keys() if er.m_callback == callback] for er in erl: self.__remove_dispatcher_record(er) def fire_events(self, event_list): for event in event_list: self.fire_event(event) def fire_event(self, event): for er in self.m_registrants.keys(): self.__fire_er(event, er) def __fire_er(self, event, er): if not er.is_match(event): return try: er.m_callback(event) except: pass if not er.m_fSingleUse: return try: del self.m_registrants[er] except KeyError: pass def register_chain_override(self, event_type_dict): """ Chain override prevents registration on chained dispatchers for specific event types. """ for t in event_type_dict.keys(): self.m_chain_override_types[t] = True def __register_callback_on_chain(self, er, event_type_dict, fSingleUse): _event_type_dict = copy.copy(event_type_dict) for t in self.m_chain_override_types: if t in _event_type_dict: del _event_type_dict[t] if len(_event_type_dict) == 0: return False def callback(event, er = er): self.__fire_er(event, er) _er = self.m_chained_event_dispatcher.register_callback(callback, _event_type_dict, fSingleUse) return _er def __remove_dispatcher_record(self, er): try: if self.m_chained_event_dispatcher is not None: _er = self.m_registrants[er] if _er != False: self.m_chained_event_dispatcher.__remove_dispatcher_record(_er) del self.m_registrants[er] except KeyError: pass class CEventQueue: """ Add queue semantics above an event dispatcher. Instead of firing event callbacks, new events are returned in a list upon request. """ def __init__(self, event_dispatcher, max_event_list_length = MAX_EVENT_LIST_LENGTH): self.m_event_dispatcher = event_dispatcher self.m_event_lock = threading.Condition(threading.Lock()) self.m_max_event_list_length = max_event_list_length self.m_event_list = [] self.m_event_index = 0 def shutdown(self): self.m_event_dispatcher.remove_callback(self.event_handler) def register_event_types(self, event_type_dict): self.m_event_dispatcher.register_callback(self.event_handler, event_type_dict, fSingleUse = False) def event_handler(self, event): try: self.m_event_lock.acquire() self.m_event_list.append(event) if len(self.m_event_list) > self.m_max_event_list_length: self.m_event_list.pop(0) self.m_event_index += 1 self.m_event_lock.notifyAll() finally: self.m_event_lock.release() def get_event_index(self): return self.m_event_index def wait_for_event(self, timeout, event_index): """ Return the new events which were fired. """ try: self.m_event_lock.acquire() if event_index >= self.m_event_index: self.m_event_lock.wait(timeout) if event_index >= self.m_event_index: return (self.m_event_index, []) sub_event_list = self.m_event_list[event_index - self.m_event_index:] return (self.m_event_index, sub_event_list) finally: self.m_event_lock.release() class CStateManager: """ Manage possible debugger states (broken, running, etc...) The state manager can receive state changes via an input event dispatcher or via the set_state() method It sends state changes forward to the output event dispatcher. The state can also be queried or waited for. """ def __init__(self, initial_state, event_dispatcher_output = None, event_dispatcher_input = None): self.m_event_dispatcher_input = event_dispatcher_input self.m_event_dispatcher_output = event_dispatcher_output if self.m_event_dispatcher_input is not None: event_type_dict = {CEventState: {}} self.m_event_dispatcher_input.register_callback(self.event_handler, event_type_dict, fSingleUse = False) if self.m_event_dispatcher_output is not None: self.m_event_dispatcher_output.register_chain_override(event_type_dict) self.m_state_lock = threading.Condition(threading.Lock()) self.m_state_queue = [] self.m_state_index = 0 self.m_waiter_list = {} self.set_state(initial_state) def shutdown(self): if self.m_event_dispatcher_input is not None: self.m_event_dispatcher_input.remove_callback(self.event_handler) def event_handler(self, event): self.set_state(event.m_state) def get_state(self): return self.m_state_queue[-1] def __add_state(self, state): self.m_state_queue.append(state) self.m_state_index += 1 self.__remove_states() def __remove_states(self, treshold = None): """ Clean up old state changes from the state queue. """ index = self.__calc_min_index() if (treshold is not None) and (index <= treshold): return _delta = 1 + self.m_state_index - index self.m_state_queue = self.m_state_queue[-_delta:] def __calc_min_index(self): """ Calc the minimum state index. The calculated index is the oldest state of which all state waiters are aware of. That is, no one cares for older states and these can be removed from the state queue. """ if len(self.m_waiter_list) == 0: return self.m_state_index index_list = self.m_waiter_list.keys() min_index = min(index_list) return min_index def __add_waiter(self): index = self.m_state_index n = self.m_waiter_list.get(index, 0) self.m_waiter_list[index] = n + 1 return index def __remove_waiter(self, index): n = self.m_waiter_list[index] if n == 1: del self.m_waiter_list[index] self.__remove_states(index) else: self.m_waiter_list[index] = n - 1 def __get_states(self, index): _delta = 1 + self.m_state_index - index states = self.m_state_queue[-_delta:] return states def set_state(self, state = None, fLock = True): try: if fLock: self.m_state_lock.acquire() if state is None: state = self.get_state() self.__add_state(state) self.m_state_lock.notifyAll() finally: if fLock: self.m_state_lock.release() if self.m_event_dispatcher_output is not None: event = CEventState(state) self.m_event_dispatcher_output.fire_event(event) def wait_for_state(self, state_list): """ Wait for any of the states in the state list. """ try: self.m_state_lock.acquire() if self.get_state() in state_list: return self.get_state() while True: index = self.__add_waiter() self.m_state_lock.wait(PING_TIMEOUT) states = self.__get_states(index) self.__remove_waiter(index) for state in states: if state in state_list: return state finally: self.m_state_lock.release() def acquire(self): self.m_state_lock.acquire() def release(self): self.m_state_lock.release() # # -------------------------------------- Break Info manager --------------------------------------- # def CalcValidLines(code): l = code.co_firstlineno vl = [l] bl = [ord(c) for c in code.co_lnotab[2::2]] sl = [ord(c) for c in code.co_lnotab[1::2]] for (bi, si) in zip(bl, sl): l += si if bi == 0: continue if l != vl[-1]: vl.append(l) if len(sl) > 0: l += sl[-1] if l != vl[-1]: vl.append(l) return vl class CScopeBreakInfo: def __init__(self, fqn, valid_lines): self.m_fqn = fqn self.m_first_line = valid_lines[0] self.m_last_line = valid_lines[-1] self.m_valid_lines = valid_lines def CalcScopeLine(self, lineno): rvl = copy.copy(self.m_valid_lines) rvl.reverse() for l in rvl: if lineno >= l: break return l def __str__(self): return "('" + self.m_fqn + "', " + str(self.m_valid_lines) + ')' class CFileBreakInfo: """ Break info structure for a source file. """ def __init__(self, filename): self.m_filename = filename self.m_first_line = 0 self.m_last_line = 0 self.m_scope_break_info = [] def CalcBreakInfo(self): if is_blender_file(self.m_filename): lines = get_blender_source(self.m_filename) source = '\n'.join(lines) + '\n' else: f = open(self.m_filename, "r") source = f.read() f.close() self.__CalcBreakInfoFromSource(source) def __CalcBreakInfoFromSource(self, source): _source = source.replace('\r\n', '\n') + '\n' code = compile(_source, self.m_filename, "exec") self.m_scope_break_info = [] self.m_first_line = code.co_firstlineno self.m_last_line = 0 fqn = [] t = [code] while len(t) > 0: c = t.pop(0) if type(c) == tuple: self.m_scope_break_info.append(CScopeBreakInfo(*c)) fqn.pop() continue fqn = fqn + [c.co_name] valid_lines = CalcValidLines(c) self.m_last_line = max(self.m_last_line, valid_lines[-1]) _fqn = string.join(fqn, '.') si = (_fqn, valid_lines) subcodeslist = self.__CalcSubCodesList(c) t = subcodeslist + [si] + t def __CalcSubCodesList(self, code): tc = type(code) t = [(c.co_firstlineno, c) for c in code.co_consts if type(c) == tc] t.sort() scl = [c[1] for c in t] return scl def FindScopeByLineno(self, lineno): lineno = max(min(lineno, self.m_last_line), self.m_first_line) smaller_element = None exact_element = None for sbi in self.m_scope_break_info: if lineno > sbi.m_last_line: if (smaller_element is None) or (sbi.m_last_line >= smaller_element.m_last_line): smaller_element = sbi continue if (lineno >= sbi.m_first_line) and (lineno <= sbi.m_last_line): exact_element = sbi break assert(exact_element is not None) scope = exact_element l = exact_element.CalcScopeLine(lineno) if (smaller_element is not None) and (l <= smaller_element.m_last_line): scope = smaller_element l = smaller_element.CalcScopeLine(lineno) return (scope, l) def FindScopeByName(self, name, offset): if name.startswith(MODULE_SCOPE): alt_scope = MODULE_SCOPE2 + name[len(MODULE_SCOPE):] elif name.startswith(MODULE_SCOPE2): alt_scope = MODULE_SCOPE + name[len(MODULE_SCOPE2):] else: return self.FindScopeByName(MODULE_SCOPE2 + SCOPE_SEP + name, offset) for sbi in self.m_scope_break_info: if sbi.m_fqn in [name, alt_scope]: l = sbi.CalcScopeLine(sbi.m_first_line + offset) return (sbi, l) raise InvalidScopeName class CBreakInfoManager: """ Manage break info dictionary per filename. """ def __init__(self): self.m_file_info_dic = {} def addFile(self, filename): mbi = CFileBreakInfo(filename) mbi.CalcBreakInfo() self.m_file_info_dic[filename] = mbi def getFile(self, filename): if not filename in self.m_file_info_dic: self.addFile(filename) return self.m_file_info_dic[filename] # # -------------------------------- Break Point Manager ----------------------------- # class CBreakPoint: def __init__(self, filename, scope_fqn, scope_first_line, lineno, fEnabled, expr, fTemporary = False): """ Breakpoint constructor. scope_fqn - scope fully qualified name. e.g: module.class.method """ self.m_id = None self.m_fEnabled = fEnabled self.m_filename = filename self.m_scope_fqn = scope_fqn self.m_scope_name = scope_fqn.split(SCOPE_SEP)[-1] self.m_scope_first_line = scope_first_line self.m_scope_offset = lineno - scope_first_line self.m_lineno = lineno self.m_expr = expr self.m_code = None self.m_fTemporary = fTemporary if (expr is not None) and (expr != ''): self.m_code = compile(expr, '', 'eval') def calc_enclosing_scope_name(self): if self.m_scope_offset != 0: return None if self.m_scope_fqn in [MODULE_SCOPE, MODULE_SCOPE2]: return None scope_name_list = self.m_scope_fqn.split(SCOPE_SEP) enclosing_scope_name = scope_name_list[-2] return enclosing_scope_name def enable(self): self.m_fEnabled = True def disable(self): self.m_fEnabled = False def isEnabled(self): return self.m_fEnabled def __str__(self): return "('" + self.m_filename + "', '" + self.m_scope_fqn + "', " + str(self.m_scope_first_line) + ', ' + str(self.m_scope_offset) + ', ' + str(self.m_lineno) + ')' class CBreakPointsManagerProxy: """ A proxy for the breakpoint manager. While the breakpoint manager resides on the debuggee (the server), the proxy resides in the debugger (the client - session manager) """ def __init__(self, session_manager): self.m_session_manager = session_manager self.m_break_points_by_file = {} self.m_break_points_by_id = {} self.m_lock = threading.Lock() # # The breakpoint proxy inserts itself between the two chained # event dispatchers in the session manager. # event_type_dict = {CEventBreakpoint: {}} self.m_session_manager.m_event_dispatcher_proxy.register_callback(self.update_bp, event_type_dict, fSingleUse = False) self.m_session_manager.m_event_dispatcher.register_chain_override(event_type_dict) def update_bp(self, event): """ Handle breakpoint updates that arrive via the event dispatcher. """ try: self.m_lock.acquire() if event.m_fAll: id_list = self.m_break_points_by_id.keys() else: id_list = event.m_id_list if event.m_action == CEventBreakpoint.REMOVE: for id in id_list: try: bp = self.m_break_points_by_id.pop(id) bpm = self.m_break_points_by_file[bp.m_filename] del bpm[bp.m_lineno] if len(bpm) == 0: del self.m_break_points_by_file[bp.m_filename] except KeyError: pass return if event.m_action == CEventBreakpoint.DISABLE: for id in id_list: try: bp = self.m_break_points_by_id[id] bp.disable() except KeyError: pass return if event.m_action == CEventBreakpoint.ENABLE: for id in id_list: try: bp = self.m_break_points_by_id[id] bp.enable() except KeyError: pass return bpm = self.m_break_points_by_file.get(event.m_bp.m_filename, {}) bpm[event.m_bp.m_lineno] = event.m_bp self.m_break_points_by_id[event.m_bp.m_id] = event.m_bp finally: self.m_lock.release() self.m_session_manager.m_event_dispatcher.fire_event(event) def sync(self): try: self.m_lock.acquire() self.m_break_points_by_file = {} self.m_break_points_by_id = {} finally: self.m_lock.release() break_points_by_id = self.m_session_manager.getSession().getProxy().get_breakpoints() try: self.m_lock.acquire() self.m_break_points_by_id.update(break_points_by_id) for bp in self.m_break_points_by_id.values(): bpm = self.m_break_points_by_file.get(bp.m_filename, {}) bpm[bp.m_lineno] = bp finally: self.m_lock.release() def clear(self): try: self.m_lock.acquire() self.m_break_points_by_file = {} self.m_break_points_by_id = {} finally: self.m_lock.release() def get_breakpoints(self): return self.m_break_points_by_id def get_breakpoint(self, filename, lineno): bpm = self.m_break_points_by_file[filename] bp = bpm[lineno] return bp class CBreakPointsManager: def __init__(self): self.m_break_info_manager = CBreakInfoManager() self.m_active_break_points_by_file = {} self.m_break_points_by_function = {} self.m_break_points_by_file = {} self.m_break_points_by_id = {} self.m_lock = threading.Lock() self.m_temp_bp = None self.m_fhard_tbp = False def get_active_break_points_by_file(self, filename): """ Get active breakpoints for file. """ _filename = winlower(filename) return self.m_active_break_points_by_file.setdefault(_filename, {}) def __calc_active_break_points_by_file(self, filename): bpmpt = self.m_active_break_points_by_file.setdefault(filename, {}) bpmpt.clear() bpm = self.m_break_points_by_file.get(filename, {}) for bp in bpm.values(): if bp.m_fEnabled: bpmpt[bp.m_lineno] = bp tbp = self.m_temp_bp if (tbp is not None) and (tbp.m_filename == filename): bpmpt[tbp.m_lineno] = tbp def __remove_from_function_list(self, bp): function_name = bp.m_scope_name try: bpf = self.m_break_points_by_function[function_name] del bpf[bp] if len(bpf) == 0: del self.m_break_points_by_function[function_name] except KeyError: pass # # In some cases a breakpoint belongs to two scopes at the # same time. For example a breakpoint on the declaration line # of a function. # _function_name = bp.calc_enclosing_scope_name() if _function_name is None: return try: _bpf = self.m_break_points_by_function[_function_name] del _bpf[bp] if len(_bpf) == 0: del self.m_break_points_by_function[_function_name] except KeyError: pass def __add_to_function_list(self, bp): function_name = bp.m_scope_name bpf = self.m_break_points_by_function.setdefault(function_name, {}) bpf[bp] = True # # In some cases a breakpoint belongs to two scopes at the # same time. For example a breakpoint on the declaration line # of a function. # _function_name = bp.calc_enclosing_scope_name() if _function_name is None: return _bpf = self.m_break_points_by_function.setdefault(_function_name, {}) _bpf[bp] = True def get_breakpoint(self, filename, lineno): """ Get breakpoint by file and line number. """ bpm = self.m_break_points_by_file[filename] bp = bpm[lineno] return bp def del_temp_breakpoint(self, fLock = True, breakpoint = None): """ Delete a temoporary breakpoint. A temporary breakpoint is used when the debugger is asked to run-to a particular line. Hard temporary breakpoints are deleted only when actually hit. """ if self.m_temp_bp is None: return try: if fLock: self.m_lock.acquire() if self.m_temp_bp is None: return if self.m_fhard_tbp and not breakpoint is self.m_temp_bp: return bp = self.m_temp_bp self.m_temp_bp = None self.m_fhard_tbp = False self.__remove_from_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) finally: if fLock: self.m_lock.release() def set_temp_breakpoint(self, filename, scope, lineno, fhard = False): """ Set a temoporary breakpoint. A temporary breakpoint is used when the debugger is asked to run-to a particular line. Hard temporary breakpoints are deleted only when actually hit. """ _filename = winlower(filename) mbi = self.m_break_info_manager.getFile(_filename) if scope != '': (s, l) = mbi.FindScopeByName(scope, lineno) else: (s, l) = mbi.FindScopeByLineno(lineno) bp = CBreakPoint(_filename, s.m_fqn, s.m_first_line, l, fEnabled = True, expr = '', fTemporary = True) try: self.m_lock.acquire() self.m_fhard_tbp = False self.del_temp_breakpoint(fLock = False) self.m_fhard_tbp = fhard self.m_temp_bp = bp self.__add_to_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) finally: self.m_lock.release() def set_breakpoint(self, filename, scope, lineno, fEnabled, expr): """ Set breakpoint. scope - a string (possibly empty) with the dotted scope of the breakpoint. eg. 'my_module.my_class.foo' expr - a string (possibly empty) with a python expression that will be evaluated at the scope of the breakpoint. The breakpoint will be hit if the expression evaluates to True. """ _filename = winlower(filename) mbi = self.m_break_info_manager.getFile(_filename) if scope != '': (s, l) = mbi.FindScopeByName(scope, lineno) else: (s, l) = mbi.FindScopeByLineno(lineno) bp = CBreakPoint(_filename, s.m_fqn, s.m_first_line, l, fEnabled, expr) try: self.m_lock.acquire() bpm = self.m_break_points_by_file.setdefault(_filename, {}) # # If a breakpoint on the same line is found we use its ID. # Since the debugger lists breakpoints by IDs, this has # a similar effect to modifying the breakpoint. # try: old_bp = bpm[l] id = old_bp.m_id self.__remove_from_function_list(old_bp) except KeyError: # # Find the smallest available ID. # bpids = self.m_break_points_by_id.keys() bpids.sort() id = 0 while id < len(bpids): if bpids[id] != id: break id += 1 bp.m_id = id self.m_break_points_by_id[id] = bp bpm[l] = bp if fEnabled: self.__add_to_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) return bp finally: self.m_lock.release() def disable_breakpoint(self, id_list, fAll): """ Disable breakpoint. """ try: self.m_lock.acquire() if fAll: id_list = self.m_break_points_by_id.keys() for id in id_list: try: bp = self.m_break_points_by_id[id] except KeyError: continue bp.disable() self.__remove_from_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) finally: self.m_lock.release() def enable_breakpoint(self, id_list, fAll): """ Enable breakpoint. """ try: self.m_lock.acquire() if fAll: id_list = self.m_break_points_by_id.keys() for id in id_list: try: bp = self.m_break_points_by_id[id] except KeyError: continue bp.enable() self.__add_to_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) finally: self.m_lock.release() def delete_breakpoint(self, id_list, fAll): """ Delete breakpoint. """ try: self.m_lock.acquire() if fAll: id_list = self.m_break_points_by_id.keys() for id in id_list: try: bp = self.m_break_points_by_id[id] except KeyError: continue filename = bp.m_filename lineno = bp.m_lineno bpm = self.m_break_points_by_file[filename] if bp == bpm[lineno]: del bpm[lineno] if len(bpm) == 0: del self.m_break_points_by_file[filename] self.__remove_from_function_list(bp) self.__calc_active_break_points_by_file(bp.m_filename) del self.m_break_points_by_id[id] finally: self.m_lock.release() def get_breakpoints(self): return self.m_break_points_by_id # # ----------------------------------- Core Debugger ------------------------------------ # class CCodeContext: """ Class represents info related to code objects. """ def __init__(self, frame, bp_manager): self.m_code = frame.f_code self.m_filename = calc_frame_path(frame) self.m_basename = os.path.basename(self.m_filename) self.m_file_breakpoints = bp_manager.get_active_break_points_by_file(self.m_filename) self.m_fExceptionTrap = False def is_untraced(self): """ Return True if this code object should not be traced. """ return self.m_basename in [THREADING_FILENAME, DEBUGGER_FILENAME] def is_exception_trap_frame(self): """ Return True if this frame should be a trap for unhandled exceptions. """ return self.m_basename == THREADING_FILENAME class CDebuggerCoreThread: """ Class represents a debugged thread. This is a core structure of the debugger. It includes most of the optimization tricks and hacks, and includes a good amount of subtle bug fixes, be carefull not to mess it up... """ def __init__(self, name, core_debugger, frame, event): self.m_thread_id = thread.get_ident() self.m_thread_name = name self.m_fBroken = False self.m_fUnhandledException = False self.m_frame = frame self.m_event = event self.m_ue_lineno = None self.m_uef_lineno = None self.m_code_context = core_debugger.get_code_context(frame) self.m_locals_copy = {} self.m_core = core_debugger self.m_bp_manager = core_debugger.m_bp_manager self.m_frame_lock = threading.Condition(threading.Lock()) self.m_frame_external_references = 0 def profile(self, frame, event, arg): """ Profiler method. The Python profiling mechanism is used by the debugger mainly to handle synchronization issues related to the life time of the frame structure. """ if event == 'return': self.m_frame = frame.f_back try: self.m_code_context = self.m_core.m_code_contexts[self.m_frame.f_code] except AttributeError: if self.m_event != 'return' and self.m_core.m_ftrap: # # An exception is raised from the outer-most frame. # This means an unhandled exception. # self.m_frame = frame self.m_event = 'exception' self.m_uef_lineno = self.m_ue_lineno self.m_fUnhandledException = True self.m_core._break(self, frame, event, arg) self.m_uef_lineno = None if frame in self.m_locals_copy: self.update_locals() self.m_frame = None self.m_core.remove_thread(self.m_thread_id) sys.setprofile(None) sys.settrace(self.m_core.trace_dispatch_init) if self.m_frame_external_references == 0: return # # Wait until no one references the frame object # try: self.m_frame_lock.acquire() while self.m_frame_external_references != 0: self.m_frame_lock.wait(1.0) finally: self.m_frame_lock.release() def frame_acquire(self): """ Aquire a reference to the frame. """ try: self.m_frame_lock.acquire() self.m_frame_external_references += 1 f = self.m_frame if f is None: raise ThreadDone return f finally: self.m_frame_lock.release() def frame_release(self): """ Release a reference to the frame. """ try: self.m_frame_lock.acquire() self.m_frame_external_references -= 1 if self.m_frame_external_references == 0: self.m_frame_lock.notify() finally: self.m_frame_lock.release() def get_frame(self, base_frame, index, fException = False): """ Get frame at index depth down the stack. Starting from base_frame return the index depth frame down the stack. If fException is True use the exception stack (traceback). """ if fException: tb = base_frame.f_exc_traceback if tb is None: raise NoExceptionFound while tb.tb_next is not None: tb = tb.tb_next f = tb.tb_frame else: f = base_frame while (index > 0) and (f is not None): f = f.f_back index -= 1 if (index < 0) or (f is None): raise InvalidFrame if (self.m_uef_lineno is not None) and (f.f_back is None): lineno = self.m_uef_lineno else: lineno = f.f_lineno if fException: tb = base_frame.f_exc_traceback while tb is not None: if tb.tb_frame == f: lineno = tb.tb_lineno break tb = tb.tb_next return (f, lineno) def get_locals_copy(self, frame_index, fException, fReadOnly): """ Get globals and locals of frame. A copy scheme is used for locals to work around a bug in Python 2.3 and 2.4 that prevents modifying the local dictionary. """ try: base_frame = self.frame_acquire() (f, lineno) = self.get_frame(base_frame, frame_index, fException) if fReadOnly: gc = copy.copy(f.f_globals) else: gc = f.f_globals try: (lc, olc) = self.m_locals_copy[f] except KeyError: if f.f_code.co_name in [MODULE_SCOPE, MODULE_SCOPE2]: lc = gc olc = gc else: lc = copy.copy(f.f_locals) olc = copy.copy(lc) if not fReadOnly: self.m_locals_copy[f] = (lc, olc) self.set_local_trace(f) return (gc, lc, olc) finally: f = None base_frame = None self.frame_release() def update_locals_copy(self): """ Update copy of locals with changes in locals. """ lct = self.m_locals_copy.get(self.m_frame, None) if lct is None: return (lc, base) = lct cr = copy.copy(self.m_frame.f_locals) b = [(k, repr(v)) for k, v in base.items()] sb = sets.Set(b) c = [(k, repr(v)) for k, v in cr.items()] sc = sets.Set(c) nsc = [k for (k, v) in sc - sb] for k in nsc: lc[k] = cr[k] def update_locals(self): """ Update locals with changes from copy of locals. """ lct = self.m_locals_copy.pop(self.m_frame, None) if lct is None: return self.m_frame.f_locals.update(lct[0]) def __eval_breakpoint(self, frame, bp): """ Return True if the breakpoint is hit. """ if not bp.m_fEnabled: return False if bp.m_expr == '': return True try: if frame in self.m_locals_copy: l = self.m_locals_copy[frame][0] v = eval(bp.m_code, frame.f_globals, l) else: v = eval(bp.m_code, frame.f_globals, frame.f_locals) return (v != False) except: return False def set_local_trace(self, frame): """ Set trace callback of frame. Specialized trace methods are selected here to save switching time during actual tracing. """ if not self.m_core.m_ftrace: frame.f_trace = self.trace_dispatch_stop return code_context = self.m_core.get_code_context(frame) if self.m_core.is_break(self, frame): frame.f_trace = self.trace_dispatch_break elif code_context.m_fExceptionTrap or (frame.f_back is None): frame.f_trace = self.trace_dispatch_trap elif frame.f_code.co_name in self.m_bp_manager.m_break_points_by_function: frame.f_trace = self.trace_dispatch elif frame in self.m_locals_copy: frame.f_trace = self.trace_dispatch elif frame == self.m_core.m_return_frame: frame.f_trace = self.trace_dispatch else: del frame.f_trace def set_tracers(self): """ Set trace callbacks for all frames in stack. """ try: try: f = self.frame_acquire() while f is not None: self.set_local_trace(f) f = f.f_back except ThreadDone: f = None finally: f = None self.frame_release() def trace_dispatch_stop(self, frame, event, arg): """ Disable tracing for this thread. """ if frame in self.m_locals_copy: self.update_locals() sys.settrace(None) sys.setprofile(None) return None def trace_dispatch_break(self, frame, event, arg): """ Trace method for breaking a thread. """ if event not in ['line', 'return', 'exception']: return frame.f_trace if event == 'exception': self.set_exc_info(arg) self.m_event = event if frame in self.m_locals_copy: self.update_locals_copy() self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) return frame.f_trace def trace_dispatch_call(self, frame, event, arg): """ Initial trace method for thread. """ if not self.m_core.m_ftrace: return self.trace_dispatch_stop(frame, event, arg) self.m_frame = frame try: self.m_code_context = self.m_core.m_code_contexts[frame.f_code] except KeyError: self.m_code_context = self.m_core.get_code_context(frame) if self.m_core.m_fBreak or (self.m_core.m_step_tid == self.m_thread_id): self.m_event = event self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) return frame.f_trace if not frame.f_code.co_name in self.m_bp_manager.m_break_points_by_function: return None bp = self.m_code_context.m_file_breakpoints.get(frame.f_lineno, None) if bp is not None and self.__eval_breakpoint(frame, bp): self.m_event = event self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) return frame.f_trace return self.trace_dispatch def trace_dispatch(self, frame, event, arg): """ General trace method for thread. """ if (event == 'line'): if frame in self.m_locals_copy: self.update_locals_copy() bp = self.m_code_context.m_file_breakpoints.get(frame.f_lineno, None) if bp is not None and self.__eval_breakpoint(frame, bp): self.m_event = event self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) return frame.f_trace if event == 'return': if frame in self.m_locals_copy: self.update_locals_copy() if frame == self.m_core.m_return_frame: self.m_event = event self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() return None if event == 'exception': if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) if not frame.f_exc_traceback is arg[2]: (frame.f_exc_type, frame.f_exc_value, frame.f_exc_traceback) = arg return frame.f_trace return frame.f_trace def trace_dispatch_trap(self, frame, event, arg): """ Trace method used for frames in which unhandled exceptions should be caught. """ if (event == 'line'): self.m_event = event if frame in self.m_locals_copy: self.update_locals_copy() bp = self.m_code_context.m_file_breakpoints.get(frame.f_lineno, None) if bp is not None and self.__eval_breakpoint(frame, bp): self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) return frame.f_trace if event == 'return': last_event = self.m_event self.m_event = event if frame in self.m_locals_copy: self.update_locals_copy() if frame == self.m_core.m_return_frame: self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() if last_event == 'exception': self.m_event = last_event return None if event == 'exception': self.m_event = event if self.m_code_context.m_fExceptionTrap and self.m_core.m_ftrap: self.set_exc_info(arg) self.m_fUnhandledException = True self.m_core._break(self, frame, event, arg) if frame in self.m_locals_copy: self.update_locals() return frame.f_trace self.m_ue_lineno = frame.f_lineno if frame in self.m_locals_copy: self.update_locals() self.set_local_trace(frame) if not frame.f_exc_traceback is arg[2]: (frame.f_exc_type, frame.f_exc_value, frame.f_exc_traceback) = arg return frame.f_trace return frame.f_trace def set_exc_info(self, arg): """ Set exception information in frames of stack. """ (t, v, tb) = arg while tb is not None: f = tb.tb_frame f.f_exc_type = t f.f_exc_value = v f.f_exc_traceback = tb tb = tb.tb_next def is_breakpoint(self): """ Calc if current line is hit by breakpoint. """ bp = self.m_code_context.m_file_breakpoints.get(self.m_frame.f_lineno, None) if bp is not None and self.__eval_breakpoint(self.m_frame, bp): return True return False def get_breakpoint(self): """ Return current line breakpoint if any. """ return self.m_code_context.m_file_breakpoints.get(self.m_frame.f_lineno, None) class CDebuggerCore: """ Base class for the debugger. Handles basic debugger functionality. """ def __init__(self, fembedded = False): self.m_ftrace = True self.m_current_ctx = None self.m_f_first_to_break = True self.m_f_break_on_init = False self.m_builtins_hack = None self.m_timer_embedded_giveup = None self.m_threads_lock = threading.Condition(threading.Lock()) self.m_threads = {} self.m_event_dispatcher = CEventDispatcher() self.m_state_manager = CStateManager(STATE_RUNNING, self.m_event_dispatcher) self.m_ftrap = True self.m_fUnhandledException = False self.m_fBreak = False self.m_lastest_event = None self.m_step_tid = None self.m_next_frame = None self.m_return_frame = None self.m_bp_manager = CBreakPointsManager() self.m_code_contexts = {None: None} self.m_fembedded = fembedded def shutdown(self): self.m_event_dispatcher.shutdown() self.m_state_manager.shutdown() def is_embedded(self): return self.m_fembedded def send_events(self, event): pass def set_request_go_timer(self, timeout): """ Set timeout thread to release debugger from waiting for a client to attach. """ if timeout is None: return _timeout = max(5.0, timeout) self.m_timer_embedded_giveup = threading.Timer(_timeout, self.request_go) self.m_timer_embedded_giveup.start() def cancel_request_go_timer(self): t = self.m_timer_embedded_giveup if t is not None: self.m_timer_embedded_giveup = None t.cancel() def setbreak(self, f): """ Set thread to break on next statement. """ tid = thread.get_ident() if not tid in self.m_threads: return self.settrace(f) ctx = self.m_threads[tid] f.f_trace = ctx.trace_dispatch_break self.m_next_frame = f def settrace(self, f = None, f_break_on_init = True, timeout = None, builtins_hack = None): """ Start tracing mechanism for thread. """ if not self.m_ftrace: return tid = thread.get_ident() if tid in self.m_threads: return self.set_request_go_timer(timeout) self.m_f_break_on_init = f_break_on_init self.m_builtins_hack = builtins_hack threading.settrace(self.trace_dispatch_init) sys.settrace(self.trace_dispatch_init) if f is not None: f.f_trace = self.trace_dispatch_init def stoptrace(self): """ Stop tracing mechanism for thread. """ threading.settrace(None) sys.settrace(None) sys.setprofile(None) self.m_ftrace = False self.set_all_tracers() try: self.request_go() except DebuggerNotBroken: pass #self.m_threads = {} def get_code_context(self, frame): try: return self.m_code_contexts[frame.f_code] except KeyError: if self.m_builtins_hack != None: if calc_frame_path(frame) == self.m_builtins_hack: self.m_builtins_hack = None frame.f_globals['__builtins__'] = sys.modules['__builtin__'] code_context = CCodeContext(frame, self.m_bp_manager) return self.m_code_contexts.setdefault(frame.f_code, code_context) def get_current_ctx(self): if len(self.m_threads) == 0: raise NoThreads return self.m_current_ctx def wait_for_first_thread(self): """ Wait until at least one debuggee thread is alive. Python can have 0 threads in some circumstances as embedded Python and the Python interpreter console. """ if self.m_current_ctx is not None: return try: self.m_threads_lock.acquire() while self.m_current_ctx is None: self.m_threads_lock.wait(1.0) finally: self.m_threads_lock.release() def notify_first_thread(self): """ Notify that first thread is available for tracing. """ try: self.m_threads_lock.acquire() self.m_threads_lock.notify() finally: self.m_threads_lock.release() def set_exception_trap_frame(self, frame): """ Set trap for unhandled exceptions in relevant frame. """ while frame is not None: code_context = self.get_code_context(frame) if code_context.is_exception_trap_frame(): code_context.m_fExceptionTrap = True return frame = frame.f_back def trace_dispatch_init(self, frame, event, arg): """ Initial tracing method. """ if event not in ['call', 'line', 'return']: return None code_context = self.get_code_context(frame) if event == 'call' and code_context.is_untraced(): return None self.set_exception_trap_frame(frame) try: t = threading.currentThread() name = t.getName() except: name = '' ctx = CDebuggerCoreThread(name, self, frame, event) ctx.set_tracers() self.m_threads[ctx.m_thread_id] = ctx if len(self.m_threads) == 1: g_blender_text.clear() self.m_current_ctx = ctx self.notify_first_thread() if self.m_f_break_on_init: self.m_f_break_on_init = False self.request_break() sys.settrace(ctx.trace_dispatch_call) sys.setprofile(ctx.profile) if event == 'call': return ctx.trace_dispatch_call(frame, event, arg) elif hasattr(frame, 'f_trace') and (frame.f_trace is not None): return frame.f_trace(frame, event, arg) else: return None def set_all_tracers(self): """ Set trace methods for all frames of all threads. """ for ctx in self.m_threads.values(): ctx.set_tracers() def remove_thread(self, thread_id): try: del self.m_threads[thread_id] if self.m_current_ctx.m_thread_id == thread_id: self.m_current_ctx = self.m_threads.values()[0] except (KeyError, IndexError): pass def set_break_flag(self): self.m_fBreak = (self.m_state_manager.get_state() == STATE_BROKEN) def is_break(self, ctx, frame, event = None): if self.m_fBreak: return True if ctx.m_fUnhandledException: return True if self.m_step_tid == ctx.m_thread_id: return True if self.m_next_frame == frame: return True if (self.m_return_frame == frame) and (event == 'return'): return True return False def _break(self, ctx, frame, event, arg): """ Main break logic. """ if not self.is_break(ctx, frame, event) and not ctx.is_breakpoint(): ctx.set_tracers() return ctx.m_fBroken = True f_full_notification = False f_uhe_notification = False try: self.m_state_manager.acquire() if self.m_state_manager.get_state() != STATE_BROKEN: self.set_break_dont_lock() if not frame.f_exc_traceback is None: ctx.set_exc_info((frame.f_exc_type, frame.f_exc_value, frame.f_exc_traceback)) try: t = threading.currentThread() ctx.m_thread_name = t.getName() except: pass if ctx.m_fUnhandledException and not self.m_fUnhandledException and not 'SCRIPT_TERMINATED' in frame.f_locals: self.m_fUnhandledException = True f_uhe_notification = True if self.m_f_first_to_break or (self.m_current_ctx == ctx): self.m_current_ctx = ctx self.m_lastest_event = event self.m_step_tid = None self.m_next_frame = None self.m_return_frame = None self.m_bp_manager.del_temp_breakpoint(breakpoint = ctx.get_breakpoint()) self.m_f_first_to_break = False f_full_notification = True finally: self.m_state_manager.release() if f_full_notification: self.send_events(None) else: self.notify_thread_broken(ctx.m_thread_id, ctx.m_thread_name) self.notify_namespace() if f_uhe_notification: self.send_unhandled_exception_event() state = self.m_state_manager.wait_for_state([STATE_RUNNING]) ctx.m_fUnhandledException = False ctx.m_fBroken = False ctx.set_tracers() def notify_thread_broken(self, tid, name): """ Notify that thread (tid) has broken. This notification is sent for each thread that breaks after the first one. """ _event = CEventThreadBroken(tid, name) self.m_event_dispatcher.fire_event(_event) def notify_namespace(self): """ Notify that a namespace update query should be done. """ _event = CEventNamespace() self.m_event_dispatcher.fire_event(_event) def get_state(self): return self.m_state_manager.get_state() def verify_broken(self): if self.m_state_manager.get_state() != STATE_BROKEN: raise DebuggerNotBroken def get_current_filename(self, frame_index, fException): """ Return path of sources corresponding to the frame at depth 'frame_index' down the stack of the current thread. """ ctx = self.get_current_ctx() try: f = None base_frame = ctx.frame_acquire() (f, frame_lineno) = ctx.get_frame(base_frame, frame_index, fException) frame_filename = calc_frame_path(f) return frame_filename finally: f = None base_frame = None ctx.frame_release() def get_threads(self): return self.m_threads def set_break_dont_lock(self): self.m_f_first_to_break = True self.m_state_manager.set_state(STATE_BROKEN, fLock = False) self.set_break_flag() self.set_all_tracers() def request_break(self): """ Ask debugger to break (pause debuggee). """ if len(self.m_threads) == 0: self.wait_for_first_thread() try: self.m_state_manager.acquire() if self.m_state_manager.get_state() == STATE_BROKEN: return self.set_break_dont_lock() finally: self.m_state_manager.release() self.send_events(None) def request_go(self, fLock = True): """ Let debugger run. """ try: if fLock: self.m_state_manager.acquire() self.verify_broken() self.m_fUnhandledException = False self.m_state_manager.set_state(STATE_RUNNING, fLock = False) if self.m_fembedded: time.sleep(0.33) self.set_break_flag() finally: if fLock: self.m_state_manager.release() def request_go_breakpoint(self, filename, scope, lineno, frame_index, fException): """ Let debugger run until temp breakpoint as defined in the arguments. """ try: self.m_state_manager.acquire() self.verify_broken() if filename in [None, '']: _filename = self.get_current_filename(frame_index, fException) elif is_blender_file(filename): _filename = filename else: _filename = FindFile(filename, fModules = True) self.m_bp_manager.set_temp_breakpoint(_filename, scope, lineno) self.set_all_tracers() self.request_go(fLock = False) finally: self.m_state_manager.release() def request_step(self, fLock = True): """ Let debugger run until next statement is reached or a breakpoint is hit in another thread. """ try: if fLock: self.m_state_manager.acquire() self.verify_broken() try: ctx = self.get_current_ctx() except NoThreads: return self.m_step_tid = ctx.m_thread_id self.m_next_frame = None self.m_return_frame = None self.request_go(fLock = False) finally: if fLock: self.m_state_manager.release() def request_next(self): """ Let debugger run until next statement in the same frame is reached or a breakpoint is hit in another thread. """ try: self.m_state_manager.acquire() self.verify_broken() try: ctx = self.get_current_ctx() except NoThreads: return if self.m_lastest_event in ['return', 'exception']: return self.request_step(fLock = False) self.m_next_frame = ctx.m_frame self.m_return_frame = None self.request_go(fLock = False) finally: self.m_state_manager.release() def request_return(self): """ Let debugger run until end of frame frame is reached or a breakpoint is hit in another thread. """ try: self.m_state_manager.acquire() self.verify_broken() try: ctx = self.get_current_ctx() except NoThreads: return if self.m_lastest_event == 'return': return self.request_step(fLock = False) self.m_next_frame = None self.m_return_frame = ctx.m_frame self.request_go(fLock = False) finally: self.m_state_manager.release() def request_jump(self, lineno): """ Jump to line number 'lineno'. """ try: self.m_state_manager.acquire() self.verify_broken() try: ctx = self.get_current_ctx() except NoThreads: return frame = ctx.m_frame code = frame.f_code valid_lines = CalcValidLines(code) sbi = CScopeBreakInfo('', valid_lines) l = sbi.CalcScopeLine(lineno) frame.f_lineno = l finally: frame = None self.m_state_manager.release() self.send_events(None) def set_thread(self, tid): """ Switch focus to specified thread. """ try: self.m_state_manager.acquire() self.verify_broken() try: if (tid >= 0) and (tid < 100): _tid = self.m_threads.keys()[tid] else: _tid = tid ctx = self.m_threads[_tid] except (IndexError, KeyError): raise ThreadNotFound self.m_current_ctx = ctx self.m_lastest_event = ctx.m_event finally: self.m_state_manager.release() self.send_events(None) class CDebuggerEngine(CDebuggerCore): """ Main class for the debugger. Adds functionality on top of CDebuggerCore. """ def __init__(self, fembedded = False): CDebuggerCore.__init__(self, fembedded) event_type_dict = { CEventState: {}, CEventStackDepth: {}, CEventBreakpoint: {}, CEventThreads: {}, CEventNoThreads: {}, CEventThreadBroken: {}, CEventNamespace: {}, CEventUnhandledException: {}, CEventStack: {}, CEventExit: {} } self.m_event_queue = CEventQueue(self.m_event_dispatcher) self.m_event_queue.register_event_types(event_type_dict) event_type_dict = {CEventSync: {}} self.m_event_dispatcher.register_callback(self.send_events, event_type_dict, fSingleUse = False) def shutdown(self): self.m_event_queue.shutdown() CDebuggerCore.shutdown(self) def send_event_exit(self): """ Notify client that the debuggee is shutting down. """ event = CEventExit() self.m_event_dispatcher.fire_event(event) def sync_with_events(self, fException, fSendUnhandled): """ Send debugger state to client. """ if len(self.m_threads) == 0: self.wait_for_first_thread() index = self.m_event_queue.get_event_index() event = CEventSync(fException, fSendUnhandled) self.m_event_dispatcher.fire_event(event) return index def wait_for_event(self, timeout, event_index): """ Wait for new events and return them as list of events. """ self.cancel_request_go_timer() (new_event_index, sel) = self.m_event_queue.wait_for_event(timeout, event_index) return (new_event_index, sel) def set_breakpoint(self, filename, scope, lineno, fEnabled, expr, frame_index, fException): if expr != '': try: compile(expr, '', 'eval') except: raise SyntaxError fLock = False try: if filename in [None, '']: self.m_state_manager.acquire() fLock = True self.verify_broken() _filename = self.get_current_filename(frame_index, fException) elif is_blender_file(filename): _filename = filename else: _filename = FindFile(filename, fModules = True) bp = self.m_bp_manager.set_breakpoint(_filename, scope, lineno, fEnabled, expr) self.set_all_tracers() event = CEventBreakpoint(bp) self.m_event_dispatcher.fire_event(event) finally: if fLock: self.m_state_manager.release() def disable_breakpoint(self, id_list, fAll): self.m_bp_manager.disable_breakpoint(id_list, fAll) self.set_all_tracers() event = CEventBreakpoint(None, CEventBreakpoint.DISABLE, id_list, fAll) self.m_event_dispatcher.fire_event(event) def enable_breakpoint(self, id_list, fAll): self.m_bp_manager.enable_breakpoint(id_list, fAll) self.set_all_tracers() event = CEventBreakpoint(None, CEventBreakpoint.ENABLE, id_list, fAll) self.m_event_dispatcher.fire_event(event) def delete_breakpoint(self, id_list, fAll): self.m_bp_manager.delete_breakpoint(id_list, fAll) self.set_all_tracers() event = CEventBreakpoint(None, CEventBreakpoint.REMOVE, id_list, fAll) self.m_event_dispatcher.fire_event(event) def get_breakpoints(self): """ return id->breakpoint dictionary. """ bpl = self.m_bp_manager.get_breakpoints() _items = [(id, copy.copy(bp)) for (id, bp) in bpl.items()] for (id, bp) in _items: bp.m_code = None _bpl = dict(_items) return _bpl def send_events(self, event): """ Send series of events that define the debugger state. """ if isinstance(event, CEventSync): fException = event.m_fException fSendUnhandled = event.m_fSendUnhandled else: fException = False fSendUnhandled = False try: if isinstance(event, CEventSync) and not fException: self.m_state_manager.set_state() self.send_stack_depth() self.send_threads_event(fException) self.send_stack_event(fException) self.send_namespace_event() if fSendUnhandled and self.m_fUnhandledException: self.send_unhandled_exception_event() except NoThreads: self.send_no_threads_event() except: print_debug() raise def send_unhandled_exception_event(self): event = CEventUnhandledException() self.m_event_dispatcher.fire_event(event) def send_stack_depth(self): """ Send event with stack depth and exception stack depth. """ ctx = self.get_current_ctx() try: try: f = None f = ctx.frame_acquire() except ThreadDone: return try: g_traceback_lock.acquire() s = traceback.extract_stack(f) finally: g_traceback_lock.release() stack_depth = len(s) if f.f_exc_traceback is None: stack_depth_exception = None else: try: g_traceback_lock.acquire() _s = traceback.extract_tb(f.f_exc_traceback) finally: g_traceback_lock.release() stack_depth_exception = stack_depth + len(_s) - 1 event = CEventStackDepth(stack_depth, stack_depth_exception) self.m_event_dispatcher.fire_event(event) finally: f = None ctx.frame_release() def send_threads_event(self, fException): """ Send event with current thread list. In case of exception, send only the current thread. """ tl = self.get_thread_list() if fException: ctid = tl[0] itl = tl[1] _itl = [a for a in itl if a[DICT_KEY_TID] == ctid] _tl = (ctid, _itl) else: _tl = tl event = CEventThreads(*_tl) self.m_event_dispatcher.fire_event(event) def send_stack_event(self, fException): sl = self.get_stack([], False, fException) if len(sl) == 0: return event = CEventStack(sl[0]) self.m_event_dispatcher.fire_event(event) def send_namespace_event(self): """ Send event notifying namespace should be queried again. """ event = CEventNamespace() self.m_event_dispatcher.fire_event(event) def send_no_threads_event(self): _event = CEventNoThreads() self.m_event_dispatcher.fire_event(_event) def __get_stack(self, ctx, ctid, fException): tid = ctx.m_thread_id try: try: f = None f = ctx.frame_acquire() except ThreadDone: return None _f = f try: g_traceback_lock.acquire() s = traceback.extract_stack(f) finally: g_traceback_lock.release() if fException: if f.f_exc_traceback is None: raise NoExceptionFound _tb = f.f_exc_traceback while _tb.tb_next is not None: _tb = _tb.tb_next _f = _tb.tb_frame try: g_traceback_lock.acquire() _s = traceback.extract_tb(f.f_exc_traceback) finally: g_traceback_lock.release() s = s[:-1] + _s code_list = [] while _f is not None: rc = repr(_f.f_code).split(',')[0].split()[-1] code_list.insert(0, rc) _f = _f.f_back finally: f = None _f = None ctx.frame_release() #print code_list path_dict = {} for e in s: path = e[0] if path in path_dict: continue try: expanded_path = FindFile(path, fModules = True) except IOError: expanded_path = path path_dict[path] = winlower(expanded_path) #print >> sys.__stderr__, path, path_dict[path] __s = [(path_dict[a], b, c, d) for (a, b, c, d) in s] if (ctx.m_uef_lineno is not None) and (len(__s) > 0): (a, b, c, d) = __s[0] __s = [(a, ctx.m_uef_lineno, c, d)] + __s[1:] r = {} r[DICT_KEY_STACK] = __s r[DICT_KEY_CODE_LIST] = code_list r[DICT_KEY_TID] = tid r[DICT_KEY_BROKEN] = ctx.m_fBroken r[DICT_KEY_EVENT] = ctx.m_event if tid == ctid: r[DICT_KEY_CURRENT_TID] = True return r def get_stack(self, tid_list, fAll, fException): if fException and (fAll or (len(tid_list) != 0)): raise BadArgument ctx = self.get_current_ctx() ctid = ctx.m_thread_id if fAll: ctx_list = self.get_threads().values() elif fException or (len(tid_list) == 0): ctx_list = [ctx] else: ctx_list = [self.get_threads().get(t, None) for t in tid_list] _sl = [self.__get_stack(ctx, ctid, fException) for ctx in ctx_list if ctx is not None] sl = [s for s in _sl if s is not None] return sl def get_source_file(self, filename, lineno, nlines, frame_index, fException): if lineno < 1: lineno = 1 nlines = -1 #if (filename != '') and not IsPythonSourceFile(filename): # raise IOError _lineno = lineno r = {} frame_filename = None try: ctx = self.get_current_ctx() try: f = None base_frame = None base_frame = ctx.frame_acquire() (f, frame_lineno) = ctx.get_frame(base_frame, frame_index, fException) frame_filename = calc_frame_path(f) finally: f = None base_frame = None ctx.frame_release() frame_event = [[ctx.m_event, 'call'][frame_index > 0], 'exception'][fException] except NoThreads: if filename in [None, '']: raise fBlender = False if filename in [None, '']: __filename = frame_filename r[DICT_KEY_TID] = ctx.m_thread_id elif is_blender_file(filename): fBlender = True __filename = filename else: __filename = FindFile(filename, fModules = True) _filename = winlower(__filename) lines = [] breakpoints = {} while nlines != 0: try: g_traceback_lock.acquire() line = get_source_line(_filename, _lineno, fBlender) finally: g_traceback_lock.release() if line == '': break lines.append(line) try: bp = self.m_bp_manager.get_breakpoint(_filename, _lineno) breakpoints[_lineno] = [STATE_DISABLED, STATE_ENABLED][bp.isEnabled()] except KeyError: pass _lineno += 1 nlines -= 1 if frame_filename == _filename: r[DICT_KEY_FRAME_LINENO] = frame_lineno r[DICT_KEY_EVENT] = frame_event r[DICT_KEY_BROKEN] = ctx.m_fBroken r[DICT_KEY_LINES] = lines r[DICT_KEY_FILENAME] = _filename r[DICT_KEY_BREAKPOINTS] = breakpoints r[DICT_KEY_FIRST_LINENO] = lineno return r def __get_source(self, ctx, nlines, frame_index, fException): tid = ctx.m_thread_id _frame_index = [0, frame_index][tid == self.m_current_ctx.m_thread_id] try: try: f = None base_frame = None base_frame = ctx.frame_acquire() (f, frame_lineno) = ctx.get_frame(base_frame, _frame_index, fException) frame_filename = calc_frame_path(f) except (ThreadDone, InvalidFrame): return None finally: f = None base_frame = None ctx.frame_release() frame_event = [[ctx.m_event, 'call'][frame_index > 0], 'exception'][fException] first_line = max(1, frame_lineno - nlines / 2) _lineno = first_line fBlender = is_blender_file(frame_filename) lines = [] breakpoints = {} while nlines != 0: try: g_traceback_lock.acquire() line = get_source_line(frame_filename, _lineno, fBlender) finally: g_traceback_lock.release() if line == '': break lines.append(line) try: bp = self.m_bp_manager.get_breakpoint(frame_filename, _lineno) breakpoints[_lineno] = [STATE_DISABLED, STATE_ENABLED][bp.isEnabled()] except KeyError: pass _lineno += 1 nlines -= 1 r = {} r[DICT_KEY_FRAME_LINENO] = frame_lineno r[DICT_KEY_EVENT] = frame_event r[DICT_KEY_BROKEN] = ctx.m_fBroken r[DICT_KEY_TID] = tid r[DICT_KEY_LINES] = lines r[DICT_KEY_FILENAME] = frame_filename r[DICT_KEY_BREAKPOINTS] = breakpoints r[DICT_KEY_FIRST_LINENO] = first_line return r def get_source_lines(self, nlines, fAll, frame_index, fException): if fException and fAll: raise BadArgument if fAll: ctx_list = self.get_threads().values() else: ctx = self.get_current_ctx() ctx_list = [ctx] _sl = [self.__get_source(ctx, nlines, frame_index, fException) for ctx in ctx_list] sl = [s for s in _sl if s is not None] return sl def __get_locals_globals(self, frame_index, fException, fReadOnly = False): ctx = self.get_current_ctx() (_globals, _locals, _original_locals_copy) = ctx.get_locals_copy(frame_index, fException, fReadOnly) return (_globals, _locals, _original_locals_copy) def __is_verbose_attr(self, r, a): try: v = getattr(r, a) except AttributeError: return True if a == '__class__': if self.__parse_type(type(v)) != 'type': return False #return self.__parse_type(v) in BASIC_TYPES_LIST if (a == '__bases__'): if (type(v) == tuple) and (len(v) > 0): return False if a in ['__name__', '__file__', '__doc__']: return False if a.startswith('__') and a.endswith('__'): return True if self.__is_property_attr(r, a): return True t = self.__parse_type(type(v)) if ('method' in t) and not ('builtin' in t): return True return False def __is_property_attr(self, r, a): if a.startswith('__') and a.endswith('__'): return False try: v = getattr(r, a) except AttributeError: return False t = self.__parse_type(type(v)) if 'descriptor' in t: return True if t == 'property': return True return False def __calc_property_list(self, r): pl = [a for a in r.__dict__.keys() if self.__is_property_attr(r, a)] for b in r.__bases__: if (self.__parse_type(type(b)) == 'type') and (self.__parse_type(b) == 'object'): continue pl += self.__calc_property_list(b) return pl def __calc_attribute_list(self, r): if hasattr(r, '__dict__'): al = r.__dict__.keys() else: al = [a for a in dir(r)] if hasattr(r, '__class__') and not '__class__' in al: al = ['__class__'] + al if hasattr(r, '__bases__') and not '__bases__' in al: al = ['__bases__'] + al if hasattr(r, '__class__'): pl = self.__calc_property_list(r.__class__) _pl = [p for p in pl if not p in al] al += _pl _al = [a for a in al if hasattr(r, a)] __al = [a for a in _al if not self.__is_verbose_attr(r, a)] return __al def __calc_number_of_subnodes(self, r): if self.__parse_type(type(r)) in BASIC_TYPES_LIST: return 0 try: if type(r) == set or isinstance(r, set): return len(r) except NameError: pass if type(r) == sets.Set or isinstance(r, sets.Set): return len(r) if type(r) in [dict, list, tuple]: return len(r) if isinstance(r, dict): return len(r) if isinstance(r, list): return len(r) if isinstance(r, tuple): return len(r) if hasattr(r, '__class__') or hasattr(r, '__bases__'): return 1 return 0 #return len(self.__calc_attribute_list(r)) def __parse_type(self, t): rt = repr(t) st = rt.split("'")[1] return st def __is_filtered_type(self, v): t = self.__parse_type(type(v)) if 'function' in t: return True if 'module' in t: return True if 'classobj' in t: return True return False def __calc_subnodes(self, expr, r, fForceNames, fFilter): snl = [] try: if type(r) == set or isinstance(r, set) or isinstance(r, sets.Set): g = [i for i in r] g.sort() for i in g[0: MAX_NAMESPACE_ITEMS]: e = {} e[DICT_KEY_EXPR] = '[v for v in (%s) if repr(v) == "%s"][0]' % (expr, repr(i)) e[DICT_KEY_NAME] = repr(i) e[DICT_KEY_REPR] = safe_repr_limited(i) e[DICT_KEY_TYPE] = self.__parse_type(type(i)) e[DICT_KEY_N_SUBNODES] = self.__calc_number_of_subnodes(i) snl.append(e) if len(g) > MAX_NAMESPACE_ITEMS: snl.append(MAX_NAMESPACE_WARNING) return snl except NameError: pass if (type(r) in [list, tuple]) or isinstance(r, list) or isinstance(r, tuple): for i, v in enumerate(r[0: MAX_NAMESPACE_ITEMS]): e = {} e[DICT_KEY_EXPR] = '%s[%d]' % (expr, i) e[DICT_KEY_NAME] = repr(i) e[DICT_KEY_REPR] = safe_repr_limited(v) e[DICT_KEY_TYPE] = self.__parse_type(type(v)) e[DICT_KEY_N_SUBNODES] = self.__calc_number_of_subnodes(v) snl.append(e) if len(r) > MAX_NAMESPACE_ITEMS: snl.append(MAX_NAMESPACE_WARNING) return snl if (type(r) == dict) or isinstance(r, dict): kl = r.keys() kl.sort() for k in kl: v = r[k] if fFilter and (expr in ['globals()', 'locals()']) and self.__is_filtered_type(v): continue if len(snl) >= MAX_NAMESPACE_ITEMS: snl.append(MAX_NAMESPACE_WARNING) break e = {} rk = repr(k) if rk.startswith('<'): e[DICT_KEY_EXPR] = '[v for k, v in (%s).items() if repr(k) == "%s"][0]' % (expr, rk) else: e[DICT_KEY_EXPR] = '%s[%s]' % (expr, rk) e[DICT_KEY_NAME] = [repr(k), k][fForceNames] e[DICT_KEY_REPR] = safe_repr_limited(v) e[DICT_KEY_TYPE] = self.__parse_type(type(v)) e[DICT_KEY_N_SUBNODES] = self.__calc_number_of_subnodes(v) snl.append(e) return snl al = self.__calc_attribute_list(r) al.sort() for a in al: try: v = getattr(r, a) except AttributeError: continue if fFilter and self.__is_filtered_type(v): continue if len(snl) >= MAX_NAMESPACE_ITEMS: snl.append(MAX_NAMESPACE_WARNING) break e = {} e[DICT_KEY_EXPR] = '%s.%s' % (expr, a) e[DICT_KEY_NAME] = a e[DICT_KEY_REPR] = safe_repr_limited(v) e[DICT_KEY_TYPE] = self.__parse_type(type(v)) e[DICT_KEY_N_SUBNODES] = self.__calc_number_of_subnodes(v) snl.append(e) return snl def get_exception(self, frame_index, fException): ctx = self.get_current_ctx() try: f = None base_frame = None base_frame = ctx.frame_acquire() (f, frame_lineno) = ctx.get_frame(base_frame, frame_index, fException) e = {'type': f.f_exc_type, 'value': f.f_exc_value, 'traceback': f.f_exc_traceback} return e finally: f = None base_frame = None ctx.frame_release() def is_child_of_failure(self, failed_expr_list, expr): for failed_expr in failed_expr_list: if expr.startswith(failed_expr): return True return False def calc_expr(self, expr, fExpand, fFilter, frame_index, fException, _globals, _locals, lock, rl, index): sys.settrace(None) sys.setprofile(None) e = {} try: __globals = _globals __locals = _locals if RPDB_EXEC_INFO in expr: rpdb_exception_info = self.get_exception(frame_index, fException) __globals = globals() __locals = locals() r = eval(expr, __globals, __locals) e[DICT_KEY_EXPR] = expr e[DICT_KEY_REPR] = safe_repr_limited(r) e[DICT_KEY_TYPE] = self.__parse_type(type(r)) e[DICT_KEY_N_SUBNODES] = self.__calc_number_of_subnodes(r) if fExpand and (e[DICT_KEY_N_SUBNODES] > 0): fForceNames = (expr in ['globals()', 'locals()']) or (RPDB_EXEC_INFO in expr) e[DICT_KEY_SUBNODES] = self.__calc_subnodes(expr, r, fForceNames, fFilter) e[DICT_KEY_N_SUBNODES] = len(e[DICT_KEY_SUBNODES]) except: print_debug() e[DICT_KEY_ERROR] = repr(sys.exc_info()) lock.acquire() if len(rl) == index: rl.append(e) lock.release() def get_namespace(self, nl, fFilter, frame_index, fException): try: (_globals, _locals, x) = self.__get_locals_globals(frame_index, fException, fReadOnly = True) except: print_debug() raise failed_expr_list = [] rl = [] index = 0 lock = threading.Condition() for (expr, fExpand) in nl: if self.is_child_of_failure(failed_expr_list, expr): continue args = (expr, fExpand, fFilter, frame_index, fException, _globals, _locals, lock, rl, index) t = threading.Thread(target = self.calc_expr, args = args) t.start() t.join(2) lock.acquire() if len(rl) == index: rl.append('error') failed_expr_list.append(expr) index += 1 lock.release() if len(failed_expr_list) > 3: break _rl = [r for r in rl if r != 'error'] return _rl def evaluate(self, expr, frame_index, fException): """ Evaluate expression in context of frame at depth 'frame-index'. """ (_globals, _locals, x) = self.__get_locals_globals(frame_index, fException) v = '' w = '' e = '' try: r = eval(expr, _globals, _locals) v = safe_repr(r) if len(v) > MAX_EVALUATE_LENGTH: v = v[0: MAX_EVALUATE_LENGTH] + '... *** %s ***' % STR_MAX_EVALUATE_LENGTH_WARNING w = STR_MAX_EVALUATE_LENGTH_WARNING except: exc_info = sys.exc_info() e = "%s, %s" % (str(exc_info[0]), str(exc_info[1])) self.notify_namespace() return (v, w, e) def execute(self, suite, frame_index, fException): """ Execute suite (Python statement) in context of frame at depth 'frame-index'. """ (_globals, _locals, _original_locals_copy) = self.__get_locals_globals(frame_index, fException) if frame_index > 0 and not _globals is _locals: _locals_copy = copy.copy(_locals) w = '' e = '' try: exec suite in _globals, _locals except: exc_info = sys.exc_info() e = "%s, %s" % (str(exc_info[0]), str(exc_info[1])) if frame_index > 0 and (not _globals is _locals) and _locals != _locals_copy: l = [(k, repr(v)) for k, v in _locals.items()] sl = sets.Set(l) lc = [(k, repr(v)) for k, v in _locals_copy.items()] slc = sets.Set(lc) nsc = [k for (k, v) in sl - slc if k in _original_locals_copy] if len(nsc) != 0: w = STR_LOCAL_NAMESPACE_WARNING self.notify_namespace() return (w, e) def get_thread_list(self): """ Return thread list with tid, state, and last event of each thread. """ ctx = self.get_current_ctx() if ctx is None: current_thread_id = -1 else: current_thread_id = ctx.m_thread_id ctx_list = self.get_threads().values() tl = [{DICT_KEY_TID: c.m_thread_id, DICT_KEY_NAME: c.m_thread_name, DICT_KEY_BROKEN: c.m_fBroken, DICT_KEY_EVENT: c.m_event} for c in ctx_list] return (current_thread_id, tl) def stop_debuggee(self): """ Notify the client and terminate this proccess. """ thread.start_new_thread(_atexit, (True, )) def set_trap_unhandled_exceptions(self, ftrap): self.m_ftrap = ftrap event = CEventTrap(ftrap) self.m_event_dispatcher.fire_event(event) # # ------------------------------------- RPC Server -------------------------------------------- # class CWorkQueue: """ Worker threads pool mechanism for RPC server. """ def __init__(self, size = N_WORK_QUEUE_THREADS, ftrace = True): self.m_lock = threading.Condition() self.m_work_items = [] self.m_f_shutdown = False self.m_ftrace = ftrace self.m_size = size self.m_n_threads = 0 self.m_n_available = 0 self.__create_thread() def __create_thread(self): t = threading.Thread(target = self.__worker_target) #t.setDaemon(True) t.start() def shutdown(self): """ Signal worker threads to exit, and wait until they do. """ self.m_lock.acquire() self.m_f_shutdown = True self.m_lock.notifyAll() while self.m_n_threads > 0: self.m_lock.wait() self.m_lock.release() def __worker_target(self): if not self.m_ftrace: sys.settrace(None) sys.setprofile(None) try: self.m_lock.acquire() self.m_n_threads += 1 self.m_n_available += 1 fcreate_thread = not self.m_f_shutdown and self.m_n_threads < self.m_size self.m_lock.release() if fcreate_thread: self.__create_thread() self.m_lock.acquire() while not self.m_f_shutdown: self.m_lock.wait() if self.m_f_shutdown: break if len(self.m_work_items) == 0: continue fcreate_thread = self.m_n_available == 1 (target, args) = self.m_work_items.pop() self.m_n_available -= 1 self.m_lock.release() if fcreate_thread: self.__create_thread() try: target(*args) except: print_debug() self.m_lock.acquire() self.m_n_available += 1 if self.m_n_available > self.m_size: break self.m_n_threads -= 1 self.m_n_available -= 1 self.m_lock.notifyAll() finally: self.m_lock.release() def post_work_item(self, target, args): if self.m_f_shutdown: return try: self.m_lock.acquire() if self.m_f_shutdown: return self.m_work_items.append((target, args)) self.m_lock.notify() finally: self.m_lock.release() class CUnTracedThreadingMixIn(SocketServer.ThreadingMixIn): """ Modification of SocketServer.ThreadingMixIn that uses a worker thread queue instead of spawning threads to process requests. This mod was needed to resolve deadlocks that were generated in some circumstances. """ def init_work_queue(self): self.m_work_queue = CWorkQueue(ftrace = False) def shutdown_work_queue(self): self.m_work_queue.shutdown() def process_request(self, request, client_address): self.m_work_queue.post_work_item(target = SocketServer.ThreadingMixIn.process_request_thread, args = (self, request, client_address)) def my_xmlrpclib_loads(data): """ Modification of Python 2.3 xmlrpclib.loads() that does not do an import. Needed to prevent deadlocks. """ p, u = xmlrpclib.getparser() p.feed(data) p.close() return u.close(), u.getmethodname() class CXMLRPCServer(CUnTracedThreadingMixIn, SimpleXMLRPCServer.SimpleXMLRPCServer): allow_reuse_address = False """ Modification of Python 2.3 SimpleXMLRPCServer.SimpleXMLRPCDispatcher that uses my_xmlrpclib_loads(). Needed to prevent deadlocks. """ def __marshaled_dispatch(self, data, dispatch_method = None): params, method = my_xmlrpclib_loads(data) # generate response try: if dispatch_method is not None: response = dispatch_method(method, params) else: response = self._dispatch(method, params) # wrap response in a singleton tuple response = (response,) response = xmlrpclib.dumps(response, methodresponse=1) except xmlrpclib.Fault, fault: response = xmlrpclib.dumps(fault) except: # report exception back to server response = xmlrpclib.dumps( xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value)) ) print_debug() return response if sys.version_info[:2] <= (2, 3): _marshaled_dispatch = __marshaled_dispatch class CPwdServerProxy: """ Encrypted proxy to the debuggee. Works by wrapping a xmlrpclib.ServerProxy object. """ def __init__(self, crypto, uri, transport = None, target_rid = 0): self.m_crypto = crypto self.m_proxy = xmlrpclib.ServerProxy(uri, transport) self.m_fEncryption = is_encryption_supported() self.m_target_rid = target_rid self.m_method = getattr(self.m_proxy, DISPACHER_METHOD) def __set_encryption(self, fEncryption): self.m_fEncryption = fEncryption def get_encryption(self): return self.m_fEncryption def __request(self, name, params): """ Call debuggee method 'name' with parameters 'params'. """ while True: try: # # Encrypt method and params. # _params = self.m_crypto.do_crypto((name, params, self.m_target_rid), self.get_encryption()) rpdb_version = get_interface_compatibility_version() r = self.m_method(rpdb_version + _params) # # Decrypt response. # ((max_index, _r, _e), fe)= self.m_crypto.undo_crypto(r, fVerifyIndex = False) if _e is not None: raise _e except AuthenticationBadIndex, e: self.m_crypto.set_index(e.m_max_index, e.m_anchor) continue except xmlrpclib.Fault, fault: if class_name(BadVersion) in fault.faultString: s = fault.faultString.split("'") version = ['', s[1]][len(s) > 0] raise BadVersion(version) if class_name(EncryptionExpected) in fault.faultString: raise EncryptionExpected elif class_name(EncryptionNotSupported) in fault.faultString: if self.m_crypto.m_fAllowUnencrypted: self.__set_encryption(False) continue raise EncryptionNotSupported elif class_name(DecryptionFailure) in fault.faultString: raise DecryptionFailure elif class_name(AuthenticationBadData) in fault.faultString: raise AuthenticationBadData elif class_name(AuthenticationFailure) in fault.faultString: raise AuthenticationFailure else: print_debug() assert False except xmlrpclib.ProtocolError: print_debug() raise CConnectionException return _r def __getattr__(self, name): return xmlrpclib._Method(self.__request, name) class CIOServer(threading.Thread): """ Base class for debuggee server. """ def __init__(self, pwd, fAllowUnencrypted, fAllowRemote, rid): threading.Thread.__init__(self) self.m_crypto = CCrypto(pwd, fAllowUnencrypted, rid) self.m_fAllowRemote = fAllowRemote self.m_rid = rid self.m_port = None self.m_stop = False self.m_server = None self.setDaemon(True) def shutdown(self): self.stop() def stop(self): if self.m_stop: return self.m_stop = True while self.isAlive(): try: proxy = CPwdServerProxy(self.m_crypto, calcURL(LOOPBACK, self.m_port), CLocalTimeoutTransport()) proxy.null() except (socket.error, CException): pass self.join(0.5) self.m_server.shutdown_work_queue() def export_null(self): return 0 def run(self): # # Turn tracing off. We don't want debugger threads traced. # sys.settrace(None) sys.setprofile(None) (self.m_port, self.m_server) = self.__StartXMLRPCServer() self.m_server.init_work_queue() self.m_server.register_function(self.dispatcher_method) while not self.m_stop: self.m_server.handle_request() def dispatcher_method(self, params): """ Process RPC call. """ rpdb_version = get_interface_compatibility_version() if params[: len(rpdb_version)] != rpdb_version: raise BadVersion(get_version()) _params = params[len(rpdb_version):] try: # # Decrypt parameters. # ((name, _params, target_rid), fEncryption) = self.m_crypto.undo_crypto(_params) except AuthenticationBadIndex, e: #print_debug() # # Notify the caller on the expected index. # fEncryption = self.m_crypto.is_encrypted(_params) max_index = self.m_crypto.get_max_index() _r = self.m_crypto.do_crypto((max_index, None, e), fEncryption) return _r r = None e = None try: # # We are forcing the 'export_' prefix on methods that are # callable through XML-RPC to prevent potential security # problems # func = getattr(self, 'export_' + name) except AttributeError: raise Exception('method "%s" is not supported' % ('export_' + name, )) try: if (target_rid != 0) and (target_rid != self.m_rid): raise NotAttached r = func(*_params) except Exception, _e: print_debug() e = _e # # Send the encrypted result. # max_index = self.m_crypto.get_max_index() _r = self.m_crypto.do_crypto((max_index, r, e), fEncryption) return _r def __StartXMLRPCServer(self): """ As the name says, start the XML RPC server. Looks for an available tcp port to listen on. """ host = [LOOPBACK, ""][self.m_fAllowRemote] port = SERVER_PORT_RANGE_START while True: try: server = CXMLRPCServer((host, port), logRequests = 0) return (port, server) except socket.error, e: if not GetSocketError(e) in [ERROR_SOCKET_ADDRESS_IN_USE_WIN, ERROR_SOCKET_ADDRESS_IN_USE_UNIX, ERROR_SOCKET_ADDRESS_IN_USE_MAC]: raise if port >= SERVER_PORT_RANGE_START + SERVER_PORT_RANGE_LENGTH - 1: raise port += 1 continue class CServerInfo: def __init__(self, age, port, pid, filename, rid, state, fembedded): self.m_age = age self.m_port = port self.m_pid = pid self.m_filename = filename self.m_module_name = CalcModuleName(filename) self.m_rid = rid self.m_state = state self.m_fembedded = fembedded def __str__(self): return 'age: %d, port: %d, pid: %d, filename: %s, rid: %s' % (self.m_age, self.m_port, self.m_pid, self.m_filename, self.m_rid) class CDebuggeeServer(CIOServer): """ The debuggee XML RPC server class. """ def __init__(self, filename, debugger, pwd, fAllowUnencrypted, fAllowRemote, rid = None): if rid is None: rid = generate_rid() CIOServer.__init__(self, pwd, fAllowUnencrypted, fAllowRemote, rid) self.m_filename = filename self.m_pid = _getpid() self.m_time = time.time() self.m_debugger = debugger self.m_rid = rid def shutdown(self): CIOServer.shutdown(self) def export_server_info(self): age = time.time() - self.m_time state = self.m_debugger.get_state() fembedded = self.m_debugger.is_embedded() si = CServerInfo(age, self.m_port, self.m_pid, self.m_filename, self.m_rid, state, fembedded) return si def export_sync_with_events(self, fException, fSendUnhandled): ei = self.m_debugger.sync_with_events(fException, fSendUnhandled) return ei def export_wait_for_event(self, timeout, event_index): (new_event_index, s) = self.m_debugger.wait_for_event(timeout, event_index) return (new_event_index, s) def export_set_breakpoint(self, filename, scope, lineno, fEnabled, expr, frame_index, fException): self.m_debugger.set_breakpoint(filename, scope, lineno, fEnabled, expr, frame_index, fException) return 0 def export_disable_breakpoint(self, id_list, fAll): self.m_debugger.disable_breakpoint(id_list, fAll) return 0 def export_enable_breakpoint(self, id_list, fAll): self.m_debugger.enable_breakpoint(id_list, fAll) return 0 def export_delete_breakpoint(self, id_list, fAll): self.m_debugger.delete_breakpoint(id_list, fAll) return 0 def export_get_breakpoints(self): bpl = self.m_debugger.get_breakpoints() return bpl def export_request_break(self): self.m_debugger.request_break() return 0 def export_request_go(self): self.m_debugger.request_go() return 0 def export_request_go_breakpoint(self, filename, scope, lineno, frame_index, fException): self.m_debugger.request_go_breakpoint(filename, scope, lineno, frame_index, fException) return 0 def export_request_step(self): self.m_debugger.request_step() return 0 def export_request_next(self): self.m_debugger.request_next() return 0 def export_request_return(self): self.m_debugger.request_return() return 0 def export_request_jump(self, lineno): self.m_debugger.request_jump(lineno) return 0 def export_get_stack(self, tid_list, fAll, fException): r = self.m_debugger.get_stack(tid_list, fAll, fException) return r def export_get_source_file(self, filename, lineno, nlines, frame_index, fException): r = self.m_debugger.get_source_file(filename, lineno, nlines, frame_index, fException) return r def export_get_source_lines(self, nlines, fAll, frame_index, fException): r = self.m_debugger.get_source_lines(nlines, fAll, frame_index, fException) return r def export_get_thread_list(self): r = self.m_debugger.get_thread_list() return r def export_set_thread(self, tid): self.m_debugger.set_thread(tid) return 0 def export_get_namespace(self, nl, fFilter, frame_index, fException): r = self.m_debugger.get_namespace(nl, fFilter, frame_index, fException) return r def export_evaluate(self, expr, frame_index, fException): (v, w, e) = self.m_debugger.evaluate(expr, frame_index, fException) return (v, w, e) def export_execute(self, suite, frame_index, fException): (w, e) = self.m_debugger.execute(suite, frame_index, fException) return (w, e) def export_stop_debuggee(self): self.m_debugger.stop_debuggee() return 0 def export_set_trap_unhandled_exceptions(self, ftrap): self.m_debugger.set_trap_unhandled_exceptions(ftrap) return 0 # # ------------------------------------- RPC Client -------------------------------------------- # class CTimeoutHTTPConnection(httplib.HTTPConnection): """ Modification of httplib.HTTPConnection with timeout for sockets. """ _rpdb2_timeout = PING_TIMEOUT def connect(self): """Connect to the host and port specified in __init__.""" msg = "getaddrinfo returns an empty list" for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res try: self.sock = socket.socket(af, socktype, proto) self.sock.settimeout(self._rpdb2_timeout) if self.debuglevel > 0: print >> sys.__stderr__, "connect: (%s, %s)" % (self.host, self.port) self.sock.connect(sa) except socket.error, msg: if self.debuglevel > 0: print >> sys.__stderr__, 'connect fail:', (self.host, self.port) if self.sock: self.sock.close() self.sock = None continue break if not self.sock: raise socket.error, msg class CLocalTimeoutHTTPConnection(CTimeoutHTTPConnection): """ Modification of httplib.HTTPConnection with timeout for sockets. """ _rpdb2_timeout = LOCAL_TIMEOUT class CTimeoutHTTP(httplib.HTTP): """ Modification of httplib.HTTP with timeout for sockets. """ _connection_class = CTimeoutHTTPConnection class CLocalTimeoutHTTP(httplib.HTTP): """ Modification of httplib.HTTP with timeout for sockets. """ _connection_class = CLocalTimeoutHTTPConnection class CTimeoutTransport(xmlrpclib.Transport): """ Modification of xmlrpclib.Transport with timeout for sockets. """ def make_connection(self, host): # create a HTTP connection object from a host descriptor host, extra_headers, x509 = self.get_host_info(host) return CTimeoutHTTP(host) class CLocalTimeoutTransport(xmlrpclib.Transport): """ Modification of xmlrpclib.Transport with timeout for sockets. """ def make_connection(self, host): # create a HTTP connection object from a host descriptor host, extra_headers, x509 = self.get_host_info(host) return CLocalTimeoutHTTP(host) class CSession: """ Basic class that communicates with the debuggee server. """ def __init__(self, host, port, pwd, fAllowUnencrypted, rid): self.m_crypto = CCrypto(pwd, fAllowUnencrypted, rid) self.m_host = host self.m_port = port self.m_proxy = None self.m_server_info = None self.m_exc_info = None self.m_fShutDown = False def get_encryption(self): return self.m_proxy.get_encryption() def getServerInfo(self): return self.m_server_info def shut_down(self): self.m_fShutDown = True def getProxy(self): """ Return the proxy object. With this object you can invoke methods on the server. """ if self.m_fShutDown: raise NotAttached return self.m_proxy def ConnectAsync(self): t = threading.Thread(target = self.ConnectNoThrow) #t.setDaemon(True) t.start() return t def ConnectNoThrow(self): try: self.Connect() except: self.m_exc_info = sys.exc_info() def Connect(self): host = self.m_host if host.lower() == LOCALHOST: host = LOOPBACK server = CPwdServerProxy(self.m_crypto, calcURL(host, self.m_port), CTimeoutTransport()) server_info = server.server_info() self.m_proxy = CPwdServerProxy(self.m_crypto, calcURL(host, self.m_port), target_rid = server_info.m_rid) self.m_server_info = server_info def isConnected(self): return self.m_proxy is not None class CServerList: def __init__(self, host): self.m_host = host self.m_list = [] self.m_errors = {} def calcList(self, pwd, rid): sil = [] sessions = [] self.m_errors = {} port = SERVER_PORT_RANGE_START while port < SERVER_PORT_RANGE_START + SERVER_PORT_RANGE_LENGTH: s = CSession(self.m_host, port, pwd, fAllowUnencrypted = True, rid = rid) t = s.ConnectAsync() sessions.append((s, t)) port += 1 for (s, t) in sessions: t.join() if (s.m_exc_info is not None): if not issubclass(s.m_exc_info[0], socket.error): self.m_errors.setdefault(s.m_exc_info[0], []).append(s.m_exc_info) continue si = s.getServerInfo() if si is not None: sil.append((-si.m_age, si)) sil.sort() self.m_list = [s[1] for s in sil] return self.m_list def get_errors(self): return self.m_errors def findServers(self, key): fname = False try: n = int(key) except ValueError: fname = True if fname: _s = [s for s in self.m_list if key in s.m_filename] else: _s = [s for s in self.m_list if (s.m_pid == n) or (s.m_rid == key)] if _s == []: raise UnknownServer return _s class CSessionManagerInternal: def __init__(self, pwd, fAllowUnencrypted, fAllowRemote, host): self.m_pwd = [pwd, None][pwd in [None, '']] self.m_fAllowUnencrypted = fAllowUnencrypted self.m_fAllowRemote = fAllowRemote self.m_rid = generate_rid() self.m_host = host self.m_server_list_object = CServerList(host) self.m_session = None self.m_server_info = None self.m_worker_thread = None self.m_worker_thread_ident = None self.m_fStop = False self.m_stack_depth = None self.m_stack_depth_exception = None self.m_frame_index = 0 self.m_frame_index_exception = 0 self.m_remote_event_index = 0 self.m_event_dispatcher_proxy = CEventDispatcher() self.m_event_dispatcher = CEventDispatcher(self.m_event_dispatcher_proxy) self.m_state_manager = CStateManager(STATE_DETACHED, self.m_event_dispatcher, self.m_event_dispatcher_proxy) self.m_breakpoints_proxy = CBreakPointsManagerProxy(self) event_type_dict = {CEventState: {EVENT_EXCLUDE: [STATE_BROKEN, STATE_ANALYZE]}} self.register_callback(self.reset_frame_indexes, event_type_dict, fSingleUse = False) event_type_dict = {CEventStackDepth: {}} self.register_callback(self.set_stack_depth, event_type_dict, fSingleUse = False) event_type_dict = {CEventNoThreads: {}} self.register_callback(self._reset_frame_indexes, event_type_dict, fSingleUse = False) event_type_dict = {CEventExit: {}} self.register_callback(self.on_event_exit, event_type_dict, fSingleUse = False) event_type_dict = {CEventTrap: {}} self.m_event_dispatcher_proxy.register_callback(self.on_event_trap, event_type_dict, fSingleUse = False) self.m_event_dispatcher.register_chain_override(event_type_dict) self.m_printer = self.__nul_printer self.m_last_command_line = None self.m_last_fchdir = None self.m_ftrap = True def __del__(self): self.m_event_dispatcher_proxy.shutdown() self.m_event_dispatcher.shutdown() self.m_state_manager.shutdown() def __nul_printer(self, str): pass def set_printer(self, printer): self.m_printer = printer def register_callback(self, callback, event_type_dict, fSingleUse): return self.m_event_dispatcher.register_callback(callback, event_type_dict, fSingleUse) def remove_callback(self, callback): return self.m_event_dispatcher.remove_callback(callback) def __wait_for_debuggee(self, rid): try: for i in range(0,STARTUP_RETRIES): try: self.m_server_list_object.calcList(self.m_pwd, self.m_rid) return self.m_server_list_object.findServers(rid)[0] except UnknownServer: time.sleep(STARTUP_TIMEOUT) continue self.m_server_list_object.calcList(self.m_pwd, self.m_rid) return self.m_server_list_object.findServers(rid)[0] finally: errors = self.m_server_list_object.get_errors() self.__report_server_errors(errors, fsupress_pwd_warning = True) def get_encryption(self): return self.getSession().get_encryption() def launch(self, fchdir, command_line, fload_breakpoints = True): self.__verify_unattached() if not os.name in [POSIX, 'nt']: self.m_printer(STR_SPAWN_UNSUPPORTED) raise SpawnUnsupported if self.m_pwd is None: self.set_random_password() if command_line == '': raise BadArgument (path, filename, args) = split_command_line_path_filename_args(command_line) #if not IsPythonSourceFile(filename): # raise IOError _filename = os.path.join(path, filename) ExpandedFilename = FindFile(_filename) self.set_host(LOCALHOST) self.m_printer(STR_STARTUP_SPAWN_NOTICE) rid = generate_rid() create_pwd_file(rid, self.m_pwd) self.m_state_manager.set_state(STATE_SPAWNING) try: try: try: self._spawn_server(fchdir, ExpandedFilename, args, rid) except SpawnUnsupported: self.m_printer(STR_SPAWN_UNSUPPORTED) raise try: server = self.__wait_for_debuggee(rid) except UnknownServer: self.m_printer(STR_STARTUP_FAILURE) raise self.attach(server.m_rid, server.m_filename, fsupress_pwd_warning = True) self.m_last_command_line = command_line self.m_last_fchdir = fchdir except: if self.m_state_manager.get_state() != STATE_DETACHED: self.m_state_manager.set_state(STATE_DETACHED) raise try: if fload_breakpoints: self.load_breakpoints() except: pass finally: delete_pwd_file(rid) def restart(self): """ Restart debug session with same command_line and fchdir arguments which were used in last launch. """ if None in (self.m_last_fchdir, self.m_last_command_line): return if self.m_state_manager.get_state() != STATE_DETACHED: self.stop_debuggee() self.launch(self.m_last_fchdir, self.m_last_command_line) def get_launch_args(self): """ Return command_line and fchdir arguments which were used in last launch as (last_fchdir, last_command_line). Returns None if there is no info. """ if None in (self.m_last_fchdir, self.m_last_command_line): return (None, None) return (self.m_last_fchdir, self.m_last_command_line) def _spawn_server(self, fchdir, ExpandedFilename, args, rid): """ Start an OS console to act as server. What it does is to start rpdb again in a new console in server only mode. """ if g_fScreen: name = SCREEN elif sys.platform == DARWIN: name = DARWIN else: try: import terminalcommand name = MAC except: name = os.name if name == 'nt' and g_fDebug: name = NT_DEBUG e = ['', ' --encrypt'][not self.m_fAllowUnencrypted] r = ['', ' --remote'][self.m_fAllowRemote] c = ['', ' --chdir'][fchdir] p = ['', ' --pwd="%s"' % (self.m_pwd, )][os.name == 'nt'] debugger = os.path.abspath(__file__) if debugger[-1:] == 'c': debugger = debugger[:-1] debug_prints = ['', ' --debug'][g_fDebug] options = '"%s"%s --debugee%s%s%s%s --rid=%s "%s" %s' % (debugger, debug_prints, p, e, r, c, rid, ExpandedFilename, args) python_exec = sys.executable if python_exec.endswith('w.exe'): python_exec = python_exec[:-5] + '.exe' if name == POSIX: terminal_command = CalcTerminalCommand() if terminal_command == GNOME_DEFAULT_TERM: command = osSpawn[GNOME_DEFAULT_TERM] % (python_exec, options) else: command = osSpawn[name] % (terminal_command, python_exec, options) else: command = osSpawn[name] % (python_exec, options) if name == DARWIN: s = 'cd "%s" ; %s' % (os.getcwdu(), command) command = CalcMacTerminalCommand(s) if name == MAC: terminalcommand.run(command) else: os.popen(command) def attach(self, key, name = None, fsupress_pwd_warning = False): self.__verify_unattached() if key == '': raise BadArgument if self.m_pwd is None: self.m_printer(STR_PASSWORD_MUST_BE_SET) raise UnsetPassword if name is None: name = key _name = name self.m_printer(STR_STARTUP_NOTICE) self.m_state_manager.set_state(STATE_ATTACHING) try: self.m_server_list_object.calcList(self.m_pwd, self.m_rid) servers = self.m_server_list_object.findServers(key) server = servers[0] _name = server.m_filename errors = self.m_server_list_object.get_errors() if not key in [server.m_rid, str(server.m_pid)]: self.__report_server_errors(errors, fsupress_pwd_warning) self.__attach(server) if len(servers) > 1: self.m_printer(STR_MULTIPLE_DEBUGGEES % (key, )) self.m_printer(STR_ATTACH_CRYPTO_MODE % ([' ' + STR_ATTACH_CRYPTO_MODE_NOT, ''][self.get_encryption()], )) self.m_printer(STR_ATTACH_SUCCEEDED % (server.m_filename, )) return except (socket.error, CConnectionException): self.m_printer(STR_ATTACH_FAILED_NAME % (_name, )) self.m_state_manager.set_state(STATE_DETACHED) raise except: print_debug() assert False def report_exception(self, _type, value, tb): msg = g_error_mapping.get(_type, STR_ERROR_OTHER) _str = msg % {'type': _type, 'value': value, 'traceback': tb} self.m_printer(_str) if not _type in g_error_mapping: print_exception(_type, value, tb, True) def __report_server_errors(self, errors, fsupress_pwd_warning = False): for k, el in errors.items(): if fsupress_pwd_warning and k in [BadVersion, AuthenticationBadData, AuthenticationFailure]: continue if k in [BadVersion]: for (t, v, tb) in el: self.report_exception(t, v, None) continue (t, v, tb) = el[0] self.report_exception(t, v, tb) def __attach(self, server): self.__verify_unattached() session = CSession(self.m_host, server.m_port, self.m_pwd, self.m_fAllowUnencrypted, self.m_rid) session.Connect() if (session.getServerInfo().m_pid != server.m_pid) or (session.getServerInfo().m_filename != server.m_filename): raise UnexpectedData self.m_session = session self.m_server_info = self.get_server_info() self.getSession().getProxy().set_trap_unhandled_exceptions(self.m_ftrap) self.request_break() self.refresh(True) self.__start_event_monitor() self.enable_breakpoint([], fAll = True) def __verify_unattached(self): if self.__is_attached(): raise AlreadyAttached def __verify_attached(self): if not self.__is_attached(): raise NotAttached def __is_attached(self): return (self.m_state_manager.get_state() != STATE_DETACHED) and (self.m_session is not None) def __verify_broken(self): if self.m_state_manager.get_state() not in [STATE_BROKEN, STATE_ANALYZE]: raise DebuggerNotBroken def refresh(self, fSendUnhandled = False): fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) self.m_remote_event_index = self.getSession().getProxy().sync_with_events(fAnalyzeMode, fSendUnhandled) self.m_breakpoints_proxy.sync() def __start_event_monitor(self): self.m_fStop = False self.m_worker_thread = threading.Thread(target = self.__event_monitor_proc) #self.m_worker_thread.setDaemon(True) self.m_worker_thread.start() def __event_monitor_proc(self): self.m_worker_thread_ident = thread.get_ident() t = 0 nfailures = 0 while not self.m_fStop: try: t = ControlRate(t, IDLE_MAX_RATE) if self.m_fStop: return (n, sel) = self.getSession().getProxy().wait_for_event(PING_TIMEOUT, self.m_remote_event_index) if True in [isinstance(e, CEventExit) for e in sel]: self.getSession().shut_down() self.m_fStop = True if n > self.m_remote_event_index: #print >> sys.__stderr__, (n, sel) self.m_remote_event_index = n self.m_event_dispatcher_proxy.fire_events(sel) nfailures = 0 except CConnectionException: self.report_exception(*sys.exc_info()) threading.Thread(target = self.detach_job).start() return except socket.error: if nfailures < COMMUNICATION_RETRIES: nfailures += 1 continue self.report_exception(*sys.exc_info()) threading.Thread(target = self.detach_job).start() return def on_event_exit(self, event): self.m_printer(STR_DEBUGGEE_TERMINATED) threading.Thread(target = self.detach_job).start() def detach_job(self): try: self.detach() except: pass def detach(self): self.__verify_attached() try: self.save_breakpoints() except: print_debug() pass self.m_printer(STR_ATTEMPTING_TO_DETACH) self.m_state_manager.set_state(STATE_DETACHING) self.__stop_event_monitor() try: self.disable_breakpoint([], fAll = True) try: self.getSession().getProxy().set_trap_unhandled_exceptions(False) self.request_go() except DebuggerNotBroken: pass finally: self.m_state_manager.set_state(STATE_DETACHED) self.m_session = None self.m_printer(STR_DETACH_SUCCEEDED) def __stop_event_monitor(self): self.m_fStop = True if self.m_worker_thread is not None: if thread.get_ident() != self.m_worker_thread_ident: self.m_worker_thread.join() self.m_worker_thread = None self.m_worker_thread_ident = None def request_break(self): self.getSession().getProxy().request_break() def request_go(self): self.getSession().getProxy().request_go() def request_go_breakpoint(self, filename, scope, lineno): frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) self.getSession().getProxy().request_go_breakpoint(filename, scope, lineno, frame_index, fAnalyzeMode) def request_step(self): self.getSession().getProxy().request_step() def request_next(self): self.getSession().getProxy().request_next() def request_return(self): self.getSession().getProxy().request_return() def request_jump(self, lineno): self.getSession().getProxy().request_jump(lineno) def set_breakpoint(self, filename, scope, lineno, fEnabled, expr): frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) self.getSession().getProxy().set_breakpoint(filename, scope, lineno, fEnabled, expr, frame_index, fAnalyzeMode) def disable_breakpoint(self, id_list, fAll): self.getSession().getProxy().disable_breakpoint(id_list, fAll) def enable_breakpoint(self, id_list, fAll): self.getSession().getProxy().enable_breakpoint(id_list, fAll) def delete_breakpoint(self, id_list, fAll): self.getSession().getProxy().delete_breakpoint(id_list, fAll) def get_breakpoints(self): self.__verify_attached() bpl = self.m_breakpoints_proxy.get_breakpoints() return bpl def save_breakpoints(self, filename = ''): self.__verify_attached() module_name = self.getSession().getServerInfo().m_module_name if module_name[:1] == '<': return path = calc_bpl_filename(module_name + filename) file = open(path, 'wb') try: try: bpl = self.get_breakpoints() sbpl = cPickle.dumps(bpl) file.write(sbpl) except: print_debug() raise CException finally: file.close() def load_breakpoints(self, filename = ''): self.__verify_attached() module_name = self.getSession().getServerInfo().m_module_name if module_name[:1] == '<': return path = calc_bpl_filename(module_name + filename) file = open(path, 'rb') ferror = False try: try: bpl = cPickle.load(file) self.delete_breakpoint([], True) except: print_debug() raise CException # # No Breakpoints were found in file. # if filename == '' and len(bpl.values()) == 0: raise IOError for bp in bpl.values(): try: self.set_breakpoint(bp.m_filename, bp.m_scope_fqn, bp.m_scope_offset, bp.m_fEnabled, bp.m_expr) except: print_debug() ferror = True if ferror: raise CException finally: file.close() def on_event_trap(self, event): ffire = self.m_ftrap != event.m_ftrap self.m_ftrap = event.m_ftrap if ffire: event = CEventTrap(ftrap) self.m_event_dispatcher.fire_event(event) def set_trap_unhandled_exceptions(self, ftrap): self.m_ftrap = ftrap if self.__is_attached(): try: self.getSession().getProxy().set_trap_unhandled_exceptions(self.m_ftrap) except NotAttached: pass event = CEventTrap(ftrap) self.m_event_dispatcher.fire_event(event) def get_trap_unhandled_exceptions(self): return self.m_ftrap def get_stack(self, tid_list, fAll): fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) r = self.getSession().getProxy().get_stack(tid_list, fAll, fAnalyzeMode) return r def get_source_file(self, filename, lineno, nlines): #if (filename != '') and not IsPythonSourceFile(filename): # raise IOError frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) r = self.getSession().getProxy().get_source_file(filename, lineno, nlines, frame_index, fAnalyzeMode) return r def get_source_lines(self, nlines, fAll): frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) r = self.getSession().getProxy().get_source_lines(nlines, fAll, frame_index, fAnalyzeMode) return r def get_thread_list(self): (current_thread_id, thread_list) = self.getSession().getProxy().get_thread_list() return (current_thread_id, thread_list) def set_thread(self, tid): self.reset_frame_indexes(None) self.getSession().getProxy().set_thread(tid) def get_namespace(self, nl, fFilter): frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) r = self.getSession().getProxy().get_namespace(nl, fFilter, frame_index, fAnalyzeMode) return r def evaluate(self, expr): self.__verify_attached() self.__verify_broken() frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) (value, warning, error) = self.getSession().getProxy().evaluate(expr, frame_index, fAnalyzeMode) return (value, warning, error) def execute(self, suite): self.__verify_attached() self.__verify_broken() frame_index = self.get_frame_index() fAnalyzeMode = (self.m_state_manager.get_state() == STATE_ANALYZE) (warning, error) = self.getSession().getProxy().execute(suite, frame_index, fAnalyzeMode) return (warning, error) def set_host(self, host): self.__verify_unattached() try: socket.getaddrinfo(host, 0) except socket.gaierror: if host.lower() != LOCALHOST: raise # # Work-around for gaierror: (-8, 'Servname not supported for ai_socktype') # return self.set_host(LOOPBACK) self.m_host = host self.m_server_list_object = CServerList(host) def get_host(self): return self.m_host def calc_server_list(self): if self.m_pwd is None: raise UnsetPassword server_list = self.m_server_list_object.calcList(self.m_pwd, self.m_rid) errors = self.m_server_list_object.get_errors() self.__report_server_errors(errors) return (server_list, errors) def get_server_info(self): return self.getSession().getServerInfo() def _reset_frame_indexes(self, event): self.reset_frame_indexes(None) def reset_frame_indexes(self, event): try: self.m_state_manager.acquire() if event is None: self.__verify_broken() elif self.m_state_manager.get_state() in [STATE_BROKEN, STATE_ANALYZE]: return self.m_stack_depth = None self.m_stack_depth_exception = None self.m_frame_index = 0 self.m_frame_index_exception = 0 finally: self.m_state_manager.release() def set_stack_depth(self, event): try: self.m_state_manager.acquire() self.__verify_broken() self.m_stack_depth = event.m_stack_depth self.m_stack_depth_exception = event.m_stack_depth_exception self.m_frame_index = min(self.m_frame_index, self.m_stack_depth - 1) self.m_frame_index_exception = min(self.m_frame_index_exception, self.m_stack_depth_exception - 1) finally: self.m_state_manager.release() def set_frame_index(self, frame_index): try: self.m_state_manager.acquire() self.__verify_broken() if (frame_index < 0) or (self.m_stack_depth is None): return self.get_frame_index(fLock = False) if self.m_state_manager.get_state() == STATE_ANALYZE: self.m_frame_index_exception = min(frame_index, self.m_stack_depth_exception - 1) si = self.m_frame_index_exception else: self.m_frame_index = min(frame_index, self.m_stack_depth - 1) si = self.m_frame_index finally: self.m_state_manager.release() event = CEventStackFrameChange(si) self.m_event_dispatcher.fire_event(event) event = CEventNamespace() self.m_event_dispatcher.fire_event(event) return si def get_frame_index(self, fLock = True): try: if fLock: self.m_state_manager.acquire() self.__verify_attached() if self.m_state_manager.get_state() == STATE_ANALYZE: return self.m_frame_index_exception else: return self.m_frame_index finally: if fLock: self.m_state_manager.release() def set_analyze(self, fAnalyze): try: self.m_state_manager.acquire() if fAnalyze and (self.m_state_manager.get_state() != STATE_BROKEN): raise DebuggerNotBroken if (not fAnalyze) and (self.m_state_manager.get_state() != STATE_ANALYZE): return state = [STATE_BROKEN, STATE_ANALYZE][fAnalyze] self.m_state_manager.set_state(state, fLock = False) finally: self.m_state_manager.release() self.refresh() def getSession(self): self.__verify_attached() return self.m_session def get_state(self): return self.m_state_manager.get_state() def set_password(self, pwd): try: self.m_state_manager.acquire() self.__verify_unattached() self.m_pwd = pwd finally: self.m_state_manager.release() def set_random_password(self): try: self.m_state_manager.acquire() self.__verify_unattached() self.m_pwd = generate_random_password() self.m_printer(STR_RANDOM_PASSWORD) finally: self.m_state_manager.release() def get_password(self): return self.m_pwd def set_remote(self, fAllowRemote): try: self.m_state_manager.acquire() self.__verify_unattached() self.m_fAllowRemote = fAllowRemote finally: self.m_state_manager.release() def get_remote(self): return self.m_fAllowRemote def stop_debuggee(self): self.__verify_attached() try: self.save_breakpoints() except: print_debug() pass self.m_printer(STR_ATTEMPTING_TO_STOP) self.m_printer(STR_ATTEMPTING_TO_DETACH) self.m_state_manager.set_state(STATE_DETACHING) self.__stop_event_monitor() try: self.getSession().getProxy().stop_debuggee() finally: self.m_state_manager.set_state(STATE_DETACHED) self.m_session = None self.m_printer(STR_DETACH_SUCCEEDED) class CConsoleInternal(cmd.Cmd, threading.Thread): def __init__(self, session_manager, stdin = None, stdout = None, fSplit = False): cmd.Cmd.__init__(self, stdin = stdin, stdout = stdout) threading.Thread.__init__(self) self.fAnalyzeMode = False self.fPrintBroken = True self.m_filename = None self.use_rawinput = [1, 0][fSplit] self.m_fSplit = fSplit self.prompt = [[CONSOLE_PROMPT, CONSOLE_PROMPT_ANALYZE][self.fAnalyzeMode], ""][fSplit] self.intro = CONSOLE_INTRO #self.setDaemon(True) self.m_session_manager = session_manager self.m_session_manager.set_printer(self.printer) event_type_dict = {CEventState: {}} self.m_session_manager.register_callback(self.event_handler, event_type_dict, fSingleUse = False) event_type_dict = {CEventTrap: {}} self.m_session_manager.register_callback(self.trap_handler, event_type_dict, fSingleUse = False) self.m_last_source_line = None self.m_last_nlines = DEFAULT_NUMBER_OF_LINES self.m_fAddPromptBeforeMsg = False self.m_eInLoop = threading.Event() self.cmdqueue.insert(0, '') def set_filename(self, filename): self.m_filename = filename def precmd(self, line): self.m_fAddPromptBeforeMsg = True if not self.m_eInLoop.isSet(): self.m_eInLoop.set() time.sleep(0.01) if not line.strip(): return line command = line.split(' ', 1)[0].split(SOURCE_MORE, 1)[0].split(SOURCE_LESS, 1)[0] if command not in ['list', 'l']: self.m_last_source_line = None self.m_last_nlines = DEFAULT_NUMBER_OF_LINES return line def postcmd(self, stop, line): self.m_fAddPromptBeforeMsg = False return stop def onecmd(self, line): """ Default Error handling and reporting of session manager errors. """ try: return cmd.Cmd.onecmd(self, line) except (socket.error, CConnectionException): self.m_session_manager.report_exception(*sys.exc_info()) except CException: self.m_session_manager.report_exception(*sys.exc_info()) except: self.m_session_manager.report_exception(*sys.exc_info()) print_debug(True) return False def emptyline(self): pass def run(self): self.cmdloop() def __get_str_wrap(self, _str, max_len): if len(_str) <= max_len and not '\n' in _str: return (_str, '') s = _str[: max_len] i = s.find('\n') if i == -1: i = s.rfind(' ') if i == -1: return (s, _str[max_len:]) return (_str[: i], _str[i + 1:]) def printer(self, _str): if not self.m_eInLoop.isSet(): self.m_eInLoop.wait() fAPBM = self.m_fAddPromptBeforeMsg prefix = ['', self.prompt.strip('\n')][fAPBM] + CONSOLE_PRINTER suffix = '\n' + [self.prompt.strip('\n'), ''][fAPBM] s = _str while s != '': s, _s = self.__get_str_wrap(s, CONSOLE_WRAP_INDEX - len(prefix + suffix)) self.stdout.write(prefix + s + suffix) s = _s self.stdout.flush() def print_notice(self, notice): nl = notice.split('\n') i = 0 for l in nl: print >> self.stdout, l i += 1 if i % PRINT_NOTICE_LINES_PER_SECTION == 0: print >> self.stdout, "\n" + PRINT_NOTICE_PROMPT, response = self.stdin.readline() if response != '\n': break print >> self.stdout def event_handler(self, event): state = event.m_state if (state == STATE_BROKEN) and self.fPrintBroken: self.fPrintBroken = False self.printer(STR_DEBUGGER_HAS_BROKEN) return if (state != STATE_ANALYZE) and self.fAnalyzeMode: self.fAnalyzeMode = False self.prompt = [CONSOLE_PROMPT, ""][self.m_fSplit] self.printer(STR_ANALYZE_MODE_TOGGLE % (MODE_OFF, )) return if (state == STATE_ANALYZE) and not self.fAnalyzeMode: self.fAnalyzeMode = True self.prompt = [CONSOLE_PROMPT_ANALYZE, ""][self.m_fSplit] self.printer(STR_ANALYZE_MODE_TOGGLE % (MODE_ON, )) return def trap_handler(self, event): self.printer(STR_TRAP_MODE_SET % (str(event.m_ftrap), )) def do_launch(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return if arg[:2] == '-k': fchdir = False _arg = arg[2:].strip() else: fchdir = True _arg = arg self.fPrintBroken = True try: self.m_session_manager.launch(fchdir, _arg) return except BadArgument: self.printer(STR_BAD_ARGUMENT) except IOError: self.printer(STR_FILE_NOT_FOUND % (arg, )) except: self.fPrintBroken = False raise self.fPrintBroken = False def do_restart(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return try: self.m_session_manager.restart() return except BadArgument: self.printer(STR_BAD_ARGUMENT) except IOError: self.printer(STR_FILE_NOT_FOUND % (arg, )) except: self.fPrintBroken = False raise self.fPrintBroken = False def do_attach(self, arg): if arg == '': return self.__scripts(arg) self.fPrintBroken = True try: self.m_session_manager.attach(arg) return except BadArgument: self.printer(STR_BAD_ARGUMENT) except: self.fPrintBroken = False raise self.fPrintBroken = False def __scripts(self, arg): if self.m_session_manager.get_password() is None: print >> self.stdout, STR_PASSWORD_MUST_BE_SET return host = self.m_session_manager.get_host() print >> self.stdout, STR_SCRIPTS_CONNECTING % (host, ) (server_list, errors) = self.m_session_manager.calc_server_list() if server_list == []: print >> self.stdout, STR_SCRIPTS_NO_SCRIPTS % (host, ) return try: spid = self.m_session_manager.get_server_info().m_pid except NotAttached: spid = None print >> self.stdout, STR_SCRIPTS_TO_DEBUG % (host, ) for s in server_list: m = ['', SYMBOL_MARKER][spid == s.m_pid] print >> self.stdout, ' %1s %-5d %s' % (m, s.m_pid, s.m_filename) def do_detach(self, arg): if not arg == '': self.printer(STR_BAD_ARGUMENT) return self.m_session_manager.detach() def do_host(self, arg): if arg == '': host = self.m_session_manager.get_host() print >> self.stdout, host return try: self.m_session_manager.set_host(arg) except socket.gaierror, e: self.printer(MSG_ERROR_HOST_TEXT % (arg, e)) def do_break(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return self.m_session_manager.request_break() do_b = do_break def __parse_bp_arg(self, arg, fAllowExpr = True): _args = arg.split(BP_EVAL_SEP) if (len(_args) > 1) and (not fAllowExpr): raise BadArgument if len(_args) > 1: expr = _args[1].strip() else: expr = '' rf = _args[0].rfind(BP_FILENAME_SEP) if rf == -1: args = [_args[0]] else: args = [_args[0][:rf], _args[0][rf + 1:]] filename = ['', args[0]][len(args) > 1] if filename in [None, '']: filename = self.m_filename try: lineno = int(args[-1]) scope = '' except ValueError: lineno = 0 scope = args[-1].strip() return (filename, scope, lineno, expr) def do_go(self, arg): if self.fAnalyzeMode: self.printer(STR_ILEGAL_ANALYZE_MODE_CMD) return try: if arg != '': (filename, scope, lineno, expr) = self.__parse_bp_arg(arg, fAllowExpr = False) self.fPrintBroken = True self.m_session_manager.request_go_breakpoint(filename, scope, lineno) return self.fPrintBroken = True self.m_session_manager.request_go() return except BadArgument: self.printer(STR_BAD_ARGUMENT) except IOError: self.printer(STR_FILE_NOT_FOUND % (filename, )) except InvalidScopeName: self.printer(STR_SCOPE_NOT_FOUND % (scope, )) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) except: self.fPrintBroken = False raise self.fPrintBroken = False do_g = do_go def do_step(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return if self.fAnalyzeMode: self.printer(STR_ILEGAL_ANALYZE_MODE_CMD) return try: self.m_session_manager.request_step() except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_s = do_step def do_next(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return if self.fAnalyzeMode: self.printer(STR_ILEGAL_ANALYZE_MODE_CMD) return try: self.m_session_manager.request_next() except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_n = do_next def do_return(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return if self.fAnalyzeMode: self.printer(STR_ILEGAL_ANALYZE_MODE_CMD) return try: self.m_session_manager.request_return() except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_r = do_return def do_jump(self, arg): try: lineno = int(arg) except ValueError: self.printer(STR_BAD_ARGUMENT) return try: self.m_session_manager.request_jump(lineno) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_j = do_jump def do_bp(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return try: (filename, scope, lineno, expr) = self.__parse_bp_arg(arg, fAllowExpr = True) self.m_session_manager.set_breakpoint(filename, scope, lineno, True, expr) except BadArgument: self.printer(STR_BAD_ARGUMENT) except IOError: self.printer(STR_FILE_NOT_FOUND % (filename, )) except InvalidScopeName: self.printer(STR_SCOPE_NOT_FOUND % (scope, )) except SyntaxError: self.printer(STR_BAD_EXPRESSION % (expr, )) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) def do_be(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return try: id_list = [] fAll = (arg == SYMBOL_ALL) if not fAll: sid_list = arg.split() id_list = [int(sid) for sid in sid_list] self.m_session_manager.enable_breakpoint(id_list, fAll) except ValueError: self.printer(STR_BAD_ARGUMENT) def do_bd(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return try: id_list = [] fAll = (arg == SYMBOL_ALL) if not fAll: sid_list = arg.split() id_list = [int(sid) for sid in sid_list] self.m_session_manager.disable_breakpoint(id_list, fAll) except ValueError: self.printer(STR_BAD_ARGUMENT) def do_bc(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return try: id_list = [] fAll = (arg == SYMBOL_ALL) if not fAll: sid_list = arg.split() id_list = [int(sid) for sid in sid_list] self.m_session_manager.delete_breakpoint(id_list, fAll) except ValueError: self.printer(STR_BAD_ARGUMENT) def do_bl(self, arg): bpl = self.m_session_manager.get_breakpoints() bplk = bpl.keys() bplk.sort() print >> self.stdout, STR_BREAKPOINTS_LIST for id in bplk: bp = bpl[id] if bp.m_expr: expr = bp.m_expr + '\n' else: expr = '' scope = bp.m_scope_fqn if scope.startswith(MODULE_SCOPE + '.'): scope = scope[len(MODULE_SCOPE) + 1:] elif scope.startswith(MODULE_SCOPE2 + '.'): scope = scope[len(MODULE_SCOPE2) + 1:] state = [STATE_DISABLED, STATE_ENABLED][bp.isEnabled()] print >> self.stdout, STR_BREAKPOINTS_TEMPLATE % (id, state, bp.m_lineno, clip_filename(bp.m_filename, 45), calc_suffix(scope, 45), calc_prefix(expr, 50)) def do_save(self, arg): self.m_session_manager.save_breakpoints(arg) print >> self.stdout, STR_BREAKPOINTS_SAVED return def do_load(self, arg): try: self.m_session_manager.load_breakpoints(arg) print >> self.stdout, STR_BREAKPOINTS_LOADED return except IOError: error = [STR_BREAKPOINTS_FILE_NOT_FOUND, STR_BREAKPOINTS_NOT_FOUND][arg == ''] self.printer(error) def do_stack(self, arg): if self.fAnalyzeMode and (arg != ''): self.printer(STR_ILEGAL_ANALYZE_MODE_ARG) return try: tid_list = [] fAll = (arg == SYMBOL_ALL) if not fAll: sid_list = arg.split() tid_list = [int(sid) for sid in sid_list] sl = self.m_session_manager.get_stack(tid_list, fAll) if len(sl) == 0: self.printer(STR_NO_THREADS_FOUND) return frame_index = self.m_session_manager.get_frame_index() m = None for st in sl: s = st.get(DICT_KEY_STACK, []) tid = st.get(DICT_KEY_TID, 0) fBroken = st.get(DICT_KEY_BROKEN, False) fCurrent = st.get(DICT_KEY_CURRENT_TID, False) if m is not None: print >> self.stdout print >> self.stdout, STR_STACK_TRACE % (tid, ) i = 0 while i < len(s): e = s[-(1 + i)] marker = [SOURCE_STATE_UNBROKEN, SYMBOL_MARKER][fBroken] if fCurrent: m = ['', marker][i == frame_index] else: m = ['', marker][i == 0] print >> self.stdout, ' %1s %5d %-28s %4d %s' % (m, i, calc_suffix(e[0], 28), e[1], calc_prefix(e[2], 20)) i += 1 except ValueError: self.printer(STR_BAD_ARGUMENT) except (NoExceptionFound, NoThreads): self.m_session_manager.report_exception(*sys.exc_info()) do_k = do_stack def do_list(self, arg): rf = arg.rfind(BP_FILENAME_SEP) if rf == -1: _filename = '' __args2 = arg else: _filename = arg[:rf] __args2 = arg[rf + 1:] _args = __args2.split(BP_EVAL_SEP) fAll = (_args[0] == SYMBOL_ALL) fMore = (_args[0] == SOURCE_MORE) fLess = (_args[0] == SOURCE_LESS) fEntire = (_args[0] == SOURCE_ENTIRE_FILE) fCurrent = (_args[0] == '') fLine = False l = 1 try: if len(_args) > 1: nlines = int(_args[1]) else: nlines = self.m_last_nlines if not (fAll or fMore or fLess or fEntire or fCurrent): l = int(_args[0]) fLine = True except ValueError: self.printer(STR_BAD_ARGUMENT) return if self.fAnalyzeMode and fAll: self.printer(STR_ILEGAL_ANALYZE_MODE_ARG) return if fMore and self.m_last_source_line: l = max(1, self.m_last_source_line + self.m_last_nlines / 2 + 1) fLine = True elif fLess and self.m_last_source_line: l = max(1, self.m_last_source_line - (self.m_last_nlines - 1) / 2 - nlines) fLine = True try: if fEntire: r = [self.m_session_manager.get_source_file(_filename, -1, -1)] elif fLine: r = [self.m_session_manager.get_source_file(_filename, l, nlines)] elif _filename != '': r = [self.m_session_manager.get_source_file(_filename, l, nlines)] else: r = self.m_session_manager.get_source_lines(nlines, fAll) if len(r) == 0: self.printer(STR_NO_THREADS_FOUND) return m = None for d in r: tid = d.get(DICT_KEY_TID, 0) filename = d.get(DICT_KEY_FILENAME, '') breakpoints = d.get(DICT_KEY_BREAKPOINTS, {}) source_lines = d.get(DICT_KEY_LINES, []) first_lineno = d.get(DICT_KEY_FIRST_LINENO, 0) if len(r) == 1 and first_lineno != 0: l = first_lineno fBroken = d.get(DICT_KEY_BROKEN, False) frame_event = d.get(DICT_KEY_EVENT, '') frame_lineno = d.get(DICT_KEY_FRAME_LINENO, 0) if m is not None: print >> self.stdout print >> self.stdout, STR_SOURCE_LINES % (tid, filename) for i, line in enumerate(source_lines): lineno = first_lineno + i if lineno != frame_lineno: m = '' elif not fBroken: m = SOURCE_STATE_UNBROKEN + SYMBOL_MARKER elif frame_event == 'call': m = SOURCE_EVENT_CALL + SYMBOL_MARKER elif frame_event == 'line': m = SOURCE_EVENT_LINE + SYMBOL_MARKER elif frame_event == 'return': m = SOURCE_EVENT_RETURN + SYMBOL_MARKER elif frame_event == 'exception': m = SOURCE_EVENT_EXCEPTION + SYMBOL_MARKER if breakpoints.get(lineno, None) == STATE_ENABLED: b = SOURCE_BP_ENABLED elif breakpoints.get(lineno, None) == STATE_DISABLED: b = SOURCE_BP_DISABLED else: b = '' print >> self.stdout, ' %2s %1s %5d %s' % (m, b, lineno, calc_prefix(line[:-1], 60)) if fAll or fEntire: self.m_last_source_line = None elif len(source_lines) != 0: self.m_last_source_line = [l + (nlines - 1) / 2, frame_lineno][l == -1] self.m_last_nlines = nlines except (InvalidFrame, IOError): self.printer(STR_SOURCE_NOT_FOUND) except (NoExceptionFound, NoThreads): self.m_session_manager.report_exception(*sys.exc_info()) do_l = do_list def do_up(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return try: fi = self.m_session_manager.get_frame_index() self.m_session_manager.set_frame_index(fi - 1) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) def do_down(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return try: fi = self.m_session_manager.get_frame_index() self.m_session_manager.set_frame_index(fi + 1) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) def evaluate_job(self, sync_event, expr): try: (value, warning, error) = self.m_session_manager.evaluate(expr) if warning: self.printer(STR_WARNING % (warning, )) if error: print >> self.stdout, error + '\n' print >> self.stdout, value if sync_event.isSet(): print >> self.stdout, self.prompt, return except (NoExceptionFound, DebuggerNotBroken): self.m_session_manager.report_exception(*sys.exc_info()) except (socket.error, CConnectionException): self.m_session_manager.report_exception(*sys.exc_info()) except CException: self.m_session_manager.report_exception(*sys.exc_info()) except: self.m_session_manager.report_exception(*sys.exc_info()) print_debug(True) def do_eval(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return sync_event = threading.Event() t = threading.Thread(target = self.evaluate_job, args = (sync_event, arg)) t.start() t.join(WAIT_FOR_BREAK_TIMEOUT) if t.isAlive(): print >> self.stdout, STR_OUTPUT_WARNING_ASYNC sync_event.set() do_v = do_eval def execute_job(self, sync_event, suite): try: (warning, error) = self.m_session_manager.execute(suite) if warning: self.printer(STR_WARNING % (warning, )) if error: print >> self.stdout, error + '\n' if sync_event.isSet(): print >> self.stdout, self.prompt, return except (NoExceptionFound, DebuggerNotBroken): self.m_session_manager.report_exception(*sys.exc_info()) except (socket.error, CConnectionException): self.m_session_manager.report_exception(*sys.exc_info()) except CException: self.m_session_manager.report_exception(*sys.exc_info()) except: self.m_session_manager.report_exception(*sys.exc_info()) print_debug(True) def do_exec(self, arg): if arg == '': self.printer(STR_BAD_ARGUMENT) return print >> self.stdout, STR_OUTPUT_WARNING sync_event = threading.Event() t = threading.Thread(target = self.execute_job, args = (sync_event, arg)) t.start() t.join(WAIT_FOR_BREAK_TIMEOUT) if t.isAlive(): print >> self.stdout, STR_OUTPUT_WARNING_ASYNC sync_event.set() do_x = do_exec def do_thread(self, arg): if self.fAnalyzeMode and (arg != ''): self.printer(STR_ILEGAL_ANALYZE_MODE_ARG) return try: if arg != '': tid = int(arg) self.m_session_manager.set_thread(tid) print >> self.stdout, STR_THREAD_FOCUS_SET return (current_thread_id, tl) = self.m_session_manager.get_thread_list() print >> self.stdout, STR_ACTIVE_THREADS for i, t in enumerate(tl): m = ['', SYMBOL_MARKER][t[DICT_KEY_TID] == current_thread_id] state = [STATE_RUNNING, STR_STATE_BROKEN][t[DICT_KEY_BROKEN]] print >> self.stdout, ' %1s %3d %5d %-15s %s' % (m, i, t[DICT_KEY_TID], t[DICT_KEY_NAME], state[:25]) except ValueError: self.printer(STR_BAD_ARGUMENT) except ThreadNotFound: self.printer(STR_THREAD_NOT_FOUND) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_t = do_thread def do_analyze(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return try: self.m_session_manager.set_analyze(not self.fAnalyzeMode) except DebuggerNotBroken: self.m_session_manager.report_exception(*sys.exc_info()) do_a = do_analyze def do_trap(self, arg): if arg == '': ftrap = self.m_session_manager.get_trap_unhandled_exceptions() print >> self.stdout, STR_TRAP_MODE % (str(ftrap), ) return if arg == str(True): ftrap = True elif arg == str(False): ftrap = False else: print >> self.stdout, STR_BAD_ARGUMENT return self.m_session_manager.set_trap_unhandled_exceptions(ftrap) def do_password(self, arg): if arg == '': pwd = self.m_session_manager.get_password() if pwd is None: print >> self.stdout, STR_PASSWORD_NOT_SET else: print >> self.stdout, STR_PASSWORD_SET % (pwd, ) return pwd = arg.strip('"\'') self.m_session_manager.set_password(pwd) print >> self.stdout, STR_PASSWORD_SET % (pwd, ) def do_remote(self, arg): if arg == '': fAllowRemote = self.m_session_manager.get_remote() print >> self.stdout, STR_REMOTE_MODE % (str(fAllowRemote), ) return if arg == str(True): fAllowRemote = True elif arg == str(False): fAllowRemote = False else: print >> self.stdout, STR_BAD_ARGUMENT return self.m_session_manager.set_remote(fAllowRemote) print >> self.stdout, STR_REMOTE_MODE % (str(fAllowRemote), ) def do_stop(self, arg): self.m_session_manager.stop_debuggee() def do_exit(self, arg): if arg != '': self.printer(STR_BAD_ARGUMENT) return if self.m_session_manager.get_state() != STATE_DETACHED: try: self.do_stop('') except (socket.error, CConnectionException): self.m_session_manager.report_exception(*sys.exc_info()) except CException: self.m_session_manager.report_exception(*sys.exc_info()) except: self.m_session_manager.report_exception(*sys.exc_info()) print_debug(True) print >> self.stdout, '' return True do_EOF = do_exit def do_copyright(self, arg): self.print_notice(COPYRIGHT_NOTICE) def do_license(self, arg): self.print_notice(LICENSE_NOTICE + COPY_OF_THE_GPL_LICENSE) def do_credits(self, arg): self.print_notice(CREDITS_NOTICE) def do_help(self, arg): cmd.Cmd.do_help(self, arg) if arg == '': help_notice = """Security: ---------------- password - Get or set the channel password. remote - Get or set "allow connections from remote machines" mode. Session Control: ----------------- host - Display or change host. attach - Display scripts or attach to a script on host. detach - Detach from script. launch - Spawn a script and attach to it. restart - Restart a script. stop - Shutdown the debugged script. exit - Exit from debugger. Debuggee Control: ----------------- break - Request an immediate break. step - Continue to the next execution line. next - Continue to the next execution line in the current frame. return - Continue until the debugger is about to return from the frame. jump - Jump to a line in the current scope. go - Continue execution. Breakpoints Control: -------------------- bp - Set a break point. bd - Disable a breakpoint. be - Enable a breakpoint. bc - Clear (delete) a breakpoint. bl - List all breakpoints. load - Load session breakpoints. save - save session breakpoints. Misc: ----- thread - Display threads or switch to a particular thread. list - List source code. stack - Display stack trace. up - Go up one frame in stack. down - Go down one frame in stack. eval - Evaluate expression in the context of the current frame. exec - Execute suite in the context of the current frame. analyze - Toggle analyze last exception mode. trap - Get or set "trap unhandled exceptions" mode. License: ---------------- copyright - Print copyright notice. license - Print license. credits - Print credits information. type help for futher information.""" self.print_notice(help_notice) def help_copyright(self): print >> self.stdout, """copyright Print copyright notice.""" def help_license(self): print >> self.stdout, """license Print license.""" def help_credits(self): print >> self.stdout, """credits Print credits information.""" def help_help(self): print >> self.stdout, """help Print help for command . On the other hand I guess that you already know that, don't you?""" def help_analyze(self): print >> self.stdout, """analyze (shorthand - a) Toggle analyze last exception mode. The following changes to the debugger behavior apply in analyze mode: The debugger prompt changes to 'Analyze>'. 'go', 'step', 'next', and 'return' are not allowed. 'thread' does not allow to change the thread focus. 'stack' allows no arguments. 'list' does not accept the '*' (all threads) argument 'stack', 'list', 'eval', 'exec', 'up', and 'down' operate on the thrown exception.""" help_a = help_analyze def help_password(self): print >> self.stdout, """password Get or set the channel password. Communication between the console and the debuggee is always authenticated and optionally encrypted. The password (A secret known to the console and the debuggee alone) governs both security methods. The password is never communicated between the two components on the communication channel. A password is always required since unsecured communication between the console and the debuggee may expose your machine to attacks.""" def help_remote(self): print >> self.stdout, """remote [True | False] Get or set "allow connections from remote machines" mode. When set to False: Newly launched debuggees will listen on localhost only. In this mode, debugger consoles on remote machines will NOT BE able to see or attach to the debuggee. When set to True: Newly launched debuggees will listen on INADDR_ANY. In this mode, debugger consoles on remote machines will BE able to see and attach to the debuggee.""" def help_trap(self): print >> self.stdout, """trap [True | False] Get or set "trap unhandled exceptions" mode. When set to False: Debuggee will ignore unhandled exceptions. When set to True: Debuggee will pause on unhandled exceptions for inspection.""" def help_stop(self): print >> self.stdout, """stop Shutdown the debugged script.""" def help_launch(self): print >> self.stdout, """launch [-k] [] Spawn script and attach to it. -k Don't change the current working directory. By default the working directory of the launched script is set to its folder.""" def help_restart(self): print >> self.stdout, """restart Restart a script with same arguments from last launch.""" def help_attach(self): print >> self.stdout, """attach [] Without an argument, 'attach' prints the scripts available for debugging on the selected host. To select a host use the 'host' command. A script is considered available for debugging only if it is using the rpdb2 module or has been executed by the debugger. If the debugger is already attached to a script, a special character will mark that script in the list. When is an integer the debugger will try to attach to a script with that pid. When is a string the debugger will try to attach to a script with that name in the list.""" def help_detach(self): print >> self.stdout, """detach Detach from the script the debugger is currently attached to. The detached script will continue execution.""" def help_break(self): print >> self.stdout, """break (shorthand - b) Request script to break (pause execution as if it hit a breakpoint). The 'break' command returns immdeiately but the break is only established when an active thread submits to the debugger control. If a thread is doing a system call or executing C code, this will happen only when it returns to do python code.""" help_b = help_break def help_bp(self): print >> self.stdout, """bp [':'] ( | ) [',' ] Set a breakpoint. - either the filename or the module name. - is the line number to assign the breakpoint to. - is a "fully qualified" function name. That is, not only the function name but also the class name (in case of a member function), such as MyClass.MyMemberFunction. - condition to evaluate in the context of the frame. If it evaluates to 'True' the break point will break into the debugger. In case the is omitted, the current file is assumed. In this case the debuggee has to be waiting at break point. Examples: bp test_file.py:20 bp test_file.py:MyClass.Foo bp 304 Type 'help break' for more information on breakpoints and threads.""" def help_be(self): print >> self.stdout, """be ( | '*') Enable breakpoints. - is a space delimited list of at least one breakpoint id '*' - Enable all breakpoints.""" def help_bd(self): print >> self.stdout, """bd ( | '*') Disable breakpoints. - is a space delimited list of at least one breakpoint id '*' - disable all breakpoints.""" def help_bc(self): print >> self.stdout, """bc ( | '*') Clear (delete) breakpoints. - is a space delimited list of at least one breakpoint id '*' - clear all breakpoints.""" def help_bl(self): print >> self.stdout, """bl List all breakpoints, sorted by their id.""" def help_load(self): print >> self.stdout, """load [] Load breakpoints. - optional breakpoints filename. The filename should not include a file extension.""" def help_save(self): print >> self.stdout, """save [] save breakpoints. - optional breakpoints filename. The filename should not include a file extension.""" def help_go(self): print >> self.stdout, """go [[':'] ( | )] (shorthand - g) Resume execution of a script that is waiting at break point. If an argument is present, continue execution until that argument is reached. - is the file name which basically is the script's name without the '.py' extension. - is the line number to assign the breakpoint to. - is a "fully qualified" function name. That is, not only the function name but also the class name (in case of a member function), such as MyClass.MyMemberFunction.""" help_g = help_go def help_exit(self): print >> self.stdout, """exit Exit the debugger. If the debugger is attached to a script, the debugger will attempt to detach from the script first.""" help_EOF = help_exit def help_host(self): print >> self.stdout, """host [] Without an argument, 'host' prints the current selected host. With an argument , 'host' attempts to resolve to a known ip address or a domain name. If it is successful, that host will become the selected host. The default selected host is the local host. Subsequent 'attach' commands will be done on the selected host. Type 'help attach' for more information.""" def help_stack(self): print >> self.stdout, """stack [ | '*'] (shorthand - k) Without an argument, 'stack' prints the stack trace of the focused thread. If the thread is waiting at break point a special character will mark the focused frame. - print the stack of thread '*' - print the stacks of all active threads. Type 'help break' for more information on breakpoints and threads. Type 'help up' or 'help down' for more information on focused frames.""" help_k = help_stack def help_list(self): print >> self.stdout, """list [:][ | '+' | '-' | '^' | '*'] [',' ] (shorthand - l) Without an argument, 'list' prints the source lines around the current line of the focused thread in the focused frame. A special character sequence will mark the current line according to the event: 'C>' - call - A function is called. 'L>' - line - The interpreter is about to execute a new line of code. 'R>' - return - A function is about to return. 'E>' - exception - An exception has been thrown. '*>' - running - The thread is running. If a breakpoint is assigned to a line, that line will be marked with: 'B' - if the breakpoint is enabled 'D' - if the breakpoint is disabled - List source from filename - Print the source lines around that line number in the same file of the current line. '+' - Print the next lines in the file. '-' - Print the previous lines in the file. '^' - Print the entire file. '*' - Print the source lines for each of the active threads. - Print of source Type 'help break' for more information on breakpoints and threads. Type 'help up' or 'help down' for more information on focused frames.""" help_l = help_list def help_thread(self): print >> self.stdout, """thread [ | ] (shorthand - t) Without an argument, 'thread' prints the list of known active threads, with their corresponding state, which can be either 'running' or 'waiting at break point'. A special character will mark the focused thread. With an argument , 'thread' will attempt to set the debugger focus to the thread of that tid. With an argument , 'thread' will attempt to set the debugger focus to the thread of that order in the thread list. Type 'help break' for more information on breakpoints and threads.""" help_t = help_thread def help_jump(self): print >> self.stdout, """jump (shorthand - j) Jump to line in the current scope.""" help_j = help_jump def help_next(self): print >> self.stdout, """next (shorthand - n) Continue execution until the next line in the current function is reached or it returns.""" help_n = help_next def help_step(self): print >> self.stdout, """next (shorthand - s) Execute the current line, stop at the first possible occasion (either in a function that is called or in the current function).""" help_s = help_step def help_return(self): print >> self.stdout, """next (shorthand - r) Continue execution until the current function returns.""" help_r = help_return def help_up(self): print >> self.stdout, """up move the debugger focus one frame up the stack of the debugged thread (closer to the current, most recently executed frame). Evaluation of expressions or execution of statements will be done at the local and global name spaces of the focused frame. Type 'help eval' for more information on evaluation of expressions. Type 'help exec' for more information on execution of statements.""" def help_down(self): print >> self.stdout, """down move the debugger focus one frame down the stack of the debugged thread (closer to the current, most recently executed frame). Evaluation of expressions or execution of statements will be done at the local and global name spaces of the focused frame. Type 'help eval' for more information on evaluation of expressions. Type 'help exec' for more information on execution of statements.""" def help_eval(self): print >> self.stdout, """eval (shorthand - v) Evaluate the python expression under the global and local name spaces of the currently focused frame. Example: 'eval locals()' - will display the dictionary of the local variables. IMPORTANT: Any changes to the global name space will be discarded unless the focused stack frame is the top most frame. Type 'help up' or 'help down' for more information on focused frames.""" help_v = help_eval def help_exec(self): print >> self.stdout, """exec (shorthand - x) Execute the python suite under the global and local name spaces of the currently focused frame. Example: 'exec i += 1' IMPORTANT: Any changes to the global name space will be discarded unless the focused stack frame is the top most frame. Type 'help up' or 'help down' for more information on focused frames.""" help_x = help_exec # # ---------------------------------------- main ------------------------------------ # def __settrace(): if g_debugger is None: return f = sys._getframe(2) g_debugger.settrace(f, f_break_on_init = False) def __setbreak(): if g_debugger is None: return f = sys._getframe(2) g_debugger.setbreak(f) def _atexit(fabort = False): if g_debugger is None: return if not fabort: g_debugger.stoptrace() g_debugger.send_event_exit() time.sleep(1.0) g_server.shutdown() g_debugger.shutdown() if fabort: os.abort() def __start_embedded_debugger(pwd, fAllowUnencrypted, fAllowRemote, timeout, fDebug): global g_server global g_debugger global g_fDebug global g_initial_cwd try: g_server_lock.acquire() if g_debugger is not None: f = sys._getframe(2) g_debugger.setbreak(f) return g_fDebug = fDebug xmlrpclib.loads(XML_DATA) if (not fAllowUnencrypted) and not is_encryption_supported(): raise EncryptionNotSupported f = sys._getframe(2) filename = calc_frame_path(f) # # This is an attempt to address the Python problem of recording only # relative paths in __file__ members of modules in the following case. # if sys.path[0] == '': g_initial_cwd = [os.getcwd()] atexit.register(_atexit) g_debugger = CDebuggerEngine(fembedded = True) g_server = CDebuggeeServer(filename, g_debugger, pwd, fAllowUnencrypted, fAllowRemote) g_server.start() g_debugger.settrace(f, timeout = timeout) finally: g_server_lock.release() def StartServer(args, fchdir, pwd, fAllowUnencrypted, fAllowRemote, rid): global g_server global g_debugger try: ExpandedFilename = FindFile(args[0]) except IOError: print 'File', args[0], ' not found.' # # Replace the rpdb2.py directory with the script directory in # the search path # sys.path[0] = os.path.dirname(ExpandedFilename) if fchdir: os.chdir(os.path.dirname(ExpandedFilename)) sys.argv = args atexit.register(_atexit) g_debugger = CDebuggerEngine() g_server = CDebuggeeServer(ExpandedFilename, g_debugger, pwd, fAllowUnencrypted, fAllowRemote, rid) g_server.start() try: g_debugger.m_bp_manager.set_temp_breakpoint(ExpandedFilename, '', 1, fhard = True) except: pass f = sys._getframe(0) g_debugger.settrace(f, f_break_on_init = False, builtins_hack = ExpandedFilename) del sys.modules['__main__'] # # An exception in this line occurs if # there is a syntax error in the debugged script or if # there was a problem loading the debugged script. # # Use analyze mode for post mortem. # type 'help analyze' for more information. # imp.load_source('__main__', ExpandedFilename) def StartClient(command_line, fAttach, fchdir, pwd, fAllowUnencrypted, fAllowRemote, host): if (not fAllowUnencrypted) and not is_encryption_supported(): print STR_ENCRYPTION_SUPPORT_ERROR return 2 sm = CSessionManager(pwd, fAllowUnencrypted, fAllowRemote, host) c = CConsole(sm) c.start() time.sleep(0.5) try: if fAttach: sm.attach(command_line) elif command_line != '': sm.launch(fchdir, command_line) except: pass c.join() def PrintUsage(fExtended = False): scriptName = os.path.basename(sys.argv[0]) print """ %(rpdb)s [options] [ [...]] Where the options can be a combination of the following: -h, --help print this help. -d, --debuggee start debuggee and break into it, without starting a debugger console. -a, --attach Attach to an already started debuggee. -o, --host Specify host for attachment. -r, --remote Allow debuggees to accept connections from remote machines. -e, --encrypt Force encrypted connections between debugger and debuggees. -p, --pwd Password. This flag is available only on NT systems. On other systems the password will be queried interactively if it is needed. -s, --screen Use the Unix screen utility when spawning the debuggee. -c, --chdir Change the working directory to that of the launched script. --debug Debug prints. """ % {"rpdb": scriptName} if not fExtended: return print __doc__ def main(StartClient_func = StartClient): global g_fScreen global g_fDebug create_rpdb_settings_folder() try: options, args = getopt.getopt( sys.argv[1:], 'hdao:rtep:sc', ['help', 'debugee', 'debuggee', 'attach', 'host=', 'remote', 'plaintext', 'encrypt', 'pwd=', 'rid=', 'screen', 'chdir', 'debug'] ) except getopt.GetoptError, e: print e return 2 fWrap = False fAttach = False fSpawn = False fStart = False secret = None host = None pwd = None fchdir = False fAllowRemote = False fAllowUnencrypted = True for o, a in options: if o in ['-h', '--help']: PrintUsage() return 0 if o in ['--debug']: g_fDebug = True if o in ['-d', '--debugee', '--debuggee']: fWrap = True if o in ['-a', '--attach']: fAttach = True if o in ['-o', '--host']: host = a if o in ['-r', '--remote']: fAllowRemote = True if o in ['-t', '--plaintext']: fAllowUnencrypted = True if o in ['-e', '--encrypt']: fAllowUnencrypted = False if o in ['-p', '--pwd']: pwd = a if o in ['--rid']: secret = a if o in ['-s', '--screen']: g_fScreen = True if o in ['-c', '--chdir']: fchdir = True if (pwd is not None) and (os.name != 'nt'): print STR_PASSWORD_NOT_SUPPORTED return 2 if fWrap and (len(args) == 0): print "--debuggee option requires a script name with optional arguments" return 2 if fWrap and fAttach: print "--debuggee and --attach can not be used together." return 2 if fAttach and (len(args) == 0): print "--attach option requires a script name to attach to." return 2 if fAttach and (len(args) > 1): print "--attach option does not accept arguments." return 2 if fAttach and fAllowRemote: print "--attach and --remote can not be used together." return 2 if (host is not None) and not fAttach: print "--host can only be used together with --attach." return 2 if host is None: host = LOCALHOST fSpawn = (len(args) != 0) and (not fWrap) and (not fAttach) fStart = (len(args) == 0) if fchdir and not (fWrap or fSpawn): print "-c can only be used when launching or starting a script from command line." return 2 assert (fWrap + fAttach + fSpawn + fStart) == 1 if fAttach and (os.name == POSIX): try: int(args[0]) pwd = read_pwd_file(args[0]) delete_pwd_file(args[0]) except (ValueError, IOError): pass if (secret is not None) and (os.name == POSIX): pwd = read_pwd_file(secret) if (fWrap or fAttach) and (pwd in [None, '']): print STR_PASSWORD_MUST_BE_SET while True: _pwd = raw_input(STR_PASSWORD_INPUT) pwd = _pwd.rstrip('\n') if pwd != '': break print STR_PASSWORD_CONFIRM if fWrap or fSpawn: try: FindFile(args[0]) except IOError: print STR_FILE_NOT_FOUND % (args[0], ) return 2 if fWrap: if (not fAllowUnencrypted) and not is_encryption_supported(): print STR_ENCRYPTION_SUPPORT_ERROR return 2 StartServer(args, fchdir, pwd, fAllowUnencrypted, fAllowRemote, secret) elif fAttach: StartClient_func(args[0], fAttach, fchdir, pwd, fAllowUnencrypted, fAllowRemote, host) elif fStart: StartClient_func('', fAttach, fchdir, pwd, fAllowUnencrypted, fAllowRemote, host) else: if len(args) == 0: _args = '' else: _args = '"' + string.join(args, '" "') + '"' StartClient_func(_args, fAttach, fchdir, pwd, fAllowUnencrypted, fAllowRemote, host) return 0 if __name__=='__main__': import rpdb2 # # Debuggee breaks (pauses) here # on unhandled exceptions. # Use analyze mode for post mortem. # type 'help analyze' for more information. # ret = rpdb2.main() SCRIPT_TERMINATED = True # # Debuggee breaks (pauses) here # before program termination. # sys.exit(ret) PIDA-0.5.1/pida/utils/unique.py0000644000175000017500000000016510652670605014274 0ustar alialiimport time from random import randint def create_unique_id(): return '%s.%s' % (time.time(), randint(0,10000)) PIDA-0.5.1/pida/utils/web.py0000644000175000017500000000145410652670605013545 0ustar aliali from urllib import urlopen, urlencode from pida.utils.gthreads import AsyncTask def fetch_url(url, content_callback, data={}): """Asynchronously fetch a URL""" if data: urlargs = (url, urlencode(data)) else: urlargs = (url,) def _fetcher(): try: f = urlopen(*urlargs) content = f.read() url = f.url except Exception, e: content = str(e) url = None return url, content task = AsyncTask(_fetcher, content_callback) task.start() if __name__ == '__main__': def cc(url, data): print url, data gtk.main_quit() fetch_url('http://google.com/sdfsdfsdf', cc) import gtk gtk.threads_init() gtk.threads_enter() gtk.main() gtk.threads_leave() PIDA-0.5.1/pida/__init__.py0000644000175000017500000000335110652671371013366 0ustar aliali""" The Python Integrated Development Application IDE Framework """ PIDA_VERSION = '0.5.1' PIDA_NAME = 'PIDA' PIDA_COPYRIGHT = 'Copyright (c) 2005-2007 The PIDA Project' PIDA_WEBSITE = 'http://pida.co.uk/' PIDA_AUTHOR = 'Ali Afshar ' PIDA_MAINTAINER = PIDA_AUTHOR PIDA_AUTHORS = [PIDA_AUTHOR] + [ 'Tiago Cogumbreiro ', 'Alejandro Mery ', 'Bernard Pratz ', 'Mathieu Virbel ', 'Ronny Pfannschmidt ', 'Anders Conbere ', ] PIDA_SHORT_DESCRIPTION = 'An intergated development environment that reuses tools such as Vim, and all version control systems.' PIDA_LICENSE = """ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ PIDA-0.5.1/pida/__init__.pyc0000644000175000017500000000370310652671463013534 0ustar aliali rFc@sYdZdZdZdZdZdZeZegdddd d d gZd Zd Z dS(s= The Python Integrated Development Application IDE Framework s0.5.1tPIDAs(Copyright (c) 2005-2007 The PIDA Projectshttp://pida.co.uk/sAli Afshar s,Tiago Cogumbreiro sAlejandro Mery sBernard Pratz s Mathieu Virbel s.Ronny Pfannschmidt s#Anders Conbere seAn intergated development environment that reuses tools such as Vim, and all version control systems.s Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. N( t__doc__t PIDA_VERSIONt PIDA_NAMEtPIDA_COPYRIGHTt PIDA_WEBSITEt PIDA_AUTHORtPIDA_MAINTAINERt PIDA_AUTHORStPIDA_SHORT_DESCRIPTIONt PIDA_LICENSE(((s&/home/ali/tmp/em/pida/pida/__init__.pyss PIDA-0.5.1/pida-plugins/0002755000175000017500000000000010652671501012727 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/0002755000175000017500000000000010652671501014534 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/locale/0002755000175000017500000000000010652671501015773 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/locale/fr_FR/0002755000175000017500000000000010652671501016771 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020556 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/locale/fr_FR/LC_MESSAGES/bookmark.po0000644000175000017500000000277710652670554022745 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../bookmark.py:105 msgid "Bookmarks" msgstr "Marque-pages" #: ../../bookmark.py:135 msgid "Dirs" msgstr "Réps" #: ../../bookmark.py:142 msgid "Files" msgstr "Fichiers" #: ../../bookmark.py:195 msgid "Bookmark Viewer" msgstr "Marque-pages" #: ../../bookmark.py:196 msgid "Show bookmarks" msgstr "Afficher les marque-pages" #: ../../bookmark.py:205 ../../bookmark.py:206 msgid "Bookmark current file" msgstr "Marquer le fichier courant" #: ../../bookmark.py:215 ../../bookmark.py:216 msgid "Bookmark current directory" msgstr "Marquer le répertoire courant" #: ../../bookmark.py:225 ../../bookmark.py:226 msgid "Delete selected item" msgstr "Supprimer l'élément sélectionné" #: ../../bookmark.py:235 ../../bookmark.py:245 msgid "Add as bookmark" msgstr "Ajouter dans les marque-pages" #: ../../bookmark.py:236 msgid "Add selected directory as bookmark" msgstr "Ajouter dans les marque-pages" #: ../../bookmark.py:246 msgid "Add selected file as bookmark" msgstr "Marquer le fichier sélectionné" #: ../../bookmark.py:341 msgid "(project root)" msgstr "(racine du projet)" PIDA-0.5.1/pida-plugins/bookmark/uidef/0002755000175000017500000000000010652671501015630 5ustar alialiPIDA-0.5.1/pida-plugins/bookmark/uidef/bookmark-dir-menu.xml0000644000175000017500000000037510652670555021710 0ustar aliali PIDA-0.5.1/pida-plugins/bookmark/uidef/bookmark-file-menu.xml0000644000175000017500000000040010652670555022036 0ustar aliali PIDA-0.5.1/pida-plugins/bookmark/uidef/bookmark-toolbar.xml0000644000175000017500000000036610652670555021632 0ustar aliali PIDA-0.5.1/pida-plugins/bookmark/uidef/bookmark.xml0000644000175000017500000000235210652670555020167 0ustar aliali PIDA-0.5.1/pida-plugins/bookmark/__init__.py0000644000175000017500000000222010652670555016647 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/bookmark/bookmark.py0000644000175000017500000003170210652670555016724 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk import os import cgi from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.environment import get_uidef_path from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorTask, AsyncTask, gcall # locale from pida.core.locale import Locale locale = Locale('bookmark') _ = locale.gettext class BookmarkItem(object): def __init__(self, group='none', title='no title', data=None): self.group = group self.title = title self.data = data def get_markup(self): return self.title markup = property(get_markup) def run(self, service): pass def key(self): return self.data + self.group + self.title class BookmarkItemFile(BookmarkItem): def __init__(self, title='no title', data=None, line=1): self.line = line BookmarkItem.__init__(self, group='file', title=title, data=data) def key(self): return BookmarkItem.key(self) + str(self.line) def run(self, service): service.boss.cmd('buffer', 'open_file', file_name=self.data) service.boss.editor.goto_line(self.line) class BookmarkItemPath(BookmarkItem): ICON_NAME = 'folder' def __init__(self, title='no title', data=None): BookmarkItem.__init__(self, group='path', title=title, data=data) def run(self, service): service.boss.cmd('filemanager', 'browse', new_path=self.data) service.boss.cmd('filemanager', 'present_view') """ class BookmarkItemUrl(BookmarkItem): ICON_NAME = 'www' def __init__(self, title='no title', data=None): BookmarkItem.__init__(self, group='url', title=title, data=data) def run(self, service): service.boss.call_command('webbrowser', 'browse', url=self.data) """ class BookmarkView(PidaView): icon_name = 'gtk-library' label_text = _('Bookmarks') def create_ui(self): self._vbox = gtk.VBox() self.create_toolbar() self.create_ui_list() self.add_main_widget(self._vbox) self._vbox.show_all() def create_tab_label(self, icon_name, text): if None in [icon_name, text]: return None label = gtk.Label(text) b_factory = gtk.HBox b = b_factory(spacing=2) icon = gtk.image_new_from_stock(icon_name, gtk.ICON_SIZE_MENU) b.pack_start(icon) b.pack_start(label) b.show_all() return b def create_ui_list(self): self._books = gtk.Notebook() self._books.set_border_width(6) self._list_dirs = ObjectList([Column('markup', data_type=str, use_markup=True)]) self._list_dirs.connect('row-activated', self._on_item_activated) self._list_dirs.connect('selection-changed', self._on_item_selected) self._list_dirs.set_headers_visible(False) self._list_dirs.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._books.append_page(self._list_dirs, tab_label=self.create_tab_label('stock_folder', _('Dirs'))) self._list_files = ObjectList([Column('markup', data_type=str, use_markup=True)]) self._list_files.connect('row-activated', self._on_item_activated) self._list_files.connect('selection-changed', self._on_item_selected) self._list_files.set_headers_visible(False) self._list_files.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._books.append_page(self._list_files, tab_label=self.create_tab_label('text-x-generic', _('Files'))) """ self._list_url = ObjectList([Column('markup', data_type=str, use_markup=True)]) self._list_url.set_headers_visible(False) self._books.add(self._list_url) """ self._vbox.add(self._books) self._books.show_all() def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('bookmark-toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) self._vbox.pack_start(self._toolbar, expand=False) self._toolbar.show_all() def add_item(self, item): if item.group == 'file': self._list_files.append(item) elif item.group == 'path': self._list_dirs.append(item) elif item.group == 'url': self._list_urls.append(item) def remove_item(self, item): if item.group == 'file': self._list_files.remove(item) elif item.group == 'path': self._list_dirs.remove(item) elif item.group == 'url': self._list_urls.remove(item) def clear_all(self): self._list_files.clear() self._list_dirs.clear() #self._list_urls.clear() def can_be_closed(self): self.svc.get_action('show_bookmark').set_active(False) def _on_item_selected(self, olist, item): self.svc.set_current(item) def _on_item_activated(self, olist, item): item.run(self.svc) class BookmarkActions(ActionsConfig): def create_actions(self): self.create_action( 'show_bookmark', TYPE_TOGGLE, _('Bookmark Viewer'), _('Show bookmarks'), '', self.on_show_bookmark, '1', ) self.create_action( 'bookmark_curfile', TYPE_NORMAL, _('Bookmark current file'), _('Bookmark current file'), 'text-x-generic', self.on_bookmark_curfile, 'NOACCEL', ) self.create_action( 'bookmark_curdir', TYPE_NORMAL, _('Bookmark current directory'), _('Bookmark current directory'), 'stock_folder', self.on_bookmark_curdir, 'NOACCEL', ) self.create_action( 'bookmark_delsel', TYPE_NORMAL, _('Delete selected item'), _('Delete selected item'), gtk.STOCK_DELETE, self.on_bookmark_delsel, 'NOACCEL', ) self.create_action( 'bookmark-for-dir', TYPE_NORMAL, _('Add as bookmark'), _('Add selected directory as bookmark'), gtk.STOCK_ADD, self.on_bookmark_for_dir, 'NOACCEL', ) self.create_action( 'bookmark-for-file', TYPE_NORMAL, _('Add as bookmark'), _('Add selected file as bookmark'), gtk.STOCK_ADD, self.on_bookmark_for_file, 'NOACCEL', ) def on_show_bookmark(self, action): if action.get_active(): self.svc.show_bookmark() else: self.svc.hide_bookmark() def on_bookmark_curdir(self, action): self.svc.bookmark_dir() def on_bookmark_curfile(self, action): self.svc.bookmark_file() def on_bookmark_delsel(self, action): self.svc.remove_current() def on_bookmark_for_file(self, action): self.svc.bookmark_file(filename=action.contexts_kw['file_name']) def on_bookmark_for_dir(self, action): self.svc.bookmark_dir(path=action.contexts_kw['dir_name']) class BookmarkFeatures(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('contexts', 'file-menu', (self.svc.get_action_group(), 'bookmark-file-menu.xml')) self.subscribe_foreign_feature('contexts', 'dir-menu', (self.svc.get_action_group(), 'bookmark-dir-menu.xml')) class BookmarkEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('project', 'project_switched', self.svc.on_project_switched) # Service class class Bookmark(Service): """Manage bookmarks""" actions_config = BookmarkActions features_config = BookmarkFeatures events_config = BookmarkEvents def start(self): self._view = BookmarkView(self) self._has_loaded = False self._items = [] self._current = None self._project = None def show_bookmark(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_bookmark(self): self.boss.cmd('window', 'remove_view', view=self._view) def set_current(self, item): self._current = item def add_item(self, item): for t in self._items: if t.key() == item.key(): return self._items.append(item) self._view.add_item(item) self.save() def remove_current(self): if self._current == None: return self._items.remove(self._current) self._view.remove_item(self._current) self._current = None self.save() def bookmark_dir(self, path=None): if path == None: path = self.boss.cmd('filemanager', 'get_browsed_path') source_directory = self.boss.cmd('project', 'get_current_project').source_directory title = path if title.startswith(source_directory): title = title[len(source_directory):] if title == '': title = _('(project root)') else: title = '.' + title item = BookmarkItemPath(title=title, data=path) self.add_item(item) def bookmark_file(self, filename=None, line=None): if filename == None: document = self.boss.cmd('buffer', 'get_current') if document == None: return filename = document.get_filename() if line == None: line = self.boss.editor.cmd('get_current_line_number') filename_title = os.path.basename(filename) title = '%s:%d' % ( cgi.escape(filename_title), int(line)) item = BookmarkItemFile(title=title, data=filename, line=line) self.add_item(item) def on_project_switched(self, project): if project != self._project: self._project = project self.load() def _serialize(self): data = {} for t in self._items: if not data.has_key(t.group): data[t.group] = [] if t.group == 'file': data[t.group].append('%s:%d' % (t.data, int(t.line))) else: data[t.group].append(t.data) return data def _unserialize(self, data): if data == None: return for key in data: items = data[key] for item in items: if key == 'file': t = item.rsplit(':') self.bookmark_file(filename=t[0], line=t[1]) elif key == 'path': self.bookmark_dir(path=item) def load(self): self._items = [] self._view.clear_all() data = self.boss.cmd('project', 'get_current_project_data', section_name='bookmark') self._unserialize(data) def save(self): data = self._serialize() self.boss.cmd('project', 'save_to_current_project', section_name='bookmark', section_data=data) def stop(self): if self.get_action('show_bookmark').get_active(): self.hide_bookmark() # Required Service attribute for service loading Service = Bookmark # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/bookmark/service.pida0000644000175000017500000000032510652670555017041 0ustar aliali[plugin] plugin = bookmark name = Bookmarks author = Mathieu Virbel version = 0.3 require_pida = 0.5 depends = "gtk,os,cgi" category = user description = "Manage bookmark (files, directories...)"PIDA-0.5.1/pida-plugins/bookmark/test_bookmark.py0000644000175000017500000000222010652670555017754 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/checklist/0002755000175000017500000000000010652671501014700 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/locale/0002755000175000017500000000000010652671501016137 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/locale/fr_FR/0002755000175000017500000000000010652671501017135 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020722 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/locale/fr_FR/LC_MESSAGES/checklist.po0000644000175000017500000000210510652670566023241 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../checklist.py:58 msgid "Check list" msgstr "Todo liste" #: ../../checklist.py:82 msgid "Done" msgstr "Fait" #: ../../checklist.py:83 msgid "Title" msgstr "Titre" #: ../../checklist.py:131 msgid "Checklist Viewer" msgstr "Todo liste" #: ../../checklist.py:132 msgid "Show checklists" msgstr "Afficher la todo liste" #: ../../checklist.py:141 ../../checklist.py:142 msgid "Add something in checklist" msgstr "Ajouter un élément dans la todo liste" #: ../../checklist.py:151 ../../checklist.py:152 msgid "Delete selected item" msgstr "Supprimer l'élément courant" #: ../../checklist.py:167 msgid "__edit_this__" msgstr "__à_éditer__" PIDA-0.5.1/pida-plugins/checklist/pixmaps/0002755000175000017500000000000010652671501016361 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/pixmaps/gtk-todo.svg0000644000175000017500000004443610652670567020656 0ustar aliali image/svg+xml Ubuntu Icon 2005-06-28 andrew@fitzsimon.com.au Andrew Fitzsimon Cannonical http://andy.fitzsimon.com.au Ubuntu SVG Etiquette AndyFitz Breezy GNOME Inkscape Ubuntu Icon theme Andrew Fitzsimon PIDA-0.5.1/pida-plugins/checklist/uidef/0002755000175000017500000000000010652671501015774 5ustar alialiPIDA-0.5.1/pida-plugins/checklist/uidef/checklist-toolbar.xml0000644000175000017500000000025110652670567022136 0ustar aliali PIDA-0.5.1/pida-plugins/checklist/uidef/checklist.xml0000644000175000017500000000235410652670567020504 0ustar aliali PIDA-0.5.1/pida-plugins/checklist/__init__.py0000644000175000017500000000222010652670570017010 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/checklist/checklist.py0000644000175000017500000002360610652670570017235 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk from pida.ui.objectlist import AttrSortCombo from kiwi.ui.objectlist import ObjectList, Column from kiwi.python import enum # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.environment import get_uidef_path from pida.ui.views import PidaView from pida.utils.unique import create_unique_id # locale from pida.core.locale import Locale locale = Locale('checklist') _ = locale.gettext #Critical, Major, Minor, Warning, Normal class ChecklistStatus(enum): (LOW, NORMAL, HIGH) = range(3) def __new__(cls, value, name): self = enum.__new__(cls, value, name) self.value = value return self class ChecklistItem(object): def __init__(self, title, priority=ChecklistStatus.NORMAL, done=False, key=None): self.title = title self.priority = priority self.done = done if key is not None: self.key = key else: self.key = str(create_unique_id()) class ChecklistView(PidaView): icon_name = 'gtk-todo' label_text = _('Check list') def create_ui(self): self._vbox = gtk.VBox(spacing=3) self._vbox.set_border_width(3) self.create_toolbar() self.create_newitem() self.create_list() self.add_main_widget(self._vbox) self._vbox.show_all() def create_tab_label(self, icon_name, text): if None in [icon_name, text]: return None label = gtk.Label(text) b_factory = gtk.HBox b = b_factory(spacing=2) icon = gtk.image_new_from_stock(icon_name, gtk.ICON_SIZE_MENU) b.pack_start(icon) b.pack_start(label) b.show_all() return b def create_list(self): self._list = ObjectList([ Column('done', title=_('Done'), data_type=bool, editable=True), Column('title', title=_('Title'), data_type=str, editable=True, expand=True), Column('priority', title=_('Priority'), data_type=ChecklistStatus, editable=True) ]) self._list.connect('cell-edited', self._on_item_edit) self._list.connect('selection-changed', self._on_item_selected) self._list.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self._vbox.add(self._list) self._sort_combo = AttrSortCombo(self._list, [ ('done', _('Done')), ('title', _('Title')), ('priority', _('Priority')), ], 'title') self._vbox.pack_start(self._sort_combo, expand=False) self._list.show_all() self._sort_combo.show_all() def create_newitem(self): self._hbox = gtk.HBox(spacing=3) self._newitem_title = gtk.Entry() self._newitem_title.connect('changed', self._on_newitem_changed) self._newitem_ok = gtk.Button(stock=gtk.STOCK_ADD) self._newitem_ok.connect('clicked', self._on_item_add) self._newitem_ok.set_sensitive(False) self._hbox.pack_start(self._newitem_title, expand=True) self._hbox.pack_start(self._newitem_ok, expand=False) self._vbox.pack_start(self._hbox, expand=False) self._hbox.show_all() def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('checklist-toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) self._vbox.pack_start(self._toolbar, expand=False) self.svc.get_action('checklist_del').set_sensitive(False) self._toolbar.show_all() def add_item(self, item): self._list.append(item, select=True) self.svc.save() def update_item(self, item): self._list.update(item) self.svc.save() def remove_item(self, item): self._list.remove(item) self.svc.save() def clear(self): self._list.clear() def _on_item_selected(self, olist, item): self.svc.get_action('checklist_del').set_sensitive(item is not None) self.svc.set_current(item) def _on_item_edit(self, olist, item, value): self.svc.save() def _on_item_add(self, w): title = self._newitem_title.get_text() self.svc.add_item(ChecklistItem(title=title)) self._newitem_title.set_text('') def _on_newitem_changed(self, w): self._newitem_ok.set_sensitive(self._newitem_title.get_text() != '') def can_be_closed(self): self.svc.get_action('show_checklist').set_active(False) class ChecklistActions(ActionsConfig): def create_actions(self): self.create_action( 'show_checklist', TYPE_TOGGLE, _('Checklist Viewer'), _('Show checklists'), '', self.on_show_checklist, '1', ) self.create_action( 'checklist_add', TYPE_NORMAL, _('Add something in checklist'), _('Add something in checklist'), gtk.STOCK_ADD, self.on_checklist_add, 'NOACCEL', ) self.create_action( 'checklist_del', TYPE_NORMAL, _('Delete selected item'), _('Delete selected item'), gtk.STOCK_DELETE, self.on_checklist_del, 'NOACCEL', ) def on_show_checklist(self, action): if action.get_active(): self.svc.show_checklist() else: self.svc.hide_checklist() def on_checklist_add(self, action): self.svc.add_item(ChecklistItem(title=_('__edit_this__'))) def on_checklist_del(self, action): self.svc.remove_current() class ChecklistEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('project', 'project_switched', self.svc.on_project_switched) # Service class class Checklist(Service): """Manage checklists""" actions_config = ChecklistActions events_config = ChecklistEvents def start(self): self._view = ChecklistView(self) self._has_loaded = False self._items = {} self._current = None self._project = None def show_checklist(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_checklist(self): self.boss.cmd('window', 'remove_view', view=self._view) def set_current(self, item): self._current = item def remove_current(self): if self._current == None: return del self._items[self._current.key] self._view.remove_item(self._current) self._current = None self.save() def add_item(self, item): self._items[item.key] = item self._view.add_item(item) self.save() def update_item(self, item, value): item.title = value self._items[item.key] = item self._view.update_item(item) self.save() def on_project_switched(self, project): if project != self._project: self._project = project self.load() def _serialize(self): data = {} for key in self._items: item = self._items[key] data[key] = '%s:%d:%s' % (item.done, item.priority.value, item.title) return data def _unserialize(self, data): if data == None: return for key in data: line = data[key] t = line.rsplit(':',2) done = False if t[0] == 'True': done = True self.add_item(ChecklistItem( title=str(t[2]), priority=ChecklistStatus.get(int(t[1])), done=done, key=key)) def load(self): self._items = {} self._view.clear() data = self.boss.cmd('project', 'get_current_project_data', section_name='checklist') self._unserialize(data) def save(self): data = self._serialize() self.boss.cmd('project', 'save_to_current_project', section_name='checklist', section_data=data) def stop(self): if self.get_action('show_checklist').get_active(): self.hide_checklist() # Required Service attribute for service loading Service = Checklist # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/checklist/service.pida0000644000175000017500000000033610652670570017204 0ustar aliali[plugin] plugin = checklist name = Todo manager version = 0.1.3 author = Mathieu Virbel require_pida = 0.5 depends = gtk description = Manage a personnal todo list per project website = "" category = userPIDA-0.5.1/pida-plugins/checklist/test_checklist.py0000644000175000017500000000222010652670570020261 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/debugger_gdb/0002755000175000017500000000000010652671501015327 5ustar alialiPIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/0002755000175000017500000000000010652671501017010 5ustar alialiPIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/break.xpm0000644000175000017500000000066510652670545020636 0ustar alialiPNG  IHDR_hPLTEI+zJtRNS@fbKGDH pHYs  tIME 59EIDATx33333DDDDCDDDDCEUDDCDDDDCDUUUCDDDDC DUUUC!DDDDC DEUTC!DDDDC DUUUC!DDDDC DDDDC33333uN*yIENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/forward.svg0000644000175000017500000000620710652670545021207 0ustar aliali ]> PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-break.png0000644000175000017500000000040210652670545021335 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cd`g`46fj Og$p4]9k F&Hptדk10V ?38=```GcS@ Egc8\&p&C7q `o`d؇pb %bhM l}a+IENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-go.png0000644000175000017500000000036610652670545020667 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cdf`ٴ5rj Hg$p4]9>oY 21!c5 zr-i85\? ]ZpJ]Ov#gHCA|`b3k[(s1 0D9@fIIENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-goto.png0000644000175000017500000000033610652670545021227 0ustar alialiPNG  IHDRubKGDC pHYs  ~IDAT8A0wߓ'' S-IM5r6dFL?|,U\*TVݹ `W2}<'05rG3@][Rw9k $#z -eHf9W"UIENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-locked.png0000644000175000017500000000034610652670545021521 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8K DGӃԛq31*X$ ٕPZ '7Z`R@2T;<ЀGbOtymN.p YclR>R=<K9f׎_c}R~_*IENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-next.png0000644000175000017500000000035110652670545021232 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Ez:T'CFSEba>p,#φ"@"Ob yoq_LrDD2n gMZ`xTNǚ]? /%}g`mZQ XEkw 0[gAYxIENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-return.png0000644000175000017500000000035410652670545021576 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Fz:GғIhC'8  2Ju"|#{+"lӂ|42=9Hjz~k\jӴ\Kv-µe7}˲ aK IENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-step.png0000644000175000017500000000035010652670545021226 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8ՔK DH.+^ZLj͟ kL{=C}oY'ǵ,߱.>IENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-toggle-bp.png0000644000175000017500000000032310652670545022133 0ustar alialiPNG  IHDRubKGD pHYs  sIDAT8A 0y{ Җj7bn%d5m8ǤHʏrHvDuБ<ZEU&')}ZF7!>]1Rqi,=}hyOɛBns{,:IENDB`PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/gdb-unlocked.png0000644000175000017500000000036110652670545022061 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8A w>̼ gԪII(dnHx 8SmS83ER$TkTl(@%jZ |=#k _Ks;QBN2>y~YyyG% image/svg+xml PIDA-0.5.1/pida-plugins/debugger_gdb/pixmaps/toggle_bp.xpm0000644000175000017500000000027410652670545021510 0ustar alialiPNG  IHDRu pHYs  nIDATx 0C]lci[4њ(HEE\DGB6+EQ%X.ܙ]ehLP6}~Y,s# ^m:sCgy/ # 'Current thread is (.*)' : # lambda self, m: self.emit('thread', thread=m.group(1)), # line: Starting program: 'Starting program: (.*) (.*)' : lambda self, m: self.emit('start_debugging', executable=m.group(1), arguments=m.group(2)), # line: *** Blank or comment '\*\*\* Blank or comment' : None, # line: Breakpoint set in file , line . 'Breakpoint (\d+) set in file (.*), line (\d+).' : lambda self, m: self.emit('add_breakpoint', ident=m.group(1), file=m.group(2), line=m.group(3)), # line: Deleted breakpoint 'Deleted breakpoint (\d+)' : lambda self, m: self.emit('del_breakpoint', ident=m.group(1)), # line: (:): '\((.*):(\d+)\): (.*)' : lambda self, m: self.emit('step', file=m.group(1), line=m.group(2), function=m.group(3)), '--Call--' : lambda self, m: self.emit('function_call'), '--Return--' : lambda self, m: self.emit('function_return') } # }}} # private methods def _parse(self, data): """ Parses a string of output and execute the correct command @param data line of output """ for pattern in self._parser_patterns: m = re.search(pattern, data) if m is not None: if self._parser_patterns[pattern] is not None: self._parser_patterns[pattern](self,m) def _send_cmd(self, cmd): """ Sends a command to the debugger """ if self._is_running: os.write(self._console.master, cmd + '\n') return True return False # implementation of the interface def init(self): """ Initiates the debugging session """ gdb_path = self.get_option('gdb_executable_path').value commandargs = [gdb_path, "--cd="+self._controller.get_cwd(), "--args", self._executable, self._parameters] self._console = self.boss.cmd('commander','execute', commandargs=commandargs, cwd=self._controller.get_cwd(), title='gdb', icon=None, use_python_fork=True, parser_func=self._parse) self._console.can_be_closed = self._end if self._console is not None: return True return False def end(self): """ Ends the debugging session """ self._send_cmd('quit') self._console = None return True def step_in(self): """ step in instruction """ self._send_cmd('step') def step_over(self): """ Step over instruction """ self._send_cmd('next') def cont(self): """ Continue execution """ self._send_cmd('continue') def finish(self): """ Jump to end of current function """ self._send_cmd('return') def add_breakpoint(self, file, line): self._send_cmd('break '+file+':'+str(line)) def del_breakpoint(self, file, line): self._send_cmd('clear '+file+':'+str(line)) # Required Service attribute for service loading Service = Gdb # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/debugger_gdb/test_anydbg.py0000644000175000017500000000222010652670546020206 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/debugger_pydb/0002755000175000017500000000000010652671501015531 5ustar alialiPIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/0002755000175000017500000000000010652671501017212 5ustar alialiPIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/break.xpm0000644000175000017500000000066510652670550021034 0ustar alialiPNG  IHDR_hPLTEI+zJtRNS@fbKGDH pHYs  tIME 59EIDATx33333DDDDCDDDDCEUDDCDDDDCDUUUCDDDDC DUUUC!DDDDC DEUTC!DDDDC DUUUC!DDDDC DDDDC33333uN*yIENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/forward.svg0000644000175000017500000000620710652670550021405 0ustar aliali ]> PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-break.png0000644000175000017500000000040210652670550021533 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cd`g`46fj Og$p4]9k F&Hptדk10V ?38=```GcS@ Egc8\&p&C7q `o`d؇pb %bhM l}a+IENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-go.png0000644000175000017500000000036610652670550021065 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cdf`ٴ5rj Hg$p4]9>oY 21!c5 zr-i85\? ]ZpJ]Ov#gHCA|`b3k[(s1 0D9@fIIENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-goto.png0000644000175000017500000000033610652670550021425 0ustar alialiPNG  IHDRubKGDC pHYs  ~IDAT8A0wߓ'' S-IM5r6dFL?|,U\*TVݹ `W2}<'05rG3@][Rw9k $#z -eHf9W"UIENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-locked.png0000644000175000017500000000034610652670550021717 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8K DGӃԛq31*X$ ٕPZ '7Z`R@2T;<ЀGbOtymN.p YclR>R=<K9f׎_c}R~_*IENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-next.png0000644000175000017500000000035110652670550021430 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Ez:T'CFSEba>p,#φ"@"Ob yoq_LrDD2n gMZ`xTNǚ]? /%}g`mZQ XEkw 0[gAYxIENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-return.png0000644000175000017500000000035410652670550021774 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Fz:GғIhC'8  2Ju"|#{+"lӂ|42=9Hjz~k\jӴ\Kv-µe7}˲ aK IENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-step.png0000644000175000017500000000035010652670550021424 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8ՔK DH.+^ZLj͟ kL{=C}oY'ǵ,߱.>IENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-toggle-bp.png0000644000175000017500000000032310652670550022331 0ustar alialiPNG  IHDRubKGD pHYs  sIDAT8A 0y{ Җj7bn%d5m8ǤHʏrHvDuБ<ZEU&')}ZF7!>]1Rqi,=}hyOɛBns{,:IENDB`PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/gdb-unlocked.png0000644000175000017500000000036110652670550022257 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8A w>̼ gԪII(dnHx 8SmS83ER$TkTl(@%jZ |=#k _Ks;QBN2>y~YyyG% image/svg+xml PIDA-0.5.1/pida-plugins/debugger_pydb/pixmaps/toggle_bp.xpm0000644000175000017500000000027410652670550021706 0ustar alialiPNG  IHDRu pHYs  nIDATx 0C]lci[4њ(HEE\DGB6+EQ%X.ܙ]ehLP6}~Y,s# ^m:sCgy/ # 'Current thread is (.*)' : # lambda self, m: self.emit('thread', thread=m.group(1)), # line: Restarting with arguments: 'Restarting (.*) with arguments:(.*)' : lambda self, m: self.emit('start_debugging', executable=m.group(1), arguments=m.group(2)), # line: *** Blank or comment '\*\*\* Blank or comment' : None, # line: Breakpoint set in file , line . 'Breakpoint (\d+) set in file (.*), line (\d+).' : lambda self, m: self.emit('add_breakpoint', ident=m.group(1), file=m.group(2), line=m.group(3)), # line: Deleted breakpoint 'Deleted breakpoint (\d+)' : lambda self, m: self.emit('del_breakpoint', ident=m.group(1)), # line: (:): '\((.*):(\d+)\): (.*)' : lambda self, m: self.emit('step', file=m.group(1), line=m.group(2), function=m.group(3)), '--Call--' : lambda self, m: self.emit('function_call'), '--Return--' : lambda self, m: self.emit('function_return') } # }}} # private methods def _parse(self, data): """ Parses a string of output and execute the correct command @param data line of output """ for pattern in self._parser_patterns: m = re.search(pattern, data) if m is not None: if self._parser_patterns[pattern] is not None: self._parser_patterns[pattern](self,m) def _send_cmd(self, cmd): """ Sends a command to the debugger """ if self._is_running: os.write(self._console.master, cmd + '\n') return True return False # implementation of the interface def init(self): """ Initiates the debugging session """ gdb_path = self.get_option('pydb_executable_path').value commandargs = [gdb_path, "--cd="+self._controller.get_cwd(), self._executable,self._parameters] self._console = self.boss.cmd('commander','execute', commandargs=commandargs, cwd=self._controller.get_cwd(), title='pydb', icon=None, use_python_fork=True, parser_func=self._parse) self._console.can_be_closed = self._end if self._console is not None: return True return False def end(self): """ Ends the debugging session """ self._send_cmd('quit') self._console = None return True def step_in(self): """ step in instruction """ self._send_cmd('step') def step_over(self): """ Step over instruction """ self._send_cmd('next') def cont(self): """ Continue execution """ self._send_cmd('continue') def finish(self): """ Jump to end of current function """ self._send_cmd('return') def add_breakpoint(self, file, line): self._send_cmd('break '+file+':'+str(line)) def del_breakpoint(self, file, line): self._send_cmd('clear '+file+':'+str(line)) # Required Service attribute for service loading Service = Pydb # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/debugger_pydb/test_anydbg.py0000644000175000017500000000222010652670551020404 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/gtags/0002755000175000017500000000000010652671501014034 5ustar alialiPIDA-0.5.1/pida-plugins/gtags/locale/0002755000175000017500000000000010652671501015273 5ustar alialiPIDA-0.5.1/pida-plugins/gtags/locale/fr_FR/0002755000175000017500000000000010652671501016271 5ustar alialiPIDA-0.5.1/pida-plugins/gtags/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020056 5ustar alialiPIDA-0.5.1/pida-plugins/gtags/locale/fr_FR/LC_MESSAGES/gtags.po0000644000175000017500000000232710652670553021533 0ustar aliali# PIDA. # Copyright (C) The PIDA Team # This file is distributed under the same license as the PIDA package. # Mathieu Virbel , 2007. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-10 18:32+0200\n" "PO-Revision-Date: 2007-05-10 18:32+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: gtags.py:85 msgid "Pattern : " msgstr "Recherche : " #: gtags.py:102 msgid "Build TAGS database" msgstr "Construire la base de données GTAGS" #: gtags.py:111 msgid "Symbol" msgstr "Symbole" #: gtags.py:113 msgid "Location" msgstr "Emplacement" #: gtags.py:115 msgid "Data" msgstr "Données" #: gtags.py:126 #, python-format msgid "%d match" msgstr "%d résultat" #: gtags.py:128 #, python-format msgid "%d matchs" msgstr "%d résultats" #: gtags.py:154 msgid "Gtags Viewer" msgstr "Visualiseur Gtags" #: gtags.py:155 msgid "Show the gtags" msgstr "Afficher les Gtags" #: gtags.py:164 gtags.py:165 msgid "Complete current word" msgstr "Trouver les occurences du mots courants" #: gtags.py:224 msgid "Gtags build..." msgstr "Constructions des Gtags..." PIDA-0.5.1/pida-plugins/gtags/uidef/0002755000175000017500000000000010652671501015130 5ustar alialiPIDA-0.5.1/pida-plugins/gtags/uidef/gtags-toolbar.xml0000644000175000017500000000015010652670554020420 0ustar aliali PIDA-0.5.1/pida-plugins/gtags/uidef/gtags.xml0000644000175000017500000000171410652670554016767 0ustar aliali PIDA-0.5.1/pida-plugins/gtags/__init__.py0000644000175000017500000000222010652670554016146 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/gtags/gtags.py0000644000175000017500000002356610652670554015534 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk import re from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.environment import Environment, get_uidef_path from pida.core.service import Service from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_TOGGLE, TYPE_NORMAL from pida.ui.buttons import create_mini_button from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorTask, GeneratorSubprocessTask # locale from pida.core.locale import Locale locale = Locale('gtags') _ = locale.gettext class GtagsItem(object): def __init__(self, file, line, dataline, symbol, search): self.file = file self.line = line self._dataline = dataline self._symbol = symbol self.search = search self.symbol = self._color_match(symbol, search) self.dataline = self._color(dataline, symbol) self.filename = '%s:%s' % (self.file, self.line) def _color_match(self, data, match): return data.replace(match, '%s' % match) def _color(self, data, match): return data.replace(match, '%s' % match) class GtagsView(PidaView): label_text = _('Gtags') def create_ui(self): self._hbox = gtk.HBox(spacing=3) self._hbox.set_border_width(2) self.add_main_widget(self._hbox) self._vbox = gtk.VBox(spacing=3) self._hbox.pack_start(self._vbox) self.create_searchbar() self.create_list() self.create_progressbar() self.create_toolbar() self._hbox.show_all() self._count = 0 def create_searchbar(self): h = gtk.HBox(spacing=3) h.set_border_width(2) # label l = gtk.Label() l.set_text(_('Pattern : ')) h.pack_start(l, expand=False) # pattern self._search = gtk.Entry() self._search.connect('changed', self._on_search_changed) self._search.set_sensitive(False) h.pack_start(self._search) # info self._info = gtk.Label() self._info.set_text('-') h.pack_start(self._info, expand=False) self._vbox.pack_start(h, expand=False) self._search.show_all() def create_toolbar(self): self._bar = gtk.VBox(spacing=1) self._refresh_button = create_mini_button( gtk.STOCK_REFRESH, _('Build TAGS database'), self._on_refresh_button_clicked) self._bar.pack_start(self._refresh_button, expand=False) self._hbox.pack_start(self._bar, expand=False) self._bar.show_all() def create_list(self): self._list = ObjectList( [ Column('symbol', data_type=str, title=_('Symbol'), use_markup=True), Column('filename', data_type=str, title=_('Location'), use_markup=True), Column('dataline', data_type=str, title=_('Data'), use_markup=True) ] ) self._list.connect('double-click', self._on_list_double_click) self._vbox.pack_start(self._list) self._list.show_all() def create_progressbar(self): self._progressbar = gtk.ProgressBar() self._vbox.pack_start(self._progressbar, expand=False) self._progressbar.set_no_show_all(True) self._progressbar.hide() def update_progressbar(self, current, max): if max > 1: self._progressbar.set_fraction(float(current) / float(max)) def show_progressbar(self, show): self._progressbar.set_no_show_all(False) if show: self._progressbar.show() else: self._progressbar.hide() def add_item(self, item): self._list.append(item) self._count += 1 if self._count == 1: self._info.set_text(_('%d match') % self._count) else: self._info.set_text(_('%d matchs') % self._count) def clear_items(self): self._list.clear() self._count = 0 self._info.set_text('-') def activate(self, activate): self._search.set_sensitive(activate) self._list.set_sensitive(activate) def can_be_closed(self): self.svc.get_action('show_gtags').set_active(False) def _on_search_changed(self, w): self.svc.tag_search(self._search.get_text()) def _on_refresh_button_clicked(self, w): self.svc.build_db() def _on_list_double_click(self, o, w): self.svc.boss.cmd('buffer', 'open_file', file_name=w.file) self.svc.boss.editor.goto_line(w.line) class GtagsActions(ActionsConfig): def create_actions(self): self.create_action( 'show_gtags', TYPE_TOGGLE, _('Gtags Viewer'), _('Show the gtags'), '', self.on_show_gtags, 'y', ) self.create_action( 'gtags_current_word_file', TYPE_NORMAL, _('Complete current word'), _('Complete current word'), gtk.STOCK_FIND, self.on_gtags_current_word, 'slash' ) def on_show_gtags(self, action): if action.get_active(): self.svc.show_gtags() else: self.svc.hide_gtags() def on_gtags_current_word(self, action): self.svc.tag_search_current_word() class GtagsEvents(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('project', 'project_switched', self.svc.on_project_switched) # Service class class Gtags(Service): """Fetch gtags list and show an gtags""" actions_config = GtagsActions events_config = GtagsEvents def start(self): self._view = GtagsView(self) self._has_loaded = False self._project = self.boss.cmd('project', 'get_current_project') self._ticket = 0 self.task = self._task = None def show_gtags(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_gtags(self): self.boss.cmd('window', 'remove_view', view=self._view) def have_database(self): if self._project is None: return False return os.path.exists(os.path.join(self._project.source_directory, 'GTAGS')) def build_db(self): if self._project is None: return False if self.have_database(): commandargs = ['global', '-v', '-u'] else: commandargs = ['gtags', '-v'] self._view._refresh_button.set_sensitive(False) self.boss.cmd('commander', 'execute', commandargs=commandargs, cwd=self._project.source_directory, title=_('Gtags build...'), eof_handler=self.build_db_finished) def build_db_finished(self, w): self._view.activate(self.have_database()) self._view._refresh_button.set_sensitive(True) self.boss.cmd('notify', 'notify', title=_('Gtags'), data=_('Database build complete')) def on_project_switched(self, project): if project != self._project: self._project = project self._view.activate(self.have_database()) def tag_search_current_word(self): self.boss.editor.cmd('call_with_current_word', callback=self.tag_search_cw) def tag_search_cw(self, word): self.get_action('show_gtags').set_active(True) self._view._list.grab_focus() self._view._search.set_text(word) def tag_search(self, pattern): if not self.have_database() or pattern is None: return self._view.clear_items() if pattern == '': return if self._task: self._task.stop() def _line(line): match = re.search('([^\ ]*)[\ ]+([0-9]+) ([^\ ]+) (.*)', line) if match is None: return data = match.groups() self._view.add_item(GtagsItem(file=data[2], line=data[1], dataline=data[3], symbol=data[0], search=pattern)) if self._view._count > 200: self._task.stop() cmd = 'for foo in `global -c %s`; do global -x -e $foo; done' % pattern self._task = GeneratorSubprocessTask(_line) self._task.start(cmd, cwd=self._project.source_directory, shell=True) def stop(self): if self._task: self._task.stop() if self.get_action('show_gtags').get_active(): self.hide_gtags() # Required Service attribute for service loading Service = Gtags # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/gtags/service.pida0000644000175000017500000000035710652670554016345 0ustar aliali[plugin] plugin = gtags name = GNU Global Integration version = 0.2 author = Mathieu Virbel require_pida = 0.5 depends = "os,gtk,re" description = "Build global index, search through database" website = "" category = codePIDA-0.5.1/pida-plugins/gtags/test_gtags.py0000644000175000017500000000222010652670554016553 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/koders/0002755000175000017500000000000010652671501014216 5ustar alialiPIDA-0.5.1/pida-plugins/koders/pixmaps/0002755000175000017500000000000010652671501015677 5ustar alialiPIDA-0.5.1/pida-plugins/koders/pixmaps/koders-logo.png0000644000175000017500000000122010652670564020633 0ustar alialiPNG  IHDR ex+tEXtCreation Timelun 9 abr 2007 18:56:55 +0100n CtIME 9}l pHYsnu>gAMA a&PLTEɨΧߦڪة̽鵴իԺuJ^ycpJaJaIacpH_IaL_|ƂIDATxc@ `%>!C}"BCC#!ap@`%*f% 8IH:`oie-)jck'.foio`hd,cbjfΠ(!*#! -##+'$###/'+#/ ($/"*&.!)7*P3IENDB`PIDA-0.5.1/pida-plugins/koders/uidef/0002755000175000017500000000000010652671501015312 5ustar alialiPIDA-0.5.1/pida-plugins/koders/uidef/koders.xml0000644000175000017500000000234610652670564017336 0ustar aliali PIDA-0.5.1/pida-plugins/koders/__init__.py0000644000175000017500000000222010652670565016332 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/koders/koders.py0000644000175000017500000001476010652670565016076 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk import re import urllib import gobject from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.environment import Environment, get_uidef_path from pida.core.service import Service from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_TOGGLE, TYPE_NORMAL from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorTask, gcall from pida.utils.web import fetch_url from pida.utils.feedparser import parse # locale from pida.core.locale import Locale locale = Locale('koders') _ = locale.gettext class KodersItem(object): def __init__(self, entry): print entry self.title = entry['title'] self.link = entry['link'] self.description = entry['description'] class KodersView(PidaView): label_text = 'Koders' icon_name = 'koders_logo' def create_ui(self): self._vbox = gtk.VBox(spacing=3) self._vbox.set_border_width(6) self.add_main_widget(self._vbox) self.create_searchbar() self.create_list() self.create_view() self.create_pulsebar() self._vbox.show_all() def create_searchbar(self): h = gtk.HBox() self._search_description = gtk.Entry() self._search_description.connect('changed', self._on_search_changed) l = gtk.Label() l.set_text(_('Filter : ')) h.pack_start(l, expand=False) h.pack_start(self._search_description) self._vbox.pack_start(h, expand=False) self._search_description.show_all() def create_list(self): self._list = ObjectList( [ Column('title', data_type=str, title=_('Title'), expand=True), ] ) self._list.connect('selection-changed', self._on_list_selected) self._list.connect('double-click', self._on_list_double_click) self._vbox.pack_start(self._list) self._list.show_all() def create_view(self): self._textview = gtk.TextView() self._vbox.pack_start(self._textview, expand=False) self._textview.show_all() def create_pulsebar(self): self.__pulse_bar = gtk.ProgressBar() self.add_main_widget(self.__pulse_bar, expand=False) self.__pulse_bar.show_all() self.__pulse_bar.set_size_request(-1, 12) self.__pulse_bar.set_pulse_step(0.01) self._vbox.pack_start(self.__pulse_bar, expand=False) self.__pulse_bar.set_no_show_all(True) self.__pulse_bar.hide() def append(self, item): self._list.append(item) def clear(self): self._list.clear() def can_be_closed(self): self.svc.get_action('show_koders').set_active(False) def _on_list_selected(self, ot, item): self._textview.get_buffer().set_text(item.description) def _on_list_double_click(self, ot, item): self.svc.browse(url=item.link) def _on_search_changed(self, w): self.svc.search(pattern=self._search_description.get_text()) def start_pulse(self): self._pulsing = True gobject.timeout_add(100, self._pulse) def stop_pulse(self): self._pulsing = False def _pulse(self): self.__pulse_bar.pulse() return self._pulsing class KodersActions(ActionsConfig): def create_actions(self): self.create_action( 'show_koders', TYPE_TOGGLE, _('Koders Viewer'), _('Show Koders search window'), '', self.on_show_koders, '', ) def on_show_koders(self, action): if action.get_active(): self.svc.show_koders() else: self.svc.hide_koders() # Service class class Koders(Service): """Browse Koders database""" actions_config = KodersActions def start(self): self._view = KodersView(self) self._has_loaded = False self.task = None def show_koders(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_koders(self): self.boss.cmd('window', 'remove_view', view=self._view) def browse(self, id): self.boss.cmd('webbrowser', 'browse', url=(self.url_rfctmpl + id)) def stop(self): if self.task != None: self.task.stop() if self.get_action('show_koders').get_active(): self.hide_koders() def search(self, pattern, language='', licence=''): url = 'http://www.koders.com/?output=rss&s=%s' % pattern if language != '': url = url + '&la=%s' % language if licence != '': url = url + '&li=%s' % licence self._view.start_pulse() fetch_url(url, self.search_callback) def search_callback(self, url, data): self._view.clear() def parse_search(data): def parse_result(data): feed = parse(data) for entry in feed.entries: yield entry for entry in parse_result(data): yield KodersItem(entry) for item in parse_search(data): self._view.append(item) self._view.stop_pulse() def browse(self, url): self.boss.cmd('webbrowser', 'browse', url=url) # Required Service attribute for service loading Service = Koders # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/koders/service.pida0000644000175000017500000000036110652670565016524 0ustar aliali[plugin] plugin = koders name = Koders integration version = 0.1 author = Mathieu Virbel require_pida = 0.5 depends = "os,gtk,re,urllib" description = "Search throught Koders database" website = "" category = documentation PIDA-0.5.1/pida-plugins/koders/test_koders.py0000644000175000017500000000222010652670565017121 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/library/0002755000175000017500000000000010652671501014373 5ustar alialiPIDA-0.5.1/pida-plugins/library/glade/0002755000175000017500000000000010652671501015447 5ustar alialiPIDA-0.5.1/pida-plugins/library/glade/library-viewer.glade0000644000175000017500000001444510652670552021423 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Books 1 tab False False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Contents 1 tab 1 False False 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-refresh True False 2 PIDA-0.5.1/pida-plugins/library/locale/0002755000175000017500000000000010652671501015632 5ustar alialiPIDA-0.5.1/pida-plugins/library/locale/fr_FR/0002755000175000017500000000000010652671501016630 5ustar alialiPIDA-0.5.1/pida-plugins/library/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020415 5ustar alialiPIDA-0.5.1/pida-plugins/library/locale/fr_FR/LC_MESSAGES/library.po0000644000175000017500000000171610652670552022431 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/library-viewer.glade.h:1 msgid "Books" msgstr "Livres" #: glade/library-viewer.glade.h:2 msgid "Contents" msgstr "Contenu" #: library.py:62 library.py:318 msgid "Documentation" msgstr "Documentation" #: library.py:115 msgid "Documentation Library" msgstr "Documentations" #: library.py:116 msgid "Show the documentation library" msgstr "Afficher la documentation" #: library.py:125 library.py:126 msgid "Documentation Browser" msgstr "Navigateur de documentation" #: library.py:169 msgid "untitled" msgstr "sans titre" PIDA-0.5.1/pida-plugins/library/uidef/0002755000175000017500000000000010652671501015467 5ustar alialiPIDA-0.5.1/pida-plugins/library/uidef/library.xml0000644000175000017500000000255510652670552017667 0ustar aliali PIDA-0.5.1/pida-plugins/library/__init__.py0000644000175000017500000000222010652670552016503 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/library/library.py0000644000175000017500000002553510652670552016426 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gzip import tempfile import threading import xml.sax import xml.dom.minidom as minidom xml.sax.handler.feature_external_pes = False import gtk import gobject from kiwi.ui.objectlist import ObjectTree, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.utils.gthreads import GeneratorTask, AsyncTask # locale from pida.core.locale import Locale locale = Locale('library') _ = locale.gettext class LibraryView(PidaGladeView): _columns = [ Column('title', expand=True, sorted=True) ] gladefile = 'library-viewer' locale = locale icon_name = 'gtk-library' label_text = _('Documentation') def create_ui(self): self.books_list.set_columns(self._columns) self.contents_tree.set_columns(self._columns) self.books_list.set_headers_visible(False) self.contents_tree.set_headers_visible(False) def fetch_books(self): self.books_list.clear() self.contents_tree.clear() task = GeneratorTask(fetch_books, self.add_book) task.start() def add_book(self, item): self.books_list.append(item) def on_books_list__double_click(self, ol, item): self.contents_tree.clear() if item is not None: task = AsyncTask(self.load_book, self.book_loaded) task.start() def on_contents_tree__double_click(self, ot, item): #self.svc.boss.cmd('webbrowser', 'browse', url=item.path) self.svc.browse_file(item.path) def load_book(self): item = self.books_list.get_selected() return item.load() def book_loaded(self, bookmarks): self.contents_tree.clear() task = GeneratorTask(bookmarks.get_subs, self.add_bookmark) task.start() self.view_book.set_current_page(1) def add_bookmark(self, bookmark, parent): self.contents_tree.append(parent, bookmark) def on_refresh_button__clicked(self, button): self.fetch_books() def can_be_closed(self): self.svc.get_action('show_library').set_active(False) class LibraryActions(ActionsConfig): def create_actions(self): self.create_action( 'show_library', TYPE_TOGGLE, _('Documentation Library'), _('Show the documentation library'), '', self.on_show_library, 'r', ) self.create_action( 'show_browser', TYPE_TOGGLE, _('Documentation Browser'), _('Documentation Browser'), '', self.on_show_browser, 'NOACCEL', ) def on_show_library(self, action): if action.get_active(): self.svc.show_library() else: self.svc.hide_library() def on_show_browser(self, action): if action.get_active(): self.svc.show_browser() else: self.svc.hide_browser() def fetch_books(): dirs = ['/usr/share/gtk-doc/html', '/usr/share/devhelp/books', os.path.expanduser('~/.devhelp/books')] use_gzip = True#self.opts.book_locations__use_gzipped_book_files for dir in dirs: for book in fetch_directory(dir): yield book def fetch_directory(directory): if os.path.exists(directory): for name in os.listdir(directory): path = os.path.join(directory, name) if os.path.exists(path): load_book = Book(path, True) yield load_book class TitleHandler(xml.sax.handler.ContentHandler): def __init__(self): self.title = _('untitled') self.is_finished = False def startElement(self, name, attributes): self.title = attributes['title'] self.is_finished = True class Book(object): def __init__(self, path, include_gz=True): self.directory = path self.key = path self.name = os.path.basename(path) self.bookmarks = None try: self.short_load() except (OSError, IOError): pass def has_load(self): return self.bookmarks is not None def short_load(self): config_path = None path = self.directory if not os.path.isdir(path): return for name in os.listdir(path): if name.endswith('.devhelp'): config_path = os.path.join(path, name) break elif name.endswith('.devhelp.gz'): gz_path = os.path.join(path, name) f = gzip.open(gz_path, 'rb', 1) gz_data = f.read() f.close() fd, config_path = tempfile.mkstemp() os.write(fd, gz_data) os.close(fd) break self.title = None if config_path: parser = xml.sax.make_parser() parser.setFeature(xml.sax.handler.feature_external_ges, 0) handler = TitleHandler() parser.setContentHandler(handler) f = open(config_path) for line in f: try: parser.feed(line) except: raise if handler.is_finished: break f.close() self.title = handler.title if not self.title: self.title = os.path.basename(path) def load(self): config_path = None path = self.directory for name in os.listdir(path): if name.endswith('.devhelp'): config_path = os.path.join(path, name) break elif name.endswith('.devhelp.gz'): gz_path = os.path.join(path, name) f = gzip.open(gz_path, 'rb', 1) gz_data = f.read() f.close() fd, config_path = tempfile.mkstemp() os.write(fd, gz_data) os.close(fd) break if config_path and os.path.exists(config_path): dom = minidom.parse(config_path) main = dom.documentElement book_attrs = dict(main.attributes) for attr in book_attrs: setattr(self, attr, book_attrs[attr].value) self.chapters = dom.getElementsByTagName('chapters')[0] self.root = os.path.join(self.directory, self.link) self.bookmarks = self.get_bookmarks() else: for index in ['index.html']: indexpath = os.path.join(path, index) if os.path.exists(indexpath): self.root = indexpath break self.root = indexpath self.key = path return self.get_bookmarks() def get_bookmarks(self): root = BookMark(self.chapters, self.directory) root.name = self.title root.path = self.root return root class BookMark(object): def __init__(self, node, root_path): try: self.name = node.attributes['name'].value except: self.name = None self.title = self.name try: self.path = os.path.join(root_path, node.attributes['link'].value) except: self.path = None self.key = self.path self.subs = [] for child in self._get_child_subs(node): bm = BookMark(child, root_path) self.subs.append(bm) def _get_child_subs(self, node): return [n for n in node.childNodes if n.nodeType == 1] def get_subs(self, parent=None): if parent is None: aparent = self else: aparent = parent for sub in aparent.subs: yield sub, parent for csub in self.get_subs(sub): yield csub def __iter__(self): yield None, sub # Service class class Library(Service): """Describe your Service Here""" actions_config = LibraryActions def start(self): self._view = LibraryView(self) bclass = self.boss.cmd('webbrowser', 'get_web_browser') self._browser = bclass(self) self._browser.label_text = _('Documentation') self._browser.connect_closed(self._on_close_clicked) self._has_loaded = False def show_library(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) if not self._has_loaded: self._has_loaded = True self._view.fetch_books() def hide_library(self): self.boss.cmd('window', 'remove_view', view=self._view) def _on_close_clicked(self, button): self.get_action('show_browser').set_active(False) def show_browser(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._browser) def hide_browser(self): self.boss.cmd('window', 'remove_view', view=self._browser) def ensure_browser_visible(self): if not self.get_action('show_browser').get_active(): self.get_action('show_browser').set_active(True) self.boss.cmd('window', 'present_view', view=self._browser) def browse_file(self, url): self.ensure_browser_visible() self._browser.fetch(url) def stop(self): if self.get_action('show_library').get_active(): self.hide_library() if self.get_action('show_browser').get_active(): self.hide_browser() # Required Service attribute for service loading Service = Library # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/library/service.pida0000644000175000017500000000037310652670552016700 0ustar aliali[plugin] plugin = library name = Docbook browser author = Ali Afshar version = 0.2.2 require_pida = 0.5 depends = "os, gzip, tempfile, threading, xml.sax, xml.com.minidom" category = documentation description = Browse local docbookPIDA-0.5.1/pida-plugins/library/test_library.py0000644000175000017500000000222010652670552017447 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/man/0002755000175000017500000000000010652671501013502 5ustar alialiPIDA-0.5.1/pida-plugins/man/locale/0002755000175000017500000000000010652671501014741 5ustar alialiPIDA-0.5.1/pida-plugins/man/locale/fr_FR/0002755000175000017500000000000010652671501015737 5ustar alialiPIDA-0.5.1/pida-plugins/man/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501017524 5ustar alialiPIDA-0.5.1/pida-plugins/man/locale/fr_FR/LC_MESSAGES/man.po0000644000175000017500000000125510652670571020646 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../man.py:89 #, python-format msgid "Man %(pattern)s(%(number)d)" msgstr "Man %(pattern)s(%(number)d)" #: ../../man.py:104 msgid "Man Viewer" msgstr "Visualiseur de Man" #: ../../man.py:105 msgid "Show the man" msgstr "Afficher le man" PIDA-0.5.1/pida-plugins/man/pixmaps/0002755000175000017500000000000010652671501015163 5ustar alialiPIDA-0.5.1/pida-plugins/man/pixmaps/gtk-library.svg0000644000175000017500000001047710652670571020150 0ustar aliali open bible 01 hash christianity religion education book Aaron Johnson Aaron Johnson Aaron Johnson image/svg+xml en PIDA-0.5.1/pida-plugins/man/uidef/0002755000175000017500000000000010652671501014576 5ustar alialiPIDA-0.5.1/pida-plugins/man/uidef/man.xml0000644000175000017500000000234010652670572016077 0ustar aliali PIDA-0.5.1/pida-plugins/man/__init__.py0000644000175000017500000000222010652670572015614 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/man/man.py0000644000175000017500000001425610652670572014644 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk import commands import re import cgi from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.service import Service from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_TOGGLE from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorSubprocessTask # locale from pida.core.locale import Locale locale = Locale('man') _ = locale.gettext class ManItem(object): def __init__(self, pattern, number, description, search): self.pattern = pattern self.number = number self.description = self._color_match(cgi.escape(description), search) patternmark = self._color_match(cgi.escape(pattern), search) self.markup = '%s(%d)' % ( patternmark, int(self.number)) def _color_match(self, data, match): return data.replace(match, '%s' % match) class ManView(PidaView): icon_name = 'gtk-library' label_text = 'Man' def create_ui(self): self._count = 0 self.__vbox = gtk.VBox(spacing=3) self.__vbox.set_border_width(6) self.__hbox = gtk.HBox() self.__entry = gtk.Entry() self.__entry.connect('changed', self.cb_entry_changed) self.__check = gtk.CheckButton(label='-k') self.__check.connect('toggled', self.cb_entry_changed) self.__list = ObjectList([ Column('markup', title=_('Man page'), sorted=True, use_markup=True), Column('description', title=_('Description'), use_markup=True), ]) self.__list.connect('double-click', self._on_man_double_click) self.__list.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.__hbox.pack_start(self.__entry) self.__hbox.pack_start(self.__check, expand=False) self.__vbox.pack_start(self.__hbox, expand=False) self.__vbox.pack_start(self.__list) self.add_main_widget(self.__vbox) self.__vbox.show_all() def clear_items(self): self._count = 0 self.__list.clear() def add_item(self, item): self._count += 1 self.__list.append(item) def _on_man_double_click(self, olist, item): commandargs = ['/usr/bin/env', 'man', item.number, item.pattern] directory = os.path.dirname(commandargs[0]) self.svc.boss.cmd('commander', 'execute', commandargs=commandargs, cwd=directory, icon='gnome-library', title='%(pattern)s(%(number)d)' % dict( pattern=item.pattern, number=int(item.number) )) def cb_entry_changed(self, w): options = '-f' if self.__check.get_active(): options = '-k' self.svc.cmd_find(options=options, pattern=self.__entry.get_text()) def can_be_closed(self): self.svc.get_action('show_man').set_active(False) class ManActions(ActionsConfig): def create_actions(self): self.create_action( 'show_man', TYPE_TOGGLE, _('Man Viewer'), _('Show the man'), '', self.on_show_man, 'm', ) def on_show_man(self, action): if action.get_active(): self.svc.show_man() else: self.svc.hide_man() # Service class class Man(Service): """Show manpage of command""" actions_config = ManActions def start(self): self._view = ManView(self) self._has_loaded = False self.task = None def show_man(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._view) if not self._has_loaded: self._has_loaded = True def hide_man(self): self.boss.cmd('window', 'remove_view', view=self._view) def cmd_find(self, options, pattern): # stop and clear task if self.task: self.task.stop() self._view.clear_items() # don't make empty search if len(pattern) <= 0: return # prepare command cmd = '/usr/bin/env man %s "%s"' % (options, pattern) reman = re.compile('[(]([\d]+)[)]') # match and add each line def _line(result): list = reman.findall(result) if not len(list): return name = result.split('(')[0].strip() res = result.split('- ',1) # avoid too much result if self._view._count > 100: self.task.stop() # add in list self._view.add_item(ManItem(name, list[0], res[1], pattern)) # launch man subprocess self.task = GeneratorSubprocessTask(_line) self.task.start(cmd, shell=True) def stop(self): if self.task: self.task.stop() if self.get_action('show_man').get_active(): self.hide_man() # Required Service attribute for service loading Service = Man # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/man/service.pida0000644000175000017500000000032010652670572016001 0ustar aliali[plugin] plugin = man name = Man author = Mathieu Virbel version = 0.2 require_pida = 0.5 depends = "os,gtk,commands,re,cgi" category = documentation description = Search and browse man pagePIDA-0.5.1/pida-plugins/man/test_man.py0000644000175000017500000000222010652670572015667 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/pastebin/0002755000175000017500000000000010652671501014534 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/glade/0002755000175000017500000000000010652671501015610 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/glade/paste-editor.glade0000644000175000017500000003133110652670557021217 0ustar aliali 440 250 True 6 300 400 True 12 True 0 GTK_SHADOW_NONE True True 6 4 2 6 12 True 1 Name: True 1 1 2 GTK_FILL GTK_FILL True 1 Location: 0 2 3 GTK_FILL True * 1 2 True * 1 2 1 2 GTK_FILL True 1 Title: True 0 GTK_FILL GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 2 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Syntax: 3 4 GTK_FILL GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 3 4 True <b>Snippet Properties</b> True 0 label_item False False True 0 GTK_SHADOW_NONE True True 6 GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_OUT True True <b>Snippet Text</b> True 0 label_item 1 True 6 GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-cancel True True gtk-clear True 1 True gtk-paste True 2 False False 3 PIDA-0.5.1/pida-plugins/pastebin/locale/0002755000175000017500000000000010652671501015773 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/locale/fr_FR/0002755000175000017500000000000010652671501016771 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020556 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/locale/fr_FR/LC_MESSAGES/pastebin.po0000644000175000017500000000277110652670557022742 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/paste-editor.glade.h:1 msgid "Snippet Properties" msgstr "Propriété de la sélection" #: glade/paste-editor.glade.h:2 msgid "Snippet Text" msgstr "Sélection de code" #: glade/paste-editor.glade.h:3 msgid "Location:" msgstr "Adresse:" #: glade/paste-editor.glade.h:4 msgid "Name:" msgstr "Nom:" #: glade/paste-editor.glade.h:5 msgid "Syntax:" msgstr "Syntaxe:" #: glade/paste-editor.glade.h:6 msgid "Title:" msgstr "Titre" #: pastebin.py:110 msgid "Paste" msgstr "Pastebin" #: pastebin.py:151 msgid "Paste Editor" msgstr "Editeur Pastebin" #: pastebin.py:179 pastebin.py:316 msgid "Paste History" msgstr "Historique de Pastebin" #: pastebin.py:234 pastebin.py:243 msgid "ERROR: No paste selected" msgstr "Erreur: Aucune séléction" #: pastebin.py:307 msgid "Upload Text Snippet" msgstr "Envoyer une sélection de code" #: pastebin.py:308 msgid "Upload a text snippet to a pastebin" msgstr "Envoyer une sélection de code sur un Pastebin" #: pastebin.py:317 msgid "Show the paste history viewer" msgstr "Afficher l'historique des envois" PIDA-0.5.1/pida-plugins/pastebin/uidef/0002755000175000017500000000000010652671501015630 5ustar alialiPIDA-0.5.1/pida-plugins/pastebin/uidef/pastebin.xml0000644000175000017500000000277610652670557020203 0ustar aliali PIDA-0.5.1/pida-plugins/pastebin/__init__.py0000644000175000017500000000222010652670557016651 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/pastebin/pastebin.py0000644000175000017500000003225310652670557016730 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import gtk, gobject from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView, PidaView from pida.utils.web import fetch_url # locale from pida.core.locale import Locale locale = Locale('pastebin') _ = locale.gettext class Bin(object): PASTE_URL = None def __init__(self, svc): self.svc = svc def create_data_dict(self, title, name, content, syntax): """Override in individual pastebins""" @classmethod def get_syntax_items(cls): """Override to return a list of syntax item tuples (lable, value)""" def post(self, *args): self.args = args fetch_url(self.PASTE_URL, self.on_posted, self.create_data_dict(*args)) def on_posted(self, url, content): self.svc.new_paste_complete(url, *self.args) class Dpaste(Bin): PASTE_URL = 'http://dpaste.com/' def create_data_dict(self, title, name, content, syntax): return dict( poster = name, title = title, content = content, language = syntax, ) @classmethod def get_syntax_items(cls): return [ ('Python', 'Python'), ('Python Interactive / Traceback', 'PythonConsole'), ('SQL', 'Sql'), ('HTML / Django Template', 'DjangoTemplate'), ('JavaScript', 'JScript'), ('CSS', 'Css'), ('XML', 'Xml'), ('Diff', 'Diff'), ('Ruby', 'Ruby'), ('Ruby HTML (ERB)', 'Rhtml'), ('Haskell', 'Haskell'), ('Apache Configuration', 'Apache'), ('Bash Script', 'Bash'), ('Plain Text', ''), ] class Rafb(Bin): PASTE_URL = 'http://www.rafb.net/paste/paste.php' def create_data_dict(self, title, name, content, syntax): return dict( text=content, nick=name, desc=title, lang=syntax, cvt_tabs=4, submit=_('Paste') ) @classmethod def get_syntax_items(cls): return [ ('C89', 'C89'), ('C', 'C'), ('C++', 'C++'), ('C#', 'C#'), ('Java', 'Java'), ('Pascal', 'Pascal'), ('Perl', 'Perl'), ('PHP', 'PHP'), ('PL/I', 'PL/I'), ('Python', 'Python'), ('Ruby', 'Ruby'), ('SQL', 'SQL'), ('VB', 'VB'), ('Plain Text', 'Plain Text') ] class Twisted(Bin): PASTE_URL = 'http://deadbeefbabe.org/paste/freeform_post!!addPasting' def create_data_dict(self, title, name, content, syntax): return dict( author=name, text=content, addPasting='addPasting', _charset_='', ) @classmethod def get_syntax_items(cls): return [('Python', '')] class PastebinEditorView(PidaGladeView): gladefile = 'paste-editor' locale = locale label_text = _('Paste Editor') icon_name = gtk.STOCK_PASTE def create_ui(self): self.paste_location.prefill(self.svc.get_pastebin_types()) def on_paste_location__content_changed(self, cmb): self.paste_syntax.prefill(self.paste_location.read().get_syntax_items()) def on_post_button__clicked(self, button): paste_type = self.paste_location.read() self.svc.commence_paste(paste_type, *self.read_values()) def on_cancel_button__clicked(self, button): self.svc.cancel_paste() def read_values(self): return (self.paste_title.get_text(), self.paste_name.get_text(), self.paste_content.get_buffer().get_text( self.paste_content.get_buffer().get_start_iter(), self.paste_content.get_buffer().get_end_iter(), ), self.paste_syntax.read(), ) def can_be_closed(self): self.svc.cancel_paste() class PasteHistoryView(PidaView): label_text = _('Paste History') icon_name = gtk.STOCK_PASTE #glade_file_name = 'paste-history.glade' def create_ui(self): self.__history_tree = ObjectList( [Column('markup', use_markup=True, expand=True)]) self.__history_tree.set_headers_visible(False) self.add_main_widget(self.__history_tree) self.__x11_clipboard = gtk.Clipboard(selection="PRIMARY") self.__gnome_clipboard = gtk.Clipboard(selection="CLIPBOARD") self.__tree_selected = None #self.__history_tree.connect('selection-changed', self.cb_paste_clicked) self.__history_tree.connect('double-click', self.cb_paste_db_clicked) #self.__history_tree.connect('middle-clicked', self.cb_paste_m_clicked) self.__history_tree.connect('right-click', self.on_paste_rclick) self.__pulse_bar = gtk.ProgressBar() self.add_main_widget(self.__pulse_bar, expand=False) self.__pulse_bar.show_all() self.__pulse_bar.set_size_request(-1, 12) self.__pulse_bar.set_pulse_step(0.01) self.__history_tree.show_all() def set(self, pastes): '''Sets the paste list to the tree view. First reset it, then rebuild it. ''' self.__history_tree.clear() for paste in pastes: self.__history_tree.append(paste) self.__tree_selected = None def add_paste(self, item): self.__history_tree.append(item) #def on_add__clicked(self, but): # '''Callback function bound to the toolbar button new that creates a new # paste to post''' # self.service.boss.call_command('pastemanager','create_paste') def copy_current_paste(self): '''Callback function bound to the toolbar button view that copies the selected paste''' if self.__tree_selected != None: self.__x11_clipboard.set_text(self.__tree_selected.get_url()) self.__gnome_clipboard.set_text(self.__tree_selected.get_url()) def view_current_paste(self): '''Callback function bound to the toolbar button view that shows the selected paste''' if self.__tree_selected != None: self.service.boss.call_command('pastemanager','view_paste', paste=self.__tree_selected) else: print _("ERROR: No paste selected") def remove_current_paste(self): '''Callback function bound to the toolbar button delete that removes the selected paste''' if self.__tree_selected != None: self.service.boss.call_command('pastemanager','delete_paste', paste=self.__tree_selected) else: print _("ERROR: No paste selected") def cb_paste_clicked(self,paste,tree_item): '''Callback function called when an item is selected in the TreeView''' self.__tree_selected = tree_item.value def cb_paste_db_clicked(self, ol, item): """ Callback function called when an item is double clicked, and copy it to the gnome/gtk clipboard """ if item is not None: self.svc.boss.cmd('webbrowser', 'browse', url=item.url) # self.__gnome_clipboard.set_text(self.__tree_selected.get_url()) # aa: view the paste def cb_paste_m_clicked(self,paste,tree_item): '''Callback function called when an item is middle clicked, and copy it to the mouse buffer clipboard''' if self.__tree_selected != None: self.__x11_clipboard.set_text(self.__tree_selected.get_url()) def cb_paste_r_clicked(self, paste, tree_item, event): menu = gtk.Menu() sensitives = (tree_item is not None) for action in ['pastemanager+new_paste', None, 'pastemanager+remove_paste', 'pastemanager+view_paste', None, 'pastemanager+copy_url_to_clipboard']: if action is None: menu.append(gtk.SeparatorMenuItem()) else: act = self.service.action_group.get_action(action) if 'new_paste' not in action: act.set_sensitive(sensitives) mi = gtk.ImageMenuItem() act.connect_proxy(mi) mi.show() menu.append(mi) menu.show_all() menu.popup(None, None, None, event.button, event.time) def on_paste_rclick(self, ol, item, event): self.svc.boss.cmd('contexts', 'popup_menu', context='url-menu', url=item.url, event=event) def start_pulse(self): '''Starts the pulse''' self._pulsing = True gobject.timeout_add(100, self._pulse) def stop_pulse(self): self._pulsing = False def _pulse(self): self.__pulse_bar.pulse() return self._pulsing def can_be_closed(self): self.svc.get_action('show_pastes').set_active(False) class PastebinActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'new_paste', TYPE_NORMAL, _('Upload Text Snippet'), _('Upload a text snippet to a pastebin'), gtk.STOCK_PASTE, self.on_new_paste, ) self.create_action( 'show_pastes', TYPE_TOGGLE, _('Paste History'), _('Show the paste history viewer'), gtk.STOCK_PASTE, self.on_show_pastes, '0', ) def on_new_paste(self, action): self.svc.new_paste() def on_show_pastes(self, action): if action.get_active(): self.svc.show_pastes() else: self.svc.hide_pastes() class PasteItem(object): def __init__(self, url, *args): self.url = url self.title, self.name, self.content, self.syntax = args def get_markup(self): return ('%s (%s)\n%s' % (self.title, self.syntax, self.url)) markup = property(get_markup) # Service class class Pastebin(Service): """Describe your Service Here""" actions_config = PastebinActionsConfig def pre_start(self): self._editor = PastebinEditorView(self) self._view = PasteHistoryView(self) def new_paste(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._editor) self.get_action('new_paste').set_sensitive(False) def show_pastes(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_pastes(self): self.boss.cmd('window', 'remove_view', view=self._view) def commence_paste(self, paste_type, *args): p = paste_type(self) p.post(*args) self.ensure_view_visible() self._view.start_pulse() self._close_paste_editor() def cancel_paste(self): self._close_paste_editor() def _close_paste_editor(self): self.boss.cmd('window', 'remove_view', view=self._editor) self.get_action('new_paste').set_sensitive(True) def new_paste_complete(self, url, *args): self._view.stop_pulse() self._view.add_paste(PasteItem(url, *args)) self.ensure_view_visible() def ensure_view_visible(self): act = self.get_action('show_pastes') if not act.get_active(): act.set_active(True) def get_pastebin_types(self): return [ ('DPaste', Dpaste), ('Rafb.net', Rafb), #('Twisted', Twisted), #Broken for some reason ] def stop(self): if not self.get_action('new_paste').get_sensitive(): self._close_paste_editor() if self.get_action('show_pastes').get_active(): self.hide_pastes() # Required Service attribute for service loading Service = Pastebin # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/pastebin/service.pida0000644000175000017500000000031410652670557017041 0ustar aliali[plugin] plugin = pastebin name = Pastebin author = Ali Afshar version = 0.2.2 require_pida = 0.5 depends = "gtk, gobject" category = code description = Send code to a pastebin servicePIDA-0.5.1/pida-plugins/pastebin/test_pastebin.py0000644000175000017500000000222010652670557017756 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python/0002755000175000017500000000000010652671501014250 5ustar alialiPIDA-0.5.1/pida-plugins/python/glade/0002755000175000017500000000000010652671501015324 5ustar alialiPIDA-0.5.1/pida-plugins/python/glade/python-source-browser.glade0000644000175000017500000000261610652670547022636 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN PIDA-0.5.1/pida-plugins/python/locale/0002755000175000017500000000000010652671501015507 5ustar alialiPIDA-0.5.1/pida-plugins/python/locale/fr_FR/0002755000175000017500000000000010652671501016505 5ustar alialiPIDA-0.5.1/pida-plugins/python/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501020272 5ustar alialiPIDA-0.5.1/pida-plugins/python/locale/fr_FR/LC_MESSAGES/python.po0000644000175000017500000000447110652670546022167 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: python.py:62 msgid "Python Errors" msgstr "Erreurs Python" #: python.py:76 python.py:176 msgid "Line Number" msgstr "Numéro de ligne" #: python.py:77 msgid "Message" msgstr "Message" #: python.py:78 python.py:178 msgid "Type" msgstr "Type" #: python.py:162 msgid "Source" msgstr "Source" #: python.py:177 msgid "Name" msgstr "Nom" #: python.py:235 msgid "Python Executable" msgstr "Executable Python" #: python.py:246 msgid "Python Controller" msgstr "Contrôleur Python" #: python.py:249 msgid "File to execute" msgstr "Fichier à exécuter" #: python.py:250 msgid "Args to execute" msgstr "Arguments à exécuter" #: python.py:257 msgid "Controller has no \"execute_file\" set" msgstr "Le contrôleur n'a pas d'\"execute_file\" défini" #: python.py:272 msgid "Distutils Controller" msgstr "Contrôleur Distutils" #: python.py:275 msgid "Distutils command" msgstr "Commande Distutils" #: python.py:276 msgid "Args for command" msgstr "Arguments de la commande" #: python.py:282 msgid "Controller has no \"command\" set" msgstr "Le contrôleur n'a pas de \"command\" définie" #: python.py:312 msgid "Python Executable for executing" msgstr "Exécutable Python à exécuter" #: python.py:315 msgid "The Python executable when executing a module" msgstr "Exécutable Python quand le module est exécuté" #: python.py:334 msgid "Execute Python Module" msgstr "Exécuter le module Python" #: python.py:335 msgid "Execute the current Python module in a shell" msgstr "Exécuter le module Python courant dans un shell" #: python.py:343 msgid "Python Error Viewer" msgstr "Visualiseur d'erreurs Python" #: python.py:344 msgid "Show the python error browser" msgstr "Afficher le navigateur d'erreurs Python" #: python.py:352 msgid "Python Source Viewer" msgstr "Visualiseur de sources Python" #: python.py:353 msgid "Show the python source browser" msgstr "Afficher le visualiseur de sources Python" PIDA-0.5.1/pida-plugins/python/pixmaps/0002755000175000017500000000000010652671501015731 5ustar alialiPIDA-0.5.1/pida-plugins/python/pixmaps/python-icon.png0000644000175000017500000000265710652670546020726 0ustar alialiPNG  IHDR..W+7sBIT|dfIDATh]hW3;M1d[-UShBE-} BC_*>Y"-(`Kb%4P)~ H,ZkZ(ZE IlB$ۇ;wc%?\vv~̹眝!X6,x Ύ ɼTh4rjúWr˱4 =!1TAȆf }+g!óA(CC]wo03Oę۰kcskq!e50 sHcW sϖGѱ6ڑ-D\bY,ӣ )E{>[** Nz`O.PcgW"65 - #~> 0?-1 <ŜQ`Y@ ّӟnajN h.쳾(W(@@N]=8hG.(PG{P@!+@c\9:Qf5o- awCT/݅ceCL ƦtDk-B%Ν BYU2 6=(K{ ܟ%`u fl4+ qRC_1 Q j` h:RsRrs R\$0& DxWL8kңwtaSx4n1 |^%7b\cUG; 8?cExss+?[GHTF!4sz\AOk[(O"M!M ~aPlDYZxMJǼ }~ݷ+u?AZ:ppTIހ\ ZhM ,-0l` R8'^>it2t1 kVSPh0k6ǃosB{Pt* 2,ˮ{SWU\5D9ISt&e ZP|MB Tv_Pw9J-@od׷p`JBJbQ.ZBwqdž$']Vm6aƤȡ+s:OV, 6p.WP~/ w{A1 R_nF |ϰ L7Wk悯*ן}Q_&1j |+0[17AlZLe- `,_"LtU jE%)O!X&aY+xeu)5Ǖ&#ښq738_)h/Չz#+/Bz3p Iq̹LHAC~bodF,U0u3?w2x}cY'Pܢ$EZ__ŠWgBIENDB`PIDA-0.5.1/pida-plugins/python/uidef/0002755000175000017500000000000010652671501015344 5ustar alialiPIDA-0.5.1/pida-plugins/python/uidef/python.xml0000644000175000017500000000255210652670547017422 0ustar aliali PIDA-0.5.1/pida-plugins/python/__init__.py0000644000175000017500000000222010652670547016364 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python/python.py0000644000175000017500000003351010652670547016154 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # stdlib import sys, compiler # gtk import gtk # kiwi from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports # core from pida.core.service import Service from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig, TYPE_NORMAL, TYPE_TOGGLE from pida.core.options import OptionsConfig, OTypeString from pida.core.features import FeaturesConfig from pida.core.projects import ProjectController, ProjectKeyDefinition from pida.core.interfaces import IProjectController # ui from pida.ui.views import PidaView, PidaGladeView from pida.ui.objectlist import AttrSortCombo # utils from pida.utils import pyflakes from pida.utils import pythonparser from pida.utils.gthreads import AsyncTask, GeneratorTask # locale from pida.core.locale import Locale locale = Locale('python') _ = locale.gettext ### Pyflakes class PyflakeView(PidaView): icon_name = 'python-icon' label_text = _('Python Errors') def create_ui(self): self.errors_ol = ObjectList( Column('markup', use_markup=True) ) self.errors_ol.set_headers_visible(False) self.errors_ol.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.add_main_widget(self.errors_ol) self.errors_ol.connect('double-click', self._on_errors_double_clicked) self.errors_ol.show_all() self.sort_combo = AttrSortCombo( self.errors_ol, [ ('lineno', _('Line Number')), ('message_string', _('Message')), ('name', _('Type')), ], 'lineno', ) self.sort_combo.show() self.add_main_widget(self.sort_combo, expand=False) def clear_items(self): self.errors_ol.clear() def set_items(self, items): self.clear_items() for item in items: self.errors_ol.append(self.decorate_pyflake_message(item)) def decorate_pyflake_message(self, msg): args = [('%s' % arg) for arg in msg.message_args] msg.message_string = msg.message % tuple(args) msg.name = msg.__class__.__name__ msg.markup = ('%s %s\n%s' % (msg.lineno, msg.name, msg.message_string)) return msg def _on_errors_double_clicked(self, ol, item): self.svc.boss.editor.cmd('goto_line', line=item.lineno) def can_be_closed(self): self.svc.get_action('show_python_errors').set_active(False) class Pyflaker(object): def __init__(self, svc): self.svc = svc self._view = PyflakeView(self.svc) self.set_current_document(None) def set_current_document(self, document): self._current = document if self._current is not None: self.refresh_view() self._view.get_toplevel().set_sensitive(True) else: self.set_view_items([]) self._view.get_toplevel().set_sensitive(False) def refresh_view(self): if self.svc.is_current_python(): task = AsyncTask(self.check_current, self.set_view_items) task.start() else: self._view.clear_items() def check_current(self): return self.check(self._current) def check(self, document): code_string = document.string filename = document.filename try: tree = compiler.parse(code_string) except (SyntaxError, IndentationError), e: msg = e msg.name = e.__class__.__name__ value = sys.exc_info()[1] (lineno, offset, line) = value[1][1:] if line.endswith("\n"): line = line[:-1] msg.lineno = lineno msg.message_args = (line,) msg.message = '%%s\n%s^' % (' ' * (offset - 2)) return [msg] else: w = pyflakes.Checker(tree, filename) return w.messages def set_view_items(self, items): self._view.set_items(items) def get_view(self): return self._view class SourceView(PidaGladeView): gladefile = 'python-source-browser' locale = locale icon_name = 'python-icon' label_text = _('Source') def create_ui(self): self.source_tree.set_columns( [ Column('linenumber'), Column('ctype_markup', use_markup=True), Column('nodename_markup', use_markup=True), ] ) self.source_tree.set_headers_visible(False) self.sort_box = AttrSortCombo( self.source_tree, [ ('linenumber', _('Line Number')), ('nodename', _('Name')), ('nodetype', _('Type')), ], 'linenumber' ) self.sort_box.show() self.main_vbox.pack_start(self.sort_box, expand=False) def clear_items(self): self.source_tree.clear() def add_node(self, node, parent): self.source_tree.append(parent, node) def can_be_closed(self): self.svc.get_action('show_python_source').set_active(False) def on_source_tree__double_click(self, tv, item): self.svc.boss.editor.cmd('goto_line', line=item.linenumber) class PythonBrowser(object): def __init__(self, svc): self.svc = svc self._view = SourceView(self.svc) self.set_current_document(None) def set_current_document(self, document): self._current = document if self._current is not None: self.refresh_view() self._view.get_toplevel().set_sensitive(True) else: self._view.clear_items() self._view.get_toplevel().set_sensitive(False) def refresh_view(self): self._view.clear_items() if self.svc.is_current_python(): task = GeneratorTask(self.check_current, self.add_view_node) task.start() def check_current(self): root_node = self.check(self._current) for child, parent in root_node.get_recursive_children(): if parent is root_node: parent = None yield (child, parent) def check(self, document): code_string = document.string return pythonparser.get_nodes_from_string(code_string) def add_view_node(self, node, parent): self._view.add_node(node, parent) def get_view(self): return self._view class BasePythonProjectController(ProjectController): attributes = [ ProjectKeyDefinition('python_executable', _('Python Executable'), False), ] + ProjectController.attributes def get_python_executable(self): return self.get_option('python_executable') or 'python' class PythonProjectController(BasePythonProjectController): name = 'PYTHON_CONTROLLER' label = _('Python Controller') attributes = [ ProjectKeyDefinition('execute_file', _('File to execute'), True), ProjectKeyDefinition('execute_args', _('Args to execute'), False), ] + BasePythonProjectController.attributes def execute(self): execute_file = self.get_option('execute_file') execute_args = self.get_option('execute_args') if not execute_file: self.boss.get_window().error_dlg(_('Controller has no "execute_file" set')) else: commandargs = [self.get_python_executable(), execute_file] if execute_args is not None: commandargs.extend(execute_args.split()) self.execute_commandargs( commandargs, ) class PythonDistutilstoolsController(ProjectController): """Controller for running a distutils command""" name = 'DISTUTILS_CONTROLLER' label = _('Distutils Controller') attributes = [ ProjectKeyDefinition('command', _('Distutils command'), True), ProjectKeyDefinition('args', _('Args for command'), False), ] + BasePythonProjectController.attributes def execute(self): command = self.get_option('command') if not command: self.boss.get_window().error_dlg(_('Controller has no "command" set')) else: commandargs = [self.get_python_executable(), 'setup.py', command] args = self.get_option('args') if args: args = args.split() commandargs.extend(args) self.execute_commandargs( commandargs, ) def get_python_executable(self): return self.get_option('python_executable') or 'python' class PythonFeatures(FeaturesConfig): def subscribe_foreign_features(self): self.subscribe_foreign_feature('project', IProjectController, PythonProjectController) self.subscribe_foreign_feature('project', IProjectController, PythonDistutilstoolsController) class PythonOptionsConfig(OptionsConfig): def create_options(self): self.create_option( 'python_for_executing', _('Python Executable for executing'), OTypeString, 'python', _('The Python executable when executing a module'), ) class PythonEventsConfig(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.on_document_changed) self.subscribe_foreign_event('buffer', 'document-saved', self.on_document_changed) def on_document_changed(self, document): self.svc.set_current_document(document) class PythonActionsConfig(ActionsConfig): def create_actions(self): self.create_action( 'execute_python', TYPE_NORMAL, _('Execute Python Module'), _('Execute the current Python module in a shell'), gtk.STOCK_EXECUTE, self.on_python_execute, ) self.create_action( 'show_python_errors', TYPE_TOGGLE, _('Python Error Viewer'), _('Show the python error browser'), 'error', self.on_show_errors, ) self.create_action( 'show_python_source', TYPE_TOGGLE, _('Python Source Viewer'), _('Show the python source browser'), 'info', self.on_show_source, ) def on_python_execute(self, action): self.svc.execute_current_document() def on_show_errors(self, action): if action.get_active(): self.svc.show_errors() else: self.svc.hide_errors() def on_show_source(self, action): if action.get_active(): self.svc.show_source() else: self.svc.hide_source() # Service class class Python(Service): """Service for all things Python""" events_config = PythonEventsConfig actions_config = PythonActionsConfig options_config = PythonOptionsConfig features_config = PythonFeatures def pre_start(self): """Start the service""" self._current = None self._pyflaker = Pyflaker(self) self._pysource = PythonBrowser(self) self.execute_action = self.get_action('execute_python') self.execute_action.set_sensitive(False) def set_current_document(self, document): self._current = document if self.is_current_python(): self._pyflaker.set_current_document(document) self._pysource.set_current_document(document) self.execute_action.set_sensitive(True) else: self._pyflaker.set_current_document(None) self._pysource.set_current_document(None) self.execute_action.set_sensitive(False) def is_current_python(self): if self._current is not None: return self._current.filename.endswith('.py') else: return False def execute_current_document(self): python_ex = self.opt('python_for_executing') self.boss.cmd('commander', 'execute', commandargs=[python_ex, self._current.filename], cwd = self._current.directory, ) def show_errors(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._pyflaker.get_view()) def hide_errors(self): self.boss.cmd('window', 'remove_view', view=self._pyflaker.get_view()) def show_source(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._pysource.get_view()) def hide_source(self): self.boss.cmd('window', 'remove_view', view=self._pysource.get_view()) def stop(self): if self.get_action('show_python_source').get_active(): self.hide_source() if self.get_action('show_python_errors').get_active(): self.hide_errors() # Required Service attribute for service loading Service = Python # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python/service.pida0000644000175000017500000000034110652670547016554 0ustar aliali[plugin] plugin = python name = Python author = Ali Aafshar version = 0.2.3 require_pida = 0.5 depends = "" category = code description = "Show class/function from python file, and show compilation errors"PIDA-0.5.1/pida-plugins/python/test_python.py0000644000175000017500000000222010652670547017205 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python_debugger/0002755000175000017500000000000010652671501016114 5ustar alialiPIDA-0.5.1/pida-plugins/python_debugger/pixmaps/0002755000175000017500000000000010652671501017575 5ustar alialiPIDA-0.5.1/pida-plugins/python_debugger/pixmaps/break.xpm0000644000175000017500000000066510652670562021422 0ustar alialiPNG  IHDR_hPLTEI+zJtRNS@fbKGDH pHYs  tIME 59EIDATx33333DDDDCDDDDCEUDDCDDDDCDUUUCDDDDC DUUUC!DDDDC DEUTC!DDDDC DUUUC!DDDDC DDDDC33333uN*yIENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/forward.svg0000644000175000017500000000620710652670562021773 0ustar aliali ]> PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-break.png0000644000175000017500000000040210652670562022121 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cd`g`46fj Og$p4]9k F&Hptדk10V ?38=```GcS@ Egc8\&p&C7q `o`d؇pb %bhM l}a+IENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-go.png0000644000175000017500000000036610652670562021453 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8cdf`ٴ5rj Hg$p4]9>oY 21!c5 zr-i85\? ]ZpJ]Ov#gHCA|`b3k[(s1 0D9@fIIENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-goto.png0000644000175000017500000000033610652670562022013 0ustar alialiPNG  IHDRubKGDC pHYs  ~IDAT8A0wߓ'' S-IM5r6dFL?|,U\*TVݹ `W2}<'05rG3@][Rw9k $#z -eHf9W"UIENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-locked.png0000644000175000017500000000034610652670562022305 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8K DGӃԛq31*X$ ٕPZ '7Z`R@2T;<ЀGbOtymN.p YclR>R=<K9f׎_c}R~_*IENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-next.png0000644000175000017500000000035110652670562022016 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Ez:T'CFSEba>p,#φ"@"Ob yoq_LrDD2n gMZ`xTNǚ]? /%}g`mZQ XEkw 0[gAYxIENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-return.png0000644000175000017500000000035410652670562022362 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8Ք Fz:GғIhC'8  2Ju"|#{+"lӂ|42=9Hjz~k\jӴ\Kv-µe7}˲ aK IENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-step.png0000644000175000017500000000035010652670562022012 0ustar alialiPNG  IHDRubKGDC pHYs  IDAT8ՔK DH.+^ZLj͟ kL{=C}oY'ǵ,߱.>IENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-toggle-bp.png0000644000175000017500000000032310652670562022717 0ustar alialiPNG  IHDRubKGD pHYs  sIDAT8A 0y{ Җj7bn%d5m8ǤHʏrHvDuБ<ZEU&')}ZF7!>]1Rqi,=}hyOɛBns{,:IENDB`PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/gdb-unlocked.png0000644000175000017500000000036110652670562022645 0ustar alialiPNG  IHDRubKGD pHYs  IDAT8A w>̼ gԪII(dnHx 8SmS83ER$TkTl(@%jZ |=#k _Ks;QBN2>y~YyyG% image/svg+xml PIDA-0.5.1/pida-plugins/python_debugger/pixmaps/toggle_bp.xpm0000644000175000017500000000027410652670562022274 0ustar alialiPNG  IHDRu pHYs  nIDATx 0C]lci[4њ(HEE\DGB6+EQ%X.ܙ]ehLP6}~Y,s# ^m:sCgy/ PIDA-0.5.1/pida-plugins/python_debugger/uidef/python_debugger.xml0000644000175000017500000000273310652670563023131 0ustar aliali PIDA-0.5.1/pida-plugins/python_debugger/__init__.py0000644000175000017500000000222010652670563020226 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python_debugger/python_debugger.py0000644000175000017500000007217210652670563021671 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # Standard library imports import os, sys, time, Queue, cgi # GTK imports import gtk, gobject # kiwi imports from kiwi.ui.objectlist import ObjectList, ObjectTree, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.core.environment import get_uidef_path, get_pixmap_path from pida.ui.views import PidaView from pida.ui.terminal import PidaTerminal from pida.utils import rpdb2 # locale from pida.core.locale import Locale locale = Locale('python_debugger') _ = locale.gettext # rpdb2 overrides to force PIDA terminal use class SessionManagerInternal(rpdb2.CSessionManagerInternal): def _spawn_server(self, fchdir, ExpandedFilename, args, rid): """ Start an OS console to act as server. What it does is to start rpdb again in a new console in server only mode. """ debugger = os.path.abspath(rpdb2.__file__) if debugger[-1] == 'c': debugger = debugger[:-1] baseargs = ['python', debugger, '--debugee', '--rid=%s' % rid] if fchdir: baseargs.append('--chdir') if self.m_fAllowUnencrypted: baseargs.append('--plaintext') #if self.m_fRemote: # baseargs.append('--remote') if os.name == 'nt': baseargs.append('--pwd=%s' % self.m_pwd) if 'PGD_DEBUG' in os.environ: baseargs.append('--debug') baseargs.append(ExpandedFilename) cmdargs = baseargs + args.split() python_exec = sys.executable self.terminal.fork_command(python_exec, cmdargs) class SessionManager(rpdb2.CSessionManager): def __init__(self, manager, pwd, fAllowUnencrypted, fAllowRemote, host): self.manager = manager smi = self._CSessionManager__smi = SessionManagerInternal( pwd, fAllowUnencrypted, fAllowRemote, host ) smi.terminal = self def _create_view(self): view = Terminal(self.app) self.main_window.attach_slave('outterm_holder', view) return view def fork_command(self, *args, **kw): self.manager.terminal_view.fork_command(*args, **kw) class DebuggerManager(object): """Control the debugging process and views""" def __init__(self, svc): self.svc = svc rpdb2.main(self.start_client) self.last_step = None self.connect_events() sm = self.session_manager self.locals_view = LocalsViewer(sm) self.globals_view = GlobalsViewer(sm) self.stack_view = StackViewer(sm) self.threads_view = ThreadsViewer(sm) self.breaks_view = BreakpointViewer(sm) self.breaks_view.manager = self self.console_view = DebuggerConsole(sm) self.terminal_view = PidaTerminal() def start_client(self, command_line, fAttach, fchdir, pwd, fAllowUnencrypted, fRemote, host): self.session_manager = SessionManager(self, pwd, fAllowUnencrypted, fRemote, host) def launch(self, commandline, change_directory=False): gobject.idle_add(self.session_manager.launch, change_directory, commandline) def connect_events(self): event_type_dict = {rpdb2.CEventState: {}} self.session_manager.register_callback(self.on_update_state, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventStackFrameChange: {}} self.session_manager.register_callback(self.on_update_frame, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventThreads: {}} self.session_manager.register_callback(self.on_update_threads, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventNoThreads: {}} self.session_manager.register_callback(self.on_update_no_threads, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventNamespace: {}} self.session_manager.register_callback(self.on_update_namespace, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventThreadBroken: {}} self.session_manager.register_callback(self.on_update_thread_broken, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventStack: {}} self.session_manager.register_callback(self.on_update_stack, event_type_dict, fSingleUse = False) event_type_dict = {rpdb2.CEventBreakpoint: {}} self.session_manager.register_callback(self.on_update_bp, event_type_dict, fSingleUse = False) def on_update_state(self, event): def update(): self.console_view.write_info(event.m_state + '\n') self.svc.update_state(event.m_state) gobject.idle_add(update) def on_update_frame(self, event): print 'uf', dir(event) gobject.idle_add(self.stack_view.select_frame, event.m_frame_index) self.on_update_source(event.m_frame_index) def on_update_namespace(self, *args): gobject.idle_add(self.locals_view.update_namespace) gobject.idle_add(self.globals_view.update_namespace) def on_update_stack(self, event): self.last_stack = event.m_stack gobject.idle_add(self.stack_view.update_stack, event.m_stack) self.on_update_source(-1) def on_update_source(self, frame_index): def update(): self.remove_last_step_mark() stack_item = self.last_stack['stack'][frame_index] filename, linenumber, level, code = stack_item self.svc.boss.cmd('buffer', 'open_file', file_name=filename) self.svc.boss.editor.cmd('goto_line', line=linenumber) self.svc.boss.editor.cmd('show_sign', type='step', file_name=filename, line=linenumber) self.last_step = filename, linenumber stack_item = self.last_stack['stack'][frame_index] if 'rpdb2.py' not in stack_item[0]: gobject.idle_add(update) def remove_last_step_mark(self): if self.last_step is not None: lfile, lline = self.last_step self.svc.boss.editor.cmd('hide_sign', type='step', file_name=lfile, line=lline) self.last_step = None def on_update_bp(self, event): def _u(event): act = event.m_action if event.m_bp is not None: filename = event.m_bp.m_filename linenumber = event.m_bp.m_lineno index = event.m_bp.m_id indices = None else: filename = None linenumber = None index = None indices = event.m_id_list self.breaks_view.update_bp(act, index, indices, filename, linenumber) #self.master.update_bp(act, index, indices, filename, linenumber) gobject.idle_add(_u, event) def on_update_no_threads(self, *args): print 'unt', args def on_update_threads(self, event): gobject.idle_add(self.threads_view.update_threads, event.m_thread_list, event.m_current_thread) def on_update_thread_broken(self, *args): print 'utb', args def set_breakpoint(self, index, filename, linenumber): print 'set', index, filename, linenumber self.svc.boss.editor.cmd('show_sign', type='breakpoint', file_name=filename, line=linenumber) def remove_breakpoint(self, index, filename, linenumber): print 'remove', index, filename, linenumber self.svc.boss.editor.cmd('hide_sign', type='breakpoint', file_name=filename, line=linenumber) # Views class DebuggerConsole(gtk.VBox): def __init__(self, sm): gtk.VBox.__init__(self) self.sm = sm self.console = rpdb2.CConsole(sm, self, self, True) self.console.start() self._queue = Queue.Queue() self._text = gtk.TextView() sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self._text) self._buffer = self._text.get_buffer() self._buffer.create_tag('mono', family='Monospace') self._buffer.create_tag('bold', weight=800) self.pack_start(sw) self._entry = gtk.Entry() self.pack_start(self._entry, expand=False) self._entry.connect('activate', self.on_entry__activate) def write_text(self, data, *tags): self._buffer.insert_with_tags_by_name( self._buffer.get_end_iter(), data, 'mono', *tags) self._text.scroll_to_iter(self._buffer.get_end_iter(), 0.1) def write_info(self, data): gobject.idle_add(self.write_text, '[S] ' + data, 'bold') def write_from(self, data): gobject.idle_add(self.write_text, data) def write_to(self, data): if len(data) > 1: data = '>>> ' + data gobject.idle_add(self.write_text, data, 'bold') def write(self, data): self.write_from(data) def flush(self): pass def readline(self): return self._queue.get() def on_entry__activate(self, entry): data = self._entry.get_text() + '\n' self._entry.set_text('') self.write_to(data) self._queue.put(data) nochildren = ['NoneType', 'str', 'int', 'float', 'long', 'bool'] reprable = nochildren + ['dict', 'list', 'tuple'] class NamespaceItem(object): def __init__(self, nsdict): self.name = nsdict['name'] self.stype = nsdict['type'] self.srepr = nsdict['repr'] self.expr = nsdict['expr'] self.n_subnodes = nsdict['n_subnodes'] self.key = self.name self.is_value = False self.expanded = False def get_markup(self): if self.is_value: self.name = '.' mu = cgi.escape(self.srepr) else: n = cgi.escape(self.name) t = cgi.escape(self.stype) mu = ('%s ' '%s' % (n, t)) if self.stype in reprable: v = ' %s' % cgi.escape(self.srepr) mu = ''.join([mu, v]) return mu markup = property(get_markup) def get_pixbuf(self): if self.is_value: return None return get_pixbuf(self.stype) pixbuf = property(get_pixbuf) class NamespaceViewer(gtk.VBox): def __init__(self, sm): gtk.VBox.__init__(self) self.session_manager = sm self._tree = ObjectTree( [ Column('markup', use_markup=True) ] ) self._tree.set_headers_visible(False) self.pack_start(self._tree) self._tree.connect('row-expanded', self.on_tree__row_expanded) def update_namespace(self, expr=None, parent=None): if expr is None: expr = self.get_root_expr() parent = None self._tree.clear() el = [(expr, True)] filt = None ns = self.session_manager.get_namespace(el, filt) for sn in ns[0]['subnodes']: item = NamespaceItem(sn) piter = self._tree.append(parent, item) if item.stype not in nochildren: valitem = NamespaceItem(sn) valitem.is_value = True self._tree.append(item, valitem) def on_tree__row_expanded(self, tv, item): if not item.expanded: item.expanded = True self.update_namespace(item.expr, item) def get_root_expr(self): raise NotImplementedError class GlobalsViewer(NamespaceViewer): def get_root_expr(self): return 'globals()' class LocalsViewer(NamespaceViewer): def get_root_expr(self): return 'locals()' class StackItem(object): def __init__(self, index, filename, linenumber, function, line): self.key = index self.filename = filename self.basename = os.path.basename(filename) self.dirname = os.path.dirname(filename) self.linenumber = linenumber self.function = function self.line = line self.active=False def get_markup(self): return ('' '%(basename)s:%(linenumber)s ' '%(dirname)s\n' '%(line)s' % dict(color=self.color, basename=self.basename, linenumber=self.linenumber, dirname=self.dirname, line=self.line)) markup = property(get_markup) def get_color(self): if self.active: return '#000000' else: return '#909090' color = property(get_color) def get_icon(self): if self.active: return None#icons.get(gtk.STOCK_EXECUTE, 16) else: return None pixbuf = property(get_icon) class StackViewer(gtk.VBox): def __init__(self, sm): self.session_manager = sm gtk.VBox.__init__(self) self.tree = ObjectList( [ Column('markup', use_markup=True) ] ) self.tree.set_headers_visible(False) self.pack_start(self.tree) self.tree.connect('double-click', self.on_tree__double_clicked) def update_stack(self, stack): self._current_tid = stack['current tid'] self.tree.clear() stack_item = None for i, row in enumerate(stack['stack'][3:]): fn, ln, fc, tl = row stack_item = StackItem(i, fn, ln, fc, tl) self.tree.insert(0, stack_item) if stack_item is not None: stack_item.active = True self.tree.update(stack_item) def select_frame(self, index): for item in self.tree: item.active = (item.key == index) self.tree.update(item) def on_tree__double_clicked(self, tv, item): index = item.key self.session_manager.set_frame_index(index) class ThreadItem(object): def __init__(self, tdict): self.tid = tdict[rpdb2.DICT_KEY_TID] self.broken = tdict[rpdb2.DICT_KEY_BROKEN] self.is_current = False self.key = self.tid def get_broken_text(self): if self.broken: return 'broken' else: return 'running' state = property(get_broken_text) def get_pixbuf(self): return None pixbuf = property(get_pixbuf) def get_markup(self): return ('%(tid)s %(state)s' % dict(tid=self.tid, state=self.state) ) markup = property(get_markup) class ThreadsViewer(gtk.VBox): def __init__(self, sm): gtk.VBox.__init__(self) self.sesion_manager = sm self.tree = ObjectList( [ Column('markup', use_markup=True), ] ) self.pack_start(self.tree) self.tree.set_headers_visible(False) def update_threads(self, threads_list, current_thread): self.tree.clear() for tdict in threads_list: item = ThreadItem(tdict) if item.tid == current_thread: item.is_current = True self.tree.append(item) def broken_thread(self, tid): for item in self.tree: if item.tid == tid: item.broken = True self.tree.update(item) class Breakpoint(object): def __init__(self, index, filename, linenumber): self.key = index self.filename = filename self.linenumber = linenumber self.enabled = True def get_color(self): if self.enabled: return '#000000' else: return '#a0a0a0' def get_disabled_text(self): if self.enabled: return '' else: return '(disabled)' disabled_text = property(get_disabled_text) color = property(get_color) def get_markup(self): return ('[%(key)s]' '' ' %(filename)s:%(linenumber)s ' '%(disabled_text)s') % dict( key=self.key, filename=self.filename, disabled_text=self.disabled_text, color=self.color, linenumber=self.linenumber, ) markup = property(get_markup) class BreakpointViewer(gtk.VBox): def __init__(self, sm): self.session_manager = sm gtk.VBox.__init__(self) self.tree = ObjectList( [ Column('markup', use_markup=True), ] ) self.tree.set_headers_visible(False) self.pack_start(self.tree) #self._create_actions() def update_bp(self, action, index, indices, filename, linenumber): if action == 'set': gen = self._get_all_bps([index]) try: val = gen.next() val.filename = filename val.linenumber = linenumber self.tree.update(val) except StopIteration: bp = Breakpoint(index, filename, linenumber) self.tree.append(bp) self.manager.set_breakpoint(index, filename, linenumber) elif action == 'remove': for item in self._get_all_index_rows(indices): filename = item.filename self.manager.remove_breakpoint(item.key, item.filename, item.linenumber) self.tree.remove(item) elif action == 'disable': for item in self._get_all_bps(indices): item.enabled = False self.tree.update(item) elif action == 'enable': for item in self._get_all_bps(indices): item.enabled = True self.tree.update(item) def _create_actions(self): self._current = None self.add_widget('dis_act', gtk.Action('Disable', 'Disable', 'Disable this breakpoint', gtk.STOCK_NO)) self.add_widget('en_act', gtk.Action('Enable', 'Enable', 'Enable this breakpoint', gtk.STOCK_YES)) def _create_popup(self, bp, event): self._current = bp if not bp: return menu = gtk.Menu() mi = self.dis_act.create_menu_item() menu.add(mi) self.dis_act.set_sensitive(bp.enabled) mi = self.en_act.create_menu_item() menu.add(mi) self.en_act.set_sensitive(not bp.enabled) menu.show_all() menu.popup(None, None, None, event.button, event.time) def _get_all_index_rows(self, indices): for index in indices: for item in self.tree: if item.key == index: yield item def _get_all_bps(self, indices): for item in self._get_all_index_rows(indices): yield item def _set_breakpoint_enabled_status(self, bp, enabled): if not enabled: func = self.session_manager.disable_breakpoint else: func = self.session_manager.enable_breakpoint gobject.idle_add(func, [bp.key], False) def on_tree__double_clicked(self, tv, item): if item: val = item.value self._set_breakpoint_enabled_status(val, not val.enabled) def on_tree__right_clicked(self, tv, item, event): if item is None: val = item else: val = item.value self._create_popup(val, event) def on_dis_act__activate(self, action): print 'disabled' self._set_breakpoint_enabled_status(self._current, False) def on_en_act__activate(self, action): self._set_breakpoint_enabled_status(self._current, True) # PIDA View class PythonDebuggerView(PidaView): def create_ui(self): self.manager = DebuggerManager(self.svc) self.create_toolbar() hp = gtk.HPaned() nb1 = gtk.Notebook() nb1.append_page(self.manager.console_view, gtk.Label('Console')) nb1.append_page(self.manager.terminal_view, gtk.Label('Execution Output')) nb1.append_page(self.manager.breaks_view, gtk.Label('Break Points')) hp.pack1(nb1) hb = gtk.HBox() nb2 = gtk.Notebook() nb2.append_page(self.manager.stack_view, gtk.Label('Stack')) nb2.append_page(self.manager.threads_view, gtk.Label('Threads')) nb2.append_page(self.manager.locals_view, gtk.Label('Locals')) nb2.append_page(self.manager.globals_view, gtk.Label('Globals')) hb.pack_start(nb2) hb.pack_start(self._toolbar, expand=False) hp.pack2(hb) self.add_main_widget(hp) hp.show_all() def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('python-debugger-toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) self._toolbar.set_orientation(gtk.ORIENTATION_VERTICAL) self._toolbar.show_all() # Service Configuration # Actions class DebuggerActionsConfig(ActionsConfig): def create_actions(self): # Menu self.create_action( 'show_pydebugger_view', TYPE_TOGGLE, 'Python Debugger', 'Show the Python debugger', 'accessories-text-editor', self.on_show_debugger_view, 'b', ) #self.create_action( # 'show_stack_view', # TYPE_TOGGLE, # "Debugger's stack view", # 'Show the stack of current debugger', # 'accessories-text-editor', # self.on_show_stack_view, # 's', #) # Toolbar self.create_action( 'debug_start', TYPE_NORMAL, 'Continue', 'Start debugger or Continue debbuging', 'gdb-go', self.on_start, '', ) #self.create_action( # 'debug_stop', # TYPE_NORMAL, # 'Break', # 'Stop debbuging', # 'gdb-break', # self.on_stop, # '', #) self.create_action( 'debug_next', TYPE_NORMAL, 'Next', 'Step to the next statement', 'gdb-next', self.on_step_over, '', ) self.create_action( 'debug_step', TYPE_NORMAL, 'Step', 'Step into highlighted statement', 'gdb-step', self.on_step_in, '', ) self.create_action( 'debug_return', TYPE_NORMAL, 'Finish function', 'Step until end of current function', 'gdb-return', self.on_return, '', ) self.create_action( 'debug_break', TYPE_NORMAL, 'Break', 'Breakpoint the execution', 'gdb-break', self.on_break, '', ) # Buttonbar def on_step_over(self, action): self.svc._view.manager.session_manager.request_next() def on_step_in(self, action): self.svc._view.manager.session_manager.request_step() def on_start(self, action): self.svc._view.manager.session_manager.request_go() def on_stop(self, action): self.svc._view.manager.session_manager.stop_debuggee() def on_return(self, action): self.svc._view.manager.session_manager.request_return() # Menu def on_show_debugger_view(self, action): if action.get_active(): self.svc.show_debugger_view() else: self.svc.boss.cmd('window', 'remove_view', view=self.svc._breakpoints_view) def on_show_stack_view(self, action): if not self.svc._stack_view: self.svc._stack_view = DebuggerStackView(self.svc) if action.get_active(): self.svc.boss.cmd('window', 'add_view', paned='Terminal', view=self.svc._stack_view) else: self.svc.boss.cmd('window', 'remove_view', view=self.svc._stack_view) # def on_break(self, action): self.svc._view.manager.session_manager.request_break() class DebuggerCommands(CommandsConfig): def launch(self, command_line, change_directory=False): self.svc.launch(command_line, change_directory) class DebuggerEventsConfig(EventsConfig): def subscribe_foreign_events(self): #self.subscribe_foreign_event('buffer', 'document-changed', # self.on_document_changed) self.subscribe_foreign_event('editor', 'started', self.on_editor_startup) def on_editor_startup(self): """ Set the highlights in vim """ self.svc.boss.editor.cmd('define_sign_type', type="breakpoint", icon=get_pixmap_path("stop.svg"), linehl="", text="X", texthl="Search") self.svc.boss.editor.cmd('define_sign_type', type="step", icon=get_pixmap_path("forward.svg"), linehl="lCursor", text=">", texthl="lCursor") def on_document_changed(self, document): if document is not None: self.svc.get_action('debug_toggle_breakpoint').set_sensitive(True) self.svc.update_editor(document) else: self.svc.get_action('debug_toggle_breakpoint').set_sensitive(False) # Service class class Python_debugger(Service): """Describe your Service Here""" actions_config = DebuggerActionsConfig commands_config = DebuggerCommands events_config = DebuggerEventsConfig def start(self): self._view = PythonDebuggerView(self) self.set_all_actions_insensitive() def show_debugger_view(self): self.boss.cmd('window', 'add_view', paned='Terminal', view=self._view) def hide_debugger_view(self): self.boss.cmd('window', 'remove_view', view=self._view) def ensure_view_visible(self): if not self.get_action('show_pydebugger_view').get_active(): self.get_action('show_pydebugger_view').set_active(True) self.boss.cmd('window', 'present_view', view=self._view) def launch(self, command_line, change_directory): self.ensure_view_visible() self._view.manager.launch(command_line, change_directory) def update_state(self, state): if state == 'broken': self.set_broken_actions() elif state == 'running': self.set_running_actions() elif state == 'detached': self.set_all_actions_insensitive() self._view.manager.remove_last_step_mark() else: self.set_all_actions_insensitive() def set_all_actions_insensitive(self): for actname in ['debug_start', 'debug_next', 'debug_break', 'debug_step', 'debug_return']: self.get_action(actname).set_sensitive(False) def set_running_actions(self): self.set_all_actions_insensitive() for actname in ['debug_break']: self.get_action(actname).set_sensitive(True) def set_broken_actions(self): self.set_all_actions_insensitive() for actname in ['debug_start', 'debug_next', 'debug_step', 'debug_return']: self.get_action(actname).set_sensitive(True) def stop(self): try: self._view.manager.session_manager.stop_debuggee() except: pass # Required Service attribute for service loading Service = Python_debugger # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/python_debugger/service.pida0000644000175000017500000000034510652670563020422 0ustar aliali[plugin] plugin = python_debugger name = Python Debugger author = Ali Afshar version = 0.1 require_pida = 0.5.1 depends = "" category = code description = "Python Debugger based on RPDB2 the WinPDB Back End" PIDA-0.5.1/pida-plugins/python_debugger/test_python_debugger.py0000644000175000017500000000222010652670563022713 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/rfc/0002755000175000017500000000000010652671501013501 5ustar alialiPIDA-0.5.1/pida-plugins/rfc/locale/0002755000175000017500000000000010652671501014740 5ustar alialiPIDA-0.5.1/pida-plugins/rfc/locale/fr_FR/0002755000175000017500000000000010652671501015736 5ustar alialiPIDA-0.5.1/pida-plugins/rfc/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501017523 5ustar alialiPIDA-0.5.1/pida-plugins/rfc/locale/fr_FR/LC_MESSAGES/rfc.po0000644000175000017500000000156310652670570020645 0ustar aliali# PIDA. # Copyright (C) The PIDA Team # This file is distributed under the same license as the PIDA package. # Mathieu Virbel , 2007. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-08 22:32+0200\n" "PO-Revision-Date: 2007-05-08 22:32+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: rfc.py:80 msgid "Number" msgstr "Numéro" #: rfc.py:82 msgid "Description" msgstr "Description" #: rfc.py:91 rfc.py:141 rfc.py:142 msgid "Download RFC Index" msgstr "Télécharger l'index RFC" #: rfc.py:121 msgid "Rfc Viewer" msgstr "Visualiseur de RFC" #: rfc.py:122 msgid "Show the rfc" msgstr "Afficher la RFC" #: rfc.py:131 rfc.py:132 msgid "Refresh RFC Index" msgstr "Actualise la liste des RFC" PIDA-0.5.1/pida-plugins/rfc/uidef/0002755000175000017500000000000010652671501014575 5ustar alialiPIDA-0.5.1/pida-plugins/rfc/uidef/rfc-toolbar.xml0000644000175000017500000000026610652670571017541 0ustar aliali PIDA-0.5.1/pida-plugins/rfc/uidef/rfc.xml0000644000175000017500000000234010652670571016074 0ustar aliali PIDA-0.5.1/pida-plugins/rfc/__init__.py0000644000175000017500000000222010652670571015612 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/rfc/rfc.py0000644000175000017500000002317410652670571014640 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os import gtk import re import urllib from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.environment import Environment, get_uidef_path from pida.core.service import Service from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_TOGGLE, TYPE_NORMAL from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorTask, gcall # locale from pida.core.locale import Locale locale = Locale('rfc') _ = locale.gettext class RfcItem(object): def __init__(self, number='0000', data=''): self.number = number self.data = data list = re.split('\(([^\(]*)\)', data) self.description = list[0] class RfcView(PidaView): label_text = 'RFC' def create_ui(self): self._vbox = gtk.VBox(spacing=3) self._vbox.set_border_width(6) self.add_main_widget(self._vbox) self.create_toolbar() self.create_searchbar() self.create_list() self.create_progressbar() self._vbox.show_all() def create_searchbar(self): h = gtk.HBox() self._search_description = gtk.Entry() self._search_description.connect('changed', self._on_search_changed) l = gtk.Label() l.set_text(_('Filter : ')) h.pack_start(l, expand=False) h.pack_start(self._search_description) self._vbox.pack_start(h, expand=False) self._search_description.show_all() def create_toolbar(self): self._uim = gtk.UIManager() self._uim.insert_action_group(self.svc.get_action_group(), 0) self._uim.add_ui_from_file(get_uidef_path('rfc-toolbar.xml')) self._uim.ensure_update() self._toolbar = self._uim.get_toplevels('toolbar')[0] self._toolbar.set_style(gtk.TOOLBAR_ICONS) self._toolbar.set_icon_size(gtk.ICON_SIZE_SMALL_TOOLBAR) self._vbox.pack_start(self._toolbar, expand=False) self._toolbar.show_all() def create_list(self): self._list = ObjectList( [ Column('number', data_type=str, title=_('Number')), Column('description', data_type=str, title=_('Description')) ] ) self._list.connect('double-click', self._on_list_double_click) self._vbox.pack_start(self._list) self._list.show_all() def create_progressbar(self): self._progressbar = gtk.ProgressBar() self._progressbar.set_text(_('Download RFC Index')) self._vbox.pack_start(self._progressbar, expand=False) self._progressbar.set_no_show_all(True) self._progressbar.hide() def update_progressbar(self, current, max): if max > 1: self._progressbar.set_fraction(float(current) / float(max)) def show_progressbar(self, show): self._progressbar.set_no_show_all(False) if show: self._progressbar.show() else: self._progressbar.hide() def set_items(self, items): self._list.add_list(items, True) def clear(self): self._list.clear() def can_be_closed(self): self.svc.get_action('show_rfc').set_active(False) def _on_list_double_click(self, ot, item): self.svc.browse(id=item.number) def _on_search_changed(self, w): self.svc.filter(self._search_description.get_text()) class RfcActions(ActionsConfig): def create_actions(self): self.create_action( 'show_rfc', TYPE_TOGGLE, _('Rfc Viewer'), _('Show the rfc'), '', self.on_show_rfc, '', ) self.create_action( 'rfc_refreshindex', TYPE_NORMAL, _('Refresh RFC Index'), _('Refresh RFC Index'), gtk.STOCK_REFRESH, self.on_rfc_refreshindex, 'NOACCEL', ) self.create_action( 'rfc_downloadindex', TYPE_NORMAL, _('Download RFC Index'), _('Download RFC Index'), gtk.STOCK_GO_DOWN, self.on_rfc_downloadindex, 'NOACCEL', ) def on_show_rfc(self, action): if action.get_active(): self.svc.show_rfc() else: self.svc.hide_rfc() def on_rfc_downloadindex(self, action): self.svc.download_index() def on_rfc_refreshindex(self, action): self.svc.refresh_index() # Service class class Rfc(Service): """Fetch rfc list and show an rfc""" actions_config = RfcActions url_rfcindex = 'http://www.ietf.org/iesg/1rfc_index.txt' url_rfctmpl = 'http://tools.ietf.org/html/rfc' buffer_len = 16384 def start(self): self._filename = os.path.join(Environment.pida_home, 'rfc-index.txt') self._view = RfcView(self) self._has_loaded = False self.list = [] self.counter = 0 self.task = None self._filter_id = 0 self.is_refresh = False def show_rfc(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) if not self._has_loaded: self._has_loaded = True if not self.is_refresh: gcall(self.refresh_index) self.is_refresh = True def hide_rfc(self): self.boss.cmd('window', 'remove_view', view=self._view) def download_index(self): if self.task != None: self.task.stop() def _download_index_finished(): self._view.show_progressbar(False) self.get_action('rfc_downloadindex').set_sensitive(True) self.boss.cmd('notify', 'notify', title=_('RFC'), data=_('Index download completed')) gcall(self.refresh_index) self.task = GeneratorTask(self._download_index, _download_index_finished) self.task.start() def refresh_index(self): def _refresh_index_finished(): self._view.set_items(self.list) def _refresh_index_add(item): self.list.append(item) def _refresh_index(): try: fp = open(self._filename) except IOError: return data = '' zap = True for line in fp: line = line.rstrip('\n') data += line.strip(' ') + ' ' if line == '': t = data.split(' ', 1) if zap == False: if data != '' and t[1].strip(' ') != 'Not Issued.': yield RfcItem(number=t[0], data=t[1]) data = '' elif t[0] == '0001': zap = False elif zap == True: data = '' fp.close() self.list = [] self._view.clear() task = GeneratorTask(_refresh_index, _refresh_index_add, _refresh_index_finished) task.start() def filter(self, pattern): self._filter_id += 1 gcall(self._filter, pattern, self._filter_id) def _filter(self, pattern, id): if pattern == '': if self._filter_id == id: self._view.set_items(self.list) else: r = re.compile(pattern, re.IGNORECASE) list = [item for item in self.list if r.search(item.data)] if self._filter_id == id: self._view.set_items(list) def _download_index(self): self.get_action('rfc_downloadindex').set_sensitive(False) self._view.show_progressbar(True) sock = urllib.urlopen(self.url_rfcindex) fp = open(self._filename, 'w', 0) progress_max = 0 progress_current = 0 if sock.headers.has_key('content-length'): progress_max = int(sock.headers.getheader('content-length')) try: while True: buffer = sock.read(self.buffer_len) if buffer == '': break fp.write(buffer) progress_current += len(buffer) gcall(self._view.update_progressbar, progress_current, progress_max) finally: sock.close() fp.close() yield None def browse(self, id): self.boss.cmd('webbrowser', 'browse', url=(self.url_rfctmpl + id)) def stop(self): if self.task != None: self.task.stop() if self.get_action('show_rfc').get_active(): self.hide_rfc() # Required Service attribute for service loading Service = Rfc # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/rfc/service.pida0000644000175000017500000000037710652670571016013 0ustar aliali[plugin] plugin = rfc name = RFC Viewer version = 0.2 author = Mathieu Virbel require_pida = 0.5 depends = "os,gtk,re,urllib" description = "Download RFC index, search and view RFC pages inside PIDA" website = "" category = documentationPIDA-0.5.1/pida-plugins/rfc/test_rfc.py0000644000175000017500000000222010652670571015664 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/snippets/0002755000175000017500000000000010652671501014574 5ustar alialiPIDA-0.5.1/pida-plugins/snippets/glade/0002755000175000017500000000000010652671501015650 5ustar alialiPIDA-0.5.1/pida-plugins/snippets/glade/snippets-manager.glade0000644000175000017500000005570510652670563022144 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 300 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 1 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 0 5 5 False 5 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-delete True False 5 2 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-apply 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Installed 1 tab False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 0 5 5 False 5 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_SPREAD True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-refresh True True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-save True 1 False 5 2 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-down 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Available 1 tab 1 False False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 4 2 6 6 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK False 1 2 3 4 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 2 2 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Password: 3 4 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Username: 2 3 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Meta Data File: GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 Template File: 1 2 GTK_FILL True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Select A Meta Data File to Upload 1 2 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Select A Template File to Upload 1 2 1 2 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_BUTTONBOX_END True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-save True False 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 0 0 <i>On saving the snippet will be uploaded to the PIDA community website for all to access.</i> True True False 2 2 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-go-up 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Publish 1 tab 2 False False PIDA-0.5.1/pida-plugins/snippets/uidef/0002755000175000017500000000000010652671501015670 5ustar alialiPIDA-0.5.1/pida-plugins/snippets/uidef/snippets.xml0000644000175000017500000000323210652670563020264 0ustar aliali PIDA-0.5.1/pida-plugins/snippets/__init__.py0000644000175000017500000000222010652670564016707 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/snippets/service.pida0000644000175000017500000000031410652670564017077 0ustar aliali[plugin] plugin = snippets name = Snippets author = Ali Afshar version = 0.1 require_pida = 0.5 depends = "" category = code description = Snippets: automatically enter repetitive textPIDA-0.5.1/pida-plugins/snippets/snippets.py0000644000175000017500000004657710652670564017044 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. import os, xmlrpclib, base64 import gtk, gobject # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.options import OptionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.utils.path import walktree from pida.utils.gthreads import GeneratorTask, AsyncTask from pida.utils.configobj import ConfigObj from kiwi.ui.objectlist import Column # locale from pida.core.locale import Locale locale = Locale('snippets') _ = locale.gettext RPC_URL = 'http://pida.co.uk/RPC2' server_proxy = xmlrpclib.ServerProxy(RPC_URL) def get_value(tab, key): if not tab.has_key(key): return '' return tab[key] class SnippetsManagerView(PidaGladeView): gladefile = 'snippets-manager' locale = locale label_text = _('Snippets manager') icon_name = gtk.STOCK_INDEX def create_ui(self): self._current = None self.item = None self.installed_list.set_columns([ Column('title', title=_('Snippets'), sorted=True, data_type=str, expand=True) ]) self.available_list.set_columns([ Column('title', title=_('Snippets'), sorted=True, data_type=str, expand=True) ]) def can_be_closed(self): self.svc.get_action('show_snippets').set_active(False) def clear_installed(self): self.installed_list.clear() def add_installed(self, item): self.installed_list.append(item) def clear_available(self): self.available_list.clear() def add_available(self, item): self.available_list.append(item) def set_label_from_snippet(self, label, snippet): label_markup = _('Title: %(title)s\nShortcut: %(shortcut)s') label.set_markup(label_markup % dict( title=snippet.title, shortcut=snippet.shortcut )) def on_installed_list__selection_changed(self, ol, item): self.set_label_from_snippet( self.installed_information_label, item ) def on_available_list__selection_changed(self, ol, item): self.set_label_from_snippet( self.available_information_label, item ) def on_available_refresh__clicked(self, button): self.svc.get_available_snippets() def on_available_save__clicked(self, button): selected = self.available_list.get_selected() if selected is not None: self.svc.install_available(selected) def get_meta_filename(self): return self.meta_file_chooser.get_filename() def get_template_filename(self): return self.template_file_chooser.get_filename() def on_meta_file_chooser__selection_changed(self, chooser): m_file = self.get_meta_filename() t_file = m_file.rsplit('.', 1)[0] + '.tmpl' if os.path.exists(t_file): self.template_file_chooser.set_filename(t_file) def on_publish_button__clicked(self, button): self.svc.publish_snippet( self.username_entry.get_text(), self.password_entry.get_text(), self.get_meta_filename(), self.get_template_filename() ) class SnippetWindow(gtk.Window): def __init__(self, snippet, response_callback): gtk.Window.__init__(self) self.response_callback = response_callback self.set_decorated(False) self.add_events(gtk.gdk.FOCUS_CHANGE_MASK) #self.connect('set-focus-child', self.on_focus_child) self.connect('focus-out-event', self.focus_out) self._focused = False self._vars = {} self._create_ui() self._vals = {} self._valids = {} self._snippet = snippet self.set_title_label(self._snippet.title) self._create_entries() def _create_ui(self): self._frame = gtk.Frame() self.add(self._frame) self._frame.set_border_width(6) hb = gtk.HBox(spacing=6) hb.set_border_width(6) self._frame.add(hb) vb = gtk.VBox() hb.pack_start(vb, expand=False) vb.pack_start(self._create_labels(), expand=False) vb.pack_start(self._create_entries_box(), expand=False) hb.pack_start(self._create_preview_pane(), expand=False) def _create_entries(self): for variable in self._snippet.variables: self._vars[variable.name] = variable self.add_entry(variable) self.preview() def _create_labels(self): vb = gtk.VBox() self._primary_label = gtk.Label() vb.pack_start(self._primary_label, expand=False) return vb def _create_preview_pane(self): self._preview_text = gtk.TextView() self._preview_text.set_left_margin(6) self._preview_text.set_right_margin(6) self._preview_text.set_cursor_visible(False) self._preview_text.set_editable(False) #self._preview_text.set_app_paintable(True) #self._preview_text.connect('expose-event', self._on_preview_text_expose_event) #w = gtk.Window() #w.set_name('gtk-tooltips') #self._ttstyle = w.style #self._preview_text.modify_base(gtk.STATE_NORMAL, #self._ttstyle.base[gtk.STATE_NORMAL]) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add(self._preview_text) sw.set_size_request(200, -1) return sw def _create_entries_box(self): self._entries = {} self._entries_order = [] self._entries_vb = gtk.VBox(spacing=6) self._label_sizer = gtk.SizeGroup(gtk.ORIENTATION_HORIZONTAL) return self._entries_vb def add_entry(self, variable): name = variable.name label_text = variable.label default = variable.default hb = gtk.HBox(spacing=6) label = gtk.Label() label.set_text(label_text) label.set_alignment(1, 0.5) self._label_sizer.add_widget(label) hb.pack_start(label) self._valids[name] = gtk.Image() entry = gtk.Entry() entry.set_text(default) self.validate(entry, name) entry.connect('changed', self.on_entry_changed, name) self._vals[name] = default hb.pack_start(entry, expand=False) hb.pack_start(self._valids[name], expand=False) self._entries_vb.pack_start(hb, expand=False) self._entries[name] = entry self._entries_order.append(entry) entry.connect('key-press-event', self.on_entry_keypress, name) def grab_entry(self): self._entry.grab_focus() def set_title_label(self, value): self._frame.set_label(value) def set_primary_label(self, value): self._primary_label.set_text(value) def set_secondary_label(self, value): self._secondary_label.set_text(value) def on_entry_changed(self, entry, name): self._vals[name] = entry.get_text() self.validate(entry, name) self.preview() def validate(self, entry, name): if self._vars[name].required: if entry.get_text(): self._valids[name].set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) else: self._valids[name].set_from_stock(gtk.STOCK_NO, gtk.ICON_SIZE_MENU) else: self._valids[name].set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_MENU) def preview(self): self._preview_text.get_buffer().set_text(self.get_substituted_text()) def get_substituted_text(self): return self._snippet.substitute(self._vals) def on_entry_keypress(self, entry, event, name): #print name, [event.keyval, event.state, event.string] if event.string == '\r': if event.state & gtk.gdk.CONTROL_MASK: self.respond_success() else: index = self._entries_order.index(entry) if index == len(self._entries_order) - 1: self.respond_success() else: self._entries_order[index + 1].grab_focus() elif event.string == '\x1b': self.respond_failure() def _on_preview_text_expose_event(self, textview, event): #self._ttstyle.attach(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT)) textview.style.paint_flat_box(textview.get_window(gtk.TEXT_WINDOW_TEXT), gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, textview, "tooltip", 0, 0, -1, -1) #self._ttstyle.apply_default_background(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT), # True, gtk.STATE_NORMAL, None, 0, 0, -1, -1) #self._ttstyle.set_background(self._preview_text.get_window(gtk.TEXT_WINDOW_TEXT), # gtk.STATE_NORMAL) return False def focus_out(self, window, event): self.respond_failure() def close(self): self.hide_all() self.destroy() def respond_success(self): self.response_callback(True, self.get_substituted_text()) self.close() def respond_failure(self): self.response_callback(False, None) self.close() class MissingSnippetMetadata(Exception): """The template had missing metadata""" class SnippetVariable(object): def __init__(self, name, config): self.name = name self.config = config self.label = config['label'] self.default = config['default'] self.required = config.as_bool('required') class BaseSnippet(object): def __init__(self, snippet_meta): self.meta = snippet_meta self.name = self.meta.name self.title = self.meta.title self.variables = self.meta.variables self.text = self.meta.get_text() self.create_template() def create_template(self): raise NotImplementedError def substitute(self, values): raise NotImplementedError class StringTemplateSnippet(BaseSnippet): def create_template(self): from string import Template self.template = Template(self.text) def substitute(self, values): return self.template.substitute(values) class JinjaSnippet(BaseSnippet): def create_template(self): from jinja import from_string self.template = from_string(self.text) def substitute(self, values): return self.template.render(values) class SnippetMetaBase(object): def __init__(self): self.read_metadata() self._text = None def get_configobj(self): raise NotImplementedError def read_metadata(self): config = self.get_configobj() self.name = config['meta']['name'] self.title = config['meta']['title'] self.shortcut = config['meta']['shortcut'] self.tags = config['meta'].get('tags', []) self.variables = [] for sect in config['variables']: self.variables.append(SnippetVariable(sect, config['variables'][sect])) def read_text(self): raise NotImplementedError def get_text(self): if self._text is None: self._text = self.read_text() return self._text class CommunitySnippetMeta(SnippetMetaBase): def __init__(self, data_dict): self.data_dict = data_dict self.meta_data = data_dict['meta'] self.id = data_dict['id'] SnippetMetaBase.__init__(self) def get_configobj(self): return ConfigObj(self.read_meta_data().splitlines()) def read_meta_data(self): return base64.b64decode(self.meta_data) def read_text(self): return base64.b64decode(self.data_dict['data']) def save_in(self, directory): file_base = 'pida.co.uk.%s.%%s' % self.id self.save_meta_in(file_base, directory) self.save_data_in(file_base, directory) def save_meta_in(self, file_base, directory): filename = os.path.join(directory, file_base % 'meta') f = open(filename, 'w') f.write(self.read_meta_data()) f.close() def save_data_in(self, file_base, directory): filename = os.path.join(directory, file_base % 'tmpl') f = open(filename, 'w') f.write(self.read_text()) f.close() class InstalledSnippetMeta(SnippetMetaBase): def __init__(self, filename): self.filename = filename self.template_filename = self.filename.rsplit('.', 1)[0] + '.tmpl' SnippetMetaBase.__init__(self) def get_configobj(self): return ConfigObj(self.filename) def create_snippet(self): config = self.get_configobj() return StringTemplateSnippet(self) def read_text(self): f = open(self.template_filename, 'r') text = f.read() f.close() return text class PublishingSnippetMeta(InstalledSnippetMeta): def __init__(self, meta_filename, template_filename): self.filename = meta_filename self.template_filename = template_filename SnippetMetaBase.__init__(self) def get_encoded_text(self): return base64.b64encode(self.get_text()) def get_encoded_meta(self): return base64.b64encode(self.read_meta_file()) def read_meta_file(self): f = open(self.filename, 'r') meta_data = f.read() f.close() return meta_data class SnippetActions(ActionsConfig): def create_actions(self): self.create_action( 'insert_snippet', TYPE_NORMAL, _('Insert snippet from word'), _('Insert a snippet with the current word'), gtk.STOCK_ADD, self.on_insert_snippet, 'g', ) self.create_action( 'show_snippets', TYPE_TOGGLE, _('Snippets manager'), _('Show the snippets'), gtk.STOCK_EXECUTE, self.on_show_snippets, '' ) def on_show_snippets(self, action): if action.get_active(): self.svc.show_snippets() else: self.svc.hide_snippets() def on_insert_snippet(self, action): self.svc.boss.editor.cmd('call_with_current_word', callback=self.svc.popup_snippet) # Service class class Snippets(Service): """Describe your Service Here""" actions_config = SnippetActions def start(self): self._view = SnippetsManagerView(self) self.snippets = {} self.create_snippet_directories() self.get_snippet_list() def add_snippet_meta(self, snippet_meta): self.snippets[snippet_meta.shortcut] = snippet_meta def create_snippet_directories(self): self._snippet_dir = os.path.join(self.boss.get_pida_home(), 'snippets') if not os.path.exists(self._snippet_dir): os.mkdir(self._snippet_dir) def get_snippet_list(self): self._view.clear_installed() task = GeneratorTask(self._list_snippets, self._list_snippets_got) task.start() def _list_snippets(self): for name in os.listdir(self._snippet_dir): if name.endswith('.meta'): yield InstalledSnippetMeta(os.path.join(self._snippet_dir, name)) def _list_snippets_got(self, snippet_meta): self.add_snippet_meta(snippet_meta) self._view.add_installed(snippet_meta) def get_available_snippets(self): self.boss.cmd('notify', 'notify', title=_('Snippets'), data=_('Fetching available snippets')) self._view.clear_available() task = GeneratorTask( self._available_snippets, self._available_snippets_got, self._available_snippets_completed, ) task.start() def _available_snippets(self): for snippet_id, snippet_data in server_proxy.snippet.get([]).items(): yield CommunitySnippetMeta(snippet_data) def _available_snippets_got(self, snippet_meta): self._view.add_available(snippet_meta) def _available_snippets_completed(self): self.boss.cmd('notify', 'notify', title=_('Snippets'), data=_('Fetching available snippets')) def install_available(self, snippet_meta): snippet_meta.save_in(self._snippet_dir) self.boss.cmd('notify', 'notify', title=_('Snippets'), data=_('Installed Snippet')) self.get_snippet_list() def publish_snippet(self, username, password, meta_file, template_file): snippet = PublishingSnippetMeta(meta_file, template_file) task = AsyncTask(self.publish_snippet_do, self.publish_snippet_done) task.start(username, password, snippet) def publish_snippet_do(self, username, password, snippet): try: response = server_proxy.snippet.push(username, password, snippet.title, snippet.get_encoded_meta(), snippet.get_encoded_text(), snippet.tags ) except Exception, e: response = str(e) return response def publish_snippet_done(self, reply): if reply == 'OK': msg = 'Success' else: msg = 'Error: %s' % reply self.boss.cmd('notify', 'notify', title=_('Snippets'), data=_('Publish Snippet: %(msg)s') % dict(msg = msg) ) def popup_snippet(self, word): try: snippet = self.snippets[word].create_snippet() except KeyError: self.error_dlg(_('Snippet does not exist')) return popup = SnippetWindow(snippet, self.snippet_completed) popup.set_transient_for(self.window) popup.show_all() def snippet_completed(self, success, text): if success: self.insert_snippet(text) def insert_snippet(self, text): self.boss.editor.cmd('delete_current_word') self.boss.editor.cmd('insert_text', text=text) def show_snippets(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) #self.update_installed_snippets() def hide_snippets(self): self.boss.cmd('window', 'remove_view', view=self._view) # Required Service attribute for service loading Service = Snippets # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/snippets/test_snippets.py0000644000175000017500000000222010652670564020054 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/todo/0002755000175000017500000000000010652671501013674 5ustar alialiPIDA-0.5.1/pida-plugins/todo/locale/0002755000175000017500000000000010652671501015133 5ustar alialiPIDA-0.5.1/pida-plugins/todo/locale/fr_FR/0002755000175000017500000000000010652671501016131 5ustar alialiPIDA-0.5.1/pida-plugins/todo/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501017716 5ustar alialiPIDA-0.5.1/pida-plugins/todo/locale/fr_FR/LC_MESSAGES/todo.po0000644000175000017500000000122210652670557021230 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-02 18:40+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: ../../../todo.py:52 msgid "TODO" msgstr "TODO" #: ../../../todo.py:86 msgid "Todo Viewer" msgstr "Visualiseur de Todo" #: ../../../todo.py:87 msgid "Show the Todo Viewer" msgstr "Afficher le visualiseur de Todo" PIDA-0.5.1/pida-plugins/todo/uidef/0002755000175000017500000000000010652671501014770 5ustar alialiPIDA-0.5.1/pida-plugins/todo/uidef/todo.xml0000644000175000017500000000244410652670560016465 0ustar aliali PIDA-0.5.1/pida-plugins/todo/__init__.py0000644000175000017500000000222010652670560016003 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/todo/service.pida0000644000175000017500000000032410652670560016174 0ustar aliali[plugin] plugin = todo name = Todo parser author = Ali Afshar version = 0.2.1 require_pida = 0.5 depends = "" category = code description = "Parse current document for TODO, FIXME or XXX tag" PIDA-0.5.1/pida-plugins/todo/test_todo.py0000644000175000017500000000222010652670560016250 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/todo/todo.py0000644000175000017500000001176510652670560015227 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from kiwi.ui.objectlist import ObjectList, Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaView from pida.utils.gthreads import GeneratorTask, gcall # locale from pida.core.locale import Locale locale = Locale('todo') _ = locale.gettext class TodoItem(object): def __init__(self, todo, line, marker): self.todo = todo self.line = line self.marker = marker class TodoView(PidaView): label_text = _('TODO') icon_name = 'accessories-text-editor' def create_ui(self): self.todo_list = ObjectList( [ Column('line', sorted=True), Column('todo'), Column('marker'), ] ) self.todo_list.connect('double-click', self._on_todo_double_click) self.add_main_widget(self.todo_list) self.todo_list.show_all() def clear_items(self): gcall(self.todo_list.clear) def add_item(self, todo, line, marker): self.todo_list.append(TodoItem(todo, line, marker)) def _on_todo_double_click(self, olist, item): self.svc.boss.editor.cmd('goto_line', line=item.line) def can_be_closed(self): self.svc.get_action('show_todo').set_active(False) #XXX Banana class TodoActionsConfig(ActionsConfig): def create_actions(self): #XXX: blah self.create_action( 'show_todo', TYPE_TOGGLE, _('Todo Viewer'), _('Show the Todo Viewer'), 'accessories-text-editor', self.on_show_todo, 'd', ) def on_show_todo(self, action): if action.get_active(): self.svc.show_todo() else: self.svc.hide_todo() class TodoEventsConfig(EventsConfig): def subscribe_foreign_events(self): self.subscribe_foreign_event('buffer', 'document-changed', self.on_document_changed) self.subscribe_foreign_event('buffer', 'document-saved', self.on_document_changed) def on_document_changed(self, document): self.svc.set_current_document(document) # Service class class Todo(Service): """Describe your Service Here""" actions_config = TodoActionsConfig events_config = TodoEventsConfig _markers = ['TODO', 'XXX'] def start(self): self._current = None self._view = TodoView(self) def show_todo(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_todo(self): self.boss.cmd('window','remove_view', view=self._view) def check_current(self): for row in self.check_document(self._current): yield row def check_document(self, document): """Check the given lines for TODO messages.""" self._view.clear_items() for i, line in enumerate(document.lines): for marker in self._markers: if marker in line: pre, post = line.split(marker, 1) todo = post.strip().strip(':').strip() yield (todo, i + 1, marker) def add_todo_item(self, todo, line, marker): self._view.add_item(todo, line, marker) def set_current_document(self, document): self._current = document if self._current is not None: task = GeneratorTask(self.check_current, self.add_todo_item) task.start() def stop(self): if self.get_action('show_todo').get_active(): self.hide_todo() # Required Service attribute for service loading Service = Todo # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/trac/0002755000175000017500000000000010652671501013660 5ustar alialiPIDA-0.5.1/pida-plugins/trac/glade/0002755000175000017500000000000010652671501014734 5ustar alialiPIDA-0.5.1/pida-plugins/trac/glade/trac-browser.glade0000644000175000017500000002416710652670566020366 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Trac Address False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 True True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-refresh True False 2 False 350 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 300 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 6 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Report Number False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 1 False True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_ETCHED_IN 1 False True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 3 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK gtk-about True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK Tickets 1 tab False False 0 True True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK GTK_POLICY_AUTOMATIC GTK_POLICY_AUTOMATIC GTK_SHADOW_IN 1 True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK 2 False False 2 PIDA-0.5.1/pida-plugins/trac/locale/0002755000175000017500000000000010652671501015117 5ustar alialiPIDA-0.5.1/pida-plugins/trac/locale/fr_FR/0002755000175000017500000000000010652671501016115 5ustar alialiPIDA-0.5.1/pida-plugins/trac/locale/fr_FR/LC_MESSAGES/0002755000175000017500000000000010652671501017702 5ustar alialiPIDA-0.5.1/pida-plugins/trac/locale/fr_FR/LC_MESSAGES/trac.po0000644000175000017500000000152610652670565021206 0ustar aliali# PIDA # Copyright (C) 2005-2007 The PIDA Team # This file is distributed under the same license as the PIDA package. # msgid "" msgstr "" "Project-Id-Version: 0.5\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-05-03 10:41+0200\n" "PO-Revision-Date: 2007-05-02 18:40+0200\n" "Last-Translator: Mathieu Virbel \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: glade/trac-browser.glade.h:1 msgid "Report Number" msgstr "Numéro du rapport" #: glade/trac-browser.glade.h:2 msgid "Tickets" msgstr "Tickets" #: glade/trac-browser.glade.h:3 msgid "Trac Address" msgstr "Adresse du Trac" #: trac.py:52 msgid "Trac" msgstr "Trac" #: trac.py:123 msgid "Trac Viewer" msgstr "Visualiseur Trac" #: trac.py:124 msgid "Show the Trac Viewer" msgstr "Afficher le visualiseur Trac" PIDA-0.5.1/pida-plugins/trac/pixmaps/0002755000175000017500000000000010652671501015341 5ustar alialiPIDA-0.5.1/pida-plugins/trac/pixmaps/trac_logo.svg0000644000175000017500000001146410652670565020050 0ustar aliali image/svg+xml PIDA-0.5.1/pida-plugins/trac/uidef/0002755000175000017500000000000010652671501014754 5ustar alialiPIDA-0.5.1/pida-plugins/trac/uidef/trac.xml0000644000175000017500000000244410652670566016443 0ustar aliali PIDA-0.5.1/pida-plugins/trac/__init__.py0000644000175000017500000000222010652670566015775 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/trac/service.pida0000644000175000017500000000032310652670566016165 0ustar aliali[plugin] plugin = trac name = Trac version = 0.1.1 author = Ali Afshar require_pida = 0.5 depends = urlparse description = View bugs from Trac project inside PIDA website = "" category = codePIDA-0.5.1/pida-plugins/trac/test_trac.py0000644000175000017500000000222010652670566016226 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/pida-plugins/trac/trac.py0000644000175000017500000001201710652670566015174 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. from urlparse import urljoin import gtk from kiwi.ui.objectlist import Column # PIDA Imports from pida.core.service import Service from pida.core.features import FeaturesConfig from pida.core.commands import CommandsConfig from pida.core.events import EventsConfig from pida.core.actions import ActionsConfig from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE from pida.ui.views import PidaGladeView from pida.ui.htmltextview import HtmlTextView from pida.utils.web import fetch_url from pida.utils.feedparser import parse # locale from pida.core.locale import Locale locale = Locale('trac') _ = locale.gettext class TracView(PidaGladeView): gladefile = 'trac-browser' locale = locale icon_name = 'trac_logo' label_text = _('Trac') def create_ui(self): self.tickets_list.set_columns( [ Column('ticket', sorted=True, data_type=int), Column('summary'), ] ) self.set_base_address('http://pida.co.uk/trac/') self.item_text = HtmlTextView() self.item_text_holder.add(self.item_text) self.item_text.show() def set_base_address(self, address): self.address_entry.set_text(address) def get_base_address(self): return self.address_entry.get_text() def on_connect_button__clicked(self, button): trac_report(self.get_base_address(), 1, self.report_received) def on_tickets_list__selection_changed(self, ol, item): self.item_text.clear_html() self.item_text.display_html(item.description.strip()) def report_received(self, url, data): self.tickets_list.clear() for item in parse_report(data): self.tickets_list.append(item) def can_be_closed(self): self.svc.get_action('show_trac').set_active(False) class ReportItem(object): def __init__(self, entry): ticket, summary = entry['title'].split(':', 1) self.ticket = int(ticket.strip('#').strip()) self.summary = summary.strip() self.description = entry['description'] def parse_result(data): feed = parse(data) for entry in feed.entries: yield entry def parse_report(data): for entry in parse_result(data): yield ReportItem(entry) def trac_ticket(base_address, ticket_id, callback): trac_action(base_address, 'ticket', ticket_id, callback) def trac_report(base_address, report_id, callback): trac_action(base_address, 'report', report_id, callback) def trac_action(base_address, action, id, callback): action_fragment = '%s/%s?format=rss' % (action, id) action_url = urljoin(base_address, action_fragment) fetch_url(action_url, callback) class TracActions(ActionsConfig): def create_actions(self): self.create_action( 'show_trac', TYPE_TOGGLE, _('Trac Viewer'), _('Show the Trac Viewer'), gtk.STOCK_INFO, self.on_show_trac, 'j', ) def on_show_trac(self, action): if action.get_active(): self.svc.show_trac() else: self.svc.hide_trac() # Service class class Trac(Service): """Describe your Service Here""" actions_config = TracActions def pre_start(self): self._view = TracView(self) def show_trac(self): self.boss.cmd('window', 'add_view', paned='Plugin', view=self._view) def hide_trac(self): self.boss.cmd('window', 'remove_view', view=self._view) def ensure_view_visible(self): action = self.get_action('show_trac') if not action.get_active(): action.set_active(True) self.boss.cmd('window', 'presnet_view', view=self._view) def stop(self): if self.get_action('show_trac').get_active(): self.hide_trac() # Required Service attribute for service loading Service = Trac # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/tests/0002755000175000017500000000000010652671502011476 5ustar alialiPIDA-0.5.1/tests/core/0002755000175000017500000000000010652671503012427 5ustar alialiPIDA-0.5.1/tests/core/__init__.py0000644000175000017500000000000010652670575014534 0ustar alialiPIDA-0.5.1/tests/core/test_actions.py0000644000175000017500000000312710652670575015511 0ustar aliali import gtk from pida.core.actions import ActionsConfig, TYPE_NORMAL from unittest import TestCase from pida.utils.testing import refresh_gui class MyActions(ActionsConfig): def create_actions(self): self.create_action('banana', TYPE_NORMAL, 'do a banana', 'bananatt', gtk.STOCK_OPEN) self.create_action('banana2', TYPE_NORMAL, 'do a banana', 'bananatt', gtk.STOCK_OPEN, self.my_handler) def act_banana(self, action): self.svc.banana = True def my_handler(self, action): self.svc.banana = True class ActionTestCase(TestCase): def setUp(self): self.banana = False self.boss = None self._acts = MyActions(self) self._acts.create() self._act = self._acts._actions.get_action('banana') def get_name(self): return 'testcase' def test_action(self): self.assertEqual(self.banana, False) self._acts._actions.get_action('banana').activate() refresh_gui() self.assertEqual(self.banana, True) def test_label(self): self.assertEqual(self._acts.get_action('banana').get_property('label'), 'do a banana') def test_tt(self): self.assertEqual(self._acts.get_action('banana').get_property('tooltip'), 'bananatt') def test_sid(self): self.assertEqual(self._acts.get_action('banana').get_property('stock_id'), gtk.STOCK_OPEN) def test_action_callback(self): self.assertEqual(self.banana, False) self._acts._actions.get_action('banana2').activate() refresh_gui() self.assertEqual(self.banana, True) PIDA-0.5.1/tests/core/test_base.py0000644000175000017500000000064110652670575014761 0ustar alialifrom unittest import TestCase from pida.core.base import BaseConfig from pida.utils.testing.mock import Mock class TestConfig(BaseConfig): """A Test Subclass""" class BaseConfigTest(TestCase): def setUp(self): self._svc = Mock({'get_name': 'banana'}) self._conf = TestConfig(self._svc) def test_get_name(self): self.assertEqual(self._conf.get_service_name(), 'banana') PIDA-0.5.1/tests/core/test_boss.py0000644000175000017500000000041210652670575015011 0ustar aliali from pida.core.boss import Boss from unittest import TestCase class BossTest(TestCase): def setUp(self): self._b = Boss(None) def test_start(self): return #self._b.start() def test_stop(self): self._b.stop() PIDA-0.5.1/tests/core/test_commands.py0000644000175000017500000000455710652670575015662 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # Standard Library Imports from unittest import TestCase from pida.core.commands import CommandsConfig class MyCommands(CommandsConfig): def do_something(self): self.svc.something_done = True def do_another(self, banana): self.svc.another = banana def do_one_more(self): return 12345 class TestCommandConfig(TestCase): def setUp(self): self.something_done = False self.another = None self.com = MyCommands(self) def test_basic_call(self): self.assertEqual(self.something_done, False) self.com.do_something() self.assertEqual(self.something_done, True) def test_named_call(self): self.assertEqual(self.something_done, False) self.com.call('do_something') self.assertEqual(self.something_done, True) def test_argument(self): self.assertEqual(self.another, None) self.com.call('do_another', banana='melon') self.assertEqual(self.another, 'melon') def test_error_non_kw(self): def c(): self.com.call('do_another', 'melon') self.assertRaises(TypeError, c) def test_return_val(self): self.assertEqual(self.com.call('do_one_more'), 12345) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/tests/core/test_document.py0000644000175000017500000000440210652670575015664 0ustar aliali import os from pida.core.document import Document as document #from pida.core.testing import test, assert_equal, assert_notequal from unittest import TestCase from tempfile import mktemp def c(): tmp = mktemp() f = open(tmp, 'w') txt ="""Hello I am a document vlah blah""" f.write(txt) f.close() doc = document(filename=tmp) return doc, tmp, txt def d(tmp): os.remove(tmp) class DocumentTest(TestCase): def test_new_document(self): doc = document() self.assertEqual(doc.is_new, True) def test_unnamed_document(self): doc = document() self.assertEqual(doc.filename, None) def test_unnamed_is_new(self): doc = document() self.assertEqual(doc.is_new, True) self.assertEqual(doc.filename, None) def test_new_index(self): doc = document() doc2 = document() self.assertNotEqual(doc.newfile_index, doc2.newfile_index) def test_no_project(self): doc = document() self.assertEqual(doc.project_name, '') def test_no_handler(self): doc = document() self.assertEqual(doc.handler, None) def test_unique_id(self): doc = document() doc2 = document() self.assertNotEqual(doc.unique_id, doc2.unique_id) def test_real_file(self): doc, tmp, txt = c() self.assertEqual(doc.filename, tmp) d(tmp) def test_file_text(self): doc, tmp, txt = c() self.assertEqual(doc.string, txt) d(tmp) def test_file_lines(self): doc, tmp, txt = c() self.assertEqual(len(doc.lines), 2) d(tmp) def test_file_len(self): doc, tmp, txt = c() self.assertEqual(len(doc), len(txt)) d(tmp) def test_file_length(self): doc, tmp, txt = c() self.assertEqual(doc.length, len(doc)) d(tmp) def test_directory(self): doc, tmp, txt = c() self.assertEqual(doc.directory, '/tmp') d(tmp) def test_directory_basename(self): doc, tmp, txt = c() self.assertEqual(doc.directory_basename, 'tmp') d(tmp) def test_basename(self): doc, tmp, txt = c() self.assertEqual(doc.basename, os.path.basename(tmp)) d(tmp) PIDA-0.5.1/tests/core/test_editor.py0000644000175000017500000000231010652670575015330 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # Standard Library Imports from unittest import TestCase # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/tests/core/test_environment.py0000644000175000017500000000000010652670575016400 0ustar alialiPIDA-0.5.1/tests/core/test_events.py0000644000175000017500000000435610652670575015362 0ustar aliali# -*- coding: utf-8 -*- # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: from pida.core.events import Event import unittest class MockCallback(object): def __init__(self): self.count = 0 self.args = [] self.kw = {} def cb(self, *args, **kw): self.count = self.count + 1 self.args = args self.kw = kw return True def c(): return Event(), MockCallback() class EventTestCase(unittest.TestCase): def setUp(self): self.e = Event() self.__dummycount = 0 self.__dummyargs = [] self.__dummykw = {} def test_create_event(self): e, cb = c() self.assertEqual(e.has_event('banana'), False) e.create_event('banana') self.assertEqual(e.has_event('banana'), True) def test_register_callback(self): e, cb = c() e.create_event('banana') e.register('banana', cb.cb) def test_emit_event(self): e, cb = c() e.create_event('banana') e.register('banana', cb.cb) self.assertEqual(cb.count, 0) e.emit('banana') self.assertEqual(cb.count, 1) self.assertEqual(cb.args, ()) self.assertEqual(cb.kw, {}) def test_emit_event_multiple(self): e, cb = c() e.create_event('banana') e.register('banana', cb.cb) self.assertEqual(cb.count, 0) e.emit('banana') self.assertEqual(cb.count, 1) e.emit('banana') self.assertEqual(cb.count, 2) e.emit('banana') self.assertEqual(cb.count, 3) def test_emit_event_with_argument(self): e, cb = c() e.create_event('banana') e.register('banana', cb.cb) self.assertEqual(cb.count, 0) e.emit('banana') self.assertEqual(cb.count, 1) e.emit('banana', parameter=1) self.assertEqual(cb.count, 2) self.assertEqual(cb.args, ()) self.assertEqual(cb.kw, {'parameter': 1}) def test_emit_event_bad_argument(self): e, cb = c() e.create_event('banana') e.register('banana', cb.cb) try: e.emit('banana', 1) raise AssertionError('TypeError not raised') except TypeError: pass PIDA-0.5.1/tests/core/test_features.py0000644000175000017500000000372210652670575015670 0ustar aliali# -*- coding: utf-8 -*- # Copyright (c) 2007 The PIDA Project #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. # Standard Library Imports from unittest import TestCase from pida.core.features import FeaturesConfig class MyFeatureConfig(FeaturesConfig): def create_features(self): self.create_feature('banana') class TestFeatureConfig(TestCase): def setUp(self): self._fc = MyFeatureConfig(self) self._fc.create() def test_add_feature(self): self._fc.create_feature('banana') self.assert_('banana' in self._fc.list_features()) def test_subscribe_feature(self): self._fc.create_feature('banana') self.assert_('banana' in self._fc.list_features()) inst = 123 self._fc.subscribe_feature('banana', inst) self.assert_(123 in self._fc.get_feature_providers('banana')) self.assert_(12 not in self._fc.get_feature_providers('banana')) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: PIDA-0.5.1/tests/core/test_options.py0000644000175000017500000000011010652670575015531 0ustar alialifrom pida.core.options import OptionsManager o = OptionsManager(None) PIDA-0.5.1/tests/core/test_plugins.py0000644000175000017500000001324510652670575015534 0ustar alialifrom unittest import TestCase from pida.core.plugins import Registry, NamedSets, \ StrictNamedSets, DynamicNamedSets, ExtensionPoint, \ Plugin, PluginFactory, SingletonError, ExtensionPointError, \ FactoryDict class ISingleton: """A singleton definition""" class IFeature: """A feature definition""" class Singleton(object): """Singleton Implementation""" def __init__(self, registry=None): pass class TestSingleton(TestCase): def reg_singleton(self,**kw): s = Singleton() self.reg.register_plugin( instance=s, singletons=(ISingleton,), **kw ) return s def setUp(self): self.reg = Registry() def test_register_instance(self): s = self.reg_singleton() assert self.reg.get_singleton(ISingleton) is s def test_register_factory(self): s = Singleton self.reg.register_plugin( factory=s, singletons=(ISingleton,) ) self.assertEqual(type(self.reg.get_singleton(ISingleton)),s) def test_duplicate_singleton_error(self): self.reg_singleton() self.assertRaises(SingletonError, self.reg_singleton) def test_unregister_singleton(self): self.reg_singleton() self.reg.unregister_singleton(ISingleton) def test_unregsiter_singleton_unknown(self): def test(): self.reg.unregister_singleton(ISingleton) self.assertRaises(SingletonError,test) def test_register_with_feature(self): self.reg_singleton(features=(IFeature,)) def test_uregister_with_feature(self): self.reg_singleton(features=(IFeature,)) plugin = list(self.reg)[0] self.reg.unregister_feature(IFeature,plugin) def test_iter(self): plugin = self.reg_singleton() self.assertEqual(list(self.reg)[0].get_instance(None),plugin) def test_plugin_from_singleton(self): self.reg_singleton() got = self.reg.get_plugin_from_singleton(ISingleton) self.assertEqual(type(got),Plugin) def test_pluin_from_unknown_singleton_fail(self): self.assertRaises( SingletonError, lambda: self.reg.get_plugin_from_singleton(ISingleton)) def test_clear_registry(self): self.reg_singleton() self.reg.clear() def test_ext_points_get(self): self.reg_singleton() self.reg.ext_points["test"] def gen_data(sets): sets.add("job", "manager") sets.add("name", "josh") class TestNamedSets(TestCase): def setUp(self): self.sets=NamedSets() def test_fail_getitem(self): self.assertRaises(NotImplementedError, lambda:self.sets["test"]) def test_fail_add(self): self.assertRaises(NotImplementedError, lambda:self.sets.add("test","test")) def test_fail_remove(self): self.assertRaises(NotImplementedError, lambda:self.sets.remove("test","test")) class TestDynamicNamedSets(TestCase): def setUp(self): self.sets = DynamicNamedSets() def test_repr(self): self.assertEqual(repr(self.sets),'') def test_add(self): gen_data(self.sets) def test_remove(self): gen_data(self.sets) self.sets.remove("name", "josh") self.failIf("name" in self.sets.data) def test_remove_unknown(self): self.sets.remove("name", "josh") def test_delitem(self): gen_data(self.sets) del self.sets["name"] self.failIf("name" in self.sets.data) def test_delitem_unknown(self): del self.sets["name"] class TestStrictNamedSets(TestCase): def setUp(self): self.sets=StrictNamedSets(("name","job")) def test_create(self): self.assertEqual( repr(StrictNamedSets(("test",))), "") def test_add(self): gen_data(self.sets) def test_getitem(self): self.assertEqual(self.sets["name"], set([])) def test_remove(self): gen_data(self.sets) self.sets.remove("name","josh") self.assertEqual(self.sets["name"], set([])) class TestExtensionPoint(TestCase): def setUp(self): self.p = ExtensionPoint() def test_init(self): gen_data(self.p) self.p.init_extensions(["name"]) self.failIf(hasattr(self.p, "lazy")) def test_uninit_fail_getitem(self): def test(): self.p["name"] self.assertRaises( ExtensionPointError, test) def test_uninit_fail_keys(self): self.assertRaises(ExtensionPointError, self.p.keys) def test_keys(self): gen_data(self.p) self.p.init_extensions(["name"]) self.assertEqual( self.p.keys(), ["name"]) class TestPluginFactory(TestCase): def test_need_params(self): self.assertRaises(TypeError,PluginFactory) class TestPlugin(TestCase): def setUp(self): self.pstr=Plugin(factory=str) self.pin=Plugin(instance="test") def test_create_fail_factory(self): self.assertRaises(TypeError,lambda: Plugin(factory="")) def test_factory(self): self.assertEqual(self.pstr.get_instance("test"),"test") def test_reset_factory(self): self.pstr.reset() class TestFactoryDict(TestCase): def setUp(self): self.dict=FactoryDict(str.upper) def test_gen(self): self.assertEqual(self.dict["test"],"TEST") self.assertEqual(self.dict.data.keys(),["test"]) del self.dict["test"] PIDA-0.5.1/tests/core/test_projects.py0000644000175000017500000000357010652670575015704 0ustar alialifrom unittest import TestCase import os from tempfile import mkstemp # the matching example config file from pida.core.projects import ProjectControllerMananger, \ ExecutionActionType, project_action, ProjectController PYCONF=""" name = My Project [Python] controller = PYTHON_CONTROLLER execute_file = banana.py source_package = src test_command = nosetests """ class GenericExecutionController(ProjectController): name = 'GENERIC_EXECUTION' @project_action(kind=ExecutionActionType) def execute(self): self.execute_commandline( self.get_option('command_line'), self.get_option('env'), self.get_option('cwd') or self.project.source_directory, ) EXCONF = """ name = My Project [Execution] controller = GENERIC_EXECUTION command_line = ls -al """ class BasicTest(TestCase): def setUp(self): self._pcm = ProjectControllerMananger() self._pcm.register_controller(GenericExecutionController) f, self._path = mkstemp() os.write(f, EXCONF) os.close(f) self._pr = self._pcm.create_project(self._path) def test_register(self): self.assertEqual(len(self._pr.action_kinds[ExecutionActionType]), 1) def test_execute(self): self._pr.controllers[0].execute_commandline = self._execute self._pr.action_kinds[ExecutionActionType][0]() self.assertEqual(len(self.args), 3) def test_set_option(self): self._pr.controllers[0].execute_commandline = self._execute self._pr.action_kinds[ExecutionActionType][0]() self.assertEqual(self.args[0], 'ls -al') self._pr.set_option('Execution', 'command_line', 'ls') self._pr.action_kinds[ExecutionActionType][0]() self.assertEqual(self.args[0], 'ls') def _execute(self, *args): self.args = args def tearDown(self): os.remove(self._path) PIDA-0.5.1/tests/core/test_servicemanager.py0000644000175000017500000001425710652670575017052 0ustar aliali from unittest import TestCase from tempfile import mkdtemp import shutil import os from pida.core.interfaces import IService from pida.core.servicemanager import ServiceLoader, ServiceManager from pida.utils.testing.mock import Mock from pida.core.environment import get_glade_path class ServiceLoadTest(TestCase): def setUp(self): # A working service self._tdir = mkdtemp() self._spath = os.path.join(self._tdir, 'testservice') os.mkdir(self._spath) for name in ['__init__.py', 'testservice.py', 'service.pida']: f = open(os.path.join(self._spath, name), 'w') if name == 'testservice.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._spath = os.path.join(self._tdir, 'testservice2') os.mkdir(self._spath) for name in ['__init__.py', 'testservice2.py', 'service.pida']: f = open(os.path.join(self._spath, name), 'w') if name == 'testservice2.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._tdir2 = mkdtemp() self._spath2 = os.path.join(self._tdir2, 'testservice') os.mkdir(self._spath2) for name in ['__init__.py', 'testservice.py', 'service.pida']: f = open(os.path.join(self._spath2, name), 'w') if name == 'testservice.py': f.write('class NoService(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._tdir3 = mkdtemp() self._spath3 = os.path.join(self._tdir3, 'testservice') os.mkdir(self._spath3) for name in ['__init__.py', 'testservice.py']: f = open(os.path.join(self._spath3, name), 'w') if name == 'testservice.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._tdir4 = mkdtemp() self._spath4 = os.path.join(self._tdir4, 'nottestservice') os.mkdir(self._spath4) for name in ['__init__.py', 'testservice.py', 'service.pida']: f = open(os.path.join(self._spath4, name), 'w') if name == 'testservice.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._tdir5 = mkdtemp() self._spath5 = os.path.join(self._tdir5, 'testservice') os.mkdir(self._spath5) for name in ['__init__.py', 'testservice.py', 'service.pida']: f = open(os.path.join(self._spath5, name), 'w') if name == 'testservice.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() self._gladedir = os.path.join(self._spath5, 'glade') os.mkdir(self._gladedir) self._dumglade = os.path.join(self._gladedir, 'banana.glade') f = open(self._dumglade, 'w') f.close() self.loader = ServiceLoader() def test_get(self): services = [svc for svc in self.loader.get_all_services([self._tdir])] self.assertEqual(services[0].__name__, 'Service') def test_get_both(self): services = [svc for svc in self.loader.get_all_services([self._tdir])] self.assertEqual(len(services), 2) def test_load(self): services = [svc for svc in self.loader.load_all_services([self._tdir], None)] self.assertEqual(services[0].__class__.__name__, 'Service') def test_bad_load(self): services = [svc for svc in self.loader.get_all_services([self._tdir2])] self.assertEqual(services, []) def test_no_pidafile(self): services = [svc for svc in self.loader.get_all_services([self._tdir3])] self.assertEqual(services, []) def test_import_error(self): services = [svc for svc in self.loader.get_all_services([self._tdir4])] self.assertEqual(services, []) def test_env(self): self.loader.load_all_services([self._tdir5], None) gp = get_glade_path('banana.glade') self.assertEqual(gp, self._dumglade) def tearDown(self): shutil.rmtree(self._tdir) shutil.rmtree(self._tdir2) shutil.rmtree(self._tdir3) shutil.rmtree(self._tdir4) class ServiceManagerTest(TestCase): def setUp(self): # A working service self._tdir = mkdtemp() self._spath = os.path.join(self._tdir, 'testservice') os.mkdir(self._spath) for name in ['__init__.py', 'testservice.py', 'service.pida']: f = open(os.path.join(self._spath, name), 'w') if name == 'testservice.py': f.write('class Service(object):\n') f.write(' def __init__(self, boss):\n') f.write(' """A test"""\n') f.close() class MyService: servicename = 'MyService' self._svc = MyService() self._boss = Mock( dict( get_service_dirs = [self._tdir] ) ) self._sm = ServiceManager(self._boss) def tearDown(self): shutil.rmtree(self._tdir) def test_service_manager_register(self): self._sm.register_service(self._svc) self.assertEqual( self._sm.get_service('MyService').servicename, self._svc.servicename ) self.assertEqual( [s for s in self._sm.get_services()][0], self._svc ) def test_service_manager_load(self): self._sm.load_services() self.assertEqual( self._sm.get_service('testservice').__class__.__name__, 'Service' ) self.assertEqual( [s for s in self._sm.get_services()][0].__class__.__name__, 'Service' ) PIDA-0.5.1/tests/core/test_services.py0000644000175000017500000000411710652670575015674 0ustar aliali from unittest import TestCase from pida.core.service import Service from pida.core.options import OptionsConfig, OTypeString from pida.core.commands import CommandsConfig from pida.core.interfaces import IOptions from pida.core.log import build_logger class MockBoss(object): log = build_logger('mylog', None) def add_action_group_and_ui(*args): pass class MYOptions(OptionsConfig): def create_options(self): self.svc.o_test = self.create_option( name='g1', label='G1 Label', rtype=OTypeString, default='default value', doc='Document for my group' ) class MyCommands(CommandsConfig): def do_something(self, val): self.svc.something = val class MYService(Service): options_config = MYOptions commands_config = MyCommands def __init__(self, boss): Service.__init__(self, boss) self.something = False def get_name(self): return 'MyServiceName' class TestOptions(TestCase): def setUp(self): pass def test_options_setup(self): svc = MYService(boss=MockBoss()) svc.create_all() self.assertEqual( svc.reg.get_singleton(IOptions).get_option('g1'), svc.o_test ) def test_option_get(self): svc = MYService(boss=MockBoss()) svc.create_all() self.assertEqual( svc.get_option('g1'), svc.o_test ) def test_option_get_value(self): svc = MYService(boss=MockBoss()) svc.create_all() self.assertEqual( svc.opt('g1'), 'default value' ) class TestCommands(TestCase): def setUp(self): self.svc = MYService(boss=MockBoss()) self.svc.create_all() def test_call(self): self.assertEqual(self.svc.something, False) self.svc.cmd('do_something', val=True) self.assertEqual(self.svc.something, True) def test_non_named(self): def c(): self.svc.cmd('do_something', True) self.assertRaises(TypeError, c) PIDA-0.5.1/tests/ui/0002755000175000017500000000000010652671503012114 5ustar alialiPIDA-0.5.1/tests/ui/__init__.py0000644000175000017500000000000010652670576014222 0ustar alialiPIDA-0.5.1/tests/ui/test_books.py0000644000175000017500000001725610652670576014664 0ustar aliali from unittest import TestCase import gtk from pida.ui.books import BookConfigurator, BookManager from pida.ui.books import ORIENTATION_SIDEBAR_LEFT, ORIENTATION_SIDEBAR_RIGHT from pida.ui.books import BOOK_TERMINAL, BOOK_EDITOR, BOOK_BUFFER, BOOK_PLUGIN from pida.utils.testing import refresh_gui from pida.utils.testing.mock import Mock class TestConfig(TestCase): def setUp(self): self._SL = BookConfigurator(ORIENTATION_SIDEBAR_LEFT) self._SR = BookConfigurator(ORIENTATION_SIDEBAR_RIGHT) self._nbSL = dict( tl_book = gtk.Notebook(), tr_book = gtk.Notebook(), bl_book = gtk.Notebook(), br_book = gtk.Notebook(), ) self._nbSR = dict( tl_book = gtk.Notebook(), tr_book = gtk.Notebook(), bl_book = gtk.Notebook(), br_book = gtk.Notebook(), ) for name, book in self._nbSL.iteritems(): self._SL.configure_book(name, book) for name, book in self._nbSR.iteritems(): self._SR.configure_book(name, book) refresh_gui() def test_TerminalBook(self): self.assertEqual(self._nbSL['br_book'], self._SL.get_book('Terminal')) self.assertEqual(self._nbSR['bl_book'], self._SR.get_book('Terminal')) def test_TerminalBook_tab_pos(self): self.assertEqual( self._SL.get_book('Terminal').get_tab_pos(), gtk.POS_TOP ) self.assertEqual( self._SR.get_book('Terminal').get_tab_pos(), gtk.POS_TOP ) def test_TerminalBook_tab_vis(self): self.assertEqual( self._SL.get_book('Terminal').get_show_tabs(), True ) self.assertEqual( self._SR.get_book('Terminal').get_show_tabs(), True ) def test_Editor(self): self.assertEqual(self._nbSL['tr_book'], self._SL.get_book('Editor')) self.assertEqual(self._nbSR['tl_book'], self._SR.get_book('Editor')) def test_Editor_tab_pos(self): self.assertEqual( self._SL.get_book('Editor').get_tab_pos(), gtk.POS_TOP ) self.assertEqual( self._SR.get_book('Editor').get_tab_pos(), gtk.POS_TOP ) def test_Editor_tab_vis(self): self.assertEqual( self._SL.get_book('Editor').get_show_tabs(), False ) self.assertEqual( self._SR.get_book('Editor').get_show_tabs(), False ) def test_Plugin(self): self.assertEqual(self._nbSL['bl_book'], self._SL.get_book('Plugin')) self.assertEqual(self._nbSR['br_book'], self._SR.get_book('Plugin')) def test_Editor_tab_pos(self): self.assertEqual( self._SL.get_book('Plugin').get_tab_pos(), gtk.POS_RIGHT ) self.assertEqual( self._SR.get_book('Plugin').get_tab_pos(), gtk.POS_LEFT ) def test_Editor_tab_vis(self): self.assertEqual( self._SL.get_book('Plugin').get_show_tabs(), True ) self.assertEqual( self._SR.get_book('Plugin').get_show_tabs(), True ) def test_Buffer(self): self.assertEqual(self._nbSL['tl_book'], self._SL.get_book('Buffer')) self.assertEqual(self._nbSR['tr_book'], self._SR.get_book('Buffer')) def test_Editor_tab_pos(self): self.assertEqual( self._SL.get_book('Buffer').get_tab_pos(), gtk.POS_RIGHT ) self.assertEqual( self._SR.get_book('Buffer').get_tab_pos(), gtk.POS_LEFT ) def test_Editor_tab_vis(self): self.assertEqual( self._SL.get_book('Buffer').get_show_tabs(), True ) self.assertEqual( self._SR.get_book('Buffer').get_show_tabs(), True ) def test_bad_name(self): def b(): self._SL.get_book('banana') self.assertRaises(KeyError, b) class TestBookManager(TestCase): def setUp(self): self._SL = BookConfigurator(ORIENTATION_SIDEBAR_LEFT) self._nbSL = dict( tl_book = gtk.Notebook(), tr_book = gtk.Notebook(), bl_book = gtk.Notebook(), br_book = gtk.Notebook(), ) w = gtk.Window() vb = gtk.VBox() w.add(vb) for nb in self._nbSL.values(): vb.pack_start(nb) for name, book in self._nbSL.iteritems(): self._SL.configure_book(name, book) self._man = BookManager(self._SL) w.show_all() refresh_gui() self._mview1 = Mock( dict( get_unique_id = 1, get_toplevel = gtk.Label('1') ) ) self._mview2 = Mock( dict( get_unique_id = 2, get_toplevel = gtk.Label('2') ) ) def test_add_view(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self.assertEqual(self._SL.get_book(BOOK_TERMINAL).get_n_pages(), 1) def test_has_view(self): self.assertEqual(self._man.has_view(self._mview1), False) self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self.assertEqual(self._man.has_view(self._mview1), True) def test_add_view_twice(self): def add(): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() add() self.assertRaises(ValueError, add) def test_remove_view(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self.assertEqual(self._SL.get_book(BOOK_TERMINAL).get_n_pages(), 1) self._man.remove_view(self._mview1) refresh_gui() self.assertEqual(self._SL.get_book(BOOK_TERMINAL).get_n_pages(), 0) self.assertEqual(self._man.has_view(self._mview1), False) def test_remove_nonexistent_view(self): def r(): self._man.remove_view(self._mview1) self.assertRaises(ValueError, r) def test_move_view(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self.assertEqual(self._SL.get_book(BOOK_TERMINAL).get_n_pages(), 1) self._man.move_view(BOOK_EDITOR, self._mview1) refresh_gui() self.assertEqual(self._SL.get_book(BOOK_TERMINAL).get_n_pages(), 0) self.assertEqual(self._SL.get_book(BOOK_EDITOR).get_n_pages(), 1) def test_add_2_pages(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self._man.add_view(BOOK_TERMINAL, self._mview2) refresh_gui() book = self._man._get_book(BOOK_TERMINAL) self.assertEqual(book.get_n_pages(), 2) def test_prev_page(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self._man.add_view(BOOK_TERMINAL, self._mview2) refresh_gui() self._man.prev_page(BOOK_TERMINAL) refresh_gui() self._man.prev_page(BOOK_TERMINAL) refresh_gui() book = self._man._get_book(BOOK_TERMINAL) self.assertEqual(book.get_n_pages(), 2) def test_next_page(self): self._man.add_view(BOOK_TERMINAL, self._mview1) refresh_gui() self._man.add_view(BOOK_TERMINAL, self._mview2) refresh_gui() self._man.next_page(BOOK_TERMINAL) refresh_gui() self._man.next_page(BOOK_TERMINAL) refresh_gui() book = self._man._get_book(BOOK_TERMINAL) self.assertEqual(book.get_n_pages(), 2) PIDA-0.5.1/tests/ui/test_uimanager.py0000644000175000017500000000574210652670576015514 0ustar aliali from unittest import TestCase import gtk from pida.utils.testing import refresh_gui from pida.ui.uimanager import PidaUIManager class UIMSetupTestCase(TestCase): def setUp(self): self.uim = PidaUIManager() refresh_gui() def test_base_xml(self): self.assert_('main_menu' in self.uim._uim.get_ui()) self.assert_('main_toolbar' in self.uim._uim.get_ui()) def test_menu_file(self): self.assert_('FileMenu' in self.uim._uim.get_ui()) def test_menu_edit(self): self.assert_('EditMenu' in self.uim._uim.get_ui()) def test_menu_project(self): self.assert_('ProjectMenu' in self.uim._uim.get_ui()) def test_menu_tools(self): self.assert_('ToolsMenu' in self.uim._uim.get_ui()) def test_menu_view(self): self.assert_('ViewMenu' in self.uim._uim.get_ui()) def test_menu_help(self): self.assert_('HelpMenu' in self.uim._uim.get_ui()) def test_menu_actions_file(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('FileMenu' in a) def test_menu_actions_edit(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('EditMenu' in a) def test_menu_actions_project(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('ProjectMenu' in a) def test_menu_actions_language(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('LanguageMenu' in a) def test_menu_actions_tools(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('ToolsMenu' in a) def test_menu_actions_view(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('ViewMenu' in a) def test_menu_actions_help(self): a = [a.get_name() for a in self.uim._base_ag.list_actions()] self.assert_('HelpMenu' in a) def test_menubar(self): menubar = self.uim.get_menubar() refresh_gui() self.assert_(isinstance(menubar, gtk.MenuBar)) [self.assert_(isinstance(m, gtk.MenuItem)) for m in menubar.get_children()] def test_toolbar(self): toolbar = self.uim.get_toolbar() refresh_gui() def test_add_ui(self): ag = gtk.ActionGroup(name='myacts') ag.add_actions( [ ('MyMenu', None, 'MyMenu', None, None), ] ) self.uim.add_action_group(ag) self.uim.add_ui_from_string( """ """ ) refresh_gui() menubar = self.uim.get_menubar() fm = menubar.get_children()[0] acts = [m.get_action().get_name() for m in fm.get_submenu() if m.get_action() is not None] self.assert_('MyMenu' in acts) PIDA-0.5.1/tests/ui/test_views.py0000644000175000017500000000252710652670576014677 0ustar aliali from unittest import TestCase from pida.utils.testing import refresh_gui from pida.ui.views import PidaView import pida.ui.window class TestView(PidaView): gladefile = 'test_view' def on_b1__clicked(self, button): self.svc._clicked = True class ActionView(PidaView): gladefile = 'test_view' def on_test_act__activate(self, action): self.svc._clicked = True def on_b2__clicked(self, button): self.svc._clicked = True class BasicViewTest(TestCase): def setUp(self): self._v = TestView(self) refresh_gui() def test_has_toplevel(self): self.assertNotEqual(self._v.get_toplevel(), None) def test_has_no_parent(self): self.assertEqual(self._v.get_toplevel().get_parent(), None) class ViewCallbackTest(TestCase): def setUp(self): self._v = TestView(self) self._clicked = False refresh_gui() def test_event_callback(self): self.assertEqual(self._clicked, False) self._v.b1.clicked() refresh_gui() self.assertEqual(self._clicked, True) class ViewActionsTest(TestCase): def setUp(self): self._v = TestView(self) self._clicked = False refresh_gui() def test_actions(self): self.assertEqual(self._clicked, False) self._v.test_act.activate() PIDA-0.5.1/tests/ui/test_window.py0000644000175000017500000000063510652670576015047 0ustar aliali from unittest import TestCase from pida.utils.testing import refresh_gui from pida.ui.window import PidaWindow from pida.ui.books import BOOK_TERMINAL, BOOK_PLUGIN from tests.ui.test_views import TestView class BasicWindowTest(TestCase): def setUp(self): self._w = PidaWindow(self) refresh_gui() def test_basic(self): self._w.get_toplevel().show_all() refresh_gui() PIDA-0.5.1/tests/utils/0002755000175000017500000000000010652671503012637 5ustar alialiPIDA-0.5.1/tests/utils/rat/0002755000175000017500000000000010652671503013425 5ustar alialiPIDA-0.5.1/tests/utils/rat/__init__.py0000644000175000017500000000000010652670574015531 0ustar alialiPIDA-0.5.1/tests/utils/rat/testgwp.py0000644000175000017500000001366610652670574015515 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import unittest from pida.utils.rat import gwp import gconf import gtk GCONF_KEY = "/apps/gwp/key_str" class TestGConfValue(unittest.TestCase): def setUp(self): self.gconf = gconf.client_get_default().add_dir("/apps/gwp", gconf.CLIENT_PRELOAD_NONE) self.value = gwp.GConfValue( key = GCONF_KEY, data_spec = gwp.Spec.STRING ) def test_set_default(self): self.assertEqual(self.value.data_spec, gwp.Spec.STRING) self.assertEqual(self.value.default, self.value.data_spec.default) self.value.default = "default" self.assertEqual(self.value.default, "default") self.value.data = self.value.default self.assertEqual(self.value.client.get_string(self.value.key), self.value.default) self.assertEqual(self.value.data, self.value.default) def test_set_data(self): self.value.data = self.value.default self.value.data = "foo" self.assertEqual(self.value.client.get_string(self.value.key), "foo") self.assertEqual(self.value.data, "foo") def callback1(self, *args): self.assertTrue(self.value.data, "bar") self.foo = True gtk.main_quit() def test_set_callback(self): self.value.data = self.value.default self.foo = False self.value.set_callback(self.callback1) self.value.data = "bar" gtk.main() self.assertTrue(self.foo) def test_default(self): self.assertEqual(self.value.default, "") self.value.default = "var" self.assertEqual(self.value.default, "var") self.value.reset_default() self.assertEqual(self.value.default, "") class TestDataEntry(unittest.TestCase): def setUp(self): self.entry = gtk.Entry() self.entry.set_text("foo") self.value = gwp.create_persistency_link(self.entry, GCONF_KEY) def test_unset_data(self): # First we make sure the integrity exists upon start self.assertEqual(self.entry.get_text(), self.value.data) self.assertEqual(self.value.storage.data, self.value.data) def test_widget_to_gconf(self): """From widget to gconf entry""" self.entry.set_text("bar") self.assertEqual(self.value.data, "bar") def test_gconf_to_widget(self): """From gconf to widget""" self.value.data = "foo" self.assertEqual(self.entry.get_text(), "foo") def test_destroy_widget(self): self.entry.destroy() assert self.value.widget is None def test_widget_signal(self): pass def test_gconf_signal(self): pass def test_gconf_disabled(self): pass def test_sync_widget(self): pass def test_sync_gconf(self): pass class TestDataCheckbutton(unittest.TestCase): def setUp(self): self.entry = gtk.CheckButton() self.entry.set_active(True) self.value = gwp.create_persistency_link(self.entry, GCONF_KEY) def test_unset_data(self): # First we make sure the integrity exists upon start self.assertEqual(self.entry.get_active(), self.value.data) self.assertEqual(self.value.storage.data, self.value.data) def test_widget_to_gconf(self): """From widget to gconf entry""" self.entry.set_active(False) self.assertEqual(self.value.data, False) def test_gconf_to_widget(self): """From gconf to widget""" self.value.data = False self.assertEqual(self.entry.get_active(), False) def test_destroy_widget(self): self.entry.destroy() assert self.value.widget is None def test_widget_signal(self): pass def test_gconf_signal(self): pass def test_gconf_disabled(self): pass def test_sync_widget(self): pass def test_sync_gconf(self): pass class TestToggleButton(TestDataCheckbutton): def setUp(self): self.entry = gtk.ToggleButton() self.entry.set_active(True) self.value = gwp.create_persistency_link(self.entry, GCONF_KEY) class TestSpinButton(unittest.TestCase): def setUp(self): self.entry = gtk.CheckButton() self.entry.set_active(True) self.value = gwp.create_persistency_link(self.entry, GCONF_KEY) def test_unset_data(self): # First we make sure the integrity exists upon start self.assertEqual(self.entry.get_active(), self.value.data) self.assertEqual(self.value.storage.data, self.value.data) def test_widget_to_gconf(self): """From widget to gconf entry""" self.entry.set_active(False) self.assertEqual(self.value.data, False) def test_gconf_to_widget(self): """From gconf to widget""" self.value.data = False self.assertEqual(self.entry.get_active(), False) def test_destroy_widget(self): self.entry.destroy() assert self.value.widget is None def test_widget_signal(self): pass def test_gconf_signal(self): pass def test_gconf_disabled(self): pass def test_sync_widget(self): pass def test_sync_gconf(self): pass class TestRadioButtonData: def test_unset_data(self): pass def test_set_data(self): pass def test_destroy_widget(self): pass def test_widget_signal(self): pass def test_gconf_signal(self): pass def test_gconf_disabled(self): pass def test_sync_widget(self): pass def test_sync_gconf(self): pass def main(): unittest.main() if __name__ == '__main__': main() PIDA-0.5.1/tests/utils/rat/testsensitive.py0000644000175000017500000001266110652670574016723 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import gtk import unittest from pida.utils.rat import sensitive class TestCounter(unittest.TestCase): def setUp(self): self.amount = 0 def cb(self, amount): self.amount = amount def test_counter(self): counter = sensitive.Counter(self.cb) counter.inc() self.assertEqual(self.amount, 1) counter.inc() self.assertEqual(self.amount, 2) counter.dec() self.assertEqual(self.amount, 1) counter.dec() self.assertEqual(self.amount, 0) counter.dec() self.assertEqual(self.amount, -1) class TestClient(unittest.TestCase): def setUp(self): self.amount = 0 self.counter = sensitive.Counter(self.cb) def cb(self, amount): self.amount = amount def test_client(self): # When we create a client the amount is at 0 self.assertEqual(self.amount, 0) client = sensitive.SensitiveClient(self.counter) self.assertEqual(self.amount, 0) # Setting it to Sensitive should maintain it to 0 client.set_sensitive(True) self.assertEqual(self.amount, 0) # Setting it again does nothing client.set_sensitive(True) self.assertEqual(self.amount, 0) # Setting it to false makes it go to 1 client.set_sensitive(False) self.assertEqual(self.amount, 1) # Setting it again, makes nothing client.set_sensitive(False) self.assertEqual(self.amount, 1) # Setting it to Sensitive should maintain it to 0 again client.set_sensitive(True) self.assertEqual(self.amount, 0) # Setting it to insensitive and removing its reference should make # the amount back to 1 client.set_sensitive(False) self.assertEqual(self.amount, 1) client = None self.assertEqual(self.amount, 0) class TestController(unittest.TestCase): def setUp(self): self.lbl = gtk.Label() self.cnt = sensitive.SensitiveController(self.lbl) def is_sensitive(self): return self.lbl.get_property("sensitive") def test_0_controller_ref(self): # If the label is insensitive and we loose our controller's ref # __del__ will set the sensitive back to True self.lbl.set_sensitive(False) self.cnt = None self.assertTrue(self.is_sensitive()) # When we create a controller then it should make the associated # widget sensitive self.lbl.set_sensitive(False) self.cnt = sensitive.SensitiveController(self.lbl) self.assertTrue(self.is_sensitive()) # In this test we'll register an affecter and make it insensitive # After this we'll loose the reference to our controller, this # should make the 'self.lbl' sensitive again client = self.cnt.create_client() client.set_sensitive(False) self.failIf(self.is_sensitive()) self.cnt = None self.assertTrue(self.is_sensitive()) def test_1_client(self): # A widget starts as sensitive self.assertTrue(self.is_sensitive()) # When we register it it maintains sensitive client = self.cnt.create_client() self.assertTrue(self.is_sensitive()) # Since we only have one registred client, which is self # setting it to False will make the widget not sensitive too client.set_sensitive(False) self.failIf(self.is_sensitive()) # Making it sensitive again will also affect global sensitive status client.set_sensitive(True) self.assertTrue(self.is_sensitive()) # Setting it back to False and removing the client will # reset the sensitive status back to True client.set_sensitive(False) self.failIf(self.is_sensitive()) client = None self.assertTrue(self.is_sensitive()) def test_destroy_object(self): client = self.cnt.create_client() self.lbl.destroy() def test_2_signal_bind(self): # We'll bind the 'text' property of the 'gtk.Entry' # through the signal 'changed' to make the 'self.lbl' sensitive when # its text is empty entry = gtk.Entry() bind = sensitive.SignalBind(self.cnt) bind.bind( entry, "text", "changed", lambda text: text != "" ) # Since the bind has effect once the instance is created and the text # on the 'gtk.Entry' starts empty then it # should make our label insensitive self.failIf(self.is_sensitive()) # Changing the text to something else entry.set_text("Foo") self.assertTrue(self.is_sensitive()) # Clearing the text entry again entry.set_text("") self.failIf(self.is_sensitive()) # If we loose the reference to the 'bind' object then the connection # should be terminated, which means it should be sensitive again bind = None self.assertTrue(self.is_sensitive()) def main(): unittest.main() if __name__ == '__main__': main() PIDA-0.5.1/tests/utils/rat/testshiftpaned.py0000644000175000017500000000505710652670574017040 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2006, Tiago Cogumbreiro" import unittest import gtk from pida.utils.rat.shiftpaned import ShiftPaned, SHOW_BOTH, SHOW_CHILD1, SHOW_CHILD2 class TestPaned(unittest.TestCase): def setUp(self): self.paned = ShiftPaned() def assertChild(self, *widget): # Calls the super, dirty hack to get the real elements children = tuple(self.paned.get_children()) self.assertEquals(widget, children) def assertState(self, state): self.assertEquals(state, self.paned.get_state()) def test_paned(self): # It is initially empty self.assertChild() # When it contains only one element it remains empty lbl1 = gtk.Label("left") self.paned.pack1(lbl1) self.assertChild(lbl1) self.assertEquals(self.paned.child1_widget, lbl1) # When it contaisn two elements it cointains the container of the # elements of the given type lbl2 = gtk.Label("right") self.paned.pack2(lbl2) self.assertChild(lbl1, lbl2) self.assertEquals(self.paned.child2_widget, lbl2) # It should begin on 'SHOW_BOTH' state self.assertState(SHOW_BOTH) self.paned.pack2(lbl2) # Changing it to SHOW_BOTH does no effect self.paned.set_state(SHOW_BOTH) self.assertChild(lbl1, lbl2) # Their children should be now filled self.paned.set_state(SHOW_CHILD1) self.assertChild(lbl1) self.assertChild(self.paned.child1_widget) # Changing it to SHOW_BOTH does no effect self.paned.set_state(SHOW_BOTH) self.assertChild(lbl1, lbl2) # Now show the right self.paned.set_state(SHOW_CHILD2) self.assertChild(lbl2) self.assertChild(self.paned.child2_widget) # Their children should be now filled self.paned.set_state(SHOW_CHILD1) self.assertChild(self.paned.child1_widget) # Changing it to SHOW_BOTH does no effect self.paned.set_state(SHOW_BOTH) self.assertChild(lbl1, lbl2) # Now show the right self.paned.set_state(SHOW_CHILD2) self.assertChild(lbl2) self.assertChild(self.paned.child2_widget) # Changing it to SHOW_BOTH does no effect self.paned.set_state(SHOW_BOTH) self.assertChild(lbl1, lbl2) if __name__ == '__main__': unittest.main() PIDA-0.5.1/tests/utils/rat/testtext.py0000644000175000017500000004303410652670574015674 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import unittest from pida.utils.rat.text import * class TestLineIterator(unittest.TestCase): def test_empty(self): buff = gtk.TextBuffer() buff.set_text("") bounds = buff.get_bounds() iters = list(line_iterator(buff, *bounds)) self.assertEquals(1, len(iters)) assert iters[0].equal(bounds[0]) assert iters[0].equal(bounds[1]) def test_one_line(self): buff = gtk.TextBuffer() buff.set_text("\n") bounds = buff.get_bounds() iters = list(line_iterator(buff, *bounds)) self.assertEquals(2, len(iters)) assert iters[0].equal(bounds[0]) assert iters[1].equal(bounds[1]) def test_many_lines(self): buff = gtk.TextBuffer() buff.set_text("a\nb\nbar") bounds = buff.get_bounds() iters = list(line_iterator(buff, *bounds)) self.assertEquals(3, len(iters)) def test_middle_line(self): buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(4) self.assertEquals("23\n\n", buff.get_text(start_iter, end_iter)) iters = list(line_iterator(buff, start_iter, end_iter)) self.assertEquals(3, len(iters)) class TestSelectedLineIterator(unittest.TestCase): def test_empty(self): buff = gtk.TextBuffer() buff.set_text("") iters = list(selected_line_iterator(buff)) self.assertEquals(0, len(iters)) def test_empty2(self): buff = gtk.TextBuffer() buff.set_text("foo\nbar\ngaz") iters = list(selected_line_iterator(buff)) self.assertEquals(0, len(iters)) def test_middle_line(self): # In this case we only select the '2' # The only iterator should be the begining of the first line buff = gtk.TextBuffer() buff.set_text("123\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(1) self.assertEquals("2", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("2", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(1, len(iters)) assert iters[0].equal(buff.get_start_iter()) def test_middle_line2(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("123\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(4) self.assertEquals("23\nb", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("23\nb", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(2, len(iters)) assert iters[0].equal(buff.get_start_iter()) assert iters[1].starts_line() def test_skip_line(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(4) self.assertEquals("23\n\n", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\n", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(1, len(iters)) assert iters[0].equal(buff.get_start_iter()) def test_skip_first_line(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("\n\nbar\ngaz") start_iter = buff.get_start_iter() end_iter = start_iter.copy() end_iter.forward_chars(3) self.assertEquals("\n\nb", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("\n\nb", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(1, len(iters)) def test_skip_middle_line(self): # In this case we select the '23' an empty line and a 'b' # The first iterator should be the begining of the first line # The second iterator should be the begining of the third line buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(5) self.assertEquals("23\n\nb", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\nb", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(2, len(iters)) assert iters[0].equal(buff.get_start_iter()) assert iters[1].starts_line() def test_full(self): # In this case we select the '23' an empty line and a 'b' # The first iterator should be the begining of the first line # The second iterator should be the begining of the third line buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter, end_iter = buff.get_bounds() self.assertEquals("123\n\nbar\ngaz", buff.get_text(start_iter, end_iter)) buff.select_range(start_iter, end_iter) self.assertEquals("123\n\nbar\ngaz", buff.get_text(*buff.get_selection_bounds())) iters = list(selected_line_iterator(buff)) self.assertEquals(3, len(iters)) assert iters[0].equal(buff.get_start_iter()) assert iters[1].starts_line() assert iters[2].starts_line() class TestIndentSelected(unittest.TestCase): def checkSelected(self, buff, text): self.assertEquals(text, buff.get_text(*buff.get_bounds())) def test_empty(self): buff = gtk.TextBuffer() buff.set_text("") indent_selected(buff, "\t") self.checkSelected(buff, "") def test_empty2(self): buff = gtk.TextBuffer() buff.set_text("ffoo\nbar\n") indent_selected(buff, "\t") self.checkSelected(buff, "ffoo\nbar\n") def test_select_middle(self): # In this case we only select the '2' # The only iterator should be the begining of the first line buff = gtk.TextBuffer() buff.set_text("123\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(1) buff.select_range(start_iter, end_iter) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\nbar\ngaz") def test_middle_line2(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("123\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(4) buff.select_range(start_iter, end_iter) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\n\tbar\ngaz") def test_skip_line(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(4) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\n", buff.get_text(*buff.get_selection_bounds())) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\n\nbar\ngaz") def test_skip_first_line(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("\n\nbar\ngaz") start_iter = buff.get_start_iter() end_iter = start_iter.copy() end_iter.forward_chars(3) buff.select_range(start_iter, end_iter) self.assertEquals("\n\nb", buff.get_text(*buff.get_selection_bounds())) indent_selected(buff, "\t") self.checkSelected(buff, "\n\n\tbar\ngaz") def test_skip_middle_line(self): # In this case we select the '23' an empty line and a 'b' # The first iterator should be the begining of the first line # The second iterator should be the begining of the third line buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(5) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\nb", buff.get_text(*buff.get_selection_bounds())) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\n\n\tbar\ngaz") def test_skip_middle_line2(self): buff = gtk.TextBuffer() buff.set_text("123\nbar\n\ngaz") start_iter = buff.get_start_iter() end_iter = start_iter.copy() end_iter.forward_chars(9) # Selected the first two lines buff.select_range(start_iter, end_iter) self.assertEquals("123\nbar\n\n", buff.get_text(*buff.get_selection_bounds())) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\n\tbar\n\ngaz") def test_full(self): # In this case we select the '23' an empty line and a 'b' # The first iterator should be the begining of the first line # The second iterator should be the begining of the third line buff = gtk.TextBuffer() buff.set_text("123\n\nbar\ngaz") start_iter, end_iter = buff.get_bounds() buff.select_range(start_iter, end_iter) self.assertEquals("123\n\nbar\ngaz", buff.get_text(*buff.get_selection_bounds())) indent_selected(buff, "\t") self.checkSelected(buff, "\t123\n\n\tbar\n\tgaz") class TestUnindentSelected(unittest.TestCase): def checkSelected(self, buff, text): self.assertEquals(text, buff.get_text(*buff.get_bounds())) def test_empty(self): buff = gtk.TextBuffer() buff.set_text("") unindent_selected(buff, "\t") self.checkSelected(buff, "") def test_empty2(self): # Because the carret is on the start of the file the first # tab must be removed. buff = gtk.TextBuffer() buff.set_text("\tffoo\nbar\n") # Move the carret to the begining of the text start_iter = buff.get_start_iter() buff.move_mark(buff.get_insert(), start_iter) unindent_selected(buff, "\t") self.checkSelected(buff, "ffoo\nbar\n") def test_select_middle(self): # In this case we only select the '2' # The only iterator should be the begining of the first line buff = gtk.TextBuffer() buff.set_text("\t123\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(1) buff.select_range(start_iter, end_iter) unindent_selected(buff, "\t") self.checkSelected(buff, "123\n\tbar\ngaz") def test_middle_line2(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("\t123\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(6) # '23\n\tb' buff.select_range(start_iter, end_iter) unindent_selected(buff, "\t") self.checkSelected(buff, "123\nbar\ngaz") def test_middle_line3(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("123\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(5) # '23\n\tb' buff.select_range(start_iter, end_iter) unindent_selected(buff, "\t") self.checkSelected(buff, "123\nbar\ngaz") def test_middle_line4(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("\t\t123\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(6) # '23\n\tb' buff.select_range(start_iter, end_iter) unindent_selected(buff, "\t\t") self.checkSelected(buff, "123\nbar\ngaz") def test_middle_line5(self): # In this case we only select the '23\nb' # The first iterator should be the begining of the first line # The second should be the begining of the second line buff = gtk.TextBuffer() buff.set_text("\t\t123\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(1) end_iter = start_iter.copy() end_iter.forward_chars(5) # '23\n\tb' buff.select_range(start_iter, end_iter) unindent_selected(buff, "\t\t") self.checkSelected(buff, "123\n\tbar\ngaz") def test_skip_line(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("\t123\n\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(2) end_iter = start_iter.copy() end_iter.forward_chars(4) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\n", buff.get_text(*buff.get_selection_bounds())) unindent_selected(buff, "\t") self.checkSelected(buff, "123\n\n\tbar\ngaz") def test_skip_line2(self): # In this case we only select the '23' and an empty line # The first iterator should be the begining of the first line # It must be the only iterator there buff = gtk.TextBuffer() buff.set_text("\t123\n\n\tbar\ngaz") start_iter = buff.get_start_iter() start_iter.forward_chars(2) end_iter = start_iter.copy() end_iter.forward_chars(5) buff.select_range(start_iter, end_iter) self.assertEquals("23\n\n\t", buff.get_text(*buff.get_selection_bounds())) unindent_selected(buff, "\t") self.checkSelected(buff, "123\n\nbar\ngaz") def test_full(self): # In this case we select the '23' an empty line and a 'b' # The first iterator should be the begining of the first line # The second iterator should be the begining of the third line buff = gtk.TextBuffer() buff.set_text("\t123\n\n\tbar\n\tgaz") start_iter, end_iter = buff.get_bounds() buff.select_range(start_iter, end_iter) self.assertEquals("\t123\n\n\tbar\n\tgaz", buff.get_text(*buff.get_selection_bounds())) unindent_selected(buff, "\t") self.checkSelected(buff, "123\n\nbar\ngaz") def test_chomp(self): """This test verifies if unindent eats characters or not""" def checkUnindent(src, dst): buff = gtk.TextBuffer() buff.set_text(src) buff.select_range(*buff.get_bounds()) unindent_selected(buff, " ") self.assertEquals(dst, buff.get_text(*buff.get_bounds())) checkUnindent("foo", "foo") checkUnindent(" foo", "foo") checkUnindent(" foo", "foo") checkUnindent(" foo", "foo") checkUnindent(" foo", "foo") checkUnindent(" " * 5 + "foo", " " * 1 + "foo") checkUnindent(" " * 6 + "foo", " " * 2 + "foo") checkUnindent(" " * 7 + "foo", " " * 3 + "foo") checkUnindent(" " * 8 + "foo", " " * 4 + "foo") # TODO: when the text is indented the start should move back to the start of the # of the line # and the end should finish on the end of the line if __name__ == '__main__': unittest.main() PIDA-0.5.1/tests/utils/rat/testutil.py0000644000175000017500000001475310652670574015673 0ustar aliali__license__ = "MIT " __author__ = "Tiago Cogumbreiro " __copyright__ = "Copyright 2005, Tiago Cogumbreiro" import unittest import gtk import gobject from pida.utils.rat.util import * list_children = lambda *args, **kwargs: list(iterate_widget_children(*args, **kwargs)) list_parents = lambda *args, **kwargs: list(iterate_widget_parents(*args, **kwargs)) count_children = lambda *args, **kwargs: len(list_children(*args, **kwargs)) count_parents = lambda *args, **kwargs: len(list_parents(*args, **kwargs)) class TestWidgetIterators(unittest.TestCase): def test_count_children(self): container = gtk.VBox() # The number of children of the container starts at empty self.assertEqual(count_children(container), 0) # The number of children of the container starts at empty self.assertEqual(count_children(container, recurse_children=True), 0) container2 = gtk.VBox() # Add a sub-container to a container container.add(container2) # It affects the container where it was added self.assertEqual(count_children(container), 1) assert list_children(container)[0] is container2 # It affects the container where it was added self.assertEqual(count_children(container, recurse_children=True), 1) assert list_children(container, recurse_children=True)[0] is container2 # The number of children of the sub-container starts at empty self.assertEqual(count_children(container2), 0) # The number of children of the sub-container starts at empty self.assertEqual(count_children(container2, recurse_children=True), 0) # Adding a container in the sub-container lbl1 = gtk.Label() container2.add(lbl1) # It does not affect the children of main container self.assertEqual(count_children(container), 1) self.assertEqual(list_children(container), [container2]) # It affects the list of all children (recurring) self.assertEqual(count_children(container, recurse_children=True), 2) self.assertEqual(list_children(container, recurse_children=True), [container2, lbl1]) # It affects the container where it was added self.assertEqual(count_children(container2), 1) self.assertEqual(list_children(container2), [lbl1]) # It affects the container where it was added self.assertEqual(count_children(container2, recurse_children=True), 1) self.assertEqual(list_children(container2, recurse_children=True), [lbl1]) # Adding a child to the main container should not affect the count of elements lbl2 = gtk.Label() container.add(lbl2) self.assertEqual(count_children(container), 2) self.assertEqual(list_children(container), [container2, lbl2]) self.assertEqual(count_children(container, recurse_children=True), 3) self.assertEqual(list_children(container, recurse_children=True), [container2, lbl1, lbl2]) self.assertEqual(count_children(container2), 1) self.assertEqual(list_children(container2), [lbl1]) self.assertEqual(count_children(container2, recurse_children=True), 1) self.assertEqual(list_children(container2, recurse_children=True), [lbl1]) def test_iterate_parents(self): vbox1 = gtk.VBox() self.assertEqual(list_parents(vbox1),[]) vbox2 = gtk.VBox() vbox1.add(vbox2) self.assertEqual(list_parents(vbox2),[vbox1]) vbox3 = gtk.VBox() vbox1.add(vbox3) self.assertEqual(list_parents(vbox3),[vbox1]) vbox4 = gtk.VBox() vbox2.add(vbox4) self.assertEqual(list_parents(vbox4),[vbox2, vbox1]) vbox5 = gtk.VBox() vbox4.add(vbox5) self.assertEqual(list_parents(vbox5),[vbox4, vbox2, vbox1]) def test_get_root_parent(self): # Adding widgets in different depths maintains the root widget vbox1 = gtk.VBox() self.assertEqual(get_root_parent(vbox1), None) vbox2 = gtk.VBox() vbox1.add(vbox2) self.assertEqual(get_root_parent(vbox2), vbox1) vbox3 = gtk.VBox() vbox2.add(vbox3) self.assertEqual(get_root_parent(vbox3), vbox1) self.assertEqual(get_root_parent(vbox2), vbox1) def test_find_child_widget(self): w1 = gtk.VBox() w1.set_name("w1") self.assertEqual(find_child_widget(w1, "w1"), w1) #fails so commented out #self.assertEqual(find_child_widget(w1, "foo"), None) w2 = gtk.VBox() w2.set_name("w2") w1.add(w2) self.assertEqual(find_child_widget(w1, "w2"), w2) w3 = gtk.VBox() w3.set_name("w3") w2.add(w3) self.assertEqual(find_child_widget(w1, "w3"), w3) self.assertEqual(find_child_widget(w2, "w3"), w3) self.assertEqual(find_child_widget(w3, "w3"), w3) def test_find_parent_widget(self): w1 = gtk.VBox() w1.set_name("w1") self.assertEqual(find_parent_widget(w1, "w1"), w1) # Fails, this raises #self.assertEqual(find_parent_widget(w1, "w1", find_self=False), None) #self.assertEqual(find_parent_widget(w1, "foo"), None) w2 = gtk.VBox() w2.set_name("w2") w1.add(w2) self.assertEqual(find_parent_widget(w2, "w1"), w1) w3 = gtk.VBox() w3.set_name("w3") w2.add(w3) self.assertEqual(find_parent_widget(w1, "w1"), w1) #self.assertEqual(find_parent_widget(w1, "w1", find_self=False), None) self.assertEqual(find_parent_widget(w2, "w1"), w1) self.assertEqual(find_parent_widget(w3, "w1"), w1) def test_ListSpec(self): spec = ListSpec(("A", gobject.TYPE_STRING), ("B", gobject.TYPE_INT)) self.assertEqual(spec.A, 0) self.assertEqual(spec.B, 1) store = spec.create_list_store() self.assertEqual(store.get_n_columns(), 2) self.assertEqual(store.get_column_type(spec.A), gobject.TYPE_STRING) self.assertEqual(store.get_column_type(spec.B), gobject.TYPE_INT) row = spec.to_tree_row({spec.A: "foo", spec.B: 1}) self.assertEqual(row, ["foo", 1]) store.append(row) row = store[0] self.assertEqual(row[spec.A], "foo") self.assertEqual(row[spec.B], 1) def main(): unittest.main() if __name__ == '__main__': main() PIDA-0.5.1/tests/utils/__init__.py0000644000175000017500000000000010652670574014743 0ustar alialiPIDA-0.5.1/tests/__init__.py0000644000175000017500000000000010652670576013605 0ustar alialiPIDA-0.5.1/tools/0002755000175000017500000000000010652671503011475 5ustar alialiPIDA-0.5.1/tools/glade3-plugin/0002755000175000017500000000000010652671503014130 5ustar alialiPIDA-0.5.1/tools/glade3-plugin/py-widget/0002755000175000017500000000000010652671503016041 5ustar alialiPIDA-0.5.1/tools/glade3-plugin/py-widget/pywidgets.py0000644000175000017500000000335310652670574020443 0ustar alialiimport gtk, gobject import glade class ServiceView(gtk.VBox): __gtype_name__ = 'ServiceView' def __init__(self): self._frame = gtk.Frame() self.pack_start(self._frame) self._bb = gtk.HButtonBox() self._bb.set_layout(gtk.BUTTONBOX_END) self._bb.pack_start(gtk.Button(stock=gtk.STOCK_CLOSE)) self.pack_start(self._bb, expand=False) def add_main_widget(self, widget): self._frame.add(widget) def remove_main_widget(self, widget): self._frame.remove(widget) def do_add(self, widget): self.add_main_widget(widget) class ServiceViewAdaptor(glade.get_adaptor_for_type('GtkVBox')): __gtype_name__ = 'ServiceViewAdaptor' def do_post_create(self, sv, reason): sv.add_main_widget(gobject.new('GladePlaceholder')) def do_get_children(self, obj): return obj._frame.get_children() def do_add(self, sv, child): sv.add_main_widget(child) def do_remove(self, sv, child): sv.remove_main_widget(child) def do_child_get_property(self, sv, child, prop): if prop in ['expand', 'fill']: return True elif prop == 'padding': return 0 elif prop == 'position': return 0 elif prop == 'pack-type': return gtk.PACK_START return True def do_child_set_property(self, sv, child, prop, val): if prop in ['expand', 'fill', 'padding', 'pack-type']: pass def do_replace_child(self, sv, old, new): sv.remove_main_widget(old) sv.add_main_widget(new) #def do_child_verify_property(self, sv, *args): # print 'dcvp', args #def do_get_internal_child(self, sv, *args): # print 'dgic', sv, args PIDA-0.5.1/tools/glade3-plugin/py-widget/pywidgets.xml0000644000175000017500000000127510652670574020614 0ustar aliali PIDA-0.5.1/tools/glade3-plugin/py-widget/serviceview.py0000644000175000017500000000136410652670574020757 0ustar aliali import gobject import gtk class ServiceView(gtk.VBox): __gtype_name__ = 'ServiceView' def __init__(self): self._frame = gtk.Frame() self.pack_start(self._frame) self._bb = gtk.HButtonBox() self._bb.set_layout(gtk.BUTTONBOX_END) self._bb.pack_start(gtk.Button(stock=gtk.STOCK_CLOSE)) self.pack_start(self._bb, expand=False) def add_main_widget(self, widget): self._frame.add(widget) def remove_main_widget(self, widget): self._frame.remove(widget) def do_add(self, widget): self.add_main_widget(widget) def test(): from gtk.glade import XML XML('test.glade').get_widget('window1').show_all() gtk.main() if __name__ == '__main__': test() PIDA-0.5.1/tools/glade3-plugin/py-widget/setup.py0000644000175000017500000000121410652670574017556 0ustar alialifrom distutils.core import setup from os.path import join from kiwi.dist import listfiles glade3_prefix = '/usr/local' setup( data_files=[ (join(glade3_prefix, 'share', 'glade3', 'catalogs'), ('pywidgets.xml',)), (join(glade3_prefix, 'lib', 'glade3', 'modules'), ('pywidgets.py',)), #(join(glade3_prefix, 'share', 'glade3', 'pixmaps', '22x22'), # listfiles('..', 'gazpacho-plugin', 'resources', 'kiwiwidgets', '*.png')), #(join(glade3_prefix, 'share', 'glade3', 'pixmaps', '16x16'), # listfiles('..', 'gazpacho-plugin', 'resources', 'kiwiwidgets', '*.png')), ] ) PIDA-0.5.1/tools/glade3-plugin/py-widget/test.glade0000644000175000017500000000207410652670574020026 0ustar aliali GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK True GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK label PIDA-0.5.1/tools/glade3-plugin/kiwiwidgets.py0000644000175000017500000000110610652670574017037 0ustar alialiimport gtk import gobject import glade from kiwi.ui.hyperlink import HyperLink from kiwi.ui.objectlist import ObjectList, ObjectTree, Column from kiwi.ui.widgets.label import ProxyLabel from kiwi.ui.widgets.combo import ProxyComboEntry, ProxyComboBox from kiwi.ui.widgets.checkbutton import ProxyCheckButton from kiwi.ui.widgets.radiobutton import ProxyRadioButton from kiwi.ui.widgets.entry import ProxyEntry, ProxyDateEntry from kiwi.ui.widgets.spinbutton import ProxySpinButton from kiwi.ui.widgets.textview import ProxyTextView from kiwi.ui.widgets.button import ProxyButton PIDA-0.5.1/tools/glade3-plugin/kiwiwidgets.xml0000644000175000017500000000723210652670574017215 0ustar aliali PIDA-0.5.1/tools/glade3-plugin/pidawidgets.py0000644000175000017500000000237510652670574017022 0ustar aliali import glade import gtk, gobject import sys sys.path.insert(0, '/home/ali/working/pida-next/') from pida.core import environment from pida.ui.views import PidaViewWidget from vte import Terminal as VteTerminal from kiwi.utils import gproperty, PropertyObject class PidaViewWidgetAdaptor(glade.get_adaptor_for_type('GtkVBox')): __gtype_name__ = 'PidaViewWidgetAdaptor' def do_post_create(self, pvw, reason): pvw.add_main_widget(gobject.new('GladePlaceholder')) def do_replace_child(self, pvw, old, new): pvw.remove_main_widget() pvw.add_main_widget(new) def do_get_children(self, pvw): return [pvw.get_main_widget()] def do_add(self, pvw, child): pvw.add_main_widget(child) def do_remove(self, pvw, child): pvw.remove_main_widget() def do_child_get_property(self, pvw, child, prop): if prop in ['expand', 'fill']: return True elif prop == 'padding': return 0 elif prop == 'position': return 0 elif prop == 'pack-type': return gtk.PACK_START return True def do_child_set_property(self, pvw, child, prop, val): if prop in ['expand', 'fill', 'padding', 'pack-type']: pass PIDA-0.5.1/tools/glade3-plugin/pidawidgets.xml0000644000175000017500000000203210652670574017160 0ustar aliali PIDA-0.5.1/tools/glade3-plugin/setup.py0000644000175000017500000000052610652670574015652 0ustar alialifrom distutils.core import setup from os.path import join from kiwi.dist import listfiles glade3_prefix = '/usr/local' setup( data_files=[ (join(glade3_prefix, 'share', 'glade3', 'catalogs'), ('pidawidgets.xml',)), (join(glade3_prefix, 'lib', 'glade3', 'modules'), ('pidawidgets.py',)), ] ) PIDA-0.5.1/tools/create-po.sh0000755000175000017500000000100310652670574013712 0ustar aliali#!/usr/bin/env bash if [ "X$1" == "X" ]; then echo "Usage: ./create-po.sh fr_FR" exit fi FILES=`find -iname '*.py' | grep -v -E 'pida/services|pida/utils|contrib'` mkdir -p pida/resources/locale/$1/LC_MESSAGES/ if [ -f pida/resource/locale/$1/LC_MESSAGES/pida.po ]; then xgettext -o new.po $FILES sed -e 's/CHARSET/utf-8/' new.po > new2.po mv new2.po new.po msgmerge -U pida/resources/locale/$1/LC_MESSAGES/pida.po new.po rm new.po else xgettext -o pida/resources/locale/$1/LC_MESSAGES/pida.po $FILES fi PIDA-0.5.1/tools/creator.py0000644000175000017500000001640710652670574013523 0ustar aliali from optparse import OptionParser import sys, os from datetime import date def log(txt): print txt python_module_template = '''# -*- coding: utf-8 -*- # Copyright (c) %(year)s %(copyright_holder)s #Permission is hereby granted, free of charge, to any person obtaining a copy #of this software and associated documentation files (the "Software"), to deal #in the Software without restriction, including without limitation the rights #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the Software is #furnished to do so, subject to the following conditions: #The above copyright notice and this permission notice shall be included in #all copies or substantial portions of the Software. #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #SOFTWARE. %(system_imports)s %(gtk_imports)s %(library_imports)s %(pida_imports)s %(content)s # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: ''' def create_python_file(root_path, name, content, copyright_holder='The PIDA Project', s_imports=[], g_imports=[], l_imports=[], p_imports=[]): if not name.endswith('.py'): name = '%s.py' % name path = os.path.join(root_path, name) f = open(path, 'w') if s_imports: s_imports.insert(0, '# Standard Library Imports') sys_imports = '\n'.join(s_imports) if g_imports: g_imports.insert(0, '# GTK Imports') gtk_imports = '\n'.join(g_imports) if l_imports: l_imports.insert(0, '# Other Imports') library_imports = '\n'.join(l_imports) if p_imports: p_imports.insert(0, '# PIDA Imports') pida_imports = '\n'.join(p_imports) data = python_module_template % dict( content=content, copyright_holder=copyright_holder, system_imports=sys_imports, gtk_imports=gtk_imports, library_imports=library_imports, pida_imports=pida_imports, year=date.today().year, ) f.write(data) f.close() class ServiceCreator(object): service_template = ''' # locale from pida.core.locale import Locale locale = Locale('%(lowername)s') _ = locale.gettext # Service class class %(name)s(Service): """Describe your Service Here""" # Required Service attribute for service loading Service = %(name)s ''' service_template_imports = dict( p_imports = [ 'from pida.core.service import Service', 'from pida.core.features import FeaturesConfig', 'from pida.core.commands import CommandsConfig', 'from pida.core.events import EventsConfig', 'from pida.core.actions import ActionsConfig', 'from pida.core.options import OptionsConfig', 'from pida.core.actions import TYPE_NORMAL, TYPE_MENUTOOL, TYPE_RADIO, TYPE_TOGGLE', ] ) def __init__(self, root_path, name, opts): self._name = name self._root_path = root_path self._path = os.path.abspath(os.path.join(self._root_path, self._name)) self._opts = opts def _create_service_text(self): return self.service_template % dict(name=self._name.capitalize(), lowername=self._name) def _create_service_dir(self): if not os.path.isdir(self._root_path): raise ValueError('The root path is not a directory') if os.path.exists(self._path): raise ValueError('The plugin directory already exists') else: os.mkdir(self._path) def _create_service_module(self): contents = self._create_service_text() create_python_file(self._path, self._name, contents, **self.service_template_imports) create_python_file(self._path, '__init__', '') create_python_file(self._path, 'test_%s' % self._name, '') def _create_servicefile(self): servicefile_path = os.path.join(self._path, 'service.pida') f = open(servicefile_path, 'w') f.close() def _create_resource_dirs(self): for res in ['glade', 'data', 'uidef', 'pixmaps', 'locale']: respath = os.path.join(self._path, res) os.mkdir(respath) def _add_svn(self): if self._opts.add_svn: os.system('svn add %s' % self._path) def create(self): log('Creating service %s in %s' % (self._name, self._root_path)) self._create_service_dir() self._create_service_module() self._create_servicefile() self._create_resource_dirs() self._add_svn() class ModuleCreator(object): def __init__(self, root_path, name, opts): self._name = name self._root_path = root_path self._path = os.path.join(self._root_path, name) self._opts = opts def _create_module(self): create_python_file(self._root_path, self._name, '') def _create_test_module(self): create_python_file(self._root_path, 'test_%s' % self._name, '', s_imports=[ 'from unittest import TestCase' ] ) def create(self): self._create_module() self._create_test_module() self._add_svn() def _add_svn(self): if self._opts.add_svn: os.system('svn add %s' % self._path) def get_service_details(): defpath = os.path.join(os.getcwd(), 'pida', 'services') path = raw_input('Please enter the path for the service [%s]: ' % defpath) if not path: path = defpath name = '' while not name: name = raw_input('Please enter the Service name: ') return path, name.lower() def get_module_details(): defpath = os.getcwd() path = raw_input('Please enter the path for the module [%s]: ' % defpath) if not path: path = defpath name = '' while not name: name = raw_input('Please enter the module name: ') return path, name.lower() def prime_parser(): usage = "usage: %prog [options] module|m|service|s" parser = OptionParser(usage=usage) parser.add_option('-n', '--no-svn', help='Do not add the creation to the subversion repo', dest='add_svn', action='store_false', default=True) return parser def main(): parser = prime_parser() opts, args = parser.parse_args() if not args: parser.error('You must provide an action') act = args[0] if act in ['module', 'm']: path, name = get_module_details() ModuleCreator(path, name, opts).create() elif act in ['service', 's']: path, name = get_service_details() ServiceCreator(path, name, opts).create() else: parser.error('The action must be one of "module" or "m" for a module,' ' or "service" or "s" for a service') if __name__ == '__main__': main() PIDA-0.5.1/tools/generate-handbook.sh0000644000175000017500000000035210652670574015413 0ustar alialiasciidoc -a icons -a toc -a numbered -a toclevels=4 docs/txt/handbook.txt echo "Now:" echo " Change the id=\"header\" to class=\"header\"" echo " Move the TOC script inside the body" echo " Copy the toc stylesheet inside the body" PIDA-0.5.1/tools/generate_docs.sh0000644000175000017500000000017110652670574014637 0ustar aliali#! /bin/sh a2x -f xhtml -d docs/html/ docs/txt/dev-services.txt a2x -f xhtml -d docs/html/ docs/txt/dev-coding-style.txt PIDA-0.5.1/tools/locale.sh0000755000175000017500000000746710652670574013316 0ustar aliali#!/usr/bin/env bash function usage() { echo "Usage: tools/locale.sh [build|update|create] " echo "Examples:" echo " - Build all locales : tools/locale.sh build" echo " - Create po for a service : tools/locale.sh create service fr_FR" echo " - Update po for a service : tools/locale.sh update service fr_FR" echo " - Update po for pida : tools/locale.sh update pida fr_FR" } function locale_build() { echo "Searching all 'po' files..." for file in `find . -iname '*.po' | grep -v 'contrib'`; do echo -n "Processing $file... " msgfmt -o `dirname $file`/`basename $file .po`.mo $file echo "OK" done echo "Build done." } function locale_create() { if [ ! -d "pida/services/$1" ]; then echo "Service '$1' not exist" exit fi cd pida/services/$1 dir="locale/$2/LC_MESSAGES" file="$dir/$1.po" if [ -f "$dir/$1.po" ]; then cd ../../.. echo "Service '$1' have already a po file, use update instead of create" echo "Remove this file if you really want to create it" exit fi echo "Create directory $dir" mkdir -p $dir echo "Generate temporary .h for glade files" find glade -iname '*.glade' -exec intltool-extract --type=gettext/glade {} \; echo "Extract messages" xgettext -k_ -kN_ -o $file `ls *.py glade/*.glade.h 2>/dev/null` echo "Update some info" sed -e 's/CHARSET/utf-8/' $file > $file.bak sed -e 's/SOME\ DESCRIPTIVE\ TITLE/PIDA/' $file.bak > $file sed -e "s/YEAR\ THE\ PACKAGE'S\ COPYRIGHT\ HOLDER/The\ PIDA\ Team/" $file > $file.bak sed -e 's/the\ PACKAGE\ package/the\ PIDA\ package/' $file.bak > $file rm $file.bak 2>/dev/null rm glade/*.glade.h 2>/dev/null echo "Done." echo "" echo `grep 'msgstr ""' $file | wc -l` "messages need to be translated (approx)" echo "Now edit file pida/services/$1/$file" cd ../../.. } function locale_update() { if [ ! -d "pida/services/$1" ]; then echo "Service '$1' not exist" exit fi cd pida/services/$1 dir="locale/$2/LC_MESSAGES" file="$dir/$1.po" if [ ! -f "$dir/$1.po" ]; then cd ../../.. echo "Service '$1' don't have a po file, use create instead of update" exit fi echo "Generate temporary .h for glade files" find glade -iname '*.glade' -exec intltool-extract --type=gettext/glade {} \; echo "Extract messages" xgettext --omit-header --foreign-user -k_ -kN_ -o $file.new `ls *.py glade/*.glade.h 2>/dev/null` echo "Merging messages" msgmerge -U $file $file.new rm $file.new 2>/dev/null rm glade/*.glade.h 2>/dev/null echo "Done." echo "" echo `grep 'msgstr ""' $file | wc -l` "messages need to be translated (approx)" echo "Now edit file pida/services/$1/$file" cd ../../.. } function locale_update_pida() { cd pida dir="resources/locale/$1/LC_MESSAGES" file="$dir/pida.po" if [ ! -f "$file" ]; then cd .. echo "Pida po file don't exist ?!" exit fi echo "Generate temporary .h for glade files" find resources/glade -iname '*.glade' -exec intltool-extract --type=gettext/glade {} \; echo "Find py files" files=`find . -iname '*py' | grep -v -E '\/.svn\/|\/services\/|\/utils\/feedparser.py'` echo "Extract messages" xgettext --omit-header --foreign-user -k_ -kN_ -o $file.new $files resources/glade/*.glade.h echo "Merging messages" msgmerge -U $file $file.new rm $file.new 2>/dev/null rm resources/glade/*.glade.h 2>/dev/null echo "Done." echo "" echo `grep 'msgstr ""' $file | wc -l` "messages need to be translated (approx)" echo "Now edit file pida/$file" cd .. } case "$1" in "build") locale_build ;; "update") if [ "X$2" == "X" ]; then usage exit fi if [ "X$3" == "X" ]; then usage exit fi if [ "$2" == "pida" ]; then locale_update_pida $3 else locale_update $2 $3 fi ;; "create") if [ "X$2" == "X" ]; then usage exit fi if [ "X$3" == "X" ]; then usage exit fi locale_create $2 $3 ;; *) usage ;; esac PIDA-0.5.1/tools/pida-pydoctor.cfg0000644000175000017500000000030610652670574014740 0ustar alialipackages: pida projectname: PIDA htmloutput: docs/api makehtml: True htmlusesorttable: True htmlusesplitlinks: True htmlshortenlists: True docformat: restructuredtext projecturl: http://pida.co.uk/ PIDA-0.5.1/tools/remove-glade-requires.sh0000644000175000017500000000007410652670574016243 0ustar alialifind . -name "*.glade" | xargs sed -i -e "s/.*requires.*//" PIDA-0.5.1/tools/snippet-creator.py0000644000175000017500000000156710652670574015204 0ustar aliali import sys import os meta_tmpl = """[meta] name = title = shortcut = [variables] """ def get_location(): location = raw_input('Directory to store the snippet: [~/.pida2/snippets/]: ') location = os.path.expanduser(location.strip()) if not location: location = os.path.expanduser('~/.pida2/snippets/') return location def get_name(): name = raw_input('Enter snippet name: ') name = name.strip() if not name: raise Exception('You must enter a name') else: return name def create_snippet(location, name): base = os.path.join(location, name) meta = base + '.meta' tmpl = base + '.tmpl' f = open(meta, 'w') f.write(meta_tmpl) f.close() open(tmpl, 'w').close() def main(): location = get_location() name = get_name() create_snippet(location, name) if __name__ == '__main__': main() PIDA-0.5.1/tools/test_coverage.py0000644000175000017500000000311610652670574014707 0ustar aliali import subprocess import gtk from kiwi.ui.objectlist import ObjectList, Column, ColoredColumn p = subprocess.Popen(['nosetests', '--with-coverage', '--cover-package=pida'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) class Line(object): def __init__(self, vals): self.name = vals[0] self.percentage = int(vals[3].rstrip('%')) self.missing = ' '.join(vals[4:]) class Reader(object): def __init__(self, p): self._p = p self._on = False self._lines = [] for line in self._p.stderr: if self._on: vals = line.strip().split() if len(vals) >= 3: if '%' in vals[3]: self._lines.append(Line(vals)) if line.startswith('---'): self._on = not self._on def build_tree(self): w = gtk.Window() w.connect('delete-event', lambda *a: gtk.main_quit()) ol = ObjectList( [ Column('name'), ColoredColumn('percentage', data_type=int, sorted=True, color='red', data_func = lambda i: i < 100), Column('missing') ], sortable = True ) self._lines.sort(lambda x, y: x.percentage - y.percentage) for line in self._lines: ol.append(line) w.set_title('PIDA Tests Coverage') w.resize(600, 900) w.add(ol) w.show_all() gtk.main() if __name__ == '__main__': Reader(p).build_tree() PIDA-0.5.1/AUTHORS0000644000175000017500000000057410652670647011421 0ustar alialiThe Python Integrated Development Application IDE Framework 0.5.0 version by: Ali Afshar Tiago Cogumbreiro Alejandro Mery Bernard Pratz Mathieu Virbel Ronny Pfannschmidt Anders Conbere ' David Soulayrol PIDA-0.5.1/CHANGELOG0000644000175000017500000000262110652670647011556 0ustar aliali0.5.1 "Rhubarb Crumble" Features [1119] Added History to status bar. [1118] Added First run wizard. [1122] Shortcut to focus editor. [1129] Allow projects to change their names. Launchpad bug:123323. Closes #141. [1133] Shortcut to go to parent directory in file manager. Closes #111. [1137] Shortcut to focus terminal pane. Closes #143. [1138] Project-relative filenames in buffer list. [1170] Completion hooks for vim. [1178] Allow setting keyboard shortcuts for basic editor actions. [1182] Added and option to use Gamin. False by default. Closes #153. Bugs [1107] Corrected AUTHORS file. [1121] Update context menus on plugin load/unload. Fixes #122. [1126] Fixed bug reporting mechanism. [1130] Allow Vim filenames with commas. LP: 78773. Fixes: #142. [1132] Add missing subversion state Fixes #123. [1134] Handle pending merges output for bzr. Fixes LP 76368. [1162] Fix crash on plugin loading errors. Fixes #145. [1163] Allow PIDA to be started on any screen. Fixes #146. [1171] Add commit support for GIT. [1180] Good error message when Gvim is not isntalled. Fixes #152. [1186] Ensure plugin pixmaps get registered as stock icons. Fixes #154. [1189] Add missing Emacs UI Definition. Non Core [1111] Prototype debugger plugin based on WinPDB. [1115] Alphapetically sort documentation library book list. [1152] Python source viewer now jumps to line. [1177] Koders.com plugin. 0.5.0 "Strawberries and Cream" PIDA-0.5.1/COPYING0000644000175000017500000000205110652670647011374 0ustar alialiCopyright (c) 2005-2006 The PIDA Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PIDA-0.5.1/INSTALL0000644000175000017500000000031610652670647011374 0ustar aliali= PIDA, The Python Integrated Development Application In an attempt to not duplicate documentation, please read the handbook in the docs/txt (text version) or docs/html (HTML version) of this source code. PIDA-0.5.1/README0000644000175000017500000000031610652670647011223 0ustar aliali= PIDA, The Python Integrated Development Application In an attempt to not duplicate documentation, please read the handbook in the docs/txt (text version) or docs/html (HTML version) of this source code. PIDA-0.5.1/run-pida-remote.sh0000755000175000017500000000004610652670647013712 0ustar alialiPYTHONPATH='.' python bin/pida-remote PIDA-0.5.1/run-pida.sh0000755000175000017500000000014310652670647012417 0ustar aliali#!/bin/sh PIDA_PATH=`dirname $0` PYTHONPATH="$PYTHONPATH:$PIDA_PATH" python $PIDA_PATH/bin/pida $* PIDA-0.5.1/setup.py0000644000175000017500000000504510652670647012061 0ustar aliali""" The PIDA Installer """ import os from distutils.core import setup, Extension from distutils.command.build_ext import build_ext from pida import PIDA_VERSION, PIDA_AUTHOR, PIDA_WEBSITE, \ PIDA_SHORT_DESCRIPTION, PIDA_NAME # Moo Extension from moo.dsutils import pkc_get_include_dirs, pkc_get_libraries, pkc_get_library_dirs moo = Extension( 'moo_stub', [ 'moo/moopane.c', 'moo/moopaned.c', 'moo/moobigpaned.c', 'moo/moomarshals.c', 'moo/moo-pygtk.c', 'moo/moo-stub.c', ], include_dirs=pkc_get_include_dirs('gtk+-2.0 pygtk-2.0'), libraries=pkc_get_libraries('gtk+-2.0 pygtk-2.0'), library_dirs=pkc_get_library_dirs('gtk+-2.0 pygtk-2.0'), ) class BuildExt(build_ext): def build_extension(self, ext): if ext.name == 'moo_stub': if os.system('cd moo && make prepare'): raise RuntimeError() build_ext.build_extension(self, ext) # Modified from kiwi def listpackages(root): packages = [] if os.path.exists(os.path.join(root, '__init__.py')): packages.append(root.replace('/', '.')) for filename in os.listdir(root): full = os.path.join(root, filename) if os.path.isdir(full): packages.extend(listpackages(full)) return packages def list_pida_packages(): packages = [] for package in ['pida', 'pida/core', 'pida/ui', 'pida/utils']: packages.extend(listpackages(package)) return packages def list_pida_services(package_data): packages = listpackages('pida/services') + listpackages('pida/editors') for package in packages: package_data[package] = [ 'service.pida', 'glade/*', 'pixmaps/*', 'uidef/*', 'data/*', 'locale/fr_FR/LC_MESSAGES/*', ] return packages def get_main_data(): return { 'pida': [ 'resources/glade/*', 'resources/pixmaps/*', 'resources/uidef/*', 'resources/locale/fr_FR/LC_MESSAGES/*' ] } all_package_data = get_main_data() all_packages = list_pida_packages() + list_pida_services(all_package_data) setup( name = PIDA_NAME, version = PIDA_VERSION, packages = all_packages, package_data = all_package_data, ext_modules = [moo], cmdclass={'build_ext': BuildExt}, scripts=['bin/pida', 'bin/pida-remote'], author = PIDA_AUTHOR, author_email = PIDA_AUTHOR, url = PIDA_WEBSITE, download_url = PIDA_WEBSITE + 'download/', description = PIDA_SHORT_DESCRIPTION, ) PIDA-0.5.1/PKG-INFO0000644000175000017500000000056310652671503011434 0ustar alialiMetadata-Version: 1.0 Name: PIDA Version: 0.5.1 Summary: An intergated development environment that reuses tools such as Vim, and all version control systems. Home-page: http://pida.co.uk/ Author: Ali Afshar Author-email: Ali Afshar License: UNKNOWN Download-URL: http://pida.co.uk/download/ Description: UNKNOWN Platform: UNKNOWN