Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlan Aguiar <alanjas@hotmail.com>2012-10-21 08:55:07 (GMT)
committer Alan Aguiar <alanjas@hotmail.com>2012-10-21 08:55:07 (GMT)
commita6161308da868451f2250df8e6b11823953122ff (patch)
tree3f8d5cb662327f6a016a0f91fe2f6b3a614b9199
parente573e421998cc051256811ab56729e3b21eaf8a3 (diff)
add new timeline 0.18
-rw-r--r--CHANGES19
-rw-r--r--HACKING4
-rw-r--r--README2
-rwxr-xr-xexecute-specs.py3
-rw-r--r--libs/dependencies/icalendar-2.1/icalendar/prop.py10
-rw-r--r--libs/dependencies/pysvg-0.2.1/pysvg/__init__.py2
-rw-r--r--libs/dependencies/pysvg-0.2.1/pysvg/linking.py2
-rw-r--r--libs/dependencies/pysvg-0.2.1/pysvg/shape.py2
-rw-r--r--libs/dependencies/pysvg-0.2.1/pysvg/turtle.py2
-rw-r--r--specs/AlertController.py29
-rw-r--r--specs/Backend.py72
-rw-r--r--specs/CategoriesTree.py4
-rw-r--r--specs/CategoryEditor.py2
-rw-r--r--specs/CategorySorter.py2
-rw-r--r--specs/ContainerEditor.py13
-rw-r--r--specs/ContainerObject.py13
-rw-r--r--specs/ContainerStrategyInterface.py9
-rw-r--r--specs/DbOpen.py15
-rw-r--r--specs/DefaultContainerStrategy.py85
-rw-r--r--specs/DuplicateEventDialog.py19
-rw-r--r--specs/EventEditor.py22
-rw-r--r--specs/EventObject.py19
-rw-r--r--specs/FileTimeline.py33
-rw-r--r--specs/MemoryDB.py20
-rw-r--r--specs/NumTimePicker.py2
-rw-r--r--specs/NumTimeType.py8
-rw-r--r--specs/PlayController.py8
-rw-r--r--specs/PyDateTimePicker.py6
-rw-r--r--specs/PyTimeNavigationFunctions.py48
-rw-r--r--specs/PyTimeType.py20
-rw-r--r--specs/Scene.py18
-rw-r--r--specs/SubeventObject.py12
-rw-r--r--specs/TextDisplayEditor.py57
-rw-r--r--specs/TimePeriod.py4
-rw-r--r--specs/TimelineApplication.py4
-rw-r--r--specs/TimelineView.py20
-rw-r--r--specs/WxDateTimePicker.py6
-rw-r--r--specs/WxTimeType.py12
-rw-r--r--specs/XmlParser.py4
-rw-r--r--specs/XmlTimeline.py25
-rw-r--r--specs/utils.py23
-rw-r--r--timelinelib/application.py4
-rw-r--r--timelinelib/db/__init__.py14
-rw-r--r--timelinelib/db/backends/__init__.py21
-rw-r--r--timelinelib/db/backends/dir.py12
-rw-r--r--timelinelib/db/backends/file.py29
-rw-r--r--timelinelib/db/backends/ics.py49
-rw-r--r--timelinelib/db/backends/memory.py31
-rw-r--r--timelinelib/db/backends/tutorial.py128
-rw-r--r--timelinelib/db/backends/xmlfile.py36
-rw-r--r--timelinelib/db/exceptions.py27
-rw-r--r--timelinelib/db/interface.py189
-rw-r--r--timelinelib/db/objects/__init__.py27
-rw-r--r--timelinelib/db/objects/category.py47
-rw-r--r--timelinelib/db/objects/container.py56
-rw-r--r--timelinelib/db/objects/event.py139
-rw-r--r--timelinelib/db/objects/subevent.py57
-rw-r--r--timelinelib/db/objects/timeperiod.py230
-rw-r--r--timelinelib/db/observer.py39
-rw-r--r--timelinelib/db/search.py26
-rw-r--r--timelinelib/db/strategies.py71
-rw-r--r--timelinelib/db/utils.py12
-rw-r--r--timelinelib/drawing/__init__.py10
-rw-r--r--timelinelib/drawing/drawers/default.py23
-rw-r--r--timelinelib/drawing/interface.py2
-rw-r--r--timelinelib/drawing/scene.py48
-rw-r--r--timelinelib/drawing/viewproperties.py2
-rw-r--r--timelinelib/editors/category.py6
-rw-r--r--timelinelib/editors/container.py18
-rw-r--r--timelinelib/editors/duplicateevent.py10
-rw-r--r--timelinelib/editors/event.py42
-rw-r--r--timelinelib/editors/textdisplay.py32
-rw-r--r--timelinelib/export/svg.py44
-rw-r--r--timelinelib/help/pages.py2
-rw-r--r--timelinelib/meta/version.py2
-rw-r--r--timelinelib/play/playcontroller.py2
-rw-r--r--timelinelib/time/__init__.py4
-rw-r--r--timelinelib/time/numtime.py6
-rw-r--r--timelinelib/time/pytime.py51
-rw-r--r--timelinelib/time/wxtime.py28
-rw-r--r--timelinelib/view/drawingarea.py57
-rw-r--r--timelinelib/view/move.py2
-rw-r--r--timelinelib/view/periodbase.py2
-rw-r--r--timelinelib/view/periodevent.py2
-rw-r--r--timelinelib/view/resize.py2
-rw-r--r--timelinelib/view/scrolldrag.py10
-rw-r--r--timelinelib/wxgui/component.py86
-rw-r--r--timelinelib/wxgui/components/__init__.py23
-rw-r--r--timelinelib/wxgui/components/categorychoice.py12
-rw-r--r--timelinelib/wxgui/components/cattree.py8
-rw-r--r--timelinelib/wxgui/components/numtimepicker.py8
-rw-r--r--timelinelib/wxgui/components/pydatetimepicker.py60
-rw-r--r--timelinelib/wxgui/components/timelineview.py49
-rw-r--r--timelinelib/wxgui/components/wxdatetimepicker.py56
-rw-r--r--timelinelib/wxgui/dialogs/categorieseditor.py4
-rw-r--r--timelinelib/wxgui/dialogs/containereditor.py20
-rw-r--r--timelinelib/wxgui/dialogs/duplicateevent.py28
-rw-r--r--timelinelib/wxgui/dialogs/eventeditor.py78
-rw-r--r--timelinelib/wxgui/dialogs/mainframe.py156
-rw-r--r--timelinelib/wxgui/dialogs/preferences.py18
-rw-r--r--timelinelib/wxgui/dialogs/textdisplay.py81
-rw-r--r--timelinelib/wxgui/utils.py6
-rw-r--r--timelinelib/xml/__init__.py0
-rw-r--r--timelinelib/xml/parser.py270
104 files changed, 2234 insertions, 1060 deletions
diff --git a/CHANGES b/CHANGES
index 6bfe05a..e02eb9f 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,20 @@
+Timeline 0.18.0, released on 30 September 2012
+==============================================
+
+New features, enhancements:
+
+ * Zooming with scroll wheel zooms at cursor position instead of center.
+
+Bug fixes:
+
+ * Adding multiple events without closing event dialog, works again.
+ * Alert time comparision problem solved
+ * Fixed problem with ends-today property
+ * Fit millennium now works close to edges
+ * Fit century now works close to edges
+
+
+
Timeline 0.17.0, released on 15 June 2012
=========================================
@@ -10,7 +27,7 @@ New features, enhancements:
Bug fixes:
- * No Error when fitting month, december, when using extended timetype.
+ * No Error when fitting month, december, when using extended timetype.
Timeline 0.16.0, released on 31 January 2012
============================================
diff --git a/HACKING b/HACKING
index 86b721c..ce9049b 100644
--- a/HACKING
+++ b/HACKING
@@ -53,6 +53,7 @@ Features for the next version (x.y+1) can continue to be developed in main.
1. version.py
2. CHANGES
3. Run `python execute-specs.py` to find where else you need to modify
+ 4. Commit and push
Work on stable
--------------
@@ -62,6 +63,9 @@ Work on stable
1. Request download from here (login required)
http://translations.launchpad.net/thetimelineproj/trunk/+translations
2. Run `python import-po-from-launchpad-export.py /path/to/launchpad-export.tar.gz`
+ 3. Upload new pot-file
+ Create new po-file with the command
+ scons pot
3. Check that information and version numbers are correct in
1. version.py
2. CHANGES
diff --git a/README b/README
index 29ff32b..8d7ac6c 100644
--- a/README
+++ b/README
@@ -1,4 +1,4 @@
-This directory contains the 0.17.0 release of Timeline.
+This directory contains the 0.18.0 release of Timeline.
Timeline is a cross-platform application for displaying and navigating events
on a timeline.
diff --git a/execute-specs.py b/execute-specs.py
index d863ec8..9fd9fcf 100755
--- a/execute-specs.py
+++ b/execute-specs.py
@@ -32,6 +32,7 @@ def execute_all_specs():
def setup_paths():
root_dir = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(root_dir, "libs", "dev", "mock-0.7.2"))
+ sys.path.insert(0, os.path.join(root_dir, "libs", "dependencies", "icalendar-2.1"))
def install_gettext_in_builtin_namespace():
def _(message):
@@ -57,7 +58,7 @@ def add_specs(suite):
load_test_cases_from_module_name(suite, abs_module_name)
def add_doctests(suite):
- load_doc_test_from_module_name(suite, "timelinelib.db.backends.xmlparser")
+ load_doc_test_from_module_name(suite, "timelinelib.xml.parser")
load_doc_test_from_module_name(suite, "timelinelib.utils")
def load_test_cases_from_module_name(suite, module_name):
diff --git a/libs/dependencies/icalendar-2.1/icalendar/prop.py b/libs/dependencies/icalendar-2.1/icalendar/prop.py
index 9722bf0..50840d3 100644
--- a/libs/dependencies/icalendar-2.1/icalendar/prop.py
+++ b/libs/dependencies/icalendar-2.1/icalendar/prop.py
@@ -73,7 +73,7 @@ class vBinary:
'This is gibberish'
The roundtrip test
- >>> x = 'Binary data æ ø å \x13 \x56'
+ >>> x = 'Binary data æ ø å \x13 \x56'
>>> vBinary(x).ical()
'QmluYXJ5IGRhdGEg5iD4IOUgEyBW'
>>> vBinary.from_ical('QmluYXJ5IGRhdGEg5iD4IOUgEyBW')
@@ -1055,12 +1055,12 @@ class vText(unicode):
If you pass a unicode object, it will be utf-8 encoded. As this is the
(only) standard that RFC 2445 support.
- >>> t = vText(u'international chars æøå ÆØÅ ü')
+ >>> t = vText(u'international chars æøå ÆØÅ ü')
>>> t.ical()
'international chars \\xc3\\xa6\\xc3\\xb8\\xc3\\xa5 \\xc3\\x86\\xc3\\x98\\xc3\\x85 \\xc3\\xbc'
Unicode is converted to utf-8
- >>> t = vText(u'international æ ø å')
+ >>> t = vText(u'international æ ø å')
>>> str(t)
'international \\xc3\\xa6 \\xc3\\xb8 \\xc3\\xa5'
@@ -1386,12 +1386,12 @@ class TypesFactory(CaselessDict):
datetime.datetime(2005, 1, 1, 12, 30)
It can also be used to directly encode property and parameter values
- >>> comment = factory.ical('comment', u'by Rasmussen, Max Møller')
+ >>> comment = factory.ical('comment', u'by Rasmussen, Max Møller')
>>> str(comment)
'by Rasmussen\\\\, Max M\\xc3\\xb8ller'
>>> factory.ical('priority', 1)
'1'
- >>> factory.ical('cn', u'Rasmussen, Max Møller')
+ >>> factory.ical('cn', u'Rasmussen, Max Møller')
'Rasmussen\\\\, Max M\\xc3\\xb8ller'
>>> factory.from_ical('cn', 'Rasmussen\\\\, Max M\\xc3\\xb8ller')
diff --git a/libs/dependencies/pysvg-0.2.1/pysvg/__init__.py b/libs/dependencies/pysvg-0.2.1/pysvg/__init__.py
index ed916d5..0a81d9f 100644
--- a/libs/dependencies/pysvg-0.2.1/pysvg/__init__.py
+++ b/libs/dependencies/pysvg-0.2.1/pysvg/__init__.py
@@ -11,4 +11,4 @@
-
+ \ No newline at end of file
diff --git a/libs/dependencies/pysvg-0.2.1/pysvg/linking.py b/libs/dependencies/pysvg-0.2.1/pysvg/linking.py
index ed003b1..3f4fc5a 100644
--- a/libs/dependencies/pysvg-0.2.1/pysvg/linking.py
+++ b/libs/dependencies/pysvg-0.2.1/pysvg/linking.py
@@ -64,4 +64,4 @@ class view(BaseElement, CoreAttrib, ExternalAttrib):
def set_viewTarget(self,viewTarget):
self._attributes['viewTarget']=viewTarget
def get_viewTarget(self):
- return self._attributes['viewTarget']
+ return self._attributes['viewTarget'] \ No newline at end of file
diff --git a/libs/dependencies/pysvg-0.2.1/pysvg/shape.py b/libs/dependencies/pysvg-0.2.1/pysvg/shape.py
index 5b87251..c61058d 100644
--- a/libs/dependencies/pysvg-0.2.1/pysvg/shape.py
+++ b/libs/dependencies/pysvg-0.2.1/pysvg/shape.py
@@ -478,4 +478,4 @@ class polygon(polyline):
def __init__(self, points=None, **kwargs):
BaseElement.__init__(self,'polygon')
self.set_points(points)
- self.setKWARGS(**kwargs)
+ self.setKWARGS(**kwargs) \ No newline at end of file
diff --git a/libs/dependencies/pysvg-0.2.1/pysvg/turtle.py b/libs/dependencies/pysvg-0.2.1/pysvg/turtle.py
index febcada..a15a16c 100644
--- a/libs/dependencies/pysvg-0.2.1/pysvg/turtle.py
+++ b/libs/dependencies/pysvg-0.2.1/pysvg/turtle.py
@@ -203,4 +203,4 @@ class Turtle(object):
for element in self.getSVGElements():
svgContainer.addElement(element)
return svgContainer
-
+ \ No newline at end of file
diff --git a/specs/AlertController.py b/specs/AlertController.py
index 2e57c68..4b916d6 100644
--- a/specs/AlertController.py
+++ b/specs/AlertController.py
@@ -37,27 +37,24 @@ class AlertControllerSpec(unittest.TestCase):
self.given_early_pytimes()
self.given_controller_time_type(PyTimeType())
time_as_text = "%s" % self.tm
- expired = self.controller._time_has_expired(time_as_text)
+ expired = self.controller._time_has_expired(self.tm)
self.assertTrue(expired)
-
+
def test_pytime_has_not_expired(self):
self.given_late_pytimes()
- time_as_text = "%s" % self.tm
- expired = self.controller._time_has_expired(time_as_text)
+ expired = self.controller._time_has_expired(self.tm)
self.assertFalse(expired)
-
+
def test_wxtime_has_expired(self):
self.given_early_wxtimes()
self.given_controller_time_type(WxTimeType())
- time_as_text = "%s" % self.tm
- expired = self.controller._time_has_expired(time_as_text)
+ expired = self.controller._time_has_expired(self.tm)
self.assertTrue(expired)
-
+
def test_wxtime_has_not_expired(self):
self.given_late_wxtimes()
self.given_controller_time_type(WxTimeType())
- time_as_text = "%s" % self.tm
- expired = self.controller._time_has_expired(time_as_text)
+ expired = self.controller._time_has_expired(self.tm)
self.assertFalse(expired)
def given_early_pytimes(self):
@@ -89,24 +86,24 @@ class AlertControllerSpec(unittest.TestCase):
def given_wxtime_later(self):
self.tm = self.now + wx.TimeSpan(hours=12)
-
+
def given_wxtime_earlier(self):
self.tm = self.now - wx.TimeSpan(hours=12)
-
+
def given_pytime_now(self):
self.now = PyTimeType().now()
-
+
def given_pytime_later(self):
self.tm = self.now + datetime.timedelta(days=1)
-
+
def given_pytime_earlier(self):
self.tm = self.now + datetime.timedelta(days=-1)
def given_controller_time_type(self, time_type):
self.controller.time_type = time_type
-
+
def setUp(self):
self.now = PyTimeType().now()
self.alert = (self.now, "Time to go")
- self.event = an_event()
+ self.event = an_event()
self.controller = AlertController()
diff --git a/specs/Backend.py b/specs/Backend.py
new file mode 100644
index 0000000..3bac5fd
--- /dev/null
+++ b/specs/Backend.py
@@ -0,0 +1,72 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+import unittest
+
+from specs.utils import TmpDirTestCase
+from timelinelib.db.backends.ics import IcsTimeline
+from timelinelib.db.backends.memory import MemoryDB
+
+
+class BackendTest(object):
+
+ # These tests should work for any backend. They are tested with different
+ # backends below.
+
+ def test_get_all_events_returns_a_list(self):
+ all_events = self.backend.get_all_events()
+ self.assertTrue(isinstance(all_events, list))
+
+ def test_has_time_type_method(self):
+ self.backend.get_time_type()
+
+
+class MemoryBackendTest(unittest.TestCase, BackendTest):
+
+ def setUp(self):
+ self.backend = MemoryDB()
+
+
+class IcsBackendTest(TmpDirTestCase, BackendTest):
+
+ def setUp(self):
+ TmpDirTestCase.setUp(self)
+ self.backend = IcsTimeline(self.write_ics_content())
+
+ def write_ics_content(self):
+ tmp_path = self.get_tmp_path("test.ics")
+ f = open(tmp_path, "w")
+ f.write(ICS_EXAMPLE)
+ f.close()
+ return tmp_path
+
+
+ICS_EXAMPLE = """
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//hacksw/handcal//NONSGML v1.0//EN
+BEGIN:VEVENT
+UID:uid1@example.com
+DTSTAMP:19970714T170000Z
+ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com
+DTSTART:19970714T170000Z
+DTEND:19970715T035959Z
+SUMMARY:Bastille Day Party
+END:VEVENT
+END:VCALENDAR
+"""
diff --git a/specs/CategoriesTree.py b/specs/CategoriesTree.py
index 9c76bdf..62a08cc 100644
--- a/specs/CategoriesTree.py
+++ b/specs/CategoriesTree.py
@@ -20,7 +20,7 @@ import unittest
from mock import Mock
-from timelinelib.db.interface import TimelineDB
+from timelinelib.db.backends.memory import MemoryDB
from timelinelib.db.objects import Category
from timelinelib.wxgui.components.cattree import CategoriesTree
from timelinelib.wxgui.components.cattree import CategoriesTreeController
@@ -51,7 +51,7 @@ class describe_categories_tree_control(unittest.TestCase):
self.controller.initialize_from_timeline_view(None)
def setUp(self):
- self.db = Mock(TimelineDB)
+ self.db = Mock(MemoryDB)
self.foo = Category("foo", (255, 0, 0), None, True, parent=None)
self.foofoo = Category("foofoo", (255, 0, 0), None, True, parent=self.foo)
self.bar = Category("bar", (255, 0, 0), None, True, parent=None)
diff --git a/specs/CategoryEditor.py b/specs/CategoryEditor.py
index 3941026..30e47a5 100644
--- a/specs/CategoryEditor.py
+++ b/specs/CategoryEditor.py
@@ -20,7 +20,7 @@ import unittest
from mock import Mock
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
from timelinelib.editors.category import CategoryEditor
from timelinelib.repositories.interface import CategoryRepository
diff --git a/specs/CategorySorter.py b/specs/CategorySorter.py
index 37c4021..88eedaa 100644
--- a/specs/CategorySorter.py
+++ b/specs/CategorySorter.py
@@ -18,8 +18,8 @@
import unittest
+from timelinelib.db.objects.category import sort_categories
from timelinelib.db.objects import Category
-from timelinelib.domain.category import sort_categories
class CategorySorter(unittest.TestCase):
diff --git a/specs/ContainerEditor.py b/specs/ContainerEditor.py
index bec39b6..d01b34e 100644
--- a/specs/ContainerEditor.py
+++ b/specs/ContainerEditor.py
@@ -21,11 +21,11 @@ import unittest
from mock import Mock, sentinel
from specs.utils import an_event_with, human_time_to_py, ObjectWithTruthValue
+from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.objects import Container
from timelinelib.editors.container import ContainerEditor
from timelinelib.repositories.interface import EventRepository
from timelinelib.wxgui.dialogs.eventeditor import ContainerEditorDialog
-from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.container import Container
class ContainerEditorTestCase(unittest.TestCase):
@@ -42,25 +42,24 @@ class ContainerEditorTestCase(unittest.TestCase):
self.given_editor_without_container()
self.view.set_name.assert_called_with("")
self.view.set_category.assert_called_with(None)
-
+
def testConstructionWithContainer(self):
self.given_editor_with_container()
self.view.set_name.assert_called_with("Container1")
self.view.set_category.assert_called_with(None)
-
+
def testContainerCreated(self):
self.given_editor_without_container()
self.editor.save()
self.view.get_name.assert_called()
self.view.get_category.assert_called()
self.assertFalse(self.editor.container == None)
-
+
def given_editor_without_container(self):
self.editor = ContainerEditor(self.view, self.db, None)
def given_editor_with_container(self):
self.editor = ContainerEditor(self.view, self.db, self.container)
-
+
def time(self, tm):
return self.db.get_time_type().parse_time(tm)
-
diff --git a/specs/ContainerObject.py b/specs/ContainerObject.py
index bca851a..c1bbf9d 100644
--- a/specs/ContainerObject.py
+++ b/specs/ContainerObject.py
@@ -18,10 +18,10 @@
import unittest
-from timelinelib.db.objects import Category
-from timelinelib.db.container import Container
-from timelinelib.db.subevent import Subevent
from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.objects import Category
+from timelinelib.db.objects import Container
+from timelinelib.db.objects import Subevent
class ContainerSpec(unittest.TestCase):
@@ -49,7 +49,7 @@ class ContainerSpec(unittest.TestCase):
def testNameAndCategoryCanBeUpdated(self):
self.given_default_container()
new_name = "new text"
- new_category = Category("cat", (255,0,0), (255,0,0), True)
+ new_category = Category("cat", (255,0,0), (255,0,0), True)
self.container.update_properties(new_name, new_category)
self.assertEqual(new_category, self.container.category)
@@ -62,12 +62,12 @@ class ContainerSpec(unittest.TestCase):
self.container = Container(self.db.get_time_type(), self.now, self.now, "container")
def given_period_subevent(self):
- self.event = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
+ self.event = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
self.time("2000-01-03 10:01:01"), "evt")
def time(self, tm):
return self.db.get_time_type().parse_time(tm)
-
+
def setUp(self):
self.db = MemoryDB()
self.now = self.db.get_time_type().now()
@@ -98,4 +98,3 @@ class ContainerConstructorSpec(unittest.TestCase):
def setUp(self):
self.db = MemoryDB()
self.now = self.db.get_time_type().now()
-
diff --git a/specs/ContainerStrategyInterface.py b/specs/ContainerStrategyInterface.py
index 1d6da79..755616a 100644
--- a/specs/ContainerStrategyInterface.py
+++ b/specs/ContainerStrategyInterface.py
@@ -22,11 +22,11 @@ from timelinelib.db.interface import ContainerStrategy
class ContainerStrategyInterfaceSpec(unittest.TestCase):
-
+
def testConstruction(self):
self.given_strategy_with_none_container()
self.assertEqual(None, self.strategy.container)
-
+
def testRegisterSubeventNotImplemented(self):
self.given_strategy_with_none_container()
self.assertRaises(NotImplementedError, self.strategy.register_subevent, None)
@@ -34,14 +34,13 @@ class ContainerStrategyInterfaceSpec(unittest.TestCase):
def testUnregisterSubeventNotImplemented(self):
self.given_strategy_with_none_container()
self.assertRaises(NotImplementedError, self.strategy.unregister_subevent, None)
-
+
def testUpdateSubeventNotImplemented(self):
self.given_strategy_with_none_container()
self.assertRaises(NotImplementedError, self.strategy.update, None)
-
+
def given_strategy_with_none_container(self):
self.strategy = ContainerStrategy(None)
def setUp(self):
pass
-
diff --git a/specs/DbOpen.py b/specs/DbOpen.py
index 67a58c7..85a8d76 100644
--- a/specs/DbOpen.py
+++ b/specs/DbOpen.py
@@ -18,12 +18,8 @@
from datetime import datetime
import codecs
-import os
-import os.path
-import shutil
-import tempfile
-import unittest
+from specs.utils import TmpDirTestCase
from timelinelib.db.backends.xmlfile import XmlTimeline
from timelinelib.db import db_open
from timelinelib.drawing.viewproperties import ViewProperties
@@ -93,7 +89,7 @@ CONTENT_0100 = u"""
""".strip()
-class DbOpenSpec(unittest.TestCase):
+class DbOpenSpec(TmpDirTestCase):
IO = True
@@ -245,11 +241,8 @@ class DbOpenSpec(unittest.TestCase):
self.fail("Unknown category.")
def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(prefix="timeline-test")
- self.tmp_path = os.path.join(self.tmp_dir, "test.timeline")
-
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
+ TmpDirTestCase.setUp(self)
+ self.tmp_path = self.get_tmp_path("test.timeline")
def writeContentToTmpFile(self, content):
f = codecs.open(self.tmp_path, "w", "utf-8")
diff --git a/specs/DefaultContainerStrategy.py b/specs/DefaultContainerStrategy.py
index b3946ef..668aca7 100644
--- a/specs/DefaultContainerStrategy.py
+++ b/specs/DefaultContainerStrategy.py
@@ -19,13 +19,13 @@
import unittest
from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.container import Container
-from timelinelib.db.subevent import Subevent
+from timelinelib.db.objects import Container
+from timelinelib.db.objects import Subevent
from timelinelib.db.strategies import DefaultContainerStrategy
class DefaultContainerStartegySpec(unittest.TestCase):
-
+
def test_construction(self):
self.given_strategy_with_container()
self.assertEqual(self.container, self.strategy.container)
@@ -60,7 +60,7 @@ class DefaultContainerStartegySpec(unittest.TestCase):
self.strategy.update(self.subevent2)
self.assert_equal_start(self.container, self.subevent1)
self.assert_equal_end(self.container, self.subevent2)
-
+
def test_adding_partial_overlapping_event_moves_overlapped_event_backwards(self):
# Container event: +-------+
# New sub-event: +-------+
@@ -84,27 +84,27 @@ class DefaultContainerStartegySpec(unittest.TestCase):
# New sub-event: +---+
self.given_container_with_two_events_with_same_start_time()
self.assert_start_equals_end(self.subevent1, self.subevent2)
-
+
def test_overlapping_nonperiod_event_at_begining_moves_nonperiod_event_backwards(self):
# Container event: +
# New sub-event: +----------+
self.given_strategy_with_container()
self.given_event_overlapping_point_event()
self.assert_start_equals_start(self.subevent1, self.subevent2)
-
+
def test_overlapping_nonperiod_event_at_end_moves_nonperiod_event_forward(self):
# Container event: +
# New sub-event: +----------+
self.given_strategy_with_container()
self.given_event_overlapping_point_event2()
self.assert_start_equals_end(self.subevent1, self.subevent2)
-
+
def given_container_with_two_events_with_nonoverlapping_periods(self):
self.given_strategy_with_container()
self.given_two_events_with_nonoverlapping_periods()
self.strategy.register_subevent(self.subevent1)
self.strategy.register_subevent(self.subevent2)
-
+
def given_container_with_two_events_with_overlapping_periods(self):
self.given_strategy_with_container()
self.given_two_overlapping_events()
@@ -130,85 +130,84 @@ class DefaultContainerStartegySpec(unittest.TestCase):
self.strategy.register_subevent(self.subevent2)
def given_strategy_with_container(self):
- self.container = Container(self.db.get_time_type(),
- self.time("2000-01-01 10:01:01"),
+ self.container = Container(self.db.get_time_type(),
+ self.time("2000-01-01 10:01:01"),
self.time("2000-01-01 10:01:01"), "Container1")
self.strategy = DefaultContainerStrategy(self.container)
def given_event_overlapping_point_event(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-05-01 10:02:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-05-01 10:02:01"),
self.time("2000-05-01 10:02:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-05-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-05-01 10:01:01"),
self.time("2000-07-01 10:01:01"), "Container1")
self.strategy.register_subevent(self.subevent1)
self.strategy.register_subevent(self.subevent2)
def given_event_overlapping_point_event2(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-07-01 10:00:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-07-01 10:00:01"),
self.time("2000-07-01 10:00:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-05-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-05-01 10:01:01"),
self.time("2000-07-01 10:01:01"), "Container1")
self.strategy.register_subevent(self.subevent1)
self.strategy.register_subevent(self.subevent2)
-
+
def given_two_overlapping_events(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-06-01 10:01:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-05-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-05-01 10:01:01"),
self.time("2000-07-01 10:01:01"), "Container1")
def given_two_events_with_same_period(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-06-01 10:01:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-06-01 10:01:01"), "Container1")
def given_two_events_with_same_start_time(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-06-01 10:01:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-04-01 10:01:01"), "Container1")
def given_two_events_with_nonoverlapping_periods(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-01-01 10:01:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-01-01 10:01:01"),
self.time("2000-02-01 10:01:01"), "Container1")
- self.subevent2 = Subevent(self.db.get_time_type(),
- self.time("2000-03-01 10:01:01"),
+ self.subevent2 = Subevent(self.db.get_time_type(),
+ self.time("2000-03-01 10:01:01"),
self.time("2000-04-01 10:01:01"), "Container1")
-
+
def given_subevent1(self):
- self.subevent1 = Subevent(self.db.get_time_type(),
- self.time("2000-01-01 10:01:01"),
+ self.subevent1 = Subevent(self.db.get_time_type(),
+ self.time("2000-01-01 10:01:01"),
self.time("2000-02-01 10:01:01"), "Container1")
def assert_equal_start(self, obj1, obj2):
self.assertEqual(obj1.time_period.start_time, obj2.time_period.start_time)
-
+
def assert_equal_end(self, obj1, obj2):
self.assertEqual(obj1.time_period.end_time, obj2.time_period.end_time)
def assert_start_equals_end(self, obj1, obj2):
self.assertEqual(obj1.time_period.start_time, obj2.time_period.end_time)
-
+
def assert_start_equals_start(self, obj1, obj2):
self.assertEqual(obj1.time_period.start_time, obj2.time_period.start_time)
-
+
def time(self, tm):
return self.db.get_time_type().parse_time(tm)
-
+
def setUp(self):
self.db = MemoryDB()
self.now = self.db.get_time_type().now()
self.time_type = self.db.get_time_type()
-
diff --git a/specs/DuplicateEventDialog.py b/specs/DuplicateEventDialog.py
index 1d64cc1..74ee882 100644
--- a/specs/DuplicateEventDialog.py
+++ b/specs/DuplicateEventDialog.py
@@ -16,22 +16,21 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import unittest
import datetime
+import unittest
from mock import Mock
-from timelinelib.db.interface import TimelineDB
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Event
from timelinelib.db.objects import TimePeriod
-from timelinelib.wxgui.dialogs.duplicateevent import DuplicateEventDialog
-from timelinelib.editors.duplicateevent import DuplicateEventEditor
-from timelinelib.time import PyTimeType
-
-from timelinelib.editors.duplicateevent import FORWARD
from timelinelib.editors.duplicateevent import BACKWARD
from timelinelib.editors.duplicateevent import BOTH
+from timelinelib.editors.duplicateevent import DuplicateEventEditor
+from timelinelib.editors.duplicateevent import FORWARD
+from timelinelib.time import PyTimeType
+from timelinelib.wxgui.dialogs.duplicateevent import DuplicateEventDialog
class duplicate_event_dialog_spec_base(unittest.TestCase):
@@ -56,13 +55,13 @@ class duplicate_event_dialog_spec_base(unittest.TestCase):
return self.move_period_fn
def _create_db_mock(self):
- self.db = Mock(TimelineDB)
+ self.db = Mock(MemoryDB)
self.db.get_time_type.return_value = PyTimeType()
return self.db
def _create_event(self):
self.event = Event(
- self.db.get_time_type(),
+ self.db.get_time_type(),
datetime.datetime(2010, 1, 1),
datetime.datetime(2010, 1, 1),
"foo",
diff --git a/specs/EventEditor.py b/specs/EventEditor.py
index d25bbbf..895c6e5 100644
--- a/specs/EventEditor.py
+++ b/specs/EventEditor.py
@@ -51,7 +51,7 @@ class EventEditorTestCase(unittest.TestCase):
def when_editor_opened_with(self, start, end, event):
self.editor = EventEditor(self.view)
- self.editor.edit(PyTimeType(), self.event_repository, self.timeline,
+ self.editor.edit(PyTimeType(), self.event_repository, self.timeline,
start, end, event)
def simulate_user_enters_start_time(self, time):
@@ -153,6 +153,26 @@ class describe_event_editor__locked_checkbox(EventEditorTestCase):
self.when_editor_opened_with_event(event)
self.view.set_locked.assert_called_with(sentinel.LOCKED)
+ def test_new_event_starting_in_history(self):
+ self.when_editor_opened_with_time("1 Jan 3010")
+ self.assertFalse(self.editor.start_is_in_history())
+
+ def test_new_event_not_starting_in_history(self):
+ self.when_editor_opened_with_time("1 Jan 2010")
+ self.assertTrue(self.editor.start_is_in_history())
+
+ def test_event_not_starting_in_history(self):
+ event = Mock()
+ event.time_period.start_time = human_time_to_py("1 Jan 3010")
+ self.when_editor_opened_with_event(event)
+ self.assertFalse(self.editor.start_is_in_history())
+
+ def test_event_starting_in_history(self):
+ event = Mock()
+ event.time_period.start_time = human_time_to_py("1 Jan 2010")
+ self.when_editor_opened_with_event(event)
+ self.assertTrue(self.editor.start_is_in_history())
+
class describe_event_editor__ends_today_checkbox(EventEditorTestCase):
diff --git a/specs/EventObject.py b/specs/EventObject.py
index 1d70e75..812272d 100644
--- a/specs/EventObject.py
+++ b/specs/EventObject.py
@@ -26,30 +26,30 @@ class EventSpec(unittest.TestCase):
def testEventPropertyEndsTodayCanBeUpdated(self):
self.given_default_point_event()
- self.event.update(self.now, self.now, "evt", ends_today=True)
+ self.event.update(self.now, self.now, "evt", ends_today=True)
self.assertEqual(True, self.event.ends_today)
def testEventPropertyFuzzyCanBeUpdated(self):
self.given_default_point_event()
- self.event.update(self.now, self.now, "evt", fuzzy=True)
+ self.event.update(self.now, self.now, "evt", fuzzy=True)
self.assertEqual(True, self.event.fuzzy)
def testEventPropertyLockedCanBeUpdated(self):
self.given_default_point_event()
- self.event.update(self.now, self.now, "evt", locked=True)
+ self.event.update(self.now, self.now, "evt", locked=True)
self.assertEqual(True, self.event.locked)
def testEventPropertyEndsTodayCantBeSetOnLockedEvent(self):
self.given_default_point_event()
- self.event.update(self.now, self.now, "evt", locked=True)
- self.event.update(self.now, self.now, "evt", ends_today=True)
+ self.event.update(self.now, self.now, "evt", locked=True)
+ self.event.update(self.now, self.now, "evt", ends_today=True)
self.assertEqual(False, self.event.ends_today)
def testEventPropertyEndsTodayCantBeUnsetOnLockedEvent(self):
self.given_default_point_event()
- self.event.update(self.now, self.now, "evt", locked=True, ends_today=True)
+ self.event.update(self.now, self.now, "evt", locked=True, ends_today=True)
self.assertEqual(True, self.event.ends_today)
- self.event.update(self.now, self.now, "evt", ends_today=False)
+ self.event.update(self.now, self.now, "evt", ends_today=False)
self.assertEqual(True, self.event.ends_today)
def setUp(self):
@@ -63,7 +63,7 @@ class EventSpec(unittest.TestCase):
self.event = Event(self.db.get_time_type(), self.now, self.now, "evt")
def given_point_event(self):
- self.event = Event(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
+ self.event = Event(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
self.time("2000-01-01 10:01:01"), "evt")
@@ -76,7 +76,7 @@ class EventCosntructorSpec(unittest.TestCase):
self.assertEqual(False, self.event.ends_today)
self.assertEqual(False, self.event.is_container())
self.assertEqual(False, self.event.is_subevent())
-
+
def testEventPropertyFuzzyCanBeSetAtConstruction(self):
self.given_fuzzy_point_event()
self.assertEqual(True, self.event.fuzzy)
@@ -118,4 +118,3 @@ class EventFunctionsSpec(unittest.TestCase):
def setUp(self):
self.db = MemoryDB()
self.now = self.db.get_time_type().now()
-
diff --git a/specs/FileTimeline.py b/specs/FileTimeline.py
index 74dee1a..19eb02d 100644
--- a/specs/FileTimeline.py
+++ b/specs/FileTimeline.py
@@ -16,25 +16,21 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import tempfile
-import shutil
-import os.path
-import unittest
-import os
-import stat
import datetime
+import unittest
-from timelinelib.db.interface import TimelineIOError
-from timelinelib.db.objects import TimePeriod
+from specs.utils import TmpDirTestCase
+from timelinelib.db.backends.file import dequote
from timelinelib.db.backends.file import FileTimeline
from timelinelib.db.backends.file import quote
-from timelinelib.db.backends.file import dequote
from timelinelib.db.backends.file import split_on_semicolon
-from timelinelib.time import PyTimeType
+from timelinelib.db.exceptions import TimelineIOError
+from timelinelib.db.objects import TimePeriod
from timelinelib.drawing.viewproperties import ViewProperties
+from timelinelib.time import PyTimeType
-class FileTimelineSpec(unittest.TestCase):
+class FileTimelineSpec(TmpDirTestCase):
IO = True
@@ -89,13 +85,13 @@ class FileTimelineSpec(unittest.TestCase):
self.assertRaises(TimelineIOError, timeline.save_view_properties, vp)
def setUp(self):
+ TmpDirTestCase.setUp(self)
# Create temporary dir and names
- self.tmp_dir = tempfile.mkdtemp(prefix="timeline-test")
- self.corrupt_file = os.path.join(self.tmp_dir, "corrupt.timeline")
- self.missingeof_file = os.path.join(self.tmp_dir, "missingeof.timeline")
- self._021_file = os.path.join(self.tmp_dir, "021.timeline")
- self.invalid_time_period_file = os.path.join(self.tmp_dir, "invalid_time_period.timeline")
- self.valid_file = os.path.join(self.tmp_dir, "valid.timeline")
+ self.corrupt_file = self.get_tmp_path("corrupt.timeline")
+ self.missingeof_file = self.get_tmp_path("missingeof.timeline")
+ self._021_file = self.get_tmp_path("021.timeline")
+ self.invalid_time_period_file = self.get_tmp_path("invalid_time_period.timeline")
+ self.valid_file = self.get_tmp_path("valid.timeline")
# Write content to files
HEADER_030 = "# Written by Timeline 0.3.0 on 2009-7-23 9:40:33"
HEADER_030_DEV = "# Written by Timeline 0.3.0dev on 2009-7-23 9:40:33"
@@ -120,9 +116,6 @@ class FileTimelineSpec(unittest.TestCase):
]
self.write_timeline(self.valid_file, valid)
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
-
def write_timeline(self, path, lines):
f = file(path, "w")
f.write("\n".join(lines))
diff --git a/specs/MemoryDB.py b/specs/MemoryDB.py
index bed6a62..2d9cf29 100644
--- a/specs/MemoryDB.py
+++ b/specs/MemoryDB.py
@@ -21,13 +21,13 @@ import unittest
from mock import Mock
-from timelinelib.time import PyTimeType
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
from timelinelib.db.objects import Event
from timelinelib.db.objects import TimePeriod
-from timelinelib.db.backends.memory import MemoryDB
from timelinelib.drawing.viewproperties import ViewProperties
+from timelinelib.time import PyTimeType
class MemoryDBSpec(unittest.TestCase):
@@ -84,7 +84,7 @@ class MemoryDBSpec(unittest.TestCase):
self.assertRaises(TimelineIOError, self.db.save_view_properties, vp)
def testGetSetDisplayedPeriod(self):
- tp = TimePeriod(self.db.get_time_type(), datetime(2010, 3, 23),
+ tp = TimePeriod(self.db.get_time_type(), datetime(2010, 3, 23),
datetime(2010, 3, 24))
self.db._set_displayed_period(tp)
# Assert that we get back the same period
@@ -268,7 +268,7 @@ class MemoryDBSpec(unittest.TestCase):
def testSaveNewEvent(self):
self.db.save_event(self.e1)
- tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
+ tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
datetime(2010, 2, 14))
self.assertTrue(self.e1.has_id())
self.assertEqual(self.db.get_events(tp), [self.e1])
@@ -282,7 +282,7 @@ class MemoryDBSpec(unittest.TestCase):
id_before = self.e1.id
self.e1.text = "new text"
self.db.save_event(self.e1)
- tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
+ tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
datetime(2010, 2, 14))
self.assertEqual(id_before, self.e1.id)
self.assertEqual(self.db.get_events(tp), [self.e1])
@@ -300,7 +300,7 @@ class MemoryDBSpec(unittest.TestCase):
self.assertEquals(self.db._save.call_count, 0)
def testDeleteExistingEvent(self):
- tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
+ tp = TimePeriod(self.db.get_time_type(), datetime(2010, 2, 12),
datetime(2010, 2, 15))
self.db.save_event(self.e1)
self.db.save_event(self.e2)
@@ -406,10 +406,10 @@ class MemoryDBSpec(unittest.TestCase):
self.db_listener = Mock()
self.c1 = Category("work", (255, 0, 0), None, True)
self.c2 = Category("private", (0, 255, 0), None, True)
- self.e1 = Event(self.db.get_time_type(), datetime(2010, 2, 13), datetime(2010, 2, 13),
+ self.e1 = Event(self.db.get_time_type(), datetime(2010, 2, 13), datetime(2010, 2, 13),
"holiday")
- self.e2 = Event(self.db.get_time_type(), datetime(2010, 2, 14), datetime(2010, 2, 14),
+ self.e2 = Event(self.db.get_time_type(), datetime(2010, 2, 14), datetime(2010, 2, 14),
"work starts")
- self.e3 = Event(self.db.get_time_type(), datetime(2010, 2, 15), datetime(2010, 2, 16),
+ self.e3 = Event(self.db.get_time_type(), datetime(2010, 2, 15), datetime(2010, 2, 16),
"period")
self.db.register(self.db_listener)
diff --git a/specs/NumTimePicker.py b/specs/NumTimePicker.py
index f90c176..f5a2226 100644
--- a/specs/NumTimePicker.py
+++ b/specs/NumTimePicker.py
@@ -36,5 +36,5 @@ class ANumTimePicker(unittest.TestCase):
def testTimeControlIsAssignedZeroIfSetWithValueNone(self):
self.controller.set_value(None)
- self.time_picker.set_value.assert_called_with(0)
+ self.time_picker.set_value.assert_called_with(0)
diff --git a/specs/NumTimeType.py b/specs/NumTimeType.py
index 63de73d..41b4561 100644
--- a/specs/NumTimeType.py
+++ b/specs/NumTimeType.py
@@ -32,17 +32,17 @@ class NumTimeTypeSpec(unittest.TestCase):
def test_returns_half_delta(self):
delta = 126
half_delta = self.time_type.half_delta(delta)
- self.assertEquals(63, half_delta)
+ self.assertEquals(63, half_delta)
def test_returns_margin_delta(self):
delta = 24 * 12345
margin_delta = self.time_type.margin_delta(delta)
- self.assertEquals(12345, margin_delta)
+ self.assertEquals(12345, margin_delta)
def test_format_delta_1(self):
delta = 1
- self.assertEquals("1", self.time_type.format_delta(delta))
+ self.assertEquals("1", self.time_type.format_delta(delta))
def test_format_delta_2(self):
delta = 2
- self.assertEquals("2", self.time_type.format_delta(delta))
+ self.assertEquals("2", self.time_type.format_delta(delta))
diff --git a/specs/PlayController.py b/specs/PlayController.py
index 7e49002..5f02694 100644
--- a/specs/PlayController.py
+++ b/specs/PlayController.py
@@ -21,11 +21,9 @@ import unittest
from mock import Mock
-from specs.utils import an_event
-from timelinelib.wxgui.dialogs.playframe import PlayFrame
-from timelinelib.db.interface import TimelineDB
+from timelinelib.db.backends.memory import MemoryDB
from timelinelib.play.playcontroller import PlayController
-from timelinelib.time.pytime import PyTimeType
+from timelinelib.wxgui.dialogs.playframe import PlayFrame
class PlayControllerSpec(unittest.TestCase):
@@ -33,7 +31,7 @@ class PlayControllerSpec(unittest.TestCase):
def setUp(self):
self.play_frame = Mock(PlayFrame)
self.play_frame.get_view_period_length.return_value = datetime.timedelta(1)
- self.timeline = Mock(TimelineDB)
+ self.timeline = Mock(MemoryDB)
self.drawing_algorithm = Mock()
self.config = Mock()
self.controller = PlayController(self.play_frame, self.timeline,
diff --git a/specs/PyDateTimePicker.py b/specs/PyDateTimePicker.py
index 8f95cd0..83b7758 100644
--- a/specs/PyDateTimePicker.py
+++ b/specs/PyDateTimePicker.py
@@ -93,7 +93,7 @@ class PyDatePickerBaseFixture(unittest.TestCase):
self.controller.on_text_changed()
def simulate_change_insertion_point(self, new_insertion_point):
- self.py_date_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
+ self.py_date_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
self.py_date_picker.GetInsertionPoint.return_value = new_insertion_point
def _update_insertion_point_and_selection(self, from_pos, to_pos):
@@ -359,12 +359,12 @@ class PyTimePickerBaseFixture(unittest.TestCase):
self.controller.on_text_changed()
def simulate_change_insertion_point(self, new_insertion_point):
- self.py_time_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
+ self.py_time_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
self.py_time_picker.GetInsertionPoint.return_value = new_insertion_point
def _update_insertion_point_and_selection(self, from_pos, to_pos):
self.py_time_picker.GetInsertionPoint.return_value = from_pos
- self.py_time_picker.GetSelection.return_value = (from_pos, to_pos)
+ self.py_time_picker.GetSelection.return_value = (from_pos, to_pos)
class APyTimePicker(PyTimePickerBaseFixture):
diff --git a/specs/PyTimeNavigationFunctions.py b/specs/PyTimeNavigationFunctions.py
index 4b75a52..7c14b7b 100644
--- a/specs/PyTimeNavigationFunctions.py
+++ b/specs/PyTimeNavigationFunctions.py
@@ -37,6 +37,14 @@ class PyTimeNavigationFunctionsSpec(unittest.TestCase):
self.when_navigating(fit_day_fn, "1 Jan 2010", "4 Jan 2010")
self.then_period_becomes("2 Jan 2010", "3 Jan 2010")
+ def test_fit_first_day_should_display_the_day_that_is_in_the_center(self):
+ self.when_navigating(fit_day_fn, "1 Jan 10", "2 Jan 10")
+ self.then_period_becomes("1 Jan 10", "2 Jan 10")
+
+ def test_fit_last_day_should_display_the_day_that_is_in_the_center(self):
+ self.when_navigating(fit_day_fn, "31 Dec 9989", "1 Jan 9990")
+ self.then_period_becomes("31 Dec 9989", "1 Jan 9990")
+
def test_fit_month_should_display_the_month_that_is_in_the_center(self):
self.when_navigating(fit_month_fn, "1 Jan 2010", "2 Jan 2010")
self.then_period_becomes("1 Jan 2010", "1 Feb 2010")
@@ -45,22 +53,62 @@ class PyTimeNavigationFunctionsSpec(unittest.TestCase):
self.when_navigating(fit_month_fn, "1 Dec 2010", "2 Dec 2010")
self.then_period_becomes("1 Dec 2010", "1 Jan 2011")
+ def test_fit_first_month_december_should_display_the_month_that_is_in_the_center(self):
+ self.when_navigating(fit_month_fn, "1 Jan 10", "2 Jan 10")
+ self.then_period_becomes("1 Jan 10", "1 Feb 10")
+
+ def test_fit_last_month_december_should_display_the_month_that_is_in_the_center(self):
+ self.when_navigating(fit_month_fn, "1 Dec 9989", "1 Jan 9990")
+ self.then_period_becomes("1 Dec 9989", "1 Jan 9990")
+
def test_fit_year_should_display_the_year_that_is_in_the_center(self):
self.when_navigating(fit_year_fn, "1 Jan 2010", "2 Jan 2010")
self.then_period_becomes("1 Jan 2010", "1 Jan 2011")
+ def test_fit_first_year_should_display_the_year_that_is_in_the_center(self):
+ self.when_navigating(fit_year_fn, "1 Jan 10", "2 Jan 10")
+ self.then_period_becomes("1 Jan 10", "1 Jan 11")
+
+ def test_fit_last_year_should_display_the_year_that_is_in_the_center(self):
+ self.when_navigating(fit_year_fn, "1 Jan 9989", "1 Jan 9990")
+ self.then_period_becomes("1 Jan 9989", "1 Jan 9990")
+
def test_fit_decade_should_display_the_decade_that_is_in_the_center(self):
self.when_navigating(fit_decade_fn, "1 Jan 2010", "2 Jan 2010")
self.then_period_becomes("1 Jan 2010", "1 Jan 2020")
+ def test_fit_first_decade_should_display_the_decade_that_is_in_the_center(self):
+ self.when_navigating(fit_decade_fn, "1 Jan 10", "2 Jan 10")
+ self.then_period_becomes("1 Jan 10", "1 Jan 20")
+
+ def test_fit_last_decade_should_display_the_decade_that_is_in_the_center(self):
+ self.when_navigating(fit_decade_fn, "1 Jan 9989", "1 Jan 9990")
+ self.then_period_becomes("1 Jan 9980", "1 Jan 9990")
+
def test_fit_century_should_display_the_century_that_is_in_the_center(self):
self.when_navigating(fit_century_fn, "1 Jan 2010", "2 Jan 2010")
self.then_period_becomes("1 Jan 2000", "1 Jan 2100")
+ def test_fit_first_century_should_display_the_century_that_is_in_the_center(self):
+ self.when_navigating(fit_century_fn, "1 Jan 10", "1 Jan 11")
+ self.then_period_becomes("1 Jan 10", "1 Jan 110")
+
+ def test_fit_last_century_should_display_the_century_that_is_in_the_center(self):
+ self.when_navigating(fit_century_fn, "1 Jan 9989", "1 Jan 9990")
+ self.then_period_becomes("1 Jan 9890", "1 Jan 9990")
+
def test_fit_millennium_should_display_the_millennium_that_is_in_the_center(self):
self.when_navigating(fit_millennium_fn, "1 Jan 2010", "2 Jan 2010")
self.then_period_becomes("1 Jan 2000", "1 Jan 3000")
+ def test_fit_first_millennium_should_display_the_millennium_that_is_in_the_center(self):
+ self.when_navigating(fit_millennium_fn, "1 Jan 10", "2 Jan 10")
+ self.then_period_becomes("1 Jan 10", "1 Jan 1010")
+
+ def test_fit_last_millennium_should_display_the_millennium_that_is_in_the_center(self):
+ self.when_navigating(fit_millennium_fn, "1 Jan 9989", "1 Jan 9990")
+ self.then_period_becomes("1 Jan 8990", "1 Jan 9990")
+
def test_move_page_smart_not_smart_forward(self):
self.when_navigating(forward_fn, "1 Jan 2010", "5 Jan 2010")
self.then_period_becomes("5 Jan 2010", "9 Jan 2010")
diff --git a/specs/PyTimeType.py b/specs/PyTimeType.py
index 3090371..315a81d 100644
--- a/specs/PyTimeType.py
+++ b/specs/PyTimeType.py
@@ -49,7 +49,7 @@ class PyTimeTypeSpec(unittest.TestCase):
self.time_type.parse_time, "2010-31-hello 0:0:0")
def test_formats_period_to_string(self):
- time_period = TimePeriod(self.time_type,
+ time_period = TimePeriod(self.time_type,
datetime.datetime(2010, 8, 01, 13, 44),
datetime.datetime(2010, 8, 02, 13, 30))
self.assertEquals(
@@ -57,22 +57,22 @@ class PyTimeTypeSpec(unittest.TestCase):
self.time_type.format_period(time_period))
def test_returns_min_time(self):
- self.assertEquals(datetime.datetime(10, 1, 1),
- self.time_type.get_min_time()[0])
+ self.assertEquals(datetime.datetime(10, 1, 1),
+ self.time_type.get_min_time()[0])
def test_returns_max_time(self):
- self.assertEquals(datetime.datetime(9990, 1, 1),
+ self.assertEquals(datetime.datetime(9990, 1, 1),
self.time_type.get_max_time()[0])
def test_returns_half_delta(self):
delta = datetime.timedelta(days=4)
half_delta = self.time_type.half_delta(delta)
- self.assertEquals(datetime.timedelta(days=2), half_delta)
+ self.assertEquals(datetime.timedelta(days=2), half_delta)
def test_returns_margin_delta(self):
delta = datetime.timedelta(days=48)
margin_delta = self.time_type.margin_delta(delta)
- self.assertEquals(datetime.timedelta(days=2), margin_delta)
+ self.assertEquals(datetime.timedelta(days=2), margin_delta)
def test_event_date_string_method(self):
self.assertEquals(
@@ -100,10 +100,10 @@ class PyTimeTypeDeltaFormattingSpec(unittest.TestCase):
self.time_type = PyTimeType()
def test_format_delta_method(self):
- time_period1 = TimePeriod(self.time_type,
+ time_period1 = TimePeriod(self.time_type,
datetime.datetime(2010, 8, 01, 13, 44),
datetime.datetime(2010, 8, 01, 13, 44))
- time_period2 = TimePeriod(self.time_type,
+ time_period2 = TimePeriod(self.time_type,
datetime.datetime(2010, 8, 02, 13, 44),
datetime.datetime(2010, 8, 02, 13, 44))
delta = time_period2.start_time - time_period1.start_time
@@ -172,10 +172,10 @@ class PyTimeTypeDeltaFormattingSpec(unittest.TestCase):
self.assertEquals(u"790 %s" % _("days"), self.time_type.format_delta(delta))
def test_format_overlapping_events(self):
- time_period1 = TimePeriod(self.time_type,
+ time_period1 = TimePeriod(self.time_type,
datetime.datetime(2010, 8, 01, 13, 44),
datetime.datetime(2010, 8, 03, 13, 44))
- time_period2 = TimePeriod(self.time_type,
+ time_period2 = TimePeriod(self.time_type,
datetime.datetime(2010, 8, 01, 13, 44),
datetime.datetime(2010, 8, 03, 13, 44))
delta = time_period2.start_time - time_period1.end_time
diff --git a/specs/Scene.py b/specs/Scene.py
index fb84b48..9aa16ed 100644
--- a/specs/Scene.py
+++ b/specs/Scene.py
@@ -56,7 +56,7 @@ class SceneSpec(unittest.TestCase):
self.given_visible_event_at("5 Jan 2010")
self.given_visible_event_at("5 Jan 2010")
self.when_scene_is_created()
- self.assertTrue(self.scene.event_data[0][1].Y >
+ self.assertTrue(self.scene.event_data[0][1].Y >
self.scene.event_data[1][1].Y)
def test_point_events_on_different_dates_has_same_y_positions(self):
@@ -64,7 +64,7 @@ class SceneSpec(unittest.TestCase):
self.given_visible_event_at("2 Jan 2010")
self.given_visible_event_at("9 Jan 2010")
self.when_scene_is_created()
- self.assertEqual(self.scene.event_data[0][1].Y,
+ self.assertEqual(self.scene.event_data[0][1].Y,
self.scene.event_data[1][1].Y)
def test_period_events_with_same_period_has_different_y_positions(self):
@@ -72,7 +72,7 @@ class SceneSpec(unittest.TestCase):
self.given_visible_event_at("2 Jan 2010", "10 Jan 2010")
self.given_visible_event_at("2 Jan 2010", "10 Jan 2010")
self.when_scene_is_created()
- self.assertTrue(self.scene.event_data[0][1].Y <
+ self.assertTrue(self.scene.event_data[0][1].Y <
self.scene.event_data[1][1].Y)
def test_period_events_with_different_periods_has_same_y_positions(self):
@@ -80,9 +80,17 @@ class SceneSpec(unittest.TestCase):
self.given_visible_event_at("2 Jan 2010", "3 Jan 2010")
self.given_visible_event_at("8 Jan 2010", "10 Jan 2010")
self.when_scene_is_created()
- self.assertEqual(self.scene.event_data[0][1].Y,
+ self.assertEqual(self.scene.event_data[0][1].Y,
self.scene.event_data[1][1].Y)
+ def test_scene_must_be_created_at_last_century(self):
+ self.given_displayed_period("1 Jan 9890", "1 Jan 9990")
+ try:
+ self.when_scene_is_created()
+ self.assertTrue(self.scene != None)
+ except:
+ self.assertTrue(False)
+
def setUp(self):
self.db = MemoryDB()
self.view_properties = ViewProperties()
@@ -112,7 +120,7 @@ class SceneSpec(unittest.TestCase):
category = Category("category", (0, 0, 0), None, visible)
if end_time is None:
end_time = start_time
- event = Event(self.db.get_time_type(), human_time_to_py(start_time),
+ event = Event(self.db.get_time_type(), human_time_to_py(start_time),
human_time_to_py(end_time), "event-text", category)
self.db.save_category(category)
self.db.save_event(event)
diff --git a/specs/SubeventObject.py b/specs/SubeventObject.py
index 9b87de3..9ba999f 100644
--- a/specs/SubeventObject.py
+++ b/specs/SubeventObject.py
@@ -18,9 +18,9 @@
import unittest
-from timelinelib.db.subevent import Subevent
-from timelinelib.db.container import Container
from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.objects import Container
+from timelinelib.db.objects import Subevent
class SubeventSpec(unittest.TestCase):
@@ -33,7 +33,7 @@ class SubeventSpec(unittest.TestCase):
self.assertEqual(self.container, self.subevent.container)
def given_default_subevent(self):
- self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
+ self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
self.time("2000-01-03 10:01:01"), "evt")
def given_container_with_cid(self):
@@ -42,7 +42,7 @@ class SubeventSpec(unittest.TestCase):
def time(self, tm):
return self.db.get_time_type().parse_time(tm)
-
+
def setUp(self):
self.db = MemoryDB()
self.now = self.db.get_time_type().now()
@@ -64,11 +64,11 @@ class ContainerSubeventSpec(unittest.TestCase):
self.assertEqual(99, self.subevent.cid())
def given_default_subevent(self):
- self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
+ self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
self.time("2000-01-03 10:01:01"), "evt")
def given_subevent_with_cid(self):
- self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
+ self.subevent = Subevent(self.db.get_time_type(), self.time("2000-01-01 10:01:01"),
self.time("2000-01-03 10:01:01"), "evt", cid=99)
def time(self, tm):
diff --git a/specs/TextDisplayEditor.py b/specs/TextDisplayEditor.py
new file mode 100644
index 0000000..b3fec55
--- /dev/null
+++ b/specs/TextDisplayEditor.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+import unittest
+from mock import Mock
+
+from timelinelib.wxgui.dialogs.textdisplay import TextDisplayDialog
+from timelinelib.editors.textdisplay import TextDisplayEditor
+
+class TextDisplayEditorSpec(unittest.TestCase):
+
+ def test_set_text_sets_dialog_text(self):
+ text = "aha"
+ self.editor.set_text(text)
+ self.view.set_text.assert_called_with(text)
+
+ def test_initialization_sets_dialog_text(self):
+ self.editor.initialize()
+ self.view.set_text.assert_called_with(self.text)
+
+ def test_get_text_returns_dialog_text(self):
+ self.assertTrue(WhenDialogTextIs("foo2", self.view, self.editor).controller_returns("foo2"))
+ self.assertTrue(WhenDialogTextIs("foo3", self.view, self.editor).controller_returns("foo3"))
+
+ def setUp(self):
+ self.text = "buu"
+ self.view = Mock(TextDisplayDialog)
+ self.editor = TextDisplayEditor(self.view, self.text)
+
+
+class WhenDialogTextIs(object):
+
+ def __init__(self, text, view, editor):
+ self.text = text
+ view.get_text.return_value = text
+ self.editor = editor
+ self.editor.initialize()
+
+ def controller_returns(self, text):
+ text = self.editor.get_text()
+ return self.text == text
+
diff --git a/specs/TimePeriod.py b/specs/TimePeriod.py
index cc2fded..b2fae17 100644
--- a/specs/TimePeriod.py
+++ b/specs/TimePeriod.py
@@ -34,7 +34,7 @@ class ATime(object):
def __eq__(self, other):
return isinstance(other, ATime) and self.num == other.num
- def __ne__(self, ohter):
+ def __ne__(self, other):
return not (self == other)
def __add__(self, other):
@@ -72,7 +72,7 @@ class ADelta(object):
return isinstance(other, ADelta) and self.num == other.num
# Exists only only to simplify testing
- def __ne__(self, ohter):
+ def __ne__(self, other):
return not (self == other)
# Exists only only to simplify testing
diff --git a/specs/TimelineApplication.py b/specs/TimelineApplication.py
index d24093a..697f925 100644
--- a/specs/TimelineApplication.py
+++ b/specs/TimelineApplication.py
@@ -22,7 +22,7 @@ from mock import Mock
from timelinelib.application import TimelineApplication
from timelinelib.config.dotfile import Config
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.wxgui.dialogs.mainframe import MainFrame
@@ -65,7 +65,7 @@ class MainFrameSpec(unittest.TestCase):
self.main_frame = Mock(MainFrame)
self.db_open = Mock()
self.config = Mock(Config)
- self.config.get_use_wide_date_range.return_value = self.USE_WIDE_DATE_RANGE
+ self.config.get_use_wide_date_range.return_value = self.USE_WIDE_DATE_RANGE
self.controller = TimelineApplication(
self.main_frame, self.db_open, self.config)
diff --git a/specs/TimelineView.py b/specs/TimelineView.py
index 49cf948..bf2de11 100644
--- a/specs/TimelineView.py
+++ b/specs/TimelineView.py
@@ -138,7 +138,8 @@ class TimelineViewSpec(unittest.TestCase):
def test_zooms_timeline_by_10_percent_on_each_side_when_scrolling_while_holding_down_ctrl(self):
self.init_view_with_db_with_period("1 Aug 2010", "21 Aug 2010")
- self.controller.mouse_wheel_moved(1, ctrl_down=True, shift_down=False)
+ self.controller.mouse_wheel_moved(
+ 1, ctrl_down=True, shift_down=False, x=self.middle_x)
self.assert_displays_period("3 Aug 2010", "19 Aug 2010")
def test_displays_balloon_for_event_with_description(self):
@@ -207,7 +208,7 @@ class TimelineViewSpec(unittest.TestCase):
event = self.given_event_with(pos=(40, 60), size=(20, 10))
self.init_view_with_db()
self.simulate_mouse_double_click(50, 65)
- self.view.edit_event.assert_called_with(event)
+ self.view.open_event_editor_for.assert_called_with(event)
self.assert_timeline_redrawn()
def test_selects_and_deselects_event_when_clicking_on_it(self):
@@ -318,10 +319,12 @@ class TimelineViewSpec(unittest.TestCase):
def test_scrolls_with_10_percent_when_using_mouse_wheel(self):
self.init_view_with_db_with_period("1 Aug 2010", "21 Aug 2010")
- self.controller.mouse_wheel_moved(-1, ctrl_down=False, shift_down=False)
+ self.controller.mouse_wheel_moved(
+ -1, ctrl_down=False, shift_down=False, x=self.middle_x)
self.assert_displays_period("3 Aug 2010", "23 Aug 2010")
self.assert_timeline_redrawn()
- self.controller.mouse_wheel_moved(1, ctrl_down=False, shift_down=False)
+ self.controller.mouse_wheel_moved(
+ 1, ctrl_down=False, shift_down=False, x=self.middle_x)
self.assert_displays_period("1 Aug 2010", "21 Aug 2010")
self.assert_timeline_redrawn()
@@ -346,7 +349,8 @@ class TimelineViewSpec(unittest.TestCase):
def test_shift_scroll_changes_divider_line_value_and_redraws(self):
self.init_view_with_db()
- self.controller.mouse_wheel_moved(1, ctrl_down=False, shift_down=True)
+ self.controller.mouse_wheel_moved(
+ 1, ctrl_down=False, shift_down=True, x=self.middle_x)
self.assertTrue(self.divider_line_slider.SetValue.called)
self.assert_timeline_redrawn()
@@ -357,7 +361,9 @@ class TimelineViewSpec(unittest.TestCase):
def setUp(self):
self.db = MemoryDB()
self.view = Mock(DrawingAreaPanel)
- self.view.GetSizeTuple.return_value = (10, 10)
+ self.width = 10
+ self.middle_x = self.width / 2
+ self.view.GetSizeTuple.return_value = (self.width, 10)
self.status_bar_adapter = Mock(StatusBarAdapter)
self.config = Mock(Config)
self.mock_drawer = MockDrawer()
@@ -462,7 +468,7 @@ class TimelineViewSpec(unittest.TestCase):
self.assertTrue(self.view.redraw_surface.called)
def assert_created_event_with_period(self, start, end):
- self.view.create_new_event.assert_called_with(
+ self.view.open_create_event_editor.assert_called_with(
human_time_to_py(start), human_time_to_py(end))
def assert_is_selected(self, event):
diff --git a/specs/WxDateTimePicker.py b/specs/WxDateTimePicker.py
index 3080f9c..0b66b85 100644
--- a/specs/WxDateTimePicker.py
+++ b/specs/WxDateTimePicker.py
@@ -93,7 +93,7 @@ class WxDatePickerBaseFixture(unittest.TestCase):
self.controller.on_text_changed()
def simulate_change_insertion_point(self, new_insertion_point):
- self.py_date_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
+ self.py_date_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
self.py_date_picker.GetInsertionPoint.return_value = new_insertion_point
def _update_insertion_point_and_selection(self, from_pos, to_pos):
@@ -363,12 +363,12 @@ class WxTimePickerBaseFixture(unittest.TestCase):
self.controller.on_text_changed()
def simulate_change_insertion_point(self, new_insertion_point):
- self.py_time_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
+ self.py_time_picker.GetSelection.return_value = (new_insertion_point, new_insertion_point)
self.py_time_picker.GetInsertionPoint.return_value = new_insertion_point
def _update_insertion_point_and_selection(self, from_pos, to_pos):
self.py_time_picker.GetInsertionPoint.return_value = from_pos
- self.py_time_picker.GetSelection.return_value = (from_pos, to_pos)
+ self.py_time_picker.GetSelection.return_value = (from_pos, to_pos)
class AWxTimePicker(WxTimePickerBaseFixture):
diff --git a/specs/WxTimeType.py b/specs/WxTimeType.py
index 8abf309..50cad31 100644
--- a/specs/WxTimeType.py
+++ b/specs/WxTimeType.py
@@ -75,7 +75,7 @@ class WxTimeTypeSpec(unittest.TestCase):
self.time_type.parse_time, "2010-31-hello 0:0:0")
def test_format_period_method(self):
- time_period = TimePeriod(self.time_type,
+ time_period = TimePeriod(self.time_type,
wx.DateTimeFromDMY(1, 7, 2010, 13, 44),
wx.DateTimeFromDMY(2, 7, 2010, 13, 30))
self.assertEquals(
@@ -95,13 +95,13 @@ class WxTimeTypeSpec(unittest.TestCase):
def test_returns_margin_delta(self):
delta = wx.TimeSpan.Days(days=48)
margin_delta = self.time_type.margin_delta(delta)
- self.assertEquals(wx.TimeSpan.Days(2), margin_delta)
+ self.assertEquals(wx.TimeSpan.Days(2), margin_delta)
def test_returns_half_delta(self):
delta = wx.TimeSpan.Days(100 * 365)
half_delta = self.time_type.half_delta(delta)
- self.assertEquals(wx.TimeSpan.Days(50 * 365).GetMilliseconds(),
- half_delta.GetMilliseconds())
+ self.assertEquals(wx.TimeSpan.Days(50 * 365).GetMilliseconds(),
+ half_delta.GetMilliseconds())
class WxDateTimeConstructorSpec(unittest.TestCase):
@@ -192,10 +192,10 @@ class WxTimeTypeDeltaFormattingSpec(unittest.TestCase):
self.assertEquals(u"790 %s" % _("days"), self.time_type.format_delta(delta))
def test_format_overlapping_events(self):
- time_period1 = TimePeriod(self.time_type,
+ time_period1 = TimePeriod(self.time_type,
wx.DateTimeFromDMY(1, 7, 2010, 13, 44),
wx.DateTimeFromDMY(2, 7, 2010, 13, 30))
- time_period2 = TimePeriod(self.time_type,
+ time_period2 = TimePeriod(self.time_type,
wx.DateTimeFromDMY(1, 7, 2010, 13, 44),
wx.DateTimeFromDMY(2, 7, 2010, 13, 30))
delta = time_period2.start_time - time_period1.end_time
diff --git a/specs/XmlParser.py b/specs/XmlParser.py
index ddb9582..7de4d55 100644
--- a/specs/XmlParser.py
+++ b/specs/XmlParser.py
@@ -16,11 +16,11 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import unittest
from StringIO import StringIO
+import unittest
import xml.sax
-import timelinelib.db.backends.xmlparser as xmlparser
+import timelinelib.xml.parser as xmlparser
class TestXmlParser(unittest.TestCase):
diff --git a/specs/XmlTimeline.py b/specs/XmlTimeline.py
index 9e14d47..ce4ea2b 100644
--- a/specs/XmlTimeline.py
+++ b/specs/XmlTimeline.py
@@ -17,14 +17,12 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import codecs
-import tempfile
-import os.path
-import shutil
from datetime import datetime
-import unittest
+import codecs
+
import wx
+from specs.utils import TmpDirTestCase
from timelinelib.db.backends.xmlfile import XmlTimeline
from timelinelib.db import db_open
from timelinelib.db.objects import Category
@@ -35,32 +33,32 @@ from timelinelib.meta.version import get_version
from timelinelib.time import WxTimeType
-class XmlTimelineSpec(unittest.TestCase):
+class XmlTimelineSpec(TmpDirTestCase):
IO = True
def testUseWxTimeTypeWhenUseWideDateRangeIsTrue(self):
timeline = XmlTimeline(None, load=False, use_wide_date_range=True)
- self.assertTrue(isinstance(timeline.time_type, WxTimeType))
+ self.assertTrue(isinstance(timeline.get_time_type(), WxTimeType))
def testAlertStringParsingGivesAlertData(self):
timeline = XmlTimeline(None, load=False, use_wide_date_range=True)
time, text = timeline._parse_alert_string("2012-11-11 00:00:00;Now is the time")
self.assertEqual("Now is the time", text)
- self.assertEqual("2012-11-11 00:00:00", "%s" % timeline.time_type.time_string(time))
+ self.assertEqual("2012-11-11 00:00:00", "%s" % timeline.get_time_type().time_string(time))
def testAlertDataConversionGivesAlertString(self):
timeline = XmlTimeline(None, load=False, use_wide_date_range=False)
alert = (datetime(2010, 8, 31, 0, 0, 0), "Hoho")
alert_text = timeline.alert_string(alert)
self.assertEqual("2010-8-31 0:0:0;Hoho", alert_text)
-
+
def testWxTimeAlertDataConversionGivesAlertString(self):
timeline = XmlTimeline(None, load=False, use_wide_date_range=True)
alert = (wx.DateTimeFromDMY(30, 8, 2010, 0, 0, 0), "Hoho")
alert_text = timeline.alert_string(alert)
self.assertEqual("2010-09-30 00:00:00;Hoho", alert_text)
-
+
def testDisplayedPeriodTagNotWrittenIfNotSet(self):
# Create a new db and add one event
db = db_open(self.tmp_path)
@@ -162,8 +160,5 @@ class XmlTimelineSpec(unittest.TestCase):
self.fail("Unknown category.")
def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(prefix="timeline-test")
- self.tmp_path = os.path.join(self.tmp_dir, "test.timeline")
-
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
+ TmpDirTestCase.setUp(self)
+ self.tmp_path = self.get_tmp_path("test.timeline")
diff --git a/specs/utils.py b/specs/utils.py
index 5ddfdb0..cc5d8c5 100644
--- a/specs/utils.py
+++ b/specs/utils.py
@@ -24,17 +24,16 @@ import tempfile
import traceback
import unittest
-from mock import Mock
import wx
import wx.lib.inspection
+from timelinelib.calendar.monthnames import ABBREVIATED_ENGLISH_MONTH_NAMES
from timelinelib.config.arguments import ApplicationArguments
from timelinelib.config.dotfile import read_config
from timelinelib.db import db_open
from timelinelib.db.objects import Category
from timelinelib.db.objects import Event
from timelinelib.db.objects import TimePeriod
-from timelinelib.calendar.monthnames import ABBREVIATED_ENGLISH_MONTH_NAMES
from timelinelib.time.pytime import PyTimeType
from timelinelib.time.wxtime import WxTimeType
from timelinelib.wxgui.setup import start_wx_application
@@ -101,18 +100,30 @@ def an_event_with(start=None, end=None, time=ANY_TIME, text="foo", fuzzy=False,
fuzzy=fuzzy, locked=locked, ends_today=ends_today)
-class WxEndToEndTestCase(unittest.TestCase):
+class TmpDirTestCase(unittest.TestCase):
def setUp(self):
self.tmp_dir = tempfile.mkdtemp(prefix="timeline-test")
- self.timeline_path = os.path.join(self.tmp_dir, "test.timeline")
- self.config_file_path = os.path.join(self.tmp_dir, "thetimelineproj.cfg")
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp_dir)
+
+ def get_tmp_path(self, name):
+ return os.path.join(self.tmp_dir, name)
+
+
+class WxEndToEndTestCase(TmpDirTestCase):
+
+ def setUp(self):
+ TmpDirTestCase.setUp(self)
+ self.timeline_path = self.get_tmp_path("test.timeline")
+ self.config_file_path = self.get_tmp_path("thetimelineproj.cfg")
self.config = read_config(self.config_file_path)
self.standard_excepthook = sys.excepthook
self.error_in_gui_thread = None
def tearDown(self):
- shutil.rmtree(self.tmp_dir)
+ TmpDirTestCase.tearDown(self)
sys.excepthook = self.standard_excepthook
def start_timeline_and(self, steps_to_perform_in_gui):
diff --git a/timelinelib/application.py b/timelinelib/application.py
index afa6c91..024698e 100644
--- a/timelinelib/application.py
+++ b/timelinelib/application.py
@@ -16,7 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
class TimelineApplication(object):
@@ -46,7 +46,7 @@ class TimelineApplication(object):
self.config.append_recently_opened(path)
self.main_frame._update_open_recent_submenu()
self.main_frame._display_timeline(self.timeline)
-
+
def set_no_timeline(self):
self.timeline = None
self.main_frame._display_timeline(None)
diff --git a/timelinelib/db/__init__.py b/timelinelib/db/__init__.py
index 79b8abe..e4dddef 100644
--- a/timelinelib/db/__init__.py
+++ b/timelinelib/db/__init__.py
@@ -16,20 +16,14 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-"""
-Functionality for reading and writing timeline data from and to persistent
-storage.
-"""
-
-
import os.path
-from timelinelib.db.tutorial import create_in_memory_tutorial_db
+from timelinelib.db.backends.memory import MemoryDB
+from timelinelib.db.backends.tutorial import create_in_memory_tutorial_db
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
from timelinelib.db.objects import Event
from timelinelib.db.objects import TimePeriod
-from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.interface import TimelineIOError
from timelinelib.drawing.viewproperties import ViewProperties
@@ -59,7 +53,7 @@ def db_open(path, use_wide_date_range=False):
from timelinelib.db.backends.file import FileTimeline
from timelinelib.db.backends.xmlfile import XmlTimeline
file_db = FileTimeline(path)
- xml_db = XmlTimeline(path, load=False,
+ xml_db = XmlTimeline(path, load=False,
use_wide_date_range=use_wide_date_range)
copy_db(file_db, xml_db)
return xml_db
diff --git a/timelinelib/db/backends/__init__.py b/timelinelib/db/backends/__init__.py
index 3093a3d..e69de29 100644
--- a/timelinelib/db/backends/__init__.py
+++ b/timelinelib/db/backends/__init__.py
@@ -1,21 +0,0 @@
-# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
-#
-# This file is part of Timeline.
-#
-# Timeline is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
-
-
-"""
-Implementations of different timeline databases.
-"""
diff --git a/timelinelib/db/backends/dir.py b/timelinelib/db/backends/dir.py
index ec679a6..708fca3 100644
--- a/timelinelib/db/backends/dir.py
+++ b/timelinelib/db/backends/dir.py
@@ -31,10 +31,10 @@ import time
import wx
-from timelinelib.db.interface import TimelineIOError
from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.objects import Event
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
+from timelinelib.db.objects import Event
class DirTimeline(MemoryDB):
@@ -58,10 +58,10 @@ class DirTimeline(MemoryDB):
For each sub-directory a category is created and all events (files)
belong the category (directory) in which they are.
"""
- if not os.path.exists(dir_path):
+ if not os.path.exists(dir_path):
# Nothing to load
return
- if not os.path.isdir(dir_path):
+ if not os.path.isdir(dir_path):
# Nothing to load
return
try:
@@ -110,8 +110,8 @@ class DirTimeline(MemoryDB):
def _event_from_path(self, file_path):
stat = os.stat(file_path)
- # st_atime (time of most recent access),
- # st_mtime (time of most recent content modification),
+ # st_atime (time of most recent access),
+ # st_mtime (time of most recent content modification),
# st_ctime (platform dependent; time of most recent metadata change on
# Unix, or the time of creation on Windows):
start_time = datetime.fromtimestamp(int(stat.st_mtime))
diff --git a/timelinelib/db/backends/file.py b/timelinelib/db/backends/file.py
index e59eb35..13d4577 100644
--- a/timelinelib/db/backends/file.py
+++ b/timelinelib/db/backends/file.py
@@ -16,26 +16,22 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-"""
-Implementation of timeline database with flat file storage using our own custom
-format.
+# This database was only used in version 0.1.0 - 0.9.0.
+# We plan to remove this in version 1.0.0.
-This database was only used for version 0.1.0 - 0.9.0.
-"""
-
-import re
-import codecs
-import os.path
-from os.path import abspath
from datetime import datetime
+from os.path import abspath
import base64
+import codecs
+import os.path
+import re
import StringIO
import wx
from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
from timelinelib.db.objects import Event
from timelinelib.db.objects import TimePeriod
@@ -54,13 +50,6 @@ class ParseException(Exception):
class FileTimeline(MemoryDB):
"""
- Implements the timeline database interface.
-
- The comments in the TimelineDB class describe what the public methods do.
-
- Every public method (including the constructor) can raise a TimelineIOError
- if there was a problem reading or writing from file.
-
The general format of the file looks like this for version >= 0.3.0:
# Written by Timeline 0.3.0 on 2009-7-23 9:40:33
@@ -102,7 +91,7 @@ class FileTimeline(MemoryDB):
If a read error occurs a TimelineIOError will be raised.
"""
- if not os.path.exists(self.path):
+ if not os.path.exists(self.path):
# Nothing to load. Will create a new timeline on save.
return
try:
@@ -179,7 +168,7 @@ class FileTimeline(MemoryDB):
try:
if len(times) != 2:
raise ParseException("Unexpected number of components.")
- tp = TimePeriod(self.get_time_type(), self._parse_time(times[0]),
+ tp = TimePeriod(self.get_time_type(), self._parse_time(times[0]),
self._parse_time(times[1]))
self._set_displayed_period(tp)
if not tp.is_period():
diff --git a/timelinelib/db/backends/ics.py b/timelinelib/db/backends/ics.py
index e10c77f..5d4610c 100644
--- a/timelinelib/db/backends/ics.py
+++ b/timelinelib/db/backends/ics.py
@@ -16,42 +16,27 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-"""
-Implementation of timeline database that reads and writes (todo) ICS files.
-"""
-
-
-import re
-import codecs
-import shutil
-import os.path
-from os.path import abspath
from datetime import date
from datetime import datetime
-from datetime import timedelta
+from os.path import abspath
+import os.path
from icalendar import Calendar
-from timelinelib.db.interface import STATE_CHANGE_ANY
-from timelinelib.db.interface import STATE_CHANGE_CATEGORY
-from timelinelib.db.interface import TimelineDB
-from timelinelib.db.interface import TimelineIOError
-from timelinelib.db.objects import Category
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Event
-from timelinelib.db.objects import TimePeriod
-from timelinelib.db.objects import time_period_center
-from timelinelib.db.utils import generic_event_search
+from timelinelib.db.observer import Observable
+from timelinelib.db.search import generic_event_search
from timelinelib.db.utils import IdCounter
-from timelinelib.db.utils import safe_write
-from timelinelib.meta.version import get_version
from timelinelib.time import PyTimeType
from timelinelib.utils import ex_msg
-class IcsTimeline(TimelineDB):
+class IcsTimeline(Observable):
def __init__(self, path):
- TimelineDB.__init__(self, path)
+ Observable.__init__(self)
+ self.path = path
self.event_id_counter = IdCounter()
self._load_data()
@@ -134,9 +119,6 @@ class IcsTimeline(TimelineDB):
def _load_data(self):
self.cal = Calendar()
- if not os.path.exists(self.path):
- # Nothing to load. Will create a new timeline on save.
- return
try:
file = open(self.path, "rb")
try:
@@ -156,26 +138,19 @@ class IcsTimeline(TimelineDB):
whole_msg = (msg + "\n\n%s") % (abspath(self.path), e)
raise TimelineIOError(whole_msg)
- def _save_data(self):
- #def save(file):
- # file.write(self.cal.as_string())
- #safe_write(self.path, None, save)
- pass
-
def extract_start_end(vevent):
- """Return (start_time, end_time)."""
- start = ensure_datetime(vevent.decoded("dtstart"))
+ start = convert_to_datetime(vevent.decoded("dtstart"))
if vevent.has_key("dtend"):
- end = ensure_datetime(vevent.decoded("dtend"))
+ end = convert_to_datetime(vevent.decoded("dtend"))
elif vevent.has_key("duration"):
end = start + vevent.decoded("duration")
else:
- end = ensure_datetime(vevent.decoded("dtstart"))
+ end = convert_to_datetime(vevent.decoded("dtstart"))
return (start, end)
-def ensure_datetime(d):
+def convert_to_datetime(d):
if isinstance(d, datetime):
return datetime(d.year, d.month, d.day, d.hour, d.minute, d.second)
elif isinstance(d, date):
diff --git a/timelinelib/db/backends/memory.py b/timelinelib/db/backends/memory.py
index b413f50..0c54c0d 100644
--- a/timelinelib/db/backends/memory.py
+++ b/timelinelib/db/backends/memory.py
@@ -29,21 +29,22 @@ query persistent storage to retrieve data.
"""
-from timelinelib.db.interface import TimelineIOError
-from timelinelib.db.interface import TimelineDB
-from timelinelib.db.interface import STATE_CHANGE_ANY
-from timelinelib.db.interface import STATE_CHANGE_CATEGORY
-from timelinelib.db.objects import Event
-from timelinelib.db.container import Container
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
+from timelinelib.db.objects import Container
+from timelinelib.db.objects import Event
+from timelinelib.db.observer import Observable
+from timelinelib.db.observer import STATE_CHANGE_ANY
+from timelinelib.db.observer import STATE_CHANGE_CATEGORY
+from timelinelib.db.search import generic_event_search
from timelinelib.db.utils import IdCounter
-from timelinelib.db.utils import generic_event_search
-class MemoryDB(TimelineDB):
+class MemoryDB(Observable):
def __init__(self):
- TimelineDB.__init__(self, "")
+ Observable.__init__(self)
+ self.path = ""
self.categories = []
self.category_id_counter = IdCounter()
self.events = []
@@ -117,10 +118,10 @@ class MemoryDB(TimelineDB):
id = subevent.cid()
if id == 0:
id = self._get_max_container_id(container_events) + 1
- subevent.set_cid(id)
+ subevent.set_cid(id)
name = "[%d]Container" % id
- container = Container(subevent.time_type,
- subevent.time_period.start_time,
+ container = Container(subevent.time_type,
+ subevent.time_period.start_time,
subevent.time_period.end_time, name)
self.save_event(container)
self._register_subevent(subevent)
@@ -132,7 +133,7 @@ class MemoryDB(TimelineDB):
if id < event.cid():
id = event.cid()
return id
-
+
def _unregister_subevent(self, subevent):
container_events = [event for event in self.events
if event.is_container()]
@@ -146,7 +147,7 @@ class MemoryDB(TimelineDB):
self.events.remove(container)
except:
pass
-
+
def delete_event(self, event_or_id):
if isinstance(event_or_id, Event):
event = event_or_id
@@ -169,7 +170,7 @@ class MemoryDB(TimelineDB):
return list(self.categories)
def get_containers(self):
- containers = [event for event in self.events
+ containers = [event for event in self.events
if event.is_container()]
return containers
diff --git a/timelinelib/db/backends/tutorial.py b/timelinelib/db/backends/tutorial.py
new file mode 100644
index 0000000..00bd49b
--- /dev/null
+++ b/timelinelib/db/backends/tutorial.py
@@ -0,0 +1,128 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+from datetime import datetime
+from datetime import timedelta
+
+from timelinelib.db.objects import Category
+from timelinelib.db.objects import Event
+from timelinelib.db.objects import TimePeriod
+from timelinelib.db.backends.memory import MemoryDB
+
+
+def create_in_memory_tutorial_db():
+ tutcreator = TutorialTimelineCreator()
+ tutcreator.add_category(_("Welcome"), (255, 80, 80), (0, 0, 0))
+ tutcreator.add_event(
+ _("Welcome to Timeline"),
+ "",
+ timedelta(days=4))
+ tutcreator.add_category(_("Intro"), (250, 250, 20), (0, 0, 0))
+ tutcreator.add_event(
+ _("Hover me!"),
+ _("Hovering events with a triangle shows the event description."),
+ timedelta(days=5))
+ tutcreator.add_category(_("Features"), (100, 100, 250), (250, 250, 20))
+ tutcreator.add_event(
+ _("Scroll"),
+ _("Left click somewhere on the timeline and start dragging."
+ "\n\n"
+ "You can also use the mouse wheel."
+ "\n\n"
+ "You can also middle click with the mouse to center around that point."),
+ timedelta(days=5),
+ timedelta(days=10))
+ tutcreator.add_event(
+ _("Zoom"),
+ _("Hold down Ctrl while scrolling the mouse wheel."
+ "\n\n"
+ "Hold down Shift while dragging with the mouse."),
+ timedelta(days=6),
+ timedelta(days=11))
+ tutcreator.add_event(
+ _("Create event"),
+ _("Double click somewhere on the timeline."
+ "\n\n"
+ "Hold down Ctrl while dragging the mouse to select a period."),
+ timedelta(days=12),
+ timedelta(days=18))
+ tutcreator.add_event(
+ _("Edit event"),
+ _("Double click on an event."),
+ timedelta(days=12),
+ timedelta(days=18))
+ tutcreator.add_event(
+ _("Select event"),
+ _("Click on it."
+ "\n\n"
+ "Hold down Ctrl while clicking events to select multiple."),
+ timedelta(days=20),
+ timedelta(days=25))
+ tutcreator.add_event(
+ _("Delete event"),
+ _("Select events to be deleted and press the Del key."),
+ timedelta(days=19),
+ timedelta(days=24))
+ tutcreator.add_event(
+ _("Resize and move me!"),
+ _("First select me and then drag the handles."),
+ timedelta(days=11),
+ timedelta(days=19))
+ tutcreator.add_category(_("Saving"), (50, 200, 50), (0, 0, 0))
+ tutcreator.add_event(
+ _("Saving"),
+ _("This timeline is stored in memory and modifications to it will not "
+ "be persisted between sessions."
+ "\n\n"
+ "Choose File/New/File Timeline to create a timeline that is saved on "
+ "disk."),
+ timedelta(days=23))
+ return tutcreator.get_db()
+
+
+class TutorialTimelineCreator(object):
+
+ def __init__(self):
+ self.db = MemoryDB()
+ now = datetime.now()
+ self.start = datetime(now.year, now.month, 1, 0, 0, 0)
+ self.end = self.start + timedelta(days=30)
+ self.db._set_displayed_period(TimePeriod(self.db.get_time_type(),
+ self.start, self.end))
+ self.last_cat = None
+
+ def add_category(self, name, color, font_color, make_last_added_parent=False):
+ if make_last_added_parent:
+ parent = self.last_cat
+ else:
+ parent = None
+ self.last_cat = Category(name, color, font_color, True, parent)
+ self.db.save_category(self.last_cat)
+
+ def add_event(self, text, description, start_add, end_add=None):
+ start = self.start + start_add
+ end = start
+ if end_add is not None:
+ end = self.start + end_add
+ evt = Event(self.db.get_time_type(), start, end, text, self.last_cat)
+ if description:
+ evt.set_data("description", description)
+ self.db.save_event(evt)
+
+ def get_db(self):
+ return self.db
diff --git a/timelinelib/db/backends/xmlfile.py b/timelinelib/db/backends/xmlfile.py
index 604f242..f53dbe6 100644
--- a/timelinelib/db/backends/xmlfile.py
+++ b/timelinelib/db/backends/xmlfile.py
@@ -31,22 +31,22 @@ from xml.sax.saxutils import escape as xmlescape
import wx
from timelinelib.db.backends.memory import MemoryDB
-from timelinelib.db.backends.xmlparser import ANY
-from timelinelib.db.backends.xmlparser import OPTIONAL
-from timelinelib.db.backends.xmlparser import parse
-from timelinelib.db.backends.xmlparser import parse_fn_store
-from timelinelib.db.backends.xmlparser import SINGLE
-from timelinelib.db.backends.xmlparser import Tag
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
+from timelinelib.db.objects import Container
from timelinelib.db.objects import Event
-from timelinelib.db.container import Container
-from timelinelib.db.subevent import Subevent
+from timelinelib.db.objects import Subevent
from timelinelib.db.objects import TimePeriod
from timelinelib.db.utils import safe_write
from timelinelib.meta.version import get_version
from timelinelib.time import WxTimeType
from timelinelib.utils import ex_msg
+from timelinelib.xml.parser import ANY
+from timelinelib.xml.parser import OPTIONAL
+from timelinelib.xml.parser import parse
+from timelinelib.xml.parser import parse_fn_store
+from timelinelib.xml.parser import SINGLE
+from timelinelib.xml.parser import Tag
ENCODING = "utf-8"
@@ -111,7 +111,7 @@ class XmlTimeline(MemoryDB):
except:
#TODO: Create container
pass
-
+
def _load(self):
"""
Load timeline data from the file that this timeline points to.
@@ -122,7 +122,7 @@ class XmlTimeline(MemoryDB):
If a read error occurs a TimelineIOError will be raised.
"""
- if not os.path.exists(self.path):
+ if not os.path.exists(self.path):
# Nothing to load. Will create a new timeline on save.
return
try:
@@ -255,7 +255,7 @@ class XmlTimeline(MemoryDB):
time, text = alert
time_string = self._time_string(time)
return "%s;%s" % (time_string, text)
-
+
def _parse_alert_string(self, alert_string):
if alert_string is not None:
try:
@@ -267,13 +267,13 @@ class XmlTimeline(MemoryDB):
else:
alert = None
return alert
-
+
def _is_container_event(self, text):
return text.startswith("[")
def _is_subevent(self, text):
return text.startswith("(")
-
+
def _extract_container_id(self, text):
str_id, text = text.split("]", 1)
try:
@@ -282,7 +282,7 @@ class XmlTimeline(MemoryDB):
except:
id = -1
return id, text
-
+
def _extract_subid(self, text):
id, text = text.split(")", 1)
try:
@@ -290,7 +290,7 @@ class XmlTimeline(MemoryDB):
except:
id = -1
return id, text
-
+
def _parse_optional_bool(self, tmp_dict, id):
if tmp_dict.has_key(id):
return tmp_dict.pop(id) == "True"
@@ -326,7 +326,7 @@ class XmlTimeline(MemoryDB):
events = [event for event in self.events if not event.is_subevent()]
events.extend(subevents)
self.events = events
-
+
def _write_xml_doc(self, file):
file.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
self._write_timeline(file)
@@ -379,7 +379,7 @@ class XmlTimeline(MemoryDB):
if evt.get_data("description") is not None:
write_simple_tag(file, "description", evt.get_data("description"),
INDENT3)
- alert = evt.get_data("alert")
+ alert = evt.get_data("alert")
if alert is not None:
write_simple_tag(file, "alert", self.alert_string(alert),
INDENT3)
diff --git a/timelinelib/db/exceptions.py b/timelinelib/db/exceptions.py
new file mode 100644
index 0000000..8485b6d
--- /dev/null
+++ b/timelinelib/db/exceptions.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+class TimelineIOError(Exception):
+ """
+ Raised from a backend if a read/write error occurs.
+
+ The constructor and any of the public methods can raise this exception.
+
+ Also raised by the get_timeline method if loading of a timeline failed.
+ """
+ pass
diff --git a/timelinelib/db/interface.py b/timelinelib/db/interface.py
index b266ac2..f758b09 100644
--- a/timelinelib/db/interface.py
+++ b/timelinelib/db/interface.py
@@ -16,198 +16,19 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-# A category was added, edited, or deleted
-STATE_CHANGE_CATEGORY = 1
-# Something happened that changed the state of the timeline
-STATE_CHANGE_ANY = 2
-
-
-class Observable(object):
-
- def __init__(self):
- self.observers = []
-
- def register(self, fn):
- self.observers.append(fn)
-
- def unregister(self, fn):
- if fn in self.observers:
- self.observers.remove(fn)
-
- def _notify(self, state_change):
- for fn in self.observers:
- fn(state_change)
-
-
-class TimelineDB(Observable):
- """
- Read (and write) timeline data from persistent storage.
-
- All methods that modify timeline data should automatically write it to
- persistent storage.
-
- A TimelineIOError should be raised if reading or writing fails. After such
- a failure the database it not guarantied to return correct data. (Read and
- write errors are however very rare.)
-
- A timeline database is observable so that GUI components can update
- themselves when data changes. The two types of state changes are given as
- constants above.
-
- Future considerations: If databases get large it might be inefficient to
- save to persistent storage every time we modify the database. A solution is
- to add an explicit save method and have all the other methods just modify
- the database in memory.
- """
-
- def __init__(self, path):
- Observable.__init__(self)
- self.path = path
-
- def get_time_type(self):
- raise NotImplementedError()
-
- def is_read_only(self):
- """
- Return True if you can only read from this database and False if you
- can both read and write.
- """
- raise NotImplementedError()
-
- def supported_event_data(self):
- """
- Return a list of event data that we can write.
-
- Event data is represented by a string id. See Event.set_data for
- information what string id map to what data.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def search(self, search_string):
- """
- Return a list of events matching the search string.
- """
- raise NotImplementedError()
-
- def get_events(self, time_period):
- """
- Return a list of events within the time period.
- """
- raise NotImplementedError()
-
- def get_all_events(self, time_period):
- """
- Return a list of all events in the database.
- """
- raise NotImplementedError()
-
- def get_first_event(self):
- """Return the event with the earliest start time."""
- raise NotImplementedError()
-
- def get_last_event(self):
- """Return the event with the latest end time."""
- raise NotImplementedError()
-
- def save_event(self, event):
- """
- Make sure that the given event is saved to persistent storage.
-
- If the event is new it is given a new unique id. Otherwise the
- information in the database is just updated.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def delete_event(self, event_or_id):
- """
- Delete the event (or the event with the given id) from the database.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def get_categories(self):
- """
- Return a list of all available categories.
- """
- raise NotImplementedError()
-
- def save_category(self, category):
- """
- Make sure that the given category is saved to persistent storage.
-
- If the category is new it is given a new unique id. Otherwise the
- information in the database is just updated.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def delete_category(self, category_or_id):
- """
- Delete the category (or the category with the given id) from the
- database.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def load_view_properties(self, view_properties):
- """
- Load saved view properties from persistent storage into view_properties
- object.
- """
- raise NotImplementedError()
-
- def save_view_properties(self, view_properties):
- """
- Save subset of view properties to persistent storage.
-
- Not required if is_read_only returns True.
- """
- raise NotImplementedError()
-
- def find_event_with_id(self, id):
- """
- Return the event associated with the given event id.
- """
- raise NotImplementedError()
-
- def place_event_after_event(self, event_to_place, target_event):
- raise NotImplementedError()
-
- def place_event_before_event(self, event_to_place, target_event):
- raise NotImplementedError()
-
-
-class TimelineIOError(Exception):
- """
- Raised from a TimelineDB if a read/write error occurs.
-
- The constructor and any of the public methods can raise this exception.
-
- Also raised by the get_timeline method if loading of a timeline failed.
- """
- pass
-
-
class ContainerStrategy(object):
-
+
def __init__(self, container):
self.container = container
-
+
def register_subevent(self, subevent):
"""Return the event with the latest end time."""
- raise NotImplementedError()
+ raise NotImplementedError()
def unregister_subevent(self, subevent):
"""Return the event with the latest end time."""
- raise NotImplementedError()
+ raise NotImplementedError()
def update(self, subevent):
"""Update container properties when adding a new sub-event."""
- raise NotImplementedError()
+ raise NotImplementedError()
diff --git a/timelinelib/db/objects/__init__.py b/timelinelib/db/objects/__init__.py
new file mode 100644
index 0000000..8fd4d4d
--- /dev/null
+++ b/timelinelib/db/objects/__init__.py
@@ -0,0 +1,27 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timelinelib.db.objects.category import Category
+from timelinelib.db.objects.container import Container
+from timelinelib.db.objects.event import Event
+from timelinelib.db.objects.subevent import Subevent
+from timelinelib.db.objects.timeperiod import PeriodTooLongError
+from timelinelib.db.objects.timeperiod import TimeOutOfRangeLeftError
+from timelinelib.db.objects.timeperiod import TimeOutOfRangeRightError
+from timelinelib.db.objects.timeperiod import TimePeriod
+from timelinelib.db.objects.timeperiod import time_period_center
diff --git a/timelinelib/db/objects/category.py b/timelinelib/db/objects/category.py
new file mode 100644
index 0000000..779e3b0
--- /dev/null
+++ b/timelinelib/db/objects/category.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+class Category(object):
+
+ # NOTE: The visible flag of categories should not be used any longer.
+ # Visibility of categories are now managed in ViewProperties. However some
+ # timeline databases still use this flag to manage the saving. This flag
+ # should be removed when we can.
+
+ def __init__(self, name, color, font_color, visible, parent=None):
+ self.id = None
+ self.name = name
+ self.color = color
+ if font_color is None:
+ self.font_color = (0, 0, 0)
+ else:
+ self.font_color = font_color
+ self.visible = visible
+ self.parent = parent
+
+ def has_id(self):
+ return self.id is not None
+
+ def set_id(self, id):
+ self.id = id
+
+
+def sort_categories(categories):
+ sorted_categories = list(categories)
+ sorted_categories.sort(cmp, lambda x: x.name.lower())
+ return sorted_categories
diff --git a/timelinelib/db/objects/container.py b/timelinelib/db/objects/container.py
new file mode 100644
index 0000000..903ce36
--- /dev/null
+++ b/timelinelib/db/objects/container.py
@@ -0,0 +1,56 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timelinelib.db.objects.event import Event
+from timelinelib.db.strategies import DefaultContainerStrategy
+
+
+class Container(Event):
+
+ def __init__(self, time_type, start_time, end_time, text, category=None,
+ cid=-1):
+ Event.__init__(self, time_type, start_time, end_time, text, category,
+ False, False, False)
+ self.container_id = cid
+ self.events = []
+ self.strategy = DefaultContainerStrategy(self)
+
+ def is_container(self):
+ return True
+
+ def is_subevent(self):
+ return False
+
+ def cid(self):
+ return self.container_id
+
+ def set_cid(self, cid):
+ self.container_id = cid
+
+ def register_subevent(self, subevent):
+ self.strategy.register_subevent(subevent)
+
+ def unregister_subevent(self, subevent):
+ self.strategy.unregister_subevent(subevent)
+
+ def update_container(self, subevent):
+ self.strategy.update(subevent)
+
+ def update_properties(self, text, category=None):
+ self.text = text
+ self.category = category
diff --git a/timelinelib/db/objects/event.py b/timelinelib/db/objects/event.py
new file mode 100644
index 0000000..e5fa94e
--- /dev/null
+++ b/timelinelib/db/objects/event.py
@@ -0,0 +1,139 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timelinelib.db.objects.timeperiod import TimePeriod
+
+
+class Event(object):
+
+ def __init__(self, time_type, start_time, end_time, text, category=None,
+ fuzzy=False, locked=False, ends_today=False):
+ self.time_type = time_type
+ self.fuzzy = fuzzy
+ self.locked = locked
+ self.ends_today = ends_today
+ self.id = None
+ self.selected = False
+ self.draw_ballon = False
+ self.update(start_time, end_time, text, category)
+ self.data = {}
+
+ def has_id(self):
+ return self.id is not None
+
+ def set_id(self, id):
+ self.id = id
+
+ def update(self, start_time, end_time, text, category=None, fuzzy=None,
+ locked=None, ends_today=None):
+ """Change the event data."""
+ self.time_period = TimePeriod(self.time_type, start_time, end_time)
+ self.text = text
+ self.category = category
+ if ends_today is not None:
+ if not self.locked:
+ self.ends_today = ends_today
+ if fuzzy is not None:
+ self.fuzzy = fuzzy
+ if locked is not None:
+ self.locked = locked
+
+ def update_period(self, start_time, end_time):
+ """Change the event period."""
+ self.time_period = TimePeriod(self.time_type, start_time, end_time)
+
+ def update_period_o(self, new_period):
+ self.update_period(new_period.start_time, new_period.end_time)
+
+ def update_start(self, start_time):
+ """Change the event data."""
+ if start_time <= self.time_period.end_time:
+ self.time_period = TimePeriod(
+ self.time_type, start_time, self.time_period.end_time)
+ return True
+ return False
+
+ def update_end(self, end_time):
+ """Change the event data."""
+ if end_time >= self.time_period.start_time:
+ self.time_period = TimePeriod(
+ self.time_type, self.time_period.start_time, end_time)
+ return True
+ return False
+
+ def inside_period(self, time_period):
+ """Wrapper for time period method."""
+ return self.time_period.overlap(time_period)
+
+ def is_period(self):
+ """Wrapper for time period method."""
+ return self.time_period.is_period()
+
+ def mean_time(self):
+ """Wrapper for time period method."""
+ return self.time_period.mean_time()
+
+ def get_data(self, id):
+ """
+ Return data with the given id or None if no data with that id exists.
+
+ See set_data for information how ids map to data.
+ """
+ return self.data.get(id, None)
+
+ def set_data(self, id, data):
+ """
+ Set data with the given id.
+
+ Here is how ids map to data:
+
+ description - string
+ icon - wx.Bitmap
+ """
+ self.data[id] = data
+
+ def has_data(self):
+ """Return True if the event has associated data, or False if not."""
+ for id in self.data:
+ if self.data[id] != None:
+ return True
+ return False
+
+ def get_label(self):
+ """Returns a unicode label describing the event."""
+ return u"%s (%s)" % (self.text, self.time_period.get_label())
+
+ def clone(self):
+ # Objects of type datetime are immutable.
+ new_event = Event(self.time_type, self.time_period.start_time,
+ self.time_period.end_time, self.text, self.category)
+ # Description is immutable
+ new_event.set_data("description", self.get_data("description") )
+ # Icon is immutable in the sense that it is never changed by our
+ # application.
+ new_event.set_data("icon", self.get_data("icon"))
+ return new_event
+
+ def is_container(self):
+ return False
+
+ def is_subevent(self):
+ return False
+
+ def time_span(self):
+ return self.time_period.end_time - self.time_period.start_time
diff --git a/timelinelib/db/objects/subevent.py b/timelinelib/db/objects/subevent.py
new file mode 100644
index 0000000..c481485
--- /dev/null
+++ b/timelinelib/db/objects/subevent.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+from timelinelib.db.objects.event import Event
+
+
+class Subevent(Event):
+
+ def __init__(self, time_type, start_time, end_time, text, category=None,
+ container=None, cid=-1):
+ Event.__init__(self, time_type, start_time, end_time, text, category,
+ False, False, False)
+ self.container = container
+ if self.container != None:
+ self.container_id = self.container.cid()
+ else:
+ self.container_id = cid
+
+ def is_container(self):
+ """Overrides parent method."""
+ return False
+
+ def is_subevent(self):
+ """Overrides parent method."""
+ return True
+
+ def update_period(self, start_time, end_time):
+ """Overrides parent method."""
+ Event.update_period(self, start_time, end_time)
+ self.container.update_container(self)
+
+ def update_period_o(self, new_period):
+ """Overrides parent method."""
+ Event.update_period(self, new_period.start_time, new_period.end_time)
+ self.container.update_container(self)
+
+ def cid(self):
+ return self.container_id
+
+ def register_container(self, container):
+ self.container = container
+ self.container_id = container.cid()
diff --git a/timelinelib/db/objects/timeperiod.py b/timelinelib/db/objects/timeperiod.py
new file mode 100644
index 0000000..51a574d
--- /dev/null
+++ b/timelinelib/db/objects/timeperiod.py
@@ -0,0 +1,230 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+class TimePeriod(object):
+ """
+ Represents a period in time using a start and end time.
+
+ This is used both to store the time period for an event and for storing the
+ currently displayed time period in the GUI.
+ """
+
+ def __init__(self, time_type, start_time, end_time):
+ """
+ Create a time period.
+
+ `start_time` and `end_time` should be of a type that can be handled
+ by the time_type object.
+ """
+ self.time_type = time_type
+ self.start_time, self.end_time = self._update(start_time, end_time)
+
+ def clone(self):
+ return TimePeriod(self.time_type, self.start_time, self.end_time)
+
+ def __eq__(self, other):
+ if isinstance(other, TimePeriod):
+ return (self.start_time == other.start_time and
+ self.end_time == other.end_time)
+ return False
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __repr__(self):
+ return "TimePeriod<%s, %s>" % (self.start_time, self.end_time)
+
+ def update(self, start_time, end_time,
+ start_delta=None, end_delta=None):
+ new_start, new_end = self._update(start_time, end_time, start_delta, end_delta)
+ return TimePeriod(self.time_type, new_start, new_end)
+
+ def _update(self, start_time, end_time,
+ start_delta=None, end_delta=None):
+ """
+ Change the time period data.
+
+ Optionally add the deltas to the times like this: time + delta.
+
+ If data is invalid, it will not be set, and a ValueError will be raised
+ instead.
+
+ Data is invalid if time + delta is not within the range
+ [self.time_type.get_min_time(), self.time_type.get_max_time()] or if
+ the start time is larger than the end time.
+ """
+ new_start = self._ensure_within_range(start_time, start_delta,
+ _("Start time "))
+ new_end = self._ensure_within_range(end_time, end_delta,
+ _("End time "))
+ self._assert_period_is_valid(new_start, new_end)
+ return (new_start, new_end)
+
+ def _assert_period_is_valid(self, new_start, new_end):
+ self._assert_start_gt_end(new_start, new_end)
+ self._assert_period_lt_max(new_start, new_end)
+
+ def _assert_start_gt_end(self, new_start, new_end):
+ if new_start > new_end:
+ raise ValueError(_("Start time can't be after end time"))
+
+ def _assert_period_lt_max(self, new_start, new_end):
+ MAX_ZOOM_DELTA, max_zoom_error_text = self.time_type.get_max_zoom_delta()
+ if MAX_ZOOM_DELTA and (new_end - new_start > MAX_ZOOM_DELTA):
+ raise PeriodTooLongError(max_zoom_error_text)
+
+ def inside(self, time):
+ """
+ Return True if the given time is inside this period or on the border,
+ otherwise False.
+ """
+ return time >= self.start_time and time <= self.end_time
+
+ def overlap(self, time_period):
+ """Return True if this time period has any overlap with the given."""
+ return not (time_period.end_time < self.start_time or
+ time_period.start_time > self.end_time)
+
+ def is_period(self):
+ """
+ Return True if this time period is longer than just a point in time,
+ otherwise False.
+ """
+ return self.start_time != self.end_time
+
+ def mean_time(self):
+ """
+ Return the time in the middle if this time period is longer than just a
+ point in time, otherwise the point in time for this time period.
+ """
+ return self.start_time + self.time_type.half_delta(self.delta())
+
+ def zoom(self, times, ratio=0.5):
+ MAX_ZOOM_DELTA, max_zoom_error_text = self.time_type.get_max_zoom_delta()
+ MIN_ZOOM_DELTA, min_zoom_error_text = self.time_type.get_min_zoom_delta()
+ start_delta = self.time_type.mult_timedelta(self.delta(), times * ratio / 5.0)
+ end_delta = self.time_type.mult_timedelta(self.delta(), -times * (1.0 - ratio) / 5.0)
+ new_delta = self.delta() - 2 * start_delta
+ if MAX_ZOOM_DELTA and new_delta > MAX_ZOOM_DELTA:
+ raise ValueError(max_zoom_error_text)
+ if new_delta < MIN_ZOOM_DELTA:
+ raise ValueError(min_zoom_error_text)
+ return self.update(self.start_time, self.end_time, start_delta, end_delta)
+
+ def move(self, direction):
+ """
+ Move this time period one 10th to the given direction.
+
+ Direction should be -1 for moving to the left or 1 for moving to the
+ right.
+ """
+ delta = self.time_type.mult_timedelta(self.delta(), direction / 10.0)
+ return self.move_delta(delta)
+
+ def move_delta(self, delta):
+ return self.update(self.start_time, self.end_time, delta, delta)
+
+ def delta(self):
+ """Return the length of this time period as a timedelta object."""
+ return self.end_time - self.start_time
+
+ def center(self, time):
+ """
+ Center time period around time keeping the length.
+
+ If we can't center because we are on the edge, we do as good as we can.
+ """
+ delta = time - self.mean_time()
+ start_overflow = self._calculate_overflow(self.start_time, delta)[1]
+ end_overflow = self._calculate_overflow(self.end_time, delta)[1]
+ if start_overflow == -1:
+ delta = self.time_type.get_min_time()[0] - self.start_time
+ elif end_overflow == 1:
+ delta = self.time_type.get_max_time()[0] - self.end_time
+ return self.move_delta(delta)
+
+ def _ensure_within_range(self, time, delta, error_prefix):
+ """
+ Return new time (time + delta) or raise ValueError if it is not within
+ the range [self.time_type.get_min_time(),
+ self.time_type.get_max_time()].
+ """
+ if delta == None:
+ delta = self.time_type.get_zero_delta()
+ new_time, overflow, error_text = self._calculate_overflow(time, delta)
+ if overflow != 0:
+ error_text = "%s %s" % (error_prefix, error_text)
+ raise ValueError(error_text)
+ else:
+ return new_time
+
+ def _calculate_overflow(self, time, delta):
+ """
+ Return a tuple (new time, overflow flag).
+
+ Overflow flag can be -1 (overflow to the left), 0 (no overflow), or 1
+ (overflow to the right).
+
+ If overflow flag is 0 new time is time + delta, otherwise None.
+ """
+ try:
+ min_time, min_error_text = self.time_type.get_min_time()
+ max_time, max_error_text = self.time_type.get_max_time()
+ new_time = time + delta
+ if min_time and new_time < min_time:
+ return (None, -1, min_error_text)
+ if max_time and new_time > max_time:
+ return (None, 1, max_error_text)
+ return (new_time, 0, "")
+ except OverflowError:
+ if delta > self.time_type.get_zero_delta():
+ return (None, 1, max_error_text)
+ else:
+ return (None, -1, min_error_text)
+
+ def get_label(self):
+ """Returns a unicode string describing the time period."""
+ return self.time_type.format_period(self)
+
+ def has_nonzero_time(self):
+ return self.time_type.time_period_has_nonzero_time(self)
+
+
+class TimeOutOfRangeLeftError(ValueError):
+ pass
+
+
+class TimeOutOfRangeRightError(ValueError):
+ pass
+
+
+class PeriodTooLongError(ValueError):
+ pass
+
+
+def time_period_center(time_type, time, length):
+ """
+ TimePeriod factory method.
+
+ Return a time period with the given length (represented as a timedelta)
+ centered around `time`.
+ """
+ half_length = time_type.mult_timedelta(length, 0.5)
+ start_time = time - half_length
+ end_time = time + half_length
+ return TimePeriod(time_type, start_time, end_time)
diff --git a/timelinelib/db/observer.py b/timelinelib/db/observer.py
new file mode 100644
index 0000000..65b9806
--- /dev/null
+++ b/timelinelib/db/observer.py
@@ -0,0 +1,39 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+# A category was added, edited, or deleted
+STATE_CHANGE_CATEGORY = 1
+# Something happened that changed the state of the timeline
+STATE_CHANGE_ANY = 2
+
+
+class Observable(object):
+
+ def __init__(self):
+ self.observers = []
+
+ def register(self, fn):
+ self.observers.append(fn)
+
+ def unregister(self, fn):
+ if fn in self.observers:
+ self.observers.remove(fn)
+
+ def _notify(self, state_change):
+ for fn in self.observers:
+ fn(state_change)
diff --git a/timelinelib/db/search.py b/timelinelib/db/search.py
new file mode 100644
index 0000000..9dc9134
--- /dev/null
+++ b/timelinelib/db/search.py
@@ -0,0 +1,26 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+def generic_event_search(events, search_string):
+ def match(event):
+ return search_string.lower() in event.text.lower()
+ def mean_time(event):
+ return event.mean_time()
+ matches = [event for event in events if match(event)]
+ matches.sort(key=mean_time)
+ return matches
diff --git a/timelinelib/db/strategies.py b/timelinelib/db/strategies.py
index 394f2d5..7c51e9a 100644
--- a/timelinelib/db/strategies.py
+++ b/timelinelib/db/strategies.py
@@ -20,10 +20,10 @@ from timelinelib.db.interface import ContainerStrategy
class DefaultContainerStrategy(ContainerStrategy):
-
+
def __init__(self, container):
ContainerStrategy.__init__(self, container)
-
+
def register_subevent(self, subevent):
if subevent not in self.container.events:
self.container.events.append(subevent)
@@ -43,18 +43,18 @@ class DefaultContainerStrategy(ContainerStrategy):
self.unregister_subevent(subevent)
self.register_subevent(subevent)
self._set_time_period()
-
+
def _set_time_period(self):
"""
The container time period starts where the subevent with the earliest
- start time, starts, and it ends where the subevent whith the latest end
+ start time, starts, and it ends where the subevent whith the latest end
time ends.
Subevents +------+ +--------+ +--+
Container +--------------------------+
"""
if len(self.container.events) == 0:
return
- self._set_start_time(self.container.events[0])
+ self._set_start_time(self.container.events[0])
self._set_end_time(self.container.events[0])
for event in self.container.events:
if self._container_starts_after_event(event):
@@ -63,23 +63,23 @@ class DefaultContainerStrategy(ContainerStrategy):
self._set_end_time(event)
def _container_starts_after_event(self, subevent):
- return (self.container.time_period.start_time >
+ return (self.container.time_period.start_time >
subevent.time_period.start_time)
def _container_ends_before_event(self, event):
- return (self.container.time_period.end_time <
+ return (self.container.time_period.end_time <
event.time_period.end_time)
-
+
def _set_start_time(self, event):
self.container.time_period.start_time = event.time_period.start_time
def _set_end_time(self, event):
self.container.time_period.end_time = event.time_period.end_time
-
+
def _adjust_time_period(self, new_event):
"""
If the event to be added to the container overlaps any other
- event in the container or if the new event is outside of the
+ event in the container or if the new event is outside of the
container time period the container time period must be adjusted.
"""
event = self._event_totally_overlapping_new_event(new_event)
@@ -110,17 +110,17 @@ class DefaultContainerStrategy(ContainerStrategy):
self._move_events_left(new_event, event)
else:
self._move_events_right(new_event, event)
-
+
def _move_events_left(self, new_event, event):
delta = event.time_period.end_time - new_event.time_period.start_time
latest_start_time = event.time_period.start_time
self._move_early_events_left(new_event, latest_start_time, delta)
def _move_events_right(self, new_event, event):
- delta = new_event.time_period.end_time - event.time_period.start_time
+ delta = new_event.time_period.end_time - event.time_period.start_time
earliest_start_time = event.time_period.start_time
self._move_late_events_right(new_event, earliest_start_time, delta)
-
+
def _adjust_when_new_event_partially_overlaps_other_events(self, new_event, events):
# Situation:
# V = threshold_time
@@ -131,24 +131,24 @@ class DefaultContainerStrategy(ContainerStrategy):
# or +-------------+
# or +-------------+
# or +-------------+
- threshold_time = self._calc_threshold_time(new_event)
- event = self._some_event_in_new_event_threshold_time(new_event,
- events,
+ threshold_time = self._calc_threshold_time(new_event)
+ event = self._some_event_in_new_event_threshold_time(new_event,
+ events,
threshold_time)
if event is not None:
self._adjust_threshold_triggered_events(new_event, event, threshold_time)
- earliest_start = self._earliest_start_time_for_event_that_starts_within_new_event(new_event,
- events,
+ earliest_start = self._earliest_start_time_for_event_that_starts_within_new_event(new_event,
+ events,
threshold_time)
if earliest_start is not None:
self._adjust_events_starting_in_new_event(new_event, earliest_start)
-
+
def _calc_threshold_time(self, new_event):
- td = new_event.time_span()
+ td = new_event.time_span()
td = new_event.time_type.mult_timedelta(td, 0.2)
threshold_time = new_event.time_period.start_time + td
- return threshold_time
-
+ return threshold_time
+
def _some_event_in_new_event_threshold_time(self, new_event, events, end):
start = new_event.time_period.start_time
for event in events:
@@ -159,11 +159,11 @@ class DefaultContainerStrategy(ContainerStrategy):
if event.time_period.start_time <= start and event.time_period.end_time > end:
return event
return None
-
+
def _adjust_threshold_triggered_events(self, new_event, event, threshold_time):
delta = event.time_period.end_time - new_event.time_period.start_time
self._move_early_events_left(new_event, threshold_time, delta)
-
+
def _earliest_start_time_for_event_that_starts_within_new_event(self, new_event, events, thr):
start = new_event.time_period.start_time
end = new_event.time_period.end_time
@@ -179,11 +179,11 @@ class DefaultContainerStrategy(ContainerStrategy):
if event.time_period.start_time < min_start:
min_start = event.time_period.start_time
return min_start
-
+
def _adjust_events_starting_in_new_event(self, new_event, earliest_start):
delta = new_event.time_period.end_time - earliest_start
self._move_late_events_right(new_event, earliest_start, delta)
-
+
def _event_totally_overlapping_new_event(self, new_event):
for event in self.container.events:
if event == new_event:
@@ -191,20 +191,20 @@ class DefaultContainerStrategy(ContainerStrategy):
if (self._event_totally_overlaps_new_event(new_event, event)):
return event
return None
-
+
def _event_totally_overlaps_new_event(self, new_event, event):
- return (event.time_period.start_time <= new_event.time_period.start_time and
+ return (event.time_period.start_time <= new_event.time_period.start_time and
event.time_period.end_time >= new_event.time_period.end_time)
-
- def _events_overlapped_by_new_event(self, new_event):
+
+ def _events_overlapped_by_new_event(self, new_event):
overlapping_events = []
for event in self.container.events:
if event != new_event:
- if (self._starts_within(event, new_event) or
+ if (self._starts_within(event, new_event) or
self._ends_within(event, new_event)):
overlapping_events.append(event)
return overlapping_events
-
+
def _starts_within(self, event, new_event):
s1 = event.time_period.start_time >= new_event.time_period.start_time
s2 = event.time_period.start_time <= new_event.time_period.end_time
@@ -214,7 +214,7 @@ class DefaultContainerStrategy(ContainerStrategy):
s1 = event.time_period.end_time >= new_event.time_period.start_time
s2 = event.time_period.end_time <= new_event.time_period.end_time
return (s1 and s2)
-
+
def _move_early_events_left(self, new_event, latest_start_time, delta):
delta = -delta
for event in self.container.events:
@@ -222,17 +222,16 @@ class DefaultContainerStrategy(ContainerStrategy):
continue
if event.time_period.start_time <= latest_start_time:
self._adjust_event_time_period(event, delta)
-
+
def _move_late_events_right(self, new_event, earliest_start_time, delta):
for event in self.container.events:
if event == new_event:
continue
if event.time_period.start_time >= earliest_start_time:
self._adjust_event_time_period(event, delta)
-
+
def _adjust_event_time_period(self, event, delta):
new_start = event.time_period.start_time + delta
new_end = event.time_period.end_time + delta
event.time_period.start_time = new_start
event.time_period.end_time = new_end
-
diff --git a/timelinelib/db/utils.py b/timelinelib/db/utils.py
index 4163e31..27d1524 100644
--- a/timelinelib/db/utils.py
+++ b/timelinelib/db/utils.py
@@ -20,7 +20,7 @@ import codecs
import os
import os.path
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
class IdCounter(object):
@@ -33,16 +33,6 @@ class IdCounter(object):
return self.id
-def generic_event_search(events, search_string):
- def match(event):
- return search_string.lower() in event.text.lower()
- def mean_time(event):
- return event.mean_time()
- matches = [event for event in events if match(event)]
- matches.sort(key=mean_time)
- return matches
-
-
def safe_write(path, encoding, write_fn):
"""
Write to path in such a way that the contents of path is only modified
diff --git a/timelinelib/drawing/__init__.py b/timelinelib/drawing/__init__.py
index d980c6d..9f2d122 100644
--- a/timelinelib/drawing/__init__.py
+++ b/timelinelib/drawing/__init__.py
@@ -16,16 +16,6 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-"""
-Functionality for drawing timelines onto device contexts (wx.DC).
-"""
-
-
def get_drawer():
- """
- Factory method.
-
- Return the drawing algorithm that should be used by the application.
- """
from timelinelib.drawing.drawers.default import DefaultDrawingAlgorithm
return DefaultDrawingAlgorithm()
diff --git a/timelinelib/drawing/drawers/default.py b/timelinelib/drawing/drawers/default.py
index d821708..b48de53 100644
--- a/timelinelib/drawing/drawers/default.py
+++ b/timelinelib/drawing/drawers/default.py
@@ -22,7 +22,7 @@ import os.path
import wx
from timelinelib.config.paths import ICONS_DIR
-from timelinelib.domain.category import sort_categories
+from timelinelib.db.objects.category import sort_categories
from timelinelib.drawing.interface import Drawer
from timelinelib.drawing.scene import TimelineScene
from timelinelib.drawing.utils import darken_color
@@ -139,8 +139,7 @@ class DefaultDrawingAlgorithm(Drawer):
left_strip_time, right_strip_time = self._snap_region(time)
return right_strip_time
- def _snap_region(self, time):
- time_x = self.scene.x_pos_for_time(time)
+ def _snap_region(self, time):
left_strip_time = self.scene.minor_strip.start(time)
right_strip_time = self.scene.minor_strip.increment(left_strip_time)
return (left_strip_time, right_strip_time)
@@ -158,7 +157,7 @@ class DefaultDrawingAlgorithm(Drawer):
return event
container_event = event
else:
- return event
+ return event
return container_event
def event_with_rect_at(self, x, y, alt_down=False):
@@ -172,9 +171,9 @@ class DefaultDrawingAlgorithm(Drawer):
container_event = event
container_rect = rect
else:
- return event, rect
+ return event, rect
if container_event == None:
- return None
+ return None
return container_event, container_rect
def event_rect(self, evt):
@@ -308,14 +307,14 @@ class DefaultDrawingAlgorithm(Drawer):
def _point_subevent(self, event):
return event.is_subevent() and not event.is_period()
-
+
def _get_container_y(self, id):
for (event, rect) in self.scene.event_data:
if event.is_container():
if event.container_id == id:
return rect.y - 1
return self.scene.divider_y
-
+
def _set_line_color(self, view_properties, event):
if view_properties.is_selected(event):
self.dc.SetPen(self.red_solid_pen)
@@ -446,7 +445,7 @@ class DefaultDrawingAlgorithm(Drawer):
p3 <
\
p4 \p5 ----------
- """
+ """
x1 = rect.x
x2 = rect.x + rect.height / 2
y1 = rect.y
@@ -462,7 +461,7 @@ class DefaultDrawingAlgorithm(Drawer):
def _draw_fuzzy_end(self, rect, event):
"""
---- P2\ p1
- \
+ \
> p3
/
---- p4/ p4
@@ -561,7 +560,7 @@ class DefaultDrawingAlgorithm(Drawer):
self.dc.SetClippingRect(rect_copy)
text_x = rect.X + INNER_PADDING
if event.fuzzy or event.locked:
- text_x += rect.Height / 2
+ text_x += rect.Height / 2
text_y = rect.Y + INNER_PADDING
if text_x < INNER_PADDING:
text_x = INNER_PADDING
@@ -696,7 +695,7 @@ class DefaultDrawingAlgorithm(Drawer):
(iw, ih) = icon.Size
inner_rect_w = iw
inner_rect_h = ih
- max_text_width = max(MIN_TEXT_WIDTH, (self.scene.width - SLIDER_WIDTH - event_rect.X - iw))
+ max_text_width = max(MIN_TEXT_WIDTH, (self.scene.width - SLIDER_WIDTH - event_rect.X - iw))
# Text
self.dc.SetFont(get_default_font(8))
font_h = self.dc.GetCharHeight()
diff --git a/timelinelib/drawing/interface.py b/timelinelib/drawing/interface.py
index 95101a5..8292d0d 100644
--- a/timelinelib/drawing/interface.py
+++ b/timelinelib/drawing/interface.py
@@ -126,4 +126,4 @@ class Strip(object):
def get_font(self, time_period):
"""
Return the preferred font for this strip
- """
+ """
diff --git a/timelinelib/drawing/scene.py b/timelinelib/drawing/scene.py
index 8660d5e..a8b0255 100644
--- a/timelinelib/drawing/scene.py
+++ b/timelinelib/drawing/scene.py
@@ -37,8 +37,8 @@ class TimelineScene(object):
self._baseline_padding = 15
self._period_threshold = 20
self._data_indicator_size = 10
- self._metrics = Metrics(size, self._db.get_time_type(),
- self._view_properties.displayed_period,
+ self._metrics = Metrics(size, self._db.get_time_type(),
+ self._view_properties.displayed_period,
self._view_properties.divider_position)
self.width, self.height = size
self.divider_y = self._metrics.half_height
@@ -100,7 +100,7 @@ class TimelineScene(object):
return None
def _event_rect_drawn_as_period(self, event_rect):
- return event_rect.Y >= self.divider_y
+ return event_rect.Y >= self.divider_y
def _get_direction(self, period, up):
if up:
@@ -117,7 +117,7 @@ class TimelineScene(object):
def _get_overlapping_event(self, period, direction, selected_event, rect):
list = self._get_overlapping_events_list(period, rect)
- event = self._get_overlapping_event_from_list(list, direction,
+ event = self._get_overlapping_event_from_list(list, direction,
selected_event)
return event
@@ -131,7 +131,7 @@ class TimelineScene(object):
def _get_overlapping_event_from_list(self, list, direction, selected_event):
if direction == FORWARD:
return self._get_next_overlapping_event(list, selected_event)
- else:
+ else:
return self._get_prev_overlapping_event(list, selected_event)
def _get_next_overlapping_event(self, list, selected_event):
@@ -158,13 +158,13 @@ class TimelineScene(object):
self._calc_rects(visible_events)
def _place_subevents_last(self, events):
- reordered_events = [event for event in events
+ reordered_events = [event for event in events
if not event.is_subevent()]
- subevents = [event for event in events
+ subevents = [event for event in events
if event.is_subevent()]
reordered_events.extend(subevents)
return reordered_events
-
+
def _calc_rects(self, events):
self.event_data = []
for event in events:
@@ -181,7 +181,7 @@ class TimelineScene(object):
def _period_subevent(self, event):
return event.is_subevent() and event.is_period()
-
+
def _create_rectangle_for_period_subevent(self, event):
return self._create_ideal_rect_for_event(event)
@@ -190,7 +190,7 @@ class TimelineScene(object):
self._ensure_rect_is_not_far_outisde_screen(rect)
self._prevent_overlapping_by_adjusting_rect_y(event, rect)
return rect
-
+
def _create_ideal_rect_for_event(self, event):
if event.ends_today:
event.time_period.end_time = self._db.get_time_type().now()
@@ -219,11 +219,11 @@ class TimelineScene(object):
return min_width
def _calc_subevent_threshold_width(self, event):
- # The enlarging factor allows sub-events to be smaller than a normal
+ # The enlarging factor allows sub-events to be smaller than a normal
# event before the container becomes a point event.
enlarging_factor = 2
return enlarging_factor * self._metrics.calc_width(event.time_period)
-
+
def _create_ideal_rect_for_period_event(self, event):
tw, th = self._get_text_size(event.text)
ew = self._metrics.calc_width(event.time_period)
@@ -237,20 +237,20 @@ class TimelineScene(object):
return rect
def _get_ry(self, event):
- if event.is_subevent():
+ if event.is_subevent():
if event.is_period():
return self._get_container_ry(event)
else:
return self._metrics.half_height - self._baseline_padding
else:
return self._metrics.half_height + self._baseline_padding
-
+
def _get_container_ry(self, subevent):
for (event, rect) in self.event_data:
if event == subevent.container:
return rect.y
return self._metrics.half_height + self._baseline_padding
-
+
def _create_ideal_rect_for_non_period_event(self, event):
tw, th = self._get_text_size(event.text)
rw = tw + 2 * self._inner_padding + 2 * self._outer_padding
@@ -286,10 +286,14 @@ class TimelineScene(object):
def fill(list, strip):
"""Fill the given list with the given strip."""
current_start = strip.start(self._view_properties.displayed_period.start_time)
- while current_start < self._view_properties.displayed_period.end_time:
- next_start = strip.increment(current_start)
- list.append(TimePeriod(self._db.get_time_type(), current_start, next_start))
- current_start = next_start
+ try:
+ while current_start < self._view_properties.displayed_period.end_time:
+ next_start = strip.increment(current_start)
+ list.append(TimePeriod(self._db.get_time_type(), current_start, next_start))
+ current_start = next_start
+ except:
+ #Exception occurs when major=century and when we are at the end of the calendar
+ pass
self.major_strip_data = [] # List of time_period
self.minor_strip_data = [] # List of time_period
self.major_strip, self.minor_strip = self._db.get_time_type().choose_strip(self._metrics, self._config)
@@ -326,7 +330,7 @@ class TimelineScene(object):
return rect_with_largest_y
def _get_list_with_overlapping_period_events(self, event_rect):
- return [(event, rect) for (event, rect) in self.event_data
+ return [(event, rect) for (event, rect) in self.event_data
if (self._rects_overlap(event_rect, rect) and
rect.Y >= self.divider_y )]
@@ -344,10 +348,10 @@ class TimelineScene(object):
return rect_with_smallest_y
def _get_list_with_overlapping_point_events(self, event_rect):
- return [(event, rect) for (event, rect) in self.event_data
+ return [(event, rect) for (event, rect) in self.event_data
if (self._rects_overlap(event_rect, rect) and
rect.Y < self.divider_y )]
def _rects_overlap(self, rect1, rect2):
return (rect2.x <= rect1.x + rect1.width and
- rect1.x <= rect2.x + rect2.width)
+ rect1.x <= rect2.x + rect2.width)
diff --git a/timelinelib/drawing/viewproperties.py b/timelinelib/drawing/viewproperties.py
index 77f06dc..cb2715e 100644
--- a/timelinelib/drawing/viewproperties.py
+++ b/timelinelib/drawing/viewproperties.py
@@ -47,7 +47,7 @@ class ViewProperties(object):
if cat is None:
return True
elif e.is_subevent():
- container_visible = category_visible(e.container,
+ container_visible = category_visible(e.container,
e.container.category)
if container_visible:
if self.category_visible(cat) == True:
diff --git a/timelinelib/editors/category.py b/timelinelib/editors/category.py
index aeb5c85..bb62004 100644
--- a/timelinelib/editors/category.py
+++ b/timelinelib/editors/category.py
@@ -16,9 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import wx
-
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import Category
@@ -60,7 +58,7 @@ class CategoryEditor(object):
self.view.handle_used_name(new_name)
return
if self.category is None:
- self.category = Category(new_name, new_color, new_font_color,
+ self.category = Category(new_name, new_color, new_font_color,
True, parent=new_parent)
else:
self.category.name = new_name
diff --git a/timelinelib/editors/container.py b/timelinelib/editors/container.py
index a8936f3..e6b4a0c 100644
--- a/timelinelib/editors/container.py
+++ b/timelinelib/editors/container.py
@@ -16,7 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-from timelinelib.db.container import Container
+from timelinelib.db.objects import Container
from timelinelib.repositories.dbwrapper import DbWrapperEventRepository
@@ -30,8 +30,8 @@ class ContainerEditor(object):
container is saved to the database.
The reason for this behavior is that we don't want to have empty Conatiners
in the database.
- When updating the properties of an existing Container event the changes
- are stored in the timeline database.
+ When updating the properties of an existing Container event the changes
+ are stored in the timeline database.
"""
def __init__(self, view, db, container):
self._set_initial_values_to_member_variables(view, db, container)
@@ -41,7 +41,7 @@ class ContainerEditor(object):
self.view = view
self.db = db
self.container = container
- self.container_exists = (self.container != None)
+ self.container_exists = (self.container != None)
if self.container_exists:
self.name = self.container.text
self.category = self.container.category
@@ -52,7 +52,7 @@ class ContainerEditor(object):
def _set_view_initial_values(self):
self.view.set_name(self.name)
self.view.set_category(self.category)
-
+
#
# Dialog API
#
@@ -71,12 +71,12 @@ class ContainerEditor(object):
def get_container(self):
return self.container
-
+
#
# Internals
#
def _verify_name(self):
- name_is_invalid = (self.name == "")
+ name_is_invalid = (self.name == "")
if name_is_invalid:
msg = _("Field '%s' can't be empty.") % _("Name")
self.view.display_invalid_name(msg)
@@ -85,7 +85,7 @@ class ContainerEditor(object):
def _update_container(self):
self.container.update_properties(self.name, self.category)
self._save_to_db()
-
+
def _save_to_db(self):
try:
DbWrapperEventRepository(self.db).save(self.container)
@@ -97,5 +97,5 @@ class ContainerEditor(object):
time_type = self.db.get_time_type()
start = time_type.now()
end = start
- self.container = Container(time_type, start, end, self.name,
+ self.container = Container(time_type, start, end, self.name,
self.category)
diff --git a/timelinelib/editors/duplicateevent.py b/timelinelib/editors/duplicateevent.py
index e4bbf84..fa808ed 100644
--- a/timelinelib/editors/duplicateevent.py
+++ b/timelinelib/editors/duplicateevent.py
@@ -16,7 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
FORWARD = 0
@@ -39,13 +39,13 @@ class DuplicateEventEditor(object):
def create_duplicates_and_save(self):
(periods, nbr_of_missing_dates) = self._repeat_period(
- self.event.time_period,
+ self.event.time_period,
self.view.get_move_period_fn(),
self.view.get_frequency(),
self.view.get_count(),
self.view.get_direction())
try:
- for period in periods:
+ for period in periods:
event = self.event.clone()
event.update_period(period.start_time, period.end_time)
self.db.save_event(event)
@@ -57,14 +57,14 @@ class DuplicateEventEditor(object):
def _repeat_period(self, period, move_period_fn, frequency,
repetitions, direction):
- periods = []
+ periods = []
nbr_of_missing_dates = 0
for index in self._calc_indicies(direction, repetitions):
new_period = move_period_fn(period, index*frequency)
if new_period == None:
nbr_of_missing_dates += 1
else:
- periods.append(new_period)
+ periods.append(new_period)
return (periods, nbr_of_missing_dates)
def _calc_indicies(self, direction, repetitions):
diff --git a/timelinelib/editors/event.py b/timelinelib/editors/event.py
index 7506bae..fbe6f18 100644
--- a/timelinelib/editors/event.py
+++ b/timelinelib/editors/event.py
@@ -17,11 +17,10 @@
from timelinelib.db.objects import Event
-from timelinelib.db.subevent import Subevent
from timelinelib.db.objects import PeriodTooLongError
+from timelinelib.db.objects import Subevent
from timelinelib.db.objects import TimePeriod
from timelinelib.utils import ex_msg
-from timelinelib.repositories.dbwrapper import DbWrapperEventRepository
class EventEditor(object):
@@ -53,6 +52,11 @@ class EventEditor(object):
self.view.set_name(self.name)
self.view.set_focus("start")
+ def start_is_in_history(self):
+ if self.start is None:
+ return False
+ return self.start < self.timeline.time_type.now()
+
def _set_values(self, start, end, event):
self.event = event
if self.event != None:
@@ -71,7 +75,7 @@ class EventEditor(object):
self.fuzzy = False
self.locked = False
self.ends_today = False
-
+
def _set_view_content(self):
if self.event != None:
self.view.set_event_data(self.event.data)
@@ -131,7 +135,7 @@ class EventEditor(object):
return self.get_start_from_view()
def _dialog_has_signalled_invalid_input(self, time):
- return time == None
+ return time == None
def _verify_that_time_has_not_been_changed(self, start, end):
self._exception_if_start_has_changed(start)
@@ -166,8 +170,8 @@ class EventEditor(object):
if container_selected:
if self.event.is_subevent():
if self.event.container == self.container:
- self.event.update(self.start, self.end, self.name,
- self.category, self.fuzzy, self.locked,
+ self.event.update(self.start, self.end, self.name,
+ self.category, self.fuzzy, self.locked,
self.ends_today)
else:
self._change_container()
@@ -178,49 +182,49 @@ class EventEditor(object):
self._remove_event_from_container()
pass
else:
- self.event.update(self.start, self.end, self.name,
- self.category, self.fuzzy, self.locked,
+ self.event.update(self.start, self.end, self.name,
+ self.category, self.fuzzy, self.locked,
self.ends_today)
-
+
def _remove_event_from_container(self):
self.event.container.unregister_subevent(self.event)
self.timeline.delete_event(self.event)
self._create_new_event()
-
+
def _add_event_to_container(self):
self.timeline.delete_event(self.event)
self._create_subevent()
-
+
def _change_container(self):
self.event.container.unregister_subevent(self.event)
self.container.register_subevent(self.event)
-
+
def _create_new_event(self):
if self.container != None:
self._create_subevent()
else:
- self.event = Event(self.time_type, self.start, self.end, self.name,
- self.category, self.fuzzy, self.locked,
+ self.event = Event(self.time_type, self.start, self.end, self.name,
+ self.category, self.fuzzy, self.locked,
self.ends_today)
-
+
def _create_subevent(self):
if self.is_new_container(self.container):
self.add_new_container()
- self.event = Subevent(self.time_type, self.start, self.end, self.name,
+ self.event = Subevent(self.time_type, self.start, self.end, self.name,
self.category, self.container)
def is_new_container(self, container):
return container not in self.timeline.get_containers()
-
+
def add_new_container(self):
max_id = 0
for container in self.timeline.get_containers():
if container.cid() > max_id:
max_id = container.cid()
max_id += 1
- self.container.set_cid(max_id)
+ self.container.set_cid(max_id)
self._save_container_to_db()
-
+
def _validate_and_save_start(self, start):
if start == None:
raise ValueError()
diff --git a/timelinelib/editors/textdisplay.py b/timelinelib/editors/textdisplay.py
new file mode 100644
index 0000000..5ca62a0
--- /dev/null
+++ b/timelinelib/editors/textdisplay.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+class TextDisplayEditor(object):
+
+ def __init__(self, view, text=""):
+ self.view = view
+ self.text = text
+
+ def initialize(self):
+ self.set_text(self.text)
+
+ def set_text(self, text):
+ self.view.set_text(text)
+
+ def get_text(self):
+ return self.view.get_text()
diff --git a/timelinelib/export/svg.py b/timelinelib/export/svg.py
index 67fbfa7..6718984 100644
--- a/timelinelib/export/svg.py
+++ b/timelinelib/export/svg.py
@@ -18,6 +18,7 @@
from types import UnicodeType
+import wx
from pysvg.structure import *
from pysvg.core import *
from pysvg.text import *
@@ -25,11 +26,9 @@ from pysvg.shape import *
from pysvg.builders import *
from pysvg.filter import *
-from timelinelib.domain.category import sort_categories
+from timelinelib.db.objects.category import sort_categories
from timelinelib.drawing.utils import darken_color
-from datetime import datetime
-
OUTER_PADDING = 5 # Space between event boxes (pixels)
INNER_PADDING = 3 # Space inside event box to text (pixels)
@@ -54,7 +53,7 @@ class SVGDrawingAlgorithm(object):
self.path = path
self.scene = scene
self.view_properties = view_properties
- # SVG document size, maybe TODO
+ # SVG document size, maybe TODO
self.metrics = dict({'widthpx':1052, 'heightpx':744});
# SVG document handle
self.svg = svg(width="%dpx" % self.metrics['widthpx'], height="%dpx" % self.metrics['heightpx'])
@@ -64,8 +63,8 @@ class SVGDrawingAlgorithm(object):
self.mySmallTextStyle = StyleBuilder()
self.myHeaderStyle.setFontFamily(fontfamily="Verdana")
self.mySmallTextStyle.setFontFamily(fontfamily="Verdana")
- self.mySmallTextStyle.setFontSize('3')
- self.myHeaderStyle.setFontSize('7')
+ self.mySmallTextStyle.setFontSize('3')
+ self.myHeaderStyle.setFontSize('7')
self.mySmallTextStyle.setFilling("black")
self.myHeaderStyle.setFilling("black")
filterShadow = filter(x="-.3",y="-.5", width=1.9, height=1.9)
@@ -76,7 +75,7 @@ class SVGDrawingAlgorithm(object):
filtOffset.set_in("out1")
filtOffset.set_dx(4)
filtOffset.set_dy(-4)
- filtOffset.set_result("out2")
+ filtOffset.set_result("out2")
filtMergeNode1 = feMergeNode()
filtMergeNode1.set_in("out2")
filtMergeNode2 = feMergeNode()
@@ -136,7 +135,6 @@ class SVGDrawingAlgorithm(object):
myStyle.setFontFamily(fontfamily="Verdana")
myStyle.setFontSize("2em")
myStyle.setTextAnchor('left')
- oh = ShapeBuilder()
svgGroup = g()
self._draw_minor_strips(svgGroup, myStyle)
self._draw_major_strips(svgGroup, myStyle)
@@ -179,7 +177,7 @@ class SVGDrawingAlgorithm(object):
oh = ShapeBuilder()
style.setStrokeDashArray("")
fontSize = MAJOR_STRIP_FONT_SIZE
- style.setFontSize("%dem" % fontSize)
+ style.setFontSize("%dem" % fontSize)
for tp in self.scene.major_strip_data:
# Divider line
x = self.scene.x_pos_for_time(tp.end_time)
@@ -199,7 +197,7 @@ class SVGDrawingAlgorithm(object):
x = INNER_PADDING
extra_vertical_padding = fontSize * 4
# since there is no function like textwidth() for SVG, just take into account that text can be overwritten
- # do not perform a special handling for right border, SVG is unlimited
+ # do not perform a special handling for right border, SVG is unlimited
myText = self._text(label, x, fontSize*4+INNER_PADDING+extra_vertical_padding)
myText.set_style(style)
group.addElement(myText)
@@ -226,7 +224,7 @@ class SVGDrawingAlgorithm(object):
oh = ShapeBuilder()
line = oh.createLine(x, y, x, self.scene.divider_y, stroke=myStroke)
group.addElement(line)
- circle = oh.createCircle(x, self.scene.divider_y, 2)
+ circle = oh.createCircle(x, self.scene.divider_y, 2)
group.addElement(circle)
# self.dc.DrawLine(x, y, x, self.scene.divider_y)
# self.dc.DrawCircle(x, self.scene.divider_y, 2)
@@ -253,8 +251,8 @@ class SVGDrawingAlgorithm(object):
return border_color
def _map_svg_color(self, color):
- """
- map (r,g,b) color to svg string
+ """
+ map (r,g,b) color to svg string
"""
sColor = "#%02X%02X%02X" % color
return sColor
@@ -295,10 +293,10 @@ class SVGDrawingAlgorithm(object):
Motivation for positioning in right corner:
SVG text cannot be centered since the text width cannot be calculated
and the first part of each event text is important.
- ergo: text needs to be left aligned.
+ ergo: text needs to be left aligned.
But then the probability is high that a lot of text is at the left
bottom
- ergo: put the legend to the right.
+ ergo: put the legend to the right.
+----------+
| Name O |
@@ -335,10 +333,10 @@ class SVGDrawingAlgorithm(object):
cur_y, item_height, item_height, fill=base_color,
stroke=border_color)
svgGroup.addElement(color_box_rect)
- myText = self._svg_clipped_text(cat.name,
- (x + OUTER_PADDING + INNER_PADDING+item_height,
+ myText = self._svg_clipped_text(cat.name,
+ (x + OUTER_PADDING + INNER_PADDING+item_height,
cur_y, width-OUTER_PADDING-INNER_PADDING-item_height,
- item_height ),
+ item_height ),
myStyle)
svgGroup.addElement(myText)
cur_y = cur_y + item_height + INNER_PADDING
@@ -358,12 +356,12 @@ class SVGDrawingAlgorithm(object):
# Ensure that we can't draw content outside inner rectangle
boxColor = self._get_box_color(event)
boxBorderColor = self._get_box_border_color(event)
- svgRect = oh.createRect(rect.X, rect.Y,
- rect.GetWidth(), rect.GetHeight(),
+ svgRect = oh.createRect(rect.X, rect.Y,
+ rect.GetWidth(), rect.GetHeight(),
stroke=boxBorderColor,
fill=boxColor )
if self.shadowFlag:
- svgRect.set_filter("url(#filterShadow)")
+ svgRect.set_filter("url(#filterShadow)")
svgGroup.addElement(svgRect)
if rect.Width > 0:
# Draw the text (if there is room for it)
@@ -393,7 +391,7 @@ class SVGDrawingAlgorithm(object):
myString = self._encode_unicode_text(myString)
# Put text,clipping into a SVG group
group=g()
- rx, ry, width, height = rectTuple
+ rx, ry, width, height = rectTuple
text_x = rx + INNER_PADDING
text_y = ry + height - INNER_PADDING
# TODO: in SVG, negative value should be OK, but they
@@ -405,7 +403,7 @@ class SVGDrawingAlgorithm(object):
text_x = INNER_PADDING
pathId = "path%d_%d" % (text_x, text_y)
p = path(pathData= "M %d %d H %d V %d H %d" % \
- (rx, ry + height,
+ (rx, ry + height,
text_x+width-INNER_PADDING,
ry, rx))
clip = clipPath()
diff --git a/timelinelib/help/pages.py b/timelinelib/help/pages.py
index b327729..5bd83a2 100644
--- a/timelinelib/help/pages.py
+++ b/timelinelib/help/pages.py
@@ -90,7 +90,7 @@ The date data object used does not support week numbers for weeks that start on
# Stars produce emphasized text. DON'T remove them.
# Dashes produce bullet lists. DON'T remove them.
body=_("""
-The timeline shows dates according to the Gregorian calendar on the x-axis. Currently the dates are limited to dates between year 10 and year 9989.
+The timeline shows dates according to the Gregorian calendar on the x-axis. Currently the dates are limited to dates between year 10 and year 9989.
Future versions might support various kinds of timelines so that you for example can specify a time in terms of number of minutes since a start time. If you are interested in such a feature, please get in touch.
"""))
diff --git a/timelinelib/meta/version.py b/timelinelib/meta/version.py
index ce3fd16..4cdb852 100644
--- a/timelinelib/meta/version.py
+++ b/timelinelib/meta/version.py
@@ -16,7 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-VERSION = (0, 17, 0)
+VERSION = (0, 18, 0)
DEV = False
diff --git a/timelinelib/play/playcontroller.py b/timelinelib/play/playcontroller.py
index f132c08..0c4afcd 100644
--- a/timelinelib/play/playcontroller.py
+++ b/timelinelib/play/playcontroller.py
@@ -16,13 +16,11 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import datetime
import time
from timelinelib.db.objects import TimePeriod
from timelinelib.db.objects import time_period_center
from timelinelib.drawing.viewproperties import ViewProperties
-from timelinelib.time.pytime import PyTimeType
class PlayController(object):
diff --git a/timelinelib/time/__init__.py b/timelinelib/time/__init__.py
index 383e478..d320ea5 100644
--- a/timelinelib/time/__init__.py
+++ b/timelinelib/time/__init__.py
@@ -16,7 +16,7 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-from timelinelib.time.pytime import PyTimeType
from timelinelib.time.numtime import NumTimeType
-from timelinelib.time.wxtime import WxTimeType
+from timelinelib.time.pytime import PyTimeType
from timelinelib.time.wxtime import try_to_create_wx_date_time_from_dmy
+from timelinelib.time.wxtime import WxTimeType
diff --git a/timelinelib/time/numtime.py b/timelinelib/time/numtime.py
index ab898cc..3318ab8 100644
--- a/timelinelib/time/numtime.py
+++ b/timelinelib/time/numtime.py
@@ -16,11 +16,8 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import sys
import re
-import wx
-
from timelinelib.time.typeinterface import TimeType
from timelinelib.db.objects import time_period_center
from timelinelib.drawing.interface import Strip
@@ -76,7 +73,6 @@ class NumTimeType(TimeType):
def choose_strip(self, metrics, config):
start_time = 1
end_time = 2
- period_width = 0
limit = 30
period = TimePeriod(self, start_time, end_time)
period_width = metrics.calc_exact_width(period)
@@ -108,7 +104,7 @@ class NumTimeType(TimeType):
return time_period.start_time + delta * x_percent_of_width
def div_timedeltas(self, delta1, delta2):
- return delta1 / delta2
+ return delta1 / delta2
def get_max_zoom_delta(self):
return (None, None)
diff --git a/timelinelib/time/pytime.py b/timelinelib/time/pytime.py
index e0f0afc..bd0e335 100644
--- a/timelinelib/time/pytime.py
+++ b/timelinelib/time/pytime.py
@@ -130,7 +130,7 @@ class PyTimeType(TimeType):
collector.append(u"1 %s" % _("minute"))
elif minutes > 1:
collector.append(u"%d %s" % (minutes, _("minutes")))
- delta_string = u" ".join(collector)
+ delta_string = u" ".join(collector)
if delta_string == "":
delta_string = "0"
return delta_string
@@ -194,10 +194,10 @@ class PyTimeType(TimeType):
total_us1 = delta_to_microseconds(delta1)
total_us2 = delta_to_microseconds(delta2)
# Make sure that the result is a floating point number
- return total_us1 / float(total_us2)
+ return total_us1 / float(total_us2)
def get_max_zoom_delta(self):
- return (timedelta(days=1200*365),
+ return (timedelta(days=1200*365),
_("Can't zoom wider than 1200 years"))
def get_min_zoom_delta(self):
@@ -238,11 +238,11 @@ class PyTimeType(TimeType):
return "%02d:%02d" % (time.hour, time.minute)
def eventtimes_equals(self, time1, time2):
- s1 = "%s %s" % (self.event_date_string(time1),
+ s1 = "%s %s" % (self.event_date_string(time1),
self.event_date_string(time1))
- s2 = "%s %s" % (self.event_date_string(time2),
+ s2 = "%s %s" % (self.event_date_string(time2),
self.event_date_string(time2))
- return s1 == s2
+ return s1 == s2
def go_to_today_fn(main_frame, current_period, navigation_fn):
@@ -392,15 +392,35 @@ def backward_one_year_fn(main_frame, current_period, navigation_fn):
def fit_millennium_fn(main_frame, current_period, navigation_fn):
mean = current_period.mean_time()
- start = datetime(int(mean.year/1000)*1000, 1, 1)
- end = datetime(int(mean.year/1000)*1000 + 1000, 1, 1)
+ if mean.year > get_millenium_max_year():
+ year = get_millenium_max_year()
+ else:
+ year = max(get_min_year(), int(mean.year/1000)*1000)
+ start = datetime(year, 1, 1)
+ end = datetime(year + 1000, 1, 1)
navigation_fn(lambda tp: tp.update(start, end))
+def get_min_year():
+ return PyTimeType().get_min_time()[0].year
+
+
+def get_millenium_max_year():
+ return PyTimeType().get_max_time()[0].year - 1000
+
+
+def get_century_max_year():
+ return PyTimeType().get_max_time()[0].year - 100
+
+
def fit_century_fn(main_frame, current_period, navigation_fn):
mean = current_period.mean_time()
- start = datetime(int(mean.year/100)*100, 1, 1)
- end = datetime(int(mean.year/100)*100 + 100, 1, 1)
+ if mean.year > get_century_max_year():
+ year = get_century_max_year()
+ else:
+ year = max(get_min_year(), int(mean.year/100)*100)
+ start = datetime(year, 1, 1)
+ end = datetime(year + 100, 1, 1)
navigation_fn(lambda tp: tp.update(start, end))
@@ -449,13 +469,16 @@ class StripCentury(Strip):
return datetime(max(self._century_start_year(time.year), 10), 1, 1)
def increment(self, time):
- return time.replace(year=time.year+100)
+ return time.replace(year=time.year + 100)
def get_font(self, time_period):
return get_default_font(8)
def _century_start_year(self, year):
- return (int(year) / 100) * 100
+ year = (int(year) / 100) * 100
+ #if year > get_century_max_year():
+ # year = get_century_max_year
+ return year
class StripDecade(Strip):
@@ -643,8 +666,8 @@ def delta_to_microseconds(delta):
def move_period_num_days(period, num):
delta = timedelta(days=1) * num
- start_time = period.start_time + delta
- end_time = period.end_time + delta
+ start_time = period.start_time + delta
+ end_time = period.end_time + delta
return TimePeriod(period.time_type, start_time, end_time)
diff --git a/timelinelib/time/wxtime.py b/timelinelib/time/wxtime.py
index 7ded2ec..61b50d2 100644
--- a/timelinelib/time/wxtime.py
+++ b/timelinelib/time/wxtime.py
@@ -18,10 +18,6 @@
import sys
import re
-from wx import DateTime
-from wx import DateSpan
-from wx import TimeSpan
-import calendar
import wx
@@ -132,7 +128,7 @@ class WxTimeType(TimeType):
collector.append(u"1 %s" % _("minute"))
elif minutes > 1:
collector.append(u"%d %s" % (minutes, _("minutes")))
- delta_string = u" ".join(collector)
+ delta_string = u" ".join(collector)
if delta_string == "":
delta_string = "0"
return delta_string
@@ -195,7 +191,7 @@ class WxTimeType(TimeType):
total_us1 = delta_to_microseconds(delta1)
total_us2 = delta_to_microseconds(delta2)
# Make sure that the result is a floating point number
- return float(total_us1) / float(total_us2)
+ return float(total_us1) / float(total_us2)
def get_max_zoom_delta(self):
max_zoom_delta = wx.TimeSpan.Days(1200 * 365)
@@ -241,11 +237,11 @@ class WxTimeType(TimeType):
return time.Format("%H:%M")
def eventtimes_equals(self, time1, time2):
- s1 = "%s %s" % (self.event_date_string(time1),
+ s1 = "%s %s" % (self.event_date_string(time1),
self.event_date_string(time1))
- s2 = "%s %s" % (self.event_date_string(time2),
+ s2 = "%s %s" % (self.event_date_string(time2),
self.event_date_string(time2))
- return s1 == s2
+ return s1 == s2
def go_to_today_fn(main_frame, current_period, navigation_fn):
@@ -318,7 +314,7 @@ def _move_smart_month_backward(navigation_fn, start, end):
new_start_year, new_start_month = _months_to_year_and_month(
start_months -
month_diff)
- new_start = wx.DateTimeFromDMY(start.Day, new_start_month, new_start_year,
+ new_start = wx.DateTimeFromDMY(start.Day, new_start_month, new_start_year,
start.Hour, start.Minute, start.Second)
navigation_fn(lambda tp: tp.update(new_start, new_end))
@@ -331,7 +327,7 @@ def _move_smart_month_forward(navigation_fn, start, end):
new_end_year, new_end_month = _months_to_year_and_month(
end_months +
month_diff)
- new_end = wx.DateTimeFromDMY(end.Day, new_end_month, new_end_year,
+ new_end = wx.DateTimeFromDMY(end.Day, new_end_month, new_end_year,
end.Hour, end.Minute, end.Second)
navigation_fn(lambda tp: tp.update(new_start, new_end))
@@ -619,7 +615,7 @@ class StripHour(Strip):
return str(time.Hour)
def start(self, time):
- start_time = wx.DateTimeFromDMY(time.Day, time.Month, time.Year, time.Hour)
+ start_time = wx.DateTimeFromDMY(time.Day, time.Month, time.Year, time.Hour)
return start_time
def increment(self, time):
@@ -642,7 +638,7 @@ def microseconds_to_delta(microsecs):
counter += 1
delta = wx.TimeSpan.Milliseconds(milliseconds)
while counter > 0:
- delta = delta * 2;
+ delta = delta * 2;
counter -= 1
return delta
@@ -657,7 +653,7 @@ def delta_to_microseconds(delta):
neg = False
if days < 0:
neg = True
- days = -days
+ days = -days
hours = -hours
minutes = -minutes
seconds = -seconds
@@ -670,9 +666,9 @@ def delta_to_microseconds(delta):
if seconds >= 0:
microsecs = seconds * US_PER_SEC
if milliseconds >= 0:
- microsecs = milliseconds * 1000
+ microsecs = milliseconds * 1000
if neg:
- microsecs = -microsecs
+ microsecs = -microsecs
return microsecs
diff --git a/timelinelib/view/drawingarea.py b/timelinelib/view/drawingarea.py
index 78e473e..f1b5426 100644
--- a/timelinelib/view/drawingarea.py
+++ b/timelinelib/view/drawingarea.py
@@ -18,11 +18,11 @@
import wx
-from timelinelib.db.interface import STATE_CHANGE_ANY
-from timelinelib.db.interface import STATE_CHANGE_CATEGORY
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db.objects import TimeOutOfRangeLeftError
from timelinelib.db.objects import TimeOutOfRangeRightError
+from timelinelib.db.observer import STATE_CHANGE_ANY
+from timelinelib.db.observer import STATE_CHANGE_CATEGORY
from timelinelib.drawing.viewproperties import ViewProperties
from timelinelib.utils import ex_msg
from timelinelib.view.move import MoveByDragInputHandler
@@ -34,8 +34,8 @@ from timelinelib.view.zoom import ZoomByDragInputHandler
# The width in pixels of the vertical scroll zones.
-# When the mouse reaches the any of the two scroll zone areas, scrolling
-# of the timeline will take place if there is an ongoing selection of the
+# When the mouse reaches the any of the two scroll zone areas, scrolling
+# of the timeline will take place if there is an ongoing selection of the
# timeline. The scroll zone areas are found at the beginning and at the
# end of the timeline.
SCROLL_ZONE_WIDTH = 20
@@ -184,7 +184,7 @@ class DrawingArea(object):
"""
Event handler used when the right mouse button has been pressed.
- If the mouse hits an event and the timeline is not readonly, the
+ If the mouse hits an event and the timeline is not readonly, the
context menu for that event is displayed.
"""
if self.timeline.is_read_only():
@@ -218,13 +218,13 @@ class DrawingArea(object):
if len(selected_event_ids) > 0:
id = selected_event_ids[0]
return self.timeline.find_event_with_id(id)
- return None
+ return None
def _context_menu_on_edit_event(self, evt):
- self.view.edit_event(self.context_menu_event)
+ self.view.open_event_editor_for(self.context_menu_event)
def _context_menu_on_duplicate_event(self, evt):
- self.view.duplicate_event(self.context_menu_event)
+ self.view.open_duplicate_event_dialog_for_event(self.context_menu_event)
def _context_menu_on_delete_event(self, evt):
self.context_menu_event.selected = True
@@ -239,7 +239,7 @@ class DrawingArea(object):
Event handler used when the left mouse button has been double clicked.
If the timeline is readonly, no action is taken.
- If the mouse hits an event, a dialog opens for editing this event.
+ If the mouse hits an event, a dialog opens for editing this event.
Otherwise a dialog for creating a new event is opened.
"""
if self.timeline.is_read_only():
@@ -252,10 +252,10 @@ class DrawingArea(object):
self._toggle_event_selection(x, y, ctrl_down, alt_down)
event = self.drawing_algorithm.event_at(x, y, alt_down)
if event:
- self.view.edit_event(event)
+ self.view.open_event_editor_for(event)
else:
current_time = self.get_time(x)
- self.view.create_new_event(current_time, current_time)
+ self.view.open_create_event_editor(current_time, current_time)
def get_time(self, x):
return self.drawing_algorithm.get_time(x)
@@ -290,9 +290,9 @@ class DrawingArea(object):
Mouse event handler, when the mouse is entering the window.
If there is an ongoing selection-marking (dragscroll timer running)
- and the left mouse button is not down when we enter the window, we
- want to simulate a 'mouse left up'-event, so that the dialog for
- creating an event will be opened or sizing, moving stops.
+ and the left mouse button is not down when we enter the window, we
+ want to simulate a 'mouse left up'-event, so that the dialog for
+ creating an event will be opened or sizing, moving stops.
"""
if self.dragscroll_timer_running:
if not left_is_down:
@@ -301,10 +301,10 @@ class DrawingArea(object):
def mouse_moved(self, x, y, alt_down=False):
self.input_handler.mouse_moved(x, y, alt_down)
- def mouse_wheel_moved(self, rotation, ctrl_down, shift_down):
+ def mouse_wheel_moved(self, rotation, ctrl_down, shift_down, x):
direction = _step_function(rotation)
if ctrl_down:
- self._zoom_timeline(direction)
+ self._zoom_timeline(direction, x)
elif shift_down:
self.divider_line_slider.SetValue(self.divider_line_slider.GetValue() + direction)
self._redraw_timeline()
@@ -327,15 +327,15 @@ class DrawingArea(object):
def _move_event_vertically(self, up=True):
if self._one_and_only_one_event_selected():
selected_event = self._get_first_selected_event()
- (overlapping_event, direction) = self.drawing_algorithm.get_closest_overlapping_event(selected_event,
- up=up)
+ (overlapping_event, direction) = self.drawing_algorithm.get_closest_overlapping_event(selected_event,
+ up=up)
if overlapping_event is None:
return
if direction > 0:
- self.timeline.place_event_after_event(selected_event,
+ self.timeline.place_event_after_event(selected_event,
overlapping_event)
else:
- self.timeline.place_event_before_event(selected_event,
+ self.timeline.place_event_before_event(selected_event,
overlapping_event)
self._redraw_timeline()
@@ -362,7 +362,7 @@ class DrawingArea(object):
self._redraw_timeline()
def _timeline_changed(self, state_change):
- if (state_change == STATE_CHANGE_ANY or
+ if (state_change == STATE_CHANGE_ANY or
state_change == STATE_CHANGE_CATEGORY):
self._redraw_timeline()
@@ -448,21 +448,24 @@ class DrawingArea(object):
def _scroll_timeline_view_by_factor(self, factor):
time_period = self.view_properties.displayed_period
- delta = self.time_type.mult_timedelta(time_period.delta(), factor)
+ delta = self.time_type.mult_timedelta(time_period.delta(), factor)
self._scroll_timeline(delta)
def _scroll_timeline(self, delta):
self.navigate_timeline(lambda tp: tp.move_delta(-delta))
- def _zoom_timeline(self, direction=0):
- self.navigate_timeline(lambda tp: tp.zoom(direction))
+ def _zoom_timeline(self, direction, x):
+ """ zoom time line at position x """
+ width, height = self.view.GetSizeTuple()
+ x_percent_of_width=float(x)/width
+ self.navigate_timeline(lambda tp: tp.zoom(direction, x_percent_of_width))
def _delete_selected_events(self):
"""After acknowledge from the user, delete all selected events."""
selected_event_ids = self.view_properties.get_selected_event_ids()
nbr_of_selected_event_ids = len(selected_event_ids)
if nbr_of_selected_event_ids > 1:
- text = _("Are you sure you want to delete %d events?" %
+ text = _("Are you sure you want to delete %d events?" %
nbr_of_selected_event_ids)
else:
text = _("Are you sure you want to delete this event?")
@@ -475,7 +478,7 @@ class DrawingArea(object):
def balloon_visibility_changed(self, visible):
self.view_properties.show_balloons_on_hover = visible
- # When display on hovering is disabled we have to make sure
+ # When display on hovering is disabled we have to make sure
# that any visible balloon is removed.
# TODO: Do we really need that?
if not visible:
diff --git a/timelinelib/view/move.py b/timelinelib/view/move.py
index 09eb67d..811dc7f 100644
--- a/timelinelib/view/move.py
+++ b/timelinelib/view/move.py
@@ -33,7 +33,7 @@ class MoveByDragInputHandler(ScrollViewInputHandler):
selected_events = self.drawing_area.get_selected_events()
if not event_being_dragged in selected_events:
return
- for event in selected_events:
+ for event in selected_events:
period_pair = (event, event.time_period)
if event == event_being_dragged:
self.event_periods.insert(0, period_pair)
diff --git a/timelinelib/view/periodbase.py b/timelinelib/view/periodbase.py
index 011b658..8b530f3 100644
--- a/timelinelib/view/periodbase.py
+++ b/timelinelib/view/periodbase.py
@@ -60,7 +60,7 @@ class SelectPeriodByDragInputHandler(ScrollViewInputHandler):
start = t1
end = t2
return TimePeriod(
- self.controller.get_timeline().get_time_type(),
+ self.controller.get_timeline().get_time_type(),
self.controller.get_drawer().snap(start),
self.controller.get_drawer().snap(end))
diff --git a/timelinelib/view/periodevent.py b/timelinelib/view/periodevent.py
index 41edd44..9cab5ca 100644
--- a/timelinelib/view/periodevent.py
+++ b/timelinelib/view/periodevent.py
@@ -27,4 +27,4 @@ class CreatePeriodEventByDragInputHandler(SelectPeriodByDragInputHandler):
def end_action(self):
period = self.get_last_valid_period()
- self.view.create_new_event(period.start_time, period.end_time)
+ self.view.open_create_event_editor(period.start_time, period.end_time)
diff --git a/timelinelib/view/resize.py b/timelinelib/view/resize.py
index 9ac7412..ea215b7 100644
--- a/timelinelib/view/resize.py
+++ b/timelinelib/view/resize.py
@@ -71,6 +71,6 @@ class ResizeByDragInputHandler(ScrollViewInputHandler):
def _adjust_container_edges(self):
self.event.strategy._set_time_period()
-
+
def _clear_status_text(self):
self.status_bar.set_text("")
diff --git a/timelinelib/view/scrolldrag.py b/timelinelib/view/scrolldrag.py
index 0b8bd62..c698d37 100644
--- a/timelinelib/view/scrolldrag.py
+++ b/timelinelib/view/scrolldrag.py
@@ -51,7 +51,7 @@ class ScrollByDragInputHandler(InputHandler):
if elapsed_clock_time == 0:
self.speed_px_per_sec = MAX_SPEED
else:
- self.speed_px_per_sec = min(MAX_SPEED, abs(self.last_x_distance /
+ self.speed_px_per_sec = min(MAX_SPEED, abs(self.last_x_distance /
elapsed_clock_time))
self.last_clock_time = current_clock_time
@@ -62,7 +62,7 @@ class ScrollByDragInputHandler(InputHandler):
def _inertial_scrolling(self):
frame_time = self._calculate_frame_time()
- value_factor = self._calculate_scroll_factor()
+ value_factor = self._calculate_scroll_factor()
inertial_func = (0.20, 0.15, 0.10, 0.10, 0.10, 0.08, 0.06, 0.06, 0.05)
#inertial_func = (0.20, 0.15, 0.10, 0.10, 0.07, 0.05, 0.02, 0.05)
self.controller.use_fast_draw(True)
@@ -78,7 +78,7 @@ class ScrollByDragInputHandler(InputHandler):
def _calculate_frame_time(self):
MAX_FRAME_RATE = 26.0
- frames_per_second = (MAX_FRAME_RATE * self.speed_px_per_sec /
+ frames_per_second = (MAX_FRAME_RATE * self.speed_px_per_sec /
(100 + self.speed_px_per_sec))
frame_time = 1.0 / frames_per_second
return frame_time
@@ -88,6 +88,6 @@ class ScrollByDragInputHandler(InputHandler):
direction = 1
else:
direction = -1
- scroll_factor = (direction * self.speed_px_per_sec /
+ scroll_factor = (direction * self.speed_px_per_sec /
self.INERTIAL_SCROLLING_SPEED_THRESHOLD)
- return scroll_factor
+ return scroll_factor
diff --git a/timelinelib/wxgui/component.py b/timelinelib/wxgui/component.py
new file mode 100644
index 0000000..410996c
--- /dev/null
+++ b/timelinelib/wxgui/component.py
@@ -0,0 +1,86 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+import wx
+
+from timelinelib.db import db_open
+from timelinelib.wxgui.dialogs.mainframe import TimelinePanel
+
+
+class DummyConfig(object):
+
+ def __init__(self):
+ self.window_size = (100, 100)
+ self.window_pos = (100, 100)
+ self.window_maximized = False
+ self.show_sidebar = True
+ self.show_legend = True
+ self.sidebar_width = 200
+ self.recently_opened = []
+ self.open_recent_at_startup = False
+ self.balloon_on_hover = True
+ self.week_start = "monaday"
+ self.use_wide_date_range = False
+ self.use_inertial_scrolling = False
+
+ def get_sidebar_width(self):
+ return self.sidebar_width
+
+ def get_show_sidebar(self):
+ return self.show_sidebar
+
+ def get_show_legend(self):
+ return self.show_legend
+
+ def get_balloon_on_hover(self):
+ return self.balloon_on_hover
+
+
+class DummyStatusBarAdapter(object):
+
+ def set_text(self, text):
+ pass
+
+ def set_hidden_event_count_text(self, text):
+ pass
+
+ def set_read_only_text(self, text):
+ pass
+
+
+class DummyMainFrame(object):
+
+ def enable_disable_menus(self):
+ pass
+
+
+class TimelineComponent(TimelinePanel):
+
+ def __init__(self, parent):
+ TimelinePanel.__init__(
+ self, parent, DummyConfig(), self.handle_db_error,
+ DummyStatusBarAdapter(), DummyMainFrame())
+ self.activated()
+
+ def handle_db_error(self, e):
+ pass
+
+ def open_timeline(self, path):
+ timeline = db_open(path)
+ self.drawing_area.set_timeline(timeline)
+ self.sidebar.cattree.initialize_from_timeline_view(self.drawing_area)
diff --git a/timelinelib/wxgui/components/__init__.py b/timelinelib/wxgui/components/__init__.py
index a8a5fad..e69de29 100644
--- a/timelinelib/wxgui/components/__init__.py
+++ b/timelinelib/wxgui/components/__init__.py
@@ -1,23 +0,0 @@
-# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
-#
-# This file is part of Timeline.
-#
-# Timeline is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
-
-
-"""
-All GUI components that can be used in multiple places in the GUI.
-
-One component per file.
-"""
diff --git a/timelinelib/wxgui/components/categorychoice.py b/timelinelib/wxgui/components/categorychoice.py
index 1b00362..6901719 100644
--- a/timelinelib/wxgui/components/categorychoice.py
+++ b/timelinelib/wxgui/components/categorychoice.py
@@ -18,11 +18,11 @@
import wx
-from timelinelib.wxgui.dialogs.categoryeditor import WxCategoryEdtiorDialog
+from timelinelib.db.exceptions import TimelineIOError
+from timelinelib.db.objects.category import sort_categories
from timelinelib.wxgui.dialogs.categorieseditor import CategoriesEditor
+from timelinelib.wxgui.dialogs.categoryeditor import WxCategoryEdtiorDialog
import timelinelib.wxgui.utils as gui_utils
-from timelinelib.db.interface import TimelineIOError
-from timelinelib.domain.category import sort_categories
class CategoryChoice(wx.Choice):
@@ -59,7 +59,7 @@ class CategoryChoice(wx.Choice):
selection = self.GetSelection()
category = self.GetClientData(selection)
return category
-
+
def on_choice(self, e):
new_selection_index = e.GetSelection()
if new_selection_index > self.last_real_category_index:
@@ -73,7 +73,7 @@ class CategoryChoice(wx.Choice):
def _add_category(self):
def create_category_editor():
- return WxCategoryEdtiorDialog(self, _("Add Category"),
+ return WxCategoryEdtiorDialog(self, _("Add Category"),
self.timeline, None)
def handle_success(dialog):
if dialog.GetReturnCode() == wx.ID_OK:
@@ -97,4 +97,4 @@ class CategoryChoice(wx.Choice):
gui_utils.handle_db_error_in_dialog(self, e)
gui_utils.show_modal(create_categories_editor,
gui_utils.create_dialog_db_error_handler(self),
- handle_success)
+ handle_success)
diff --git a/timelinelib/wxgui/components/cattree.py b/timelinelib/wxgui/components/cattree.py
index dd7955c..f5b812a 100644
--- a/timelinelib/wxgui/components/cattree.py
+++ b/timelinelib/wxgui/components/cattree.py
@@ -19,8 +19,8 @@
import wx
import wx.lib.agw.customtreectrl as customtreectrl
-from timelinelib.db.interface import STATE_CHANGE_CATEGORY
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
+from timelinelib.db.observer import STATE_CHANGE_CATEGORY
from timelinelib.wxgui.dialogs.categoryeditor import WxCategoryEdtiorDialog
from timelinelib.wxgui.utils import _ask_question
from timelinelib.wxgui.utils import category_tree
@@ -231,8 +231,8 @@ def delete_category(parent_ctrl, db, cat, fn_handle_db_error):
update_warning = _("Events belonging to '%s' will no longer "
"belong to a category.") % cat.name
else:
- update_warning = _("Events belonging to '%(name)s' will now belong to '%(parent)s'.") \
- % {'name': cat.name, 'parent': cat.parent.name}
+ update_warning = _("Events belonging to '%(name)s' will now belong to '%(parent)s'.") \
+ % {'name': cat.name, 'parent': cat.parent.name}
question = "%s\n\n%s" % (delete_warning, update_warning)
if _ask_question(question, parent_ctrl) == wx.YES:
try:
diff --git a/timelinelib/wxgui/components/numtimepicker.py b/timelinelib/wxgui/components/numtimepicker.py
index 2724895..ed3568e 100644
--- a/timelinelib/wxgui/components/numtimepicker.py
+++ b/timelinelib/wxgui/components/numtimepicker.py
@@ -16,20 +16,15 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-import os.path
-import re
-
import wx
-from timelinelib.time import NumTimeType
-
class NumTimePicker(wx.Panel):
def __init__(self, parent, show_time=False, config=None):
wx.Panel.__init__(self, parent)
self.time_picker = self._create_gui()
- self.controller = NumTimePickerController(self, 0)
+ self.controller = NumTimePickerController(self, 0)
def get_value(self):
return self.time_picker.GetValue()
@@ -59,7 +54,6 @@ class NumTimePicker(wx.Panel):
class NumTimePickerController(object):
def __init__(self, time_picker, default_num_time):
- import sys
self.time_picker = time_picker
self.time_picker.set_range(-10000, 10000)
self.default_num_time = default_num_time
diff --git a/timelinelib/wxgui/components/pydatetimepicker.py b/timelinelib/wxgui/components/pydatetimepicker.py
index df7c735..78a1656 100644
--- a/timelinelib/wxgui/components/pydatetimepicker.py
+++ b/timelinelib/wxgui/components/pydatetimepicker.py
@@ -131,14 +131,14 @@ class CalendarPopup(wx.PopupTransientWindow):
def _create_calendar_control(self, wx_date, border):
style = self._get_cal_style()
- cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
+ cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
pos=(border,border), style=style)
self._set_cal_range(cal)
return cal
def _get_cal_style(self):
- style = (wx.calendar.CAL_SHOW_HOLIDAYS |
- wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
+ style = (wx.calendar.CAL_SHOW_HOLIDAYS |
+ wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
if self.config.week_start == "monday":
style |= wx.calendar.CAL_MONDAY_FIRST
else:
@@ -186,7 +186,7 @@ class CalendarPopupController(object):
# month or day. The control is closed on a double-click on a day or
# a single click outside of the control
if self.repop and not self.repoped:
- self.calendar_popup.Popup()
+ self.calendar_popup.Popup()
self.repoped = True
@@ -241,8 +241,8 @@ class PyDatePicker(wx.TextCtrl):
elif evt.GetKeyCode() == wx.WXK_DOWN:
self.controller.on_down()
else:
- evt.Skip()
- self.Bind(wx.EVT_KEY_DOWN, on_key_down)
+ evt.Skip()
+ self.Bind(wx.EVT_KEY_DOWN, on_key_down)
def _resize_to_fit_text(self):
w, h = self.GetTextExtent("0000-00-00")
@@ -285,7 +285,7 @@ class PyDatePickerController(object):
self.py_date_picker.SetSelection(start, end)
else:
self._select_region_if_possible(self.region_year)
- self.last_selection = self.py_date_picker.GetSelection()
+ self.last_selection = self.py_date_picker.GetSelection()
def on_kill_focus(self):
if self.last_selection:
@@ -312,8 +312,8 @@ class PyDatePickerController(object):
# To prevent saving of preferred day when year or month is changed
# in on_up() and on_down()...
# Save preferred day only when text is entered in the date text
- # control and not when up or down keys has been used.
- # When up and down keys are used, the preferred day is saved in
+ # control and not when up or down keys has been used.
+ # When up and down keys are used, the preferred day is saved in
# on_up() and on_down() only when day is changed.
if self.save_preferred_day:
self._save_preferred_day(current_date)
@@ -325,9 +325,9 @@ class PyDatePickerController(object):
return date
def increment_month(date):
if date.month < 12:
- return self._set_valid_day(date.year, date.month + 1,
+ return self._set_valid_day(date.year, date.month + 1,
date.day)
- elif date.year < PyTimeType().get_max_time()[0].year - 1:
+ elif date.year < PyTimeType().get_max_time()[0].year - 1:
return self._set_valid_day(date.year + 1, 1, date.day)
return date
def increment_day(date):
@@ -345,8 +345,8 @@ class PyDatePickerController(object):
else:
new_date = increment_day(current_date)
self._save_preferred_day(new_date)
- if current_date != new_date:
- self._set_new_date_and_restore_selection(new_date, selection)
+ if current_date != new_date:
+ self._set_new_date_and_restore_selection(new_date, selection)
def on_down(self):
def decrement_year(date):
@@ -356,7 +356,7 @@ class PyDatePickerController(object):
def decrement_month(date):
if date.month > 1:
return self._set_valid_day(date.year, date.month - 1, date.day)
- elif date.year > PyTimeType().get_min_time()[0].year:
+ elif date.year > PyTimeType().get_min_time()[0].year:
return self._set_valid_day(date.year - 1, 12, date.day)
return date
def decrement_day(date):
@@ -378,8 +378,8 @@ class PyDatePickerController(object):
else:
new_date = decrement_day(current_date)
self._save_preferred_day(new_date)
- if current_date != new_date:
- self._set_new_date_and_restore_selection(new_date, selection)
+ if current_date != new_date:
+ self._set_new_date_and_restore_selection(new_date, selection)
def _change_background_depending_on_date_validity(self):
if self._current_date_is_valid():
@@ -409,7 +409,7 @@ class PyDatePickerController(object):
self.py_date_picker.SetSelection(selection[0], selection[1])
self.save_preferred_day = False
if self.preferred_day != None:
- new_date = self._set_valid_day(new_date.year, new_date.month,
+ new_date = self._set_valid_day(new_date.year, new_date.month,
self.preferred_day)
self.set_py_date(new_date)
restore_selection(selection)
@@ -421,7 +421,7 @@ class PyDatePickerController(object):
try:
date = datetime.date(year=new_year, month=new_month, day=new_day)
done = True
- except Exception, ex:
+ except Exception, ex:
new_day -= 1
return date
@@ -449,16 +449,16 @@ class PyDatePickerController(object):
return self.py_date_picker.GetInsertionPoint() in region_range
def _get_region_range(self, n):
- # Returns a range of valid cursor positions for a valid region year,
+ # Returns a range of valid cursor positions for a valid region year,
# month or day.
def region_is_not_valid(region):
- return region not in (self.region_year, self.region_month,
+ return region not in (self.region_year, self.region_month,
self.region_day)
def date_has_exactly_two_seperators(datestring):
return len(datestring.split(self.separator)) == 3
def calculate_pos_range(region, datestring):
pos_of_separator1 = datestring.find(self.separator)
- pos_of_separator2 = datestring.find(self.separator,
+ pos_of_separator2 = datestring.find(self.separator,
pos_of_separator1 + 1)
if region == self.region_year:
return range(0, pos_of_separator1 + 1)
@@ -526,8 +526,8 @@ class PyTimePicker(wx.TextCtrl):
elif evt.GetKeyCode() == wx.WXK_DOWN:
self.controller.on_down()
else:
- evt.Skip()
- self.Bind(wx.EVT_KEY_DOWN, on_key_down)
+ evt.Skip()
+ self.Bind(wx.EVT_KEY_DOWN, on_key_down)
def _resize_to_fit_text(self):
w, h = self.GetTextExtent("00:00")
@@ -600,7 +600,7 @@ class PyTimePickerController(object):
def increment_minutes(time):
new_hour = time.hour
new_minute = time.minute + 1
- if new_minute > 59:
+ if new_minute > 59:
new_minute = 0
new_hour = time.hour + 1
if new_hour > 23:
@@ -614,8 +614,8 @@ class PyTimePickerController(object):
new_time = increment_hour(current_time)
else:
new_time = increment_minutes(current_time)
- if current_time != new_time:
- self._set_new_time_and_restore_selection(new_time, selection)
+ if current_time != new_time:
+ self._set_new_time_and_restore_selection(new_time, selection)
def on_down(self):
def decrement_hour(time):
@@ -626,7 +626,7 @@ class PyTimePickerController(object):
def decrement_minutes(time):
new_hour = time.hour
new_minute = time.minute - 1
- if new_minute < 0:
+ if new_minute < 0:
new_minute = 59
new_hour = time.hour - 1
if new_hour < 0:
@@ -640,8 +640,8 @@ class PyTimePickerController(object):
new_time = decrement_hour(current_time)
else:
new_time = decrement_minutes(current_time)
- if current_time != new_time:
- self._set_new_time_and_restore_selection(new_time, selection)
+ if current_time != new_time:
+ self._set_new_time_and_restore_selection(new_time, selection)
def _set_new_time_and_restore_selection(self, new_time, selection):
def restore_selection(selection):
@@ -661,7 +661,7 @@ class PyTimePickerController(object):
return
if part == self.hour_part:
self.py_time_picker.SetSelection(0, self._separator_pos())
- else:
+ else:
time_string_len = len(self.py_time_picker.get_time_string())
self.py_time_picker.SetSelection(self._separator_pos() + 1, time_string_len)
self.preferred_part = part
diff --git a/timelinelib/wxgui/components/timelineview.py b/timelinelib/wxgui/components/timelineview.py
index a928884..7879921 100644
--- a/timelinelib/wxgui/components/timelineview.py
+++ b/timelinelib/wxgui/components/timelineview.py
@@ -20,14 +20,20 @@ import wx
from timelinelib.drawing import get_drawer
from timelinelib.view.drawingarea import DrawingArea
+from timelinelib.wxgui.dialogs.duplicateevent import open_duplicate_event_dialog_for_event
+from timelinelib.wxgui.dialogs.eventeditor import open_create_event_editor
+from timelinelib.wxgui.dialogs.eventeditor import open_event_editor_for
from timelinelib.wxgui.utils import _ask_question
class DrawingAreaPanel(wx.Panel):
def __init__(self, parent, status_bar_adapter, divider_line_slider,
- fn_handle_db_error, config):
+ fn_handle_db_error, config, main_frame):
wx.Panel.__init__(self, parent, style=wx.NO_BORDER)
+ self.fn_handle_db_error = fn_handle_db_error
+ self.config = config
+ self.main_frame = main_frame
self.controller = DrawingArea(
self, status_bar_adapter, config, get_drawer(),
divider_line_slider, fn_handle_db_error)
@@ -79,16 +85,31 @@ class DrawingAreaPanel(wx.Panel):
self.Update()
def enable_disable_menus(self):
- wx.GetTopLevelParent(self).enable_disable_menus()
-
- def edit_event(self, event):
- wx.GetTopLevelParent(self).edit_event(event)
-
- def duplicate_event(self, event):
- wx.GetTopLevelParent(self).duplicate_event(event)
-
- def create_new_event(self, start_time, end_time):
- wx.GetTopLevelParent(self).create_new_event(start_time, end_time)
+ self.main_frame.enable_disable_menus()
+
+ def open_event_editor_for(self, event):
+ open_event_editor_for(
+ self,
+ self.config,
+ self.controller.get_timeline(),
+ self.fn_handle_db_error,
+ event)
+
+ def open_duplicate_event_dialog_for_event(self, event):
+ open_duplicate_event_dialog_for_event(
+ self,
+ self.controller.get_timeline(),
+ self.fn_handle_db_error,
+ event)
+
+ def open_create_event_editor(self, start_time, end_time):
+ open_create_event_editor(
+ self,
+ self.config,
+ self.controller.get_timeline(),
+ self.fn_handle_db_error,
+ start_time,
+ end_time)
def start_balloon_show_timer(self, milliseconds=-1, oneShot=False):
self.balloon_show_timer.Start(milliseconds, oneShot)
@@ -164,7 +185,7 @@ class DrawingAreaPanel(wx.Panel):
self.controller.window_resized()
def _on_left_down(self, evt):
- self.controller.left_mouse_down(evt.m_x, evt.m_y, evt.m_controlDown,
+ self.controller.left_mouse_down(evt.m_x, evt.m_y, evt.m_controlDown,
evt.m_shiftDown, evt.m_altDown)
evt.Skip()
@@ -172,7 +193,7 @@ class DrawingAreaPanel(wx.Panel):
self.controller.right_mouse_down(evt.m_x, evt.m_y, evt.m_altDown)
def _on_left_dclick(self, evt):
- self.controller.left_mouse_dclick(evt.m_x, evt.m_y, evt.m_controlDown,
+ self.controller.left_mouse_dclick(evt.m_x, evt.m_y, evt.m_controlDown,
evt.m_altDown)
def _on_middle_up(self, evt):
@@ -188,7 +209,7 @@ class DrawingAreaPanel(wx.Panel):
self.controller.mouse_moved(evt.m_x, evt.m_y, evt.m_altDown)
def _on_mousewheel(self, evt):
- self.controller.mouse_wheel_moved(evt.m_wheelRotation, evt.ControlDown(), evt.ShiftDown())
+ self.controller.mouse_wheel_moved(evt.m_wheelRotation, evt.ControlDown(), evt.ShiftDown(), evt.GetX())
def _on_key_down(self, evt):
self.controller.key_down(evt.GetKeyCode(), evt.AltDown())
diff --git a/timelinelib/wxgui/components/wxdatetimepicker.py b/timelinelib/wxgui/components/wxdatetimepicker.py
index 1d3e541..1267701 100644
--- a/timelinelib/wxgui/components/wxdatetimepicker.py
+++ b/timelinelib/wxgui/components/wxdatetimepicker.py
@@ -72,7 +72,7 @@ class WxDateTimePicker(wx.Panel):
self._position_calendar_popup(evt)
self.calendar_popup.Popup()
except:
- _display_error_message(_("Invalid date"))
+ _display_error_message(_("Invalid date"))
def _create_calendar_popup(self):
wx_date = self.controller.get_value()
@@ -128,14 +128,14 @@ class CalendarPopup(wx.PopupTransientWindow):
wx.PopupTransientWindow.__init__(self, parent, style=wx.BORDER_NONE)
border = 2
style = self._get_cal_style()
- self.cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
+ self.cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
pos=(border,border), style=style)
self._set_cal_range()
self._set_size(border)
self._bind_events()
def _get_cal_style(self):
- style = (wx.calendar.CAL_SHOW_HOLIDAYS |
+ style = (wx.calendar.CAL_SHOW_HOLIDAYS |
wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
if self.config.week_start == "monday":
style |= wx.calendar.CAL_MONDAY_FIRST
@@ -183,7 +183,7 @@ class CalendarPopupController(object):
# month or day. The control is closed on a double-click on a day or
# a single click outside of the control
if self.repop and not self.repoped:
- self.calendar_popup.Popup()
+ self.calendar_popup.Popup()
self.repoped = True
@@ -212,7 +212,7 @@ class WxDatePicker(wx.TextCtrl):
self.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus)
self.Bind(wx.EVT_CHAR, self._on_char)
self.Bind(wx.EVT_TEXT, self._on_text)
- self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
+ self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
def _on_set_focus(self, evt):
# CallAfter is a trick to prevent default behavior of selecting all
@@ -244,7 +244,7 @@ class WxDatePicker(wx.TextCtrl):
elif evt.GetKeyCode() == wx.WXK_DOWN:
self.controller.on_down()
else:
- evt.Skip()
+ evt.Skip()
def _resize_to_fit_text(self):
w, h = self.GetTextExtent("0000-00-00")
@@ -287,7 +287,7 @@ class WxDatePickerController(object):
self.wx_date_picker.SetSelection(start, end)
else:
self._select_region_if_possible(self.region_year)
- self.last_selection = self.wx_date_picker.GetSelection()
+ self.last_selection = self.wx_date_picker.GetSelection()
def on_kill_focus(self):
if self.last_selection:
@@ -314,8 +314,8 @@ class WxDatePickerController(object):
# To prevent saving of preferred day when year or month is changed
# in on_up() and on_down()...
# Save preferred day only when text is entered in the date text
- # control and not when up or down keys has been used.
- # When up and down keys are used, the preferred day is saved in
+ # control and not when up or down keys has been used.
+ # When up and down keys are used, the preferred day is saved in
# on_up() and on_down() only when day is changed.
if self.save_preferred_day:
self._save_preferred_day(current_date)
@@ -330,7 +330,7 @@ class WxDatePickerController(object):
else:
new_date = self._increment_day()
self._save_preferred_day(new_date)
- self._set_new_date_and_restore_selection(new_date)
+ self._set_new_date_and_restore_selection(new_date)
def on_down(self):
if not self._current_date_is_valid():
@@ -342,7 +342,7 @@ class WxDatePickerController(object):
else:
new_date = self._decrement_day()
self._save_preferred_day(new_date)
- self._set_new_date_and_restore_selection(new_date)
+ self._set_new_date_and_restore_selection(new_date)
def _increment_year(self):
date = self.get_date()
@@ -354,7 +354,7 @@ class WxDatePickerController(object):
date = self.get_date()
if date.Month < 11:
date = self._set_valid_day(date.Year, date.Month + 1, date.Day)
- elif date.Year < WxTimeType().get_max_time()[0].Year - 1:
+ elif date.Year < WxTimeType().get_max_time()[0].Year - 1:
date = self._set_valid_day(date.Year + 1, 0, date.Day)
return date
@@ -374,7 +374,7 @@ class WxDatePickerController(object):
date = self.get_date()
if date.Month > 0:
date = self._set_valid_day(date.Year, date.Month - 1, date.Day)
- elif date.Year > WxTimeType().get_min_time()[0].Year:
+ elif date.Year > WxTimeType().get_min_time()[0].Year:
date = self._set_valid_day(date.Year - 1, 11, date.Day)
return date
@@ -399,10 +399,10 @@ class WxDatePickerController(object):
def _parse_year_month_day(self):
date_string = self.wx_date_picker.get_date_string()
- date_bc = False
+ date_bc = False
if (date_string[0:1] == self.separator):
date_string = date_string[1:]
- date_bc = True
+ date_bc = True
components = date_string.split(self.separator)
if len(components) != 3:
raise ValueError()
@@ -424,7 +424,7 @@ class WxDatePickerController(object):
selection = self.wx_date_picker.GetSelection()
self.save_preferred_day = False
if self.preferred_day != None:
- new_date = self._set_valid_day(new_date.Year, new_date.Month,
+ new_date = self._set_valid_day(new_date.Year, new_date.Month,
self.preferred_day)
self.set_date(new_date)
restore_selection(selection)
@@ -434,7 +434,7 @@ class WxDatePickerController(object):
while True:
try:
return try_to_create_wx_date_time_from_dmy(new_day, new_month, new_year)
- except ValueError:
+ except ValueError:
new_day -= 1
def _save_preferred_day(self, date):
@@ -462,16 +462,16 @@ class WxDatePickerController(object):
return self.wx_date_picker.GetInsertionPoint() in region_range
def _get_region_range(self, n):
- # Returns a range of valid cursor positions for a valid region year,
+ # Returns a range of valid cursor positions for a valid region year,
# month or day.
def region_is_not_valid(region):
- return region not in (self.region_year, self.region_month,
+ return region not in (self.region_year, self.region_month,
self.region_day)
def date_has_exactly_two_seperators(datestring):
return len(datestring.split(self.separator)) == 3
def calculate_pos_range(region, datestring):
pos_of_separator1 = datestring.find(self.separator)
- pos_of_separator2 = datestring.find(self.separator,
+ pos_of_separator2 = datestring.find(self.separator,
pos_of_separator1 + 1)
if region == self.region_year:
return range(0, pos_of_separator1 + 1)
@@ -513,7 +513,7 @@ class WxTimePicker(wx.TextCtrl):
self.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus)
self.Bind(wx.EVT_CHAR, self._on_char)
self.Bind(wx.EVT_TEXT, self._on_text)
- self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
+ self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
def _on_set_focus(self, evt):
# CallAfter is a trick to prevent default behavior of selecting all
@@ -545,7 +545,7 @@ class WxTimePicker(wx.TextCtrl):
elif evt.GetKeyCode() == wx.WXK_DOWN:
self.controller.on_down()
else:
- evt.Skip()
+ evt.Skip()
def _resize_to_fit_text(self):
w, h = self.GetTextExtent("00:00")
@@ -616,7 +616,7 @@ class WxTimePickerController(object):
new_time = self._increment_hour()
else:
new_time = self._increment_minutes()
- self._set_new_time_and_restore_selection(new_time)
+ self._set_new_time_and_restore_selection(new_time)
def on_down(self):
if not self._time_is_valid():
@@ -625,7 +625,7 @@ class WxTimePickerController(object):
new_time = self._decrement_hour()
else:
new_time = self._decrement_minutes()
- self._set_new_time_and_restore_selection(new_time)
+ self._set_new_time_and_restore_selection(new_time)
def _increment_hour(self):
time = self.get_time()
@@ -639,13 +639,13 @@ class WxTimePickerController(object):
time = self.get_time()
new_hour = time.Hour
new_minute = time.Minute + 1
- if new_minute > 59:
+ if new_minute > 59:
new_minute = 0
new_hour = time.Hour + 1
if new_hour > 23:
new_hour = 0
time.SetHour(new_hour)
- time.SetMinute(new_minute)
+ time.SetMinute(new_minute)
return time
def _decrement_hour(self):
@@ -660,7 +660,7 @@ class WxTimePickerController(object):
time = self.get_time()
new_hour = time.Hour
new_minute = time.Minute - 1
- if new_minute < 0:
+ if new_minute < 0:
new_minute = 59
new_hour = time.Hour - 1
if new_hour < 0:
@@ -688,7 +688,7 @@ class WxTimePickerController(object):
return
if part == self.hour_part:
self.wx_time_picker.SetSelection(0, self._separator_pos())
- else:
+ else:
time_string_len = len(self.wx_time_picker.get_time_string())
self.wx_time_picker.SetSelection(self._separator_pos() + 1, time_string_len)
self.preferred_part = part
diff --git a/timelinelib/wxgui/dialogs/categorieseditor.py b/timelinelib/wxgui/dialogs/categorieseditor.py
index af8980a..46a02d7 100644
--- a/timelinelib/wxgui/dialogs/categorieseditor.py
+++ b/timelinelib/wxgui/dialogs/categorieseditor.py
@@ -84,7 +84,7 @@ class CategoriesEditor(wx.Dialog):
def _btn_edit_on_click(self, e):
selected_category = self.cat_tree.get_selected_category()
if selected_category is not None:
- edit_category(self, self.db, selected_category,
+ edit_category(self, self.db, selected_category,
self.db_error_handler)
self._updateButtons()
@@ -108,7 +108,7 @@ class CategoriesEditor(wx.Dialog):
def _btn_del_on_click(self, e):
selected_category = self.cat_tree.get_selected_category()
if selected_category is not None:
- delete_category(self, self.db, selected_category,
+ delete_category(self, self.db, selected_category,
self.db_error_handler)
self._updateButtons()
diff --git a/timelinelib/wxgui/dialogs/containereditor.py b/timelinelib/wxgui/dialogs/containereditor.py
index b78f58e..144604c 100644
--- a/timelinelib/wxgui/dialogs/containereditor.py
+++ b/timelinelib/wxgui/dialogs/containereditor.py
@@ -69,7 +69,7 @@ class StaticContainerEditorDialog(wx.Dialog):
def _create_buttons(self, properties_box):
button_box = self.CreateStdDialogButtonSizer(wx.OK|wx.CANCEL)
properties_box.Add(button_box, flag=wx.EXPAND|wx.ALL, border=BORDER)
-
+
class ContainerEditorControllerApi(object):
@@ -79,19 +79,19 @@ class ContainerEditorControllerApi(object):
def set_name(self, name):
self.txt_name.SetValue(name)
-
+
def get_name(self):
return self.txt_name.GetValue().strip()
def set_category(self, category):
self.lst_category.select(category)
-
+
def get_category(self):
return self.lst_category.get()
-
+
def display_invalid_name(self, message):
_display_error_message(message, self)
- _set_focus_and_select(self.txt_name)
+ _set_focus_and_select(self.txt_name)
def display_db_exception(self, e):
gui_utils.handle_db_error_in_dialog(self, e)
@@ -102,12 +102,12 @@ class ContainerEditorControllerApi(object):
def _bind_events(self):
self.Bind(wx.EVT_BUTTON, self._btn_ok_on_click, id=wx.ID_OK)
self.Bind(wx.EVT_CHOICE, self.lst_category.on_choice, self.lst_category)
-
+
def _btn_ok_on_click(self, evt):
- self.controller.save()
-
-
-class ContainerEditorDialog(StaticContainerEditorDialog,
+ self.controller.save()
+
+
+class ContainerEditorDialog(StaticContainerEditorDialog,
ContainerEditorControllerApi):
"""
This dialog is used for two purposes, editing an existing container
diff --git a/timelinelib/wxgui/dialogs/duplicateevent.py b/timelinelib/wxgui/dialogs/duplicateevent.py
index fd5d11d..2480b00 100644
--- a/timelinelib/wxgui/dialogs/duplicateevent.py
+++ b/timelinelib/wxgui/dialogs/duplicateevent.py
@@ -18,10 +18,10 @@
import wx
+from timelinelib.editors.duplicateevent import DuplicateEventEditor
from timelinelib.wxgui.utils import BORDER
-from timelinelib.wxgui.utils import _set_focus_and_select
from timelinelib.wxgui.utils import _display_error_message
-from timelinelib.editors.duplicateevent import DuplicateEventEditor
+from timelinelib.wxgui.utils import _set_focus_and_select
import timelinelib.wxgui.utils as gui_utils
@@ -37,13 +37,13 @@ class DuplicateEventDialog(wx.Dialog):
self.sc_count.SetValue(count)
def get_count(self):
- return self.sc_count.GetValue()
+ return self.sc_count.GetValue()
def set_frequency(self, count):
self.sc_frequency.SetValue(count)
def get_frequency(self):
- return self.sc_frequency.GetValue()
+ return self.sc_frequency.GetValue()
def select_move_period_fn_at_index(self, index):
self.rb_period.SetSelection(index)
@@ -52,10 +52,10 @@ class DuplicateEventDialog(wx.Dialog):
return self._move_period_fns[self.rb_period.GetSelection()]
def set_direction(self, direction):
- self.rb_direction.SetSelection(direction)
+ self.rb_direction.SetSelection(direction)
def get_direction(self):
- return self.rb_direction.GetSelection()
+ return self.rb_direction.GetSelection()
def close(self):
self.EndModal(wx.ID_OK)
@@ -65,7 +65,7 @@ class DuplicateEventDialog(wx.Dialog):
def handle_date_errors(self, error_count):
_display_error_message(
- _("%d Events not duplicated due to missing dates.")
+ _("%d Events not duplicated due to missing dates.")
% error_count)
def _create_gui(self, move_period_config):
@@ -95,9 +95,9 @@ class DuplicateEventDialog(wx.Dialog):
return hbox
def _create_and_add_rb_period(self, form, period_list):
- self.rb_period = wx.RadioBox(self, wx.ID_ANY, _("Period"),
- wx.DefaultPosition, wx.DefaultSize,
- period_list)
+ self.rb_period = wx.RadioBox(self, wx.ID_ANY, _("Period"),
+ wx.DefaultPosition, wx.DefaultSize,
+ period_list)
form.Add(self.rb_period, flag=wx.ALL|wx.EXPAND, border=BORDER)
def _create_and_add_sc_frequency_box(self, form):
@@ -116,7 +116,7 @@ class DuplicateEventDialog(wx.Dialog):
def _create_and_add_rb_direction(self, form):
direction_list = [_("Forward"), _("Backward"), _("Both")]
- self.rb_direction = wx.RadioBox(self, wx.ID_ANY, _("Direction"),
+ self.rb_direction = wx.RadioBox(self, wx.ID_ANY, _("Direction"),
choices=direction_list)
form.Add(self.rb_direction, flag=wx.ALL|wx.EXPAND, border=BORDER)
@@ -129,3 +129,9 @@ class DuplicateEventDialog(wx.Dialog):
gui_utils.set_wait_cursor(self)
self.controller.create_duplicates_and_save()
gui_utils.set_default_cursor(self)
+
+
+def open_duplicate_event_dialog_for_event(parent, db, handle_db_error, event):
+ def create_dialog():
+ return DuplicateEventDialog(parent, db, event)
+ gui_utils.show_modal(create_dialog, handle_db_error)
diff --git a/timelinelib/wxgui/dialogs/eventeditor.py b/timelinelib/wxgui/dialogs/eventeditor.py
index 37cc9a0..d2b2a53 100644
--- a/timelinelib/wxgui/dialogs/eventeditor.py
+++ b/timelinelib/wxgui/dialogs/eventeditor.py
@@ -20,16 +20,16 @@ import os.path
import wx
-from timelinelib.db.interface import TimelineIOError
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.editors.event import EventEditor
-from timelinelib.wxgui.dialogs.containereditor import ContainerEditorDialog
+from timelinelib.repositories.dbwrapper import DbWrapperEventRepository
from timelinelib.wxgui.components.categorychoice import CategoryChoice
+from timelinelib.wxgui.dialogs.containereditor import ContainerEditorDialog
from timelinelib.wxgui.utils import BORDER
from timelinelib.wxgui.utils import _display_error_message
from timelinelib.wxgui.utils import _set_focus_and_select
from timelinelib.wxgui.utils import time_picker_for
import timelinelib.wxgui.utils as gui_utils
-from timelinelib.repositories.dbwrapper import DbWrapperEventRepository
class EventEditorDialog(wx.Dialog):
@@ -63,12 +63,12 @@ class EventEditorDialog(wx.Dialog):
main_box_content = wx.StaticBoxSizer(groupbox, wx.VERTICAL)
self._create_detail_content(main_box_content)
self._create_notebook_content(main_box_content)
- sizer.Add(main_box_content, flag=wx.EXPAND|wx.ALL,
+ sizer.Add(main_box_content, flag=wx.EXPAND|wx.ALL,
border=BORDER, proportion=1)
def _create_detail_content(self, properties_box_content):
details = self._create_details()
- properties_box_content.Add(details, flag=wx.ALL|wx.EXPAND,
+ properties_box_content.Add(details, flag=wx.ALL|wx.EXPAND,
border=BORDER)
def _create_details(self):
@@ -79,7 +79,7 @@ class EventEditorDialog(wx.Dialog):
self._create_text_field(grid)
self._create_categories_listbox(grid)
self._create_container_listbox(grid)
- return grid
+ return grid
def _create_time_details(self, grid):
grid.Add(wx.StaticText(self, label=_("When:")),
@@ -123,7 +123,7 @@ class EventEditorDialog(wx.Dialog):
label = wx.StaticText(self, label=_("Container:"))
grid.Add(label, flag=wx.ALIGN_CENTER_VERTICAL)
grid.Add(self.lst_containers)
- self.Bind(wx.EVT_CHOICE, self._lst_containers_on_choice,
+ self.Bind(wx.EVT_CHOICE, self._lst_containers_on_choice,
self.lst_containers)
def _lst_containers_on_choice(self, e):
@@ -137,14 +137,15 @@ class EventEditorDialog(wx.Dialog):
else:
self.current_container_selection = new_selection_index
self._enable_disable_checkboxes()
-
+
def _enable_disable_checkboxes(self):
self._enable_disable_ends_today()
self._enable_disable_locked()
def _enable_disable_ends_today(self):
- enable = (self._container_not_selected() and
- not self.chb_locked.GetValue())
+ enable = (self._container_not_selected() and
+ not self.chb_locked.GetValue() and
+ self.controller.start_is_in_history())
self.chb_ends_today.Enable(enable)
def _enable_disable_locked(self):
@@ -154,7 +155,7 @@ class EventEditorDialog(wx.Dialog):
def _container_not_selected(self):
index = self.lst_containers.GetSelection()
return (index == 0)
-
+
def _add_container(self):
def create_container_editor():
return ContainerEditorDialog(self, _("Add Container"), self.timeline, None)
@@ -167,7 +168,7 @@ class EventEditorDialog(wx.Dialog):
gui_utils.show_modal(create_container_editor,
gui_utils.create_dialog_db_error_handler(self),
handle_success)
-
+
def _create_period_checkbox(self, box):
handler = self._chb_period_on_checkbox
return self._create_chb(box, _("Period"), handler)
@@ -220,7 +221,7 @@ class EventEditorDialog(wx.Dialog):
def _create_notebook_content(self, properties_box_content):
notebook = self._create_notebook()
- properties_box_content.Add(notebook, border=BORDER,
+ properties_box_content.Add(notebook, border=BORDER,
flag=wx.ALL|wx.EXPAND, proportion=1)
def _create_notebook(self):
@@ -242,7 +243,7 @@ class EventEditorDialog(wx.Dialog):
"alert" : (_("Alert"), AlertEditor),
"icon" : (_("Icon"), IconEditor) }
if editors.has_key(editor_class_id):
- return editors[editor_class_id]
+ return editors[editor_class_id]
else:
return None
@@ -293,7 +294,7 @@ class EventEditorDialog(wx.Dialog):
self.lst_containers.SetSelection(current_item_index)
selection_set = True
current_item_index += 1
-
+
self.last_real_container_index = current_item_index - 1
self.add_container_item_index = self.last_real_container_index + 2
self.edit_container_item_index = self.last_real_container_index + 3
@@ -343,19 +344,19 @@ class EventEditorDialog(wx.Dialog):
def set_name(self, name):
self.txt_text.SetValue(name)
-
+
def get_name(self):
return self.txt_text.GetValue().strip()
def set_category(self, category):
self.lst_category.select(category)
-
+
def get_category(self):
return self.lst_category.get()
def set_container(self, container):
self._fill_containers_listbox(container)
-
+
def get_container(self):
selection = self.lst_containers.GetSelection()
if selection != -1:
@@ -402,7 +403,7 @@ class EventEditorDialog(wx.Dialog):
def _display_invalid_input(self, message, control):
_display_error_message(message, self)
- _set_focus_and_select(control)
+ _set_focus_and_select(control)
def display_db_exception(self, e):
gui_utils.handle_db_error_in_dialog(self, e)
@@ -445,8 +446,8 @@ class IconEditor(wx.Panel):
self.MAX_SIZE = (128, 128)
# Controls
self.img_icon = wx.StaticBitmap(self, size=self.MAX_SIZE)
- label = _("Images will be scaled to fit inside a 128x128 box.")
- description = wx.StaticText(self, label=label)
+ label = _("Images will be scaled to fit inside a %ix%i box.")
+ description = wx.StaticText(self, label=label % self.MAX_SIZE)
btn_select = wx.Button(self, wx.ID_OPEN)
btn_clear = wx.Button(self, wx.ID_CLEAR)
self.Bind(wx.EVT_BUTTON, self._btn_select_on_click, btn_select)
@@ -517,7 +518,7 @@ class AlertEditor(wx.Panel):
self.editor = editor
self._create_gui()
self._initialize_data()
-
+
def _create_gui(self):
self._create_controls()
self._layout_controls()
@@ -526,7 +527,7 @@ class AlertEditor(wx.Panel):
self._set_initial_time()
self._set_initial_text()
self._set_visible(False)
-
+
def _set_initial_time(self):
if self.editor.event is not None:
self.dtp_start.set_value(self.editor.event.time_period.start_time)
@@ -535,12 +536,12 @@ class AlertEditor(wx.Panel):
def _set_initial_text(self):
self.text_data.SetValue("")
-
+
def _create_controls(self):
self.btn_add = self._create_add_button()
self.btn_clear = self._create_clear_button()
self.alert_panel = self._create_input_controls()
-
+
def _layout_controls(self):
self._layout_input_controls(self.alert_panel)
sizer = wx.GridBagSizer(5, 5)
@@ -548,7 +549,7 @@ class AlertEditor(wx.Panel):
sizer.Add(self.btn_clear, wx.GBPosition(0, 1), wx.GBSpan(1, 1))
sizer.Add(self.alert_panel, wx.GBPosition(1, 0), wx.GBSpan(4, 5))
self.SetSizerAndFit(sizer)
-
+
def _create_add_button(self):
btn_add = wx.Button(self, wx.ID_ADD)
self.Bind(wx.EVT_BUTTON, self._btn_add_on_click, btn_add)
@@ -558,7 +559,7 @@ class AlertEditor(wx.Panel):
btn_clear = wx.Button(self, wx.ID_CLEAR)
self.Bind(wx.EVT_BUTTON, self._btn_clear_on_click, btn_clear)
return btn_clear
-
+
def _create_input_controls(self):
alert_panel = wx.Panel(self)
time_type = self.editor.timeline.get_time_type()
@@ -583,7 +584,7 @@ class AlertEditor(wx.Panel):
return (time, text)
else:
return None
-
+
def set_data(self, data):
if data == None:
self._set_visible(False)
@@ -597,6 +598,9 @@ class AlertEditor(wx.Panel):
self._set_visible(True)
def _btn_clear_on_click(self, evt):
+ self.clear_data()
+
+ def clear_data(self):
self._set_initial_time()
self._set_initial_text()
self._set_visible(False)
@@ -607,3 +611,21 @@ class AlertEditor(wx.Panel):
self.btn_add.Enable(not value)
self.btn_clear.Enable(value)
self.GetSizer().Layout()
+
+
+def open_event_editor_for(parent, config, db, handle_db_error, event):
+ def create_event_editor():
+ if event.is_container():
+ title = _("Edit Container")
+ return ContainerEditorDialog(parent, title, db, event)
+ else:
+ return EventEditorDialog(
+ parent, config, _("Edit Event"), db, event=event)
+ gui_utils.show_modal(create_event_editor, handle_db_error)
+
+
+def open_create_event_editor(parent, config, db, handle_db_error, start=None, end=None):
+ def create_event_editor():
+ return EventEditorDialog(
+ parent, config, _("Create Event"), db, start, end)
+ gui_utils.show_modal(create_event_editor, handle_db_error)
diff --git a/timelinelib/wxgui/dialogs/mainframe.py b/timelinelib/wxgui/dialogs/mainframe.py
index 51eb64f..179ebeb 100644
--- a/timelinelib/wxgui/dialogs/mainframe.py
+++ b/timelinelib/wxgui/dialogs/mainframe.py
@@ -23,8 +23,8 @@ import wx
from timelinelib.application import TimelineApplication
from timelinelib.config.dotfile import read_config
from timelinelib.config.paths import ICONS_DIR
+from timelinelib.db.exceptions import TimelineIOError
from timelinelib.db import db_open
-from timelinelib.db.interface import TimelineIOError
from timelinelib.db.objects import TimePeriod
from timelinelib.export.bitmap import export_to_image
from timelinelib.meta.about import APPLICATION_NAME
@@ -36,9 +36,8 @@ from timelinelib.wxgui.components.hyperlinkbutton import HyperlinkButton
from timelinelib.wxgui.components.search import SearchBar
from timelinelib.wxgui.components.timelineview import DrawingAreaPanel
from timelinelib.wxgui.dialogs.categorieseditor import CategoriesEditor
-from timelinelib.wxgui.dialogs.containereditor import ContainerEditorDialog
-from timelinelib.wxgui.dialogs.duplicateevent import DuplicateEventDialog
-from timelinelib.wxgui.dialogs.eventeditor import EventEditorDialog
+from timelinelib.wxgui.dialogs.duplicateevent import open_duplicate_event_dialog_for_event
+from timelinelib.wxgui.dialogs.eventeditor import open_create_event_editor
from timelinelib.wxgui.dialogs.helpbrowser import HelpBrowser
from timelinelib.wxgui.dialogs.playframe import PlayFrame
from timelinelib.wxgui.dialogs.preferences import PreferencesDialog
@@ -49,7 +48,6 @@ from timelinelib.wxgui.utils import _display_error_message
from timelinelib.wxgui.utils import WildcardHelper
import timelinelib.printing as printing
import timelinelib.wxgui.utils as gui_utils
-from timelinelib.time.wxtime import WxTimeType
class MainFrame(wx.Frame):
@@ -57,7 +55,7 @@ class MainFrame(wx.Frame):
def __init__(self, application_arguments):
self.config = read_config(application_arguments.get_config_file_path())
- wx.Frame.__init__(self, None, size=self.config.get_window_size(),
+ wx.Frame.__init__(self, None, size=self.config.get_window_size(),
pos=self.config.get_window_pos(),
style=wx.DEFAULT_FRAME_STYLE, name="main_frame")
@@ -87,7 +85,7 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_TIMER, self._timer_tick, self.timer)
self.timer.Start(10000)
self.alert_dialog_open = False
-
+
def _set_initial_values_to_member_variables(self):
self.timeline = None
self.timeline_wildcard_helper = WildcardHelper(
@@ -112,7 +110,7 @@ class MainFrame(wx.Frame):
self.status_bar_adapter = StatusBarAdapter(self.GetStatusBar())
def _create_main_panel(self):
- self.main_panel = MainPanel(self, self.config)
+ self.main_panel = MainPanel(self, self.config, self)
def _create_main_menu_bar(self):
main_menu_bar = wx.MenuBar()
@@ -303,7 +301,6 @@ class MainFrame(wx.Frame):
def _mnu_file_exit_on_click(self, evt):
self.Close()
- exit()
def _create_edit_menu(self, main_menu_bar):
edit_menu = wx.Menu()
@@ -387,14 +384,8 @@ class MainFrame(wx.Frame):
self.menu_controller.add_menu_requiring_writable_timeline(create_event_item)
def _mnu_timeline_create_event_on_click(self, evt):
- self.create_new_event()
-
- def create_new_event(self, start=None, end=None):
- def create_event_editor():
- return EventEditorDialog(
- self, self.config, _("Create Event"), self.timeline,
- start, end)
- gui_utils.show_modal(create_event_editor, self.handle_db_error)
+ open_create_event_editor(
+ self, self.config, self.timeline, self.handle_db_error)
def _create_timeline_duplicate_event_menu_item(self, timeline_menu):
self.mnu_timeline_duplicate_event = timeline_menu.Append(
@@ -404,31 +395,27 @@ class MainFrame(wx.Frame):
self.menu_controller.add_menu_requiring_writable_timeline(self.mnu_timeline_duplicate_event)
def _mnu_timeline_duplicate_event_on_click(self, evt):
- self.duplicate_event()
+ try:
+ drawing_area = self.main_panel.drawing_area
+ id = drawing_area.get_view_properties().get_selected_event_ids()[0]
+ event = self.timeline.find_event_with_id(id)
+ except IndexError, e:
+ # No event selected so do nothing!
+ return
+ open_duplicate_event_dialog_for_event(
+ self,
+ self.timeline,
+ self.handle_db_error,
+ event)
def _create_timeline_measure_distance_between_events_menu_item(self, timeline_menu):
self.mnu_timeline_measure_distance_between_events = timeline_menu.Append(
- wx.ID_ANY, _("&Measure Distance between two Events..."),
+ wx.ID_ANY, _("&Measure Distance between two Events..."),
_("Measure the Distance between two Events"))
self.Bind(wx.EVT_MENU, self._mnu_timeline_measure_distance_between_events_on_click,
self.mnu_timeline_measure_distance_between_events)
self.menu_controller.add_menu_requiring_writable_timeline(self.mnu_timeline_measure_distance_between_events)
- def duplicate_event(self, event=None):
- def show_dialog(event):
- def create_dialog():
- return DuplicateEventDialog(self, self.timeline, event)
- gui_utils.show_modal(create_dialog, self.handle_db_error)
- if event is None:
- try:
- drawing_area = self.main_panel.drawing_area
- id = drawing_area.get_view_properties().get_selected_event_ids()[0]
- event = self.timeline.find_event_with_id(id)
- except IndexError, e:
- # No event selected so do nothing!
- return
- show_dialog(event)
-
def _mnu_timeline_measure_distance_between_events_on_click(self, evt):
self._measure_distance_between_events()
@@ -445,12 +432,12 @@ class MainFrame(wx.Frame):
event2 = self.timeline.find_event_with_id(event_id_2)
return event1, event2
- def _calc_events_distance(self,event1, event2):
+ def _calc_events_distance(self,event1, event2):
if event1.time_period.start_time <= event2.time_period.start_time:
- distance = (event2.time_period.start_time -
+ distance = (event2.time_period.start_time -
event1.time_period.end_time)
- else:
- distance = (event1.time_period.start_time -
+ else:
+ distance = (event1.time_period.start_time -
event2.time_period.end_time)
return distance
@@ -462,7 +449,7 @@ class MainFrame(wx.Frame):
self._display_text(header, distance_text)
def _display_text(self, header, text):
- dialog = wx.MessageDialog(self, text, header,
+ dialog = wx.MessageDialog(self, text, header,
wx.OK | wx.ICON_INFORMATION)
dialog.ShowModal()
dialog.Destroy()
@@ -504,7 +491,7 @@ class MainFrame(wx.Frame):
if event:
start = event.time_period.start_time
delta = self.main_panel.drawing_area.get_view_properties().displayed_period.delta()
- end = start + delta
+ end = start + delta
margin_delta = self.timeline.get_time_type().margin_delta(delta)
self._navigate_timeline(lambda tp: tp.update(start, end, -margin_delta))
@@ -630,18 +617,6 @@ class MainFrame(wx.Frame):
self.controller.open_timeline(input_file)
self._update_navigation_menu_items()
- def edit_event(self, event):
- def create_event_editor():
- if event.is_container():
- parent = self
- title = _("Edit Container")
- timeline = self.timeline
- return ContainerEditorDialog(parent, title, timeline, event)
- else:
- return EventEditorDialog(self, self.config, _("Edit Event"),
- self.timeline, event=event)
- gui_utils.show_modal(create_event_editor, self.handle_db_error)
-
def handle_db_error(self, error):
_display_error_message(ex_msg(error), self)
self._switch_to_error_view(error)
@@ -738,7 +713,7 @@ class MainFrame(wx.Frame):
_display_error_message(_("File '%s' does not exist.") % path, self)
def enable_disable_menus(self):
- self.menu_controller.enable_disable_menus(self.main_panel.timeline_panel_visible())
+ self.menu_controller.enable_disable_menus(self.main_panel.timeline_panel_visible())
self._enable_disable_duplicate_event_menu()
self._enable_disable_measure_distance_between_two_events_menu()
self._enable_disable_searchbar()
@@ -753,7 +728,7 @@ class MainFrame(wx.Frame):
two_events_selected = len(view_properties.selected_event_ids) == 2
self.mnu_timeline_measure_distance_between_events.Enable(two_events_selected)
- def _enable_disable_searchbar(self):
+ def _enable_disable_searchbar(self):
if self.timeline == None:
self.main_panel.show_searchbar(False)
@@ -792,7 +767,7 @@ class MainFrame(wx.Frame):
def _timer_tick(self, evt):
self._handle_event_alerts()
-
+
def _handle_event_alerts(self):
if self.timeline is None:
return
@@ -804,44 +779,43 @@ class MainFrame(wx.Frame):
def _display_events_alerts(self):
self.alert_dialog_open = True
all_events = self.timeline.get_all_events()
- AlertController().display_events_alerts(all_events, self.timeline.time_type)
+ AlertController().display_events_alerts(all_events, self.timeline.get_time_type())
class AlertController(object):
-
+
def display_events_alerts(self, all_events, time_type):
self.time_type = time_type
for event in all_events:
alert = event.get_data("alert")
if alert is not None:
- alert_time = self._alert_time_as_text(alert)
- if self._time_has_expired(alert_time):
+ if self._time_has_expired(alert[0]):
self._display_and_delete_event_alert(event, alert)
def _display_and_delete_event_alert(self, event, alert):
self._display_alert_dialog(alert, event)
event.set_data("alert", None)
-
+
def _alert_time_as_text(self, alert):
return "%s" % alert[0]
-
- def _time_has_expired(self, time_as_text):
- now_as_text = "%s" % self.time_type.now()
- return time_as_text <= now_as_text
-
+
+ def _time_has_expired(self, time):
+ return time <= self.time_type.now()
+
+
def _display_alert_dialog(self, alert, event):
text = self._format_alert_text(alert, event)
dialog = TextDisplayDialog("Alert", text)
dialog.ShowModal()
dialog.Destroy()
-
- def _format_alert_text(self, alert, event):
+
+ def _format_alert_text(self, alert, event):
text1 = "Trigger time: %s\n\n" % alert[0]
text2 = "Event: %s\n\n" % event.get_label()
text = "%s%s%s" % (text1, text2, alert[1])
return text
-
+
class MenuController(object):
def __init__(self):
@@ -899,9 +873,10 @@ class MainPanel(wx.Panel):
Also displays the search bar.
"""
- def __init__(self, parent, config):
+ def __init__(self, parent, config, main_frame):
wx.Panel.__init__(self, parent)
self.config = config
+ self.main_frame = main_frame
self._create_gui()
# Install variables for backwards compatibility
self.cattree = self.timeline_panel.sidebar.cattree
@@ -935,9 +910,11 @@ class MainPanel(wx.Panel):
self.searchbar = SearchBar(self, search_close)
self.searchbar.Show(False)
# Panels
- self.welcome_panel = WelcomePanel(self)
- self.timeline_panel = TimelinePanel(self, self.config)
- self.error_panel = ErrorPanel(self)
+ self.welcome_panel = WelcomePanel(self, self.main_frame)
+ self.timeline_panel = TimelinePanel(
+ self, self.config, self.main_frame.handle_db_error,
+ self.main_frame.status_bar_adapter, self.main_frame)
+ self.error_panel = ErrorPanel(self, self.main_frame)
# Layout
self.sizerOuter = wx.BoxSizer(wx.VERTICAL)
self.sizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -961,8 +938,9 @@ class MainPanel(wx.Panel):
class WelcomePanel(wx.Panel):
- def __init__(self, parent):
+ def __init__(self, parent, main_frame):
wx.Panel.__init__(self, parent)
+ self.main_frame = main_frame
self._create_gui()
def _create_gui(self):
@@ -985,7 +963,7 @@ class WelcomePanel(wx.Panel):
self.SetSizer(hsizer)
def _btn_tutorial_on_click(self, e):
- wx.GetTopLevelParent(self).open_timeline(":tutorial:")
+ self.main_frame.open_timeline(":tutorial:")
def activated(self):
pass
@@ -993,9 +971,13 @@ class WelcomePanel(wx.Panel):
class TimelinePanel(wx.Panel):
- def __init__(self, parent, config):
+ def __init__(self, parent, config, handle_db_error, status_bar_adapter,
+ main_frame):
wx.Panel.__init__(self, parent)
self.config = config
+ self.handle_db_error = handle_db_error
+ self.status_bar_adapter = status_bar_adapter
+ self.main_frame = main_frame
self.sidebar_width = self.config.get_sidebar_width()
self._create_gui()
@@ -1023,16 +1005,16 @@ class TimelinePanel(wx.Panel):
self.sidebar_width = self.splitter.GetSashPosition()
def _create_sidebar(self):
- self.sidebar = Sidebar(self.splitter)
+ self.sidebar = Sidebar(self.splitter, self.handle_db_error)
def _create_drawing_area(self):
- main_frame = wx.GetTopLevelParent(self)
self.drawing_area = DrawingAreaPanel(
self.splitter,
- main_frame.status_bar_adapter,
+ self.status_bar_adapter,
self.divider_line_slider,
- main_frame.handle_db_error,
- self.config)
+ self.handle_db_error,
+ self.config,
+ self.main_frame)
def _layout_components(self):
sizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -1058,8 +1040,9 @@ class TimelinePanel(wx.Panel):
class ErrorPanel(wx.Panel):
- def __init__(self, parent):
+ def __init__(self, parent, main_frame):
wx.Panel.__init__(self, parent)
+ self.main_frame = main_frame
self._create_gui()
def populate(self, error):
@@ -1085,7 +1068,7 @@ class ErrorPanel(wx.Panel):
self.SetSizer(hsizer)
def _btn_contact_on_click(self, e):
- wx.GetTopLevelParent(self).help_browser.show_page("contact")
+ self.main_frame.help_browser.show_page("contact")
def activated(self):
pass
@@ -1098,13 +1081,12 @@ class Sidebar(wx.Panel):
Currently only shows the categories with visibility check boxes.
"""
- def __init__(self, parent):
+ def __init__(self, parent, handle_db_error):
wx.Panel.__init__(self, parent, style=wx.BORDER_NONE)
- self._create_gui()
+ self._create_gui(handle_db_error)
- def _create_gui(self):
- main_frame = wx.GetTopLevelParent(self)
- self.cattree = CategoriesTree(self, main_frame.handle_db_error)
+ def _create_gui(self, handle_db_error):
+ self.cattree = CategoriesTree(self, handle_db_error)
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.cattree, flag=wx.GROW, proportion=1)
diff --git a/timelinelib/wxgui/dialogs/preferences.py b/timelinelib/wxgui/dialogs/preferences.py
index cb2d280..25cdd9c 100644
--- a/timelinelib/wxgui/dialogs/preferences.py
+++ b/timelinelib/wxgui/dialogs/preferences.py
@@ -36,7 +36,7 @@ class PreferencesDialog(wx.Dialog):
def set_checkbox_use_inertial_scrolling(self, value):
self.chb_inertial_scrolling.SetValue(value)
- def set_checkbox_open_recent_at_startup(self, value):
+ def set_checkbox_open_recent_at_startup(self, value):
self.chb_open_recent.SetValue(value)
def set_week_start(self, index):
@@ -69,7 +69,7 @@ class PreferencesDialog(wx.Dialog):
return button_box
def _create_general_tab(self, notebook):
- panel = self._create_tab_panel(notebook, _("General"))
+ panel = self._create_tab_panel(notebook, _("General"))
controls = self._create_general_tab_controls(panel)
self._size_tab_panel(panel, controls)
@@ -79,7 +79,7 @@ class PreferencesDialog(wx.Dialog):
return (self.chb_open_recent, self.chb_inertial_scrolling)
def _create_date_time_tab(self, notebook):
- panel = self._create_tab_panel(notebook, _("Date && Time"))
+ panel = self._create_tab_panel(notebook, _("Date && Time"))
controls = self._create_date_time_tab_controls(panel)
self._size_tab_panel(panel, controls)
@@ -87,7 +87,7 @@ class PreferencesDialog(wx.Dialog):
self.chb_wide_date_range = self._create_chb_wide_date_range(panel)
self.choice_week = self._create_choice_week(panel)
grid = wx.FlexGridSizer(1, 2, BORDER, BORDER)
- grid.Add(wx.StaticText(panel, label=_("Week start on:")),
+ grid.Add(wx.StaticText(panel, label=_("Week start on:")),
flag=wx.ALIGN_CENTER_VERTICAL)
grid.Add(self.choice_week, flag=wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT)
warning = _("This feature is experimental. If events are\ncreated in the extended range, you can not\ndisable this option and successfully load\nthe timeline again. A reload of the timeline\nis also needed for this to take effect.")
@@ -110,19 +110,19 @@ class PreferencesDialog(wx.Dialog):
label = _("Open most recent timeline on startup")
handler = self._chb_open_recent_startup_on_checkbox
chb = self._create_chb(panel, label, handler)
- return chb
+ return chb
def _create_chb_inertial_scrolling(self, panel):
label = _("Use inertial scrolling")
handler = self._chb_use_inertial_scrolling_on_checkbox
- chb = self._create_chb(panel, label, handler)
+ chb = self._create_chb(panel, label, handler)
return chb
def _create_chb_wide_date_range(self, panel):
label = _("Use extended date range (before 1 AD)")
handler = self._chb_use_wide_date_range_on_checkbox
- chb = self._create_chb(panel, label, handler)
- return chb
+ chb = self._create_chb(panel, label, handler)
+ return chb
def _create_chb(self, panel, label, handler):
chb = wx.CheckBox(panel, label=label)
@@ -140,7 +140,7 @@ class PreferencesDialog(wx.Dialog):
btn_close.SetFocus()
self.SetAffirmativeId(wx.ID_CLOSE)
self.Bind(wx.EVT_BUTTON, self._btn_close_on_click, btn_close)
- return btn_close
+ return btn_close
def _chb_use_wide_date_range_on_checkbox(self, evt):
self._controller.on_use_wide_date_range_changed(evt.IsChecked())
diff --git a/timelinelib/wxgui/dialogs/textdisplay.py b/timelinelib/wxgui/dialogs/textdisplay.py
index cbc0713..4c13a95 100644
--- a/timelinelib/wxgui/dialogs/textdisplay.py
+++ b/timelinelib/wxgui/dialogs/textdisplay.py
@@ -16,45 +16,82 @@
# along with Timeline. If not, see <http://www.gnu.org/licenses/>.
-from timelinelib.wxgui.utils import BORDER
-
import wx
+from timelinelib.wxgui.utils import BORDER
+from timelinelib.wxgui.utils import _display_error_message
+from timelinelib.editors.textdisplay import TextDisplayEditor
-class TextDisplayDialog(wx.Dialog):
- def __init__(self, title, text, parent=None):
+class TextDisplayDialogGui(wx.Dialog):
+
+ def __init__(self, title, parent=None):
wx.Dialog.__init__(self, parent, title=title)
self._create_gui()
- self._text.SetValue(text)
def _create_gui(self):
- self._text = wx.TextCtrl(self, size=(660, 300), style=wx.TE_MULTILINE)
+ self._text = self._create_text_control()
+ button_box = self._create_button_box()
+ vbox = self._create_vbox(self._text, button_box)
+ self.SetSizerAndFit(vbox)
+
+ def _create_text_control(self):
+ return wx.TextCtrl(self, size=(660, 300), style=wx.TE_MULTILINE)
+
+ def _create_button_box(self):
+ self.btn_copy = self._create_copy_btn()
+ self.btn_close = self._create_close_btn()
+ button_box = wx.BoxSizer(wx.HORIZONTAL)
+ button_box.Add(self.btn_copy, flag=wx.RIGHT, border=BORDER)
+ button_box.AddStretchSpacer()
+ button_box.Add(self.btn_close, flag=wx.LEFT, border=BORDER)
+ return button_box
+
+ def _create_vbox(self, text, btn_box):
+ vbox = wx.BoxSizer(wx.VERTICAL)
+ vbox.Add(text, flag=wx.ALL|wx.EXPAND, border=BORDER)
+ vbox.Add(btn_box, flag=wx.ALL|wx.EXPAND, border=BORDER)
+ return vbox
+
+ def _create_copy_btn(self):
btn_copy = wx.Button(self, wx.ID_COPY)
- self.Bind(wx.EVT_BUTTON, self._btn_copy_on_click, btn_copy)
+ return btn_copy
+
+ def _create_close_btn(self):
btn_close = wx.Button(self, wx.ID_CLOSE)
btn_close.SetDefault()
btn_close.SetFocus()
self.SetAffirmativeId(wx.ID_CLOSE)
- self.Bind(wx.EVT_BUTTON, self._btn_close_on_click, btn_close)
- # Layout
- vbox = wx.BoxSizer(wx.VERTICAL)
- vbox.Add(self._text, flag=wx.ALL|wx.EXPAND, border=BORDER)
- button_box = wx.BoxSizer(wx.HORIZONTAL)
- button_box.Add(btn_copy, flag=wx.RIGHT, border=BORDER)
- button_box.AddStretchSpacer()
- button_box.Add(btn_close, flag=wx.LEFT, border=BORDER)
- vbox.Add(button_box, flag=wx.ALL|wx.EXPAND, border=BORDER)
- self.SetSizerAndFit(vbox)
+ return btn_close
+
+
+class TextDisplayDialog(TextDisplayDialogGui):
+
+ def __init__(self, title, text, parent=None):
+ TextDisplayDialogGui.__init__(self, title, parent)
+ self._bind_events()
+ self.controller = TextDisplayEditor(self, text)
+ self.controller.initialize()
+
+ def set_text(self, text):
+ self._text.SetValue(text)
+
+ def get_text(self):
+ return self._text.GetValue()
+
+ def _bind_events(self):
+ self.Bind(wx.EVT_BUTTON, self._btn_copy_on_click, self.btn_copy)
+ self.Bind(wx.EVT_BUTTON, self._btn_close_on_click, self.btn_close)
def _btn_copy_on_click(self, evt):
if wx.TheClipboard.Open():
- obj = wx.TextDataObject(self._text.GetValue())
- wx.TheClipboard.SetData(obj)
- wx.TheClipboard.Close()
+ self._copy_text_to_clipboard()
else:
- msg = _("Unable to copy to clipboard.")
- _display_error_message(msg)
+ _display_error_message(_("Unable to copy to clipboard."))
+ def _copy_text_to_clipboard(self):
+ obj = wx.TextDataObject(self.controller.get_text())
+ wx.TheClipboard.SetData(obj)
+ wx.TheClipboard.Close()
def _btn_close_on_click(self, evt):
self.Close()
diff --git a/timelinelib/wxgui/utils.py b/timelinelib/wxgui/utils.py
index 0038e02..b7fc101 100644
--- a/timelinelib/wxgui/utils.py
+++ b/timelinelib/wxgui/utils.py
@@ -18,8 +18,8 @@
import wx
-from timelinelib.db.interface import TimelineIOError
-from timelinelib.domain.category import sort_categories
+from timelinelib.db.exceptions import TimelineIOError
+from timelinelib.db.objects.category import sort_categories
# Border, in pixels, between controls in a window (should always be used when
@@ -146,7 +146,7 @@ def set_wait_cursor(parent):
def set_default_cursor(parent):
- parent.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
+ parent.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT))
def time_picker_for(time_type):
diff --git a/timelinelib/xml/__init__.py b/timelinelib/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/timelinelib/xml/__init__.py
diff --git a/timelinelib/xml/parser.py b/timelinelib/xml/parser.py
new file mode 100644
index 0000000..94933ee
--- /dev/null
+++ b/timelinelib/xml/parser.py
@@ -0,0 +1,270 @@
+# Copyright (C) 2009, 2010, 2011 Rickard Lindberg, Roger Lindberg
+#
+# This file is part of Timeline.
+#
+# Timeline is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Timeline 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 Timeline. If not, see <http://www.gnu.org/licenses/>.
+
+
+"""
+A simple, validating, SAX-based XML parser.
+
+Since it is simple, it has some limitations:
+
+ - It can not parse attributes
+ - It can not parse arbitrary nested structures
+ - It can only parse text in leaf nodes: in other words, this piece of XML
+ is not possible to parse: <a>some text <b>here</b> and there</a>
+
+Here's an example how to parse a simple XML document using this module.
+
+First we create a file-like object containing the XML data (any file-like
+object is fine, but we create a StringIO for the purpose of making a working
+example):
+
+ >>> from StringIO import StringIO
+
+ >>> xml_stream = StringIO('''
+ ... <db>
+ ... <person>
+ ... <name>Rickard</name>
+ ... </person>
+ ... <person>
+ ... <name>James</name>
+ ... <age>38</age>
+ ... </person>
+ ... </db>
+ ... ''')
+
+Then we define two parser functions that we later associate with Tag objects.
+Parse functions are called when the end tag has been read. The first argument
+to a parse function is the text that the tag contains. It will be empty for all
+tags except leaf tags. The second argument is a dictionary that can be used to
+store temporary variables. This dictionary is passed to all parse functions,
+providing a way to share information between parse functions.
+
+ >>> def parse_name(text, tmp_dict):
+ ... tmp_dict["tmp_name"] = text
+
+ >>> def parse_person(text, tmp_dict):
+ ... # text is empty here since person is not a leaf tag
+ ... name = tmp_dict.pop("tmp_name")
+ ... age = tmp_dict.pop("tmp_age", None)
+ ... print("Found %s in db." % name)
+ ... if age is not None:
+ ... print("%s is %s years old." % (name, age))
+
+Next we define the structure of the XML document that we are going to parse by
+creating Tag objects. The first argument is the name of the tag, the second
+specifies how many times it can occur inside its parent (should be one of
+SINGLE, OPTIONAL, or ANY), the third argument is the parse function to be used
+for this tag (can be None if no parsing is needed), and the fourth argument is
+a list of child tags.
+
+ >>> root_tag = Tag("db", SINGLE, None, [
+ ... Tag("person", ANY, parse_person, [
+ ... Tag("name", SINGLE, parse_name),
+ ... Tag("age", OPTIONAL, parse_fn_store("tmp_age")),
+ ... ]),
+ ... ])
+
+The parse_fn_store function returns a parser function that works exactly like
+parse_name: it takes the text of the tag and stores it in the dictionary with
+the given key (tmp_age in this case).
+
+The last step is to call the parse function with the stream, the tag
+configuration, and a dictionary. The dictionary can be populated with values
+before parsing starts if needed.
+
+ >>> parse(xml_stream, root_tag, {})
+ Found Rickard in db.
+ Found James in db.
+ James is 38 years old.
+
+The parse function will raise a ValidationError if the XML is not valid and a
+SAXException the if the XML is not well-formed.
+"""
+
+
+from xml.sax import parse as sax_parse
+import sys
+import xml.sax.handler
+
+
+# Occurrence rules for tags
+SINGLE = 1
+OPTIONAL = 2
+ANY = 3
+
+
+class ValidationError(Exception):
+ """Raised when parsed xml document does not follow the schema."""
+ pass
+
+
+class Tag(object):
+ """
+ Represents a tag in an xml document.
+
+ Used to define structure of an xml document and define parser functions for
+ individual parts of an xml document.
+
+ Parser functions are called when the end tag has been read.
+
+ See SaxHandler class defined below to see how this class is used.
+ """
+
+ def __init__(self, name, occurrence_rule, parse_fn, child_tags=[]):
+ self.name = name
+ self.occurrence_rule = occurrence_rule
+ self.parse_fn = parse_fn
+ self.child_tags = []
+ self.add_child_tags(child_tags)
+ self.parent = None
+ # Variables defining state
+ self.occurrences = 0
+ self.next_possible_child_pos = 0
+ self.start_read = False
+
+ def add_child_tags(self, tags):
+ for tag in tags:
+ self.add_child_tag(tag)
+
+ def add_child_tag(self, tag):
+ tag.parent = self
+ self.child_tags.append(tag)
+
+ def read_enough_times(self):
+ return self.occurrences > 0 or self.occurrence_rule in (OPTIONAL, ANY)
+
+ def can_read_more(self):
+ return self.occurrences == 0 or self.occurrence_rule == ANY
+
+ def handle_start_tag(self, name, tmp_dict):
+ if name == self.name:
+ if self.start_read == True:
+ # Nested tag
+ raise ValidationError("Did not expect <%s>." % name)
+ else:
+ self.start_read = True
+ return self
+ elif self.start_read == True:
+ next_child = self._find_next_child(name)
+ return next_child.handle_start_tag(name, tmp_dict)
+ else:
+ raise ValidationError("Expected <%s> but got <%s>."
+ % (self.name, name))
+
+ def handle_end_tag(self, name, text, tmp_dict):
+ self._ensure_end_tag_valid(name, text)
+ if self.parse_fn is not None:
+ self.parse_fn(text, tmp_dict)
+ self._ensure_all_children_read()
+ self._reset_parse_data()
+ self.occurrences += 1
+ return self.parent
+
+ def _ensure_end_tag_valid(self, name, text):
+ if name != self.name:
+ raise ValidationError("Expected </%s> but got </%s>."
+ % (self.name, name))
+ if self.child_tags:
+ if text.strip():
+ raise ValidationError("Did not expect text but got '%s'."
+ % text)
+
+ def _ensure_all_children_read(self):
+ num_child_tags = len(self.child_tags)
+ while self.next_possible_child_pos < num_child_tags:
+ child = self.child_tags[self.next_possible_child_pos]
+ if not child.read_enough_times():
+ raise ValidationError("<%s> not read enough times."
+ % child.name)
+ self.next_possible_child_pos += 1
+
+ def _reset_parse_data(self):
+ for child_tag in self.child_tags:
+ child_tag.occurrences = 0
+ self.next_possible_child_pos = 0
+ self.start_read = False
+
+ def _find_next_child(self, name):
+ num_child_tags = len(self.child_tags)
+ while self.next_possible_child_pos < num_child_tags:
+ child = self.child_tags[self.next_possible_child_pos]
+ if child.name == name:
+ if child.can_read_more():
+ return child
+ else:
+ break
+ else:
+ if child.read_enough_times():
+ self.next_possible_child_pos += 1
+ else:
+ break
+ raise ValidationError("Did not expect <%s>." % name)
+
+
+class SaxHandler(xml.sax.handler.ContentHandler):
+
+ def __init__(self, root_tag, tmp_dict):
+ self.tag_to_parse = root_tag
+ self.tmp_dict = tmp_dict
+ self.text = ""
+
+ def startElement(self, name, attrs):
+ """
+ Called when a start tag has been read.
+ """
+ if attrs.getLength() > 0:
+ raise ValidationError("Did not expect attributes on <%s>." % name)
+ if self.text.strip():
+ raise ValidationError("Did not expect text but got '%s'."
+ % self.text)
+ self.tag_to_parse = self.tag_to_parse.handle_start_tag(name,
+ self.tmp_dict)
+ self.text = ""
+
+ def endElement(self, name):
+ """
+ Called when an end tag (and everything between the start and end tag)
+ has been read.
+ """
+ self.tag_to_parse = self.tag_to_parse.handle_end_tag(name, self.text,
+ self.tmp_dict)
+ self.text = ""
+
+ def characters(self, content):
+ self.text += content
+
+
+def parse(xml, schema, tmp_dict):
+ """
+ xml should be a filename or a file-like object containing xml data.
+
+ schema should be a Tag object defining the structure of the xml document.
+
+ tmp_dict is used by parser functions in Tag objects to share data. It can
+ be pre-populated with values.
+ """
+ if isinstance(xml, unicode):
+ # Workaround for "Sax parser crashes if given unicode file name" bug:
+ # http://bugs.python.org/issue11159
+ xml = xml.encode(sys.getfilesystemencoding())
+ sax_parse(xml, SaxHandler(schema, tmp_dict))
+
+
+def parse_fn_store(store_key):
+ def fn(text, tmp_dict):
+ tmp_dict[store_key] = text
+ return fn