diff --git a/gramps/gui/editors/editplace.py b/gramps/gui/editors/editplace.py index 81b039d7f..38730b81c 100644 --- a/gramps/gui/editors/editplace.py +++ b/gramps/gui/editors/editplace.py @@ -158,24 +158,30 @@ class EditPlace(EditPrimary): self.obj.set_code, self.obj.get_code, self.db.readonly) + entry = self.top.get_object("lon_entry") + entry.set_ltr_mode() self.longitude = MonitoredEntry( - self.top.get_object("lon_entry"), + entry, self.obj.set_longitude, self.obj.get_longitude, self.db.readonly) self.longitude.connect("validate", self._validate_coordinate, "lon") #force validation now with initial entry - self.top.get_object("lon_entry").validate(force=True) + entry.validate(force=True) + entry = self.top.get_object("lat_entry") + entry.set_ltr_mode() self.latitude = MonitoredEntry( - self.top.get_object("lat_entry"), + entry, self.obj.set_latitude, self.obj.get_latitude, self.db.readonly) self.latitude.connect("validate", self._validate_coordinate, "lat") #force validation now with initial entry - self.top.get_object("lat_entry").validate(force=True) + entry.validate(force=True) + entry = self.top.get_object("latlon_entry") + entry.set_ltr_mode() self.latlon = MonitoredEntry( - self.top.get_object("latlon_entry"), + entry, self.set_latlongitude, self.get_latlongitude, self.db.readonly) diff --git a/gramps/gui/editors/editplaceref.py b/gramps/gui/editors/editplaceref.py index 325da6f98..34d2e2619 100644 --- a/gramps/gui/editors/editplaceref.py +++ b/gramps/gui/editors/editplaceref.py @@ -151,24 +151,30 @@ class EditPlaceRef(EditReference): self.source.set_code, self.source.get_code, self.db.readonly) + entry = self.top.get_object("lon_entry") + entry.set_ltr_mode() self.longitude = MonitoredEntry( - self.top.get_object("lon_entry"), + entry, self.source.set_longitude, self.source.get_longitude, self.db.readonly) self.longitude.connect("validate", self._validate_coordinate, "lon") #force validation now with initial entry - self.top.get_object("lon_entry").validate(force=True) + entry.validate(force=True) + entry = self.top.get_object("lat_entry") + entry.set_ltr_mode() self.latitude = MonitoredEntry( - self.top.get_object("lat_entry"), + entry, self.source.set_latitude, self.source.get_latitude, self.db.readonly) self.latitude.connect("validate", self._validate_coordinate, "lat") #force validation now with initial entry - self.top.get_object("lat_entry").validate(force=True) + entry.validate(force=True) + entry = self.top.get_object("latlon_entry") + entry.set_ltr_mode() self.latlon = MonitoredEntry( - self.top.get_object("latlon_entry"), + entry, self.set_latlongitude, self.get_latlongitude, self.db.readonly) diff --git a/gramps/gui/views/treemodels/placemodel.py b/gramps/gui/views/treemodels/placemodel.py index b94950e4e..c4b47e09c 100644 --- a/gramps/gui/views/treemodels/placemodel.py +++ b/gramps/gui/views/treemodels/placemodel.py @@ -143,7 +143,7 @@ class PlaceBaseModel: value = conv_lat_lon('0', data[3], format='DEG')[1] if not value: return _("Error in format") - return value + return ("\u202d" + value + "\u202e") if glocale.rtl_locale else value def column_latitude(self, data): if not data[4]: @@ -151,7 +151,7 @@ class PlaceBaseModel: value = conv_lat_lon(data[4], '0', format='DEG')[0] if not value: return _("Error in format") - return value + return ("\u202d" + value + "\u202e") if glocale.rtl_locale else value def sort_longitude(self, data): if not data[3]: diff --git a/gramps/gui/widgets/undoableentry.py b/gramps/gui/widgets/undoableentry.py index 9d733d918..358d88a8e 100644 --- a/gramps/gui/widgets/undoableentry.py +++ b/gramps/gui/widgets/undoableentry.py @@ -46,10 +46,10 @@ from gi.repository import Gtk # #------------------------------------------------------------------------- from .undoablebuffer import Stack - +from gramps.gen.const import GRAMPS_LOCALE as glocale # table for skipping illegal control chars -INVISIBLE = dict.fromkeys(list(range(32))) +INVISIBLE = dict.fromkeys(list(range(32)) + [0x202d, 0x202e]) class UndoableInsertEntry: @@ -89,6 +89,10 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): Additional features: - Undo and Redo on CTRL-Z/CTRL-SHIFT-Z + + - ltr_mode (forces the field to always be left to right, useful for GPS + coordinates and similar numbers that might contain RTL characters. + See set_ltr_mode. """ __gtype_name__ = 'UndoableEntry' @@ -99,12 +103,12 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): undo_stack_size = 50 def __init__(self): - Gtk.Entry.__init__(self) self.undo_stack = Stack(self.undo_stack_size) self.redo_stack = [] self.not_undoable_action = False self.undo_in_progress = False - + self.ltr_mode = False + Gtk.Entry.__init__(self) self.connect('delete-text', self._on_delete_text) self.connect('key-press-event', self._on_key_press_event) @@ -162,10 +166,16 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): return True text = text.translate(INVISIBLE) + if self.ltr_mode: + if position == 0: + position = 1 + elif position >= self.get_text_length(): + position -= 1 + if not self.undo_in_progress: self.__empty_redo_stack() while not self.not_undoable_action: - undo_action = self.insertclass(text, length, self.get_position()) + undo_action = self.insertclass(text, length, position) try: prev_insert = self.undo_stack.pop() except IndexError: @@ -186,6 +196,7 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): self.get_buffer().insert_text(position, text, len(text)) return position + len(text) + def _on_delete_text(self, editable, start, end): def can_be_merged(prev, cur): """ @@ -212,32 +223,49 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): return False return True - if not self.undo_in_progress: - self.__empty_redo_stack() - if self.not_undoable_action: - return - undo_action = self.deleteclass(editable, start, end) - try: - prev_delete = self.undo_stack.pop() - except IndexError: - self.undo_stack.append(undo_action) - return - if not isinstance(prev_delete, self.deleteclass): - self.undo_stack.append(prev_delete) - self.undo_stack.append(undo_action) - return - if can_be_merged(prev_delete, undo_action): - if prev_delete.start == undo_action.start: # delete key used - prev_delete.text += undo_action.text - prev_delete.end += (undo_action.end - undo_action.start) - else: # Backspace used - prev_delete.text = "%s%s" % (undo_action.text, - prev_delete.text) - prev_delete.start = undo_action.start - self.undo_stack.append(prev_delete) - else: - self.undo_stack.append(prev_delete) - self.undo_stack.append(undo_action) + if self.ltr_mode: # limit deletes to area between LRO/PDF + if start == 0: + start = 1 + elif start > self.get_text_length() - 1: + start -= 1 + if end == 0: + end = 1 + elif end > self.get_text_length() - 1: + end -= 1 + elif end < 0: + end = self.get_text_length() - 1 + + while True: + if not self.undo_in_progress: + self.__empty_redo_stack() + if self.not_undoable_action: + break + undo_action = self.deleteclass(self, start, end) + try: + prev_delete = self.undo_stack.pop() + except IndexError: + self.undo_stack.append(undo_action) + break + if not isinstance(prev_delete, self.deleteclass): + self.undo_stack.append(prev_delete) + self.undo_stack.append(undo_action) + break + if can_be_merged(prev_delete, undo_action): + if prev_delete.start == undo_action.start: # delete key used + prev_delete.text += undo_action.text + prev_delete.end += (undo_action.end - undo_action.start) + else: # Backspace used + prev_delete.text = "%s%s" % (undo_action.text, + prev_delete.text) + prev_delete.start = undo_action.start + self.undo_stack.append(prev_delete) + else: + self.undo_stack.append(prev_delete) + self.undo_stack.append(undo_action) + break + self.get_buffer().delete_text(start, end - start) + self.stop_emission_by_name('delete-text') + return True def begin_not_undoable_action(self): """don't record the next actions @@ -329,3 +357,40 @@ class UndoableEntry(Gtk.Entry, Gtk.Editable): def _handle_redo(self, redo_action): raise NotImplementedError + + def set_ltr_mode(self): + """ sets up the Entry to always be in LTR left to right even if some + characters are RTL. + This works by inserting the LRO/PDF Unicode Explicit Directional + Override characters around the entry text. These characters are then + protected agains insert/delete operations. + + This call must be made before other text is inserted to the Entry. + + Note: we only enable this during rtl_local languages because it has a + minor consequence; if cutting a field from this Entry with this mode + enabled, the LRO/PDF characters may end up in the clipboard. If pasted + back into another UndoableEntry, this is ignored, but if pasted in + another app it may be noticable. + """ + if glocale.rtl_locale: + self.get_buffer().set_text("\u202d\u202e", -1) + self.ltr_mode = True + + def do_set_position(self, position): + """ In ltr_mode, this ensures that the cursor cannot be put outside + the LRO/PDF characters on the ends of the buffer. """ + if position < 0: + position = self.get_text_length() + if self.ltr_mode: + if position == 0: + position = 1 + elif position == self.get_text_length(): + position -= 1 + Gtk.Editable.select_region(self, position, position) + + def get_text(self): + """ Used to remove the LRO/PDF characters when in ltr_mode. + """ + text = Gtk.Entry.get_text(self) + return text[1:-1] if self.ltr_mode else text