added keyboard copy/paste selection in EditableWidget (addresses #105)

increased width of edit cursor
This commit is contained in:
thrust26 2020-11-04 12:36:18 +01:00
parent e8464fb0bf
commit c6093a8d6f
7 changed files with 297 additions and 138 deletions

View File

@ -64,13 +64,6 @@ void EventHandlerSDL2::copyText(const string& text) const
SDL_SetClipboardText(text.c_str()); SDL_SetClipboardText(text.c_str());
}; };
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventHandlerSDL2::cutText(string& text) const
{
copyText(text);
text = "";
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string EventHandlerSDL2::pasteText(string& text) const string EventHandlerSDL2::pasteText(string& text) const
{ {

View File

@ -48,7 +48,6 @@ private:
Clipboard methods. Clipboard methods.
*/ */
void copyText(const string& text) const override; void copyText(const string& text) const override;
void cutText(string& text) const override;
string pasteText(string& text) const override; string pasteText(string& text) const override;
/** /**

View File

@ -718,7 +718,7 @@ void PromptWidget::textCut()
#if defined(PSEUDO_CUT_COPY_PASTE) #if defined(PSEUDO_CUT_COPY_PASTE)
string text = getLine(); string text = getLine();
instance().eventHandler().cutText(text); instance().eventHandler().copyText(text);
// Remove the current line // Remove the current line
_currentPos = _promptStartPos; _currentPos = _promptStartPos;

View File

@ -342,7 +342,6 @@ class EventHandler
Clipboard methods. Clipboard methods.
*/ */
virtual void copyText(const string& text) const = 0; virtual void copyText(const string& text) const = 0;
virtual void cutText(string& text) const = 0;
virtual string pasteText(string& text) const = 0; virtual string pasteText(string& text) const = 0;
#endif #endif

View File

@ -68,6 +68,7 @@ void EditTextWidget::handleMouseDown(int x, int y, MouseButton b, int clickCount
if(!isEditable()) if(!isEditable())
return; return;
resetSelection();
x += _editScrollOffset; x += _editScrollOffset;
int width = 0; int width = 0;
@ -105,8 +106,10 @@ void EditTextWidget::drawWidget(bool hilite)
_changed && onTop && isEnabled() _changed && onTop && isEnabled()
? kDbgChangedTextColor ? kDbgChangedTextColor
: onTop && isEnabled() ? _textcolor : kColor, : onTop && isEnabled() ? _textcolor : kColor,
TextAlign::Left, isEditable() ? -_editScrollOffset : 0, !isEditable()); TextAlign::Left, scrollOffset(), !isEditable());
// Draw selected text
drawSelection();
// Draw the caret // Draw the caret
drawCaret(); drawCaret();
} }
@ -120,6 +123,7 @@ Common::Rect EditTextWidget::getEditRect() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EditTextWidget::lostFocusWidget() void EditTextWidget::lostFocusWidget()
{ {
EditableWidget::lostFocusWidget();
// If we loose focus, 'commit' the user changes // If we loose focus, 'commit' the user changes
_backupString = editString(); _backupString = editString();
} }

View File

@ -75,6 +75,12 @@ void EditableWidget::setEditable(bool editable, bool hiliteBG)
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EditableWidget::lostFocusWidget()
{
_selectSize = 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::tryInsertChar(char c, int pos) bool EditableWidget::tryInsertChar(char c, int pos)
{ {
@ -112,15 +118,142 @@ bool EditableWidget::handleKeyDown(StellaKey key, StellaMod mod)
if(StellaModTest::isAlt(mod)) if(StellaModTest::isAlt(mod))
return true; return true;
if(StellaModTest::isControl(mod) && handleControlKeys(key, mod))
return true;
if(StellaModTest::isShift(mod) && handleShiftKeys(key)) if(StellaModTest::isShift(mod) && handleShiftKeys(key))
return true; return true;
if(StellaModTest::isControl(mod) && handleControlKeys(key))
return true;
return handleNormalKeys(key); return handleNormalKeys(key);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::handleControlKeys(StellaKey key, StellaMod mod)
{
bool shift = StellaModTest::isShift(mod);
bool handled = true;
bool dirty = true;
switch(key)
{
case KBDK_A:
setCaretPos(0);
_selectSize = -int(_editString.size());
break;
case KBDK_C:
case KBDK_INSERT:
copySelectedText();
break;
case KBDK_E:
if(shift)
_selectSize += _caretPos - int(_editString.size());
else
_selectSize = 0;
setCaretPos(int(_editString.size()));
break;
case KBDK_D:
handled = killChar(+1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_K: // TODO
handled = killLine(+1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_U: // TODO
handled = killLine(-1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_V:
pasteSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_W: // TODO
handled = killLastWord();
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_X:
cutSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_LEFT:
handled = moveWord(-1, shift);
if(!shift)
_selectSize = 0;
break;
case KBDK_RIGHT:
handled = moveWord(+1, shift);
if(!shift)
_selectSize = 0;
break;
default:
handled = false;
dirty = false;
}
if(dirty)
setDirty();
return handled;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::handleShiftKeys(StellaKey key)
{
bool handled = true;
switch(key)
{
case KBDK_DELETE:
case KBDK_KP_PERIOD:
cutSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_INSERT:
pasteSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_LEFT:
if(_caretPos > 0)
handled = moveCaretPos(-1);
break;
case KBDK_RIGHT:
if(_caretPos < int(_editString.size()))
handled = moveCaretPos(+1);
break;
case KBDK_HOME:
handled = moveCaretPos(-_caretPos);
break;
case KBDK_END:
handled = moveCaretPos(int(_editString.size()) - _caretPos);
break;
default:
handled = false;
}
if(handled)
setDirty();
return handled;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::handleNormalKeys(StellaKey key) bool EditableWidget::handleNormalKeys(StellaKey key)
{ {
@ -128,6 +261,12 @@ bool EditableWidget::handleNormalKeys(StellaKey key)
switch(key) switch(key)
{ {
case KBDK_LSHIFT:
case KBDK_RSHIFT:
// stay in select mode
handled = false;
break;
case KBDK_RETURN: case KBDK_RETURN:
case KBDK_KP_ENTER: case KBDK_KP_ENTER:
// confirm edit and exit editmode // confirm edit and exit editmode
@ -174,120 +313,24 @@ bool EditableWidget::handleNormalKeys(StellaKey key)
} }
if(handled) if(handled)
setDirty();
return handled;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::handleShiftKeys(StellaKey key)
{
bool handled = true;
switch(key)
{ {
case KBDK_DELETE:
case KBDK_KP_PERIOD:
cutSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_INSERT:
pasteSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
default:
handled = false;
}
if(handled)
setDirty(); setDirty();
_selectSize = 0;
return handled;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::handleControlKeys(StellaKey key)
{
bool handled = true;
switch(key)
{
case KBDK_A:
setCaretPos(0);
break;
case KBDK_C:
copySelectedText();
break;
case KBDK_E:
setCaretPos(int(_editString.size()));
break;
case KBDK_D:
handled = killChar(+1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_K:
handled = killLine(+1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_U:
handled = killLine(-1);
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_V:
pasteSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_W:
handled = killLastWord();
if(handled) sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_X:
cutSelectedText();
sendCommand(EditableWidget::kChangedCmd, key, _id);
break;
case KBDK_LEFT:
handled = moveWord(-1);
break;
case KBDK_RIGHT:
handled = moveWord(+1);
break;
case KBDK_INSERT:
copySelectedText();
break;
default:
handled = false;
} }
if(handled)
setDirty();
return handled; return handled;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int EditableWidget::getCaretOffset() const int EditableWidget::getCaretOffset() const
{ {
int caretpos = 0; int caretOfs = 0;
for (int i = 0; i < _caretPos; i++) for (int i = 0; i < _caretPos; i++)
caretpos += _font.getCharWidth(_editString[i]); caretOfs += _font.getCharWidth(_editString[i]);
caretpos -= _editScrollOffset; caretOfs -= _editScrollOffset;
return caretpos; return caretOfs;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -307,7 +350,46 @@ void EditableWidget::drawCaret()
y += _y; y += _y;
FBSurface& s = _boss->dialog().surface(); FBSurface& s = _boss->dialog().surface();
s.vLine(x, y+2, y + editRect.h() - 2, kTextColorHi); s.vLine(x, y + 2, y + editRect.h() - 2, kTextColorHi);
s.vLine(x-1, y + 2, y + editRect.h() - 2, kTextColorHi);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EditableWidget::drawSelection()
{
// Only draw if item is visible
if(!_editable || !isVisible() || !_boss->isVisible() || !_hasFocus
|| !_selectSize)
return;
FBSurface& s = _boss->dialog().surface();
string text = selectString();
const Common::Rect& editRect = getEditRect();
int x = editRect.x();
int y = editRect.y();
int w = editRect.w();
int h = editRect.h();
int wt = int(text.length()) * _font.getMaxCharWidth() + 1;
int dx = selectPos() * _font.getMaxCharWidth() - _editScrollOffset;
if(dx < 0)
{
// selected text starts left of displayed rect
text = text.substr(-(dx - 1) / _font.getMaxCharWidth());
wt += dx;
dx = 0;
}
else
x += dx;
// limit selection to the right of displayed rect
w = std::min(w - dx + 1, wt);
x += _x;
y += _y;
s.fillRect(x - 1, y + 1, w + 1, h - 3, kTextColorHi);
s.drawString(_font, text, x, y + 1, w, h,
kTextColorInv, TextAlign::Left, 0, false);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -319,6 +401,17 @@ bool EditableWidget::setCaretPos(int newPos)
return adjustOffset(); return adjustOffset();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::moveCaretPos(int direction)
{
if(setCaretPos(_caretPos + direction))
{
_selectSize -= direction;
return true;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::adjustOffset() bool EditableWidget::adjustOffset()
{ {
@ -328,18 +421,18 @@ bool EditableWidget::adjustOffset()
// For some reason (differences in ScummVM event handling??), // For some reason (differences in ScummVM event handling??),
// this method should always return true. // this method should always return true.
int caretpos = getCaretOffset(); int caretOfs = getCaretOffset();
const int editWidth = getEditRect().w(); const int editWidth = getEditRect().w();
if (caretpos < 0) if (caretOfs < 0)
{ {
// scroll left // scroll left
_editScrollOffset += caretpos; _editScrollOffset += caretOfs;
} }
else if (caretpos >= editWidth) else if (caretOfs >= editWidth)
{ {
// scroll right // scroll right
_editScrollOffset -= (editWidth - caretpos); _editScrollOffset -= (editWidth - caretOfs);
} }
else if (_editScrollOffset > 0) else if (_editScrollOffset > 0)
{ {
@ -356,11 +449,19 @@ bool EditableWidget::adjustOffset()
return true; return true;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int EditableWidget::scrollOffset()
{
return _editable ? -_editScrollOffset : 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::killChar(int direction) bool EditableWidget::killChar(int direction)
{ {
bool handled = false; bool handled = killSelectedText();
if(!handled)
{
if(direction == -1) // Delete previous character (backspace) if(direction == -1) // Delete previous character (backspace)
{ {
if(_caretPos > 0) if(_caretPos > 0)
@ -375,6 +476,7 @@ bool EditableWidget::killChar(int direction)
_editString.erase(_caretPos, 1); _editString.erase(_caretPos, 1);
handled = true; handled = true;
} }
}
return handled; return handled;
} }
@ -442,7 +544,7 @@ bool EditableWidget::killLastWord()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::moveWord(int direction) bool EditableWidget::moveWord(int direction, bool select)
{ {
bool handled = false; bool handled = false;
bool space = true; bool space = true;
@ -461,6 +563,8 @@ bool EditableWidget::moveWord(int direction)
space = false; space = false;
currentPos--; currentPos--;
if(select)
_selectSize++;
} }
_caretPos = currentPos; _caretPos = currentPos;
handled = true; handled = true;
@ -478,6 +582,8 @@ bool EditableWidget::moveWord(int direction)
space = false; space = false;
currentPos++; currentPos++;
if(select)
_selectSize--;
} }
_caretPos = currentPos; _caretPos = currentPos;
handled = true; handled = true;
@ -486,12 +592,56 @@ bool EditableWidget::moveWord(int direction)
return handled; return handled;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const string EditableWidget::selectString() const
{
if(_selectSize)
{
int caretPos = _caretPos;
int selectSize = _selectSize;
if(selectSize < 0)
{
caretPos += selectSize;
selectSize = -selectSize;
}
return _editString.substr(caretPos, selectSize);
}
return EmptyString;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int EditableWidget::selectPos()
{
if(_selectSize < 0)
return _caretPos + _selectSize;
else
return _caretPos;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EditableWidget::killSelectedText()
{
if(_selectSize)
{
if(_selectSize < 0)
{
_caretPos += _selectSize;
_selectSize = -_selectSize;
}
_editString.erase(_caretPos, _selectSize);
_selectSize = 0;
return true;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EditableWidget::cutSelectedText() void EditableWidget::cutSelectedText()
{ {
#if defined(PSEUDO_CUT_COPY_PASTE) #if defined(PSEUDO_CUT_COPY_PASTE)
instance().eventHandler().cutText(_editString); instance().eventHandler().copyText(selectString());
_caretPos = 0; killSelectedText();
#endif #endif
} }
@ -499,7 +649,7 @@ void EditableWidget::cutSelectedText()
void EditableWidget::copySelectedText() void EditableWidget::copySelectedText()
{ {
#if defined(PSEUDO_CUT_COPY_PASTE) #if defined(PSEUDO_CUT_COPY_PASTE)
instance().eventHandler().copyText(_editString); instance().eventHandler().copyText(selectString());
#endif #endif
} }
@ -507,7 +657,11 @@ void EditableWidget::copySelectedText()
void EditableWidget::pasteSelectedText() void EditableWidget::pasteSelectedText()
{ {
#if defined(PSEUDO_CUT_COPY_PASTE) #if defined(PSEUDO_CUT_COPY_PASTE)
instance().eventHandler().pasteText(_editString); string text;
_caretPos = int(_editString.length());
instance().eventHandler().pasteText(text);
killSelectedText();
_editString.insert(_caretPos, text);
_caretPos += int(text.length());
#endif #endif
} }

View File

@ -64,6 +64,8 @@ class EditableWidget : public Widget, public CommandSender
void setTextFilter(const TextFilter& filter) { _filter = filter; } void setTextFilter(const TextFilter& filter) { _filter = filter; }
protected: protected:
void lostFocusWidget() override;
virtual void startEditMode() { setFlags(Widget::FLAG_WANTS_RAWDATA); } virtual void startEditMode() { setFlags(Widget::FLAG_WANTS_RAWDATA); }
virtual void endEditMode() { clearFlags(Widget::FLAG_WANTS_RAWDATA); } virtual void endEditMode() { clearFlags(Widget::FLAG_WANTS_RAWDATA); }
virtual void abortEditMode() { clearFlags(Widget::FLAG_WANTS_RAWDATA); } virtual void abortEditMode() { clearFlags(Widget::FLAG_WANTS_RAWDATA); }
@ -72,22 +74,29 @@ class EditableWidget : public Widget, public CommandSender
virtual int getCaretOffset() const; virtual int getCaretOffset() const;
void drawCaret(); void drawCaret();
bool setCaretPos(int newPos); bool setCaretPos(int newPos);
bool moveCaretPos(int direction);
bool adjustOffset(); bool adjustOffset();
// This method is used internally by child classes wanting to // This method is used internally by child classes wanting to
// access/edit the internal buffer // access/edit the internal buffer
string& editString() { return _editString; } string& editString() { return _editString; }
const string selectString() const;
void resetSelection() { _selectSize = 0; }
void drawSelection();
int scrollOffset();
private: private:
// Line editing // Line editing
bool handleControlKeys(StellaKey key); bool handleControlKeys(StellaKey key, StellaMod mod);
bool handleShiftKeys(StellaKey key); bool handleShiftKeys(StellaKey key);
bool handleNormalKeys(StellaKey key); bool handleNormalKeys(StellaKey key);
bool killChar(int direction); bool killChar(int direction);
bool killLine(int direction); bool killLine(int direction);
bool killLastWord(); bool killLastWord();
bool moveWord(int direction); bool moveWord(int direction, bool select);
bool killSelectedText();
int selectPos();
// Clipboard // Clipboard
void cutSelectedText(); void cutSelectedText();
void copySelectedText(); void copySelectedText();
@ -101,6 +110,7 @@ class EditableWidget : public Widget, public CommandSender
bool _editable{true}; bool _editable{true};
string _editString; string _editString;
int _caretPos{0}; int _caretPos{0};
int _selectSize{0};
protected: protected:
int _editScrollOffset{0}; int _editScrollOffset{0};