// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" using namespace std; using namespace NLMISC; #include "nel/gui/action_handler.h" #include "group_editbox.h" #include "interface_manager.h" #include "../client_chat_manager.h" #include "people_interraction.h" #include "../r2/editor.h" /*#include "user_controls.h" #include "view.h" #include "misc.h" #include "input.h"*/ //////////// // EXTERN // //////////// extern CClientChatManager ChatMngr; //////////// // GLOBAL // //////////// /********************************************************************************************************** * * * edit handlers actions * * * ***********************************************************************************************************/ // *************************************************************************** // used for character classifiction (when the user press Ctrl-arrow) static inline uint getCharacterCategory(ucchar c) { if (c == ' ') return 0; if (c > 127 || isalpha((char) c)) return 1; // alpha & other characters if (isdigit((char) c)) return 2; return 3; // other cases, including punctuation } // *************************************************************************** /** skip a block of character in a string, (same behaviour than when Ctrl-arrow is pressed) * It returns the new index */ static uint skipUCCharsRight(uint startPos, const ucstring &str) { uint pos = startPos; uint endIndex = (uint)str.length(); uint ccat = getCharacterCategory(str[pos]); // skip characters of the same category while (pos != endIndex && getCharacterCategory(str[pos]) == ccat) ++pos; // skip spaces while (pos != endIndex && str[pos] == ' ') ++pos; return pos; } // *************************************************************************** /** skip a block of character in a string, (same behaviour than when Ctrl-arrow is pressed) * It returns the new index */ static uint skipUCCharsLeft(uint startPos, const ucstring &str) { uint pos = startPos; -- pos; while (pos != 0 && str[pos] == ' ') --pos; if (pos == 0) return pos; uint ccat = getCharacterCategory(str[pos]); // skip characters of the same category while (pos != 0 && getCharacterCategory(str[pos - 1]) == ccat) --pos; return pos; } // *************************************************************************** class CAHEdit : public IActionHandler { protected: // Current edit box. Filled by init. CGroupEditBox *_GroupEdit; bool _LooseSelection; // Init the action manager void init () { // Get the current edit box _GroupEdit = NULL; _LooseSelection = false; // Get the interface manager CInterfaceManager *pIM = CInterfaceManager::getInstance(); if (pIM) { CCtrlBase *basectrl = CWidgetManager::getInstance()->getCaptureKeyboard(); if (basectrl) _GroupEdit = dynamic_cast(basectrl); } if (_GroupEdit) _GroupEdit->stopParentBlink(); } // For action handler that modify selection void handleSelection () { if (_GroupEdit) { // If selection not active if (CGroupEditBox::getCurrSelection() == NULL) { // then start selection at curPos CGroupEditBox::setSelectCursorPos(_GroupEdit->getCursorPos()); CGroupEditBox::setCurrSelection (_GroupEdit); } } } // For action handler that unselect void handleUnselection() { if (_GroupEdit) { // If selection active CGroupEditBox *currSelection = dynamic_cast< CGroupEditBox* >( CGroupEditBox::getCurrSelection() ); if (currSelection != NULL) { if (currSelection != _GroupEdit) { nlwarning("Selection can only be on focus"); } // disable selection CGroupEditBox::setCurrSelection(NULL); _LooseSelection = true; } } } // Init part virtual void initPart () { init(); handleUnselection(); } // Action part virtual void actionPart () = 0; // R2 editor part (no op by default) virtual void forwardToEditor() {} // Tha action handler virtual void execute (CCtrlBase * /* pCaller */, const string &/* Params */) { initPart (); if (_GroupEdit) { actionPart (); } else { forwardToEditor(); } } }; // *************************************************************************** class CAHEditPreviousChar : public CAHEdit { void actionPart () { if (_LooseSelection) { sint32 minPos= min(_GroupEdit->getCursorPos(), _GroupEdit->getSelectCursorPos()); sint32 maxPos= max(_GroupEdit->getCursorPos(), _GroupEdit->getSelectCursorPos()); // Special Code if left or right: Set the cursor pos to bounds of selection. if(minPos!=maxPos) { _GroupEdit->setCursorPos(minPos); // don't call std Left return; } } if (_GroupEdit->getCursorPos() > 0) { _GroupEdit->setCursorPos(_GroupEdit->getCursorPos()-1); _GroupEdit->setCursorAtPreviousLineEnd(false); } } }; REGISTER_ACTION_HANDLER (CAHEditPreviousChar, "edit_previous_char"); // *************************************************************************** class CAHEditNextChar : public CAHEdit { void actionPart () { if (_LooseSelection) { sint32 minPos = min(_GroupEdit->getCursorPos(), _GroupEdit->getSelectCursorPos()); sint32 maxPos = max(_GroupEdit->getCursorPos(), _GroupEdit->getSelectCursorPos()); if(minPos!=maxPos) { _GroupEdit->setCursorPos(maxPos); // don't call std Right return; } } if (_GroupEdit->getCursorPos() < (sint32) _GroupEdit->getInputStringRef().length()) { _GroupEdit->setCursorPos(_GroupEdit->getCursorPos()+1); _GroupEdit->setCursorAtPreviousLineEnd(false); } } }; REGISTER_ACTION_HANDLER (CAHEditNextChar, "edit_next_char"); // *************************************************************************** class CAHEditPreviousWord : public CAHEdit { void actionPart () { if (_GroupEdit->getCursorPos() > 0) { _GroupEdit->setCursorPos(skipUCCharsLeft(_GroupEdit->getCursorPos(), _GroupEdit->getInputStringRef())); _GroupEdit->setCursorAtPreviousLineEnd(false); } } }; REGISTER_ACTION_HANDLER (CAHEditPreviousWord, "edit_previous_word"); // *************************************************************************** class CAHEditNextWord : public CAHEdit { void actionPart () { if (_GroupEdit->getCursorPos() < (sint32) _GroupEdit->getInputStringRef().length()) { _GroupEdit->setCursorPos(skipUCCharsRight(_GroupEdit->getCursorPos(), _GroupEdit->getInputStringRef())); _GroupEdit->setCursorAtPreviousLineEnd(false); } } }; REGISTER_ACTION_HANDLER (CAHEditNextWord, "edit_next_word"); // *************************************************************************** class CAHEditGotoLineBegin : public CAHEdit { void actionPart () { // go to the start of line if (_GroupEdit->getViewText()) { sint line = _GroupEdit->getViewText()->getLineFromIndex(_GroupEdit->getCursorPos() + (uint)_GroupEdit->getPrompt().length()); if (line == -1) return; sint newPos = std::max(_GroupEdit->getViewText()->getLineStartIndex(line), (sint) _GroupEdit->getPrompt().length()); if (newPos == -1) return; _GroupEdit->setCursorPos(newPos - (sint32)_GroupEdit->getPrompt().length()); _GroupEdit->setCursorAtPreviousLineEnd(false); } } }; REGISTER_ACTION_HANDLER (CAHEditGotoLineBegin, "edit_goto_line_begin"); // *************************************************************************** class CAHEditGotoLineEnd : public CAHEdit { void actionPart () { // go to the end of line if (_GroupEdit->getViewText()) { if (_GroupEdit->getViewText()->getMultiLine()) { sint line = _GroupEdit->getViewText()->getLineFromIndex(_GroupEdit->getCursorPos() + (uint)_GroupEdit->getPrompt().length(), _GroupEdit->isCursorAtPreviousLineEnd()); if (line == -1) return; sint newPos; bool endOfPreviousLine; _GroupEdit->getViewText()->getLineEndIndex(line, newPos, endOfPreviousLine); if (newPos != -1) { _GroupEdit->setCursorPos(newPos - (sint32)_GroupEdit->getPrompt().length()); _GroupEdit->setCursorAtPreviousLineEnd(endOfPreviousLine); } } else { _GroupEdit->setCursorPos((sint32)_GroupEdit->getPrompt().length() + (sint32)_GroupEdit->getInputString().length()); } } } }; REGISTER_ACTION_HANDLER (CAHEditGotoLineEnd, "edit_goto_line_end"); // *************************************************************************** class CAHEditGotoBlockBegin : public CAHEdit { void actionPart () { _GroupEdit->setCursorPos(0); _GroupEdit->setCursorAtPreviousLineEnd(false); } }; REGISTER_ACTION_HANDLER (CAHEditGotoBlockBegin, "edit_goto_block_begin"); // *************************************************************************** class CAHEditGotoBlockEnd : public CAHEdit { void actionPart () { _GroupEdit->setCursorPos((sint32)_GroupEdit->getInputStringRef().length()); _GroupEdit->setCursorAtPreviousLineEnd(false); } }; REGISTER_ACTION_HANDLER (CAHEditGotoBlockEnd, "edit_goto_block_end"); // *************************************************************************** class CAHEditPreviousLine : public CAHEdit { void actionPart () { if (_GroupEdit->getMaxHistoric() && (! _GroupEdit->getViewText()->getMultiLine())) { // Get the start of the string. ucstring startStr= _GroupEdit->getInputStringRef().substr(0, _GroupEdit->getCursorPos()); // Search all historic string that match startStr. for(sint i=_GroupEdit->getCurrentHistoricIndex()+1;i<(sint)_GroupEdit->getNumHistoric();i++) { if( _GroupEdit->getHistoric(i).compare(0, _GroupEdit->getCursorPos(), startStr)==0 ) { _GroupEdit->setInputStringRef (_GroupEdit->getHistoric(i)); _GroupEdit->setCurrentHistoricIndex(i); break; } } } else if (_GroupEdit->getViewText()->getMultiLine()) { uint cursorPosInText = _GroupEdit->getCursorPos() + (uint)_GroupEdit->getPrompt().length(); if ( (_GroupEdit->getCursorPos() == (sint32) _GroupEdit->getInputStringRef().length() && _GroupEdit->getViewText()->getNumLine() == 1) || _GroupEdit->getViewText()->getLineFromIndex(cursorPosInText, _GroupEdit->isCursorAtPreviousLineEnd()) == 0 ) // in the first line .. ? { // .. so do nothing return; } sint cx, cy; sint height; _GroupEdit->getViewText()->getCharacterPositionFromIndex(cursorPosInText, _GroupEdit->isCursorAtPreviousLineEnd(), cx, cy, height); cy += height + _GroupEdit->getViewText()->getMultiLineSpace(); uint newCharIndex; bool newLineEnd; _GroupEdit->getViewText()->getCharacterIndexFromPosition(cx, cy, newCharIndex, newLineEnd); if (newLineEnd) { _GroupEdit->setCursorPos(newCharIndex - (sint32)_GroupEdit->getPrompt().length()); _GroupEdit->setCursorAtPreviousLineEnd(true); sint32 newPos = _GroupEdit->getCursorPos(); clamp(newPos, (sint32) 0, (sint32) _GroupEdit->getInputStringRef().size()); _GroupEdit->setCursorPos(newPos); return; } _GroupEdit->setCursorAtPreviousLineEnd(false); // takes character whose X is closer to the current X sint cx0, cx1; sint cy0, cy1; _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex, _GroupEdit->isCursorAtPreviousLineEnd(), cx0, cy0, height); _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex + 1, _GroupEdit->isCursorAtPreviousLineEnd(), cx1, cy1, height); if (abs(cx0 - cx) < abs(cx1 - cx) || cy0 != cy1) { _GroupEdit->setCursorPos(newCharIndex); } else { _GroupEdit->setCursorPos(newCharIndex + 1); } _GroupEdit->setCursorPos(_GroupEdit->getCursorPos()-(sint32)_GroupEdit->getPrompt().length()); sint32 newpos = _GroupEdit->getCursorPos(); clamp(newpos, (sint32) 0, (sint32)_GroupEdit->getInputStringRef().size()); _GroupEdit->setCursorPos(newpos); } } }; REGISTER_ACTION_HANDLER (CAHEditPreviousLine, "edit_previous_line"); // *************************************************************************** class CAHEditNextLine : public CAHEdit { void actionPart () { if( (! _GroupEdit->getViewText()->getMultiLine()) && _GroupEdit->getMaxHistoric() && _GroupEdit->getCurrentHistoricIndex()>0) { // Get the start of the string. ucstring startStr= _GroupEdit->getInputStringRef().substr(0, _GroupEdit->getCursorPos()); // Search all historic string that match startStr. for(sint i=_GroupEdit->getCurrentHistoricIndex()-1;i>=0;i--) { if( _GroupEdit->getHistoric(i).compare(0, _GroupEdit->getCursorPos(), startStr)==0 ) { _GroupEdit->setInputStringRef (_GroupEdit->getHistoric(i)); _GroupEdit->setCurrentHistoricIndex(i); break; } } } else if (_GroupEdit->getViewText()->getMultiLine()) { sint cx, cy; sint height; _GroupEdit->getViewText()->getCharacterPositionFromIndex(_GroupEdit->getCursorPos() + (sint)_GroupEdit->getPrompt().length(), _GroupEdit->isCursorAtPreviousLineEnd(), cx, cy, height); if (cy != 0) { cy -= height; uint newCharIndex; bool newLineEnd; _GroupEdit->getViewText()->getCharacterIndexFromPosition(cx, cy, newCharIndex, newLineEnd); if (newLineEnd) { _GroupEdit->setCursorPos(newCharIndex - (sint32)_GroupEdit->getPrompt().length()); _GroupEdit->setCursorAtPreviousLineEnd(true); sint32 newPos = _GroupEdit->getCursorPos(); clamp(newPos, (sint32) 0, (sint32) _GroupEdit->getInputStringRef().size()); _GroupEdit->setCursorPos(newPos); return; } _GroupEdit->setCursorAtPreviousLineEnd(false); // takes character whose X is closer to the current X sint cx0, cx1; sint cy0, cy1; _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex, _GroupEdit->isCursorAtPreviousLineEnd(), cx0, cy0, height); _GroupEdit->getViewText()->getCharacterPositionFromIndex(newCharIndex + 1, _GroupEdit->isCursorAtPreviousLineEnd(), cx1, cy1, height); if (abs(cx0 - cx) < abs(cx1 - cx) || cy0 != cy1) { _GroupEdit->setCursorPos(newCharIndex); } else { _GroupEdit->setCursorPos(min(sint32(newCharIndex + 1), sint32(_GroupEdit->getInputStringRef().length() + _GroupEdit->getPrompt().length()))); } _GroupEdit->setCursorPos(_GroupEdit->getCursorPos()-(sint32)_GroupEdit->getPrompt().length()); sint32 newPos = _GroupEdit->getCursorPos(); clamp(newPos, (sint32) 0, (sint32) _GroupEdit->getInputStringRef().size()); _GroupEdit->setCursorPos(newPos); } } } }; REGISTER_ACTION_HANDLER (CAHEditNextLine, "edit_next_line"); // *************************************************************************** class CAHEditDeleteChar : public CAHEdit { protected: void initPart () { init(); } void actionPart () { // if selection is activated and not same cursors pos, then cut the selection if(CGroupEditBox::getCurrSelection() != NULL && _GroupEdit->getCursorPos() != CGroupEditBox::getSelectCursorPos()) { if (CGroupEditBox::getCurrSelection() != _GroupEdit) { nlwarning("Selection can only be on focus"); } _GroupEdit->cutSelection(); // Change Handler if (!_GroupEdit->getAHOnChange().empty()) { CInterfaceManager *pIM = CInterfaceManager::getInstance(); CAHManager::getInstance()->runActionHandler(_GroupEdit->getAHOnChange(), _GroupEdit, _GroupEdit->getParamsOnChange()); } } // else cut forwards else if(_GroupEdit->getCursorPos() < (sint32) _GroupEdit->getInputStringRef().length()) { ucstring inputString = _GroupEdit->getInputStringRef(); ucstring::iterator it = inputString.begin() + _GroupEdit->getCursorPos(); inputString.erase(it); _GroupEdit->setInputStringRef (inputString); if (!_GroupEdit->getAHOnChange().empty()) { CInterfaceManager *pIM = CInterfaceManager::getInstance(); CAHManager::getInstance()->runActionHandler(_GroupEdit->getAHOnChange(), _GroupEdit, _GroupEdit->getParamsOnChange()); } } // must stop selection in all case CGroupEditBox::setCurrSelection(NULL); _GroupEdit->setCursorAtPreviousLineEnd(false); } }; REGISTER_ACTION_HANDLER (CAHEditDeleteChar, "edit_delete_char"); // *************************************************************************** class CAHEditSelectAll : public CAHEdit { void initPart () { init(); } void actionPart () { _GroupEdit->setSelectionAll(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectAll, "edit_select_all"); // *************************************************************************** class CAHEditCopy : public CAHEdit { void initPart () { init(); } void actionPart () { _GroupEdit->copy(); } void forwardToEditor() { R2::getEditor().copy(); } }; REGISTER_ACTION_HANDLER (CAHEditCopy, "edit_copy"); // *************************************************************************** class CAHEditPaste : public CAHEdit { void initPart () { init(); } void actionPart () { _GroupEdit->paste(); } void forwardToEditor() { R2::getEditor().paste(); } }; REGISTER_ACTION_HANDLER (CAHEditPaste, "edit_paste"); // *************************************************************************** class CAHEditCut : public CAHEditDeleteChar { void initPart () { init(); } void actionPart () { // if selection is activated and not same cursors pos, then cut the selection if(CGroupEditBox::getCurrSelection() != NULL && _GroupEdit->getCursorPos() != CGroupEditBox::getSelectCursorPos()) { // Copy selection _GroupEdit->copy(); // Cut selection CAHEditDeleteChar::actionPart(); } } }; REGISTER_ACTION_HANDLER (CAHEditCut, "edit_cut"); // *************************************************************************** class CAHEditExpand : public CAHEdit { void initPart () { init(); } void actionPart () { _GroupEdit->expand(); } }; REGISTER_ACTION_HANDLER (CAHEditExpand, "edit_expand"); // *************************************************************************** class CAHEditExpandOrCycleTell : public CAHEdit { void initPart () { init(); } void actionPart () { // If the line starts with '/tell ', do not try to expand static const ucstring TELL_STR("/tell "); if (_GroupEdit->getInputString().substr(0, TELL_STR.length()) != TELL_STR) { if (_GroupEdit->expand()) return; } CInterfaceManager *im = CInterfaceManager::getInstance(); if (!im->isInGame()) return; // there was no / at the start of the line so try to cycle through the last people on which a tell was done const ucstring *lastTellPeople = ChatMngr.cycleLastTell(); if (!lastTellPeople) return; // Get chat box from ist edit box // If it isn't a user chat or the main chat, just display 'tell' with the name. Otherwise, change the target of the window CChatWindow *cw = getChatWndMgr().getChatWindowFromCaller(_GroupEdit); if (!cw) return; CFilteredChat *fc = PeopleInterraction.getFilteredChatFromChatWindow(cw); if (fc) { fc->Filter.setTargetPlayer(*lastTellPeople); } else { // it is not a filtered chat, display 'tell' (must be ingame) _GroupEdit->setCommand(ucstring("tell ") + *lastTellPeople + (ucchar) ' ', false); } } }; REGISTER_ACTION_HANDLER (CAHEditExpandOrCycleTell, "edit_expand_or_cycle_tell"); // *************************************************************************** class CAHEditBack: public CAHEdit { void initPart () { init(); } void actionPart () { _GroupEdit->back(); } }; REGISTER_ACTION_HANDLER (CAHEditBack, "edit_back"); // *************************************************************************** class CAHEditSelectPreviousChar : public CAHEditPreviousChar { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectPreviousChar, "edit_select_previous_char"); // *************************************************************************** class CAHEditSelectNextChar : public CAHEditNextChar { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectNextChar, "edit_select_next_char"); // *************************************************************************** class CAHEditSelectPreviousWord : public CAHEditPreviousWord { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectPreviousWord, "edit_select_previous_word"); // *************************************************************************** class CAHEditSelectNextWord : public CAHEditNextWord { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectNextWord, "edit_select_next_word"); // *************************************************************************** class CAHEditSelectToLineBegin : public CAHEditGotoLineBegin { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectToLineBegin, "edit_select_to_line_begin"); // *************************************************************************** class CAHEditSelectToLineEnd : public CAHEditGotoLineEnd { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectToLineEnd, "edit_select_to_line_end"); // *************************************************************************** class CAHEditSelectToBlockBegin : public CAHEditGotoBlockBegin { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectToBlockBegin, "edit_select_to_block_begin"); // *************************************************************************** class CAHEditSelectToBlockEnd : public CAHEditGotoBlockEnd { void initPart () { init(); handleSelection(); } }; REGISTER_ACTION_HANDLER (CAHEditSelectToBlockEnd, "edit_select_to_block_end"); // ***************************************************************************