Move more text editing code into class EditableWidget; ListWidget now has all the editing capabilities of EditTextWidget

svn-id: r16694
This commit is contained in:
Max Horn 2005-01-29 18:04:34 +00:00
parent 66c524f0ee
commit b43a53f74d
8 changed files with 187 additions and 197 deletions

11
TODO
View File

@ -194,11 +194,12 @@ Files
GUI
===
* Remove hard coded 320x200 assumptions, use game screen size
* Add ability to scale GUI (ie. to make the GUI less tiny in COMI)
* Remove code duplication between EditTextWidget and ListWidget (i.e. text
editing code; maybe we can factor that out into a common base or aggregate
class... not yet sure).
* Fix EditTextWidget::drawCaret and ListWidget::drawCaret support for alternate
* EditableWidget: Make it possible to specify a min/max length for the text
* EditableWidget: Let setEditString filter the string it gets
* EditableWidget: Right now, custom filtering requires the user to subclass;
it would be nice if there was simply a "validator hook" or so.
Maybe take some inspiration from Java's Swing in this matter.
* Improve EditTextWidget::drawCaret and ListWidget::drawCaret support for alternate
fonts (the current code overdraws chars partly, and relies on the fact that
our default built-in font has a separation pixel column on the *left* side;
most other bitmap fonts have it on the right, though). To this end, we maybe

View File

@ -28,16 +28,15 @@ namespace GUI {
EditTextWidget::EditTextWidget(GuiObject *boss, int x, int y, int w, int h, const String &text)
: EditableWidget(boss, x, y - 1, w, h + 2) {
_editString = text;
_backupString = text;
_flags = WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE;
_type = kEditTextWidget;
_caretPos = _editString.size();
setEditString(text);
}
_editScrollOffset = (g_gui.getStringWidth(_editString) - (getEditRect().width()));
if (_editScrollOffset < 0)
_editScrollOffset = 0;
void EditTextWidget::setEditString(const String &str) {
EditableWidget::setEditString(str);
_backupString = str;
}
void EditTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
@ -61,75 +60,6 @@ void EditTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
draw();
}
bool EditTextWidget::tryInsertChar(char c, int pos) {
if (isprint(c)) {
_editString.insertChar(c, pos);
return true;
}
return false;
}
bool EditTextWidget::handleKeyDown(uint16 ascii, int keycode, int modifiers) {
bool handled = true;
bool dirty = false;
// First remove caret
if (_caretVisible)
drawCaret(true);
switch (keycode) {
case '\n': // enter/return
case '\r':
// confirm edit and exit editmode
endEditMode();
dirty = true;
break;
case 27: // escape
abortEditMode();
dirty = true;
break;
case 8: // backspace
if (_caretPos > 0) {
_caretPos--;
_editString.deleteChar(_caretPos);
}
dirty = true;
break;
case 127: // delete
_editString.deleteChar(_caretPos);
dirty = true;
break;
case 256 + 20: // left arrow
if (_caretPos > 0) {
dirty = setCaretPos(_caretPos - 1);
}
break;
case 256 + 19: // right arrow
if (_caretPos < (int)_editString.size()) {
dirty = setCaretPos(_caretPos + 1);
}
break;
case 256 + 22: // home
dirty = setCaretPos(0);
break;
case 256 + 23: // end
dirty = setCaretPos(_editString.size());
break;
default:
if (tryInsertChar((char)ascii, _caretPos)) {
_caretPos++;
dirty = true;
} else {
handled = false;
}
}
if (dirty)
draw();
return handled;
}
void EditTextWidget::drawWidget(bool hilite) {
// Draw a thin frame around us.
@ -149,16 +79,6 @@ Common::Rect EditTextWidget::getEditRect() const {
return r;
}
int EditTextWidget::getCaretOffset() const {
int caretpos = 0;
for (int i = 0; i < _caretPos; i++)
caretpos += g_gui.getCharWidth(_editString[i]);
caretpos -= _editScrollOffset;
return caretpos;
}
void EditTextWidget::receivedFocusWidget() {
}
@ -176,46 +96,8 @@ void EditTextWidget::endEditMode() {
}
void EditTextWidget::abortEditMode() {
_editString = _backupString;
_caretPos = _editString.size();
_editScrollOffset = (g_gui.getStringWidth(_editString) - (getEditRect().width()));
if (_editScrollOffset < 0)
_editScrollOffset = 0;
setEditString(_backupString);
releaseFocus();
}
bool EditTextWidget::setCaretPos(int newPos) {
assert(newPos >= 0 && newPos <= (int)_editString.size());
_caretPos = newPos;
return adjustOffset();
}
bool EditTextWidget::adjustOffset() {
// check if the caret is still within the textbox; if it isn't,
// adjust _editScrollOffset
int caretpos = getCaretOffset();
const int editWidth = getEditRect().width();
if (caretpos < 0) {
// scroll left
_editScrollOffset += caretpos;
return true;
} else if (caretpos >= editWidth) {
// scroll right
_editScrollOffset -= (editWidth - caretpos);
return true;
} else if (_editScrollOffset > 0) {
const int strWidth = g_gui.getStringWidth(_editString);
if (strWidth - _editScrollOffset < editWidth) {
// scroll right
_editScrollOffset = (strWidth - editWidth);
if (_editScrollOffset < 0)
_editScrollOffset = 0;
}
}
return false;
}
} // End of namespace GUI

View File

@ -36,11 +36,9 @@ protected:
public:
EditTextWidget(GuiObject *boss, int x, int y, int w, int h, const String &text);
// void setString(const String &str) { _editString = str; }
const String &getString() const { return _editString; }
void setEditString(const String &str);
virtual void handleMouseDown(int x, int y, int button, int clickCount);
virtual bool handleKeyDown(uint16 ascii, int keycode, int modifiers);
virtual bool wantsFocus() { return true; };
@ -54,11 +52,6 @@ protected:
void abortEditMode();
Common::Rect getEditRect() const;
int getCaretOffset() const;
bool setCaretPos(int newPos);
bool adjustOffset();
virtual bool tryInsertChar(char c, int pos);
};
} // End of namespace GUI

View File

@ -124,7 +124,7 @@ void ListWidget::handleMouseDown(int x, int y, int button, int clickCount) {
}
// TODO: Determine where inside the string the user clicked and place the
// caret accordingly.
// caret accordingly. See _editScrollOffset and EditTextWidget::handleMouseDown.
draw();
}
@ -193,35 +193,8 @@ bool ListWidget::handleKeyDown(uint16 ascii, int keycode, int modifiers) {
scrollToCurrent();
} else if (_editMode) {
if (_caretVisible)
drawCaret(true);
switch (keycode) {
case '\n': // enter/return
case '\r':
// confirm edit and exit editmode
endEditMode();
dirty = true;
break;
case 27: // escape
// abort edit and exit editmode
abortEditMode();
dirty = true;
break;
case 8: // backspace
_editString.deleteLastChar();
dirty = true;
break;
default:
if (isprint((char)ascii)) {
_editString += (char)ascii;
dirty = true;
} else {
handled = false;
}
}
// Class EditableWidget handles all text editing related key presses for us
handled = EditableWidget::handleKeyDown(ascii, keycode, modifiers);
} else {
// not editmode
@ -313,6 +286,7 @@ void ListWidget::drawWidget(bool hilite) {
NewGui *gui = &g_gui;
int i, pos, len = _list.size();
Common::String buffer;
int offset, deltax;
// Draw a thin frame around the list.
gui->hLine(_x, _y, _x + _w - 1, gui->_color);
@ -321,26 +295,41 @@ void ListWidget::drawWidget(bool hilite) {
// Draw the list items
for (i = 0, pos = _currentPos; i < _entriesPerPage && pos < len; i++, pos++) {
if (_numberingMode != kListNumberingOff) {
char temp[10];
sprintf(temp, "%2d. ", (pos + _numberingMode));
buffer = temp;
} else {
buffer.clear();
}
if (_selectedItem == pos && _editMode)
buffer += _editString;
else
buffer += _list[pos];
const OverlayColor textColor = (_selectedItem == pos && _hasFocus) ? gui->_bgcolor : gui->_textcolor;
const int y = _y + 2 + kLineHeight * i;
// Draw the selected item inverted, on a highlighted background.
if (_selectedItem == pos) {
if (_hasFocus)
gui->fillRect(_x + 1, _y + 1 + kLineHeight * i, _w - 1, kLineHeight, gui->_textcolorhi);
else
gui->frameRect(_x + 1, _y + 1 + kLineHeight * i, _w - 1, kLineHeight, gui->_textcolorhi);
}
gui->drawString(buffer, _x + 2, _y + 2 + kLineHeight * i, _w - 4,
(_selectedItem == pos && _hasFocus) ? gui->_bgcolor : gui->_textcolor);
// If in numbering mode, we first print a number prefix
if (_numberingMode != kListNumberingOff) {
char temp[10];
sprintf(temp, "%2d. ", (pos + _numberingMode));
buffer = temp;
gui->drawString(buffer, _x + 2, y, _w - 4, textColor);
offset = gui->getStringWidth(buffer);
} else {
offset = 0;
}
Common::Rect r(getEditRect());
if (_selectedItem == pos && _editMode) {
buffer = _editString;
adjustOffset();
deltax = -_editScrollOffset;
gui->drawString(buffer, _x + r.left, y, r.width(), textColor, kTextAlignLeft, deltax, false);
} else {
buffer = _list[pos];
deltax = 0;
gui->drawString(buffer, _x + r.left, y, r.width(), textColor);
}
}
}
@ -359,14 +348,6 @@ Common::Rect ListWidget::getEditRect() const {
return r;
}
int ListWidget::getCaretOffset() const {
int caretpos = 0;
caretpos += g_gui.getStringWidth(_editString);
return caretpos;
}
void ListWidget::scrollToCurrent() {
// Only do something if the current item is not in our view port
if (_selectedItem < _currentPos) {
@ -389,8 +370,7 @@ void ListWidget::scrollToCurrent() {
void ListWidget::startEditMode() {
if (_editable && !_editMode && _selectedItem >= 0) {
_editMode = true;
_editString = _list[_selectedItem];
_caretPos = _editString.size();
setEditString(_list[_selectedItem]);
draw();
}
}

View File

@ -96,7 +96,6 @@ protected:
void abortEditMode();
Common::Rect getEditRect() const;
int getCaretOffset() const;
void lostFocusWidget();
void scrollToCurrent();

View File

@ -1,5 +1,5 @@
/* ScummVM - Scumm Interpreter
* Copyright (C) 2002-2005 The ScummVM project
* Copyright (C) 2005 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -39,6 +39,25 @@ EditableWidget::EditableWidget(GuiObject *boss, int x, int y, int w, int h)
EditableWidget::~EditableWidget() {
}
void EditableWidget::setEditString(const String &str) {
// TODO: We probably should filter the input string here,
// e.g. using tryInsertChar.
_editString = str;
_caretPos = _editString.size();
_editScrollOffset = (g_gui.getStringWidth(_editString) - (getEditRect().width()));
if (_editScrollOffset < 0)
_editScrollOffset = 0;
}
bool EditableWidget::tryInsertChar(char c, int pos) {
if (isprint(c)) {
_editString.insertChar(c, pos);
return true;
}
return false;
}
void EditableWidget::handleTickle() {
uint32 time = getMillis();
if (_caretTime < time) {
@ -47,6 +66,77 @@ void EditableWidget::handleTickle() {
}
}
bool EditableWidget::handleKeyDown(uint16 ascii, int keycode, int modifiers) {
bool handled = true;
bool dirty = false;
// First remove caret
if (_caretVisible)
drawCaret(true);
switch (keycode) {
case '\n': // enter/return
case '\r':
// confirm edit and exit editmode
endEditMode();
dirty = true;
break;
case 27: // escape
abortEditMode();
dirty = true;
break;
case 8: // backspace
if (_caretPos > 0) {
_caretPos--;
_editString.deleteChar(_caretPos);
dirty = true;
}
break;
case 127: // delete
_editString.deleteChar(_caretPos);
dirty = true;
break;
case 256 + 20: // left arrow
if (_caretPos > 0) {
dirty = setCaretPos(_caretPos - 1);
}
break;
case 256 + 19: // right arrow
if (_caretPos < (int)_editString.size()) {
dirty = setCaretPos(_caretPos + 1);
}
break;
case 256 + 22: // home
dirty = setCaretPos(0);
break;
case 256 + 23: // end
dirty = setCaretPos(_editString.size());
break;
default:
if (tryInsertChar((char)ascii, _caretPos)) {
_caretPos++;
dirty = true;
} else {
handled = false;
}
}
if (dirty)
draw();
return handled;
}
int EditableWidget::getCaretOffset() const {
int caretpos = 0;
for (int i = 0; i < _caretPos; i++)
caretpos += g_gui.getCharWidth(_editString[i]);
caretpos -= _editScrollOffset;
return caretpos;
}
void EditableWidget::drawCaret(bool erase) {
// Only draw if item is visible
if (!isVisible() || !_boss->isVisible())
@ -70,5 +160,39 @@ void EditableWidget::drawCaret(bool erase) {
_caretVisible = !erase;
}
bool EditableWidget::setCaretPos(int newPos) {
assert(newPos >= 0 && newPos <= (int)_editString.size());
_caretPos = newPos;
return adjustOffset();
}
bool EditableWidget::adjustOffset() {
// check if the caret is still within the textbox; if it isn't,
// adjust _editScrollOffset
int caretpos = getCaretOffset();
const int editWidth = getEditRect().width();
if (caretpos < 0) {
// scroll left
_editScrollOffset += caretpos;
return true;
} else if (caretpos >= editWidth) {
// scroll right
_editScrollOffset -= (editWidth - caretpos);
return true;
} else if (_editScrollOffset > 0) {
const int strWidth = g_gui.getStringWidth(_editString);
if (strWidth - _editScrollOffset < editWidth) {
// scroll right
_editScrollOffset = (strWidth - editWidth);
if (_editScrollOffset < 0)
_editScrollOffset = 0;
}
}
return false;
}
} // End of namespace GUI

View File

@ -1,5 +1,5 @@
/* ScummVM - Scumm Interpreter
* Copyright (C) 2002-2005 The ScummVM project
* Copyright (C) 2005 The ScummVM project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -27,7 +27,10 @@
namespace GUI {
/**
* Base class for widgets which need to edit text, like ListWidget and
* EditTextWidget.
*/
class EditableWidget : public Widget {
public:
typedef Common::String String;
@ -46,7 +49,11 @@ public:
EditableWidget(GuiObject *boss, int x, int y, int w, int h);
virtual ~EditableWidget();
virtual void setEditString(const String &str);
virtual const String &getEditString() const { return _editString; }
virtual void handleTickle();
virtual bool handleKeyDown(uint16 ascii, int keycode, int modifiers);
protected:
virtual void startEditMode() = 0;
@ -54,8 +61,12 @@ protected:
virtual void abortEditMode() = 0;
virtual Common::Rect getEditRect() const = 0;
virtual int getCaretOffset() const = 0;
virtual int getCaretOffset() const;
void drawCaret(bool erase);
bool setCaretPos(int newPos);
bool adjustOffset();
virtual bool tryInsertChar(char c, int pos);
};
} // End of namespace GUI

View File

@ -303,7 +303,7 @@ void EditGameDialog::open() {
void EditGameDialog::close() {
if (getResult()) {
ConfMan.set("description", _descriptionWidget->getString(), _domain);
ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
if (lang < 0)
@ -389,7 +389,7 @@ void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
case kOKCmd: {
// Write back changes made to config object
String newDomain(_domainWidget->getString());
String newDomain(_domainWidget->getEditString());
if (newDomain != _domain) {
if (newDomain.isEmpty() || ConfMan.hasGameDomain(newDomain)) {
MessageDialog alert("This game ID is already taken. Please choose another one.");