1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2000-2007  Donald N. Allingham
5# Copyright (C) 2010       Michiel D. Nauta
6# Copyright (C) 2011       Tim G L Lyons
7# Copyright (C) 2013       Doug Blank <doug.blank@gmail.com>
8# Copyright (C) 2017       Nick Hall
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24
25"""
26Source object for Gramps.
27"""
28
29#-------------------------------------------------------------------------
30#
31# Gramps modules
32#
33#-------------------------------------------------------------------------
34from .primaryobj import PrimaryObject
35from .mediabase import MediaBase
36from .notebase import NoteBase
37from .tagbase import TagBase
38from .attrbase import SrcAttributeBase
39from .reporef import RepoRef
40from .const import DIFFERENT, EQUAL, IDENTICAL
41from .citationbase import IndirectCitationBase
42from ..const import GRAMPS_LOCALE as glocale
43_ = glocale.translation.gettext
44
45#-------------------------------------------------------------------------
46#
47# Source class
48#
49#-------------------------------------------------------------------------
50class Source(MediaBase, NoteBase, SrcAttributeBase, IndirectCitationBase,
51             PrimaryObject):
52    """A record of a source of information."""
53
54    def __init__(self):
55        """Create a new Source instance."""
56        PrimaryObject.__init__(self)
57        MediaBase.__init__(self)
58        NoteBase.__init__(self)
59        SrcAttributeBase.__init__(self)
60        self.title = ""
61        self.author = ""
62        self.pubinfo = ""
63        self.abbrev = ""
64        self.reporef_list = []
65
66    def serialize(self):
67        """
68        Convert the object to a serialized tuple of data.
69        """
70        return (self.handle,                                       # 0
71                self.gramps_id,                                    # 1
72                str(self.title),                                  # 2
73                str(self.author),                                 # 3
74                str(self.pubinfo),                                # 4
75                NoteBase.serialize(self),                          # 5
76                MediaBase.serialize(self),                         # 6
77                str(self.abbrev),                                 # 7
78                self.change,                                       # 8
79                SrcAttributeBase.serialize(self),                  # 9
80                [rr.serialize() for rr in self.reporef_list],      # 10
81                TagBase.serialize(self),                           # 11
82                self.private)                                      # 12
83
84    @classmethod
85    def get_schema(cls):
86        """
87        Returns the JSON Schema for this class.
88
89        :returns: Returns a dict containing the schema.
90        :rtype: dict
91        """
92        from .srcattribute import SrcAttribute
93        from .reporef import RepoRef
94        from .mediaref import MediaRef
95        return {
96            "type": "object",
97            "title": _("Source"),
98            "properties": {
99                "_class": {"enum": [cls.__name__]},
100                "handle": {"type": "string",
101                           "maxLength": 50,
102                           "title": _("Handle")},
103                "gramps_id": {"type": "string",
104                              "title": _("Gramps ID")},
105                "title": {"type": "string",
106                          "title": _("Title")},
107                "author": {"type": "string",
108                           "title": _("Author")},
109                "pubinfo": {"type": "string",
110                            "title": _("Publication info")},
111                "note_list": {"type": "array",
112                              "items": {"type": "string",
113                                        "maxLength": 50},
114                              "title": _("Notes")},
115                "media_list": {"type": "array",
116                               "items": MediaRef.get_schema(),
117                               "title": _("Media")},
118                "abbrev": {"type": "string",
119                           "title": _("Abbreviation")},
120                "change": {"type": "integer",
121                           "title": _("Last changed")},
122                "attribute_list": {"type": "array",
123                                   "items": SrcAttribute.get_schema(),
124                                   "title": _("Source Attributes")},
125                "reporef_list": {"type": "array",
126                                 "items": RepoRef.get_schema(),
127                                 "title": _("Repositories")},
128                "tag_list": {"type": "array",
129                             "items": {"type": "string",
130                                       "maxLength": 50},
131                             "title": _("Tags")},
132                "private": {"type": "boolean",
133                            "title": _("Private")}
134            }
135        }
136
137    def unserialize(self, data):
138        """
139        Convert the data held in a tuple created by the serialize method
140        back into the data in a Source structure.
141        """
142        (self.handle,       #  0
143         self.gramps_id,    #  1
144         self.title,        #  2
145         self.author,       #  3
146         self.pubinfo,      #  4
147         note_list,         #  5
148         media_list,        #  6
149         self.abbrev,       #  7
150         self.change,       #  8
151         srcattr_list,      #  9
152         reporef_list,      #  10
153         tag_list,          #  11
154         self.private       #  12
155        ) = data
156
157        NoteBase.unserialize(self, note_list)
158        MediaBase.unserialize(self, media_list)
159        TagBase.unserialize(self, tag_list)
160        SrcAttributeBase.unserialize(self, srcattr_list)
161        self.reporef_list = [RepoRef().unserialize(item) for item in reporef_list]
162        return self
163
164    def _has_handle_reference(self, classname, handle):
165        """
166        Return True if the object has reference to a given handle of given
167        primary object type.
168
169        :param classname: The name of the primary object class.
170        :type classname: str
171        :param handle: The handle to be checked.
172        :type handle: str
173        :returns: Returns whether the object has reference to this handle of
174                  this object type.
175        :rtype: bool
176        """
177        if classname == 'Repository':
178            return handle in [ref.ref for ref in self.reporef_list]
179        return False
180
181    def _remove_handle_references(self, classname, handle_list):
182        """
183        Remove all references in this object to object handles in the list.
184
185        :param classname: The name of the primary object class.
186        :type classname: str
187        :param handle_list: The list of handles to be removed.
188        :type handle_list: str
189        """
190        if classname == 'Repository':
191            new_list = [ref for ref in self.reporef_list
192                        if ref.ref not in handle_list]
193            self.reporef_list = new_list
194
195    def _replace_handle_reference(self, classname, old_handle, new_handle):
196        """
197        Replace all references to old handle with those to the new handle.
198
199        :param classname: The name of the primary object class.
200        :type classname: str
201        :param old_handle: The handle to be replaced.
202        :type old_handle: str
203        :param new_handle: The handle to replace the old one with.
204        :type new_handle: str
205        """
206        if classname == 'Repository':
207            handle_list = [ref.ref for ref in self.reporef_list]
208            while old_handle in handle_list:
209                idx = handle_list.index(old_handle)
210                self.reporef_list[idx].ref = new_handle
211                handle_list[idx] = ''
212
213    def get_text_data_list(self):
214        """
215        Return the list of all textual attributes of the object.
216
217        :returns: Returns the list of all textual attributes of the object.
218        :rtype: list
219        """
220        return [self.title, self.author, self.pubinfo, self.abbrev,
221                self.gramps_id]
222
223    def get_text_data_child_list(self):
224        """
225        Return the list of child objects that may carry textual data.
226
227        :returns: Returns the list of child objects that may carry textual data.
228        :rtype: list
229        """
230        return self.media_list + self.reporef_list + self.attribute_list
231
232    def get_citation_child_list(self):
233        """
234        Return the list of child secondary objects that may refer citations.
235
236        :returns: Returns the list of child secondary child objects that may
237                  refer citations.
238        :rtype: list
239        """
240        return self.media_list
241
242    def get_note_child_list(self):
243        """
244        Return the list of child secondary objects that may refer notes.
245
246        :returns: Returns the list of child secondary child objects that may
247                  refer notes.
248        :rtype: list
249        """
250        return self.media_list + self.reporef_list
251
252    def get_handle_referents(self):
253        """
254        Return the list of child objects which may, directly or through
255        their children, reference primary objects.
256
257        :returns: Returns the list of objects referencing primary objects.
258        :rtype: list
259        """
260        return self.get_citation_child_list() + self.reporef_list
261
262    def get_referenced_handles(self):
263        """
264        Return the list of (classname, handle) tuples for all directly
265        referenced primary objects.
266
267        :returns: List of (classname, handle) tuples for referenced objects.
268        :rtype: list
269        """
270        return (self.get_referenced_note_handles() +
271                self.get_referenced_tag_handles())
272
273    def merge(self, acquisition):
274        """
275        Merge the content of acquisition into this source.
276
277        :param acquisition: The source to merge with the present source.
278        :type acquisition: Source
279        """
280        self._merge_privacy(acquisition)
281        self._merge_note_list(acquisition)
282        self._merge_media_list(acquisition)
283        self._merge_tag_list(acquisition)
284        self._merge_attribute_list(acquisition)
285        self._merge_reporef_list(acquisition)
286
287    def set_title(self, title):
288        """
289        Set the descriptive title of the Source object.
290
291        :param title: descriptive title to assign to the Source
292        :type title: str
293        """
294        self.title = title
295
296    def get_title(self):
297        """
298        Return the descriptive title of the Place object.
299
300        :returns: Returns the descriptive title of the Place
301        :rtype: str
302        """
303        return self.title
304
305    def set_author(self, author):
306        """Set the author of the Source."""
307        self.author = author
308
309    def get_author(self):
310        """Return the author of the Source."""
311        return self.author
312
313    def set_publication_info(self, text):
314        """Set the publication information of the Source."""
315        self.pubinfo = text
316
317    def get_publication_info(self):
318        """Return the publication information of the Source."""
319        return self.pubinfo
320
321    def set_abbreviation(self, abbrev):
322        """Set the title abbreviation of the Source."""
323        self.abbrev = abbrev
324
325    def get_abbreviation(self):
326        """Return the title abbreviation of the Source."""
327        return self.abbrev
328
329    def add_repo_reference(self, repo_ref):
330        """
331        Add a :class:`~.reporef.RepoRef` instance to the Source's reporef list.
332
333        :param repo_ref: :class:`~.reporef.RepoRef` instance to be added to the
334                         object's reporef list.
335        :type repo_ref: :class:`~.reporef.RepoRef`
336        """
337        self.reporef_list.append(repo_ref)
338
339    def get_reporef_list(self):
340        """
341        Return the list of :class:`~.reporef.RepoRef` instances associated with
342        the Source.
343
344        :returns: list of :class:`~.reporef.RepoRef` instances associated with
345                  the Source
346        :rtype: list
347        """
348        return self.reporef_list
349
350    def set_reporef_list(self, reporef_list):
351        """
352        Set the list of :class:`~.reporef.RepoRef` instances associated with
353        the Source. It replaces the previous list.
354
355        :param reporef_list: list of :class:`~.reporef.RepoRef` instances to be
356                             assigned to the Source.
357        :type reporef_list: list
358        """
359        self.reporef_list = reporef_list
360
361    def _merge_reporef_list(self, acquisition):
362        """
363        Merge the list of repository references from acquisition with our own.
364
365        :param acquisition: the repository references list of this object will
366                            be merged with the current repository references
367                            list.
368        :type acquisition: RepoRef
369        """
370        reporef_list = self.reporef_list[:]
371        for addendum in acquisition.get_reporef_list():
372            for reporef in reporef_list:
373                equi = reporef.is_equivalent(addendum)
374                if equi == IDENTICAL:
375                    break
376                elif equi == EQUAL:
377                    reporef.merge(addendum)
378                    break
379            else:
380                self.reporef_list.append(addendum)
381
382    def has_repo_reference(self, repo_handle):
383        """
384        Return True if the Source has reference to this Repository handle.
385
386        :param repo_handle: The Repository handle to be checked.
387        :type repo_handle: str
388        :returns: Returns whether the Source has reference to this Repository
389                  handle.
390        :rtype: bool
391        """
392        return repo_handle in [repo_ref.ref for repo_ref in self.reporef_list]
393
394    def remove_repo_references(self, repo_handle_list):
395        """
396        Remove references to all Repository handles in the list.
397
398        :param repo_handle_list: The list of Repository handles to be removed.
399        :type repo_handle_list: list
400        """
401        new_reporef_list = [repo_ref for repo_ref in self.reporef_list
402                            if repo_ref.ref not in repo_handle_list]
403        self.reporef_list = new_reporef_list
404
405    def replace_repo_references(self, old_handle, new_handle):
406        """
407        Replace all references to old Repository handle with the new handle
408        and merge equivalent entries.
409
410        :param old_handle: The Repository handle to be replaced.
411        :type old_handle: str
412        :param new_handle: The Repository handle to replace the old one with.
413        :type new_handle: str
414        """
415        refs_list = [repo_ref.ref for repo_ref in self.reporef_list]
416        new_ref = None
417        if new_handle in refs_list:
418            new_ref = self.reporef_list[refs_list.index(new_handle)]
419        n_replace = refs_list.count(old_handle)
420        for ix_replace in range(n_replace):
421            idx = refs_list.index(old_handle)
422            self.reporef_list[idx].ref = new_handle
423            refs_list[idx] = new_handle
424            if new_ref:
425                repo_ref = self.reporef_list[idx]
426                equi = new_ref.is_equivalent(repo_ref)
427                if equi != DIFFERENT:
428                    if equi == EQUAL:
429                        new_ref.merge(repo_ref)
430                    self.reporef_list.pop(idx)
431                    refs_list.pop(idx)
432