Allow text selection

FbTk::TextBox now acts like any contemporary input field ;-)
This commit is contained in:
Thomas Lübking 2016-04-24 07:57:08 +02:00 committed by Mathias Gumz
parent 2e8766174e
commit 58b50fb786
2 changed files with 151 additions and 62 deletions

View file

@ -53,7 +53,8 @@ TextBox::TextBox(int screen_num,
m_gc(0),
m_cursor_pos(0),
m_start_pos(0),
m_end_pos(0) {
m_end_pos(0),
m_select_pos(-1) {
FbTk::EventManager::instance()->add(*this, *this);
}
@ -128,6 +129,9 @@ void TextBox::cursorBackward() {
}
void TextBox::backspace() {
if (hasSelection())
return deleteForward();
if (m_start_pos || cursorPosition()) {
FbString t = text();
t.erase(m_start_pos + cursorPosition() - 1, 1);
@ -141,15 +145,27 @@ void TextBox::backspace() {
}
void TextBox::deleteForward() {
if (m_start_pos + m_cursor_pos < m_end_pos) {
std::string::size_type pos = m_start_pos + m_cursor_pos;
int length = 1;
if (hasSelection()) {
pos = std::min(m_start_pos + m_cursor_pos, m_select_pos);
length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - pos;
m_cursor_pos = pos - m_start_pos;
}
if (pos < m_end_pos) {
FbString t = text();
t.erase(m_start_pos + m_cursor_pos, 1);
t.erase(pos, length);
m_text.setLogical(t);
adjustEndPos();
}
if (length > 1)
adjustStartPos();
}
void TextBox::insertText(const std::string &val) {
if (hasSelection())
deleteForward();
FbString t = text();
t.insert(m_start_pos + cursorPosition(), val);
m_text.setLogical(t);
@ -168,20 +184,48 @@ void TextBox::killToEnd() {
}
void TextBox::clear() {
Display *dpy = FbTk::App::instance()->display();
FbWindow::clear();
// center text by default
int center_pos = (height() + font().ascent())/2;
if (gc() == 0)
setGC(DefaultGC(FbTk::App::instance()->display(), screenNumber()));
setGC(DefaultGC(dpy, screenNumber()));
font().drawText(*this, screenNumber(),
gc(),
int cursor_pos = font().textWidth(m_text.visual().c_str() + m_start_pos, m_cursor_pos);
font().drawText(*this, screenNumber(), gc(),
m_text.visual().c_str() + m_start_pos,
m_end_pos - m_start_pos,
0, center_pos); // pos
m_end_pos - m_start_pos, -1, center_pos); // pos
if (hasSelection()) {
int select_pos = m_select_pos <= m_start_pos ? 0 :
font().textWidth(m_text.visual().c_str() + m_start_pos,
m_select_pos - m_start_pos);
int start = std::max(m_start_pos, std::min(m_start_pos + m_cursor_pos, m_select_pos));
int length = std::max(m_start_pos + m_cursor_pos, m_select_pos) - start;
int x = std::min(select_pos, cursor_pos);
int width = std::abs(select_pos - cursor_pos);
XGCValues backup;
XGetGCValues(dpy, gc(), GCForeground|GCBackground, &backup);
XSetForeground(dpy, gc(), backup.foreground);
fillRectangle(gc(), x, 0, width, height());
XColor c;
c.pixel = backup.foreground;
XQueryColor(dpy, DefaultColormap(dpy, screenNumber()), &c);
XSetForeground(dpy, gc(), c.red + c.green + c.blue > 0x17ffe ?
BlackPixel(dpy, screenNumber()) :
WhitePixel(dpy, screenNumber()));
font().drawText(*this, screenNumber(), gc(),
m_text.visual().c_str() + start, length, x, center_pos); // pos
XSetForeground(dpy, gc(), backup.foreground);
}
// draw cursor position
int cursor_pos = font().textWidth(m_text.visual().c_str() + m_start_pos, m_cursor_pos) + 1;
drawLine(gc(), cursor_pos, center_pos, cursor_pos, center_pos - font().height());
}
@ -231,12 +275,52 @@ void TextBox::keyPressEvent(XKeyEvent &event) {
// a modifier key by itself doesn't do anything
if (IsModifierKey(ks)) return;
if (FbTk::KeyUtil::instance().isolateModifierMask(event.state)) { // handle keybindings with state
if ((event.state & ControlMask) == ControlMask) {
switch (ks) {
case XK_Left: {
if (m_select_pos == -1 && (event.state & ShiftMask) == ShiftMask) {
m_select_pos = m_cursor_pos + m_start_pos;
}
if ((event.state & ControlMask) == ControlMask) {
switch (ks) {
case XK_Left: {
unsigned int pos = findEmptySpaceLeft();
if (pos < m_start_pos){
m_start_pos = pos;
m_cursor_pos = 0;
} else if (m_start_pos > 0) {
m_cursor_pos = pos - m_start_pos;
} else {
m_cursor_pos = pos;
}
adjustPos();
}
break;
case XK_Right:
if (!m_text.logical().empty() && m_cursor_pos < m_text.logical().size()){
unsigned int pos = findEmptySpaceRight();
if (pos > m_start_pos)
pos -= m_start_pos;
else
pos = 0;
if (m_start_pos + pos <= m_end_pos)
m_cursor_pos = pos;
else if (m_end_pos < text().size()) {
m_cursor_pos = pos;
m_end_pos = pos;
}
adjustPos();
}
break;
case XK_BackSpace: {
unsigned int pos = findEmptySpaceLeft();
FbString t = text();
t.erase(pos, m_cursor_pos - pos + m_start_pos);
m_text.setLogical(t);
if (pos < m_start_pos){
m_start_pos = pos;
m_cursor_pos = 0;
@ -247,54 +331,17 @@ void TextBox::keyPressEvent(XKeyEvent &event) {
}
adjustPos();
}
break;
case XK_Right:
if (!m_text.logical().empty() && m_cursor_pos < m_text.logical().size()){
unsigned int pos = findEmptySpaceRight();
if (pos > m_start_pos)
pos -= m_start_pos;
else
pos = 0;
if (m_start_pos + pos <= m_end_pos)
m_cursor_pos = pos;
else if (m_end_pos < text().size()) {
m_cursor_pos = pos;
m_end_pos = pos;
}
adjustPos();
}
break;
case XK_BackSpace: {
unsigned int pos = findEmptySpaceLeft();
FbString t = text();
t.erase(pos, m_cursor_pos - pos + m_start_pos);
m_text.setLogical(t);
if (pos < m_start_pos){
m_start_pos = pos;
m_cursor_pos = 0;
} else if (m_start_pos > 0) {
m_cursor_pos = pos - m_start_pos;
} else {
m_cursor_pos = pos;
}
adjustPos();
}
break;
case XK_Delete: {
if (text().empty() || m_cursor_pos >= text().size())
break;
unsigned int pos = findEmptySpaceRight();
FbString t = text();
t.erase(m_cursor_pos + m_start_pos, pos - (m_cursor_pos + m_start_pos));
m_text.setLogical(t);
adjustPos();
}
break;
break;
case XK_Delete: {
if (text().empty() || m_cursor_pos >= text().size())
break;
unsigned int pos = findEmptySpaceRight();
FbString t = text();
t.erase(m_cursor_pos + m_start_pos, pos - (m_cursor_pos + m_start_pos));
m_text.setLogical(t);
adjustPos();
}
break;
}
} else { // no state
@ -358,6 +405,8 @@ void TextBox::keyPressEvent(XKeyEvent &event) {
val += keychar[0];
insertText(val);
}
if ((event.state & ShiftMask) != ShiftMask)
m_select_pos = -1;
clear();
}
@ -369,6 +418,7 @@ void TextBox::setCursorPosition(int pos) {
void TextBox::adjustEndPos() {
m_end_pos = text().size();
m_start_pos = std::min(m_start_pos, m_end_pos);
int text_width = font().textWidth(text().c_str() + m_start_pos, m_end_pos - m_start_pos);
while (text_width > static_cast<signed>(width())) {
m_end_pos--;
@ -381,7 +431,7 @@ void TextBox::adjustStartPos() {
const char* visual = m_text.visual().c_str();
int text_width = font().textWidth(visual, m_end_pos);
if (text_width < static_cast<signed>(width()))
if (m_cursor_pos > -1 && text_width < static_cast<signed>(width()))
return;
int start_pos = 0;
@ -440,4 +490,39 @@ void TextBox::adjustPos(){
adjustStartPos();
}
void TextBox::select(std::string::size_type pos, int length)
{
if (length < 0) {
length = -length;
pos = pos >= length ? pos - length : 0;
}
if (length > 0 && pos < text().size()) {
m_select_pos = pos;
pos = std::min(text().size(), pos + length);
if (pos > m_start_pos)
pos -= m_start_pos;
else
pos = 0;
if (m_start_pos + pos <= m_end_pos)
m_cursor_pos = pos;
else if (m_end_pos < text().size()) {
m_cursor_pos = pos;
m_end_pos = pos;
}
adjustPos();
} else {
m_select_pos = -1;
}
clear();
}
void TextBox::selectAll() {
select(0, m_text.visual().size());
}
} // end namespace FbTk

View file

@ -65,6 +65,10 @@ public:
int cursorPosition() const { return m_cursor_pos; }
int textStartPos() const { return m_start_pos; }
bool hasSelection() const { return m_select_pos != -1 && m_select_pos != m_cursor_pos + m_start_pos; }
void select(std::string::size_type pos, int length);
void selectAll();
unsigned int findEmptySpaceLeft();
unsigned int findEmptySpaceRight();
@ -77,7 +81,7 @@ private:
const FbTk::Font *m_font;
BiDiString m_text;
GC m_gc;
std::string::size_type m_cursor_pos, m_start_pos, m_end_pos;
std::string::size_type m_cursor_pos, m_start_pos, m_end_pos, m_select_pos;
};
} // end namespace FbTk