[tryton-debian-vcs] goocalendar branch upstream created. 1ec25723a1daecd5adf16c3352fd4ba789d79cc2

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Wed Nov 27 16:48:09 UTC 2013


The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/goocalendar.git;a=commitdiff;h=1ec25723a1daecd5adf16c3352fd4ba789d79cc2
commit 1ec25723a1daecd5adf16c3352fd4ba789d79cc2
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Oct 15 18:51:33 2013 +0200

    Adding upstream version 0.1.

diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..416204d
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,2 @@
+Version 0.1 - 2013-02-18
+* Initial release
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..2e334b4
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,16 @@
+Copyright (C) 2012 Antoine Smolders
+Copyright (C) 2012-2013 Cédric Krier
+Copyright (C) 2007 Samuel Abels <http://debain.org>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License version 2, as
+published by the Free Software Foundation.
+
+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
diff --git a/GooCalendar.egg-info/PKG-INFO b/GooCalendar.egg-info/PKG-INFO
new file mode 100644
index 0000000..c9fd974
--- /dev/null
+++ b/GooCalendar.egg-info/PKG-INFO
@@ -0,0 +1,28 @@
+Metadata-Version: 1.1
+Name: GooCalendar
+Version: 0.1
+Summary: A calendar widget for GTK using PyGoocanvas
+Home-page: http://code.google.com/p/goocalendar/
+Author: Cédric Krier
+Author-email: cedric.krier at b2ck.com
+License: GPL-2
+Download-URL: http://code.google.com/p/goocalendar/downloads/
+Description: GooCalendar
+        ===========
+        
+        A calendar widget for GTK using PyGoocanvas
+        
+        Nutshell
+        --------
+        
+        .. TODO
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: X11 Applications :: GTK
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2 :: Only
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Widget Sets
diff --git a/GooCalendar.egg-info/SOURCES.txt b/GooCalendar.egg-info/SOURCES.txt
new file mode 100644
index 0000000..cce47d8
--- /dev/null
+++ b/GooCalendar.egg-info/SOURCES.txt
@@ -0,0 +1,18 @@
+CHANGELOG
+COPYRIGHT
+INSTALL
+LICENSE
+MANIFEST.in
+README
+setup.py
+GooCalendar.egg-info/PKG-INFO
+GooCalendar.egg-info/SOURCES.txt
+GooCalendar.egg-info/dependency_links.txt
+GooCalendar.egg-info/top_level.txt
+doc/Makefile
+doc/conf.py
+doc/index.rst
+goocalendar/__init__.py
+goocalendar/_calendar.py
+goocalendar/_event.py
+goocalendar/util.py
\ No newline at end of file
diff --git a/GooCalendar.egg-info/dependency_links.txt b/GooCalendar.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/GooCalendar.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/GooCalendar.egg-info/top_level.txt b/GooCalendar.egg-info/top_level.txt
new file mode 100644
index 0000000..cdeff5e
--- /dev/null
+++ b/GooCalendar.egg-info/top_level.txt
@@ -0,0 +1 @@
+goocalendar
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..281d620
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,26 @@
+Installing GooCalendar
+======================
+
+Prerequisites
+-------------
+
+ * Python (http://www.python.org/)
+ * PyGoocanvas (https://live.gnome.org/PyGoocanvas)
+
+Installation
+------------
+
+Once you've downloaded and unpacked the GooCalendar source release, enter the
+directory where the archive was unpacked, and run:
+
+    python setup.py install
+
+Note that you may need administrator/root privileges for this step, as
+this command will by default attempt to install module to the Python
+site-packages directory on your system.
+
+For advanced options, please refer to the easy_install and/or the distutils
+documentation:
+
+  http://peak.telecommunity.com/DevCenter/EasyInstall
+  http://docs.python.org/inst/inst.html
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..884bd41
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,6 @@
+include LICENSE
+include COPYRIGHT
+include README
+include CHANGELOG
+include INSTALL
+include doc/*
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..c9fd974
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,28 @@
+Metadata-Version: 1.1
+Name: GooCalendar
+Version: 0.1
+Summary: A calendar widget for GTK using PyGoocanvas
+Home-page: http://code.google.com/p/goocalendar/
+Author: Cédric Krier
+Author-email: cedric.krier at b2ck.com
+License: GPL-2
+Download-URL: http://code.google.com/p/goocalendar/downloads/
+Description: GooCalendar
+        ===========
+        
+        A calendar widget for GTK using PyGoocanvas
+        
+        Nutshell
+        --------
+        
+        .. TODO
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: X11 Applications :: GTK
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 2 :: Only
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Software Development :: Widget Sets
diff --git a/README b/README
new file mode 100644
index 0000000..2eb50c8
--- /dev/null
+++ b/README
@@ -0,0 +1,9 @@
+GooCalendar
+===========
+
+A calendar widget for GTK using PyGoocanvas
+
+Nutshell
+--------
+
+.. TODO
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..7ebd347
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,153 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/GooCalendar.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/GooCalendar.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/GooCalendar"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/GooCalendar"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..4d5ab0e
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,284 @@
+# -*- coding: utf-8 -*-
+#
+# GooCalendar documentation build configuration file, created by
+# sphinx-quickstart on Mon Aug 13 13:24:40 2012.
+#
+# This file is execfile()d with the current directory set to its containing dir
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'GooCalendar'
+copyright = u'2012, Samuel Abels, Cédric Krier, Antoine Smolders'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'GooCalendardoc'
+
+
+# -- Options for LaTeX output -------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual])
+latex_documents = [
+    ('index', 'GooCalendar.tex', u'GooCalendar Documentation',
+        u'Samuel Abels, Cédric Krier, Antoine Smolders', 'manual'),
+    ]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output -------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'goocalendar', u'GooCalendar Documentation',
+        [u'Samuel Abels, Cédric Krier, Antoine Smolders'], 1)
+    ]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output -----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    ('index', 'GooCalendar', u'GooCalendar Documentation',
+        u'Samuel Abels, Cédric Krier, Antoine Smolders', 'GooCalendar',
+        'One line description of project.',
+        'Miscellaneous'),
+    ]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+
+# -- Options for Epub output --------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = u'GooCalendar'
+epub_author = u'Samuel Abels, Cédric Krier, Antoine Smolders'
+epub_publisher = u'Samuel Abels, Cédric Krier, Antoine Smolders'
+epub_copyright = u'2012, Samuel Abels, Cédric Krier, Antoine Smolders'
+
+# The language of the text. It defaults to the language option
+# or en if the language is not set.
+#epub_language = ''
+
+# The scheme of the identifier. Typical schemes are ISBN or URL.
+#epub_scheme = ''
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#epub_identifier = ''
+
+# A unique identification for the text.
+#epub_uid = ''
+
+# A tuple containing the cover image and cover page html template filenames.
+#epub_cover = ()
+
+# HTML files that should be inserted before the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_pre_files = []
+
+# HTML files shat should be inserted after the pages created by sphinx.
+# The format is a list of tuples containing the path and title.
+#epub_post_files = []
+
+# A list of files that should not be packed into the epub file.
+#epub_exclude_files = []
+
+# The depth of the table of contents in toc.ncx.
+#epub_tocdepth = 3
+
+# Allow duplicate toc entries.
+#epub_tocdup = True
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..76b8c97
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,420 @@
+:mod:`goocalendar` --- Calendar widget using GooCanvas
+======================================================
+
+.. module:: goocalendar
+   :synopsis: Calendar widget using GooCanvas
+.. moduleauthor:: Samuel Abels <http://debain.org>
+.. moduleauthor:: Cedric Krier <cedric.krier at b2ck.com>
+.. moduleauthor:: Antoine Smolders <smoldersan at gmail.com>
+.. sectionauthor:: Antoine Smolders <smoldersan at gmail.com>
+
+The :mod:`goocalendar <goocalendar>` module supplies a calendar widget drawed
+with GooCanvas that can display a month view and a week view. It also supplies
+classes to manage events you can add to the calendar.
+
+
+.. _calendar:
+
+Calendar Objects
+------------------
+A :class:`Calendar <goocalendar.Calendar>` is a calendar widget using
+GooCanvas that can display a month view and a week view. It holds an
+:class:`EventStore<goocalendar.EventStore>` which contains events
+displayed in the calendar.
+
+.. class:: goocalendar.Calendar([event_store[, view[, time_format[, \
+   firstweekday]]]])
+
+   Creates a :class:`Calendar<goocalendar.Calendar>` object. All arguments are
+   optional. *event_store* should be an
+   :class:`EventStore<goocalendar.EventStore>`.
+   *view* should be either 'month' or 'week'. Default value is 'month'.
+   *time_format* determines the format of displayed time in the calendar.
+   Default value is '%H:%M'. *firstweekday* is a constant of module calendar
+   specifying the first day of the week. Default value is *calendar.SUNDAY*.
+
+Instance attributes:
+
+.. attribute:: event_store
+
+   :class:`EventStore <goocalendar.EventStore>` currently plugged.
+   Setting a new event store will automatically redraw the canvas to display
+   the events of the new event store.
+
+.. attribute:: view
+
+   The current view: 'month' or 'week'.
+
+.. attribute:: selected_date
+
+   `datetime.date
+   <http://docs.python.org/library/datetime.html#date-objects>`_
+   which determines the current selected day in the calendar.
+
+.. attribute:: firstweekday
+
+   Determines the first day of the week (0 is Monday).
+
+Instance methods:
+
+.. method:: select(date)
+
+   Select the given date in the calendar. Date should be a
+   `datetime.date
+   <http://docs.python.org/library/datetime.html#date-objects>`_.
+
+.. method:: previous_page()
+
+   Go to the previous page of the calendar.
+
+.. method:: next_page()
+
+   Go to the next page of the calendar.
+
+.. method:: set_view(view)
+
+   Change calendar's view. Possible values: 'month' or 'week'.
+
+.. method:: draw_events()
+
+   Redraws events.
+
+.. method:: update()
+
+   Redraws calendar and events.
+
+Instance signals:
+
+``event-pressed``
+
+   The ``event-pressed`` signal is emitted when an Event is pressed with the
+   button 1 of the mouse.
+
+   ``def callback(calendar, event, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *event*
+      The pressed :class:`Event <goocalendar.Event>` object.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``event-activated``
+
+   The ``event-activated`` signal is emitted when an
+   :class:`Event <goocalendar.Event>` is double-clicked
+   with the button 1 of the mouse.
+
+   ``def callback(calendar, event, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *event*
+      The double-clicked :class:`Event <goocalendar.Event>` object.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``event-released``
+
+   The ``event-released`` signal is emitted when the button 1 of the mouse is
+   released on an event.
+
+   ``def callback(calendar, event, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *event*
+      The double-clicked :class:`Event <goocalendar.Event>` object.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``day-pressed``
+
+   The ``day-pressed`` signal is emitted when a day is pressed with the
+   mouse button 1.
+
+   ``def callback(calendar, date, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *date*
+      `datetime.date
+      <http://docs.python.org/library/datetime.html#date-objects>`_
+      corresponding to the day pressed.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``day-activated``
+
+   The ``day-activated`` signal is emitted when the day is double-clicked with
+   the mouse button 1.
+
+   ``def callback(calendar, date, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *date*
+      `datetime.date
+      <http://docs.python.org/library/datetime.html#date-objects>`_
+      corresponding to the activated day.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method
+
+   *...*
+      additional user parameters (if any).
+
+``day-selected``
+
+   The ``day-selected`` signal is emitted when the selected day changes.
+
+   ``def callback(calendar, date, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *date*
+      `datetime.date
+      <http://docs.python.org/library/datetime.html#date-objects>`_
+      corresponding to the new selected day.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``view-changed``
+
+   The ``view-changed`` signal is emitted when the view changes
+
+   ``def callback(calendar, view, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *view*
+      'month' or 'week'
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method
+
+   *...*
+      additional user parameters (if any).
+
+``page-changed``
+
+   The ``page-changed`` signal is emitted when the page currently showed in
+   the calendar is changed.
+
+   ``def callback(calendar, date, user_param1, ...)``
+
+   *calendar*
+      The :class:`Calendar <goocalendar.Calendar>` that received the signal.
+
+   *date*
+      `datetime.date
+      <http://docs.python.org/library/datetime.html#date-objects>`_
+      corresponding to the selected day in the calendar.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+.. _eventstore:
+
+EventStore Objects
+--------------------
+
+An :class:`EventStore <goocalendar.EventStore>` is the store of
+:class:`Event <goocalendar.Event>` that can be plugged to a
+:class:`Calendar <goocalendar.Calendar>`.
+
+.. class:: goocalendar.EventStore()
+
+   There is no arguments for this class.
+
+Instance methods:
+
+.. method:: add(event)
+
+   Add the given event to the event store.
+
+.. method:: remove(event)
+
+   Remove the given event from the event store.
+
+.. method:: clear()
+
+   Remove all events from the event store and restore it to initial state.
+
+.. method:: get_events(start, end)
+
+   Returns a list of all events that intersect with the given start and end
+   datetime. If no start time nor end time are given, the method returns a
+   list containing all events.
+
+Instance signals:
+
+``event-added``
+
+   The ``event-added`` signal is emitted when an Event is added to the
+   event store.
+
+   ``def callback(event_store, event, user_param1, ...)``
+
+   *event_store*
+      The :class:`EventStore <goocalendar.EventStore>` that received the signal.
+
+   *event*
+      The added :class:`Event <goocalendar.Event>`.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``event-removed``
+
+   The ``event-removed`` signal is emitted when an Event is removed from
+   the event store.
+
+   ``def callback(event_store, event, user_param1, ...)``
+
+   *event_store*
+      The :class:`EventStore <goocalendar.EventStore>` that received the signal.
+
+   *event*
+      The removed :class:`Event <goocalendar.Event>`.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+``events-cleared``
+
+   The ``events-cleared`` signal is emitted when the event store is cleared.
+
+   ``def callback(event_store, user_param1, ...)``
+
+   *event_store*
+      The :class:`EventStore <goocalendar.EventStore>` that received the signal.
+
+   *user_param1*
+      the first user parameter (if any) specified with the connect() method.
+
+   *...*
+      additional user parameters (if any).
+
+
+
+.. _event:
+
+Event Objects
+---------------
+
+An :class:`Event <goocalendar.Event>` represents an event in a
+:class:`Calendar <goocalendar.Calendar>`.
+
+.. class:: goocalendar.Event(caption, start[, end[, all_day[, text_color \
+   [, bg_color]]]])
+
+   *caption* argument is mandatory and will be the string displayed on the
+   event.  *start* argument is mandatory and determines the starting time of
+   the event. It should be a
+   `datetime\
+   <http://docs.python.org/library/datetime.html#datetime-objects>`_.
+   All other arguments are optional. *end* argument may be a datetime,
+   all_day a boolean value. An event will be considered as all day
+   event if no *end* argument is supplied. *text_color* and *bg_color*
+   arguments are supposed to be color strings.
+
+Instance attributes:
+
+.. attribute:: id
+
+   Unique identification integer.
+
+.. attribute:: caption
+
+   Caption to display on the event in the calendar.
+
+.. attribute:: start
+
+   `datetime <http://docs.python.org/library/datetime.html#datetime-objects>`_
+   determining event start time.
+
+.. attribute:: end
+
+   `datetime <http://docs.python.org/library/datetime.html#datetime-objects>`_
+   determining event end time.
+
+.. attribute:: all_day
+
+   Boolean determining if the day is an all day event or a normal event.
+
+.. attribute:: text_color
+
+   String determining caption text color.
+
+.. attribute:: bg_color
+
+   String determining background color.
+
+.. attribute:: multidays
+
+   Boolean property determining if the event is longer than one day.
+
+Supported operations:
+
+All comparisons operations are supported.
+
+event1 is considered less than event2 if it starts before event2.
+If two events start at the same time, the event which ends the first
+one is considered smaller.
+
+Example usage::
+
+   >>> import datetime
+   >>> import goocalendar
+   >>> event_store = goocalendar.EventStore()
+   >>> calendar = goocalendar.Calendar(event_store)
+   >>> event = goocalendar.Event('Event number 1',
+   ...     datetime.datetime(2012, 8, 21, 14),
+   ...     datetime.datetime(2012, 8, 21, 17),
+   ...     bg_color='lightgreen')
+   >>> event_store.add(event)
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/goocalendar/__init__.py b/goocalendar/__init__.py
new file mode 100644
index 0000000..615d42f
--- /dev/null
+++ b/goocalendar/__init__.py
@@ -0,0 +1,7 @@
+#This file is part of GooCalendar.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+from ._calendar import Calendar
+from ._event import Event, EventStore
+
+__all__ = ['Calendar', 'EventStore', 'Event']
+__version__ = '0.1'
diff --git a/goocalendar/_calendar.py b/goocalendar/_calendar.py
new file mode 100644
index 0000000..1d2f5b5
--- /dev/null
+++ b/goocalendar/_calendar.py
@@ -0,0 +1,1276 @@
+#This file is part of GooCalendar.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+import datetime
+import calendar
+import math
+from operator import add
+
+import gtk
+import gobject
+import goocanvas
+import pango
+
+import util
+from .util import left_click
+
+
+class Calendar(goocanvas.Canvas):
+    AVAILABLE_VIEWS = ["month", "week"]
+    MIN_PER_LEVEL = 15  # Number of minutes per graduation for drag and drop
+
+    def __init__(self, event_store=None, view="month", time_format="%H:%M",
+            firstweekday=calendar.SUNDAY):
+        super(Calendar, self).__init__()
+        self._selected_day = None
+        self._bg_rect = None
+        self._timeline = None
+        self._line_height = 0
+        self._realized = False
+        self._event_store = None
+        self._event_removed_sigid = None
+        self._event_added_sigid = None
+        self._events_cleared_sigid = None
+        self.event_store = event_store
+        self.firstweekday = firstweekday
+        self._drag_start_date = None
+        self._drag_date = None
+        self._drag_x = None
+        self._drag_y = None
+        self._drag_height = 0
+        self._last_click_x = None
+        self._last_click_y = None
+        self._last_click_time = 0
+        self._day_width = 0
+        self._day_height = 0
+        self._event_items = []
+        assert view in self.AVAILABLE_VIEWS
+        self.view = view
+        self.selected_date = datetime.date.today()
+        self.time_format = time_format
+        self.set_bounds(0, 0, 200, 200)
+        self.set_flags(gtk.CAN_FOCUS)
+        self.set_events(gtk.gdk.EXPOSURE_MASK
+            | gtk.gdk.BUTTON_PRESS_MASK
+            | gtk.gdk.BUTTON_RELEASE_MASK
+            | gtk.gdk.POINTER_MOTION_MASK
+            | gtk.gdk.POINTER_MOTION_HINT_MASK
+            | gtk.gdk.KEY_PRESS_MASK
+            | gtk.gdk.KEY_RELEASE_MASK
+            | gtk.gdk.ENTER_NOTIFY_MASK
+            | gtk.gdk.LEAVE_NOTIFY_MASK
+            | gtk.gdk.FOCUS_CHANGE_MASK)
+        self.connect_after('realize', self.on_realize)
+        self.connect('size-allocate', self.on_size_allocate)
+        self.connect('key-press-event', self.on_key_press_event)
+
+        # Initialize background, timeline and days and add them to canvas
+        root = self.get_root_item()
+        style = self.get_style()
+        color = util.color_to_string(style.bg[gtk.STATE_PRELIGHT])
+        self._bg_rect = goocanvas.Rect(parent=root, x=0, y=0,
+            stroke_color=color, fill_color=color)
+        self._timeline = TimelineItem(self, time_format=self.time_format)
+        root.add_child(self._timeline)
+        self.days = []
+        while len(self.days) < 42:  # 6 rows of 7 days
+            box = DayItem(self)
+            root.add_child(box)
+            box.connect('button_press_event',
+                self.on_day_item_button_press_event)
+            self.days.append(box)
+
+    def select(self, new_date):
+        cal = calendar.Calendar(self.firstweekday)
+        if hasattr(new_date, 'date'):
+            new_date = new_date.date()
+        old_date = self.selected_date
+        old_day = self._selected_day
+        self.selected_date = new_date
+        page_changed = False
+        if self.view == "month":
+            page_changed = not util.same_month(old_date, new_date)
+        elif self.view == "week":
+            old_first_weekday = util.first_day_of_week(cal, old_date)
+            new_first_weekday = util.first_day_of_week(cal, new_date)
+            page_changed = old_first_weekday != new_first_weekday
+
+        # This is slow: When the month was changed we need to update
+        # the entire canvas.
+        if old_day is None or page_changed:
+            self.update()
+            self.emit('day-selected', self.selected_date)
+            self.emit('page-changed', self.selected_date)
+            return
+
+        # This is fast: Update only the old and newly selected days.
+        # Find the canvas item that corresponds to the new date.
+        weeks = cal.monthdayscalendar(new_date.year, new_date.month)
+        found = -1
+        for weekno, week in enumerate(weeks):
+            for dayno, day in enumerate(week):
+                if day == new_date.day:
+                    found = weekno * 7 + dayno
+                    break
+            if found != -1:
+                break
+
+        # Swap border colors.
+        new_day = self.days[found]
+        old_border_color = old_day.border_color
+        old_day.full_border = False
+        old_day.border_color = new_day.border_color
+        new_day.border_color = old_border_color
+        new_day.full_border = True
+
+        # Redraw.
+        old_day.update()
+        new_day.update()
+        self._selected_day = new_day
+        if old_day != new_day:
+            self.emit('day-selected', self.selected_date)
+
+    def previous_page(self):
+        cal = calendar.Calendar(self.firstweekday)
+        if self.view == "month":
+            new_date = util.previous_month(cal, self.selected_date)
+        elif self.view == "week":
+            new_date = util.previous_week(cal, self.selected_date)
+        self.select(new_date)
+
+    def next_page(self):
+        cal = calendar.Calendar(self.firstweekday)
+        if self.view == "month":
+            new_date = util.next_month(cal, self.selected_date)
+        elif self.view == "week":
+            new_date = util.next_month(cal, self.selected_date)
+        self.select(new_date)
+
+    def set_view(self, level):
+        if level == self.view:
+            return
+        assert level in self.AVAILABLE_VIEWS
+        self.view = level
+        self.update()
+        self.emit('view-changed', self.view)
+
+    @property
+    def event_store(self):
+        return self._event_store
+
+    @event_store.setter
+    def event_store(self, event_store):
+        # Disconnect previous event store if any
+        if self._event_store:
+            self._event_store.disconnect(self._event_removed_sigid)
+            self._event_store.disconnect(self._event_added_sigid)
+            self._event_store_disconnect(self._events_cleared_sigid)
+
+        # Set and connect new event_store
+        self._event_store = event_store
+        self.update()
+        if not event_store:
+            return
+        self._event_removed_sigid = self._event_store.connect('event-removed',
+            self.on_event_store_event_removed)
+        self._event_added_sigid = self._event_store.connect('event-added',
+            self.on_event_store_event_added)
+        self._events_cleared_sigid = \
+            self._event_store.connect('events-cleared',
+            self.on_event_store_events_cleared)
+
+    def on_realize(self, *args):
+        self._realized = True
+        self.grab_focus(self.get_root_item())
+        self.on_size_allocate(*args)
+
+    def on_size_allocate(self, *args):
+        alloc = self.get_allocation()
+        if not self._realized or alloc.width < 10 or alloc.height < 10:
+            return
+        self.set_bounds(0, 0, alloc.width, alloc.height)
+        self.update()
+
+    def update(self):
+        if not self._realized:
+            return
+        self.draw_background()
+        if self.view == "month":
+            self.draw_month()
+        elif self.view == "week":
+            self.draw_week()
+        self.draw_events()
+
+    def draw_background(self):
+        x, y, w, h = self.get_bounds()
+        self._bg_rect.set_property('width', w)
+        self._bg_rect.set_property('height', h)
+
+    def draw_week(self):
+        """
+        Draws the currently selected week.
+        """
+        style = self.get_style()
+        pango_size = style.font_desc.get_size()
+        text_color = util.color_to_string(style.fg[gtk.STATE_NORMAL])
+        border_color = util.color_to_string(style.mid[gtk.STATE_NORMAL])
+        body_color = util.color_to_string(style.light[gtk.STATE_ACTIVE])
+        selected_border_color = util.color_to_string(
+            style.mid[gtk.STATE_SELECTED])
+        today_body_color = 'ivory'
+        x, y, w, h = self.get_bounds()
+        timeline_w = self._timeline.width
+        caption_size = max(len(day_name) for day_name in calendar.day_name)
+        caption_size += 3  # The needed space for the date before the day_name
+        day_width_min = caption_size * pango_size / pango.SCALE
+        day_width_max = (w - timeline_w) / 7
+        self._day_width = max(day_width_min, day_width_max)
+        self._day_height = h
+        width, height = self.get_size_request()
+        new_width = int(timeline_w + 7 * self._day_width)
+        if (width != new_width and day_width_min >= day_width_max):
+            self.set_size_request(new_width, height)  # Minimum widget size
+
+        # Redraw all days.
+        cal = calendar.Calendar(self.firstweekday)
+        weeks = util.my_monthdatescalendar(cal, self.selected_date)
+        for weekno, week in enumerate(weeks):
+            # Hide all days that are not part of the current week.
+            if self.selected_date not in week:
+                for dayno, date in enumerate(week):
+                    box = self.days[weekno * 7 + dayno]
+                    box.set_property('visibility', goocanvas.ITEM_INVISIBLE)
+                continue
+
+            # Draw the days that are part of the current week.
+            for dayno, current_date in enumerate(week):
+                # Highlight the day according to it's selection.
+                selected = current_date == self.selected_date
+                if selected:
+                    the_border_color = selected_border_color
+                else:
+                    the_border_color = border_color
+                if current_date == datetime.date.today():
+                    the_body_color = today_body_color
+                else:
+                    the_body_color = body_color
+
+                # Draw.
+                box = self.days[weekno * 7 + dayno]
+                box.x = self._day_width * dayno + timeline_w
+                box.y = 0
+                box.width = self._day_width - 2
+                box.height = self._day_height
+                box.type = 'week'
+                box.date = current_date
+                box.full_border = selected
+                box.border_color = the_border_color
+                box.body_color = the_body_color
+                box.title_text_color = text_color
+                box.event_text_color = text_color
+                box.set_property('visibility', goocanvas.ITEM_VISIBLE)
+                box.update()
+
+                if selected:
+                    self._selected_day = box
+                    self._line_height = self._selected_day.line_height
+
+    def draw_month(self):
+        """
+        Draws the currently selected month.
+        """
+        style = self.get_style()
+        x1, y1, w, h = self.get_bounds()
+        pango_size = style.font_desc.get_size()
+        caption_size = max(len(day_name) for day_name in calendar.day_name)
+        caption_size += 3  # The needed space for the date before the day_name
+        day_width_min = caption_size * pango_size / pango.SCALE
+        day_width_max = w / 7
+        self._day_width = max(day_width_min, day_width_max)
+        self._day_height = h / 6
+        text_color = util.color_to_string(style.fg[gtk.STATE_NORMAL])
+        inactive_text_color = util.color_to_string(
+            style.fg[gtk.STATE_INSENSITIVE])
+        border_color = util.color_to_string(style.mid[gtk.STATE_NORMAL])
+        selected_border_color = util.color_to_string(
+            style.mid[gtk.STATE_SELECTED])
+        inactive_border_color = util.color_to_string(
+            style.bg[gtk.STATE_PRELIGHT])
+        body_color = util.color_to_string(style.light[gtk.STATE_ACTIVE])
+        today_body_color = 'ivory'
+
+        # Hide the timeline.
+        if self._timeline is not None:
+            self._timeline.set_property('visibility', goocanvas.ITEM_INVISIBLE)
+
+        # Draw the grid.
+        y_pos = 0
+        cal = calendar.Calendar(self.firstweekday)
+        weeks = util.my_monthdatescalendar(cal, self.selected_date)
+        for weekno, week in enumerate(weeks):
+            for dayno, date in enumerate(week):
+                # The color depends on whether each day is part of the
+                # current month.
+                if (not util.same_month(date, self.selected_date)):
+                    the_border_color = inactive_border_color
+                    the_text_color = inactive_text_color
+                else:
+                    the_border_color = border_color
+                    the_text_color = text_color
+
+                # Highlight the day according to it's selection.
+                selected = date == self.selected_date
+                if selected:
+                    the_border_color = selected_border_color
+                if date == datetime.date.today():
+                    the_body_color = today_body_color
+                else:
+                    the_body_color = body_color
+
+                # Draw a box for the day.
+                box = self.days[weekno * 7 + dayno]
+                box.x = self._day_width * dayno
+                box.y = y_pos
+                box.width = self._day_width - 2
+                box.height = self._day_height - 2
+                box.date = date
+                box.full_border = selected
+                box.border_color = the_border_color
+                box.body_color = the_body_color
+                box.title_text_color = the_text_color
+                box.event_text_color = the_text_color
+                box.type = 'month'
+                box.set_property('visibility', goocanvas.ITEM_VISIBLE)
+                box.update()
+
+                if selected:
+                    self._selected_day = box
+                    self._line_height = self._selected_day.line_height
+
+            y_pos += self._day_height
+
+        width, height = self.get_size_request()
+        new_width = int(7 * self._day_width)
+        new_height = int(14 * box.line_height)
+        if ((width != new_width and self._day_width == day_width_min)
+                or new_height != height):
+            self.set_size_request(new_width, new_height)
+
+    def _get_day_item(self, find_date):
+        cal = calendar.Calendar(self.firstweekday)
+        weeks = util.my_monthdatescalendar(cal, find_date)
+        for weekno, week in enumerate(weeks):
+            for dayno, date in enumerate(week):
+                if date == find_date:
+                    return self.days[weekno * 7 + dayno]
+        raise Exception('Day not found: %s' % (find_date))
+
+    def _get_day_items(self, event):
+        """
+        Given an event, this method returns a list containing the
+        DayItem corresponding with each day on which the event takes
+        place.
+        Days that are currently not in the view are not returned.
+        """
+        cal = calendar.Calendar(self.firstweekday)
+        weeks = util.my_monthdatescalendar(cal, self.selected_date)
+        start = event.start.date()
+        end = event.end.date() if event.end else event.start.date()
+        assert start <= end
+        days = []
+        for weekno, week in enumerate(weeks):
+            if self.view == "week":
+                if self.selected_date not in week:
+                    continue
+            for dayno, date in enumerate(week):
+                if date >= start and date <= end:
+                    days.append(self.days[weekno * 7 + dayno])
+                if date == end:
+                    return days
+        if len(days) > 0:
+            return days
+        raise Exception('Days not found: %s %s' % (event.start, end))
+
+    def _find_free_line(self, days):
+        for line in range(days[0].n_lines):
+            free = True
+            for day in days:
+                if line in day.lines:
+                    free = False
+                    break
+            if free:
+                return line
+        return None
+
+    def draw_events(self):
+        # Clear previous events.
+        for item in self._event_items:
+            self.get_root_item().remove_child(item)
+        self._event_items = []
+        for day in self.days:
+            day.lines.clear()
+            day.show_indic = False
+            day.update()
+
+        if not self._event_store:
+            return
+
+        cal = calendar.Calendar(self.firstweekday)
+        if self.view == "month":
+            weeks = util.my_monthdatescalendar(cal, self.selected_date)
+            dates = []
+            for week in weeks:
+                dates += week
+        else:
+            dates = util.my_weekdatescalendar(cal, self.selected_date)
+
+        # Retrieve a list of all events in the current time span,
+        # and sort them by event length.
+        onedaydelta = (datetime.timedelta(days=1)
+            - datetime.timedelta(microseconds=1))
+        start = datetime.datetime.combine(dates[0], datetime.time())
+        end = datetime.datetime.combine(dates[-1], datetime.time()) \
+            + onedaydelta
+        events = self._event_store.get_events(start, end)
+        events.sort(util.event_days, reverse=True)
+
+        # Draw all-day events, longest event first.
+        max_y = self._selected_day.line_height
+        non_all_day_events = []
+        for event in events:
+            event.event_items = []
+            # Handle non-all-day events differently in week mode.
+            if (self.view == "week" and not event.all_day
+                    and not event.multidays):
+                non_all_day_events.append(event)
+                continue
+
+            # Find a line that is free in all of the days.
+            days = self._get_day_items(event)
+            free_line = self._find_free_line(days)
+            if free_line is None:
+                for day in days:
+                    day.show_indic = True
+                    day.update()
+                continue
+
+            max_line_height = max(x.line_height for x in days)
+            all_day_events_height = (free_line + 2) * max_line_height
+            all_day_events_height += (free_line + 1) * 2  # 2px margin per line
+            all_day_events_height += 1  # 1px padding-top
+            max_y = max(all_day_events_height, max_y)
+            for day in days:
+                day.lines[free_line] = 1
+
+            # Split days into weeks.
+            weeks = []
+            week_start = 0
+            week_end = 0
+            while week_end < len(days):
+                day = days[week_start]
+                weekday = (day.date.weekday() - self.firstweekday) % 7
+                week_end = week_start + (7 - weekday)
+                week = days[week_start:week_end]
+                weeks.append(week)
+                week_start = week_end
+
+            for week in weeks:
+                dayno = 0
+                day = week[dayno]
+                event_item = EventItem(self, event=event,
+                    time_format=self.time_format)
+                if len(event.event_items):
+                    event_item.no_caption = True
+                event.event_items.append(event_item)
+                event_item.connect('button_press_event',
+                    self.on_event_item_button_press_event)
+                event_item.connect('button_release_event',
+                    self.on_event_item_button_release)
+                event_item.connect('motion_notify_event',
+                    self.on_event_item_motion_notified)
+                self._event_items.append(event_item)
+                self.get_root_item().add_child(event_item)
+                event_item.x = day.x
+                event_item.left_border = day.x + 2
+                event_item.y = day.y + (free_line + 1) * day.line_height
+                event_item.y += free_line * 2  # 2px of margin per line
+                event_item.y += 1  # 1px padding-top
+                event_item.width = (day.width + 2) * len(week)
+                event_item.height = day.line_height
+                week_start = week[0].date
+                week_end = week[-1].date
+                end = event.end if event.end else event.start
+                if (event.start.date() < week_start
+                        and end.date() > week_end):
+                    event_item.type = 'mid'
+                    event_item.width -= 3
+                elif event.start.date() < week_start:
+                    event_item.type = 'right'
+                    event_item.width -= 4
+                elif end.date() > week_end:
+                    event_item.type = 'left'
+                    event_item.x += 2
+                    event_item.width -= 4
+                else:
+                    event_item.x += 2
+                    event_item.width -= 6
+                    event_item.type = 'leftright'
+                event_item.update()
+
+        if self.view != "week":
+            return
+
+        # Redraw the timeline.
+        style = self.get_style()
+        text_color = util.color_to_string(style.fg[gtk.STATE_NORMAL])
+        border_color = util.color_to_string(style.mid[gtk.STATE_NORMAL])
+        body_color = util.color_to_string(style.light[gtk.STATE_ACTIVE])
+        self._timeline.set_property('visibility', goocanvas.ITEM_VISIBLE)
+        x, y, w, h = self.get_bounds()
+        self._timeline.x = x
+        self._timeline.y = max_y
+        self._timeline.height = h - max_y - 2
+        self._timeline.line_color = body_color
+        self._timeline.bg_color = border_color
+        self._timeline.text_color = text_color
+        self._timeline.update()
+        width, height = self.get_size_request()
+        min_line_height = self._timeline.min_line_height
+        line_height = self._timeline.line_height
+        self.minute_height = line_height / 60.0
+        new_height = int(max_y + 24 * min_line_height)
+        if (height != new_height):
+            self.set_size_request(width, new_height)
+
+        # Draw non-all-day events.
+        for date in dates:
+            date_start = datetime.datetime.combine(date, datetime.time())
+            date_end = (datetime.datetime.combine(date_start, datetime.time())
+                + datetime.timedelta(days=1))
+            day = self._get_day_item(date)
+            day_events = util.get_intersection_list(non_all_day_events,
+                date_start, date_end)
+            day_events.sort()
+            columns = []
+            column = 0
+
+            # Sort events into columns.
+            remaining_events = day_events[:]
+            while len(remaining_events) > 0:
+                columns.append([remaining_events[0]])
+                for event in remaining_events:
+                    intersections = util.count_intersections(columns[-1],
+                        event.start, event.end)
+                    if intersections == 0:
+                        columns[-1].append(event)
+                for event in columns[-1]:
+                    remaining_events.remove(event)
+
+            # Walk through all columns.
+            for columnno, column in enumerate(columns):
+                for event in column:
+                    # Crop the event to the current day.
+                    event1_start = max(event.start, date_start)
+                    event1_end = min(event.end, date_end)
+
+                    parallel = util.count_parallel_events(day_events,
+                        event1_start, event1_end)
+
+                    # Draw.
+                    top_offset = event1_start - date_start
+                    bottom_offset = event1_end - event1_start
+                    top_offset_mins = top_offset.seconds / 60
+                    bottom_offset_mins = ((bottom_offset.days * 24 * 60)
+                        + bottom_offset.seconds / 60)
+
+                    event_item = EventItem(self, event=event,
+                        time_format=self.time_format)
+                    if event.event_items:
+                        event_item.no_caption = True
+                    event.event_items.append(event_item)
+                    event_item.connect('button_press_event',
+                        self.on_event_item_button_press_event)
+                    event_item.connect('button_release_event',
+                        self.on_event_item_button_release)
+                    event_item.connect('motion_notify_event',
+                        self.on_event_item_motion_notified)
+                    self._event_items.append(event_item)
+                    self.get_root_item().add_child(event_item)
+                    y_off1 = top_offset_mins * self.minute_height
+                    y_off2 = bottom_offset_mins * self.minute_height
+                    column_width = day.width / parallel
+                    event_item.left_border = day.x + 2
+                    event_item.x = day.x + (columnno * column_width) + 2
+                    event_item.y = max_y + y_off1
+                    event_item.width = column_width - 4
+                    if columnno != (parallel - 1):
+                        event_item.width += column_width / 1.2
+                    event_item.height = y_off2
+                    if event.start < event1_start and event.end > event1_end:
+                        event_item.type = 'mid'
+                    elif event.start < event1_start:
+                        event_item.type = 'top'
+                    elif event.end > event1_end:
+                        event_item.type = 'bottom'
+                    else:
+                        event_item.type = 'topbottom'
+                    event_item.update()
+
+    def on_event_store_event_removed(self, store, event):
+        self.update()
+
+    def on_event_store_event_added(self, store, event):
+        self.update()
+
+    def on_event_store_events_cleared(self, store):
+        self.update()
+
+    def on_key_press_event(self, widget, event):
+        date = self.selected_date
+        if event.keyval == gtk.gdk.keyval_from_name('Up'):
+            self.select(date - datetime.timedelta(7))
+        elif event.keyval == gtk.gdk.keyval_from_name('Down'):
+            self.select(date + datetime.timedelta(7))
+        elif event.keyval == gtk.gdk.keyval_from_name('Left'):
+            self.select(date - datetime.timedelta(1))
+        elif event.keyval == gtk.gdk.keyval_from_name('Right'):
+            self.select(date + datetime.timedelta(1))
+
+    @left_click
+    def on_day_item_button_press_event(self, day, widget2, event):
+        self.emit('day-pressed', day.date)
+        self.select(day.date)
+
+        if self._is_double_click(event):
+            self.emit('day-activated', day.date)
+
+    def _is_double_click(self, event):
+        gtk_settings = gtk.settings_get_default()
+        double_click_distance = gtk_settings.props.gtk_double_click_distance
+        double_click_time = gtk_settings.props.gtk_double_click_time
+        if (self._last_click_x is not None and
+                event.time < (self._last_click_time + double_click_time) and
+                abs(event.x - self._last_click_x) <= double_click_distance and
+                abs(event.y - self._last_click_y) <= double_click_distance):
+            self._last_click_x = None
+            self._last_click_y = None
+            self._last_click_time = None
+            return True
+        else:
+            self._last_click_x = event.x
+            self._last_click_y = event.y
+            self._last_click_time = event.time
+            return False
+
+    def get_cur_pointed_date(self, x, y):
+        """
+        Return the date of the day_item pointed by two coordinates [x,y]
+        """
+        # Get current week
+        cal = calendar.Calendar(self.firstweekday)
+        weeks = util.my_monthdatescalendar(cal, self.selected_date)
+        if self.view == 'week':
+            cur_week, = (week for week in weeks for date in week
+                if self.selected_date == date)
+        elif self.view == 'month':
+            max_height = 6 * self._day_height
+            if y < 0:
+                weekno = 0
+            elif y > max_height:
+                weekno = 5
+            else:
+                weekno = int(y / self._day_height)
+            cur_week = weeks[weekno]
+
+        # Get Current pointed date
+        max_width = 7 * self._day_width
+        if x < 0:
+            day_no = 0
+        elif x > max_width:
+            day_no = 6
+        else:
+            offset_x = self._timeline.width if self.view == 'week' else 0
+            day_no = int((x - offset_x) / self._day_width)
+        return cur_week[day_no]
+
+    @left_click
+    def on_event_item_button_press_event(self, event_item, rect, event):
+
+        # Drag and drop starting coordinates
+        self._drag_x = event.x
+        self._drag_y = event.y
+        self._drag_height = 0
+        self._drag_start_date = self.get_cur_pointed_date(event.x, event.y)
+        self._drag_date = self._drag_start_date
+        self.set_has_tooltip(False)
+        event_item.raise_(None)
+        event_item.transparent = True
+
+        event_item.width = self._day_width - 6  # Biggest event width
+        event_date = event_item.event.start.date()
+        daysdelta = self._drag_start_date - event_date
+        if self.view == 'week':
+            event_item.x = event_item.left_border
+            if ((event_item.event.all_day or event_item.event.multidays)
+                    and self._drag_start_date != event_date):
+                event_item.x += daysdelta.days * self._day_width
+                event_item.event.start += daysdelta
+                if event_item.event.end:
+                    event_item.event.end += daysdelta
+            else:
+                for item in event_item.event.event_items:
+                    if item != event_item:
+                        self.get_root_item().remove_child(item)
+                        self._event_items.remove(item)
+
+                event_item.height = 2 * self._line_height
+                day_no = (int((event.x - self._timeline.width)
+                    / self._day_width))
+                day_off = day_no * self._day_width + 2
+                event_item.x = self._timeline.width + day_off
+                if (event_item.no_caption or event.y < event_item.y
+                        or event.y > (event_item.y + event_item.height)):
+                    # click was not performed inside the new day item
+                    level_height = self.minute_height * self.MIN_PER_LEVEL
+                    cur_level = int((event.y - self._timeline.y)
+                        / level_height)
+                    nb_levels_per_hour = 60 / self.MIN_PER_LEVEL
+                    cur_level -= nb_levels_per_hour  # click is in the middle
+                    if cur_level < 0:
+                        cur_level = 0
+                    event_item.y = self._timeline.y + cur_level * level_height
+                    nb_minutes = cur_level * self.MIN_PER_LEVEL
+                    minutes = nb_minutes % 60
+                    hours = nb_minutes / 60
+                    old_start = event_item.event.start
+                    new_start = \
+                        datetime.datetime.combine(self._drag_start_date,
+                        datetime.time(hours, minutes))
+                    event_item.event.start = new_start
+                    delta = new_start - old_start
+                    if event_item.event.end:
+                        event_item.event.end += delta
+                event_item.no_caption = False
+        elif self.view == 'month':
+            for item in event_item.event.event_items:
+                if item != event_item:
+                    self.get_root_item().remove_child(item)
+                    self._event_items.remove(item)
+                else:
+                    event_item.event.start += daysdelta
+                    if event_item.event.end:
+                        event_item.event.end += daysdelta
+                    weekno = int(event.y / self._day_height)
+                    day_no = int(event.x / self._day_width)
+                    event_item.y = weekno * self._day_height
+                    event_item.y += int(self._line_height) + 1  # padding-top
+                    event_item.x = day_no * self._day_width + 2  # padding-left
+                    item_height = self._line_height + 2  # 2px between items
+                    while event_item.y < event.y:
+                        event_item.y += item_height
+                    event_item.y -= item_height
+                    event_item.no_caption = False
+        event_item.update()
+        self.emit('event-pressed', event_item.event)
+
+        if self._is_double_click(event):
+            self._stop_drag_and_drop()
+            self.emit('event-activated', event_item.event)
+
+    def on_event_item_button_release(self, event_item, rect, event):
+        event_item.transparent = False
+        self._stop_drag_and_drop()
+        self.draw_events()
+        self.emit('event-released', event_item.event)
+
+    def _stop_drag_and_drop(self):
+        self._drag_x = None
+        self._drag_y = None
+        self._drag_height = 0
+        self._drag_start_date = None
+        self._drag_date = None
+        self.set_has_tooltip(True)
+
+    def on_event_item_motion_notified(self, event_item, rect, event):
+        if self._drag_x and self._drag_y:
+            # We are currently drag and dropping this event item
+            diff_y = event.y - self._drag_y
+            self._drag_x = event.x
+            self._drag_y = event.y
+            self._drag_height += diff_y
+
+            cur_pointed_date = self.get_cur_pointed_date(event.x, event.y)
+            daysdelta = cur_pointed_date - self._drag_date
+            if self.view == 'month':
+                if cur_pointed_date != self._drag_date:
+                    event_item.event.start += daysdelta
+                    if event_item.event.end:
+                        event_item.event.end += daysdelta
+                    nb_lines = int(round(float(daysdelta.days) / 7))
+                    nb_columns = daysdelta.days - nb_lines * 7
+                    event_item.x += nb_columns * self._day_width
+                    self._drag_date = cur_pointed_date
+                event_item.y += diff_y
+                event_item.update()
+                return
+
+            # Handle horizontal translation
+            if cur_pointed_date != self._drag_date:
+                self._drag_date = cur_pointed_date
+                event_item.event.start += daysdelta
+                if event_item.event.end:
+                    event_item.event.end += daysdelta
+                event_item.x += daysdelta.days * self._day_width
+
+            if event_item.event.multidays or event_item.event.all_day:
+                event_item.update()
+                return
+
+            # Compute vertical translation
+            diff_minutes = int(round(self._drag_height / self.minute_height))
+            diff_time = datetime.timedelta(minutes=diff_minutes)
+            old_start = event_item.event.start
+            new_start = old_start + diff_time
+            next_level = util.next_level(old_start, self.MIN_PER_LEVEL)
+            prev_level = util.prev_level(old_start, self.MIN_PER_LEVEL)
+            if diff_time >= datetime.timedelta(0) and new_start >= next_level:
+                new_start = util.prev_level(new_start, self.MIN_PER_LEVEL)
+            elif diff_time < datetime.timedelta(0) and new_start <= prev_level:
+                new_start = util.next_level(new_start, self.MIN_PER_LEVEL)
+            else:
+                # We stay at the same level
+                event_item.update()
+                return
+
+            # Apply vertical translation
+            midnight = datetime.time()
+            old_start_midnight = datetime.datetime.combine(old_start, midnight)
+            onedaydelta = datetime.timedelta(days=1)
+            next_day_midnight = old_start_midnight + onedaydelta
+            if new_start.day < old_start.day:
+                new_start = old_start_midnight
+            elif new_start >= next_day_midnight:
+                seconds_per_level = 60 * self.MIN_PER_LEVEL
+                level_delta = datetime.timedelta(seconds=seconds_per_level)
+                last_level = next_day_midnight - level_delta
+                new_start = last_level
+            event_item.event.start = new_start
+            if event_item.event.end:
+                timedelta = new_start - old_start
+                event_item.event.end += timedelta
+            pxdelta = (timedelta.total_seconds() / 60 * self.minute_height)
+            event_item.y += pxdelta
+            event_item.update()
+            self._drag_height -= pxdelta
+
+gobject.signal_new('event-pressed',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('event-activated',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('event-released',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('day-pressed',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('day-activated',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('day-selected',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('view-changed',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+gobject.signal_new('page-changed',
+    Calendar,
+    gobject.SIGNAL_RUN_FIRST,
+    gobject.TYPE_NONE,
+    (gobject.TYPE_PYOBJECT,))
+
+
+class DayItem(goocanvas.Group):
+    """
+    A canvas item representing a day.
+    """
+
+    def __init__(self, cal, **kwargs):
+        super(DayItem, self).__init__()
+
+        self._cal = cal
+        self.x = kwargs.get('x', 0)
+        self.y = kwargs.get('y', 0)
+        self.width = kwargs.get('width', 0)
+        self.height = kwargs.get('height', 0)
+        self.border_color = kwargs.get('border_color')
+        self.body_color = kwargs.get('body_color')
+        self.full_border = kwargs.get('full_border')
+        self.date = kwargs.get('date')
+        self.type = kwargs.get('type', 'month')
+        self.show_indic = False
+        self.lines = {}
+        self.n_lines = 0
+        self.title_text_color = ""
+        self.line_height = 0
+
+        # Create canvas items.
+        self.border = goocanvas.Rect(parent=self)
+        self.text = goocanvas.Text(parent=self)
+        self.box = goocanvas.Rect(parent=self)
+        self.indic = goocanvas.Rect(parent=self)
+
+    def update(self):
+        if not self.date:
+            return
+
+        week_day = self.date.weekday()
+        day_name = calendar.day_name[week_day]
+        caption = '%s %s' % (self.date.day, day_name)
+        style = self._cal.get_style()
+        font_descr = style.font_desc.copy()
+        font = font_descr.to_string()
+        self.text.set_property('font', font)
+        self.text.set_property('text', caption)
+        logical_height = self.text.get_natural_extents()[1][3]
+        line_height = int(math.ceil(float(logical_height) / pango.SCALE))
+        self.line_height = line_height
+
+        # Draw the border.
+        self.border.set_property('x', self.x)
+        self.border.set_property('y', self.y)
+        self.border.set_property('width', self.width)
+        self.border.set_property('height', self.height)
+        self.border.set_property('stroke_color', self.border_color)
+        self.border.set_property('fill_color', self.border_color)
+
+        # Draw the title text.
+        padding_left = 2
+        self.text.set_property('x', self.x + padding_left)
+        self.text.set_property('y', self.y)
+        self.text.set_property('fill_color', self.title_text_color)
+
+        # Print the "body" of the day.
+        if self.full_border:
+            box_x = self.x + 2
+            box_y = self.y + line_height
+            box_width = max(self.width - 4, 0)
+            box_height = max(self.height - line_height - 3, 0)
+        else:
+            box_x = self.x + 1
+            box_y = self.y + line_height
+            box_width = max(self.width - 2, 0)
+            box_height = max(self.height - line_height, 0)
+        self.box.set_property('x', box_x)
+        self.box.set_property('y', box_y)
+        self.box.set_property('width', box_width)
+        self.box.set_property('height', box_height)
+        self.box.set_property('stroke_color', self.body_color)
+        self.box.set_property('fill_color', self.body_color)
+
+        line_height_and_margin = line_height + 2  # 2px of margin per line
+        self.n_lines = int(box_height / line_height_and_margin)
+
+        # Show an indicator in the title, if requested.
+        if not self.show_indic:
+            self.indic.set_property('visibility', goocanvas.ITEM_INVISIBLE)
+            return
+
+        self.indic.set_property('visibility', goocanvas.ITEM_VISIBLE)
+        self.indic.set_property('x',
+            self.x + self.width - line_height / 1.5)
+        self.indic.set_property('y', self.y + line_height / 3)
+        self.indic.set_property('width', line_height / 3)
+        self.indic.set_property('height', line_height / 3)
+        self.indic.set_property('stroke_color', self.title_text_color)
+        self.indic.set_property('fill_color', self.title_text_color)
+
+        # Draw a triangle.
+        x1 = self.x + self.width - line_height / 1.5
+        y1 = self.y + line_height / 3
+        x2 = x1 + line_height / 6
+        y2 = y1 + line_height / 3
+        x3 = x1 + line_height / 3
+        y3 = y1
+        path = 'M%s,%s L%s,%s L%s,%s Z' % (x1, y1, x2, y2, x3, y3)
+        self.indic.set_property('clip_path', path)
+
+
+class EventItem(goocanvas.Group):
+    """
+    A canvas item representing an event.
+    """
+
+    def __init__(self, cal, **kwargs):
+        super(EventItem, self).__init__()
+
+        self._cal = cal
+        self.x = kwargs.get('x')
+        self.y = kwargs.get('y')
+        self.width = kwargs.get('width')
+        self.height = kwargs.get('height')
+        self.bg_color = kwargs.get('bg_color')
+        self.text_color = kwargs.get('text_color')
+        self.event = kwargs.get('event')
+        self.type = kwargs.get('type', 'leftright')
+        self.time_format = kwargs.get('time_format')
+        self.transparent = False
+        self.no_caption = False
+
+        # Create canvas items.
+        self.box = goocanvas.Rect(parent=self)
+        self.text = goocanvas.Text(parent=self)
+        style = self._cal.get_style()
+        font_descr = style.font_desc.copy()
+        self.font = font_descr.to_string()
+        self.text.set_property('font', self.font)
+        logical_height = self.text.get_natural_extents()[1][3]
+        self.line_height = logical_height / pango.SCALE
+
+        if self.x is not None:
+            self.update()
+
+    def update(self):
+        if (self.event.all_day or self._cal.view == "month"
+                or self.event.multidays):
+            self.update_all_day_event()
+        else:
+            self.update_event()
+
+    def update_event(self):
+        self.width = max(self.width, 0)
+        starttime = self.event.start.strftime(self.time_format)
+        endtime = self.event.end.strftime(self.time_format)
+        tooltip = '%s - %s\n%s' % (starttime, endtime, self.event.caption)
+
+        # Do we have enough width for caption
+        first_line = starttime + ' - ' + endtime
+        self.text.set_property('text', first_line)
+        logical_width = self.text.get_natural_extents()[1][2] / pango.SCALE
+        if self.width < logical_width:
+            first_line = starttime + ' - '
+
+        second_line = self.event.caption
+        self.text.set_property('text', second_line)
+        logical_width = self.text.get_natural_extents()[1][2] / pango.SCALE
+        if self.width < logical_width:
+            second_line = None
+
+        # Do we have enough height for whole caption
+        if self.height >= (2 * self.line_height):
+            caption = first_line
+            if second_line:
+                caption += '\n' + second_line
+        elif self.height >= self.line_height:
+            caption = first_line
+        else:
+            caption = ''
+        caption = '' if self.no_caption else caption
+        the_event_bg_color = self.event.bg_color
+
+        # Choose text color.
+        if self.event.text_color is None:
+            the_event_text_color = self.text_color
+        else:
+            the_event_text_color = self.event.text_color
+
+        if the_event_bg_color is not None:
+            self.box.set_property('x', self.x)
+            self.box.set_property('y', self.y)
+            self.box.set_property('width', self.width)
+            self.box.set_property('height', self.height)
+            self.box.set_property('stroke_color', the_event_bg_color)
+            bg_rgba_color = self.box.get_property('stroke_color_rgba')
+            bg_colors = list(util.rgba_to_colors(bg_rgba_color))
+            # We make background color 1/4 brighter (+64 over 255)
+            bg_colors[:3] = map(min,
+                map(add, bg_colors[:3], (64,) * 3), (255,) * 3)
+            bg_colors = util.colors_to_rgba(*bg_colors)
+            self.box.set_property('fill_color_rgba', bg_colors)
+
+            # Alpha color is set to half of 255, i.e an opacity of 5O percents
+            transparent_color = self.box.get_property('fill_color_rgba') - 128
+            if self.transparent:
+                self.box.set_property('stroke_color_rgba', transparent_color)
+                self.box.set_property('fill_color_rgba', transparent_color)
+            self.box.set_property('tooltip', tooltip)
+
+        # Print the event name into the title box.
+        self.text.set_property('x', self.x + 2)
+        self.text.set_property('y', self.y)
+        self.text.set_property('text', caption)
+        self.text.set_property('fill_color', the_event_text_color)
+        self.text.set_property('tooltip', tooltip)
+
+        # Clip the text.
+        x2, y2 = self.x + self.width, self.y + self.height,
+        path = 'M%s,%s L%s,%s L%s,%s L%s,%s Z' % (self.x, self.y, self.x, y2,
+            x2, y2, x2, self.y)
+        self.text.set_property('clip_path', path)
+
+    def update_all_day_event(self):
+        self.width = max(self.width, 0)
+        startdate = self.event.start.strftime('%x')
+        starttime = self.event.start.strftime(self.time_format)
+        if self.event.end:
+            enddate = self.event.end.strftime('%x')
+            endtime = self.event.end.strftime(self.time_format)
+
+        if self.event.all_day:
+            caption = self.event.caption
+            if not self.event.end:
+                tooltip = '%s\n%s' % (startdate, caption)
+            else:
+                tooltip = '%s - %s\n%s' % (startdate, enddate, caption)
+        elif self.event.multidays:
+            caption = '%s %s' % (starttime, self.event.caption)
+            tooltip = '%s %s - %s %s\n%s' % (startdate, starttime, enddate,
+                endtime, self.event.caption)
+        else:
+            caption = '%s %s' % (starttime, self.event.caption)
+            tooltip = '%s - %s\n%s' % (starttime, endtime, self.event.caption)
+        caption = '' if self.no_caption else caption
+        the_event_bg_color = self.event.bg_color
+        self.text.set_property('text', caption)
+        logical_height = self.text.get_natural_extents()[1][3]
+        self.height = logical_height / pango.SCALE
+
+        # Choose text color.
+        if self.event.text_color is None:
+            the_event_text_color = self.text_color
+        else:
+            the_event_text_color = self.event.text_color
+
+        if the_event_bg_color is not None:
+            self.box.set_property('x', self.x)
+            self.box.set_property('y', self.y)
+            self.box.set_property('width', self.width)
+            self.box.set_property('height', self.height)
+            self.box.set_property('stroke_color', the_event_bg_color)
+            bg_rgba_color = self.box.get_property('stroke_color_rgba')
+            bg_colors = list(util.rgba_to_colors(bg_rgba_color))
+            bg_colors[:3] = map(min,
+                map(add, bg_colors[:3], (64,) * 3), (255,) * 3)
+            bg_colors = util.colors_to_rgba(*bg_colors)
+            self.box.set_property('fill_color_rgba', bg_colors)
+            transparent_color = self.box.get_property('fill_color_rgba') - 128
+            if self.transparent:
+                self.box.set_property('stroke_color_rgba', transparent_color)
+                self.box.set_property('fill_color_rgba', transparent_color)
+            self.box.set_property('tooltip', tooltip)
+
+        # Print the event name into the title box.
+        self.text.set_property('x', self.x + 2)
+        self.text.set_property('y', self.y)
+        self.text.set_property('fill_color', the_event_text_color)
+        self.text.set_property('tooltip', tooltip)
+
+        # Clip the text.
+        x2, y2 = self.x + self.width, self.y + self.height,
+        path = 'M%s,%s L%s,%s L%s,%s L%s,%s Z' % (
+            self.x, self.y, self.x, y2, x2, y2, x2, self.y)
+        self.text.set_property('clip_path', path)
+
+
+class TimelineItem(goocanvas.Group):
+    """
+    A canvas item representing a timeline.
+    """
+
+    def __init__(self, cal, **kwargs):
+        super(TimelineItem, self).__init__()
+
+        self._cal = cal
+        self.x = kwargs.get('x')
+        self.y = kwargs.get('y')
+        self.line_color = kwargs.get('line_color')
+        self.bg_color = kwargs.get('bg_color')
+        self.text_color = kwargs.get('text_color')
+        self.time_format = kwargs.get('time_format')
+        self.width = 0
+
+        # Create canvas items.
+        self._timeline_rect = {}
+        self._timeline_text = {}
+        for n in range(24):
+            caption = datetime.time(n).strftime(self.time_format)
+            self._timeline_rect[n] = goocanvas.Rect(parent=self)
+            self._timeline_text[n] = goocanvas.Text(parent=self, text=caption)
+
+        if self.x is not None:
+            self.update()
+
+    @property
+    def min_line_height(self):
+        logical_height = 0
+        self.ink_padding_top = 0
+        for n in range(24):
+            natural_extents = self._timeline_text[n].get_natural_extents()
+            logical_rect = natural_extents[1]
+            logical_height = max(logical_height, logical_rect[3])
+            ink_rect = natural_extents[0]
+            self.ink_padding_top = max(self.ink_padding_top, ink_rect[0])
+        line_height = int(math.ceil(float(logical_height) / pango.SCALE))
+        return line_height
+
+    @property
+    def line_height(self):
+        self.padding_top = 0
+        line_height = self.min_line_height
+        if line_height < self.height / 24:
+            line_height = self.height / 24
+            pango_size = self._cal.get_style().font_desc.get_size()
+            padding_top = (line_height - pango_size / pango.SCALE) / 2
+            padding_top -= int(math.ceil(float(self.ink_padding_top) /
+                pango.SCALE))
+            self.padding_top = padding_top
+        return line_height
+
+    def _compute_width(self):
+        style = self._cal.get_style()
+        font = style.font_desc
+        ink_padding_left = 0
+        ink_max_width = 0
+        for n in range(24):
+            self._timeline_text[n].set_property('font', font)
+            natural_extents = self._timeline_text[n].get_natural_extents()
+            ink_rect = natural_extents[0]
+            ink_padding_left = max(ink_padding_left, ink_rect[0])
+            ink_max_width = max(ink_max_width, ink_rect[2])
+        self.width = int(math.ceil(float(ink_padding_left + ink_max_width)
+            / pango.SCALE))
+
+    def update(self):
+        self._compute_width()
+        line_height = self.line_height
+
+        # Draw the timeline.
+        for n in range(24):
+            rect = self._timeline_rect[n]
+            text = self._timeline_text[n]
+            y = self.y + n * line_height
+
+            rect.set_property('x', self.x)
+            rect.set_property('y', y)
+            rect.set_property('width', self.width)
+            rect.set_property('height', line_height)
+            rect.set_property('stroke_color', self.line_color)
+            rect.set_property('fill_color', self.bg_color)
+
+            text.set_property('x', self.x)
+            text.set_property('y', y + self.padding_top)
+            text.set_property('fill_color', self.text_color)
diff --git a/goocalendar/_event.py b/goocalendar/_event.py
new file mode 100644
index 0000000..7d8f59b
--- /dev/null
+++ b/goocalendar/_event.py
@@ -0,0 +1,97 @@
+#This file is part of GooCalendar.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+import gobject
+
+import util
+from .util import total_ordering
+
+
+ at total_ordering
+class Event(object):
+    """
+    This class represents an event that can be displayed in the calendar.
+    """
+
+    def __init__(self, caption, start, end=None, **kwargs):
+        assert caption is not None
+        assert start is not None
+        self.id = None
+        self.caption = caption
+        self.start = start
+        self.end = end
+        self.all_day = kwargs.get('all_day', False)
+        self.text_color = kwargs.get('text_color', None)
+        self.bg_color = kwargs.get('bg_color', 'orangered')
+        self.event_items = []
+        if end is None:
+            self.all_day = True
+
+    @property
+    def multidays(self):
+        if not self.end:
+            return False
+        return (self.end - self.start).days > 0
+
+    def __eq__(self, other_event):
+        if not isinstance(other_event, Event):
+            raise NotImplemented
+        return (self.start, self.end) == (other_event.start, other_event.start)
+
+    def __lt__(self, other_event):
+        if not isinstance(other_event, Event):
+            raise NotImplemented
+        return (self.start, self.end) < (other_event.start, other_event.start)
+
+
+class EventStore(gobject.GObject):
+    __gsignals__ = {
+        'event-removed': (gobject.SIGNAL_RUN_FIRST,
+            gobject.TYPE_NONE,
+            (gobject.TYPE_PYOBJECT,)),
+        'event-added': (gobject.SIGNAL_RUN_FIRST,
+            gobject.TYPE_NONE,
+            (gobject.TYPE_PYOBJECT,)),
+        'events-cleared': (gobject.SIGNAL_RUN_FIRST,
+            gobject.TYPE_NONE, ())}
+
+    def __init__(self):
+        super(EventStore, self).__init__()
+        self._next_event_id = 0
+        self._events = {}
+
+    def remove(self, event):
+        assert event is not None
+        if event.id is None:
+            return
+        del self._events[event.id]
+        self.emit('event-removed', event)
+
+    def add(self, event):
+        assert event is not None
+        self.add_events([event])
+
+    def add_events(self, events):
+        for event in events:
+            assert event.id is None
+            self._events[self._next_event_id] = event
+            event.id = self._next_event_id
+            self._next_event_id += 1
+        self.emit('event-added', events)
+
+    def clear(self):
+        self._events.clear()
+        self._next_event_id = 0
+        self.emit('events-cleared')
+
+    def get_events(self, start=None, end=None):
+        """
+        Returns a list of all events that intersect with the given start
+        and end times.
+        """
+        if not start and not end:
+            return self._events.values()
+        events = []
+        for event in self._events.values():
+            if util.event_intersects(event, start, end):
+                events.append(event)
+        return events
diff --git a/goocalendar/util.py b/goocalendar/util.py
new file mode 100644
index 0000000..f332f17
--- /dev/null
+++ b/goocalendar/util.py
@@ -0,0 +1,241 @@
+#This file is part of GooCalendar.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+import sys
+import datetime
+
+
+def my_weekdatescalendar(cal, date):
+    weeks = cal.monthdatescalendar(date.year, date.month)
+    for weekno, week in enumerate(weeks):
+        # Hide all days that are not part of the current week.
+        if date in week:
+            return week
+    raise Exception('No such week')
+
+
+def my_monthdatescalendar(cal, date):
+    # Months that have only five weeks are filled with another week from
+    # the following month.
+    weeks = cal.monthdatescalendar(date.year, date.month)
+    if len(weeks) < 6:
+        last_day = weeks[-1][-1]
+        offset = datetime.timedelta(1)
+        week = []
+        for i in range(7):
+            last_day += offset
+            week.append(last_day)
+        weeks.append(week)
+    return weeks
+
+
+def first_day_of_week(cal, date):
+    firstweekday = cal.firstweekday
+    day_shift = (date.weekday() + 7 - firstweekday) % 7
+    return date - datetime.timedelta(day_shift)
+
+
+def previous_month(cal, date):
+    year, month, day = date.timetuple()[:3]
+    if month == 1:
+        year -= 1
+        month = 12
+    else:
+        month -= 1
+    prev_month_days = [d for d in cal.itermonthdays(year, month)]
+    if day not in prev_month_days:
+        day = max(prev_month_days)
+    return datetime.datetime(year, month, day)
+
+
+def next_month(cal, date):
+    year, month, day = date.timetuple()[:3]
+    if month == 12:
+        year += 1
+        month = 1
+    else:
+        month += 1
+    next_month_days = [d for d in cal.itermonthdays(year, month)]
+    if day not in next_month_days:
+        day = max(next_month_days)
+    return datetime.datetime(year, month, day)
+
+
+def same_month(date1, date2):
+    return (date1.year == date2.year and date1.month == date2.month)
+
+
+def previous_week(cal, date):
+    return date - datetime.timedelta(7)
+
+
+def next_week(cal, date):
+    return date + datetime.timedelta(7)
+
+
+def time_delta(datetime1, datetime2):
+    delta = datetime1 - datetime2
+    if delta < datetime.timedelta():
+        return -delta
+    return delta
+
+
+def event_days(event1, event2):
+    end1 = event1.end if event1.end else event1.start
+    end2 = event2.end if event2.end else event2.start
+    return (time_delta(event1.start, end1).days
+         - time_delta(event2.start, end2).days)
+
+
+def event_intersects(event, start, end=None):
+    end = end if end else start
+    event_end = event.end if event.end else event.start
+    return ((event.start >= start and event.start < end)
+        or (event_end > start and event_end <= end)
+        or (event.start < start and event_end > end))
+
+
+def get_intersection_list(list, start, end):
+    intersections = []
+    for event in list:
+        if event_intersects(event, start, end):
+            intersections.append(event)
+    return intersections
+
+
+def count_intersections(list, start, end):
+    intersections = 0
+    for event in list:
+        if event_intersects(event, start, end):
+            intersections += 1
+    return intersections
+
+
+def count_parallel_events(list, start, end):
+    """
+    Given a list of events, this function returns the maximum number of
+    parallel events in the given timeframe.
+    """
+    parallel = 0
+    i = 0
+    for i, event1 in enumerate(list):
+        if not event_intersects(event1, start, end):
+            continue
+        parallel = max(parallel, 1)
+        for f in range(i + 1, len(list)):
+            event2 = list[f]
+            new_start = max(event1.start, event2.start)
+            new_end = min(event1.end, event2.end)
+            if (event_intersects(event2, start, end)
+                    and event_intersects(event2, new_start, new_end)):
+                n = count_parallel_events(list[f:], new_start, new_end)
+                parallel = max(parallel, n + 1)
+    return parallel
+
+
+def next_level(cur_time, min_per_level):
+    """
+    Given a datetime and the duration (in minutes) of time levels,
+    return the datetime of the next level
+    """
+    delta_per_level = datetime.timedelta(minutes=min_per_level)
+    delta_min = cur_time.minute % min_per_level
+    delta_sec = cur_time.second
+    cur_delta = datetime.timedelta(minutes=delta_min, seconds=delta_sec)
+    next_level = cur_time - cur_delta + delta_per_level
+    return next_level
+
+
+def prev_level(cur_time, min_per_level):
+    """
+    Given a datetime and the duration (in minutes) of time levels,
+    return the datetime of the previous level
+    """
+    delta_per_level = datetime.timedelta(minutes=min_per_level)
+    delta_min = cur_time.minute % min_per_level
+    cur_delta = datetime.timedelta(minutes=delta_min)
+    prev_level = cur_time - cur_delta
+    if prev_level == cur_time:
+        prev_level -= delta_per_level
+    return prev_level
+
+
+def color_to_string(color):
+    hexstring = "#%02X%02X%02X" % (
+        color.red / 256, color.blue / 256, color.green / 256)
+    return hexstring
+
+
+def colors_to_rgba(red, green, blue, alpha):
+    values = [alpha, blue, green, red]
+    rgba_color = 0
+    base = 1
+    for value in values:
+        rgba_color += value * base
+        base *= 256
+    return rgba_color
+
+
+def rgba_to_colors(rgba):
+    i = 0
+    colors = []
+    base = 256
+    prev_base = 1
+    while i < 4:
+        value = (rgba % base) / prev_base
+        colors.append(value)
+        rgba -= value
+        prev_base = base
+        base *= 256
+        i += 1
+    return colors[3], colors[2], colors[1], colors[0]
+
+
+def left_click(func):
+    def wrapper(*args):
+        if args[-1].button != 1:  # left mouse button is required
+            return
+        return func(*args)
+    return wrapper
+
+
+if sys.version_info >= (2, 7):
+    from functools import total_ordering
+else:
+    # This code comes from python standard library
+    def total_ordering(cls):
+        """Class decorator that fills in missing ordering methods"""
+        convert = {
+            '__lt__': [
+                ('__gt__', lambda self, other:
+                    not (self < other or self == other)),
+                ('__le__', lambda self, other: self < other or self == other),
+                ('__ge__', lambda self, other: not self < other)],
+            '__le__': [
+                ('__ge__', lambda self, other:
+                    not self <= other or self == other),
+                ('__lt__', lambda self, other:
+                    self <= other and not self == other),
+                ('__gt__', lambda self, other: not self <= other)],
+            '__gt__': [
+                ('__lt__', lambda self, other:
+                    not (self > other or self == other)),
+                ('__ge__', lambda self, other: self > other or self == other),
+                ('__le__', lambda self, other: not self > other)],
+            '__ge__': [
+                ('__le__', lambda self, other:
+                    (not self >= other) or self == other),
+                ('__gt__', lambda self, other:
+                    self >= other and not self == other),
+                ('__lt__', lambda self, other: not self >= other)]
+            }
+        roots = set(dir(cls)) & set(convert)
+        if not roots:
+            raise ValueError(
+                'must define at least one ordering operation: < > <= >=')
+        root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
+        for opname, opfunc in convert[root]:
+            if opname not in roots:
+                opfunc.__name__ = opname
+                opfunc.__doc__ = getattr(int, opname).__doc__
+                setattr(cls, opname, opfunc)
+        return cls
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..f51b0f5
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#This file is part of GooCalendar.  The COPYRIGHT file at the top level of
+#this repository contains the full copyright notices and license terms.
+
+import os
+from setuptools import setup, find_packages
+
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(name='GooCalendar',
+    version='0.1',
+    author='Cédric Krier',
+    author_email='cedric.krier at b2ck.com',
+    url='http://code.google.com/p/goocalendar/',
+    description='A calendar widget for GTK using PyGoocanvas',
+    long_description=read('README'),
+    download_url='http://code.google.com/p/goocalendar/downloads/',
+    packages=find_packages(),
+    classifiers=[
+        'Development Status :: 5 - Production/Stable',
+        'Environment :: X11 Applications :: GTK',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python :: 2 :: Only',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Topic :: Software Development :: Widget Sets',
+        ],
+    license='GPL-2',
+    )
-- 
goocalendar



More information about the tryton-debian-vcs mailing list