fixed: align center wasn't working in edit controls
[xbmc:xbmc-antiquated.git] / guilib / GUIEditControl.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "GUIEditControl.h"
23 #include "GUIWindowManager.h"
24 #include "utils/CharsetConverter.h"
25 #include "GUIDialogKeyboard.h"
26 #include "GUIDialogNumeric.h"
27 #include "LocalizeStrings.h"
28 #include "DateTime.h"
29
30 #ifdef __APPLE__
31 #include "CocoaInterface.h"
32 #endif
33
34 const char* CGUIEditControl::smsLetters[10] = { " !@#$%^&*()[]{}<>/\\|0", ".,;:\'\"-+_=?`~1", "abc2", "def3", "ghi4", "jkl5", "mno6", "pqrs7", "tuv8", "wxyz9" };
35 const unsigned int CGUIEditControl::smsDelay = 1000;
36
37 using namespace std;
38
39 #ifdef WIN32
40 extern HWND g_hWnd;
41 #endif
42
43 CGUIEditControl::CGUIEditControl(int parentID, int controlID, float posX, float posY,
44                                  float width, float height, const CTextureInfo &textureFocus, const CTextureInfo &textureNoFocus,
45                                  const CLabelInfo& labelInfo, const std::string &text)
46     : CGUIButtonControl(parentID, controlID, posX, posY, width, height, textureFocus, textureNoFocus, labelInfo)
47 {
48   DefaultConstructor();
49   SetLabel(text);
50 }
51
52 void CGUIEditControl::DefaultConstructor()
53 {
54   ControlType = GUICONTROL_EDIT;
55   m_textOffset = 0;
56   m_textWidth = GetWidth();
57   m_cursorPos = 0;
58   m_cursorBlink = 0;
59   m_inputHeading = 0;
60   m_inputType = INPUT_TYPE_TEXT;
61   m_smsLastKey = 0;
62   m_smsKeyIndex = 0;
63   m_label.SetAlign(m_label.GetLabelInfo().align & XBFONT_CENTER_Y); // left align
64   m_label2.GetLabelInfo().offsetX = 0;
65 }
66
67 CGUIEditControl::CGUIEditControl(const CGUIButtonControl &button)
68     : CGUIButtonControl(button)
69 {
70   DefaultConstructor();
71 }
72
73 CGUIEditControl::~CGUIEditControl(void)
74 {
75 }
76
77 bool CGUIEditControl::OnMessage(CGUIMessage &message)
78 {
79   if (message.GetMessage() == GUI_MSG_SET_TYPE)
80   {
81     SetInputType((INPUT_TYPE)message.GetParam1(), (int)message.GetParam2());
82     return true;
83   }
84   else if (message.GetMessage() == GUI_MSG_ITEM_SELECTED)
85   {
86     message.SetLabel(GetLabel2());
87     return true;
88   }
89   else if (message.GetMessage() == GUI_MSG_SETFOCUS ||
90            message.GetMessage() == GUI_MSG_LOSTFOCUS)
91   {
92     m_smsTimer.Stop();
93   }
94   return CGUIButtonControl::OnMessage(message);
95 }
96
97 bool CGUIEditControl::OnAction(const CAction &action)
98 {
99   ValidateCursor();
100
101   if (action.GetID() == ACTION_BACKSPACE)
102   {
103     // backspace
104     if (m_cursorPos)
105     {
106       m_text2.erase(--m_cursorPos, 1);
107       UpdateText();
108     }
109     return true;
110   }
111   else if (action.GetID() == ACTION_MOVE_LEFT)
112   {
113     if (m_cursorPos > 0)
114     {
115       m_cursorPos--;
116       UpdateText(false);
117       return true;
118     }
119   }
120   else if (action.GetID() == ACTION_MOVE_RIGHT)
121   {
122     if ((unsigned int) m_cursorPos < m_text2.size())
123     {
124       m_cursorPos++;
125       UpdateText(false);
126       return true;
127     }
128   }
129   else if (action.GetID() == ACTION_PASTE)
130   {
131     OnPasteClipboard();
132   }
133   else if (action.GetID() >= KEY_VKEY && action.GetID() < KEY_ASCII)
134   {
135     // input from the keyboard (vkey, not ascii)
136     BYTE b = action.GetID() & 0xFF;
137     if (b == 0x24) // home
138     {
139       m_cursorPos = 0;
140       UpdateText(false);
141       return true;
142     }
143     else if (b == 0x23) // end
144     {
145       m_cursorPos = m_text2.length();
146       UpdateText(false);
147       return true;
148     }
149     if (b == 0x25 && m_cursorPos > 0)
150     { // left
151       m_cursorPos--;
152       UpdateText(false);
153       return true;
154     }
155     if (b == 0x27 && m_cursorPos < m_text2.length())
156     { // right
157       m_cursorPos++;
158       UpdateText(false);
159       return true;
160     }
161     if (b == 0x2e)
162     {
163       if (m_cursorPos < m_text2.length())
164       { // delete
165         m_text2.erase(m_cursorPos, 1);
166         UpdateText();
167         return true;
168       }
169     }
170     if (b == 0x8)
171     {
172       if (m_cursorPos > 0)
173       { // backspace
174         m_text2.erase(--m_cursorPos, 1);
175         UpdateText();
176       }
177       return true;
178     }
179   }
180   else if (action.GetID() >= KEY_ASCII)
181   {
182     // input from the keyboard
183     switch (action.GetUnicode())
184     {
185     case '\t':
186       break;
187     case 10:
188     case 13:
189       {
190         // enter - send click message, but otherwise ignore
191         SEND_CLICK_MESSAGE(GetID(), GetParentID(), 1);
192         return true;
193       }
194     case 27:
195       { // escape - fallthrough to default action
196         return CGUIButtonControl::OnAction(action);
197       }
198     case 8:
199       {
200         // backspace
201         if (m_cursorPos)
202         {
203           m_text2.erase(--m_cursorPos, 1);
204         }
205         break;
206       }
207     default:
208       {
209         m_text2.insert(m_text2.begin() + m_cursorPos++, (WCHAR)action.GetUnicode());
210         break;
211       }
212     }
213     UpdateText();
214     return true;
215   }
216   else if (action.GetID() >= REMOTE_0 && action.GetID() <= REMOTE_9)
217   { // input from the remote
218     if (m_inputType == INPUT_TYPE_FILTER)
219     { // filtering - use single number presses
220       m_text2.insert(m_text2.begin() + m_cursorPos++, L'0' + (action.GetID() - REMOTE_0));
221       UpdateText();
222     }
223     else
224       OnSMSCharacter(action.GetID() - REMOTE_0);
225     return true;
226   }
227   return CGUIButtonControl::OnAction(action);
228 }
229
230 void CGUIEditControl::OnClick()
231 {
232   // we received a click - it's not from the keyboard, so pop up the virtual keyboard, unless
233   // that is where we reside!
234   if (GetParentID() == WINDOW_DIALOG_KEYBOARD)
235     return;
236
237   CStdString utf8;
238   g_charsetConverter.wToUTF8(m_text2, utf8);
239   bool textChanged = false;
240   CStdString heading = g_localizeStrings.Get(m_inputHeading ? m_inputHeading : 16028);
241   switch (m_inputType)
242   {
243     case INPUT_TYPE_NUMBER:
244       textChanged = CGUIDialogNumeric::ShowAndGetNumber(utf8, heading);
245       break;
246     case INPUT_TYPE_SECONDS:
247       textChanged = CGUIDialogNumeric::ShowAndGetSeconds(utf8, g_localizeStrings.Get(21420));
248       break;
249     case INPUT_TYPE_DATE:
250     {
251       CDateTime dateTime;
252       dateTime.SetFromDBDate(utf8);
253       if (dateTime < CDateTime(2000,1, 1, 0, 0, 0))
254         dateTime = CDateTime(2000, 1, 1, 0, 0, 0);
255       SYSTEMTIME date;
256       dateTime.GetAsSystemTime(date);
257       if (CGUIDialogNumeric::ShowAndGetDate(date, g_localizeStrings.Get(21420)))
258       {
259         dateTime = CDateTime(date);
260         utf8 = dateTime.GetAsDBDate();
261         textChanged = true;
262       }
263       break;
264     }
265     case INPUT_TYPE_IPADDRESS:
266       textChanged = CGUIDialogNumeric::ShowAndGetIPAddress(utf8, heading);
267       break;
268     case INPUT_TYPE_SEARCH:
269       textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, true);
270       break;
271     case INPUT_TYPE_FILTER:
272       textChanged = CGUIDialogKeyboard::ShowAndGetFilter(utf8, false);
273       break;
274     case INPUT_TYPE_TEXT:
275     default:
276       textChanged = CGUIDialogKeyboard::ShowAndGetInput(utf8, heading, true, m_inputType == INPUT_TYPE_PASSWORD);
277       break;
278   }
279   if (textChanged)
280   {
281     g_charsetConverter.utf8ToW(utf8, m_text2);
282     m_cursorPos = m_text2.size();
283     UpdateText();
284     m_cursorPos = m_text2.size();
285   }
286 }
287
288 void CGUIEditControl::UpdateText(bool sendUpdate)
289 {
290   m_smsTimer.Stop();
291   if (sendUpdate)
292   {
293     SEND_CLICK_MESSAGE(GetID(), GetParentID(), 0);
294
295     vector<CGUIActionDescriptor> textChangeActions = m_textChangeActions;
296     for (unsigned int i = 0; i < textChangeActions.size(); i++)
297     {
298       CGUIMessage message(GUI_MSG_EXECUTE, GetID(), GetParentID());
299       message.SetAction(textChangeActions[i]);
300       g_windowManager.SendMessage(message);
301     }
302   }
303   SetInvalid();
304 }
305
306 void CGUIEditControl::SetInputType(CGUIEditControl::INPUT_TYPE type, int heading)
307 {
308   m_inputType = type;
309   m_inputHeading = heading;
310   // TODO: Verify the current input string?
311 }
312
313 void CGUIEditControl::RecalcLabelPosition()
314 {
315   // ensure that our cursor is within our width
316   ValidateCursor();
317
318   CStdStringW text = GetDisplayedText();
319   m_textWidth = m_label.CalcTextWidth(text + L'|');
320   float beforeCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos));
321   float afterCursorWidth = m_label.CalcTextWidth(text.Left(m_cursorPos) + L'|');
322   float leftTextWidth = m_label.GetRenderRect().Width();
323   float maxTextWidth = m_label.GetMaxRect().Width();
324   if (leftTextWidth > 0)
325     maxTextWidth -= leftTextWidth + spaceWidth;
326
327   // if skinner forgot to set height :p
328   if (m_height == 0 && m_label.GetLabelInfo().font)
329     m_height = m_label.GetLabelInfo().font->GetTextHeight(1);
330
331   if (m_textWidth > maxTextWidth)
332   { // we render taking up the full width, so make sure our cursor position is
333     // within the render window
334     if (m_textOffset + afterCursorWidth > maxTextWidth)
335     {
336       // move the position to the left (outside of the viewport)
337       m_textOffset = maxTextWidth - afterCursorWidth;
338     }
339     else if (m_textOffset + beforeCursorWidth < 0) // offscreen to the left
340     {
341       // otherwise use original position
342       m_textOffset = -beforeCursorWidth;
343     }
344     else if (m_textOffset + m_textWidth < maxTextWidth)
345     { // we have more text than we're allowed, but we aren't filling all the space
346       m_textOffset = maxTextWidth - m_textWidth;
347     }
348   }
349   else
350     m_textOffset = 0;
351 }
352
353 void CGUIEditControl::RenderText()
354 {
355   if (m_smsTimer.GetElapsedMilliseconds() > smsDelay)
356     UpdateText();
357
358   if (m_bInvalidated)
359   {
360     m_label.SetMaxRect(m_posX, m_posY, m_width, m_height);
361     m_label.SetText(m_info.GetLabel(GetParentID()));
362     RecalcLabelPosition();
363   }
364
365
366   float posX = m_label.GetMaxRect().x1;
367   float maxTextWidth = m_label.GetMaxRect().Width();
368
369   // start by rendering the normal text
370   float leftTextWidth = m_label.GetRenderRect().Width();
371   if (leftTextWidth > 0)
372   {
373     // render the text on the left
374     m_label.SetColor(GetTextColor());
375     m_label.Render();
376     
377     posX += leftTextWidth + spaceWidth;
378     maxTextWidth -= leftTextWidth + spaceWidth;
379   }
380
381   if (g_graphicsContext.SetClipRegion(posX, m_posY, maxTextWidth, m_height))
382   {
383     uint32_t align = m_label.GetLabelInfo().align & XBFONT_CENTER_Y; // start aligned left
384     if (m_label2.GetTextWidth() < maxTextWidth)
385     { // align text as our text fits
386       if (leftTextWidth > 0)
387       { // right align as we have 2 labels
388         align |= XBFONT_RIGHT;
389       }
390       else
391       { // align by whatever the skinner requests
392         align |= (m_label2.GetLabelInfo().align & 3);
393       }
394     }
395     CStdStringW text = GetDisplayedText();
396     // add the cursor if we're focused
397     if (HasFocus())
398     {
399       CStdStringW col;
400       if ((m_focusCounter % 64) > 32)
401         col = L"|";
402       else
403         col = L"[COLOR 00FFFFFF]|[/COLOR]";
404       text.Insert(m_cursorPos, col);
405     }
406
407     m_label2.SetMaxRect(posX + m_textOffset, m_posY, maxTextWidth - m_textOffset, m_height);
408     m_label2.SetTextW(text);
409     m_label2.SetAlign(align);
410     m_label2.SetColor(GetTextColor());
411     m_label2.Render();
412     g_graphicsContext.RestoreClipRegion();
413   }
414 }
415
416 CStdStringW CGUIEditControl::GetDisplayedText() const
417 {
418   if (m_inputType == INPUT_TYPE_PASSWORD)
419   {
420     CStdStringW text;
421     text.append(m_text2.size(), L'*');
422     return text;
423   }
424   return m_text2;
425 }
426
427 void CGUIEditControl::ValidateCursor()
428 {
429   if (m_cursorPos > m_text2.size())
430     m_cursorPos = m_text2.size();
431 }
432
433 void CGUIEditControl::SetLabel(const std::string &text)
434 {
435   CGUIButtonControl::SetLabel(text);
436   SetInvalid();
437 }
438
439 void CGUIEditControl::SetLabel2(const std::string &text)
440 {
441   CStdStringW newText;
442   g_charsetConverter.utf8ToW(text, newText);
443   if (newText != m_text2)
444   {
445     m_text2 = newText;
446     m_cursorPos = m_text2.size();
447     SetInvalid();
448   }
449 }
450
451 CStdString CGUIEditControl::GetLabel2() const
452 {
453   CStdString text;
454   g_charsetConverter.wToUTF8(m_text2, text);
455   return text;
456 }
457
458 unsigned int CGUIEditControl::GetCursorPosition() const
459 {
460   return m_cursorPos;
461 }
462
463 void CGUIEditControl::SetCursorPosition(unsigned int iPosition)
464 {
465   m_cursorPos = iPosition;
466 }
467
468 void CGUIEditControl::OnSMSCharacter(unsigned int key)
469 {
470   assert(key < 10);
471   bool sendUpdate = false;
472   if (m_smsTimer.IsRunning())
473   {
474     // we're already entering an SMS character
475     if (key != m_smsLastKey || m_smsTimer.GetElapsedMilliseconds() > smsDelay)
476     { // a different key was clicked than last time, or we have timed out
477       m_smsLastKey = key;
478       m_smsKeyIndex = 0;
479       sendUpdate = true;
480     }
481     else
482     { // same key as last time within the appropriate time period
483       m_smsKeyIndex++;
484       if (m_cursorPos)
485         m_text2.erase(--m_cursorPos, 1);
486     }
487   }
488   else
489   { // key is pressed for the first time
490     m_smsLastKey = key;
491     m_smsKeyIndex = 0;
492   }
493
494   m_smsKeyIndex = m_smsKeyIndex % strlen(smsLetters[key]);
495
496   m_text2.insert(m_text2.begin() + m_cursorPos++, smsLetters[key][m_smsKeyIndex]);
497   UpdateText(sendUpdate);
498   m_smsTimer.StartZero();
499 }
500
501 void CGUIEditControl::OnPasteClipboard()
502 {
503 #ifdef __APPLE__
504   const char *szStr = Cocoa_Paste();
505   if (szStr)
506   {
507     m_text2 += szStr;
508     m_cursorPos+=strlen(szStr);
509     UpdateText();
510   }
511 #elif defined _WIN32
512   if (OpenClipboard(g_hWnd))
513   {
514     HGLOBAL hglb = GetClipboardData(CF_TEXT);
515     if (hglb != NULL)
516     {
517       LPTSTR lptstr = (LPTSTR)GlobalLock(hglb);
518       if (lptstr != NULL)
519       {
520         m_text2 = (char*)lptstr;
521         GlobalUnlock(hglb);
522       }
523     }
524     CloseClipboard();
525     UpdateText();
526   }
527 #endif
528 }