llchatbar.cpp

Go to the documentation of this file.
00001 
00032 #include "llviewerprecompiledheaders.h"
00033 
00034 #include "llchatbar.h"
00035 
00036 #include "imageids.h"
00037 #include "llfontgl.h"
00038 #include "llrect.h"
00039 #include "llerror.h"
00040 #include "llparcel.h"
00041 #include "llstring.h"
00042 #include "message.h"
00043 #include "llfocusmgr.h"
00044 
00045 #include "llagent.h"
00046 #include "llbutton.h"
00047 #include "llcombobox.h"
00048 #include "llcommandhandler.h"   // secondlife:///app/chat/ support
00049 #include "llviewercontrol.h"
00050 #include "llfloaterchat.h"
00051 #include "llgesturemgr.h"
00052 #include "llkeyboard.h"
00053 #include "lllineeditor.h"
00054 #include "llstatusbar.h"
00055 #include "lltextbox.h"
00056 #include "lluiconstants.h"
00057 #include "llviewergesture.h"                    // for triggering gestures
00058 #include "llviewermenu.h"               // for deleting object with DEL key
00059 #include "llviewerstats.h"
00060 #include "llviewerwindow.h"
00061 #include "llframetimer.h"
00062 #include "llresmgr.h"
00063 #include "llworld.h"
00064 #include "llinventorymodel.h"
00065 #include "llmultigesture.h"
00066 #include "llui.h"
00067 #include "llviewermenu.h"
00068 #include "lluictrlfactory.h"
00069 
00070 
00071 //
00072 // Globals
00073 //
00074 const F32 AGENT_TYPING_TIMEOUT = 5.f;   // seconds
00075 
00076 LLChatBar *gChatBar = NULL;
00077 
00078 // legacy calllback glue
00079 void toggleChatHistory(void* user_data);
00080 void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel);
00081 
00082 
00083 class LLChatBarGestureObserver : public LLGestureManagerObserver
00084 {
00085 public:
00086         LLChatBarGestureObserver(LLChatBar* chat_barp) : mChatBar(chat_barp){}
00087         virtual ~LLChatBarGestureObserver() {}
00088         virtual void changed() { mChatBar->refreshGestures(); }
00089 private:
00090         LLChatBar* mChatBar;
00091 };
00092 
00093 
00094 //
00095 // Functions
00096 //
00097 
00098 LLChatBar::LLChatBar() 
00099 :       LLPanel("", LLRect(), BORDER_NO),
00100         mInputEditor(NULL),
00101         mGestureLabelTimer(),
00102         mLastSpecialChatChannel(0),
00103         mIsBuilt(FALSE),
00104         mGestureCombo(NULL),
00105         mObserver(NULL)
00106 {
00107         setIsChrome(TRUE);
00108         
00109         #if !LL_RELEASE_FOR_DOWNLOAD
00110         childDisplayNotFound();
00111 #endif
00112 }
00113 
00114 
00115 LLChatBar::~LLChatBar()
00116 {
00117         delete mObserver;
00118         mObserver = NULL;
00119         // LLView destructor cleans up children
00120 }
00121 
00122 BOOL LLChatBar::postBuild()
00123 {
00124         childSetAction("History", toggleChatHistory, this);
00125         childSetCommitCallback("Say", onClickSay, this);
00126 
00127         // attempt to bind to an existing combo box named gesture
00128         setGestureCombo(getChild<LLComboBox>( "Gesture"));
00129 
00130         LLButton * sayp = getChild<LLButton>("Say");
00131         if(sayp)
00132         {
00133                 setDefaultBtn(sayp);
00134         }
00135 
00136         mInputEditor = getChild<LLLineEditor>("Chat Editor");
00137         if (mInputEditor)
00138         {
00139                 mInputEditor->setCallbackUserData(this);
00140                 mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke);
00141                 mInputEditor->setFocusLostCallback(&onInputEditorFocusLost, this);
00142                 mInputEditor->setFocusReceivedCallback( &onInputEditorGainFocus, this );
00143                 mInputEditor->setCommitOnFocusLost( FALSE );
00144                 mInputEditor->setRevertOnEsc( FALSE );
00145                 mInputEditor->setIgnoreTab(TRUE);
00146                 mInputEditor->setPassDelete(TRUE);
00147                 mInputEditor->setReplaceNewlinesWithSpaces(FALSE);
00148 
00149                 mInputEditor->setMaxTextLength(1023);
00150                 mInputEditor->setEnableLineHistory(TRUE);
00151         }
00152 
00153         mIsBuilt = TRUE;
00154 
00155         return TRUE;
00156 }
00157 
00158 //-----------------------------------------------------------------------
00159 // Overrides
00160 //-----------------------------------------------------------------------
00161 
00162 // virtual
00163 BOOL LLChatBar::handleKeyHere( KEY key, MASK mask )
00164 {
00165         BOOL handled = FALSE;
00166 
00167         // ALT-RETURN is reserved for windowed/fullscreen toggle
00168         if( KEY_RETURN == key )
00169         {
00170                 if (mask == MASK_CONTROL)
00171                 {
00172                         // shout
00173                         sendChat(CHAT_TYPE_SHOUT);
00174                         handled = TRUE;
00175                 }
00176                 else if (mask == MASK_NONE)
00177                 {
00178                         // say
00179                         sendChat( CHAT_TYPE_NORMAL );
00180                         handled = TRUE;
00181                 }
00182         }
00183         // only do this in main chatbar
00184         else if ( KEY_ESCAPE == key && gChatBar == this)
00185         {
00186                 stopChat();
00187 
00188                 handled = TRUE;
00189         }
00190 
00191         return handled;
00192 }
00193 
00194 void LLChatBar::refresh()
00195 {
00196         // HACK: Leave the name of the gesture in place for a few seconds.
00197         const F32 SHOW_GESTURE_NAME_TIME = 2.f;
00198         if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME)
00199         {
00200                 LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL;
00201                 if (gestures) gestures->selectFirstItem();
00202                 mGestureLabelTimer.stop();
00203         }
00204 
00205         if ((gAgent.getTypingTime() > AGENT_TYPING_TIMEOUT) && (gAgent.getRenderState() & AGENT_STATE_TYPING))
00206         {
00207                 gAgent.stopTyping();
00208         }
00209 
00210         childSetValue("History", LLFloaterChat::instanceVisible(LLSD()));
00211 
00212         childSetEnabled("Say", mInputEditor->getText().size() > 0);
00213         childSetEnabled("Shout", mInputEditor->getText().size() > 0);
00214 
00215 }
00216 
00217 void LLChatBar::refreshGestures()
00218 {
00219         LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL;
00220         if (mGestureCombo && gestures)
00221         {
00222                 //store current selection so we can maintain it
00223                 LLString cur_gesture = mGestureCombo->getValue().asString();
00224                 gestures->selectFirstItem();
00225                 LLString label = mGestureCombo->getValue().asString();;
00226                 // clear
00227                 gestures->clearRows();
00228 
00229                 // collect list of unique gestures
00230                 std::map <std::string, BOOL> unique;
00231                 LLGestureManager::item_map_t::iterator it;
00232                 for (it = gGestureManager.mActive.begin(); it != gGestureManager.mActive.end(); ++it)
00233                 {
00234                         LLMultiGesture* gesture = (*it).second;
00235                         if (gesture)
00236                         {
00237                                 if (!gesture->mTrigger.empty())
00238                                 {
00239                                         unique[gesture->mTrigger] = TRUE;
00240                                 }
00241                         }
00242                 }
00243 
00244                 // ad unique gestures
00245                 std::map <std::string, BOOL>::iterator it2;
00246                 for (it2 = unique.begin(); it2 != unique.end(); ++it2)
00247                 {
00248                         gestures->addSimpleElement((*it2).first);
00249                 }
00250                 
00251                 gestures->sortByColumn(0, TRUE);
00252                 // Insert label after sorting
00253                 gestures->addSimpleElement(label, ADD_TOP);
00254                 
00255                 if (!cur_gesture.empty())
00256                 {
00257                         gestures->selectByValue(LLSD(cur_gesture));
00258                 }
00259                 else
00260                 {
00261                         gestures->selectFirstItem();
00262                 }
00263         }
00264 }
00265 
00266 // Move the cursor to the correct input field.
00267 void LLChatBar::setKeyboardFocus(BOOL focus)
00268 {
00269         if (focus)
00270         {
00271                 if (mInputEditor)
00272                 {
00273                         mInputEditor->setFocus(TRUE);
00274                         mInputEditor->selectAll();
00275                 }
00276         }
00277         else if (gFocusMgr.childHasKeyboardFocus(this))
00278         {
00279                 if (mInputEditor)
00280                 {
00281                         mInputEditor->deselect();
00282                 }
00283                 setFocus(FALSE);
00284         }
00285 }
00286 
00287 
00288 // Ignore arrow keys in chat bar
00289 void LLChatBar::setIgnoreArrowKeys(BOOL b)
00290 {
00291         if (mInputEditor)
00292         {
00293                 mInputEditor->setIgnoreArrowKeys(b);
00294         }
00295 }
00296 
00297 BOOL LLChatBar::inputEditorHasFocus()
00298 {
00299         return mInputEditor && mInputEditor->hasFocus();
00300 }
00301 
00302 LLString LLChatBar::getCurrentChat()
00303 {
00304         return mInputEditor ? mInputEditor->getText() : LLString::null;
00305 }
00306 
00307 void LLChatBar::setGestureCombo(LLComboBox* combo)
00308 {
00309         mGestureCombo = combo;
00310         if (mGestureCombo)
00311         {
00312                 mGestureCombo->setCommitCallback(onCommitGesture);
00313                 mGestureCombo->setCallbackUserData(this);
00314 
00315                 // now register observer since we have a place to put the results
00316                 mObserver = new LLChatBarGestureObserver(this);
00317                 gGestureManager.addObserver(mObserver);
00318 
00319                 // refresh list from current active gestures
00320                 refreshGestures();
00321         }
00322 }
00323 
00324 //-----------------------------------------------------------------------
00325 // Internal functions
00326 //-----------------------------------------------------------------------
00327 
00328 // If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20.
00329 // Otherwise returns input and channel 0.
00330 LLWString LLChatBar::stripChannelNumber(const LLWString &mesg, S32* channel)
00331 {
00332         if (mesg[0] == '/'
00333                 && mesg[1] == '/')
00334         {
00335                 // This is a "repeat channel send"
00336                 *channel = mLastSpecialChatChannel;
00337                 return mesg.substr(2, mesg.length() - 2);
00338         }
00339         else if (mesg[0] == '/'
00340                          && mesg[1]
00341                          && LLStringOps::isDigit(mesg[1]))
00342         {
00343                 // This a special "/20" speak on a channel
00344                 S32 pos = 0;
00345 
00346                 // Copy the channel number into a string
00347                 llwchar channel_string[64];
00348                 llwchar c;
00349                 do
00350                 {
00351                         c = mesg[pos+1];
00352                         channel_string[pos] = c;
00353                         pos++;
00354                 }
00355                 while(c && pos < 64 && LLStringOps::isDigit(c));
00356                 
00357                 // Move the pointer forward to the first non-whitespace char
00358                 // Check isspace before looping, so we can handle "/33foo"
00359                 // as well as "/33 foo"
00360                 while(c && iswspace(c))
00361                 {
00362                         c = mesg[pos+1];
00363                         pos++;
00364                 }
00365 
00366                 
00367                 mLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10);
00368                 *channel = mLastSpecialChatChannel;
00369                 return mesg.substr(pos, mesg.length() - pos);
00370         }
00371         else
00372         {
00373                 // This is normal chat.
00374                 *channel = 0;
00375                 return mesg;
00376         }
00377 }
00378 
00379 
00380 void LLChatBar::sendChat( EChatType type )
00381 {
00382         LLWString text;
00383         if (mInputEditor) text = mInputEditor->getWText();
00384         LLWString::trim(text);
00385 
00386         if (!text.empty())
00387         {
00388                 // store sent line in history, duplicates will get filtered
00389                 if (mInputEditor) mInputEditor->updateHistory();
00390                 // Check if this is destined for another channel
00391                 S32 channel = 0;
00392                 stripChannelNumber(text, &channel);
00393 
00394                 std::string utf8text = wstring_to_utf8str(text);
00395                 // Try to trigger a gesture, if not chat to a script.
00396                 std::string utf8_revised_text;
00397                 if (0 == channel)
00398                 {
00399                         // discard returned "found" boolean
00400                         gGestureManager.triggerAndReviseString(utf8text, &utf8_revised_text);
00401                 }
00402                 else
00403                 {
00404                         utf8_revised_text = utf8text;
00405                 }
00406 
00407                 utf8_revised_text = utf8str_trim(utf8_revised_text);
00408 
00409                 if (!utf8_revised_text.empty())
00410                 {
00411                         // Chat with animation
00412                         sendChatFromViewer(utf8_revised_text, type, TRUE);
00413                 }
00414         }
00415         childSetValue("Chat Editor", LLString::null);
00416 
00417         gAgent.stopTyping();
00418 
00419         // If the user wants to stop chatting on hitting return, lose focus
00420         // and go out of chat mode.
00421         if (gChatBar == this && gSavedSettings.getBOOL("CloseChatOnReturn"))
00422         {
00423                 stopChat();
00424         }
00425 }
00426 
00427 
00428 //-----------------------------------------------------------------------
00429 // Static functions
00430 //-----------------------------------------------------------------------
00431 
00432 // static 
00433 void LLChatBar::startChat(const char* line)
00434 {
00435         gChatBar->setVisible(TRUE);
00436         gChatBar->setKeyboardFocus(TRUE);
00437         gSavedSettings.setBOOL("ChatVisible", TRUE);
00438 
00439         if (line && gChatBar->mInputEditor)
00440         {
00441                 std::string line_string(line);
00442                 gChatBar->mInputEditor->setText(line_string);
00443         }
00444         // always move cursor to end so users don't obliterate chat when accidentally hitting WASD
00445         gChatBar->mInputEditor->setCursorToEnd();
00446 }
00447 
00448 
00449 // Exit "chat mode" and do the appropriate focus changes
00450 // static
00451 void LLChatBar::stopChat()
00452 {
00453         // In simple UI mode, we never release focus from the chat bar
00454         gChatBar->setKeyboardFocus(FALSE);
00455 
00456         // If we typed a movement key and pressed return during the
00457         // same frame, the keyboard handlers will see the key as having
00458         // gone down this frame and try to move the avatar.
00459         gKeyboard->resetKeys();
00460         gKeyboard->resetMaskKeys();
00461 
00462         // stop typing animation
00463         gAgent.stopTyping();
00464 
00465         // hide chat bar so it doesn't grab focus back
00466         gChatBar->setVisible(FALSE);
00467         gSavedSettings.setBOOL("ChatVisible", FALSE);
00468 }
00469 
00470 // static
00471 void LLChatBar::onInputEditorKeystroke( LLLineEditor* caller, void* userdata )
00472 {
00473         LLChatBar* self = (LLChatBar *)userdata;
00474 
00475         LLWString raw_text;
00476         if (self->mInputEditor) raw_text = self->mInputEditor->getWText();
00477 
00478         // Can't trim the end, because that will cause autocompletion
00479         // to eat trailing spaces that might be part of a gesture.
00480         LLWString::trimHead(raw_text);
00481 
00482         S32 length = raw_text.length();
00483 
00484         if( (length > 0) && (raw_text[0] != '/') )  // forward slash is used for escape (eg. emote) sequences
00485         {
00486                 gAgent.startTyping();
00487         }
00488         else
00489         {
00490                 gAgent.stopTyping();
00491         }
00492 
00493         /* Doesn't work -- can't tell the difference between a backspace
00494            that killed the selection vs. backspace at the end of line.
00495         if (length > 1 
00496                 && text[0] == '/'
00497                 && key == KEY_BACKSPACE)
00498         {
00499                 // the selection will already be deleted, but we need to trim
00500                 // off the character before
00501                 LLString new_text = raw_text.substr(0, length-1);
00502                 self->mInputEditor->setText( new_text );
00503                 self->mInputEditor->setCursorToEnd();
00504                 length = length - 1;
00505         }
00506         */
00507 
00508         KEY key = gKeyboard->currentKey();
00509 
00510         // Ignore "special" keys, like backspace, arrows, etc.
00511         if (length > 1 
00512                 && raw_text[0] == '/'
00513                 && key < KEY_SPECIAL)
00514         {
00515                 // we're starting a gesture, attempt to autocomplete
00516 
00517                 std::string utf8_trigger = wstring_to_utf8str(raw_text);
00518                 std::string utf8_out_str(utf8_trigger);
00519 
00520                 if (gGestureManager.matchPrefix(utf8_trigger, &utf8_out_str))
00521                 {
00522                         if (self->mInputEditor)
00523                         {
00524                                 self->mInputEditor->setText(utf8_out_str);
00525                                 S32 outlength = self->mInputEditor->getLength(); // in characters
00526                         
00527                                 // Select to end of line, starting from the character
00528                                 // after the last one the user typed.
00529                                 self->mInputEditor->setSelection(length, outlength);
00530                         }
00531                 }
00532 
00533                 //llinfos << "GESTUREDEBUG " << trigger 
00534                 //      << " len " << length
00535                 //      << " outlen " << out_str.getLength()
00536                 //      << llendl;
00537         }
00538 }
00539 
00540 // static
00541 void LLChatBar::onInputEditorFocusLost( LLFocusableElement* caller, void* userdata)
00542 {
00543         // stop typing animation
00544         gAgent.stopTyping();
00545 }
00546 
00547 // static
00548 void LLChatBar::onInputEditorGainFocus( LLFocusableElement* caller, void* userdata )
00549 {
00550         LLFloaterChat::setHistoryCursorAndScrollToEnd();
00551 }
00552 
00553 // static
00554 void LLChatBar::onClickSay( LLUICtrl* ctrl, void* userdata )
00555 {
00556         LLChatBar* self = (LLChatBar*) userdata;
00557         if (ctrl->getValue().asString() == "shout")
00558         {
00559                 self->sendChat( CHAT_TYPE_SHOUT );
00560         }
00561         else
00562         {
00563                 self->sendChat( CHAT_TYPE_NORMAL );
00564         }
00565 }
00566 
00567 void LLChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate)
00568 {
00569         sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate);
00570 }
00571 
00572 void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate)
00573 {
00574         // Look for "/20 foo" channel chats.
00575         S32 channel = 0;
00576         LLWString out_text = stripChannelNumber(wtext, &channel);
00577         std::string utf8_out_text = wstring_to_utf8str(out_text);
00578         std::string utf8_text = wstring_to_utf8str(wtext);
00579 
00580         utf8_text = utf8str_trim(utf8_text);
00581         if (!utf8_text.empty())
00582         {
00583                 utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1);
00584         }
00585 
00586         // Don't animate for chats people can't hear (chat to scripts)
00587         if (animate && (channel == 0))
00588         {
00589                 if (type == CHAT_TYPE_WHISPER)
00590                 {
00591                         lldebugs << "You whisper " << utf8_text << llendl;
00592                         gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START);
00593                 }
00594                 else if (type == CHAT_TYPE_NORMAL)
00595                 {
00596                         lldebugs << "You say " << utf8_text << llendl;
00597                         gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START);
00598                 }
00599                 else if (type == CHAT_TYPE_SHOUT)
00600                 {
00601                         lldebugs << "You shout " << utf8_text << llendl;
00602                         gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START);
00603                 }
00604                 else
00605                 {
00606                         llinfos << "send_chat_from_viewer() - invalid volume" << llendl;
00607                         return;
00608                 }
00609         }
00610         else
00611         {
00612                 if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP)
00613                 {
00614                         lldebugs << "Channel chat: " << utf8_text << llendl;
00615                 }
00616         }
00617 
00618         send_chat_from_viewer(utf8_out_text, type, channel);
00619 }
00620 
00621 void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel)
00622 {
00623         LLMessageSystem* msg = gMessageSystem;
00624         msg->newMessageFast(_PREHASH_ChatFromViewer);
00625         msg->nextBlockFast(_PREHASH_AgentData);
00626         msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
00627         msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
00628         msg->nextBlockFast(_PREHASH_ChatData);
00629         msg->addStringFast(_PREHASH_Message, utf8_out_text);
00630         msg->addU8Fast(_PREHASH_Type, type);
00631         msg->addS32("Channel", channel);
00632 
00633         gAgent.sendReliableMessage();
00634 
00635         LLViewerStats::getInstance()->incStat(LLViewerStats::ST_CHAT_COUNT);
00636 }
00637 
00638 
00639 // static
00640 void LLChatBar::onCommitGesture(LLUICtrl* ctrl, void* data)
00641 {
00642         LLChatBar* self = (LLChatBar*)data;
00643         LLCtrlListInterface* gestures = self->mGestureCombo ? self->mGestureCombo->getListInterface() : NULL;
00644         if (gestures)
00645         {
00646                 S32 index = gestures->getFirstSelectedIndex();
00647                 if (index == 0)
00648                 {
00649                         return;
00650                 }
00651                 const std::string& trigger = gestures->getSelectedValue().asString();
00652 
00653                 // pretend the user chatted the trigger string, to invoke
00654                 // substitution and logging.
00655                 std::string text(trigger);
00656                 std::string revised_text;
00657                 gGestureManager.triggerAndReviseString(text, &revised_text);
00658 
00659                 revised_text = utf8str_trim(revised_text);
00660                 if (!revised_text.empty())
00661                 {
00662                         // Don't play nodding animation
00663                         self->sendChatFromViewer(revised_text, CHAT_TYPE_NORMAL, FALSE);
00664                 }
00665         }
00666         self->mGestureLabelTimer.start();
00667         if (self->mGestureCombo != NULL)
00668         {
00669                 // free focus back to chat bar
00670                 self->mGestureCombo->setFocus(FALSE);
00671         }
00672 }
00673 
00674 void toggleChatHistory(void* user_data)
00675 {
00676         LLFloaterChat::toggleInstance(LLSD());
00677 }
00678 
00679 
00680 class LLChatHandler : public LLCommandHandler
00681 {
00682 public:
00683         // not allowed from outside the app
00684         LLChatHandler() : LLCommandHandler("chat", false) { }
00685 
00686     // Your code here
00687         bool handle(const LLSD& tokens, const LLSD& queryMap)
00688         {
00689                 if (tokens.size() < 2) return false;
00690                 S32 channel = tokens[0].asInteger();
00691                 std::string mesg = tokens[1].asString();
00692                 send_chat_from_viewer(mesg, CHAT_TYPE_NORMAL, channel);
00693                 return true;
00694         }
00695 };
00696 
00697 // Creating the object registers with the dispatcher.
00698 LLChatHandler gChatHandler;

Generated on Fri May 16 08:33:16 2008 for SecondLife by  doxygen 1.5.5