# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 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.canvas.drawing.drawers import get_progress_color
from timelinelib.general.immutable import Field
from timelinelib.general.immutable import ImmutableDict
from timelinelib.general.immutable import ImmutableRecord
[docs]class ImmutableEvent(ImmutableRecord):
text = Field(None)
time_period = Field(None)
category_id = Field(None)
category_ids = Field(ImmutableDict())
fuzzy = Field(False)
fuzzy_start = Field(False)
fuzzy_end = Field(False)
locked = Field(False)
ends_today = Field(False)
description = Field(None)
labels = Field(None)
icon = Field(None)
hyperlink = Field(None)
alert = Field(None)
progress = Field(None)
default_color = Field(None)
container_id = Field(None)
sort_order = Field(None)
[docs]class ImmutableMilestone(ImmutableRecord):
text = Field(None)
category_id = Field(None)
category_ids = Field(ImmutableDict())
time_period = Field(None)
description = Field(None)
default_color = Field((255, 255, 128))
sort_order = Field(None)
[docs]class ImmutableEra(ImmutableRecord):
name = Field(None)
time_period = Field(None)
color = Field((200, 200, 200))
ends_today = Field(False)
[docs]class ImmutableCategory(ImmutableRecord):
name = Field("")
color = Field((255, 0, 0))
progress_color = Field(get_progress_color((255, 0, 0)))
done_color = Field(get_progress_color((255, 0, 0)))
font_color = Field((0, 0, 0))
parent_id = Field(None)
[docs]class ImmutableContainer(ImmutableRecord):
text = Field(None)
category_id = Field(None)
category_ids = Field(ImmutableDict())
time_period = Field(None)
description = Field(None)
[docs]class ImmutableDB(ImmutableRecord):
categories = Field(ImmutableDict())
containers = Field(ImmutableDict())
events = Field(ImmutableDict())
milestones = Field(ImmutableDict())
eras = Field(ImmutableDict())
[docs] def save_event(self, event, id_):
self._ensure_non_none_category_exists(event.category_id)
self._ensure_non_none_container_exists(event.container_id)
return self.update(
events=self.events.update({
id_: event,
})
)
[docs] def delete_event(self, id_):
self._ensure_event_exists(id_)
return self.update(
events=self.events.remove(id_)
)
[docs] def save_milestone(self, milestone, id_):
self._ensure_non_none_category_exists(milestone.category_id)
return self.update(
milestones=self.milestones.update({
id_: milestone
})
)
[docs] def delete_milestone(self, id_):
self._ensure_milestone_exists(id_)
return self.update(
milestones=self.milestones.remove(id_)
)
[docs] def save_era(self, era, id_):
return self.update(
eras=self.eras.update({
id_: era,
})
)
[docs] def delete_era(self, id_):
self._ensure_era_exists(id_)
return self.update(
eras=self.eras.remove(id_)
)
[docs] def save_category(self, category, id_):
self._ensure_category_name_is_unique(id_, category.name)
self._ensure_non_none_category_exists(category.parent_id)
self._ensure_no_category_circular(id_, category.parent_id)
return self.update(
categories=self.categories.update({id_: category})
)
[docs] def delete_category(self, delete_id):
self._ensure_category_exists(delete_id)
new_parent_id = self.categories.get(delete_id).parent_id
def update_parent_id(category):
if category.parent_id == delete_id:
return category.update(parent_id=new_parent_id)
else:
return category
def update_category_id(thing):
if thing.category_id == delete_id:
return thing.update(category_id=new_parent_id)
else:
return thing
return self.update(
categories=self.categories.remove(delete_id).map(update_parent_id),
events=self.events.map(update_category_id),
milestones=self.milestones.map(update_category_id),
containers=self.containers.map(update_category_id)
)
[docs] def save_container(self, container, id_):
self._ensure_non_none_category_exists(container.category_id)
return self.update(
containers=self.containers.update({id_: container})
)
[docs] def delete_container(self, delete_id):
self._ensure_container_exists(delete_id)
def update_container_id(event):
if event.container_id == delete_id:
return event.update(container_id=None)
else:
return event
return self.update(
containers=self.containers.remove(delete_id),
events=self.events.map(update_container_id),
)
def _ensure_event_exists(self, id_):
if id_ not in self.events:
raise InvalidOperationError(
"Event with id {0!r} does not exist".format(id_)
)
def _ensure_milestone_exists(self, id_):
if id_ not in self.milestones:
raise InvalidOperationError(
"Milestone with id {0!r} does not exist".format(id_)
)
def _ensure_era_exists(self, id_):
if id_ not in self.eras:
raise InvalidOperationError(
"Era with id {0!r} does not exist".format(id_)
)
def _ensure_category_name_is_unique(self, save_id, save_name):
for id_, category in self.categories:
if id_ != save_id and category.name == save_name:
raise InvalidOperationError(
"Category name {0!r} is not unique".format(save_name)
)
def _ensure_non_none_category_exists(self, id_):
if id_ is not None:
self._ensure_category_exists(id_)
def _ensure_category_exists(self, id_):
if id_ not in self.categories:
raise InvalidOperationError(
"Category with id {0!r} does not exist".format(id_)
)
def _ensure_no_category_circular(self, save_id, parent_id):
while parent_id is not None:
if parent_id == save_id:
raise InvalidOperationError(
"Circular category parent"
)
else:
parent_id = self.categories.get(parent_id).parent_id
def _ensure_non_none_container_exists(self, id_):
if id_ is not None:
self._ensure_container_exists(id_)
def _ensure_container_exists(self, id_):
if id_ not in self.containers:
raise InvalidOperationError(
"Container with id {0!r} does not exist".format(id_)
)
[docs]class InvalidOperationError(Exception):
pass