1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2018 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2013 Laszlo KIS-ADAM (dfighter) <dfighter1985@gmail.com>
6 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU Affero General Public License as
10 // published by the Free Software Foundation, either version 3 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU Affero General Public License for more details.
18 // You should have received a copy of the GNU Affero General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
27 using namespace NLMISC
;
29 #include "nel/gui/action_handler.h"
30 #include "nel/gui/group_editbox.h"
31 #include "nel/misc/utf_string_view.h"
32 #include "interface_manager.h"
33 #include "../client_chat_manager.h"
34 #include "people_interraction.h"
35 #include "../r2/editor.h"
36 /*#include "user_controls.h"
45 extern CClientChatManager ChatMngr
;
52 /**********************************************************************************************************
54 * edit handlers actions *
56 ***********************************************************************************************************/
58 // ***************************************************************************
60 // used for character classifiction (when the user press Ctrl-arrow)
61 static inline uint
getCharacterCategory(u32char c
)
63 if (c
== ' ') return 0;
64 if (c
> 127 || isalpha((char) c
)) return 1; // alpha & other characters
65 if (isdigit((char) c
)) return 2;
66 return 3; // other cases, including punctuation
69 // ***************************************************************************
71 /** skip a block of character in a string, (same behaviour than when Ctrl-arrow is pressed)
72 * It returns the new index
74 static uint
skipUCCharsRight(uint startPos
, const ::u32string
&str
)
77 uint endIndex
= (uint
)str
.length();
78 uint ccat
= getCharacterCategory(str
[pos
]);
79 // skip characters of the same category
80 while (pos
!= endIndex
&& getCharacterCategory(str
[pos
]) == ccat
) ++pos
;
82 while (pos
!= endIndex
&& str
[pos
] == ' ') ++pos
;
86 // ***************************************************************************
88 /** skip a block of character in a string, (same behaviour than when Ctrl-arrow is pressed)
89 * It returns the new index
91 static uint
skipUCCharsLeft(uint startPos
, const ::u32string
&str
)
95 while (pos
!= 0 && str
[pos
] == ' ') --pos
;
96 if (pos
== 0) return pos
;
97 uint ccat
= getCharacterCategory(str
[pos
]);
98 // skip characters of the same category
99 while (pos
!= 0 && getCharacterCategory(str
[pos
- 1]) == ccat
) --pos
;
103 // ***************************************************************************
105 class CAHEdit
: public IActionHandler
108 // Current edit box. Filled by init.
109 CGroupEditBox
*_GroupEdit
;
110 bool _LooseSelection
;
112 // Init the action manager
115 // Get the current edit box
117 _LooseSelection
= false;
119 // Get the interface manager
120 CInterfaceManager
*pIM
= CInterfaceManager::getInstance();
123 CCtrlBase
*basectrl
= CWidgetManager::getInstance()->getCaptureKeyboard();
125 _GroupEdit
= dynamic_cast<CGroupEditBox
*>(basectrl
);
129 _GroupEdit
->stopParentBlink();
132 // For action handler that modify selection
133 void handleSelection ()
137 // If selection not active
138 if (CGroupEditBox::getCurrSelection() == NULL
)
140 // then start selection at curPos
141 CGroupEditBox::setSelectCursorPos(_GroupEdit
->getCursorPos());
142 CGroupEditBox::setCurrSelection (_GroupEdit
);
147 // For action handler that unselect
148 void handleUnselection()
152 // If selection active
153 CGroupEditBox
*currSelection
= dynamic_cast< CGroupEditBox
* >( CGroupEditBox::getCurrSelection() );
154 if (currSelection
!= NULL
)
156 if (currSelection
!= _GroupEdit
)
158 nlwarning("Selection can only be on focus");
162 CGroupEditBox::setCurrSelection(NULL
);
163 _LooseSelection
= true;
169 virtual void initPart ()
176 virtual void actionPart () = 0;
178 // R2 editor part (no op by default)
179 virtual void forwardToEditor() {}
181 // Tha action handler
182 virtual void execute (CCtrlBase
* /* pCaller */, const string
&/* Params */)
196 // ***************************************************************************
198 class CAHEditPreviousChar
: public CAHEdit
204 sint32 minPos
= min(_GroupEdit
->getCursorPos(), _GroupEdit
->getSelectCursorPos());
205 sint32 maxPos
= max(_GroupEdit
->getCursorPos(), _GroupEdit
->getSelectCursorPos());
207 // Special Code if left or right: Set the cursor pos to bounds of selection.
210 _GroupEdit
->setCursorPos(minPos
);
211 // don't call std Left
216 if (_GroupEdit
->getCursorPos() > 0)
218 _GroupEdit
->setCursorPos(_GroupEdit
->getCursorPos()-1);
219 _GroupEdit
->setCursorAtPreviousLineEnd(false);
223 REGISTER_ACTION_HANDLER (CAHEditPreviousChar
, "edit_previous_char");
225 // ***************************************************************************
227 class CAHEditNextChar
: public CAHEdit
233 sint32 minPos
= min(_GroupEdit
->getCursorPos(), _GroupEdit
->getSelectCursorPos());
234 sint32 maxPos
= max(_GroupEdit
->getCursorPos(), _GroupEdit
->getSelectCursorPos());
238 _GroupEdit
->setCursorPos(maxPos
);
239 // don't call std Right
244 if (_GroupEdit
->getCursorPos() < (sint32
) _GroupEdit
->getInputStringRef().length())
246 _GroupEdit
->setCursorPos(_GroupEdit
->getCursorPos()+1);
247 _GroupEdit
->setCursorAtPreviousLineEnd(false);
251 REGISTER_ACTION_HANDLER (CAHEditNextChar
, "edit_next_char");
253 // ***************************************************************************
255 class CAHEditPreviousWord
: public CAHEdit
259 if (_GroupEdit
->getCursorPos() > 0)
261 _GroupEdit
->setCursorPos(skipUCCharsLeft(_GroupEdit
->getCursorPos(), _GroupEdit
->getInputStringRef()));
262 _GroupEdit
->setCursorAtPreviousLineEnd(false);
266 REGISTER_ACTION_HANDLER (CAHEditPreviousWord
, "edit_previous_word");
268 // ***************************************************************************
270 class CAHEditNextWord
: public CAHEdit
274 if (_GroupEdit
->getCursorPos() < (sint32
) _GroupEdit
->getInputStringRef().length())
276 _GroupEdit
->setCursorPos(skipUCCharsRight(_GroupEdit
->getCursorPos(), _GroupEdit
->getInputStringRef()));
277 _GroupEdit
->setCursorAtPreviousLineEnd(false);
281 REGISTER_ACTION_HANDLER (CAHEditNextWord
, "edit_next_word");
283 // ***************************************************************************
285 class CAHEditGotoLineBegin
: public CAHEdit
289 // go to the start of line
290 if (_GroupEdit
->getViewText())
292 sint line
= _GroupEdit
->getViewText()->getLineFromIndex(_GroupEdit
->getCursorPos() + (uint
)_GroupEdit
->getPromptRef().length());
293 if (line
== -1) return;
294 sint newPos
= std::max(_GroupEdit
->getViewText()->getLineStartIndex(line
), (sint
) _GroupEdit
->getPromptRef().length());
295 if (newPos
== -1) return;
296 _GroupEdit
->setCursorPos(newPos
- (sint32
)_GroupEdit
->getPromptRef().length());
297 _GroupEdit
->setCursorAtPreviousLineEnd(false);
301 REGISTER_ACTION_HANDLER (CAHEditGotoLineBegin
, "edit_goto_line_begin");
303 // ***************************************************************************
305 class CAHEditGotoLineEnd
: public CAHEdit
309 // go to the end of line
310 if (_GroupEdit
->getViewText())
312 if (_GroupEdit
->getViewText()->getMultiLine())
314 sint line
= _GroupEdit
->getViewText()->getLineFromIndex(_GroupEdit
->getCursorPos() + (uint
)_GroupEdit
->getPromptRef().length(), _GroupEdit
->isCursorAtPreviousLineEnd());
315 if (line
== -1) return;
317 bool endOfPreviousLine
;
318 _GroupEdit
->getViewText()->getLineEndIndex(line
, newPos
, endOfPreviousLine
);
321 _GroupEdit
->setCursorPos(newPos
- (sint32
)_GroupEdit
->getPromptRef().length());
322 _GroupEdit
->setCursorAtPreviousLineEnd(endOfPreviousLine
);
327 _GroupEdit
->setCursorPos((sint32
)_GroupEdit
->getPromptRef().length() + (sint32
)_GroupEdit
->getInputString().length());
332 REGISTER_ACTION_HANDLER (CAHEditGotoLineEnd
, "edit_goto_line_end");
334 // ***************************************************************************
336 class CAHEditGotoBlockBegin
: public CAHEdit
340 _GroupEdit
->setCursorPos(0);
341 _GroupEdit
->setCursorAtPreviousLineEnd(false);
344 REGISTER_ACTION_HANDLER (CAHEditGotoBlockBegin
, "edit_goto_block_begin");
346 // ***************************************************************************
348 class CAHEditGotoBlockEnd
: public CAHEdit
352 _GroupEdit
->setCursorPos((sint32
)_GroupEdit
->getInputStringRef().length());
353 _GroupEdit
->setCursorAtPreviousLineEnd(false);
356 REGISTER_ACTION_HANDLER (CAHEditGotoBlockEnd
, "edit_goto_block_end");
358 // ***************************************************************************
360 class CAHEditPreviousLine
: public CAHEdit
364 if (_GroupEdit
->getMaxHistoric() && (! _GroupEdit
->getViewText()->getMultiLine()))
366 // Get the start of the string.
367 ::u32string startStr
= _GroupEdit
->getInputStringRef().substr(0, _GroupEdit
->getCursorPos());
369 // Search all historic string that match startStr.
370 for(sint i
=_GroupEdit
->getCurrentHistoricIndex()+1;i
<(sint
)_GroupEdit
->getNumHistoric();i
++)
372 if( _GroupEdit
->getHistoric(i
).compare(0, _GroupEdit
->getCursorPos(), startStr
)==0 )
374 _GroupEdit
->setInputStringRef (_GroupEdit
->getHistoric(i
));
375 _GroupEdit
->setCurrentHistoricIndex(i
);
380 else if (_GroupEdit
->getViewText()->getMultiLine())
382 uint cursorPosInText
= _GroupEdit
->getCursorPos() + (uint
)_GroupEdit
->getPromptRef().length();
384 (_GroupEdit
->getCursorPos() == (sint32
) _GroupEdit
->getInputStringRef().length() && _GroupEdit
->getViewText()->getNumLine() == 1) ||
385 _GroupEdit
->getViewText()->getLineFromIndex(cursorPosInText
, _GroupEdit
->isCursorAtPreviousLineEnd()) == 0
386 ) // in the first line .. ?
393 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(cursorPosInText
, _GroupEdit
->isCursorAtPreviousLineEnd(), cx
, cy
, height
);
394 cy
+= _GroupEdit
->getViewText()->getLineHeight();
397 _GroupEdit
->getViewText()->getCharacterIndexFromPosition(cx
, cy
, newCharIndex
, newLineEnd
);
400 _GroupEdit
->setCursorPos(newCharIndex
- (sint32
)_GroupEdit
->getPromptRef().length());
401 _GroupEdit
->setCursorAtPreviousLineEnd(true);
402 sint32 newPos
= _GroupEdit
->getCursorPos();
403 clamp(newPos
, (sint32
) 0, (sint32
) _GroupEdit
->getInputStringRef().size());
404 _GroupEdit
->setCursorPos(newPos
);
407 _GroupEdit
->setCursorAtPreviousLineEnd(false);
408 // takes character whose X is closer to the current X
411 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(newCharIndex
, _GroupEdit
->isCursorAtPreviousLineEnd(), cx0
, cy0
, height
);
412 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(newCharIndex
+ 1, _GroupEdit
->isCursorAtPreviousLineEnd(), cx1
, cy1
, height
);
413 if (abs(cx0
- cx
) < abs(cx1
- cx
) || cy0
!= cy1
)
415 _GroupEdit
->setCursorPos(newCharIndex
);
419 _GroupEdit
->setCursorPos(newCharIndex
+ 1);
421 _GroupEdit
->setCursorPos(_GroupEdit
->getCursorPos()-(sint32
)_GroupEdit
->getPromptRef().length());
422 sint32 newpos
= _GroupEdit
->getCursorPos();
423 clamp(newpos
, (sint32
) 0, (sint32
)_GroupEdit
->getInputStringRef().size());
424 _GroupEdit
->setCursorPos(newpos
);
428 REGISTER_ACTION_HANDLER (CAHEditPreviousLine
, "edit_previous_line");
430 // ***************************************************************************
432 class CAHEditNextLine
: public CAHEdit
436 if( (! _GroupEdit
->getViewText()->getMultiLine()) && _GroupEdit
->getMaxHistoric() && _GroupEdit
->getCurrentHistoricIndex()>0)
438 // Get the start of the string.
439 ::u32string startStr
= _GroupEdit
->getInputStringRef().substr(0, _GroupEdit
->getCursorPos());
441 // Search all historic string that match startStr.
442 for(sint i
=_GroupEdit
->getCurrentHistoricIndex()-1;i
>=0;i
--)
444 if( _GroupEdit
->getHistoric(i
).compare(0, _GroupEdit
->getCursorPos(), startStr
)==0 )
446 _GroupEdit
->setInputStringRef (_GroupEdit
->getHistoric(i
));
447 _GroupEdit
->setCurrentHistoricIndex(i
);
452 else if (_GroupEdit
->getViewText()->getMultiLine())
456 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(_GroupEdit
->getCursorPos() + (sint
)_GroupEdit
->getPromptRef().length(), _GroupEdit
->isCursorAtPreviousLineEnd(), cx
, cy
, height
);
462 _GroupEdit
->getViewText()->getCharacterIndexFromPosition(cx
, cy
, newCharIndex
, newLineEnd
);
465 _GroupEdit
->setCursorPos(newCharIndex
- (sint32
)_GroupEdit
->getPromptRef().length());
466 _GroupEdit
->setCursorAtPreviousLineEnd(true);
467 sint32 newPos
= _GroupEdit
->getCursorPos();
468 clamp(newPos
, (sint32
) 0, (sint32
) _GroupEdit
->getInputStringRef().size());
469 _GroupEdit
->setCursorPos(newPos
);
472 _GroupEdit
->setCursorAtPreviousLineEnd(false);
473 // takes character whose X is closer to the current X
476 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(newCharIndex
, _GroupEdit
->isCursorAtPreviousLineEnd(), cx0
, cy0
, height
);
477 _GroupEdit
->getViewText()->getCharacterPositionFromIndex(newCharIndex
+ 1, _GroupEdit
->isCursorAtPreviousLineEnd(), cx1
, cy1
, height
);
478 if (abs(cx0
- cx
) < abs(cx1
- cx
) || cy0
!= cy1
)
480 _GroupEdit
->setCursorPos(newCharIndex
);
484 _GroupEdit
->setCursorPos(min(sint32(newCharIndex
+ 1), sint32(_GroupEdit
->getInputStringRef().length() + _GroupEdit
->getPromptRef().length())));
486 _GroupEdit
->setCursorPos(_GroupEdit
->getCursorPos()-(sint32
)_GroupEdit
->getPromptRef().length());
487 sint32 newPos
= _GroupEdit
->getCursorPos();
488 clamp(newPos
, (sint32
) 0, (sint32
) _GroupEdit
->getInputStringRef().size());
489 _GroupEdit
->setCursorPos(newPos
);
494 REGISTER_ACTION_HANDLER (CAHEditNextLine
, "edit_next_line");
496 // ***************************************************************************
498 class CAHEditDeleteChar
: public CAHEdit
507 // if selection is activated and not same cursors pos, then cut the selection
508 if(CGroupEditBox::getCurrSelection() != NULL
&& _GroupEdit
->getCursorPos() != CGroupEditBox::getSelectCursorPos())
510 if (CGroupEditBox::getCurrSelection() != _GroupEdit
)
512 nlwarning("Selection can only be on focus");
514 _GroupEdit
->cutSelection();
516 if (!_GroupEdit
->getAHOnChange().empty())
518 CInterfaceManager
*pIM
= CInterfaceManager::getInstance();
519 CAHManager::getInstance()->runActionHandler(_GroupEdit
->getAHOnChange(), _GroupEdit
, _GroupEdit
->getParamsOnChange());
523 else if(_GroupEdit
->getCursorPos() < (sint32
) _GroupEdit
->getInputStringRef().length())
525 ::u32string inputString
= _GroupEdit
->getInputStringRef();
526 ::u32string::iterator it
= inputString
.begin() + _GroupEdit
->getCursorPos();
527 inputString
.erase(it
);
528 _GroupEdit
->setInputStringRef (inputString
);
529 if (!_GroupEdit
->getAHOnChange().empty())
531 CInterfaceManager
*pIM
= CInterfaceManager::getInstance();
532 CAHManager::getInstance()->runActionHandler(_GroupEdit
->getAHOnChange(), _GroupEdit
, _GroupEdit
->getParamsOnChange());
535 // must stop selection in all case
536 CGroupEditBox::setCurrSelection(NULL
);
537 _GroupEdit
->setCursorAtPreviousLineEnd(false);
540 REGISTER_ACTION_HANDLER (CAHEditDeleteChar
, "edit_delete_char");
542 // ***************************************************************************
544 class CAHEditSelectAll
: public CAHEdit
552 _GroupEdit
->setSelectionAll();
555 REGISTER_ACTION_HANDLER (CAHEditSelectAll
, "edit_select_all");
557 // ***************************************************************************
559 class CAHEditCopy
: public CAHEdit
569 void forwardToEditor()
571 R2::getEditor().copy();
574 REGISTER_ACTION_HANDLER (CAHEditCopy
, "edit_copy");
576 // ***************************************************************************
578 class CAHEditPaste
: public CAHEdit
588 void forwardToEditor()
590 R2::getEditor().paste();
593 REGISTER_ACTION_HANDLER (CAHEditPaste
, "edit_paste");
595 // ***************************************************************************
597 class CAHEditCut
: public CAHEditDeleteChar
605 // if selection is activated and not same cursors pos, then cut the selection
606 if(CGroupEditBox::getCurrSelection() != NULL
&& _GroupEdit
->getCursorPos() != CGroupEditBox::getSelectCursorPos())
612 CAHEditDeleteChar::actionPart();
616 REGISTER_ACTION_HANDLER (CAHEditCut
, "edit_cut");
618 // ***************************************************************************
620 class CAHEditExpand
: public CAHEdit
628 _GroupEdit
->expand();
631 REGISTER_ACTION_HANDLER (CAHEditExpand
, "edit_expand");
633 // ***************************************************************************
634 class CAHEditExpandOrCycleTell
: public CAHEdit
642 // If the line starts with '/tell ', do not try to expand
643 if (!NLMISC::startsWith(_GroupEdit
->getInputString(), "/tell "))
645 if (_GroupEdit
->expand()) return;
647 CInterfaceManager
*im
= CInterfaceManager::getInstance();
648 if (!im
->isInGame()) return;
649 // there was no / at the start of the line so try to cycle through the last people on which a tell was done
650 const string
*lastTellPeople
= ChatMngr
.cycleLastTell();
651 if (!lastTellPeople
) return;
652 // Get chat box from ist edit box
653 // If it isn't a user chat or the main chat, just display 'tell' with the name. Otherwise, change the target of the window
654 CChatWindow
*cw
= getChatWndMgr().getChatWindowFromCaller(_GroupEdit
);
656 CFilteredChat
*fc
= PeopleInterraction
.getFilteredChatFromChatWindow(cw
);
659 fc
->Filter
.setTargetPlayer(*lastTellPeople
);
663 // it is not a filtered chat, display 'tell' (must be ingame)
664 _GroupEdit
->setCommand("tell " + (*lastTellPeople
) + ' ', false);
668 REGISTER_ACTION_HANDLER (CAHEditExpandOrCycleTell
, "edit_expand_or_cycle_tell");
670 // ***************************************************************************
672 class CAHEditBack
: public CAHEdit
683 REGISTER_ACTION_HANDLER (CAHEditBack
, "edit_back");
685 // ***************************************************************************
687 class CAHEditSelectPreviousChar
: public CAHEditPreviousChar
695 REGISTER_ACTION_HANDLER (CAHEditSelectPreviousChar
, "edit_select_previous_char");
697 // ***************************************************************************
699 class CAHEditSelectNextChar
: public CAHEditNextChar
707 REGISTER_ACTION_HANDLER (CAHEditSelectNextChar
, "edit_select_next_char");
709 // ***************************************************************************
711 class CAHEditSelectPreviousWord
: public CAHEditPreviousWord
719 REGISTER_ACTION_HANDLER (CAHEditSelectPreviousWord
, "edit_select_previous_word");
721 // ***************************************************************************
723 class CAHEditSelectNextWord
: public CAHEditNextWord
731 REGISTER_ACTION_HANDLER (CAHEditSelectNextWord
, "edit_select_next_word");
733 // ***************************************************************************
735 class CAHEditSelectToLineBegin
: public CAHEditGotoLineBegin
743 REGISTER_ACTION_HANDLER (CAHEditSelectToLineBegin
, "edit_select_to_line_begin");
745 // ***************************************************************************
747 class CAHEditSelectToLineEnd
: public CAHEditGotoLineEnd
755 REGISTER_ACTION_HANDLER (CAHEditSelectToLineEnd
, "edit_select_to_line_end");
757 // ***************************************************************************
759 class CAHEditSelectToBlockBegin
: public CAHEditGotoBlockBegin
767 REGISTER_ACTION_HANDLER (CAHEditSelectToBlockBegin
, "edit_select_to_block_begin");
769 // ***************************************************************************
771 class CAHEditSelectToBlockEnd
: public CAHEditGotoBlockEnd
779 REGISTER_ACTION_HANDLER (CAHEditSelectToBlockEnd
, "edit_select_to_block_end");
781 // ***************************************************************************