1 /*************************************************************************** 2 * Copyright (c) 2017-2019 by the fifechan team * 3 * https://github.com/fifengine/fifechan * 4 * This file is part of fifechan. * 5 * * 6 * fifechan is free software; you can redistribute it and/or * 7 * modify it under the terms of the GNU Lesser General Public * 8 * License as published by the Free Software Foundation; either * 9 * version 2.1 of the License, or (at your option) any later version. * 10 * * 11 * This library is distributed in the hope that it will be useful, * 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 14 * Lesser General Public License for more details. * 15 * * 16 * You should have received a copy of the GNU Lesser General Public * 17 * License along with this library; if not, write to the * 18 * Free Software Foundation, Inc., * 19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * 20 ***************************************************************************/ 21 22 /* _______ __ __ __ ______ __ __ _______ __ __ 23 * / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___ /\ / |\/ /\ 24 * / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / / 25 * / / /__ / / // / // / // / / / ___ / // ___ / // /| ' / / 26 * / /_// /\ / /_// / // / // /_/_ / / // / // /\_/ / // / | / / 27 * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ / 28 * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ 29 * 30 * Copyright (c) 2004 - 2008 Olof Naess�n and Per Larsson 31 * 32 * 33 * Per Larsson a.k.a finalman 34 * Olof Naess�n a.k.a jansem/yakslem 35 * 36 * Visit: http://guichan.sourceforge.net 37 * 38 * License: (BSD) 39 * Redistribution and use in source and binary forms, with or without 40 * modification, are permitted provided that the following conditions 41 * are met: 42 * 1. Redistributions of source code must retain the above copyright 43 * notice, this list of conditions and the following disclaimer. 44 * 2. Redistributions in binary form must reproduce the above copyright 45 * notice, this list of conditions and the following disclaimer in 46 * the documentation and/or other materials provided with the 47 * distribution. 48 * 3. Neither the name of Guichan nor the names of its contributors may 49 * be used to endorse or promote products derived from this software 50 * without specific prior written permission. 51 * 52 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 53 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 54 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 55 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 56 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 57 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 58 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 59 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 60 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 61 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 62 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 */ 64 65 /* 66 * For comments regarding functions please see the header file. 67 */ 68 69 #include "fifechan/widgets/textfield.hpp" 70 71 #include "fifechan/font.hpp" 72 #include "fifechan/graphics.hpp" 73 #include "fifechan/key.hpp" 74 #include "fifechan/mouseinput.hpp" 75 #include "fifechan/text.hpp" 76 #include "fifechan/utf8stringeditor.hpp" 77 78 namespace fcn 79 { TextField()80 TextField::TextField(): 81 mEditable(true), 82 mXScroll(0) 83 { 84 mText = new Text(); 85 mText->addRow(""); 86 87 setFocusable(true); 88 89 addMouseListener(this); 90 addKeyListener(this); 91 92 mStringEditor = new UTF8StringEditor; 93 } 94 TextField(const std::string & text)95 TextField::TextField(const std::string& text): 96 mEditable(true), 97 mXScroll(0) 98 { 99 mText = new Text(text); 100 101 adjustSize(); 102 103 setFocusable(true); 104 105 addMouseListener(this); 106 addKeyListener(this); 107 108 mStringEditor = new UTF8StringEditor; 109 } 110 ~TextField()111 TextField::~TextField() 112 { 113 delete mText; 114 delete mStringEditor; 115 } 116 setText(const std::string & text)117 void TextField::setText(const std::string& text) 118 { 119 mText->setContent(text); 120 } 121 draw(Graphics * graphics)122 void TextField::draw(Graphics* graphics) 123 { 124 Color faceColor = getBaseColor(); 125 Color highlightColor, shadowColor; 126 int alpha = getBaseColor().a; 127 highlightColor = faceColor + 0x303030; 128 highlightColor.a = alpha; 129 shadowColor = faceColor - 0x303030; 130 shadowColor.a = alpha; 131 132 // Draw a border. 133 graphics->setColor(shadowColor); 134 graphics->drawLine(0, 0, getWidth() - 1, 0); 135 graphics->drawLine(0, 1, 0, getHeight() - 2); 136 graphics->setColor(highlightColor); 137 graphics->drawLine(getWidth() - 1, 1, getWidth() - 1, getHeight() - 1); 138 graphics->drawLine(0, getHeight() - 1, getWidth() - 1, getHeight() - 1); 139 140 // Push a clip area so the other drawings don't need to worry 141 // about the border. 142 graphics->pushClipArea(Rectangle(1, 1, getWidth() - 2, getHeight() - 2)); 143 144 graphics->setColor(getBackgroundColor()); 145 graphics->fillRectangle(0, 0, getWidth(), getHeight()); 146 147 if (isFocused()) 148 { 149 graphics->setColor(getSelectionColor()); 150 graphics->drawRectangle(0, 0, getWidth() - 2, getHeight() - 2); 151 graphics->drawRectangle(1, 1, getWidth() - 4, getHeight() - 4); 152 } 153 154 if (isFocused() && isEditable()) 155 { 156 drawCaret(graphics, mText->getCaretX(getFont()) - mXScroll); 157 } 158 159 graphics->setColor(getForegroundColor()); 160 graphics->setFont(getFont()); 161 162 const Rectangle& dim = mText->getCaretDimension(getFont()); 163 if (mText->getNumberOfRows() != 0) 164 graphics->drawText(mText->getRow(0), 1 - mXScroll, 1); 165 166 graphics->popClipArea(); 167 } 168 drawCaret(Graphics * graphics,int x)169 void TextField::drawCaret(Graphics* graphics, int x) 170 { 171 // Check the current clip area as a clip area with a different 172 // size than the widget might have been pushed (which is the 173 // case in the draw method when we push a clip area after we have 174 // drawn a border). 175 const Rectangle clipArea = graphics->getCurrentClipArea(); 176 177 graphics->setColor(getForegroundColor()); 178 graphics->drawLine(x, clipArea.height - 2, x, 1); 179 } 180 mousePressed(MouseEvent & mouseEvent)181 void TextField::mousePressed(MouseEvent& mouseEvent) 182 { 183 if (mouseEvent.getButton() == MouseEvent::Left) 184 { 185 mText->setCaretPosition(mouseEvent.getX() + mXScroll, mouseEvent.getY(), getFont()); 186 fixScroll(); 187 } 188 } 189 mouseDragged(MouseEvent & mouseEvent)190 void TextField::mouseDragged(MouseEvent& mouseEvent) 191 { 192 mouseEvent.consume(); 193 } 194 keyPressed(KeyEvent & keyEvent)195 void TextField::keyPressed(KeyEvent& keyEvent) 196 { 197 198 Key key = keyEvent.getKey(); 199 200 if (key.getValue() == Key::Left && getCaretPosition() > 0) 201 { 202 setCaretPosition(mStringEditor->prevChar(getText(), static_cast<int>(getCaretPosition()))); 203 } 204 else if (key.getValue() == Key::Right && getCaretPosition() < getText().size()) 205 { 206 setCaretPosition(mStringEditor->nextChar(getText(), static_cast<int>(getCaretPosition()))); 207 } 208 else if (key.getValue() == Key::Delete && getCaretPosition() < getText().size() && mText->getNumberOfRows() > 0) 209 { 210 setCaretPosition(mStringEditor->eraseChar(mText->getRow(0), static_cast<int>(getCaretPosition()))); 211 } 212 else if (key.getValue() == Key::Backspace && getCaretPosition() > 0 && mText->getNumberOfRows() > 0) 213 { 214 setCaretPosition(mStringEditor->prevChar(mText->getRow(0), static_cast<int>(getCaretPosition()))); 215 setCaretPosition(mStringEditor->eraseChar(mText->getRow(0), static_cast<int>(getCaretPosition()))); 216 } 217 else if (key.getValue() == Key::Enter) 218 { 219 distributeActionEvent(); 220 } 221 else if (key.getValue() == Key::Home) 222 { 223 setCaretPosition(0); 224 } 225 226 else if (key.getValue() == Key::End) 227 { 228 setCaretPosition(getText().size()); 229 } 230 231 // Add character to text, if key is really an ASCII character 232 // or is greater than 8bits long and the character is not 233 // the tab key. 234 235 else if ((key.isCharacter() || (key.getValue() > 255 && mText->getNumberOfRows() > 0)) 236 && key.getValue() != Key::Tab) 237 { 238 setCaretPosition(mStringEditor->insertChar(mText->getRow(0), getCaretPosition(), key.getValue())); 239 } 240 241 if (key.getValue() != Key::Tab) 242 { 243 // consume all characters except TAB which is needed 244 // for traversing through widgets in a container. 245 keyEvent.consume(); 246 } 247 248 fixScroll(); 249 } 250 resizeToContent(bool recursiv)251 void TextField::resizeToContent(bool recursiv) { 252 adjustSize(); 253 } 254 adjustSize()255 void TextField::adjustSize() 256 { 257 const Rectangle& dim = mText->getDimension(getFont()); 258 setWidth(dim.width + 8); 259 adjustHeight(); 260 261 fixScroll(); 262 } 263 adjustHeight()264 void TextField::adjustHeight() 265 { 266 setHeight(getFont()->getHeight() + 4); 267 } 268 fixScroll()269 void TextField::fixScroll() 270 { 271 if (isFocused()) 272 { 273 int caretX = mText->getCaretDimension(getFont()).x; 274 275 if (caretX - mXScroll >= getWidth() - 4) 276 { 277 mXScroll = caretX - getWidth() + 4; 278 } 279 else if (caretX - mXScroll <= 0) 280 { 281 mXScroll = caretX - getWidth() / 2; 282 283 if (mXScroll < 0) 284 { 285 mXScroll = 0; 286 } 287 } 288 } 289 } 290 setCaretPosition(unsigned int position)291 void TextField::setCaretPosition(unsigned int position) 292 { 293 mText->setCaretPosition(position); 294 } 295 getCaretPosition() const296 unsigned int TextField::getCaretPosition() const 297 { 298 return mText->getCaretPosition(); 299 } 300 getText() const301 std::string TextField::getText() const 302 { 303 return mText->getContent(); 304 } 305 isEditable() const306 bool TextField::isEditable() const 307 { 308 return mEditable; 309 } 310 setEditable(bool editable)311 void TextField::setEditable(bool editable) 312 { 313 mEditable = editable; 314 } 315 } 316