# $Id: Entry.py,v 1.45.2.5 2007/01/20 12:56:26 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""A widget, which handles text input."""
from Editable import Editable
from ocempgui.draw import String
from Constants import *
import base
class Entry (Editable):
"""Entry (text="") -> Entry
A widget class suitable for a single line of text input.
The Entry widget is a text input box for a single line of text. It
allows an unlimited amount of text input, but is usually more
suitable for a small or medium amount, which can be scrolled, if the
text size exceeds the visible widget size.
The 'padding' attribute and set_padding() method are used to place a
certain amount of pixels between the text and the outer edges of the
Entry.
entry.padding = 10
entry.set_padding (10)
The Entry supports different border types by setting its 'border'
attribute to a valid value of the BORDER_TYPES constants.
entry.border = BORDER_SUNKEN
entry.set_border (BORDER_SUNKEN)
It also features a password mode, which will cause it to display the
text as asterisks ('*'). It will not encrypt or protect the internal
text attribute however. The password mode can be set using the
'password' attribute and set_password() method.
entry.password = True
entry.set_password (False)
The Entry uses a default size for itself by setting the 'size'
attribute to a width of 94 pixels and a height of 24 pixels.
Default action (invoked by activate()):
See the Editable class.
Mnemonic action (invoked by activate_mnemonic()):
None
Signals:
SIG_MOUSEDOWN - Invoked, when a mouse button gets pressed on the
Entry.
SIG_MOUSEMOVE - Invoked, when the mouse moves over the Entry.
Attributes:
password - Characters will be drawn as '*' (asterisk).
padding - Additional padding between text and borders. Default is 2.
border - The border style to set for the Entry.
"""
def __init__ (self, text=""):
Editable.__init__ (self)
self._realtext = None
self._padding = 2
self._border = BORDER_SUNKEN
self._password = False
self._offset = 0 # Front offset in pixels.
# Pixel sizes of text to the left and right of caret, and char
# the caret is at.
self._leftsize = (0, 0)
self._rightsize = (0, 0)
self._cursize = (0, 0)
self._font = None
self._signals[SIG_MOUSEDOWN] = []
self._signals[SIG_MOUSEMOVE] = []
self.minsize = 94, 24 # Default size to use.
self.text = text
def set_padding (self, padding):
"""E.set_padding (...) -> None
Sets the padding between the edges and text of the Entry.
The padding value is the amount of pixels to place between the
edges of the Entry and the displayed text.
Raises a TypeError, if the argument is not a positive integer.
"""
if (type (padding) != int) or (padding < 0):
raise TypeError ("Argument must be a positive integer")
self._padding = padding
self.dirty = True
def set_password (self, password):
"""E.set_password (...) -> None
When this is set this to True, the entry's content will be drawn
with '*' (asterisk) characters instead of the actual
characters. This is useful for password dialogs, where it is
undesirable for the password to be displayed on-screen.
"""
self._password = password
self.dirty = True
def _set_caret_position (self, eventarea, position):
"""W._set_caret_position (...) -> None
Sets the position of the caret based on the given pixel position.
"""
# Get the relative mouse point.
mpos = (position[0] - eventarea.x - self.padding,
position[1] - eventarea.y - self.padding)
if mpos[0] <= 0:
# User clicked on the border or into the padding area.
self.caret = 0
return
caret = self.caret
mpoint = self._offset + mpos[0]
left, right, current = self._get_text_overhang (caret)
if mpoint > (left[0] + right[0]):
# User clicked past the length of the text.
self.caret = len (self.text)
return
# Find the click inside the text area
while (mpoint > left[0]) and (caret <= len (self.text)):
caret += 1
left, right, current = self._get_text_overhang (caret)
while (mpoint < left[0]) and (caret > 0):
caret -= 1
left, right, current = self._get_text_overhang (caret)
# Move caret to left or right, based on center of clicked character
if mpoint > (left[0] + (current[0] / 2) + self.border):
caret += 1
self.caret = caret
def _get_text_overhang (self, pos):
"""E._get_text_overhang (...) -> (int, int), (int, int), (int, int)
Gets the pixel sizes to the left and right of the caret and
the character size the caret is at in this order..
"""
# TODO: the text display should be separated in an own TextView
# class.
if self._font == None:
self._cursize = (0, 0)
return
text = self.text
if self.password:
text = '*' * len (self.text)
self._leftsize = self._font.size (text[:pos])
self._rightsize = self._font.size (text[pos:])
try:
self._cursize = self._font.size (text[pos])
except IndexError:
self._cursize = (0, 0)
return self._leftsize, self._rightsize, self._cursize
def _calculate_offset (self, textwidth, font):
"""E._calculate_offset (...) -> int
Calculates the left pixel offset for the Entry text.
"""
self._font = font
self._get_text_overhang (self.caret)
bump_size = self.minsize[0] / 4
while self._leftsize[0] < self._offset:
new_offset = self._offset - bump_size
self._offset = max (new_offset, 0)
while self._leftsize[0] > (self._offset + textwidth):
self._offset += bump_size
return min (-self._offset, 0)
def notify (self, event):
"""E.notify (...) -> None
Notifies the Entry about an event.
"""
if not self.sensitive:
return
if event.signal == SIG_MOUSEDOWN:
eventarea = self.rect_to_client ()
if eventarea.collidepoint (event.data.pos):
self.run_signal_handlers (SIG_MOUSEDOWN, event.data)
if event.data.button == 1:
self._caret_visible = True
self._set_caret_position (eventarea, event.data.pos)
if not self.focus:
self.activate ()
event.handled = True
elif event.signal == SIG_MOUSEMOVE:
eventarea = self.rect_to_client ()
if eventarea.collidepoint (event.data.pos):
self.run_signal_handlers (SIG_MOUSEMOVE, event.data)
self.entered = True
event.handled = True
else:
self.entered = False
Editable.notify (self, event)
def set_border (self, border):
"""E.set_border (...) -> None
Sets the border type to be used by the Entry.
Raises a ValueError, if the passed argument is not a value from
BORDER_TYPES
"""
if border not in BORDER_TYPES:
raise ValueError ("border must be a value from BORDER_TYPES")
self._border = border
self.dirty = True
def draw_bg (self):
"""E.draw_bg () -> Surface
Draws the surface of the Entry and returns it.
Draws the background surface of the Entry and returns it.
Creates the visible background surface of the Entry and returns
it to the caller.
"""
return base.GlobalStyle.engine.draw_entry (self)
def draw (self):
"""E.draw () -> None
Draws the Entry surface.
"""
Editable.draw (self)
cls = self.__class__
style = base.GlobalStyle
engine = style.engine
st = self.style or style.get_style (cls)
border = style.get_border_size (cls, st, self.border)
text = self.text
rect = self.image.get_rect ()
if self.password:
text = '*' * len (self.text)
rtext = style.engine.draw_string (text, self.state, cls, st)
# The 'inner' surface, which we will use for blitting the text.
sf_text = engine.draw_rect (rect.width - 2 * (self.padding + border),
rect.height - 2 * (self.padding + border),
self.state, cls, st)
# Adjust entry offset based on caret location.
font = String.create_font \
(style.get_style_entry (cls, st, "font", "name"),
style.get_style_entry (cls, st, "font", "size"))
rect_sftext = sf_text.get_rect ()
blit_pos = self._calculate_offset (rect_sftext.width, font)
sf_text.blit (rtext, (blit_pos, 0))
# Draw caret.
if self.focus and self.caret_visible:
# The caret position is at the end of the left overhang.
caret_pos = self._get_text_overhang (self.caret)[0][0]
engine.draw_caret (sf_text, blit_pos + caret_pos, 1, 2, self.state,
cls, st)
rect_sftext.center = rect.center
self.image.blit (sf_text, rect_sftext)
padding = property (lambda self: self._padding,
lambda self, var: self.set_padding (var),
doc = "The additional padding for the Entry.")
border = property (lambda self: self._border,
lambda self, var: self.set_border (var),
doc = "The border style to set for the Entry.")
password = property (lambda self: self._password,
lambda self, var: self.set_password (var),
doc = "Indicates the password mode for the Entry.")
syntax highlighted by Code2HTML, v. 0.9.1