gaupol/entries.py¶
Source code for module gaupol.entries from file gaupol/entries.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | # -*- coding: utf-8 -*-
# Copyright (C) 2005 Osmo Salomaa
#
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
"""Entry for time data in format ``[-]HH:MM:SS.SSS``."""
import aeidon
import functools
import gaupol
import re
from gi.repository import Gdk
from gi.repository import GObject
from gi.repository import Gtk
__all__ = ("TimeEntry",)
def _blocked(function):
"""Decorator for methods to be run blocked to avoid recursion."""
@functools.wraps(function)
def wrapper(entry, *args, **kwargs):
entry.handler_block(entry._delete_handler)
entry.handler_block(entry._insert_handler)
value = function(entry, *args, **kwargs)
entry.handler_unblock(entry._insert_handler)
entry.handler_unblock(entry._delete_handler)
return value
return wrapper
class TimeEntry(Gtk.Entry):
"""
Entry for time data in format ``[-]HH:MM:SS.SSS``.
:ivar _delete_handler: Handler for "delete-text" signal
:ivar _insert_handler: Handler for "insert-text" signal
This widget uses :func:`GLib.idle_add` a lot, which means that clients may
need to call :func:`Gtk.main_iteration` to ensure proper updating.
"""
_re_digit = re.compile(r"\d")
_re_time = re.compile(r"^-?\d\d:[0-5]\d:[0-5]\d\.\d\d\d$")
def __init__(self):
"""Initialize a :class:`TimeEntry` instance."""
GObject.GObject.__init__(self)
self._delete_handler = None
self._insert_handler = None
self.set_width_chars(13)
self.set_max_length(13)
self._init_signal_handlers()
def _init_signal_handlers(self):
"""Initialize signal handlers."""
aeidon.util.connect(self, self, "cut-clipboard")
aeidon.util.connect(self, self, "key-press-event")
aeidon.util.connect(self, self, "toggle-overwrite")
self._delete_handler = aeidon.util.connect(self, self, "delete-text")
self._insert_handler = aeidon.util.connect(self, self, "insert-text")
@_blocked
def _insert_text(self, value):
"""Insert `value` as text after validation."""
pos = self.get_position()
text = self.get_text()
if pos == 0 and value.startswith("-"):
text = (text if text.startswith("-") else "-{}".format(text))
length = len(value)
text = text[:pos] + value + text[pos+length:]
text = text.replace(",", ".")
if not self._re_time.match(text): return
self.set_text(text)
self.set_position(pos)
if length != 1: return
self.set_position(pos+1)
if len(text) > pos+1 and text[pos+1] in (":", "."):
self.set_position(pos+2)
def _on_cut_clipboard(self, entry):
"""Change "cut-clipboard" signal to "copy-clipboard"."""
self.stop_emission("cut-clipboard")
self.emit("copy-clipboard")
def _on_delete_text(self, entry, start_pos, end_pos):
"""Do not allow deleting text."""
self.stop_emission("delete-text")
self.set_position(start_pos)
def _on_key_press_event(self, entry, event):
"""Change numbers to zero if Backspace or Delete pressed."""
keys = (Gdk.KEY_BackSpace, Gdk.KEY_Delete)
if not event.keyval in keys: return
self.stop_emission("key-press-event")
if self.get_selection_bounds():
gaupol.util.idle_add(self._zero_selection)
elif event.keyval == Gdk.KEY_BackSpace:
gaupol.util.idle_add(self._zero_previous)
elif event.keyval == Gdk.KEY_Delete:
gaupol.util.idle_add(self._zero_next)
def _on_insert_text(self, entry, text, length, pos):
"""Insert `text` after validation."""
self.stop_emission("insert-text")
gaupol.util.idle_add(self._insert_text, text)
def _on_toggle_overwrite(self, entry):
"""Do not allow toggling overwrite."""
self.stop_emission("toggle-overwrite")
@_blocked
def _zero_next(self):
"""Change the next digit to zero."""
pos = self.get_position()
text = self.get_text()
if pos >= len(text): return
if pos == 0 and text.startswith("-"):
self.set_text(text[1:])
return self.set_position(0)
if not text[pos].isdigit(): return
self.set_text(text[:pos] + "0" + text[pos+1:])
self.set_position(pos)
@_blocked
def _zero_previous(self):
"""Change the previous digit to zero."""
pos = self.get_position()
text = self.get_text()
if pos <= 0: return
if pos == 1 and text.startswith("-"):
self.set_text(text[1:])
return self.set_position(0)
if not text[pos-1].isdigit():
return self.set_position(pos-1)
self.set_text(text[:pos-1] + "0" + text[pos:])
self.set_position(pos-1)
@_blocked
def _zero_selection(self):
"""Change digits in selection to zero."""
if not self.get_selection_bounds(): return
a, z = self.get_selection_bounds()
text = self.get_text()
zero = self._re_digit.sub("0", text[a:z])
self.set_text(text[:a] + zero + text[z:])
self.set_position(a)
|