ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

editable.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "common/rect.h"
00024 #include "common/system.h"
00025 #include "gui/widgets/editable.h"
00026 #include "gui/gui-manager.h"
00027 #include "graphics/font.h"
00028 
00029 namespace GUI {
00030 
00031 EditableWidget::EditableWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd)
00032     : Widget(boss, x, y, w, h, tooltip), CommandSender(boss), _cmd(cmd) {
00033     init();
00034 }
00035 
00036 EditableWidget::EditableWidget(GuiObject *boss, const String &name, const char *tooltip, uint32 cmd)
00037     : Widget(boss, name, tooltip), CommandSender(boss), _cmd(cmd) {
00038     init();
00039 }
00040 
00041 void EditableWidget::init() {
00042     _caretVisible = false;
00043     _caretTime = 0;
00044     _caretPos = 0;
00045 
00046     _caretInverse = false;
00047 
00048     _editScrollOffset = 0;
00049 
00050     _font = ThemeEngine::kFontStyleBold;
00051     _inversion = ThemeEngine::kTextInversionNone;
00052 }
00053 
00054 EditableWidget::~EditableWidget() {
00055 }
00056 
00057 void EditableWidget::reflowLayout() {
00058     Widget::reflowLayout();
00059 
00060     _editScrollOffset = g_gui.getStringWidth(_editString, _font) - getEditRect().width();
00061     if (_editScrollOffset < 0)
00062         _editScrollOffset = 0;
00063 }
00064 
00065 void EditableWidget::setEditString(const String &str) {
00066     // TODO: We probably should filter the input string here,
00067     // e.g. using tryInsertChar.
00068     _editString = str;
00069     _caretPos = 0;
00070 }
00071 
00072 bool EditableWidget::tryInsertChar(byte c, int pos) {
00073     if ((c >= 32 && c <= 127) || c >= 160) {
00074         _editString.insertChar(c, pos);
00075         return true;
00076     }
00077     return false;
00078 }
00079 
00080 void EditableWidget::handleTickle() {
00081     uint32 time = g_system->getMillis();
00082     if (_caretTime < time && isEnabled()) {
00083         _caretTime = time + kCaretBlinkTime;
00084         drawCaret(_caretVisible);
00085     }
00086 }
00087 
00088 bool EditableWidget::handleKeyDown(Common::KeyState state) {
00089     bool handled = true;
00090     bool dirty = false;
00091     bool forcecaret = false;
00092 
00093     if (!isEnabled())
00094         return false;
00095 
00096     // First remove caret
00097     if (_caretVisible)
00098         drawCaret(true);
00099 
00100     // Remap numeric keypad if NUM lock is *not* active.
00101     // This code relies on the fact that the various KEYCODE_KP* values are
00102     // consecutive.
00103     if (0 == (state.flags & Common::KBD_NUM)
00104         && Common::KEYCODE_KP0 <= state.keycode
00105         && state.keycode <= Common::KEYCODE_KP_PERIOD) {
00106         const Common::KeyCode remap[11] = {
00107             Common::KEYCODE_INSERT,     // KEYCODE_KP0
00108             Common::KEYCODE_END,        // KEYCODE_KP1
00109             Common::KEYCODE_DOWN,       // KEYCODE_KP2
00110             Common::KEYCODE_PAGEDOWN,   // KEYCODE_KP3
00111             Common::KEYCODE_LEFT,       // KEYCODE_KP4
00112             Common::KEYCODE_INVALID,    // KEYCODE_KP5
00113             Common::KEYCODE_RIGHT,      // KEYCODE_KP6
00114             Common::KEYCODE_HOME,       // KEYCODE_KP7
00115             Common::KEYCODE_UP,         // KEYCODE_KP8
00116             Common::KEYCODE_PAGEUP,     // KEYCODE_KP9
00117             Common::KEYCODE_DELETE,     // KEYCODE_KP_PERIOD
00118         };
00119         state.keycode = remap[state.keycode - Common::KEYCODE_KP0];
00120     }
00121 
00122     switch (state.keycode) {
00123     case Common::KEYCODE_RETURN:
00124     case Common::KEYCODE_KP_ENTER:
00125         // confirm edit and exit editmode
00126         endEditMode();
00127         dirty = true;
00128         break;
00129 
00130     case Common::KEYCODE_ESCAPE:
00131         abortEditMode();
00132         dirty = true;
00133         break;
00134 
00135     case Common::KEYCODE_BACKSPACE:
00136         if (_caretPos > 0) {
00137             _caretPos--;
00138             _editString.deleteChar(_caretPos);
00139             dirty = true;
00140 
00141             sendCommand(_cmd, 0);
00142         }
00143         forcecaret = true;
00144         break;
00145 
00146     case Common::KEYCODE_DELETE:
00147         if (_caretPos < (int)_editString.size()) {
00148             _editString.deleteChar(_caretPos);
00149             dirty = true;
00150 
00151             sendCommand(_cmd, 0);
00152         }
00153         forcecaret = true;
00154         break;
00155 
00156     case Common::KEYCODE_DOWN:
00157     case Common::KEYCODE_END:
00158         // Move caret to end
00159         dirty = setCaretPos(_editString.size());
00160         forcecaret = true;
00161         break;
00162 
00163     case Common::KEYCODE_LEFT:
00164         // Move caret one left (if possible)
00165         if (_caretPos > 0) {
00166             dirty = setCaretPos(_caretPos - 1);
00167         }
00168         forcecaret = true;
00169         dirty = true;
00170         break;
00171 
00172     case Common::KEYCODE_RIGHT:
00173         // Move caret one right (if possible)
00174         if (_caretPos < (int)_editString.size()) {
00175             dirty = setCaretPos(_caretPos + 1);
00176         }
00177         forcecaret = true;
00178         dirty = true;
00179         break;
00180 
00181     case Common::KEYCODE_UP:
00182     case Common::KEYCODE_HOME:
00183         // Move caret to start
00184         dirty = setCaretPos(0);
00185         forcecaret = true;
00186         break;
00187 
00188     case Common::KEYCODE_v:
00189         if (g_system->hasFeature(OSystem::kFeatureClipboardSupport) && state.flags & Common::KBD_CTRL) {
00190             if (g_system->hasTextInClipboard()) {
00191                 String text = g_system->getTextFromClipboard();
00192                 for (uint32 i = 0; i < text.size(); ++i) {
00193                     if (tryInsertChar(text[i], _caretPos))
00194                         ++_caretPos;
00195                 }
00196                 dirty = true;
00197             }
00198         } else {
00199             defaultKeyDownHandler(state, dirty, forcecaret, handled);
00200         }
00201         break;
00202 
00203     case Common::KEYCODE_c:
00204         if (g_system->hasFeature(OSystem::kFeatureClipboardSupport) && state.flags & Common::KBD_CTRL) {
00205             if (!getEditString().empty())
00206                 g_system->setTextInClipboard(getEditString());
00207         } else {
00208             defaultKeyDownHandler(state, dirty, forcecaret, handled);
00209         }
00210         break;
00211 
00212 #ifdef MACOSX
00213     // Let ctrl-a / ctrl-e move the caret to the start / end of the line.
00214     //
00215     // These shortcuts go back a long time for command line programs. As
00216     // for edit fields in GUIs, they are supported natively on Mac OS X,
00217     // which is why I enabled these shortcuts there.
00218     // On other systems (Windows, Gnome), Ctrl-A by default means
00219     // "select all", which is why I didn't enable the shortcuts there
00220     // for now, to avoid potential confusion.
00221     //
00222     // But since we don't support text selection, and since at least Gnome
00223     // can be configured to also support ctrl-a and ctrl-e, we may want
00224     // to extend this code to other targets, maybe even all. I'll leave
00225     // this to other porters to decide, though.
00226     case Common::KEYCODE_a:
00227     case Common::KEYCODE_e:
00228         if (state.flags & Common::KBD_CTRL) {
00229             if (state.keycode == Common::KEYCODE_a) {
00230                 // Move caret to start
00231                 dirty = setCaretPos(0);
00232                 forcecaret = true;
00233             } else if (state.keycode == Common::KEYCODE_e) {
00234                 // Move caret to end
00235                 dirty = setCaretPos(_editString.size());
00236                 forcecaret = true;
00237             }
00238             break;
00239         }
00240 #endif
00241 
00242     default:
00243         defaultKeyDownHandler(state, dirty, forcecaret, handled);
00244     }
00245 
00246     if (dirty)
00247         markAsDirty();
00248 
00249     if (forcecaret)
00250         makeCaretVisible();
00251 
00252     return handled;
00253 }
00254 
00255 void EditableWidget::defaultKeyDownHandler(Common::KeyState &state, bool &dirty, bool &forcecaret, bool &handled) {
00256     if (state.ascii < 256 && tryInsertChar((byte)state.ascii, _caretPos)) {
00257         _caretPos++;
00258         dirty = true;
00259         forcecaret = true;
00260 
00261         sendCommand(_cmd, 0);
00262     } else {
00263         handled = false;
00264     }
00265 }
00266 
00267 int EditableWidget::getCaretOffset() const {
00268     int caretpos = 0;
00269 
00270     uint last = 0;
00271     for (int i = 0; i < _caretPos; ++i) {
00272         const uint cur = _editString[i];
00273         caretpos += g_gui.getCharWidth(cur, _font) + g_gui.getKerningOffset(last, cur, _font);
00274         last = cur;
00275     }
00276 
00277     caretpos -= _editScrollOffset;
00278 
00279     return caretpos;
00280 }
00281 
00282 void EditableWidget::drawCaret(bool erase) {
00283     // Only draw if item is visible
00284     if (!isVisible() || !_boss->isVisible())
00285         return;
00286 
00287     Common::Rect editRect = getEditRect();
00288 
00289     int x = editRect.left;
00290     int y = editRect.top;
00291 
00292     const int caretOffset = getCaretOffset();
00293     x += caretOffset;
00294 
00295     if (y < 0 || y + editRect.height() > _h)
00296         return;
00297 
00298     x += getAbsX();
00299     y += getAbsY();
00300 
00301     g_gui.theme()->drawCaret(Common::Rect(x, y, x + 1, y + editRect.height()), erase);
00302 
00303     if (erase) {
00304         GUI::EditableWidget::String character;
00305         int width;
00306 
00307         if ((uint)_caretPos < _editString.size()) {
00308             const byte chr = _editString[_caretPos];
00309             width = g_gui.getCharWidth(chr, _font);
00310             character = chr;
00311 
00312             const uint last = (_caretPos > 0) ? _editString[_caretPos - 1] : 0;
00313             x += g_gui.getKerningOffset(last, chr, _font);
00314         } else {
00315             // We draw a fake space here to assure that removing the caret
00316             // does not result in color glitches in case the edit rect is
00317             // drawn with an inversion.
00318             width = g_gui.getCharWidth(' ', _font);
00319             character = " ";
00320         }
00321 
00322         // TODO: Right now we manually prevent text from being drawn outside
00323         // the edit area here. We might want to consider to use
00324         // setTextDrawableArea for this. However, it seems that only
00325         // EditTextWidget uses that but not ListWidget. Thus, one should check
00326         // whether we can unify the drawing in the text area first to avoid
00327         // possible glitches due to different methods used.
00328         width = MIN(editRect.width() - caretOffset, width);
00329         if (width > 0) {
00330             g_gui.theme()->drawText(Common::Rect(x, y, x + width, y + editRect.height()), character,
00331                                     _state, Graphics::kTextAlignLeft, _inversion, 0, false, _font,
00332                                     ThemeEngine::kFontColorNormal, true, _textDrawableArea);
00333         }
00334     }
00335 
00336     _caretVisible = !erase;
00337 }
00338 
00339 bool EditableWidget::setCaretPos(int newPos) {
00340     assert(newPos >= 0 && newPos <= (int)_editString.size());
00341     _caretPos = newPos;
00342     return adjustOffset();
00343 }
00344 
00345 bool EditableWidget::adjustOffset() {
00346     // check if the caret is still within the textbox; if it isn't,
00347     // adjust _editScrollOffset
00348 
00349     int caretpos = getCaretOffset();
00350     const int editWidth = getEditRect().width();
00351 
00352     if (caretpos < 0) {
00353         // scroll left
00354         _editScrollOffset += caretpos;
00355         return true;
00356     } else if (caretpos >= editWidth) {
00357         // scroll right
00358         _editScrollOffset -= (editWidth - caretpos);
00359         return true;
00360     } else if (_editScrollOffset > 0) {
00361         const int strWidth = g_gui.getStringWidth(_editString, _font);
00362         if (strWidth - _editScrollOffset < editWidth) {
00363             // scroll right
00364             _editScrollOffset = (strWidth - editWidth);
00365             if (_editScrollOffset < 0)
00366                 _editScrollOffset = 0;
00367         }
00368     }
00369 
00370     return false;
00371 }
00372 
00373 void EditableWidget::makeCaretVisible() {
00374     _caretTime = g_system->getMillis() + kCaretBlinkTime;
00375     _caretVisible = true;
00376     drawCaret(false);
00377 }
00378 
00379 } // End of namespace GUI


Generated on Sat May 18 2019 05:01:00 for ResidualVM by doxygen 1.7.1
curved edge   curved edge