1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2007-2009 Stephane Charette 5# Copyright (C) 2019- Serge Noiraud 6# 7# This program is free software; you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation; either version 2 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program; if not, write to the Free Software 19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 20# 21 22"Find possible leading and/or trailing spaces in places name and people" 23 24#------------------------------------------------------------------------ 25# 26# GNOME/GTK modules 27# 28#------------------------------------------------------------------------ 29from gi.repository import Gtk 30from gi.repository import GObject 31 32#------------------------------------------------------------------------ 33# 34# Gramps modules 35# 36#------------------------------------------------------------------------ 37from gramps.gen.const import URL_MANUAL_PAGE 38from gramps.gui.plug import tool 39from gramps.gui.editors import (EditPlace, EditPerson) 40from gramps.gen.errors import WindowActiveError 41from gramps.gui.managedwindow import ManagedWindow 42from gramps.gui.utils import ProgressMeter 43from gramps.gui.display import display_help 44from gramps.gui.glade import Glade 45from gramps.gen.const import GRAMPS_LOCALE as glocale 46_ = glocale.translation.sgettext 47 48#------------------------------------------------------------------------- 49# 50# Constants 51# 52#------------------------------------------------------------------------- 53WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE 54WIKI_HELP_SEC = _('manual|Remove_leading_and_trailing_spaces') 55 56def validate_lat_lon(field): 57 """ 58 Return True if some characters are found in the field 59 # hyphen (u+2010) 60 # non-breaking hyphen (u+2011) 61 # figure dash (u+2012) 62 # en dash (u+2013) 63 # em dash (u+2014) 64 # horizontal bar (u+2015) 65 """ 66 for char in (',', '\u2010', '\u2011', '\u2012', 67 '\u2013', '\u2014', '\u2015'): 68 if field.find(char) != -1: 69 return True 70 return False 71 72#------------------------------------------------------------------------ 73# 74# RemoveSpaces class 75# 76#------------------------------------------------------------------------ 77class RemoveSpaces(ManagedWindow): 78 """ 79 Find leading and trailing spaces in Place names and person names 80 """ 81 def __init__(self, dbstate, user, options_class, name, callback=None): 82 uistate = user.uistate 83 dummy_opt = options_class 84 dummy_nme = name 85 dummy_cb = callback 86 87 self.title = _('Clean input data') 88 ManagedWindow.__init__(self, uistate, [], self.__class__) 89 self.dbstate = dbstate 90 self.uistate = uistate 91 self.db = dbstate.db 92 93 top_dialog = Glade() 94 95 top_dialog.connect_signals({ 96 "destroy_passed_object" : self.close, 97 "on_help_clicked" : self.on_help_clicked, 98 "on_delete_event" : self.close, 99 }) 100 101 window = top_dialog.toplevel 102 title = top_dialog.get_object("title") 103 self.set_window(window, title, self.title) 104 tip = _('Search leading and/or trailing spaces for persons' 105 ' and places. Search comma in coordinates fields.\n' 106 'Double click on a row to edit its content.') 107 title.set_tooltip_text(tip) 108 109 # start the progress indicator 110 self.progress = ProgressMeter(self.title, _('Starting'), 111 parent=uistate.window) 112 steps = self.db.get_number_of_people() + self.db.get_number_of_places() 113 self.progress.set_pass(_('Looking for possible fields with leading or' 114 ' trailing spaces'), steps) 115 116 self.model_1 = Gtk.ListStore( 117 GObject.TYPE_STRING, # 0==handle 118 GObject.TYPE_STRING, # 1==firstname 119 GObject.TYPE_STRING, # 2==surname 120 GObject.TYPE_STRING, # 3==alternate name 121 GObject.TYPE_STRING, # 4==group_as 122 ) 123 self.model_1.set_sort_column_id( 124 Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 1) 125 126 self.label_1 = top_dialog.get_object("label_1") 127 self.label_1.set_text(_('Person')) 128 self.treeview_1 = top_dialog.get_object("treeview_1") 129 self.treeview_1.set_model(self.model_1) 130 col1 = Gtk.TreeViewColumn(_('handle'), 131 Gtk.CellRendererText(), text=0) 132 renderer1 = Gtk.CellRendererText() 133 renderer1.set_property('underline-set', True) 134 renderer1.set_property('underline', 2) # 2=double underline 135 col2 = Gtk.TreeViewColumn(_('firstname'), renderer1, text=1) 136 renderer2 = Gtk.CellRendererText() 137 renderer2.set_property('underline-set', True) 138 renderer2.set_property('underline', 2) # 2=double underline 139 col3 = Gtk.TreeViewColumn(_('surname'), renderer2, text=2) 140 renderer3 = Gtk.CellRendererText() 141 renderer3.set_property('underline-set', True) 142 renderer3.set_property('underline', 2) # 2=double underline 143 col4 = Gtk.TreeViewColumn(_('alternate name'), renderer3, text=3) 144 renderer4 = Gtk.CellRendererText() 145 renderer4.set_property('underline-set', True) 146 renderer4.set_property('underline', 2) # 2=double underline 147 col5 = Gtk.TreeViewColumn(_('group as'), renderer4, text=4) 148 col1.set_resizable(True) 149 col1.set_visible(False) 150 col2.set_resizable(True) 151 col3.set_resizable(True) 152 col4.set_resizable(True) 153 col5.set_resizable(True) 154 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 155 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 156 col3.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 157 col4.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 158 col5.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 159 self.treeview_1.append_column(col1) 160 self.treeview_1.append_column(col2) 161 self.treeview_1.append_column(col3) 162 self.treeview_1.append_column(col4) 163 self.treeview_1.append_column(col5) 164 self.treeselection = self.treeview_1.get_selection() 165 self.treeview_1.connect('row-activated', self.rowactivated_cb1) 166 167 self.model_2 = Gtk.ListStore( 168 GObject.TYPE_STRING, # 0==handle 169 GObject.TYPE_STRING, # 1==name 170 GObject.TYPE_STRING, # 2==latitude 171 GObject.TYPE_STRING) # 3==longitude 172 self.model_2.set_sort_column_id( 173 Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 1) 174 175 self.label_2 = top_dialog.get_object("label_2") 176 self.label_2.set_text(_('Place')) 177 self.treeview_2 = top_dialog.get_object("treeview_2") 178 self.treeview_2.set_model(self.model_2) 179 col1 = Gtk.TreeViewColumn(_('handle'), 180 Gtk.CellRendererText(), text=0) 181 renderer5 = Gtk.CellRendererText() 182 renderer5.set_property('underline-set', True) 183 renderer5.set_property('underline', 2) # 2=double underline 184 col2 = Gtk.TreeViewColumn(_('name'), renderer5, text=1) 185 renderer6 = Gtk.CellRendererText() 186 renderer6.set_property('underline-set', True) 187 renderer6.set_property('underline', 2) # 2=double underline 188 col3 = Gtk.TreeViewColumn(_('latitude'), renderer6, text=2) 189 renderer7 = Gtk.CellRendererText() 190 renderer7.set_property('underline-set', True) 191 renderer7.set_property('underline', 2) # 2=double underline 192 col4 = Gtk.TreeViewColumn(_('longitude'), renderer7, text=3) 193 col1.set_resizable(True) 194 col1.set_visible(False) 195 col2.set_resizable(True) 196 col3.set_resizable(True) 197 col4.set_resizable(True) 198 col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 199 col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 200 col3.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 201 col4.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) 202 self.treeview_2.append_column(col1) 203 self.treeview_2.append_column(col2) 204 self.treeview_2.append_column(col3) 205 self.treeview_2.append_column(col4) 206 self.treeselection = self.treeview_2.get_selection() 207 self.treeview_2.connect('row-activated', self.rowactivated_cb2) 208 209 self.places() 210 self.people() 211 212 # close the progress bar 213 self.progress.close() 214 215 self.show() 216 217 def places(self): 218 """ 219 For all places in the database, if the name contains leading or 220 trailing spaces. 221 """ 222 for place_handle in self.db.get_place_handles(): 223 self.progress.step() 224 place = self.db.get_place_from_handle(place_handle) 225 place_name = place.get_name() 226 pname = place_name.get_value() 227 found = False 228 if pname != pname.strip(): 229 found = True 230 plat = place.get_latitude() 231 if plat != plat.strip(): 232 found = True 233 if validate_lat_lon(plat): 234 found = True 235 plon = place.get_longitude() 236 if plon != plon.strip(): 237 found = True 238 if validate_lat_lon(plon): 239 found = True 240 if found: 241 value = (place_handle, pname, plat, plon) 242 self.model_2.append(value) 243 return True 244 245 def people(self): 246 """ 247 For all persons in the database, if the name contains leading or 248 trailing spaces. Works for alternate names and group_as. 249 """ 250 for person_handle in self.db.get_person_handles(): 251 self.progress.step() 252 person = self.db.get_person_from_handle(person_handle) 253 primary_name = person.get_primary_name() 254 fname = primary_name.get_first_name() 255 found = False 256 if fname != fname.strip(): 257 found = True 258 sname = primary_name.get_primary_surname().get_surname() 259 if sname != sname.strip(): 260 found = True 261 paname = "" 262 for name in primary_name.get_surname_list(): 263 aname = name.get_surname() 264 if aname != sname and aname != aname.strip(): 265 found = True 266 if paname != "": 267 paname += ', ' 268 paname += aname 269 groupas = primary_name.group_as 270 if groupas != groupas.strip(): 271 found = True 272 if found: 273 value = (person_handle, fname, sname, paname, groupas) 274 self.model_1.append(value) 275 return True 276 277 def rowactivated_cb1(self, treeview, path, column): 278 """ 279 Called when a Person row is activated. 280 """ 281 dummy_tv = treeview 282 dummy_col = column 283 iter_ = self.model_1.get_iter(path) 284 handle = self.model_1.get_value(iter_, 0) 285 person = self.dbstate.db.get_person_from_handle(handle) 286 if person: 287 try: 288 EditPerson(self.dbstate, self.uistate, [], person) 289 except WindowActiveError: 290 pass 291 return True 292 return False 293 294 def rowactivated_cb2(self, treeview, path, column): 295 """ 296 Called when a Place row is activated. 297 """ 298 dummy_tv = treeview 299 dummy_col = column 300 iter_ = self.model_2.get_iter(path) 301 handle = self.model_2.get_value(iter_, 0) 302 place = self.dbstate.db.get_place_from_handle(handle) 303 if place: 304 try: 305 EditPlace(self.dbstate, self.uistate, [], place) 306 except WindowActiveError: 307 pass 308 return True 309 return False 310 311 def on_help_clicked(self, _obj): 312 """ 313 Display the relevant portion of Gramps manual. 314 """ 315 display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) 316 317 def close(self, *obj): 318 ManagedWindow.close(self, *obj) 319 320#------------------------------------------------------------------------ 321# 322# RemoveSpacesOptions 323# 324#------------------------------------------------------------------------ 325class RemoveSpacesOptions(tool.ToolOptions): 326 """ 327 Defines options and provides handling interface. 328 """ 329 def __init__(self, name, person_id=None): 330 """ Initialize the options class """ 331 tool.ToolOptions.__init__(self, name, person_id) 332