aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/newview/llchatbar.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/newview/llchatbar.cpp')
-rw-r--r--linden/indra/newview/llchatbar.cpp731
1 files changed, 731 insertions, 0 deletions
diff --git a/linden/indra/newview/llchatbar.cpp b/linden/indra/newview/llchatbar.cpp
new file mode 100644
index 0000000..e62e0cb
--- /dev/null
+++ b/linden/indra/newview/llchatbar.cpp
@@ -0,0 +1,731 @@
1/**
2 * @file llchatbar.cpp
3 * @brief LLChatBar class implementation
4 *
5 * Copyright (c) 2002-2007, Linden Research, Inc.
6 *
7 * The source code in this file ("Source Code") is provided by Linden Lab
8 * to you under the terms of the GNU General Public License, version 2.0
9 * ("GPL"), unless you have obtained a separate licensing agreement
10 * ("Other License"), formally executed by you and Linden Lab. Terms of
11 * the GPL can be found in doc/GPL-license.txt in this distribution, or
12 * online at http://secondlife.com/developers/opensource/gplv2
13 *
14 * There are special exceptions to the terms and conditions of the GPL as
15 * it is applied to this Source Code. View the full text of the exception
16 * in the file doc/FLOSS-exception.txt in this software distribution, or
17 * online at http://secondlife.com/developers/opensource/flossexception
18 *
19 * By copying, modifying or distributing this software, you acknowledge
20 * that you have read and understood your obligations described above,
21 * and agree to abide by those obligations.
22 *
23 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
24 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
25 * COMPLETENESS OR PERFORMANCE.
26 */
27
28#include "llviewerprecompiledheaders.h"
29
30#include "llchatbar.h"
31
32#include "imageids.h"
33#include "llfontgl.h"
34#include "llrect.h"
35#include "llerror.h"
36#include "llparcel.h"
37#include "llstring.h"
38#include "message.h"
39#include "llfocusmgr.h"
40
41#include "llagent.h"
42#include "llbutton.h"
43#include "llcombobox.h"
44#include "llviewercontrol.h"
45#include "llfloaterchat.h"
46#include "llgesturemgr.h"
47#include "llkeyboard.h"
48#include "lllineeditor.h"
49#include "lltextbox.h"
50#include "lluiconstants.h"
51#include "llviewergesture.h" // for triggering gestures
52#include "llviewermenu.h" // for deleting object with DEL key
53#include "llviewerstats.h"
54#include "llviewerwindow.h"
55#include "llframetimer.h"
56#include "llresmgr.h"
57#include "llworld.h"
58#include "llinventorymodel.h"
59#include "llmultigesture.h"
60#include "llui.h"
61#include "llviewermenu.h"
62#include "llvieweruictrlfactory.h"
63
64
65//
66// Globals
67//
68const F32 AGENT_TYPING_TIMEOUT = 5.f; // seconds
69
70LLChatBar *gChatBar = NULL;
71
72LLChatBarGestureObserver* LLChatBar::sObserver = NULL;
73
74
75class LLChatBarGestureObserver : public LLGestureManagerObserver
76{
77public:
78 LLChatBarGestureObserver() {}
79 virtual ~LLChatBarGestureObserver() {}
80 virtual void changed() { gChatBar->refreshGestures(); }
81};
82
83
84//
85// Functions
86//
87
88LLChatBar::LLChatBar(const std::string& name, const LLRect& rect)
89: LLPanel(name, rect, BORDER_NO),
90 mInputEditor(NULL),
91 mGestureLabelTimer(),
92 mLastSpecialChatChannel(0),
93 mIsBuilt(FALSE)
94{
95 setIsChrome(TRUE);
96
97 gUICtrlFactory->buildPanel(this,"panel_chat_bar.xml");
98
99 mIsFocusRoot = TRUE;
100
101 setRect(rect); // override xml rect
102
103 setBackgroundOpaque(TRUE);
104 setBackgroundVisible(TRUE);
105
106 // Start visible if we left the app while chatting.
107 setVisible( gSavedSettings.getBOOL("ChatVisible") );
108
109 mInputEditor = LLUICtrlFactory::getLineEditorByName(this, "Chat Editor");
110 if (mInputEditor)
111 {
112 mInputEditor->setCallbackUserData(this);
113 mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke);
114 mInputEditor->setFocusLostCallback(&onInputEditorFocusLost);
115 mInputEditor->setFocusReceivedCallback( &onInputEditorGainFocus );
116 mInputEditor->setCommitOnFocusLost( FALSE );
117 mInputEditor->setRevertOnEsc( FALSE );
118 mInputEditor->setIgnoreTab(TRUE);
119 mInputEditor->setPassDelete(TRUE);
120 }
121
122 mInputEditor->setMaxTextLength(1023);
123 // Build the list of gestures
124 refreshGestures();
125
126 sObserver = new LLChatBarGestureObserver;
127 gGestureManager.addObserver(sObserver);
128
129 mIsBuilt = TRUE;
130
131 // Apply custom layout.
132 layout();
133
134#if !LL_RELEASE_FOR_DOWNLOAD
135 childDisplayNotFound();
136#endif
137
138}
139
140
141LLChatBar::~LLChatBar()
142{
143 delete sObserver;
144 sObserver = NULL;
145 // LLView destructor cleans up children
146}
147
148BOOL LLChatBar::postBuild()
149{
150 childSetAction("History", LLFloaterChat::toggle, this);
151 childSetAction("Say", onClickSay, this);
152 childSetAction("Shout", onClickShout, this);
153 childSetCommitCallback("Gesture", onCommitGesture, this);
154 LLButton * sayp = static_cast<LLButton*>(getChildByName("Say"));
155 if(sayp)
156 {
157 setDefaultBtn(sayp);
158 }
159
160 return TRUE;
161}
162
163//-----------------------------------------------------------------------
164// Overrides
165//-----------------------------------------------------------------------
166
167// virtual
168void LLChatBar::reshape(S32 width, S32 height, BOOL called_from_parent)
169{
170 LLPanel::reshape(width, height, called_from_parent);
171 if (mIsBuilt)
172 {
173 layout();
174 }
175}
176
177// virtual
178BOOL LLChatBar::handleKeyHere( KEY key, MASK mask, BOOL called_from_parent )
179{
180 BOOL handled = FALSE;
181
182 if( getVisible() && getEnabled() && !called_from_parent)
183 {
184 // ALT-RETURN is reserved for windowed/fullscreen toggle
185 if( KEY_RETURN == key )
186 {
187 //if (childGetValue("Chat Editor").asString().empty())
188 //{
189 // // no text, just close chat bar
190 // stopChat();
191 // return TRUE;
192 //}
193
194 if (mask == MASK_CONTROL)
195 {
196 // shout
197 sendChat(CHAT_TYPE_SHOUT);
198 handled = TRUE;
199 }
200 else if (mask == MASK_NONE)
201 {
202 // say
203 sendChat( CHAT_TYPE_NORMAL );
204 handled = TRUE;
205 }
206 }
207 else if ( KEY_ESCAPE == key )
208 {
209 stopChat();
210
211 handled = TRUE;
212 }
213 }
214 return handled;
215}
216
217
218void LLChatBar::layout()
219{
220 S32 rect_width = mRect.getWidth();
221 S32 count = 9; // number of elements in LLToolBar
222 S32 pad = 4;
223
224 LLRect gesture_rect;
225 S32 gesture_width = 0;
226 if (childGetRect("Gesture", gesture_rect))
227 {
228 gesture_width = gesture_rect.getWidth();
229 }
230 F32 segment_width = (F32)(rect_width - (pad + gesture_width)) / (F32)count;
231
232 S32 btn_width = lltrunc(segment_width-pad);
233
234 S32 x = 0;
235 S32 y = 1;
236 LLRect r;
237
238 x = llround(0 * segment_width);
239 r.setOriginAndSize(x, y, btn_width, BTN_HEIGHT);
240 childSetRect("History", r);
241
242 x = llround(1 * segment_width);
243 // Hack this one up so it looks nice.
244 if (mInputEditor)
245 {
246 r.setOriginAndSize(x, y+2, llfloor(6*segment_width-pad), 18);
247 mInputEditor->reshape(r.getWidth(), r.getHeight(), TRUE);
248 mInputEditor->setRect(r);
249 }
250
251 x = llround(7 * segment_width);
252 r.setOriginAndSize(x, y, btn_width, BTN_HEIGHT);
253 childSetRect("Say", r);
254
255 x = llround(8 * segment_width);
256 r.setOriginAndSize(x, y, btn_width, BTN_HEIGHT);
257 childSetRect("Shout", r);
258
259 x = rect_width - (pad + gesture_width);
260 r.setOriginAndSize(x, y, gesture_width, BTN_HEIGHT);
261 childSetRect("Gesture", r);
262}
263
264
265void LLChatBar::refresh()
266{
267 //BOOL chat_mode = gSavedSettings.getBOOL("ChatVisible");
268
269 //// Grab focus when no one else has it, and we're in chat mode.
270 //if (!gFocusMgr.getKeyboardFocus()
271 // && chat_mode)
272 //{
273 // childSetFocus("Chat Editor", TRUE);
274 //}
275
276 // Only show this view when user wants to be chatting
277 //setVisible(chat_mode);
278
279 // hide in mouselook, but keep previous visibility state
280 //BOOL mouselook = gAgent.cameraMouselook();
281 // call superclass setVisible so that we don't overwrite the saved setting
282 LLPanel::setVisible(gSavedSettings.getBOOL("ChatVisible"));
283
284 // HACK: Leave the name of the gesture in place for a few seconds.
285 const F32 SHOW_GESTURE_NAME_TIME = 2.f;
286 if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME)
287 {
288 LLCtrlListInterface* gestures = childGetListInterface("Gesture");
289 if (gestures) gestures->selectFirstItem();
290 mGestureLabelTimer.stop();
291 }
292
293 if ((gAgent.getTypingTime() > AGENT_TYPING_TIMEOUT) && (gAgent.getRenderState() & AGENT_STATE_TYPING))
294 {
295 gAgent.stopTyping();
296 }
297
298 childSetEnabled("Say", mInputEditor->getText().size() > 0);
299 childSetEnabled("Shout", mInputEditor->getText().size() > 0);
300
301}
302
303void LLChatBar::refreshGestures()
304{
305 LLCtrlListInterface* gestures = childGetListInterface("Gesture");
306 if (gestures)
307 {
308 //store current selection so we can maintain it
309 LLString cur_gesture = childGetValue("Gesture").asString();
310 gestures->selectFirstItem();
311 LLString label = childGetValue("Gesture").asString();
312 // clear
313 gestures->clearRows();
314 // add gestures
315 LLGestureManager::item_map_t::iterator it;
316 for (it = gGestureManager.mActive.begin(); it != gGestureManager.mActive.end(); ++it)
317 {
318 LLMultiGesture* gesture = (*it).second;
319 if (gesture)
320 {
321 if (!gesture->mTrigger.empty())
322 {
323 gestures->addSimpleElement(gesture->mTrigger);
324 }
325 }
326 }
327 gestures->sortByColumn(0, TRUE);
328 // Insert label after sorting
329 gestures->addSimpleElement(label, ADD_TOP);
330
331 if (!cur_gesture.empty())
332 {
333 gestures->selectByValue(LLSD(cur_gesture));
334 }
335 else
336 {
337 gestures->selectFirstItem();
338 }
339 }
340}
341
342// Move the cursor to the correct input field.
343void LLChatBar::setKeyboardFocus(BOOL focus)
344{
345 if (focus)
346 {
347 if (mInputEditor)
348 {
349 mInputEditor->setFocus(TRUE);
350 mInputEditor->selectAll();
351 }
352 }
353 else if (gFocusMgr.childHasKeyboardFocus(this))
354 {
355 if (mInputEditor)
356 {
357 mInputEditor->deselect();
358 }
359 setFocus(FALSE);
360 }
361}
362
363
364// Ignore arrow keys in chat bar
365void LLChatBar::setIgnoreArrowKeys(BOOL b)
366{
367 if (mInputEditor)
368 {
369 mInputEditor->setIgnoreArrowKeys(b);
370 }
371}
372
373BOOL LLChatBar::inputEditorHasFocus()
374{
375 return mInputEditor && mInputEditor->hasFocus();
376}
377
378LLString LLChatBar::getCurrentChat()
379{
380 return mInputEditor ? mInputEditor->getText() : LLString::null;
381}
382
383//-----------------------------------------------------------------------
384// Internal functions
385//-----------------------------------------------------------------------
386
387// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20.
388// Otherwise returns input and channel 0.
389LLWString LLChatBar::stripChannelNumber(const LLWString &mesg, S32* channel)
390{
391 if (mesg[0] == '/'
392 && mesg[1] == '/')
393 {
394 // This is a "repeat channel send"
395 *channel = mLastSpecialChatChannel;
396 return mesg.substr(2, mesg.length() - 2);
397 }
398 else if (mesg[0] == '/'
399 && mesg[1]
400 && isdigit(mesg[1]))
401 {
402 // This a special "/20" speak on a channel
403 S32 pos = 0;
404
405 // Copy the channel number into a string
406 llwchar channel_string[64];
407 llwchar c;
408 do
409 {
410 c = mesg[pos+1];
411 channel_string[pos] = c;
412 pos++;
413 }
414 while(c && pos < 64 && isdigit(c));
415
416 // Move the pointer forward to the first non-whitespace char
417 // Check isspace before looping, so we can handle "/33foo"
418 // as well as "/33 foo"
419 while(c && iswspace(c))
420 {
421 c = mesg[pos+1];
422 pos++;
423 }
424
425
426 mLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10);
427 *channel = mLastSpecialChatChannel;
428 return mesg.substr(pos, mesg.length() - pos);
429 }
430 else
431 {
432 // This is normal chat.
433 *channel = 0;
434 return mesg;
435 }
436}
437
438
439void LLChatBar::sendChat( EChatType type )
440{
441 LLWString text;
442 if (mInputEditor) text = mInputEditor->getWText();
443 LLWString::trim(text);
444
445 if (!text.empty())
446 {
447 // Check if this is destined for another channel
448 S32 channel = 0;
449 stripChannelNumber(text, &channel);
450
451 std::string utf8text = wstring_to_utf8str(text);
452 // Try to trigger a gesture, if not chat to a script.
453 std::string utf8_revised_text;
454 if (0 == channel)
455 {
456 // discard returned "found" boolean
457 gGestureManager.triggerAndReviseString(utf8text, &utf8_revised_text);
458 }
459 else
460 {
461 utf8_revised_text = utf8text;
462 }
463
464 utf8_revised_text = utf8str_trim(utf8_revised_text);
465
466 if (!utf8_revised_text.empty())
467 {
468 // Chat with animation
469 sendChatFromViewer(utf8_revised_text, type, TRUE);
470 }
471 }
472 childSetValue("Chat Editor", LLSD(LLString::null));
473
474 gAgent.stopTyping();
475
476 // If the user wants to stop chatting on hitting return, lose focus
477 // and go out of chat mode.
478 if (gSavedSettings.getBOOL("CloseChatOnReturn"))
479 {
480 stopChat();
481 }
482}
483
484
485//-----------------------------------------------------------------------
486// Static functions
487//-----------------------------------------------------------------------
488
489// static
490void LLChatBar::startChat(void* userdata)
491{
492 const char* line = (const char*)userdata;
493
494 gChatBar->setVisible(TRUE);
495 gChatBar->setKeyboardFocus(TRUE);
496 gSavedSettings.setBOOL("ChatVisible", TRUE);
497
498 if (line && gChatBar->mInputEditor)
499 {
500 std::string line_string(line);
501 gChatBar->mInputEditor->setText(line_string);
502 }
503 // always move cursor to end so users don't obliterate chat when accidentally hitting WASD
504 gChatBar->mInputEditor->setCursorToEnd();
505}
506
507
508// Exit "chat mode" and do the appropriate focus changes
509// static
510void LLChatBar::stopChat()
511{
512 // In simple UI mode, we never release focus from the chat bar
513 gChatBar->setKeyboardFocus(FALSE);
514
515 // If we typed a movement key and pressed return during the
516 // same frame, the keyboard handlers will see the key as having
517 // gone down this frame and try to move the avatar.
518 gKeyboard->resetKeys();
519 gKeyboard->resetMaskKeys();
520
521 // stop typing animation
522 gAgent.stopTyping();
523
524 // hide chat bar so it doesn't grab focus back
525 gChatBar->setVisible(FALSE);
526}
527
528void LLChatBar::setVisible(BOOL visible)
529{
530 gSavedSettings.setBOOL("ChatVisible", visible);
531 LLPanel::setVisible(visible);
532}
533
534// static
535void LLChatBar::onInputEditorKeystroke( LLLineEditor* caller, void* userdata )
536{
537 LLChatBar* self = (LLChatBar *)userdata;
538
539 LLWString raw_text;
540 if (self->mInputEditor) raw_text = self->mInputEditor->getWText();
541
542 // Can't trim the end, because that will cause autocompletion
543 // to eat trailing spaces that might be part of a gesture.
544 LLWString::trimHead(raw_text);
545
546 S32 length = raw_text.length();
547
548 if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences
549 {
550 gAgent.startTyping();
551 }
552 else
553 {
554 gAgent.stopTyping();
555 }
556
557 /* Doesn't work -- can't tell the difference between a backspace
558 that killed the selection vs. backspace at the end of line.
559 if (length > 1
560 && text[0] == '/'
561 && key == KEY_BACKSPACE)
562 {
563 // the selection will already be deleted, but we need to trim
564 // off the character before
565 LLString new_text = raw_text.substr(0, length-1);
566 self->mInputEditor->setText( new_text );
567 self->mInputEditor->setCursorToEnd();
568 length = length - 1;
569 }
570 */
571
572 KEY key = gKeyboard->currentKey();
573
574 // Ignore "special" keys, like backspace, arrows, etc.
575 if (length > 1
576 && raw_text[0] == '/'
577 && key < KEY_SPECIAL)
578 {
579 // we're starting a gesture, attempt to autocomplete
580
581 std::string utf8_trigger = wstring_to_utf8str(raw_text);
582 std::string utf8_out_str(utf8_trigger);
583
584 if (gGestureManager.matchPrefix(utf8_trigger, &utf8_out_str))
585 {
586 if (self->mInputEditor)
587 {
588 self->mInputEditor->setText(utf8_out_str);
589 S32 outlength = self->mInputEditor->getLength(); // in characters
590
591 // Select to end of line, starting from the character
592 // after the last one the user typed.
593 self->mInputEditor->setSelection(length, outlength);
594 }
595 }
596
597 //llinfos << "GESTUREDEBUG " << trigger
598 // << " len " << length
599 // << " outlen " << out_str.getLength()
600 // << llendl;
601 }
602 // make sure we don't do UI-only render as it is apparent avatar isn't animating
603 gViewerWindow->finishFastFrame();
604}
605
606// static
607void LLChatBar::onInputEditorFocusLost( LLLineEditor* caller, void* userdata)
608{
609 // stop typing animation
610 gAgent.stopTyping();
611}
612
613// static
614void LLChatBar::onInputEditorGainFocus( LLUICtrl* caller, void* userdata )
615{
616 LLFloaterChat::setHistoryCursorAndScrollToEnd();
617}
618
619// static
620void LLChatBar::onClickSay( void* userdata )
621{
622 LLChatBar* self = (LLChatBar*) userdata;
623 self->sendChat( CHAT_TYPE_NORMAL );
624}
625
626// static
627void LLChatBar::onClickShout( void* userdata )
628{
629 LLChatBar *self = (LLChatBar *)userdata;
630 self->sendChat( CHAT_TYPE_SHOUT );
631}
632
633void LLChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, BOOL animate)
634{
635 sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate);
636}
637
638void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate)
639{
640 LLMessageSystem* msg = gMessageSystem;
641
642 // Look for "/20 foo" channel chats.
643 S32 channel = 0;
644 LLWString out_text = stripChannelNumber(wtext, &channel);
645 std::string utf8_out_text = wstring_to_utf8str(out_text);
646 std::string utf8_text = wstring_to_utf8str(wtext);
647
648 utf8_text = utf8str_trim(utf8_text);
649 if (!utf8_text.empty())
650 {
651 utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1);
652 }
653
654 // Don't animate for chats people can't hear (chat to scripts)
655 if (animate && (channel == 0))
656 {
657 if (type == CHAT_TYPE_WHISPER)
658 {
659 lldebugs << "You whisper " << utf8_text << llendl;
660 gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START);
661 }
662 else if (type == CHAT_TYPE_NORMAL)
663 {
664 lldebugs << "You say " << utf8_text << llendl;
665 gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START);
666 }
667 else if (type == CHAT_TYPE_SHOUT)
668 {
669 lldebugs << "You shout " << utf8_text << llendl;
670 gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START);
671 }
672 else
673 {
674 llinfos << "send_chat_from_viewer() - invalid volume" << llendl;
675 return;
676 }
677 }
678 else
679 {
680 if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP)
681 {
682 lldebugs << "Channel chat: " << utf8_text << llendl;
683 }
684 }
685
686 msg->newMessageFast(_PREHASH_ChatFromViewer);
687 msg->nextBlockFast(_PREHASH_AgentData);
688 msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
689 msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
690 msg->nextBlockFast(_PREHASH_ChatData);
691 msg->addStringFast(_PREHASH_Message, utf8_out_text);
692 msg->addU8Fast(_PREHASH_Type, type);
693 msg->addS32("Channel", channel);
694
695 gAgent.sendReliableMessage();
696
697 gViewerStats->incStat(LLViewerStats::ST_CHAT_COUNT);
698}
699
700
701// static
702void LLChatBar::onCommitGesture(LLUICtrl* ctrl, void* data)
703{
704 LLChatBar* self = (LLChatBar*)data;
705 LLCtrlListInterface* gestures = self->childGetListInterface("Gesture");
706 if (gestures)
707 {
708 S32 index = gestures->getFirstSelectedIndex();
709 if (index == 0)
710 {
711 return;
712 }
713 const std::string& trigger = gestures->getSimpleSelectedValue().asString();
714
715 // pretend the user chatted the trigger string, to invoke
716 // substitution and logging.
717 std::string text(trigger);
718 std::string revised_text;
719 gGestureManager.triggerAndReviseString(text, &revised_text);
720
721 revised_text = utf8str_trim(revised_text);
722 if (!revised_text.empty())
723 {
724 // Don't play nodding animation
725 self->sendChatFromViewer(revised_text, CHAT_TYPE_NORMAL, FALSE);
726 }
727 }
728 self->mGestureLabelTimer.start();
729 // free focus back to chat bar
730 self->childSetFocus("Gesture", FALSE);
731}