1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2014-2017  Nick Hall
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20
21"""
22Class handling displaying of places.
23"""
24
25#---------------------------------------------------------------
26#
27# Python imports
28#
29#---------------------------------------------------------------
30import os
31import xml.dom.minidom
32
33#-------------------------------------------------------------------------
34#
35# Gramps modules
36#
37#-------------------------------------------------------------------------
38from ..const import PLACE_FORMATS, GRAMPS_LOCALE as glocale
39_ = glocale.translation.gettext
40from ..config import config
41from ..utils.location import get_location_list
42from ..lib import PlaceType
43
44#-------------------------------------------------------------------------
45#
46# PlaceFormat class
47#
48#-------------------------------------------------------------------------
49class PlaceFormat:
50    def __init__(self, name, levels, language, street, reverse):
51        self.name = name
52        self.levels = levels
53        self.language = language
54        self.street = street
55        self.reverse = reverse
56
57
58#-------------------------------------------------------------------------
59#
60# PlaceDisplay class
61#
62#-------------------------------------------------------------------------
63class PlaceDisplay:
64
65    def __init__(self):
66        self.place_formats = []
67        self.default_format = config.get('preferences.place-format')
68        if os.path.exists(PLACE_FORMATS):
69            try:
70                self.load_formats()
71                return
72            except BaseException:
73                print(_("Error in '%s' file: cannot load.") % PLACE_FORMATS)
74        pf = PlaceFormat(_('Full'), ':', '', 0, False)
75        self.place_formats.append(pf)
76
77    def display_event(self, db, event, fmt=-1):
78        if not event:
79            return ""
80        place_handle = event.get_place_handle()
81        if place_handle:
82            place = db.get_place_from_handle(place_handle)
83            return self.display(db, place, event.get_date_object(), fmt)
84        else:
85            return ""
86
87    def display(self, db, place, date=None, fmt=-1):
88        if not place:
89            return ""
90        if not config.get('preferences.place-auto'):
91            return place.title
92        else:
93            if fmt == -1:
94                fmt = config.get('preferences.place-format')
95            pf = self.place_formats[fmt]
96            lang = pf.language
97            all_places = get_location_list(db, place, date, lang)
98
99            # Apply format string to place list
100            index = _find_populated_place(all_places)
101            places = []
102            for slice in pf.levels.split(','):
103                parts = slice.split(':')
104                if len(parts) == 1:
105                    offset = _get_offset(parts[0], index)
106                    if offset is not None:
107                        try:
108                            places.append(all_places[offset])
109                        except IndexError:
110                            pass
111                elif len(parts) == 2:
112                    start = _get_offset(parts[0], index)
113                    end = _get_offset(parts[1], index)
114                    if start is None:
115                        places.extend(all_places[:end])
116                    elif end is None:
117                        places.extend(all_places[start:])
118                    else:
119                        places.extend(all_places[start:end])
120
121            if pf.street:
122                types = [item[1] for item in places]
123                try:
124                    idx = types.index(PlaceType.NUMBER)
125                except ValueError:
126                    idx = None
127                if idx is not None and len(places) > idx+1:
128                    if pf.street == 1:
129                        combined = (places[idx][0] + ' ' + places[idx+1][0],
130                                    places[idx+1][1])
131                    else:
132                        combined = (places[idx+1][0] + ' ' + places[idx][0],
133                                    places[idx+1][1])
134                    places = places[:idx] + [combined] + places[idx+2:]
135
136            names = [item[0] for item in places]
137            if pf.reverse:
138                names.reverse()
139
140            # TODO for Arabic, should the next line's comma be translated?
141            return ", ".join(names)
142
143    def get_formats(self):
144        return self.place_formats
145
146    def set_formats(self, formats):
147        self.place_formats = formats
148
149    def load_formats(self):
150        dom = xml.dom.minidom.parse(PLACE_FORMATS)
151        top = dom.getElementsByTagName('place_formats')
152
153        for fmt in top[0].getElementsByTagName('format'):
154            name = fmt.attributes['name'].value
155            levels = fmt.attributes['levels'].value
156            language = fmt.attributes['language'].value
157            street = int(fmt.attributes['street'].value)
158            reverse = fmt.attributes['reverse'].value == 'True'
159            pf = PlaceFormat(name, levels, language, street, reverse)
160            self.place_formats.append(pf)
161
162        dom.unlink()
163
164    def save_formats(self):
165        doc = xml.dom.minidom.Document()
166        place_formats = doc.createElement('place_formats')
167        doc.appendChild(place_formats)
168        for fmt in self.place_formats:
169            node = doc.createElement('format')
170            place_formats.appendChild(node)
171            node.setAttribute('name', fmt.name)
172            node.setAttribute('levels', fmt.levels)
173            node.setAttribute('language', fmt.language)
174            node.setAttribute('street', str(fmt.street))
175            node.setAttribute('reverse', str(fmt.reverse))
176        with open(PLACE_FORMATS, 'w', encoding='utf-8') as f_d:
177            doc.writexml(f_d, addindent='  ', newl='\n', encoding='utf-8')
178
179
180def _get_offset(value, index):
181    if index is not None and value.startswith('p'):
182        try:
183            offset = int(value[1:])
184        except ValueError:
185            offset = 0
186        offset += index
187    else:
188        try:
189            offset = int(value)
190        except ValueError:
191            offset = None
192    return offset
193
194def _find_populated_place(places):
195    populated_place = None
196    for index, item in enumerate(places):
197        if int(item[1]) in [PlaceType.HAMLET, PlaceType.VILLAGE,
198                            PlaceType.TOWN, PlaceType.CITY]:
199            populated_place = index
200    return populated_place
201
202displayer = PlaceDisplay()
203