OpenClonk
C4GuiEdit.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // generic user interface
17 // room for textual deconvolution
18 
19 #include "C4Include.h"
20 #include "gui/C4Gui.h"
21 
22 #include "game/C4Application.h"
23 #include "graphics/C4Draw.h"
25 #include "gui/C4MouseControl.h"
26 
27 namespace C4GUI
28 {
29 
30  const char *Edit::CursorRepresentation = "\xC2\xA6"; // U+00A6 BROKEN BAR
31 
32  namespace
33  {
34  inline bool IsUtf8ContinuationByte(char c)
35  {
36  return (c & 0xC0) == 0x80;
37  }
38  inline bool IsUtf8StartByte(char c)
39  {
40  return (c & 0xC0) == 0xC0;
41  }
42  }
43 
44 // ----------------------------------------------------
45 // Edit
46 
47  Edit::Edit(const C4Rect &rtBounds, bool fFocusEdit) : Control(rtBounds), iCursorPos(0), iSelectionStart(0), iSelectionEnd(0), fLeftBtnDown(false)
48  {
49  // create an initial buffer
50  Text = new char[256];
51  iBufferSize = 256;
52  *Text = 0;
53  // def vals
54  iMaxTextLength = 255;
56  iXScroll = 0;
60  dwBorderColor = 0; // default border
61  cPasswordMask = 0;
62  // apply client margin
63  UpdateOwnPos();
64  // add context handler
66  // add key handlers
68  pKeyCursorBack = RegisterCursorOp(COP_BACK , K_BACK , "GUIEditCursorBack", eKeyPrio);
69  pKeyCursorDel = RegisterCursorOp(COP_DELETE, K_DELETE, "GUIEditCursorDel",eKeyPrio);
70  pKeyCursorLeft = RegisterCursorOp(COP_LEFT , K_LEFT , "GUIEditCursorLeft",eKeyPrio);
71  pKeyCursorRight = RegisterCursorOp(COP_RIGHT , K_RIGHT , "GUIEditCursorRight", eKeyPrio);
72  pKeyCursorHome = RegisterCursorOp(COP_HOME , K_HOME , "GUIEditCursorHome", eKeyPrio);
73  pKeyCursorEnd = RegisterCursorOp(COP_END , K_END , "GUIEditCursorEnd", eKeyPrio);
74  pKeyEnter = new C4KeyBinding(C4KeyCodeEx(K_RETURN), "GUIEditConfirm", KEYSCOPE_Gui,
75  new ControlKeyCB<Edit>(*this, &Edit::KeyEnter), eKeyPrio);
76  pKeyCopy = new C4KeyBinding(C4KeyCodeEx(K_C, KEYS_Control), "GUIEditCopy", KEYSCOPE_Gui,
77  new ControlKeyCB<Edit>(*this, &Edit::KeyCopy), eKeyPrio);
78  pKeyPaste = new C4KeyBinding(C4KeyCodeEx(K_V, KEYS_Control), "GUIEditPaste", KEYSCOPE_Gui,
79  new ControlKeyCB<Edit>(*this, &Edit::KeyPaste), eKeyPrio);
80  pKeyCut = new C4KeyBinding(C4KeyCodeEx(K_X, KEYS_Control), "GUIEditCut", KEYSCOPE_Gui,
81  new ControlKeyCB<Edit>(*this, &Edit::KeyCut), eKeyPrio);
82  pKeySelAll = new C4KeyBinding(C4KeyCodeEx(K_A, KEYS_Control), "GUIEditSelAll", KEYSCOPE_Gui,
83  new ControlKeyCB<Edit>(*this, &Edit::KeySelectAll), eKeyPrio);
84  }
85 
87  {
88  delete[] Text;
89  delete pKeyCursorBack;
90  delete pKeyCursorDel;
91  delete pKeyCursorLeft;
92  delete pKeyCursorRight;
93  delete pKeyCursorHome;
94  delete pKeyCursorEnd;
95  delete pKeyEnter;
96  delete pKeyCopy;
97  delete pKeyPaste;
98  delete pKeyCut;
99  delete pKeySelAll;
100  }
101 
102  class C4KeyBinding *Edit::RegisterCursorOp(CursorOperation op, C4KeyCode key, const char *szName, C4CustomKey::Priority eKeyPrio)
103  {
104  // register same op for all shift states; distinction will be done in handling proc
105  C4CustomKey::CodeList KeyList;
106  KeyList.emplace_back(key);
107  KeyList.emplace_back(key, KEYS_Shift);
108  KeyList.emplace_back(key, KEYS_Control);
109  KeyList.emplace_back(key, C4KeyShiftState(KEYS_Shift | KEYS_Control));
110  return new C4KeyBinding(KeyList, szName, KEYSCOPE_Gui, new ControlKeyCBExPassKey<Edit, CursorOperation>(*this, op, &Edit::KeyCursorOp), eKeyPrio);
111  }
112 
114  {
115  // edit height for default font
117  }
118 
120  {
121  // edit height for custom font: Make it so edits and wooden labels have same height
122  return std::max<int32_t>(pUseFont->GetLineHeight()+3, C4GUI_MinWoodBarHgt);
123  }
124 
126  {
127  // free oversized buffers
128  if (iBufferSize > 256)
129  {
130  delete[] Text;
131  Text = new char[256];
132  iBufferSize = 256;
133  }
134  // clear text
135  *Text=0;
136  // reset cursor and selection
138  iXScroll = 0;
139  }
140 
141  void Edit::Deselect()
142  {
143  // reset selection
145  // cursor might have moved: ensure it is shown
147  }
148 
150  {
151  // move end text to front
152  int32_t iSelBegin = std::min(iSelectionStart, iSelectionEnd), iSelEnd = std::max(iSelectionStart, iSelectionEnd);
153  if (iSelectionStart == iSelectionEnd) return;
154  memmove(Text + iSelBegin, Text + iSelEnd, strlen(Text + iSelEnd)+1);
155  // adjust cursor pos
156  if (iCursorPos > iSelBegin) iCursorPos = std::max(iSelBegin, iCursorPos - iSelEnd + iSelBegin);
157  // cursor might have moved: ensure it is shown
159  // nothing selected
160  iSelectionStart = iSelectionEnd = iSelBegin;
161  }
162 
163  bool Edit::InsertText(const char *szText, bool fUser)
164  {
165  // empty previous selection
167  // check buffer length
168  int32_t iTextLen = SLen(szText);
169  int32_t iTextEnd = SLen(Text);
170  bool fBufferOK = (iTextLen + iTextEnd <= (iMaxTextLength-1));
171  if (!fBufferOK) iTextLen -= iTextEnd+iTextLen - (iMaxTextLength-1);
172  if (iTextLen <= 0) return false;
173  // ensure buffer is large enough
174  EnsureBufferSize(iTextEnd + iTextLen + 1);
175  // move down text buffer after cursor pos (including trailing zero-char)
176  int32_t i;
177  for (i=iTextEnd; i>=iCursorPos; --i) Text[i + iTextLen] = Text[i];
178  // insert buffer into text
179  for (i=iTextLen; i; --i) Text[iCursorPos + i - 1] = szText[i - 1];
180  if (fUser)
181  {
182  // advance cursor
183  iCursorPos += iTextLen;
184  // cursor moved: ensure it is shown
186  ScrollCursorInView();
187  }
188  // done; return whether everything was inserted
189  return fBufferOK;
190  }
191 
192  int32_t Edit::GetCharPos(int32_t iControlXPos)
193  {
194  // client offset
195  iControlXPos -= rcClientRect.x - rcBounds.x - iXScroll;
196  // well, not exactly the best idea...maybe add a new fn to the gfx system?
197  // summing up char widths is no good, because there might be spacings between characters
198  // 2do: optimize this
199  if (cPasswordMask)
200  {
201  int32_t w, h; char strMask[2] = { cPasswordMask, 0 };
202  pFont->GetTextExtent(strMask, w, h, false);
203  return Clamp<int32_t>((iControlXPos + w/2) / std::max<int32_t>(1, w), 0, SLen(Text));
204  }
205  int32_t i = 0;
206  for (int32_t iLastW = 0, w,h; Text[i]; ++i)
207  {
208  int oldi = i;
209  if (IsUtf8StartByte(Text[oldi]))
210  while (IsUtf8ContinuationByte(Text[++i + 1])) /* EMPTY */;
211  char c=Text[i+1]; Text[i+1]=0; pFont->GetTextExtent(Text, w, h, false); Text[i+1]=c;
212  if (w - (w-iLastW)/2 >= iControlXPos) return oldi;
213  iLastW = w;
214  }
215  return i;
216  }
217 
218  void Edit::EnsureBufferSize(int32_t iMinBufferSize)
219  {
220  // realloc buffer if necessary
221  if (iBufferSize < iMinBufferSize)
222  {
223  // get new buffer size (rounded up to multiples of 256)
224  iMinBufferSize = ((iMinBufferSize - 1) & ~0xff) + 0x100;
225  // fill new buffer
226  char *pNewBuffer = new char[iMinBufferSize];
227  SCopy(Text, pNewBuffer);
228  // apply new buffer
229  delete[] Text; Text = pNewBuffer;
230  iBufferSize = iMinBufferSize;
231  }
232  }
233 
234  void Edit::ScrollCursorInView()
235  {
236  if (rcClientRect.Wdt<5) return;
237  // get position of cursor
238  int32_t iScrollOff = std::min<int32_t>(20, rcClientRect.Wdt/3);
239  int32_t w,h;
240  if (!cPasswordMask)
241  {
242  char c=Text[iCursorPos]; Text[iCursorPos]=0; pFont->GetTextExtent(Text, w, h, false); Text[iCursorPos]=c;
243  }
244  else
245  {
247  pFont->GetTextExtent(Buf.getData(), w, h, false);
248  }
249  // need to scroll?
250  while (w-iXScroll < rcClientRect.Wdt/5 && w<iScrollOff+iXScroll && iXScroll > 0)
251  {
252  // left
253  iXScroll = std::max(iXScroll - std::min(100, rcClientRect.Wdt/4), 0);
254  }
255  while (w-iXScroll >= rcClientRect.Wdt/5 && w>=rcClientRect.Wdt-iScrollOff+iXScroll)
256  {
257  // right
258  iXScroll += std::min(100, rcClientRect.Wdt/4);
259  }
260  }
261 
262  bool Edit::DoFinishInput(bool fPasting, bool fPastingMore)
263  {
264  // do OnFinishInput callback and process result - returns whether pasting operation should be continued
265  InputResult eResult = OnFinishInput(fPasting, fPastingMore);
266  switch (eResult)
267  {
268  case IR_None: // do nothing and continue pasting
269  return true;
270 
271  case IR_CloseDlg: // stop any pastes and close parent dialog successfully
272  {
273  Dialog *pDlg = GetDlg();
274  if (pDlg) pDlg->UserClose(true);
275  break;
276  }
277 
278  case IR_CloseEdit: // stop any pastes and remove this control
279  delete this;
280  break;
281 
282  case IR_Abort: // do nothing and stop any pastes
283  break;
284  }
285  // input has been handled; no more pasting please
286  return false;
287  }
288 
289  bool Edit::Copy()
290  {
291  // get selected range
292  int32_t iSelBegin = std::min(iSelectionStart, iSelectionEnd), iSelEnd = std::max(iSelectionStart, iSelectionEnd);
293  if (iSelBegin == iSelEnd) return false;
294  // allocate a global memory object for the text.
295  std::string buf(Text+iSelBegin, iSelEnd-iSelBegin);
296  if (cPasswordMask)
297  buf.assign(buf.size(), cPasswordMask);
298 
299  return Application.Copy(buf);
300  }
301 
302  bool Edit::Cut()
303  {
304  // copy text
305  if (!Copy()) return false;
306  // delete copied text
307  DeleteSelection();
308  // done, success
309  return true;
310  }
311 
312  bool Edit::Paste()
313  {
314  bool fSuccess = false;
315  // check clipboard contents
316  if(!Application.IsClipboardFull()) return false;
317  StdCopyStrBuf text(Application.Paste().c_str());
318  char * szText = text.getMData();
319  if (text)
320  {
321  fSuccess = !!*szText;
322  // replace any '|'
323  int32_t iLBPos=0, iLBPos2;
324  // caution when inserting line breaks: Those must be stripped, and sent as Enter-commands
325  iLBPos=0;
326  for (;;)
327  {
328  iLBPos = SCharPos(0x0d, szText);
329  iLBPos2 = SCharPos(0x0a, szText);
330  if (iLBPos<0 && iLBPos2<0) break; // no more linebreaks
331  if (iLBPos2>=0 && (iLBPos2<iLBPos || iLBPos<0)) iLBPos = iLBPos2;
332  if (!iLBPos) { ++szText; continue; } // empty line
333  szText[iLBPos]=0x00;
334  if (!InsertText(szText, true)) fSuccess=false; // if the buffer was too long, still try to insert following stuff (don't abort just b/c one line was too long)
335  szText += iLBPos+1;
336  iLBPos=0;
337  if (!DoFinishInput(true, !!*szText))
338  {
339  // k, pasted
340  return true;
341  }
342  }
343  // insert new text (may fail due to overfull buffer, in which case parts of the text will be inserted)
344  if (*szText) fSuccess = fSuccess && InsertText(szText, true);
345  }
346  // return whether insertion was successful
347  return fSuccess;
348  }
349 
350  bool IsWholeWordSpacer(unsigned char c)
351  {
352  // characters that make up a space between words
353  // the extended characters are all seen a letters, because they vary in different
354  // charsets (danish, french, etc.) and are likely to represent localized letters
355  return !Inside<char>(c, 'A', 'Z')
356  && !Inside<char>(c, 'a', 'z')
357  && !Inside<char>(c, '0', '9')
358  && c!='_' && c<127;
359  }
360 
361  bool Edit::KeyEnter()
362  {
363  DoFinishInput(false, false);
364  // whatever happens: Enter key has been processed
365  return true;
366  }
367 
368  bool Edit::KeyCursorOp(const C4KeyCodeEx &key, const CursorOperation &op)
369  {
370  bool fShift = !!(key.dwShift & KEYS_Shift);
371  bool fCtrl = !!(key.dwShift & KEYS_Control);
372  // any selection?
374  {
375  // special handling: backspace/del with selection (delete selection)
376  if (op == COP_BACK || op == COP_DELETE) { DeleteSelection(); return true; }
377  // no shift pressed: clear selection (even if no cursor movement is done)
378  if (!fShift) Deselect();
379  }
380  // movement or regular/word deletion
381  int32_t iMoveDir = 0, iMoveLength = 0;
382  if (op == COP_LEFT && iCursorPos) iMoveDir = -1;
383  else if (op == COP_RIGHT && (uint32_t)iCursorPos < SLen(Text)) iMoveDir = +1;
384  else if (op == COP_BACK && iCursorPos && !fShift) iMoveDir = -1;
385  else if (op == COP_DELETE && (uint32_t)iCursorPos < SLen(Text) && !fShift) iMoveDir = +1;
386  else if (op == COP_HOME) iMoveLength = -iCursorPos;
387  else if (op == COP_END) iMoveLength = SLen(Text)-iCursorPos;
388  if (iMoveDir || iMoveLength)
389  {
390  // evaluate move length? (not home+end)
391  if (iMoveDir)
392  {
393  if (fCtrl)
394  {
395  // move one word
396  iMoveLength = 0;
397  bool fNoneSpaceFound = false, fSpaceFound = false;;
398  while (iCursorPos + iMoveLength + iMoveDir >= 0 && (uint32_t)(iCursorPos + iMoveLength + iMoveDir) <= SLen(Text))
399  if (IsWholeWordSpacer(Text[iCursorPos + iMoveLength + (iMoveDir-1)/2]))
400  {
401  // stop left of a complete word
402  if (fNoneSpaceFound && iMoveDir<0) break;
403  // continue
404  fSpaceFound = true;
405  iMoveLength += iMoveDir;
406  }
407  else
408  {
409  // stop right of spacings complete word
410  if (fSpaceFound && iMoveDir > 0) break;
411  // continue
412  fNoneSpaceFound = true;
413  iMoveLength += iMoveDir;
414  }
415  }
416  else
417  {
418  // Handle UTF-8
419  iMoveLength = iMoveDir;
420  while (IsUtf8ContinuationByte(Text[iCursorPos + iMoveLength])) iMoveLength += Sign(iMoveLength);
421  }
422  }
423  // delete stuff
424  if (op == COP_BACK || op == COP_DELETE)
425  {
426  // delete: make backspace command of it
427  if (op == COP_DELETE) { iCursorPos += iMoveLength; iMoveLength = -iMoveLength; }
428  // move end of string up
429  char *c; for (c = Text+iCursorPos; *c; ++c) *(c+iMoveLength) = *c;
430  // terminate string
431  *(c+iMoveLength) = 0;
432  assert(IsValidUtf8(Text));
433  }
434  else if (fShift)
435  {
436  // shift+arrow key: make/adjust selection
438  iSelectionEnd = iCursorPos + iMoveLength;
439  }
440  else
441  // simple cursor movement: clear any selection
442  if (iSelectionStart != iSelectionEnd) Deselect();
443  // adjust cursor pos
444  iCursorPos += iMoveLength;
445  }
446  // show cursor
448  ScrollCursorInView();
449  // operation recognized
450  return true;
451  }
452 
453  bool Edit::CharIn(const char * c)
454  {
455  // no control codes
456  if (((unsigned char)(c[0]))<' ' || c[0]==0x7f) return false;
457  // no '|'
458  if (c[0]=='|') return false;
459  // all extended characters are OK
460  // insert character at cursor position
461  return InsertText(c, true);
462  }
463 
464  void Edit::MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam)
465  {
466  // inherited first - this may give focus to this element
467  Control::MouseInput(rMouse, iButton, iX, iY, dwKeyParam);
468  // update dragging area
469  int32_t iPrevCursorPos = iCursorPos;
470  // dragging area is updated by drag proc
471  // process left down and up
472  switch (iButton)
473  {
475  // mark button as being down
476  fLeftBtnDown = true;
477  // set selection start
478  iSelectionStart = iSelectionEnd = GetCharPos(iX);
479  // set cursor pos here, too
481  // remember drag target
482  // no dragging movement will be done w/o drag component assigned
483  // but text selection should work even if the user goes outside the component
484  if (!rMouse.pDragElement) rMouse.pDragElement = this;
485  break;
486 
487  case C4MC_Button_LeftUp:
488  // only if button was down... (might have dragged here)
489  if (fLeftBtnDown)
490  {
491  // it's now up :)
492  fLeftBtnDown = false;
493  // set cursor to this pos
495  }
496  break;
497 
499  {
500  // word selection
501  // get character pos (half-char-offset, use this to allow selection at half-space-offset around a word)
502  int32_t iCharPos = GetCharPos(iX);
503  // was space? try left character
504  if (IsWholeWordSpacer(Text[iCharPos]))
505  {
506  if (!iCharPos) break;
507  if (IsWholeWordSpacer(Text[--iCharPos])) break;
508  }
509  // search ending of word left and right
510  // bounds-check is done by zero-char at end, which is regarded as a spacer
511  iSelectionStart = iCharPos; iSelectionEnd = iCharPos + 1;
514  // set cursor pos to end of selection
516  // ignore last btn-down-selection
517  fLeftBtnDown = false;
518  }
519  break;
521  // set selection start
522  iSelectionStart = iSelectionEnd = GetCharPos(iX);
523  // set cursor pos here, too
525 #ifndef _WIN32
526  // Insert primary selection
527  InsertText(Application.Paste(false).c_str(), true);
528 #endif
529  break;
530  };
531  // scroll cursor in view
532  if (iPrevCursorPos != iCursorPos) ScrollCursorInView();
533  }
534 
535  void Edit::DoDragging(CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam)
536  {
537  // update cursor pos
538  int32_t iPrevCursorPos = iCursorPos;
539  iCursorPos = iSelectionEnd = GetCharPos(iX);
540  // scroll cursor in view
541  if (iPrevCursorPos != iCursorPos) ScrollCursorInView();
542  }
543 
544  void Edit::OnGetFocus(bool fByMouse)
545  {
546  // inherited
547  Control::OnGetFocus(fByMouse);
548  // select all
550  // begin with a flashing cursor
552  }
553 
555  {
556  // clear selection
558  // inherited
560  }
561 
563  {
564  // draw background
566  // draw frame
567  if (dwBorderColor)
568  {
569  int32_t x1=cgo.TargetX+rcBounds.x,y1=cgo.TargetY+rcBounds.y,x2=x1+rcBounds.Wdt,y2=y1+rcBounds.Hgt;
570  pDraw->DrawFrameDw(cgo.Surface, x1, y1, x2, y2-1, dwBorderColor);
571  pDraw->DrawFrameDw(cgo.Surface, x1+1, y1+1, x2-1, y2-2, dwBorderColor);
572  }
573  else
574  // default frame color
575  Draw3DFrame(cgo);
576  // clipping
577  int cx0,cy0,cx1,cy1; bool fClip, fOwnClip;
578  fClip = pDraw->GetPrimaryClipper(cx0,cy0,cx1,cy1);
579  float nclx1 = rcClientRect.x+cgo.TargetX-2, ncly1 = rcClientRect.y+cgo.TargetY, nclx2 = rcClientRect.x+rcClientRect.Wdt+cgo.TargetX+1, ncly2 = rcClientRect.y+rcClientRect.Hgt+cgo.TargetY;
580  pDraw->ApplyZoom(nclx1, ncly1);
581  pDraw->ApplyZoom(nclx2, ncly2);
582  fOwnClip = pDraw->SetPrimaryClipper(nclx1, ncly1, nclx2, ncly2);
583  // get usable height of edit field
584  int32_t iHgt = pFont->GetLineHeight(), iY0;
585  if (rcClientRect.Hgt <= iHgt)
586  {
587  // very narrow edit field: use all of it
588  iHgt=rcClientRect.Hgt;
589  iY0=rcClientRect.y;
590  }
591  else
592  {
593  // normal edit field: center text vertically
594  iY0 = rcClientRect.y+(rcClientRect.Hgt-iHgt)/2+1;
595  // don't overdo it with selection mark
596  iHgt-=2;
597  }
598  // get text to draw, apply password mask if neccessary
599  StdStrBuf Buf; char *pDrawText;
600  if (cPasswordMask)
601  {
603  pDrawText = Buf.getMData();
604  }
605  else
606  pDrawText = Text;
607  // draw selection
609  {
610  // get selection range
611  int32_t iSelBegin = std::min(iSelectionStart, iSelectionEnd);
612  int32_t iSelEnd = std::max(iSelectionStart, iSelectionEnd);
613  // get offsets in text
614  int32_t iSelX1, iSelX2, h;
615  char c = pDrawText[iSelBegin]; pDrawText[iSelBegin]=0; pFont->GetTextExtent(pDrawText, iSelX1, h, false); pDrawText[iSelBegin]=c;
616  c = pDrawText[iSelEnd]; pDrawText[iSelEnd]=0; pFont->GetTextExtent(pDrawText, iSelX2, h, false); pDrawText[iSelEnd]=c;
617  iSelX1 -= iXScroll; iSelX2 -= iXScroll;
618  // draw selection box around it
619  pDraw->DrawBoxDw(cgo.Surface, cgo.TargetX+rcClientRect.x+iSelX1,cgo.TargetY+iY0,rcClientRect.x+iSelX2-1+cgo.TargetX,iY0+iHgt-1+cgo.TargetY,0x7f7f7f00);
620  }
621  // draw edit text
622  pDraw->TextOut(pDrawText, *pFont, 1.0f, cgo.Surface, rcClientRect.x + cgo.TargetX - iXScroll, iY0 + cgo.TargetY - 1, dwFontClr, ALeft, false);
623  // draw cursor
624  bool fBlink = ((tLastInputTime - C4TimeMilliseconds::Now())/500)%2 == 0;
625  if (HasDrawFocus() && fBlink)
626  {
627  char cAtCursor = pDrawText[iCursorPos]; pDrawText[iCursorPos]=0; int32_t w,h,wc;
628  pFont->GetTextExtent(pDrawText, w, h, false);
629  pDrawText[iCursorPos] = cAtCursor;
630  pFont->GetTextExtent(CursorRepresentation, wc, h, false); wc/=2;
631  pDraw->TextOut(CursorRepresentation, *pFont, 1.5f, cgo.Surface, rcClientRect.x + cgo.TargetX + w - wc - iXScroll, iY0 + cgo.TargetY - h/3, dwFontClr, ALeft, false);
632  }
633  // unclip
634  if (fOwnClip)
635  {
636  if (fClip) pDraw->SetPrimaryClipper(cx0,cy0,cx1,cy1);
637  else pDraw->NoPrimaryClipper();
638  }
639  }
640 
642  {
643  // safety: no text?
644  if (!Text) return;
645  // select all
646  iSelectionStart = 0;
648  }
649 
650  ContextMenu *Edit::OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
651  {
652  // safety: no text?
653  if (!Text) return nullptr;
654  // create context menu
655  ContextMenu *pCtx = new ContextMenu();
656  // fill with any valid items
657  // get selected range
658  uint32_t iSelBegin = std::min(iSelectionStart, iSelectionEnd), iSelEnd = std::max(iSelectionStart, iSelectionEnd);
659  bool fAnythingSelected = (iSelBegin != iSelEnd);
660  if (fAnythingSelected)
661  {
662  pCtx->AddItem(LoadResStr("IDS_DLG_CUT"), LoadResStr("IDS_DLGTIP_CUT"), Ico_None, new CBMenuHandler<Edit>(this, &Edit::OnCtxCut));
663  pCtx->AddItem(LoadResStr("IDS_DLG_COPY"), LoadResStr("IDS_DLGTIP_COPY"), Ico_None, new CBMenuHandler<Edit>(this, &Edit::OnCtxCopy));
664  }
666  pCtx->AddItem(LoadResStr("IDS_DLG_PASTE"), LoadResStr("IDS_DLGTIP_PASTE"), Ico_None, new CBMenuHandler<Edit>(this, &Edit::OnCtxPaste));
667 
668  if (fAnythingSelected)
669  pCtx->AddItem(LoadResStr("IDS_DLG_CLEAR"), LoadResStr("IDS_DLGTIP_CLEAR"), Ico_None, new CBMenuHandler<Edit>(this, &Edit::OnCtxClear));
670  if (*Text && (iSelBegin!=0 || iSelEnd!=SLen(Text)))
671  pCtx->AddItem(LoadResStr("IDS_DLG_SELALL"), LoadResStr("IDS_DLGTIP_SELALL"), Ico_None, new CBMenuHandler<Edit>(this, &Edit::OnCtxSelAll));
672  // return ctx menu
673  return pCtx;
674  }
675 
676  bool Edit::GetCurrentWord(char *szTargetBuf, int32_t iMaxTargetBufLen)
677  {
678  // get word before cursor pos (for nick completion)
679  if (!Text || iCursorPos<=0) return false;
680  int32_t iPos = iCursorPos;
681  while (iPos>0)
682  if (IsWholeWordSpacer(Text[iPos-1])) break; else --iPos;
683  SCopy(Text + iPos, szTargetBuf, std::min(iCursorPos - iPos, iMaxTargetBufLen));
684  return !!*szTargetBuf;
685  }
686 
687 
688 // ----------------------------------------------------
689 // RenameEdit
690 
691  RenameEdit::RenameEdit(Label *pLabel) : Edit(pLabel->GetBounds(), true), fFinishing(false), pForLabel(pLabel)
692  {
693  // ctor - construct for label
694  assert(pForLabel);
695  pForLabel->SetVisibility(false);
696  InsertText(pForLabel->GetText(), true);
697  // put self into place
698  Container *pCont = pForLabel->GetParent();
699  assert(pCont);
700  pCont->AddElement(this);
701  Dialog *pDlg = GetDlg();
702  if (pDlg)
703  {
704  pPrevFocusCtrl = pDlg->GetFocus();
705  pDlg->SetFocus(this, false);
706  }
707  else pPrevFocusCtrl=nullptr;
708  // key binding for rename abort
710  keys.emplace_back(K_ESCAPE);
712  {
714  }
715  pKeyAbort = new C4KeyBinding(keys, "GUIRenameEditAbort", KEYSCOPE_Gui,
717  }
718 
720  {
721  delete pKeyAbort;
722  }
723 
725  {
726  OnCancelRename();
727  FinishRename();
728  }
729 
730  Edit::InputResult RenameEdit::OnFinishInput(bool fPasting, bool fPastingMore)
731  {
732  // any text?
733  if (!Text || !*Text)
734  {
735  // OK without text is regarded as abort
736  OnCancelRename();
737  FinishRename();
738  }
739  else switch (OnOKRename(Text))
740  {
741  case RR_Invalid:
742  {
743  // new name was not accepted: Continue editing
744  Dialog *pDlg = GetDlg();
745  if (pDlg) if (pDlg->GetFocus() != this) pDlg->SetFocus(this, false);
746  SelectAll();
747  break;
748  }
749 
750  case RR_Accepted:
751  // okay, rename to that text
752  FinishRename();
753  break;
754 
755  case RR_Deleted:
756  // this is invalid; don't do anything!
757  break;
758  }
759  return IR_Abort;
760  }
761 
762  void RenameEdit::FinishRename()
763  {
764  // done: restore stuff
765  fFinishing = true;
766  pForLabel->SetVisibility(true);
767  Dialog *pDlg = GetDlg();
768  if (pDlg && pPrevFocusCtrl) pDlg->SetFocus(pPrevFocusCtrl, false);
769  delete this;
770  }
771 
773  {
775  // callback when control looses focus: OK input
776  if (!fFinishing) OnFinishInput(false, false);
777  }
778 
779 
780 
781 // ----------------------------------------------------
782 // LabeledEdit
783 
784  LabeledEdit::LabeledEdit(const C4Rect &rcBounds, const char *szName, bool fMultiline, const char *szPrefText, CStdFont *pUseFont, uint32_t dwTextClr)
785  : C4GUI::Window()
786  {
787  if (!pUseFont) pUseFont = &(::GraphicsResource.TextFont);
789  ComponentAligner caMain(GetClientRect(), 0,0, true);
790  int32_t iLabelWdt=100, iLabelHgt=24;
791  pUseFont->GetTextExtent(szName, iLabelWdt, iLabelHgt, true);
792  C4Rect rcLabel, rcEdit;
793  if (fMultiline)
794  {
795  rcLabel = caMain.GetFromTop(iLabelHgt);
796  caMain.ExpandLeft(-2);
797  caMain.ExpandTop(-2);
798  rcEdit = caMain.GetAll();
799  }
800  else
801  {
802  rcLabel = caMain.GetFromLeft(iLabelWdt);
803  caMain.ExpandLeft(-2);
804  rcEdit = caMain.GetAll();
805  }
806  AddElement(new Label(szName, rcLabel, ALeft, dwTextClr, pUseFont, false));
807  AddElement(pEdit = new C4GUI::Edit(rcEdit, false));
808  pEdit->SetFont(pUseFont);
809  if (szPrefText) pEdit->InsertText(szPrefText, false);
810  }
811 
812  bool LabeledEdit::GetControlSize(int *piWdt, int *piHgt, const char *szForText, CStdFont *pForFont, bool fMultiline)
813  {
814  CStdFont *pUseFont = pForFont ? pForFont : &(::GraphicsResource.TextFont);
815  int32_t iLabelWdt=100, iLabelHgt=24;
816  pUseFont->GetTextExtent(szForText, iLabelWdt, iLabelHgt, true);
817  int32_t iEditWdt = 100, iEditHgt = Edit::GetCustomEditHeight(pUseFont);
818  if (fMultiline)
819  {
820  iEditWdt += 2; // indent edit a bit
821  if (piWdt) *piWdt = std::max<int32_t>(iLabelWdt, iEditWdt);
822  if (piHgt) *piHgt = iLabelHgt + iEditHgt + 2;
823  }
824  else
825  {
826  iLabelWdt += 2; // add a bit of spacing between label and edit
827  if (piWdt) *piWdt = iLabelWdt + iEditWdt;
828  if (piHgt) *piHgt = std::max<int32_t>(iLabelHgt, iEditHgt);
829  }
830  return true;
831  }
832 
833 } // end of namespace
834 
C4Config Config
Definition: C4Config.cpp:930
C4Draw * pDraw
Definition: C4Draw.cpp:42
C4Application Application
Definition: C4Globals.cpp:44
C4GraphicsResource GraphicsResource
#define C4GUI_MinWoodBarHgt
Definition: C4Gui.h:158
#define C4GUI_EditFontColor
Definition: C4Gui.h:70
#define C4GUI_EditBGColor
Definition: C4Gui.h:69
@ KEYSCOPE_Gui
C4KeyShiftState
@ KEYS_Shift
@ KEYS_Control
unsigned long C4KeyCode
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
const int32_t C4MC_Button_MiddleDown
const int32_t C4MC_Button_LeftUp
const int32_t C4MC_Button_LeftDown
const int32_t C4MC_Button_LeftDouble
const int ALeft
Definition: C4Surface.h:41
uint32_t DWORD
int SCharPos(char cTarget, const char *szInStr, int iIndex)
Definition: Standard.cpp:239
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool IsValidUtf8(const char *text, int length)
Definition: Standard.cpp:702
int Sign(T val)
Definition: Standard.h:45
size_t SLen(const char *sptr)
Definition: Standard.h:74
bool IsClipboardFull(bool fClipboard=true)
Definition: C4AppMac.mm:58
bool Copy(const std::string &text, bool fClipboard=true)
Definition: C4AppMac.mm:34
std::string Paste(bool fClipboard=true)
Definition: C4AppMac.mm:47
int32_t GamepadGuiControl
Definition: C4Config.h:233
C4ConfigControls Controls
Definition: C4Config.h:263
std::vector< C4KeyCodeEx > CodeList
void DrawFrameDw(C4Surface *sfcDest, int x1, int y1, int x2, int y2, DWORD dwClr, float width=1.0f)
Definition: C4Draw.cpp:635
bool NoPrimaryClipper()
Definition: C4Draw.cpp:237
void DrawBoxDw(C4Surface *sfcDest, int iX1, int iY1, int iX2, int iY2, DWORD dwClr)
Definition: C4Draw.cpp:840
bool GetPrimaryClipper(int &rX1, int &rY1, int &rX2, int &rY2)
Definition: C4Draw.cpp:716
bool SetPrimaryClipper(int iX1, int iY1, int iX2, int iY2)
Definition: C4Draw.cpp:217
void ApplyZoom(float &X, float &Y)
Definition: C4Draw.cpp:778
bool TextOut(const char *szText, CStdFont &rFont, float fZoom, C4Surface *sfcDest, float iTx, float iTy, DWORD dwFCol=0xffffffff, BYTE byForm=ALeft, bool fDoMarkup=true)
Definition: C4Draw.cpp:561
C4Surface * Surface
Definition: C4Facet.h:117
Element * pDragElement
Definition: C4Gui.h:2556
bool GetFromLeft(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
Definition: C4Gui.cpp:1076
void ExpandLeft(int32_t iByWdt)
Definition: C4Gui.h:2812
void ExpandTop(int32_t iByHgt)
Definition: C4Gui.h:2811
bool GetFromTop(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
Definition: C4Gui.cpp:1059
void GetAll(C4Rect &rcOut)
Definition: C4Gui.cpp:1125
void AddElement(Element *pChild)
void AddItem(const char *szText, const char *szToolTip=nullptr, Icons icoIcon=Ico_None, MenuHandler *pMenuHandler=nullptr, ContextHandler *pSubmenuHandler=nullptr)
Definition: C4Gui.h:1874
virtual void OnLooseFocus()
Definition: C4Gui.h:1057
friend class Dialog
Definition: C4Gui.h:1070
virtual void OnGetFocus(bool fByMouse)
Definition: C4Gui.h:1056
void MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) override
void SetFocus(Control *pCtrl, bool fByMouse)
Control * GetFocus()
Definition: C4Gui.h:2116
bool GetCurrentWord(char *szTargetBuf, int32_t iMaxTargetBufLen)
Definition: C4GuiEdit.cpp:676
static int32_t GetCustomEditHeight(CStdFont *pUseFont)
Definition: C4GuiEdit.cpp:119
CStdFont * pFont
Definition: C4Gui.h:1305
ContextMenu * OnContext(C4GUI::Element *pListItem, int32_t iX, int32_t iY)
Definition: C4GuiEdit.cpp:650
int32_t iCursorPos
Definition: C4Gui.h:1309
virtual InputResult OnFinishInput(bool fPasting, bool fPastingMore)
Definition: C4Gui.h:1327
char * Text
Definition: C4Gui.h:1306
int32_t iSelectionStart
Definition: C4Gui.h:1310
bool CharIn(const char *c) override
Definition: C4GuiEdit.cpp:453
@ IR_CloseDlg
Definition: C4Gui.h:1256
@ IR_Abort
Definition: C4Gui.h:1258
@ IR_CloseEdit
Definition: C4Gui.h:1257
int32_t iXScroll
Definition: C4Gui.h:1313
static int32_t GetDefaultEditHeight()
Definition: C4GuiEdit.cpp:113
bool fLeftBtnDown
Definition: C4Gui.h:1316
int32_t iSelectionEnd
Definition: C4Gui.h:1310
void OnCtxSelAll(C4GUI::Element *pThis)
Definition: C4Gui.h:1284
char cPasswordMask
Definition: C4Gui.h:1314
int32_t iBufferSize
Definition: C4Gui.h:1308
void ClearText()
Definition: C4GuiEdit.cpp:125
void DoDragging(CMouse &rMouse, int32_t iX, int32_t iY, DWORD dwKeyParam) override
Definition: C4GuiEdit.cpp:535
void DrawElement(C4TargetFacet &cgo) override
Definition: C4GuiEdit.cpp:562
uint32_t dwBGClr
Definition: C4Gui.h:1307
void OnCtxCopy(C4GUI::Element *pThis)
Definition: C4Gui.h:1280
~Edit() override
Definition: C4GuiEdit.cpp:86
void OnCtxClear(C4GUI::Element *pThis)
Definition: C4Gui.h:1283
Edit(const C4Rect &rtBounds, bool fFocusEdit=false)
Definition: C4GuiEdit.cpp:47
void SetFont(CStdFont *pToFont)
Definition: C4Gui.h:1348
C4TimeMilliseconds tLastInputTime
Definition: C4Gui.h:1312
void OnCtxCut(C4GUI::Element *pThis)
Definition: C4Gui.h:1282
void MouseInput(CMouse &rMouse, int32_t iButton, int32_t iX, int32_t iY, DWORD dwKeyParam) override
Definition: C4GuiEdit.cpp:464
void OnCtxPaste(C4GUI::Element *pThis)
Definition: C4Gui.h:1281
bool InsertText(const char *szText, bool fUser)
Definition: C4GuiEdit.cpp:163
uint32_t dwBorderColor
Definition: C4Gui.h:1307
void OnLooseFocus() override
Definition: C4GuiEdit.cpp:554
uint32_t dwFontClr
Definition: C4Gui.h:1307
void SelectAll()
Definition: C4GuiEdit.cpp:641
void DeleteSelection()
Definition: C4GuiEdit.cpp:149
int32_t iMaxTextLength
Definition: C4Gui.h:1311
void OnGetFocus(bool fByMouse) override
Definition: C4GuiEdit.cpp:544
virtual void SetVisibility(bool fToValue)
Definition: C4Gui.cpp:207
C4Rect rcBounds
Definition: C4Gui.h:385
void SetBounds(const C4Rect &rcNewBound)
Definition: C4Gui.h:446
Container * GetParent()
Definition: C4Gui.h:429
virtual class Dialog * GetDlg()
Definition: C4Gui.cpp:288
void SetContextHandler(ContextHandler *pNewHd)
Definition: C4Gui.h:465
void Draw3DFrame(C4TargetFacet &cgo, bool fUp=false, int32_t iIndent=1, BYTE byAlpha=C4GUI_BorderAlpha, bool fDrawTop=true, int32_t iTopOff=0, bool fDrawLeft=true, int32_t iLeftOff=0)
Definition: C4Gui.cpp:291
const char * GetText()
Definition: C4Gui.h:503
LabeledEdit(const C4Rect &rcBounds, const char *szName, bool fMultiline, const char *szPrefText=nullptr, CStdFont *pUseFont=nullptr, uint32_t dwTextClr=C4GUI_CaptionFontClr)
Definition: C4GuiEdit.cpp:784
static bool GetControlSize(int *piWdt, int *piHgt, const char *szForText, CStdFont *pForFont, bool fMultiline)
Definition: C4GuiEdit.cpp:812
bool KeyAbort()
Definition: C4Gui.h:1402
virtual void OnCancelRename()
Definition: C4Gui.h:1406
~RenameEdit() override
Definition: C4GuiEdit.cpp:719
virtual RenameResult OnOKRename(const char *szNewName)=0
InputResult OnFinishInput(bool fPasting, bool fPastingMore) override
Definition: C4GuiEdit.cpp:730
void OnLooseFocus() override
Definition: C4GuiEdit.cpp:772
RenameEdit(Label *pLabel)
Definition: C4GuiEdit.cpp:691
C4Rect rcClientRect
Definition: C4Gui.h:851
void UpdateOwnPos() override
C4Rect & GetClientRect() override
Definition: C4Gui.h:864
Definition: C4Rect.h:28
int32_t y
Definition: C4Rect.h:30
int32_t Hgt
Definition: C4Rect.h:30
int32_t Wdt
Definition: C4Rect.h:30
int32_t x
Definition: C4Rect.h:30
float TargetY
Definition: C4Facet.h:165
float TargetX
Definition: C4Facet.h:165
static C4TimeMilliseconds Now()
int GetLineHeight() const
Definition: C4FontLoader.h:125
bool GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup=true)
void AppendChars(char cChar, size_t iCnt)
Definition: StdBuf.h:582
const char * getData() const
Definition: StdBuf.h:442
char * getMData()
Definition: StdBuf.h:443
bool IsWholeWordSpacer(unsigned char c)
Definition: C4GuiEdit.cpp:350
@ Ico_None
Definition: C4Gui.h:640
void Cancel(T &keys)