diff options
Diffstat (limited to 'linden/indra/newview/llviewertexteditor.cpp')
-rw-r--r-- | linden/indra/newview/llviewertexteditor.cpp | 1490 |
1 files changed, 1490 insertions, 0 deletions
diff --git a/linden/indra/newview/llviewertexteditor.cpp b/linden/indra/newview/llviewertexteditor.cpp new file mode 100644 index 0000000..00e25af --- /dev/null +++ b/linden/indra/newview/llviewertexteditor.cpp | |||
@@ -0,0 +1,1490 @@ | |||
1 | /** | ||
2 | * @file llviewertexteditor.cpp | ||
3 | * @brief Text editor widget to let users enter a a multi-line ASCII document. | ||
4 | * | ||
5 | * Copyright (c) 2001-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 "llfocusmgr.h" | ||
31 | #include "audioengine.h" | ||
32 | #include "llagent.h" | ||
33 | #include "llinventory.h" | ||
34 | #include "llinventorymodel.h" | ||
35 | #include "llinventoryview.h" | ||
36 | |||
37 | #include "llviewertexteditor.h" | ||
38 | |||
39 | #include "llfloaterchat.h" | ||
40 | #include "llfloaterworldmap.h" | ||
41 | #include "llnotify.h" | ||
42 | #include "llpreview.h" | ||
43 | #include "llpreviewtexture.h" | ||
44 | #include "llpreviewnotecard.h" | ||
45 | #include "llpreviewlandmark.h" | ||
46 | #include "llscrollbar.h" | ||
47 | #include "lltooldraganddrop.h" | ||
48 | #include "llviewercontrol.h" | ||
49 | #include "llviewerimagelist.h" | ||
50 | #include "llviewerwindow.h" | ||
51 | #include "llviewerinventory.h" | ||
52 | #include "llnotecard.h" | ||
53 | #include "llmemorystream.h" | ||
54 | |||
55 | extern BOOL gPacificDaylightTime; | ||
56 | |||
57 | //////////////////////////////////////////////////////////// | ||
58 | // LLEmbeddedItems | ||
59 | // | ||
60 | // Embedded items are stored as: | ||
61 | // * A global map of llwchar to LLInventoryItem | ||
62 | // ** This is unique for each item embedded in any notecard | ||
63 | // to support copy/paste across notecards | ||
64 | // * A per-notecard set of embeded llwchars for easy removal | ||
65 | // from the global list | ||
66 | // * A per-notecard vector of embedded lwchars for mapping from | ||
67 | // old style 0x80 + item format notechards | ||
68 | |||
69 | class LLEmbeddedItems | ||
70 | { | ||
71 | public: | ||
72 | LLEmbeddedItems(const LLViewerTextEditor* editor); | ||
73 | ~LLEmbeddedItems(); | ||
74 | void clear(); | ||
75 | |||
76 | void bindEmbeddedChars(const LLFontGL* font); | ||
77 | void unbindEmbeddedChars(const LLFontGL* font); | ||
78 | |||
79 | BOOL insertEmbeddedItem(LLInventoryItem* item, llwchar* value, bool is_new); | ||
80 | BOOL removeEmbeddedItem( llwchar ext_char ); | ||
81 | |||
82 | BOOL hasEmbeddedItem(llwchar ext_char); // returns TRUE if /this/ editor has an entry for this item | ||
83 | |||
84 | void getEmbeddedItemList( std::vector<LLPointer<LLInventoryItem> >& items ); | ||
85 | void addItems(const std::vector<LLPointer<LLInventoryItem> >& items); | ||
86 | |||
87 | llwchar getEmbeddedCharFromIndex(S32 index); | ||
88 | |||
89 | void removeUnusedChars(); | ||
90 | void copyUsedCharsToIndexed(); | ||
91 | S32 getIndexFromEmbeddedChar(llwchar wch); | ||
92 | |||
93 | void markSaved(); | ||
94 | |||
95 | static LLInventoryItem* getEmbeddedItem(llwchar ext_char); // returns item from static list | ||
96 | static BOOL getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved | ||
97 | |||
98 | struct embedded_info_t | ||
99 | { | ||
100 | LLPointer<LLInventoryItem> mItem; | ||
101 | BOOL mSaved; | ||
102 | }; | ||
103 | private: | ||
104 | typedef std::map<llwchar, embedded_info_t > item_map_t; | ||
105 | static item_map_t sEntries; | ||
106 | static std::stack<llwchar> sFreeEntries; | ||
107 | |||
108 | std::set<llwchar> mEmbeddedUsedChars; // list of used llwchars | ||
109 | std::vector<llwchar> mEmbeddedIndexedChars; // index -> wchar for 0x80 + index format | ||
110 | const LLViewerTextEditor* mEditor; | ||
111 | }; | ||
112 | |||
113 | //statics | ||
114 | LLEmbeddedItems::item_map_t LLEmbeddedItems::sEntries; | ||
115 | std::stack<llwchar> LLEmbeddedItems::sFreeEntries; | ||
116 | |||
117 | LLEmbeddedItems::LLEmbeddedItems(const LLViewerTextEditor* editor) | ||
118 | : mEditor(editor) | ||
119 | { | ||
120 | } | ||
121 | |||
122 | LLEmbeddedItems::~LLEmbeddedItems() | ||
123 | { | ||
124 | clear(); | ||
125 | } | ||
126 | |||
127 | void LLEmbeddedItems::clear() | ||
128 | { | ||
129 | // Remove entries for this editor from static list | ||
130 | for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); | ||
131 | iter != mEmbeddedUsedChars.end();) | ||
132 | { | ||
133 | std::set<llwchar>::iterator nextiter = iter++; | ||
134 | removeEmbeddedItem(*nextiter); | ||
135 | } | ||
136 | mEmbeddedUsedChars.clear(); | ||
137 | } | ||
138 | |||
139 | // Inserts a new unique entry | ||
140 | BOOL LLEmbeddedItems::insertEmbeddedItem( LLInventoryItem* item, llwchar* ext_char, bool is_new) | ||
141 | { | ||
142 | // Now insert a new one | ||
143 | llwchar wc_emb; | ||
144 | if (!sFreeEntries.empty()) | ||
145 | { | ||
146 | wc_emb = sFreeEntries.top(); | ||
147 | sFreeEntries.pop(); | ||
148 | } | ||
149 | else if (sEntries.empty()) | ||
150 | { | ||
151 | wc_emb = FIRST_EMBEDDED_CHAR; | ||
152 | } | ||
153 | else | ||
154 | { | ||
155 | item_map_t::iterator last = sEntries.end(); | ||
156 | --last; | ||
157 | wc_emb = last->first; | ||
158 | if (wc_emb >= LAST_EMBEDDED_CHAR) | ||
159 | { | ||
160 | return FALSE; | ||
161 | } | ||
162 | ++wc_emb; | ||
163 | } | ||
164 | |||
165 | sEntries[wc_emb].mItem = item; | ||
166 | sEntries[wc_emb].mSaved = is_new ? FALSE : TRUE; | ||
167 | *ext_char = wc_emb; | ||
168 | mEmbeddedUsedChars.insert(wc_emb); | ||
169 | return TRUE; | ||
170 | } | ||
171 | |||
172 | // Removes an entry (all entries are unique) | ||
173 | BOOL LLEmbeddedItems::removeEmbeddedItem( llwchar ext_char ) | ||
174 | { | ||
175 | mEmbeddedUsedChars.erase(ext_char); | ||
176 | item_map_t::iterator iter = sEntries.find(ext_char); | ||
177 | if (iter != sEntries.end()) | ||
178 | { | ||
179 | sEntries.erase(ext_char); | ||
180 | sFreeEntries.push(ext_char); | ||
181 | return TRUE; | ||
182 | } | ||
183 | return FALSE; | ||
184 | } | ||
185 | |||
186 | // static | ||
187 | LLInventoryItem* LLEmbeddedItems::getEmbeddedItem(llwchar ext_char) | ||
188 | { | ||
189 | if( ext_char >= FIRST_EMBEDDED_CHAR && ext_char <= LAST_EMBEDDED_CHAR ) | ||
190 | { | ||
191 | item_map_t::iterator iter = sEntries.find(ext_char); | ||
192 | if (iter != sEntries.end()) | ||
193 | { | ||
194 | return iter->second.mItem; | ||
195 | } | ||
196 | } | ||
197 | return NULL; | ||
198 | } | ||
199 | |||
200 | // static | ||
201 | BOOL LLEmbeddedItems::getEmbeddedItemSaved(llwchar ext_char) | ||
202 | { | ||
203 | if( ext_char >= FIRST_EMBEDDED_CHAR && ext_char <= LAST_EMBEDDED_CHAR ) | ||
204 | { | ||
205 | item_map_t::iterator iter = sEntries.find(ext_char); | ||
206 | if (iter != sEntries.end()) | ||
207 | { | ||
208 | return iter->second.mSaved; | ||
209 | } | ||
210 | } | ||
211 | return FALSE; | ||
212 | } | ||
213 | |||
214 | llwchar LLEmbeddedItems::getEmbeddedCharFromIndex(S32 index) | ||
215 | { | ||
216 | if (index >= (S32)mEmbeddedIndexedChars.size()) | ||
217 | { | ||
218 | llwarns << "No item for embedded char " << index << " using LL_UNKNOWN_CHAR" << llendl; | ||
219 | return LL_UNKNOWN_CHAR; | ||
220 | } | ||
221 | return mEmbeddedIndexedChars[index]; | ||
222 | } | ||
223 | |||
224 | void LLEmbeddedItems::removeUnusedChars() | ||
225 | { | ||
226 | std::set<llwchar> used = mEmbeddedUsedChars; | ||
227 | const LLWString& wtext = mEditor->getWText(); | ||
228 | for (S32 i=0; i<(S32)wtext.size(); i++) | ||
229 | { | ||
230 | llwchar wc = wtext[i]; | ||
231 | if( wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR ) | ||
232 | { | ||
233 | used.erase(wc); | ||
234 | } | ||
235 | } | ||
236 | // Remove chars not actually used | ||
237 | for (std::set<llwchar>::iterator iter = used.begin(); | ||
238 | iter != used.end(); ++iter) | ||
239 | { | ||
240 | removeEmbeddedItem(*iter); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | void LLEmbeddedItems::copyUsedCharsToIndexed() | ||
245 | { | ||
246 | // Prune unused items | ||
247 | removeUnusedChars(); | ||
248 | |||
249 | // Copy all used llwchars to mEmbeddedIndexedChars | ||
250 | mEmbeddedIndexedChars.clear(); | ||
251 | for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); | ||
252 | iter != mEmbeddedUsedChars.end(); ++iter) | ||
253 | { | ||
254 | mEmbeddedIndexedChars.push_back(*iter); | ||
255 | } | ||
256 | } | ||
257 | |||
258 | S32 LLEmbeddedItems::getIndexFromEmbeddedChar(llwchar wch) | ||
259 | { | ||
260 | S32 idx = 0; | ||
261 | for (std::vector<llwchar>::iterator iter = mEmbeddedIndexedChars.begin(); | ||
262 | iter != mEmbeddedIndexedChars.end(); ++iter) | ||
263 | { | ||
264 | if (wch == *iter) | ||
265 | break; | ||
266 | ++idx; | ||
267 | } | ||
268 | if (idx < (S32)mEmbeddedIndexedChars.size()) | ||
269 | { | ||
270 | return idx; | ||
271 | } | ||
272 | else | ||
273 | { | ||
274 | llwarns << "Embedded char " << wch << " not found, using 0" << llendl; | ||
275 | return 0; | ||
276 | } | ||
277 | } | ||
278 | |||
279 | BOOL LLEmbeddedItems::hasEmbeddedItem(llwchar ext_char) | ||
280 | { | ||
281 | std::set<llwchar>::iterator iter = mEmbeddedUsedChars.find(ext_char); | ||
282 | if (iter != mEmbeddedUsedChars.end()) | ||
283 | { | ||
284 | return TRUE; | ||
285 | } | ||
286 | return FALSE; | ||
287 | } | ||
288 | |||
289 | void LLEmbeddedItems::bindEmbeddedChars( const LLFontGL* font ) | ||
290 | { | ||
291 | if( sEntries.empty() ) | ||
292 | { | ||
293 | return; | ||
294 | } | ||
295 | |||
296 | for (std::set<llwchar>::iterator iter1 = mEmbeddedUsedChars.begin(); iter1 != mEmbeddedUsedChars.end(); ++iter1) | ||
297 | { | ||
298 | llwchar wch = *iter1; | ||
299 | item_map_t::iterator iter2 = sEntries.find(wch); | ||
300 | if (iter2 == sEntries.end()) | ||
301 | { | ||
302 | continue; | ||
303 | } | ||
304 | LLInventoryItem* item = iter2->second.mItem; | ||
305 | if (!item) | ||
306 | { | ||
307 | continue; | ||
308 | } | ||
309 | const char* img_name; | ||
310 | switch( item->getType() ) | ||
311 | { | ||
312 | case LLAssetType::AT_TEXTURE: | ||
313 | if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT) | ||
314 | { | ||
315 | img_name = "inv_item_snapshot.tga"; | ||
316 | } | ||
317 | else | ||
318 | { | ||
319 | img_name = "inv_item_texture.tga"; | ||
320 | } | ||
321 | |||
322 | break; | ||
323 | case LLAssetType::AT_SOUND: img_name = "inv_item_sound.tga"; break; | ||
324 | case LLAssetType::AT_LANDMARK: | ||
325 | if (item->getFlags() & LLInventoryItem::II_FLAGS_LANDMARK_VISITED) | ||
326 | { | ||
327 | img_name = "inv_item_landmark_visited.tga"; | ||
328 | } | ||
329 | else | ||
330 | { | ||
331 | img_name = "inv_item_landmark.tga"; | ||
332 | } | ||
333 | break; | ||
334 | case LLAssetType::AT_CLOTHING: img_name = "inv_item_clothing.tga"; break; | ||
335 | case LLAssetType::AT_OBJECT: img_name = "inv_item_object.tga"; break; | ||
336 | case LLAssetType::AT_NOTECARD: img_name = "inv_item_notecard.tga"; break; | ||
337 | case LLAssetType::AT_LSL_TEXT: img_name = "inv_item_script.tga"; break; | ||
338 | case LLAssetType::AT_BODYPART: img_name = "inv_item_bodypart.tga"; break; | ||
339 | case LLAssetType::AT_ANIMATION: img_name = "inv_item_animation.tga";break; | ||
340 | case LLAssetType::AT_GESTURE: img_name = "inv_item_gesture.tga"; break; | ||
341 | default: llassert(0); continue; | ||
342 | } | ||
343 | |||
344 | LLViewerImage* image = gImageList.getImage(LLUUID(gViewerArt.getString(img_name)), MIPMAP_FALSE, TRUE); | ||
345 | |||
346 | ((LLFontGL*)font)->addEmbeddedChar( wch, image, item->getName() ); | ||
347 | } | ||
348 | } | ||
349 | |||
350 | void LLEmbeddedItems::unbindEmbeddedChars( const LLFontGL* font ) | ||
351 | { | ||
352 | if( sEntries.empty() ) | ||
353 | { | ||
354 | return; | ||
355 | } | ||
356 | |||
357 | for (std::set<llwchar>::iterator iter1 = mEmbeddedUsedChars.begin(); iter1 != mEmbeddedUsedChars.end(); ++iter1) | ||
358 | { | ||
359 | ((LLFontGL*)font)->removeEmbeddedChar(*iter1); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | void LLEmbeddedItems::addItems(const std::vector<LLPointer<LLInventoryItem> >& items) | ||
364 | { | ||
365 | for (std::vector<LLPointer<LLInventoryItem> >::const_iterator iter = items.begin(); | ||
366 | iter != items.end(); ++iter) | ||
367 | { | ||
368 | LLInventoryItem* item = *iter; | ||
369 | if (item) | ||
370 | { | ||
371 | llwchar wc; | ||
372 | if (!insertEmbeddedItem( item, &wc, false )) | ||
373 | { | ||
374 | break; | ||
375 | } | ||
376 | mEmbeddedIndexedChars.push_back(wc); | ||
377 | } | ||
378 | } | ||
379 | } | ||
380 | |||
381 | void LLEmbeddedItems::getEmbeddedItemList( std::vector<LLPointer<LLInventoryItem> >& items ) | ||
382 | { | ||
383 | for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) | ||
384 | { | ||
385 | llwchar wc = *iter; | ||
386 | LLPointer<LLInventoryItem> item = getEmbeddedItem(wc); | ||
387 | if (item) | ||
388 | { | ||
389 | items.push_back(item); | ||
390 | } | ||
391 | } | ||
392 | } | ||
393 | |||
394 | void LLEmbeddedItems::markSaved() | ||
395 | { | ||
396 | for (std::set<llwchar>::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) | ||
397 | { | ||
398 | llwchar wc = *iter; | ||
399 | sEntries[wc].mSaved = TRUE; | ||
400 | } | ||
401 | } | ||
402 | |||
403 | /////////////////////////////////////////////////////////////////// | ||
404 | |||
405 | class LLTextCmdInsertEmbeddedItem : public LLTextCmd | ||
406 | { | ||
407 | public: | ||
408 | LLTextCmdInsertEmbeddedItem( S32 pos, LLInventoryItem* item ) | ||
409 | : LLTextCmd(pos, FALSE), | ||
410 | mExtCharValue(0) | ||
411 | { | ||
412 | mItem = item; | ||
413 | } | ||
414 | |||
415 | virtual BOOL execute( LLTextEditor* editor, S32* delta ) | ||
416 | { | ||
417 | LLViewerTextEditor* viewer_editor = (LLViewerTextEditor*)editor; | ||
418 | // Take this opportunity to remove any unused embedded items from this editor | ||
419 | viewer_editor->mEmbeddedItemList->removeUnusedChars(); | ||
420 | if(viewer_editor->mEmbeddedItemList->insertEmbeddedItem( mItem, &mExtCharValue, true ) ) | ||
421 | { | ||
422 | LLWString ws; | ||
423 | ws.assign(1, mExtCharValue); | ||
424 | *delta = insert(editor, mPos, ws ); | ||
425 | return (*delta != 0); | ||
426 | } | ||
427 | return FALSE; | ||
428 | } | ||
429 | |||
430 | virtual S32 undo( LLTextEditor* editor ) | ||
431 | { | ||
432 | remove(editor, mPos, 1); | ||
433 | return mPos; | ||
434 | } | ||
435 | |||
436 | virtual S32 redo( LLTextEditor* editor ) | ||
437 | { | ||
438 | LLWString ws; | ||
439 | ws += mExtCharValue; | ||
440 | insert(editor, mPos, ws ); | ||
441 | return mPos + 1; | ||
442 | } | ||
443 | virtual BOOL hasExtCharValue( llwchar value ) | ||
444 | { | ||
445 | return (value == mExtCharValue); | ||
446 | } | ||
447 | |||
448 | private: | ||
449 | LLPointer<LLInventoryItem> mItem; | ||
450 | llwchar mExtCharValue; | ||
451 | }; | ||
452 | |||
453 | struct LLNotecardCopyInfo | ||
454 | { | ||
455 | LLNotecardCopyInfo(LLViewerTextEditor *ed, LLInventoryItem *item) | ||
456 | : mTextEd(ed) | ||
457 | { | ||
458 | mItem = item; | ||
459 | } | ||
460 | |||
461 | LLViewerTextEditor* mTextEd; | ||
462 | // need to make this be a copy (not a * here) because it isn't stable. | ||
463 | // I wish we had passed LLPointers all the way down, but we didn't | ||
464 | LLPointer<LLInventoryItem> mItem; | ||
465 | }; | ||
466 | |||
467 | //---------------------------------------------------------------------------- | ||
468 | |||
469 | // | ||
470 | // Member functions | ||
471 | // | ||
472 | |||
473 | LLViewerTextEditor::LLViewerTextEditor(const LLString& name, | ||
474 | const LLRect& rect, | ||
475 | S32 max_length, | ||
476 | const LLString& default_text, | ||
477 | const LLFontGL* font, | ||
478 | BOOL allow_embedded_items) | ||
479 | : LLTextEditor(name, rect, max_length, default_text, font, allow_embedded_items), | ||
480 | mDragItemSaved(FALSE) | ||
481 | { | ||
482 | mEmbeddedItemList = new LLEmbeddedItems(this); | ||
483 | } | ||
484 | |||
485 | LLViewerTextEditor::~LLViewerTextEditor() | ||
486 | { | ||
487 | delete mEmbeddedItemList; | ||
488 | } | ||
489 | |||
490 | /////////////////////////////////////////////////////////////////// | ||
491 | // virtual | ||
492 | void LLViewerTextEditor::makePristine() | ||
493 | { | ||
494 | mEmbeddedItemList->markSaved(); | ||
495 | LLTextEditor::makePristine(); | ||
496 | } | ||
497 | |||
498 | /////////////////////////////////////////////////////////////////// | ||
499 | |||
500 | BOOL LLViewerTextEditor::handleToolTip(S32 x, S32 y, LLString& msg, LLRect* sticky_rect_screen) | ||
501 | { | ||
502 | if (pointInView(x, y) && getVisible()) | ||
503 | { | ||
504 | for (child_list_const_iter_t child_iter = getChildList()->begin(); | ||
505 | child_iter != getChildList()->end(); ++child_iter) | ||
506 | { | ||
507 | LLView *viewp = *child_iter; | ||
508 | S32 local_x = x - viewp->getRect().mLeft; | ||
509 | S32 local_y = y - viewp->getRect().mBottom; | ||
510 | if( viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) | ||
511 | { | ||
512 | return TRUE; | ||
513 | } | ||
514 | } | ||
515 | |||
516 | if( mSegments.empty() ) | ||
517 | { | ||
518 | return TRUE; | ||
519 | } | ||
520 | |||
521 | LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); | ||
522 | if( cur_segment ) | ||
523 | { | ||
524 | BOOL has_tool_tip = FALSE; | ||
525 | if( cur_segment->getStyle().getIsEmbeddedItem() ) | ||
526 | { | ||
527 | LLWString wtip; | ||
528 | has_tool_tip = getEmbeddedItemToolTipAtPos(cur_segment->getStart(), wtip); | ||
529 | msg = wstring_to_utf8str(wtip); | ||
530 | } | ||
531 | else | ||
532 | { | ||
533 | has_tool_tip = cur_segment->getToolTip( msg ); | ||
534 | } | ||
535 | if( has_tool_tip ) | ||
536 | { | ||
537 | // Just use a slop area around the cursor | ||
538 | // Convert rect local to screen coordinates | ||
539 | S32 SLOP = 8; | ||
540 | localPointToScreen( | ||
541 | x - SLOP, y - SLOP, | ||
542 | &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); | ||
543 | sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; | ||
544 | sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; | ||
545 | } | ||
546 | } | ||
547 | return TRUE; | ||
548 | } | ||
549 | return FALSE; | ||
550 | } | ||
551 | |||
552 | BOOL LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) | ||
553 | { | ||
554 | BOOL handled = FALSE; | ||
555 | |||
556 | // Let scrollbar have first dibs | ||
557 | handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; | ||
558 | |||
559 | // enable I Agree checkbox if the user scrolled through entire text | ||
560 | BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); | ||
561 | if (mOnScrollEndCallback && was_scrolled_to_bottom) | ||
562 | { | ||
563 | mOnScrollEndCallback(mOnScrollEndData); | ||
564 | } | ||
565 | |||
566 | if( !handled && mTakesNonScrollClicks) | ||
567 | { | ||
568 | if (!(mask & MASK_SHIFT)) | ||
569 | { | ||
570 | deselect(); | ||
571 | } | ||
572 | |||
573 | BOOL start_select = TRUE; | ||
574 | if( mAllowEmbeddedItems ) | ||
575 | { | ||
576 | setCursorAtLocalPos( x, y, FALSE ); | ||
577 | llwchar wc = 0; | ||
578 | if (mCursorPos < getLength()) | ||
579 | { | ||
580 | wc = mWText[mCursorPos]; | ||
581 | } | ||
582 | LLInventoryItem* item_at_pos = LLEmbeddedItems::getEmbeddedItem(wc); | ||
583 | if (item_at_pos) | ||
584 | { | ||
585 | mDragItem = item_at_pos; | ||
586 | mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc); | ||
587 | gFocusMgr.setMouseCapture( this, NULL ); | ||
588 | mMouseDownX = x; | ||
589 | mMouseDownY = y; | ||
590 | S32 screen_x; | ||
591 | S32 screen_y; | ||
592 | localPointToScreen(x, y, &screen_x, &screen_y ); | ||
593 | gToolDragAndDrop->setDragStart( screen_x, screen_y ); | ||
594 | |||
595 | start_select = FALSE; | ||
596 | } | ||
597 | else | ||
598 | { | ||
599 | mDragItem = NULL; | ||
600 | } | ||
601 | } | ||
602 | |||
603 | if( start_select ) | ||
604 | { | ||
605 | // If we're not scrolling (handled by child), then we're selecting | ||
606 | if (mask & MASK_SHIFT) | ||
607 | { | ||
608 | S32 old_cursor_pos = mCursorPos; | ||
609 | setCursorAtLocalPos( x, y, TRUE ); | ||
610 | |||
611 | if (hasSelection()) | ||
612 | { | ||
613 | /* Mac-like behavior - extend selection towards the cursor | ||
614 | if (mCursorPos < mSelectionStart | ||
615 | && mCursorPos < mSelectionEnd) | ||
616 | { | ||
617 | // ...left of selection | ||
618 | mSelectionStart = llmax(mSelectionStart, mSelectionEnd); | ||
619 | mSelectionEnd = mCursorPos; | ||
620 | } | ||
621 | else if (mCursorPos > mSelectionStart | ||
622 | && mCursorPos > mSelectionEnd) | ||
623 | { | ||
624 | // ...right of selection | ||
625 | mSelectionStart = llmin(mSelectionStart, mSelectionEnd); | ||
626 | mSelectionEnd = mCursorPos; | ||
627 | } | ||
628 | else | ||
629 | { | ||
630 | mSelectionEnd = mCursorPos; | ||
631 | } | ||
632 | */ | ||
633 | // Windows behavior | ||
634 | mSelectionEnd = mCursorPos; | ||
635 | } | ||
636 | else | ||
637 | { | ||
638 | mSelectionStart = old_cursor_pos; | ||
639 | mSelectionEnd = mCursorPos; | ||
640 | } | ||
641 | // assume we're starting a drag select | ||
642 | mIsSelecting = TRUE; | ||
643 | } | ||
644 | else | ||
645 | { | ||
646 | setCursorAtLocalPos( x, y, TRUE ); | ||
647 | startSelection(); | ||
648 | } | ||
649 | gFocusMgr.setMouseCapture( this, &LLTextEditor::onMouseCaptureLost ); | ||
650 | } | ||
651 | |||
652 | handled = TRUE; | ||
653 | } | ||
654 | |||
655 | if (mTakesFocus) | ||
656 | { | ||
657 | setFocus( TRUE ); | ||
658 | handled = TRUE; | ||
659 | } | ||
660 | |||
661 | // Delay cursor flashing | ||
662 | mKeystrokeTimer.reset(); | ||
663 | |||
664 | return handled; | ||
665 | } | ||
666 | |||
667 | |||
668 | BOOL LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask) | ||
669 | { | ||
670 | BOOL handled = FALSE; | ||
671 | |||
672 | if (!mDragItem) | ||
673 | { | ||
674 | // leave hover segment active during drag and drop | ||
675 | mHoverSegment = NULL; | ||
676 | } | ||
677 | if( getVisible() ) | ||
678 | { | ||
679 | if(gFocusMgr.getMouseCapture() == this ) | ||
680 | { | ||
681 | if( mIsSelecting ) | ||
682 | { | ||
683 | if (x != mLastSelectionX || y != mLastSelectionY) | ||
684 | { | ||
685 | mLastSelectionX = x; | ||
686 | mLastSelectionY = y; | ||
687 | } | ||
688 | |||
689 | if( y > mTextRect.mTop ) | ||
690 | { | ||
691 | mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); | ||
692 | } | ||
693 | else | ||
694 | if( y < mTextRect.mBottom ) | ||
695 | { | ||
696 | mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); | ||
697 | } | ||
698 | |||
699 | setCursorAtLocalPos( x, y, TRUE ); | ||
700 | mSelectionEnd = mCursorPos; | ||
701 | |||
702 | updateScrollFromCursor(); | ||
703 | getWindow()->setCursor(UI_CURSOR_IBEAM); | ||
704 | } | ||
705 | else if( mDragItem ) | ||
706 | { | ||
707 | S32 screen_x; | ||
708 | S32 screen_y; | ||
709 | localPointToScreen(x, y, &screen_x, &screen_y ); | ||
710 | if( gToolDragAndDrop->isOverThreshold( screen_x, screen_y ) ) | ||
711 | { | ||
712 | gToolDragAndDrop->beginDrag( | ||
713 | LLAssetType::lookupDragAndDropType( mDragItem->getType() ), | ||
714 | mDragItem->getUUID(), | ||
715 | LLToolDragAndDrop::SOURCE_NOTECARD, | ||
716 | mSourceID, mObjectID); | ||
717 | |||
718 | return gToolDragAndDrop->handleHover( x, y, mask ); | ||
719 | } | ||
720 | getWindow()->setCursor(UI_CURSOR_HAND); | ||
721 | } | ||
722 | |||
723 | lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; | ||
724 | handled = TRUE; | ||
725 | } | ||
726 | |||
727 | if( !handled ) | ||
728 | { | ||
729 | // Pass to children | ||
730 | handled = LLView::childrenHandleHover(x, y, mask) != NULL; | ||
731 | } | ||
732 | |||
733 | if( handled ) | ||
734 | { | ||
735 | // Delay cursor flashing | ||
736 | mKeystrokeTimer.reset(); | ||
737 | } | ||
738 | |||
739 | // Opaque | ||
740 | if( !handled && mTakesNonScrollClicks) | ||
741 | { | ||
742 | // Check to see if we're over an HTML-style link | ||
743 | if( !mSegments.empty() ) | ||
744 | { | ||
745 | LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); | ||
746 | if( cur_segment ) | ||
747 | { | ||
748 | if(cur_segment->getStyle().isLink()) | ||
749 | { | ||
750 | lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl; | ||
751 | getWindow()->setCursor(UI_CURSOR_HAND); | ||
752 | handled = TRUE; | ||
753 | } | ||
754 | else | ||
755 | if(cur_segment->getStyle().getIsEmbeddedItem()) | ||
756 | { | ||
757 | lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl; | ||
758 | getWindow()->setCursor(UI_CURSOR_HAND); | ||
759 | //getWindow()->setCursor(UI_CURSOR_ARROW); | ||
760 | handled = TRUE; | ||
761 | } | ||
762 | mHoverSegment = cur_segment; | ||
763 | } | ||
764 | } | ||
765 | |||
766 | if( !handled ) | ||
767 | { | ||
768 | lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; | ||
769 | if (!mScrollbar->getVisible() || x < mRect.getWidth() - SCROLLBAR_SIZE) | ||
770 | { | ||
771 | getWindow()->setCursor(UI_CURSOR_IBEAM); | ||
772 | } | ||
773 | else | ||
774 | { | ||
775 | getWindow()->setCursor(UI_CURSOR_ARROW); | ||
776 | } | ||
777 | handled = TRUE; | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | |||
782 | return handled; | ||
783 | } | ||
784 | |||
785 | |||
786 | BOOL LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) | ||
787 | { | ||
788 | BOOL handled = FALSE; | ||
789 | |||
790 | // let scrollbar have first dibs | ||
791 | handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL; | ||
792 | |||
793 | // enable I Agree checkbox if the user scrolled through entire text | ||
794 | BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); | ||
795 | if (mOnScrollEndCallback && was_scrolled_to_bottom) | ||
796 | { | ||
797 | mOnScrollEndCallback(mOnScrollEndData); | ||
798 | } | ||
799 | |||
800 | if( !handled && mTakesNonScrollClicks) | ||
801 | { | ||
802 | if( mIsSelecting ) | ||
803 | { | ||
804 | // Finish selection | ||
805 | if( y > mTextRect.mTop ) | ||
806 | { | ||
807 | mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); | ||
808 | } | ||
809 | else | ||
810 | if( y < mTextRect.mBottom ) | ||
811 | { | ||
812 | mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); | ||
813 | } | ||
814 | |||
815 | setCursorAtLocalPos( x, y, TRUE ); | ||
816 | endSelection(); | ||
817 | |||
818 | updateScrollFromCursor(); | ||
819 | } | ||
820 | |||
821 | if( !hasSelection() ) | ||
822 | { | ||
823 | handleMouseUpOverSegment( x, y, mask ); | ||
824 | } | ||
825 | |||
826 | handled = TRUE; | ||
827 | } | ||
828 | |||
829 | // Delay cursor flashing | ||
830 | mKeystrokeTimer.reset(); | ||
831 | |||
832 | if( gFocusMgr.getMouseCapture() == this ) | ||
833 | { | ||
834 | if (mDragItem) | ||
835 | { | ||
836 | // mouse down was on an item | ||
837 | S32 dx = x - mMouseDownX; | ||
838 | S32 dy = y - mMouseDownY; | ||
839 | if (-2 < dx && dx < 2 && -2 < dy && dy < 2) | ||
840 | { | ||
841 | openEmbeddedItem(mDragItem, mDragItemSaved); | ||
842 | } | ||
843 | } | ||
844 | mDragItem = NULL; | ||
845 | gFocusMgr.setMouseCapture( NULL, NULL ); | ||
846 | handled = TRUE; | ||
847 | } | ||
848 | |||
849 | return handled; | ||
850 | } | ||
851 | |||
852 | |||
853 | BOOL LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) | ||
854 | { | ||
855 | BOOL handled = FALSE; | ||
856 | |||
857 | // let scrollbar have first dibs | ||
858 | handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; | ||
859 | |||
860 | if( !handled && mTakesNonScrollClicks) | ||
861 | { | ||
862 | if( mAllowEmbeddedItems ) | ||
863 | { | ||
864 | LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); | ||
865 | if( cur_segment && cur_segment->getStyle().getIsEmbeddedItem() ) | ||
866 | { | ||
867 | if( openEmbeddedItemAtPos( cur_segment->getStart() ) ) | ||
868 | { | ||
869 | deselect(); | ||
870 | setFocus( FALSE ); | ||
871 | return TRUE; | ||
872 | } | ||
873 | } | ||
874 | } | ||
875 | |||
876 | if (mTakesFocus) | ||
877 | { | ||
878 | setFocus( TRUE ); | ||
879 | } | ||
880 | |||
881 | setCursorAtLocalPos( x, y, FALSE ); | ||
882 | deselect(); | ||
883 | |||
884 | const LLWString &text = getWText(); | ||
885 | |||
886 | if( isPartOfWord( text[mCursorPos] ) ) | ||
887 | { | ||
888 | // Select word the cursor is over | ||
889 | while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1])) | ||
890 | { | ||
891 | mCursorPos--; | ||
892 | } | ||
893 | startSelection(); | ||
894 | |||
895 | while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) ) | ||
896 | { | ||
897 | mCursorPos++; | ||
898 | } | ||
899 | |||
900 | mSelectionEnd = mCursorPos; | ||
901 | } | ||
902 | else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) ) | ||
903 | { | ||
904 | // Select the character the cursor is over | ||
905 | startSelection(); | ||
906 | mCursorPos++; | ||
907 | mSelectionEnd = mCursorPos; | ||
908 | } | ||
909 | |||
910 | // We don't want handleMouseUp() to "finish" the selection (and thereby | ||
911 | // set mSelectionEnd to where the mouse is), so we finish the selection here. | ||
912 | mIsSelecting = FALSE; | ||
913 | |||
914 | // delay cursor flashing | ||
915 | mKeystrokeTimer.reset(); | ||
916 | |||
917 | handled = TRUE; | ||
918 | } | ||
919 | return handled; | ||
920 | } | ||
921 | |||
922 | |||
923 | // Allow calling cards to be dropped onto text fields. Append the name and | ||
924 | // a carriage return. | ||
925 | // virtual | ||
926 | BOOL LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, | ||
927 | BOOL drop, EDragAndDropType cargo_type, void *cargo_data, | ||
928 | EAcceptance *accept, | ||
929 | LLString& tooltip_msg) | ||
930 | { | ||
931 | BOOL handled = FALSE; | ||
932 | |||
933 | if (mTakesNonScrollClicks) | ||
934 | { | ||
935 | if (getEnabled() && !mReadOnly) | ||
936 | { | ||
937 | switch( cargo_type ) | ||
938 | { | ||
939 | case DAD_CALLINGCARD: | ||
940 | if(mAcceptCallingCardNames) | ||
941 | { | ||
942 | if (drop) | ||
943 | { | ||
944 | LLInventoryItem *item = (LLInventoryItem *)cargo_data; | ||
945 | LLString name = item->getName(); | ||
946 | appendText(name, true, true); | ||
947 | } | ||
948 | *accept = ACCEPT_YES_COPY_SINGLE; | ||
949 | } | ||
950 | else | ||
951 | { | ||
952 | *accept = ACCEPT_NO; | ||
953 | } | ||
954 | break; | ||
955 | |||
956 | case DAD_TEXTURE: | ||
957 | case DAD_SOUND: | ||
958 | case DAD_LANDMARK: | ||
959 | case DAD_SCRIPT: | ||
960 | case DAD_CLOTHING: | ||
961 | case DAD_OBJECT: | ||
962 | case DAD_NOTECARD: | ||
963 | case DAD_BODYPART: | ||
964 | case DAD_ANIMATION: | ||
965 | case DAD_GESTURE: | ||
966 | { | ||
967 | LLInventoryItem *item = (LLInventoryItem *)cargo_data; | ||
968 | if( mAllowEmbeddedItems ) | ||
969 | { | ||
970 | U32 mask_next = item->getPermissions().getMaskNextOwner(); | ||
971 | if((mask_next & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) | ||
972 | { | ||
973 | if( drop ) | ||
974 | { | ||
975 | deselect(); | ||
976 | S32 old_cursor = mCursorPos; | ||
977 | setCursorAtLocalPos( x, y, TRUE ); | ||
978 | S32 insert_pos = mCursorPos; | ||
979 | setCursorPos(old_cursor); | ||
980 | BOOL inserted = insertEmbeddedItem( insert_pos, item ); | ||
981 | if( inserted && (old_cursor > mCursorPos) ) | ||
982 | { | ||
983 | setCursorPos(mCursorPos + 1); | ||
984 | } | ||
985 | |||
986 | updateLineStartList(); | ||
987 | } | ||
988 | *accept = ACCEPT_YES_COPY_MULTI; | ||
989 | } | ||
990 | else | ||
991 | { | ||
992 | *accept = ACCEPT_NO; | ||
993 | if (tooltip_msg.empty()) | ||
994 | { | ||
995 | tooltip_msg.assign("Only items with unrestricted\n" | ||
996 | "'next owner' permissions \n" | ||
997 | "can be attached to notecards."); | ||
998 | } | ||
999 | } | ||
1000 | } | ||
1001 | else | ||
1002 | { | ||
1003 | *accept = ACCEPT_NO; | ||
1004 | } | ||
1005 | break; | ||
1006 | } | ||
1007 | |||
1008 | default: | ||
1009 | *accept = ACCEPT_NO; | ||
1010 | break; | ||
1011 | } | ||
1012 | } | ||
1013 | else | ||
1014 | { | ||
1015 | // Not enabled | ||
1016 | *accept = ACCEPT_NO; | ||
1017 | } | ||
1018 | |||
1019 | handled = TRUE; | ||
1020 | lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLViewerTextEditor " << getName() << llendl; | ||
1021 | } | ||
1022 | |||
1023 | return handled; | ||
1024 | } | ||
1025 | |||
1026 | void LLViewerTextEditor::setASCIIEmbeddedText(const LLString& instr) | ||
1027 | { | ||
1028 | LLWString wtext; | ||
1029 | const U8* buffer = (U8*)(instr.c_str()); | ||
1030 | while (*buffer) | ||
1031 | { | ||
1032 | llwchar wch; | ||
1033 | U8 c = *buffer++; | ||
1034 | if (c >= 0x80) | ||
1035 | { | ||
1036 | S32 index = (S32)(c - 0x80); | ||
1037 | wch = mEmbeddedItemList->getEmbeddedCharFromIndex(index); | ||
1038 | } | ||
1039 | else | ||
1040 | { | ||
1041 | wch = (llwchar)c; | ||
1042 | } | ||
1043 | wtext.push_back(wch); | ||
1044 | } | ||
1045 | setWText(wtext); | ||
1046 | } | ||
1047 | |||
1048 | void LLViewerTextEditor::setEmbeddedText(const LLString& instr) | ||
1049 | { | ||
1050 | LLWString wtext = utf8str_to_wstring(instr); | ||
1051 | for (S32 i=0; i<(S32)wtext.size(); i++) | ||
1052 | { | ||
1053 | llwchar wch = wtext[i]; | ||
1054 | if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) | ||
1055 | { | ||
1056 | S32 index = wch - FIRST_EMBEDDED_CHAR; | ||
1057 | wtext[i] = mEmbeddedItemList->getEmbeddedCharFromIndex(index); | ||
1058 | } | ||
1059 | } | ||
1060 | setWText(wtext); | ||
1061 | } | ||
1062 | |||
1063 | LLString LLViewerTextEditor::getEmbeddedText() | ||
1064 | { | ||
1065 | #if 1 | ||
1066 | // New version (Version 2) | ||
1067 | mEmbeddedItemList->copyUsedCharsToIndexed(); | ||
1068 | LLWString outtextw; | ||
1069 | for (S32 i=0; i<(S32)mWText.size(); i++) | ||
1070 | { | ||
1071 | llwchar wch = mWText[i]; | ||
1072 | if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) | ||
1073 | { | ||
1074 | S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); | ||
1075 | wch = FIRST_EMBEDDED_CHAR + index; | ||
1076 | } | ||
1077 | outtextw.push_back(wch); | ||
1078 | } | ||
1079 | LLString outtext = wstring_to_utf8str(outtextw); | ||
1080 | return outtext; | ||
1081 | #else | ||
1082 | // Old version (Version 1) | ||
1083 | mEmbeddedItemList->copyUsedCharsToIndexed(); | ||
1084 | LLString outtext; | ||
1085 | for (S32 i=0; i<(S32)mWText.size(); i++) | ||
1086 | { | ||
1087 | llwchar wch = mWText[i]; | ||
1088 | if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) | ||
1089 | { | ||
1090 | S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); | ||
1091 | wch = 0x80 | index % 128; | ||
1092 | } | ||
1093 | else if (wch >= 0x80) | ||
1094 | { | ||
1095 | wch = LL_UNKNOWN_CHAR; | ||
1096 | } | ||
1097 | outtext.push_back((U8)wch); | ||
1098 | } | ||
1099 | return outtext; | ||
1100 | #endif | ||
1101 | } | ||
1102 | |||
1103 | LLString LLViewerTextEditor::appendTime(bool prepend_newline) | ||
1104 | { | ||
1105 | U32 utc_time; | ||
1106 | utc_time = time_corrected(); | ||
1107 | |||
1108 | // There's only one internal tm buffer. | ||
1109 | struct tm* timep; | ||
1110 | |||
1111 | // Convert to Pacific, based on server's opinion of whether | ||
1112 | // it's daylight savings time there. | ||
1113 | timep = utc_to_pacific_time(utc_time, gPacificDaylightTime); | ||
1114 | |||
1115 | LLString text = llformat("[%d:%02d] ", timep->tm_hour, timep->tm_min); | ||
1116 | appendColoredText(text, false, prepend_newline, LLColor4::grey); | ||
1117 | |||
1118 | return text; | ||
1119 | } | ||
1120 | |||
1121 | //---------------------------------------------------------------------------- | ||
1122 | //---------------------------------------------------------------------------- | ||
1123 | |||
1124 | llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char) | ||
1125 | { | ||
1126 | if (mEmbeddedItemList->hasEmbeddedItem(ext_char)) | ||
1127 | { | ||
1128 | return ext_char; // already exists in my list | ||
1129 | } | ||
1130 | LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem(ext_char); | ||
1131 | if (item) | ||
1132 | { | ||
1133 | // Add item to my list and return new llwchar associated with it | ||
1134 | llwchar new_wc; | ||
1135 | if (mEmbeddedItemList->insertEmbeddedItem( item, &new_wc, true )) | ||
1136 | { | ||
1137 | return new_wc; | ||
1138 | } | ||
1139 | } | ||
1140 | return LL_UNKNOWN_CHAR; // item not found or list full | ||
1141 | } | ||
1142 | |||
1143 | void LLViewerTextEditor::bindEmbeddedChars(const LLFontGL* font) | ||
1144 | { | ||
1145 | mEmbeddedItemList->bindEmbeddedChars( font ); | ||
1146 | } | ||
1147 | |||
1148 | void LLViewerTextEditor::unbindEmbeddedChars(const LLFontGL* font) | ||
1149 | { | ||
1150 | mEmbeddedItemList->unbindEmbeddedChars( font ); | ||
1151 | } | ||
1152 | |||
1153 | BOOL LLViewerTextEditor::getEmbeddedItemToolTipAtPos(S32 pos, LLWString &msg) | ||
1154 | { | ||
1155 | if (pos < getLength()) | ||
1156 | { | ||
1157 | LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem(mWText[pos]); | ||
1158 | if( item ) | ||
1159 | { | ||
1160 | msg = utf8str_to_wstring(item->getName()); | ||
1161 | msg += '\n'; | ||
1162 | msg += utf8str_to_wstring(item->getDescription()); | ||
1163 | return TRUE; | ||
1164 | } | ||
1165 | } | ||
1166 | return FALSE; | ||
1167 | } | ||
1168 | |||
1169 | |||
1170 | BOOL LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos) | ||
1171 | { | ||
1172 | if( pos < getLength()) | ||
1173 | { | ||
1174 | LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItem( mWText[pos] ); | ||
1175 | if( item ) | ||
1176 | { | ||
1177 | BOOL saved = LLEmbeddedItems::getEmbeddedItemSaved( mWText[pos] ); | ||
1178 | return openEmbeddedItem(item, saved); | ||
1179 | } | ||
1180 | } | ||
1181 | return FALSE; | ||
1182 | } | ||
1183 | |||
1184 | |||
1185 | BOOL LLViewerTextEditor::openEmbeddedItem(LLInventoryItem* item, BOOL saved) | ||
1186 | { | ||
1187 | switch( item->getType() ) | ||
1188 | { | ||
1189 | case LLAssetType::AT_TEXTURE: | ||
1190 | openEmbeddedTexture( item ); | ||
1191 | return TRUE; | ||
1192 | |||
1193 | case LLAssetType::AT_SOUND: | ||
1194 | openEmbeddedSound( item ); | ||
1195 | return TRUE; | ||
1196 | |||
1197 | case LLAssetType::AT_NOTECARD: | ||
1198 | openEmbeddedNotecard( item, saved ); | ||
1199 | return TRUE; | ||
1200 | |||
1201 | case LLAssetType::AT_LANDMARK: | ||
1202 | showLandmarkDialog( item ); | ||
1203 | return TRUE; | ||
1204 | |||
1205 | case LLAssetType::AT_LSL_TEXT: | ||
1206 | case LLAssetType::AT_CLOTHING: | ||
1207 | case LLAssetType::AT_OBJECT: | ||
1208 | case LLAssetType::AT_BODYPART: | ||
1209 | case LLAssetType::AT_ANIMATION: | ||
1210 | case LLAssetType::AT_GESTURE: | ||
1211 | showCopyToInvDialog( item ); | ||
1212 | return TRUE; | ||
1213 | default: | ||
1214 | return FALSE; | ||
1215 | } | ||
1216 | } | ||
1217 | |||
1218 | |||
1219 | void LLViewerTextEditor::openEmbeddedTexture( LLInventoryItem* item ) | ||
1220 | { | ||
1221 | // See if we can bring an existing preview to the front | ||
1222 | if( !LLPreview::show( item->getUUID() ) ) | ||
1223 | { | ||
1224 | // There isn't one, so make a new preview | ||
1225 | if(item) | ||
1226 | { | ||
1227 | S32 left, top; | ||
1228 | gFloaterView->getNewFloaterPosition(&left, &top); | ||
1229 | LLRect rect = gSavedSettings.getRect("PreviewTextureRect"); | ||
1230 | rect.translate( left - rect.mLeft, top - rect.mTop ); | ||
1231 | |||
1232 | LLPreviewTexture* preview = new LLPreviewTexture("preview texture", | ||
1233 | rect, | ||
1234 | item->getName(), | ||
1235 | item->getAssetUUID(), | ||
1236 | TRUE); | ||
1237 | preview->setAuxItem( item ); | ||
1238 | preview->setNotecardInfo(mNotecardInventoryID, mObjectID); | ||
1239 | } | ||
1240 | } | ||
1241 | } | ||
1242 | |||
1243 | void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item ) | ||
1244 | { | ||
1245 | // Play sound locally | ||
1246 | LLVector3d lpos_global = gAgent.getPositionGlobal(); | ||
1247 | const F32 SOUND_GAIN = 1.0f; | ||
1248 | if(gAudiop) | ||
1249 | { | ||
1250 | gAudiop->triggerSound( | ||
1251 | item->getAssetUUID(), gAgentID, SOUND_GAIN, lpos_global); | ||
1252 | } | ||
1253 | showCopyToInvDialog( item ); | ||
1254 | } | ||
1255 | |||
1256 | /* | ||
1257 | void LLViewerTextEditor::openEmbeddedLandmark( LLInventoryItem* item ) | ||
1258 | { | ||
1259 | // See if we can bring an existing preview to the front | ||
1260 | if( !LLPreview::show( item->getUUID() ) ) | ||
1261 | { | ||
1262 | // There isn't one, so make a new preview | ||
1263 | S32 left, top; | ||
1264 | gFloaterView->getNewFloaterPosition(&left, &top); | ||
1265 | LLRect rect = gSavedSettings.getRect("PreviewLandmarkRect"); | ||
1266 | rect.translate( left - rect.mLeft, top - rect.mTop ); | ||
1267 | |||
1268 | LLPreviewLandmark* preview = new LLPreviewLandmark( | ||
1269 | "preview landmark", | ||
1270 | rect, | ||
1271 | item->getName(), | ||
1272 | item->getUUID()); | ||
1273 | preview->setAuxItem( item ); | ||
1274 | preview->addCopyToInvButton(); | ||
1275 | preview->open(); | ||
1276 | } | ||
1277 | }*/ | ||
1278 | |||
1279 | void LLViewerTextEditor::openEmbeddedNotecard( LLInventoryItem* item, BOOL saved ) | ||
1280 | { | ||
1281 | if (saved) | ||
1282 | { | ||
1283 | // Copy to inventory | ||
1284 | copyInventory(item); | ||
1285 | } | ||
1286 | else | ||
1287 | { | ||
1288 | LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item); | ||
1289 | gViewerWindow->alertXml("ConfirmNotecardSave", | ||
1290 | LLViewerTextEditor::onNotecardDialog, (void*)info); | ||
1291 | } | ||
1292 | } | ||
1293 | |||
1294 | // static | ||
1295 | void LLViewerTextEditor::onNotecardDialog( S32 option, void* userdata ) | ||
1296 | { | ||
1297 | LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata; | ||
1298 | if( option == 0 ) | ||
1299 | { | ||
1300 | // itemptr is deleted by LLPreview::save | ||
1301 | LLPointer<LLInventoryItem>* itemptr = new LLPointer<LLInventoryItem>(info->mItem); | ||
1302 | LLPreview::save( info->mTextEd->mNotecardInventoryID, itemptr); | ||
1303 | } | ||
1304 | } | ||
1305 | |||
1306 | |||
1307 | void LLViewerTextEditor::showLandmarkDialog( LLInventoryItem* item ) | ||
1308 | { | ||
1309 | LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item); | ||
1310 | gViewerWindow->alertXml("ConfirmLandmarkCopy", | ||
1311 | LLViewerTextEditor::onLandmarkDialog, (void*)info); | ||
1312 | } | ||
1313 | |||
1314 | // static | ||
1315 | void LLViewerTextEditor::onLandmarkDialog( S32 option, void* userdata ) | ||
1316 | { | ||
1317 | LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata; | ||
1318 | if( option == 0 ) | ||
1319 | { | ||
1320 | // Copy to inventory | ||
1321 | info->mTextEd->copyInventory(info->mItem); | ||
1322 | /* | ||
1323 | * XXXPAM | ||
1324 | * | ||
1325 | * Yes, this is broken. We don't show the map yet. | ||
1326 | * | ||
1327 | LLInventoryItem* orig_item = (LLInventoryItem*)userdata; | ||
1328 | |||
1329 | // Copy to inventory | ||
1330 | LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem; | ||
1331 | cloneInventoryItemToViewer(orig_item, new_item); | ||
1332 | U32 flags = new_item->getFlags(); | ||
1333 | flags &= ~LLInventoryItem::II_FLAGS_LANDMARK_VISITED; | ||
1334 | new_item->setFlags(flags); | ||
1335 | new_item->updateServer(TRUE); | ||
1336 | gInventory.updateItem(new_item); | ||
1337 | gInventory.notifyObservers(); | ||
1338 | |||
1339 | LLInventoryView* view = LLInventoryView::getActiveInventory(); | ||
1340 | if(view) | ||
1341 | { | ||
1342 | view->getPanel()->setSelection(new_item->getUUID(), TAKE_FOCUS_NO); | ||
1343 | } | ||
1344 | |||
1345 | if( (0 == option) && gFloaterWorldMap ) | ||
1346 | { | ||
1347 | // Note: there's a minor race condition here. | ||
1348 | // If the user immediately tries to teleport to the landmark, the dataserver may | ||
1349 | // not yet know that the user has the landmark in his inventory and so may | ||
1350 | // disallow the teleport. However, the user will need to be pretty fast to make | ||
1351 | // this happen, and, if it does, they haven't lost anything. Once the dataserver | ||
1352 | // knows about the new item, the user will be able to teleport to it successfully. | ||
1353 | gFloaterWorldMap->trackLandmark(new_item->getUUID()); | ||
1354 | LLFloaterWorldMap::show(NULL, TRUE); | ||
1355 | }*/ | ||
1356 | } | ||
1357 | delete info; | ||
1358 | } | ||
1359 | |||
1360 | |||
1361 | void LLViewerTextEditor::showCopyToInvDialog( LLInventoryItem* item ) | ||
1362 | { | ||
1363 | LLNotecardCopyInfo *info = new LLNotecardCopyInfo(this, item); | ||
1364 | gViewerWindow->alertXml( "ConfirmItemCopy", | ||
1365 | LLViewerTextEditor::onCopyToInvDialog, (void*)info); | ||
1366 | } | ||
1367 | |||
1368 | // static | ||
1369 | void LLViewerTextEditor::onCopyToInvDialog( S32 option, void* userdata ) | ||
1370 | { | ||
1371 | LLNotecardCopyInfo *info = (LLNotecardCopyInfo *)userdata; | ||
1372 | if( 0 == option ) | ||
1373 | { | ||
1374 | info->mTextEd->copyInventory(info->mItem); | ||
1375 | } | ||
1376 | delete info; | ||
1377 | } | ||
1378 | |||
1379 | |||
1380 | |||
1381 | // Returns change in number of characters in mWText | ||
1382 | S32 LLViewerTextEditor::insertEmbeddedItem( S32 pos, LLInventoryItem* item ) | ||
1383 | { | ||
1384 | return execute( new LLTextCmdInsertEmbeddedItem( pos, item ) ); | ||
1385 | } | ||
1386 | |||
1387 | bool LLViewerTextEditor::importStream(std::istream& str) | ||
1388 | { | ||
1389 | LLNotecard nc(MAX_NOTECARD_SIZE); | ||
1390 | bool success = nc.importStream(str); | ||
1391 | if (success) | ||
1392 | { | ||
1393 | const std::vector<LLPointer<LLInventoryItem> >& items = nc.getItems(); | ||
1394 | mEmbeddedItemList->addItems(items); | ||
1395 | // Actually set the text | ||
1396 | if (mAllowEmbeddedItems) | ||
1397 | { | ||
1398 | if (nc.getVersion() == 1) | ||
1399 | setASCIIEmbeddedText( nc.getText() ); | ||
1400 | else | ||
1401 | setEmbeddedText( nc.getText() ); | ||
1402 | } | ||
1403 | else | ||
1404 | { | ||
1405 | setText( nc.getText() ); | ||
1406 | } | ||
1407 | } | ||
1408 | return success; | ||
1409 | } | ||
1410 | |||
1411 | void LLViewerTextEditor::copyInventory(LLInventoryItem* item) | ||
1412 | { | ||
1413 | copy_inventory_from_notecard(mObjectID, | ||
1414 | mNotecardInventoryID, | ||
1415 | item); | ||
1416 | } | ||
1417 | |||
1418 | //////////////////////////////////////////////////////////////////////////// | ||
1419 | |||
1420 | BOOL LLViewerTextEditor::importBuffer( const LLString& buffer ) | ||
1421 | { | ||
1422 | LLMemoryStream str((U8*)buffer.c_str(), buffer.length()); | ||
1423 | return importStream(str); | ||
1424 | } | ||
1425 | |||
1426 | BOOL LLViewerTextEditor::exportBuffer( LLString& buffer ) | ||
1427 | { | ||
1428 | LLNotecard nc(MAX_NOTECARD_SIZE); | ||
1429 | |||
1430 | std::vector<LLPointer<LLInventoryItem> > embedded_items; | ||
1431 | mEmbeddedItemList->getEmbeddedItemList(embedded_items); | ||
1432 | |||
1433 | nc.setItems(embedded_items); | ||
1434 | nc.setText(getEmbeddedText()); | ||
1435 | |||
1436 | std::stringstream out_stream; | ||
1437 | nc.exportStream(out_stream); | ||
1438 | |||
1439 | buffer = out_stream.str(); | ||
1440 | |||
1441 | return TRUE; | ||
1442 | } | ||
1443 | |||
1444 | LLView* LLViewerTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) | ||
1445 | { | ||
1446 | LLString name("text_editor"); | ||
1447 | node->getAttributeString("name", name); | ||
1448 | |||
1449 | LLRect rect; | ||
1450 | createRect(node, rect, parent, LLRect()); | ||
1451 | |||
1452 | U32 max_text_length = 255; | ||
1453 | node->getAttributeU32("max_length", max_text_length); | ||
1454 | |||
1455 | BOOL allow_embedded_items = FALSE; | ||
1456 | node->getAttributeBOOL("embedded_items", allow_embedded_items); | ||
1457 | |||
1458 | LLFontGL* font = LLView::selectFont(node); | ||
1459 | |||
1460 | LLString text = node->getValue(); | ||
1461 | |||
1462 | if (text.size() > max_text_length) | ||
1463 | { | ||
1464 | // Erase everything from max_text_length on. | ||
1465 | text.erase(max_text_length); | ||
1466 | } | ||
1467 | |||
1468 | LLViewerTextEditor* text_editor = new LLViewerTextEditor(name, | ||
1469 | rect, | ||
1470 | max_text_length, | ||
1471 | text, | ||
1472 | font, | ||
1473 | allow_embedded_items); | ||
1474 | |||
1475 | BOOL ignore_tabs = text_editor->mTabToNextField; | ||
1476 | node->getAttributeBOOL("ignore_tab", ignore_tabs); | ||
1477 | |||
1478 | text_editor->setTabToNextField(ignore_tabs); | ||
1479 | |||
1480 | |||
1481 | text_editor->setTextEditorParameters(node); | ||
1482 | |||
1483 | BOOL hide_scrollbar = FALSE; | ||
1484 | node->getAttributeBOOL("hide_scrollbar",hide_scrollbar); | ||
1485 | text_editor->setHideScrollbarForShortDocs(hide_scrollbar); | ||
1486 | |||
1487 | text_editor->initFromXML(node, parent); | ||
1488 | |||
1489 | return text_editor; | ||
1490 | } | ||