1 /** @file lineeditwidget.cpp  UI widget for an editable line of text.
2  *
3  * @authors Copyright © 2005-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2014 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "common.h"
22 #include "menu/widgets/lineeditwidget.h"
23 
24 #include "hu_menu.h"   // menu sounds
25 #include "hu_stuff.h"  // shiftXForm
26 #include "menu/page.h" // mnRendState
27 
28 using namespace de;
29 
30 namespace common {
31 namespace menu {
32 
33 static patchid_t pEditLeft;
34 static patchid_t pEditRight;
35 static patchid_t pEditMiddle;
36 
DENG2_PIMPL_NOREF(LineEditWidget)37 DENG2_PIMPL_NOREF(LineEditWidget)
38 {
39     String text;
40     String oldText;    ///< For restoring a canceled edit.
41     String emptyText;  ///< Used when value is empty.
42     int maxLength       = 0;
43     int maxVisibleChars = 0;
44 };
45 
LineEditWidget()46 LineEditWidget::LineEditWidget()
47     : Widget()
48     , d(new Impl)
49 {
50     setFont(MENU_FONT1);
51     setColor(MENU_COLOR1);
52 }
53 
~LineEditWidget()54 LineEditWidget::~LineEditWidget()
55 {}
56 
loadResources()57 void LineEditWidget::loadResources() // static
58 {
59 #if defined(MNDATA_EDIT_BACKGROUND_PATCH_LEFT)
60     pEditLeft   = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_LEFT);
61 #else
62     pEditLeft   = 0;
63 #endif
64 #if defined(MNDATA_EDIT_BACKGROUND_PATCH_RIGHT)
65     pEditRight  = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_RIGHT);
66 #else
67     pEditRight  = 0;
68 #endif
69     pEditMiddle = R_DeclarePatch(MNDATA_EDIT_BACKGROUND_PATCH_MIDDLE);
70 }
71 
drawEditBackground(Vector2i const & origin,int width,float alpha)72 static void drawEditBackground(Vector2i const &origin, int width, float alpha)
73 {
74     DGL_Color4f(1, 1, 1, alpha);
75 
76     int leftOffset = 0;
77     patchinfo_t leftInfo;
78     if(R_GetPatchInfo(pEditLeft, &leftInfo))
79     {
80         DGL_SetPatch(pEditLeft, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
81         DGL_DrawRectf2(origin.x, origin.y, leftInfo.geometry.size.width, leftInfo.geometry.size.height);
82         leftOffset = leftInfo.geometry.size.width;
83     }
84 
85     int rightOffset = 0;
86     patchinfo_t rightInfo;
87     if(R_GetPatchInfo(pEditRight, &rightInfo))
88     {
89         DGL_SetPatch(pEditRight, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
90         DGL_DrawRectf2(origin.x + width - rightInfo.geometry.size.width, origin.y, rightInfo.geometry.size.width, rightInfo.geometry.size.height);
91         rightOffset = rightInfo.geometry.size.width;
92     }
93 
94     patchinfo_t middleInfo;
95     if(R_GetPatchInfo(pEditMiddle, &middleInfo))
96     {
97         if (!pEditLeft && !pEditRight)
98         {
99             // Stretch the middle patch to the desired width.
100             DGL_SetPatch(pEditMiddle, DGL_CLAMP_TO_EDGE, DGL_CLAMP_TO_EDGE);
101             DGL_DrawRectf2(origin.x, origin.y,
102                            width, middleInfo.geometry.size.height);
103         }
104         else
105         {
106             DGL_SetPatch(pEditMiddle, DGL_REPEAT, DGL_REPEAT);
107             DGL_DrawRectf2Tiled(origin.x + leftOffset, origin.y, width - leftOffset - rightOffset, middleInfo.geometry.size.height, middleInfo.geometry.size.width, middleInfo.geometry.size.height);
108         }
109     }
110 }
111 
draw() const112 void LineEditWidget::draw() const
113 {
114     fontid_t fontId = mnRendState->textFonts[font()];
115 
116     Vector2i origin = geometry().topLeft + Vector2i(MNDATA_EDIT_OFFSET_X, MNDATA_EDIT_OFFSET_Y);
117 
118     String useText;
119     float light = 1, textOpacity = mnRendState->pageAlpha;
120     if(!d->text.isEmpty())
121     {
122         useText = d->text;
123     }
124     else if(!(isActive() && isFocused()))
125     {
126         useText = d->emptyText;
127         light *= .5f;
128         textOpacity = mnRendState->pageAlpha * .75f;
129     }
130 
131     DGL_Enable(DGL_TEXTURE_2D);
132     FR_SetFont(fontId);
133 
134     const float fadeout = scrollingFadeout();
135 
136     //int const numVisCharacters = de::clamp(0, useText.isNull()? 0 : useText.length(), d->maxVisibleChars);
137 
138     drawEditBackground(origin + Vector2i(MNDATA_EDIT_BACKGROUND_OFFSET_X, MNDATA_EDIT_BACKGROUND_OFFSET_Y),
139                        geometry().width(), mnRendState->pageAlpha * fadeout);
140 
141     //if(string)
142     {
143 //        float t = 0;
144         Vector4f color = Vector4f(Vector3f(cfg.common.menuTextColors[MNDATA_EDIT_TEXT_COLORIDX]), 1.f);
145 
146         // Flash if focused?
147         if (!isActive()) /* && isFocused() && cfg.common.menuTextFlashSpeed > 0)
148         {
149             float const speed = cfg.common.menuTextFlashSpeed / 2.f;
150             t = (1 + sin(page().timer() / (float)TICSPERSEC * speed * DD_PI)) / 2;
151         }*/
152         {
153             color = selectionFlashColor(color);
154         }
155 
156 //        Vector4f color = de::lerp(Vector3f(cfg.common.menuTextColors[MNDATA_EDIT_TEXT_COLORIDX]), Vector3f(cfg.common.menuTextFlashColor), t);
157         color *= light;
158         color.w = textOpacity;
159 
160         // Draw the text:
161         FR_SetColorAndAlpha(color.x, color.y, color.z, color.w * fadeout);
162         FR_DrawTextXY3(useText.toUtf8().constData(), origin.x, origin.y, ALIGN_TOPLEFT, Hu_MenuMergeEffectWithDrawTextFlags(0));
163 
164         // Are we drawing a cursor?
165         if(isActive() && isFocused() && (menuTime & 8) &&
166            (!d->maxLength || d->text.length() < d->maxLength))
167         {
168             origin.x += FR_TextWidth(useText.toUtf8().constData());
169             FR_DrawCharXY3('_', origin.x, origin.y, ALIGN_TOPLEFT,  Hu_MenuMergeEffectWithDrawTextFlags(0));
170         }
171     }
172 
173     DGL_Disable(DGL_TEXTURE_2D);
174 }
175 
maxLength() const176 int LineEditWidget::maxLength() const
177 {
178     return d->maxLength;
179 }
180 
setMaxLength(int newMaxLength)181 LineEditWidget &LineEditWidget::setMaxLength(int newMaxLength)
182 {
183     newMaxLength = de::max(newMaxLength, 0);
184     if(d->maxLength != newMaxLength)
185     {
186         if(newMaxLength < d->maxLength)
187         {
188             d->text.truncate(newMaxLength);
189             d->oldText.truncate(newMaxLength);
190         }
191         d->maxLength = newMaxLength;
192     }
193     return *this;
194 }
195 
text() const196 String LineEditWidget::text() const
197 {
198     return d->text;
199 }
200 
setText(String const & newText,int flags)201 LineEditWidget &LineEditWidget::setText(String const &newText, int flags)
202 {
203     d->text = newText;
204     if(d->maxLength) d->text.truncate(d->maxLength);
205 
206     if(flags & MNEDIT_STF_REPLACEOLD)
207     {
208         d->oldText = d->text;
209     }
210 
211     if(!(flags & MNEDIT_STF_NO_ACTION))
212     {
213         execAction(Modified);
214     }
215     return *this;
216 }
217 
setEmptyText(String const & newEmptyText)218 LineEditWidget &LineEditWidget::setEmptyText(String const &newEmptyText)
219 {
220     d->emptyText = newEmptyText;
221     return *this;
222 }
223 
emptyText() const224 String LineEditWidget::emptyText() const
225 {
226     return d->emptyText;
227 }
228 
229 /**
230  * Responds to alphanumeric input for edit fields.
231  */
handleEvent(event_t const & ev)232 int LineEditWidget::handleEvent(event_t const &ev)
233 {
234     if(!isActive() || ev.type != EV_KEY)
235         return false;
236 
237     if(ev.data1 == DDKEY_RSHIFT)
238     {
239         shiftdown = (ev.state == EVS_DOWN || ev.state == EVS_REPEAT);
240         return true;
241     }
242 
243     if(!(ev.state == EVS_DOWN || ev.state == EVS_REPEAT))
244         return false;
245 
246     if(ev.data1 == DDKEY_BACKSPACE)
247     {
248         if(!d->text.isEmpty())
249         {
250             d->text.truncate(d->text.length() - 1);
251             execAction(Modified);
252         }
253         return true;
254     }
255 
256     if(ev.data1 >= ' ' && ev.data1 <= 'z')
257     {
258         char ch = char(ev.data1);
259         if(shiftdown)
260         {
261             ch = shiftXForm[int(ch)];
262         }
263 
264         // Filter out nasty charactemnRendState->
265         if(ch == '%')
266             return true;
267 
268         if(!d->maxLength || d->text.length() < d->maxLength)
269         {
270             d->text += ch;
271             execAction(Modified);
272         }
273         return true;
274     }
275 
276     return false;
277 }
278 
handleCommand(menucommand_e cmd)279 int LineEditWidget::handleCommand(menucommand_e cmd)
280 {
281     if(cmd == MCMD_SELECT)
282     {
283         if(!isActive())
284         {
285             S_LocalSound(SFX_MENU_CYCLE, NULL);
286             setFlags(Active);
287             // Store a copy of the present text value so we can restore it.
288             d->oldText = d->text;
289             execAction(Activated);
290         }
291         else
292         {
293             S_LocalSound(SFX_MENU_ACCEPT, NULL);
294             d->oldText = d->text;
295             setFlags(Active, UnsetFlags);
296             execAction(Deactivated);
297         }
298         return true;
299     }
300 
301     if(isActive())
302     {
303         switch(cmd)
304         {
305         case MCMD_NAV_OUT:
306             d->text = d->oldText;
307             setFlags(Active, UnsetFlags);
308             execAction(Closed);
309             return true;
310 
311         // Eat all other navigation commands, when active.
312         case MCMD_NAV_LEFT:
313         case MCMD_NAV_RIGHT:
314         case MCMD_NAV_DOWN:
315         case MCMD_NAV_UP:
316         case MCMD_NAV_PAGEDOWN:
317         case MCMD_NAV_PAGEUP:
318             return true;
319 
320         default: break;
321         }
322     }
323 
324     return false; // Not eaten.
325 }
326 
updateGeometry()327 void LineEditWidget::updateGeometry()
328 {
329     FR_SetFont(mnRendState->textFonts[font()]);
330     geometry().setSize(Vector2ui(FR_CharWidth('w') * d->maxLength - MNDATA_EDIT_BACKGROUND_OFFSET_X*2, 14));
331 }
332 
333 } // namespace menu
334 } // namespace common
335