summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorTodd Stinson <stinson@lindenlab.com>2012-07-27 12:53:54 -0700
committerTodd Stinson <stinson@lindenlab.com>2012-07-27 12:53:54 -0700
commit3e038cd71b3f3bc74d206267e451773fb963d258 (patch)
tree63cdafa4fdcce40bf64ed65ddbf18519bf6b566b /indra/llui
parentf82d0b171964a0b24ab0eca64febc0c1e3821138 (diff)
parent364566924188c7aed5d391bf9a226fc4779ba020 (diff)
Pull and merge from ssh://hg@bitbucket.org/lindenlab/viewer-release.
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt15
-rw-r--r--indra/llui/llcontainerview.cpp18
-rw-r--r--indra/llui/llfolderview.cpp1
-rw-r--r--indra/llui/llfolderviewitem.cpp92
-rw-r--r--indra/llui/lllineeditor.cpp306
-rw-r--r--indra/llui/lllineeditor.h33
-rw-r--r--indra/llui/llmenugl.cpp10
-rw-r--r--indra/llui/llmenugl.h6
-rw-r--r--indra/llui/llmultifloater.cpp2
-rw-r--r--indra/llui/llnotifications.cpp11
-rw-r--r--indra/llui/llnotifications.h1
-rw-r--r--indra/llui/llnotificationtemplate.h19
-rw-r--r--indra/llui/llscrollcontainer.cpp3
-rw-r--r--indra/llui/llsdparam.cpp341
-rw-r--r--indra/llui/llsdparam.h126
-rw-r--r--indra/llui/llspellcheck.cpp505
-rw-r--r--indra/llui/llspellcheck.h93
-rw-r--r--indra/llui/llspellcheckmenuhandler.h46
-rw-r--r--indra/llui/llstatbar.cpp2
-rw-r--r--indra/llui/lltextbase.cpp274
-rw-r--r--indra/llui/lltextbase.h31
-rw-r--r--indra/llui/lltextbox.cpp2
-rw-r--r--indra/llui/lltexteditor.cpp38
-rw-r--r--indra/llui/lltrans.cpp295
-rw-r--r--indra/llui/lltrans.h133
-rw-r--r--indra/llui/llui.cpp6
-rw-r--r--indra/llui/lluicolor.cpp87
-rw-r--r--indra/llui/lluicolor.h71
-rw-r--r--indra/llui/llviewmodel.h1
-rw-r--r--indra/llui/llxuiparser.cpp1786
-rw-r--r--indra/llui/llxuiparser.h244
-rw-r--r--indra/llui/tests/llurlentry_stub.cpp24
-rw-r--r--indra/llui/tests/llurlentry_test.cpp15
-rw-r--r--indra/llui/tests/llurlmatch_test.cpp36
34 files changed, 4018 insertions, 655 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index a9ad0a3c0b..bc225593d8 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -12,7 +12,6 @@ include(LLRender)
include(LLWindow)
include(LLVFS)
include(LLXML)
-include(LLXUIXML)
include_directories(
${LLCOMMON_INCLUDE_DIRS}
@@ -24,7 +23,7 @@ include_directories(
${LLWINDOW_INCLUDE_DIRS}
${LLVFS_INCLUDE_DIRS}
${LLXML_INCLUDE_DIRS}
- ${LLXUIXML_INCLUDE_DIRS}
+ ${LIBS_PREBUILD_DIR}/include/hunspell
)
set(llui_SOURCE_FILES
@@ -87,10 +86,10 @@ set(llui_SOURCE_FILES
llscrolllistcolumn.cpp
llscrolllistctrl.cpp
llscrolllistitem.cpp
- llsdparam.cpp
llsearcheditor.cpp
llslider.cpp
llsliderctrl.cpp
+ llspellcheck.cpp
llspinctrl.cpp
llstatbar.cpp
llstatgraph.cpp
@@ -104,11 +103,13 @@ set(llui_SOURCE_FILES
lltextutil.cpp
lltextvalidate.cpp
lltimectrl.cpp
+ lltrans.cpp
lltransutil.cpp
lltoggleablemenu.cpp
lltoolbar.cpp
lltooltip.cpp
llui.cpp
+ lluicolor.cpp
lluicolortable.cpp
lluictrl.cpp
lluictrlfactory.cpp
@@ -125,6 +126,7 @@ set(llui_SOURCE_FILES
llview.cpp
llviewquery.cpp
llwindowshade.cpp
+ llxuiparser.cpp
)
set(llui_HEADER_FILES
@@ -197,9 +199,10 @@ set(llui_HEADER_FILES
llscrolllistcolumn.h
llscrolllistctrl.h
llscrolllistitem.h
- llsdparam.h
llsliderctrl.h
llslider.h
+ llspellcheck.h
+ llspellcheckmenuhandler.h
llspinctrl.h
llstatbar.h
llstatgraph.h
@@ -216,6 +219,7 @@ set(llui_HEADER_FILES
lltoggleablemenu.h
lltoolbar.h
lltooltip.h
+ lltrans.h
lltransutil.h
lluicolortable.h
lluiconstants.h
@@ -223,6 +227,7 @@ set(llui_HEADER_FILES
lluictrl.h
lluifwd.h
llui.h
+ lluicolor.h
lluiimage.h
lluistring.h
llundo.h
@@ -236,6 +241,7 @@ set(llui_HEADER_FILES
llview.h
llviewquery.h
llwindowshade.h
+ llxuiparser.h
)
set_source_files_properties(${llui_HEADER_FILES}
@@ -266,6 +272,7 @@ target_link_libraries(llui
${LLXUIXML_LIBRARIES}
${LLXML_LIBRARIES}
${LLMATH_LIBRARIES}
+ ${HUNSPELL_LIBRARY}
${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender
)
diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp
index e01e331acf..e08ccb0b78 100644
--- a/indra/llui/llcontainerview.cpp
+++ b/indra/llui/llcontainerview.cpp
@@ -196,24 +196,24 @@ void LLContainerView::arrange(S32 width, S32 height, BOOL called_from_parent)
if (total_height < height)
total_height = height;
+ LLRect my_rect = getRect();
if (followsTop())
{
- // HACK: casting away const. Should use setRect or some helper function instead.
- const_cast<LLRect&>(getRect()).mBottom = getRect().mTop - total_height;
+ my_rect.mBottom = my_rect.mTop - total_height;
}
else
{
- // HACK: casting away const. Should use setRect or some helper function instead.
- const_cast<LLRect&>(getRect()).mTop = getRect().mBottom + total_height;
+ my_rect.mTop = my_rect.mBottom + total_height;
}
- // HACK: casting away const. Should use setRect or some helper function instead.
- const_cast<LLRect&>(getRect()).mRight = getRect().mLeft + width;
+
+ my_rect.mRight = my_rect.mLeft + width;
+ setRect(my_rect);
top = total_height;
if (mShowLabel)
- {
- top -= 20;
- }
+ {
+ top -= 20;
+ }
bottom = top;
diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp
index d8ed09fbe6..e09026fc33 100644
--- a/indra/llui/llfolderview.cpp
+++ b/indra/llui/llfolderview.cpp
@@ -360,7 +360,6 @@ void LLFolderView::reshape(S32 width, S32 height, BOOL called_from_parent)
{
width = scroll_rect.getWidth();
}
-
LLView::reshape(width, height, called_from_parent);
mReshapeSignal(mSelectedItems, FALSE);
}
diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp
index a356d587f9..cd8d8bafbc 100644
--- a/indra/llui/llfolderviewitem.cpp
+++ b/indra/llui/llfolderviewitem.cpp
@@ -209,7 +209,7 @@ BOOL LLFolderViewItem::passedFilter(S32 filter_generation)
}
void LLFolderViewItem::refresh()
-{
+{
LLFolderViewModelItem& vmi = *getViewModelItem();
mLabel = vmi.getDisplayName();
@@ -219,11 +219,11 @@ void LLFolderViewItem::refresh()
mIconOpen = vmi.getIconOpen();
mIconOverlay = vmi.getIconOverlay();
- if (mRoot->useLabelSuffix())
- {
+ if (mRoot->useLabelSuffix())
+ {
mLabelStyle = vmi.getLabelStyle();
mLabelSuffix = vmi.getLabelSuffix();
- }
+}
//TODO RN: make sure this logic still fires
//std::string searchable_label(mLabel);
@@ -253,7 +253,7 @@ void LLFolderViewItem::arrangeAndSet(BOOL set_selection,
LLFolderView* root = getRoot();
if (getParentFolder())
{
- getParentFolder()->requestArrange();
+ getParentFolder()->requestArrange();
}
if(set_selection)
{
@@ -370,7 +370,7 @@ BOOL LLFolderViewItem::isMovable()
BOOL LLFolderViewItem::isRemovable()
{
return getViewModelItem()->isItemRemovable();
-}
+ }
void LLFolderViewItem::destroyView()
{
@@ -394,7 +394,7 @@ BOOL LLFolderViewItem::remove()
return FALSE;
}
return getViewModelItem()->removeItem();
-}
+ }
// Build an appropriate context menu for the item.
void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags)
@@ -481,24 +481,24 @@ BOOL LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask )
if( hasMouseCapture() && isMovable() )
{
- LLFolderView* root = getRoot();
+ LLFolderView* root = getRoot();
if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > drag_and_drop_threshold() * drag_and_drop_threshold()
&& root->getCurSelectedItem()
&& root->startDrag())
- {
- // RN: when starting drag and drop, clear out last auto-open
- root->autoOpenTest(NULL);
- root->setShowSelectionContext(TRUE);
+ {
+ // RN: when starting drag and drop, clear out last auto-open
+ root->autoOpenTest(NULL);
+ root->setShowSelectionContext(TRUE);
- // Release keyboard focus, so that if stuff is dropped into the
- // world, pressing the delete key won't blow away the inventory
- // item.
- gFocusMgr.setKeyboardFocus(NULL);
+ // Release keyboard focus, so that if stuff is dropped into the
+ // world, pressing the delete key won't blow away the inventory
+ // item.
+ gFocusMgr.setKeyboardFocus(NULL);
getWindow()->setCursor(UI_CURSOR_ARROW);
return TRUE;
- }
+ }
else
{
getWindow()->setCursor(UI_CURSOR_NOLOCKED);
@@ -599,17 +599,17 @@ BOOL LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
void LLFolderViewItem::draw()
{
- static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
- static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
- static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
+ static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE);
+ static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE);
+ static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE);
static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE);
- static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
- static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
- static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
- static LLUIColor sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE);
- static LLUIColor sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE);
+ static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE);
+ static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE);
+ static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE);
+ static LLUIColor sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE);
+ static LLUIColor sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE);
static LLUIColor sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE);
- static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
+ static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE);
const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>();
const S32 TOP_PAD = default_params.item_top_pad;
@@ -806,7 +806,7 @@ const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) c
LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void )
{
return getRoot()->getFolderViewModel();
-}
+ }
///----------------------------------------------------------------------------
@@ -1495,11 +1495,27 @@ BOOL LLFolderViewFolder::addItem(LLFolderViewItem* item)
item->getViewModelItem()->dirtyFilter();
+ // XXX stinson TODO : handle the creation date
+#if 0
+ // Update the folder creation date if the child is newer than our current date
+ setCreationDate(llmax<time_t>(mCreationDate, item->getCreationDate()));
+#endif
+
// Handle sorting
requestArrange();
requestSort();
getViewModelItem()->addChild(item->getViewModelItem());
+ // XXX stinson TODO : handle the creation date
+#if 0
+ // Traverse parent folders and update creation date and resort, if necessary
+ LLFolderViewFolder* parentp = getParentFolder();
+ while (parentp)
+ {
+ // Update the folder creation date if the child is newer than our current date
+ parentp->setCreationDate(llmax<time_t>(parentp->mCreationDate, item->getCreationDate()));
+ }
+#endif
//TODO RN - make sort bubble up as long as parent Folder doesn't have anything matching sort criteria
//// Traverse parent folders and update creation date and resort, if necessary
@@ -1545,14 +1561,14 @@ void LLFolderViewFolder::requestArrange()
{
//if ( mLastArrangeGeneration != -1)
{
- mLastArrangeGeneration = -1;
- // flag all items up to root
- if (mParentFolder)
- {
- mParentFolder->requestArrange();
+ mLastArrangeGeneration = -1;
+ // flag all items up to root
+ if (mParentFolder)
+ {
+ mParentFolder->requestArrange();
+ }
}
}
-}
void LLFolderViewFolder::toggleOpen()
{
@@ -1569,12 +1585,12 @@ void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType r
{
BOOL was_open = isOpen();
mIsOpen = openitem;
- if(!was_open && openitem)
- {
+ if(!was_open && openitem)
+ {
getViewModelItem()->openItem();
- }
- else if(was_open && !openitem)
- {
+ }
+ else if(was_open && !openitem)
+ {
getViewModelItem()->closeItem();
}
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index d0fbf4b913..48d49af588 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -45,6 +45,7 @@
#include "llkeyboard.h"
#include "llrect.h"
#include "llresmgr.h"
+#include "llspellcheck.h"
#include "llstring.h"
#include "llwindow.h"
#include "llui.h"
@@ -65,6 +66,7 @@ const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing
const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing
const F32 AUTO_SCROLL_TIME = 0.05f;
const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval?
+const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on
const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET
@@ -88,6 +90,7 @@ LLLineEditor::Params::Params()
background_image_focused("background_image_focused"),
select_on_focus("select_on_focus", false),
revert_on_esc("revert_on_esc", true),
+ spellcheck("spellcheck", false),
commit_on_focus_lost("commit_on_focus_lost", true),
ignore_tab("ignore_tab", true),
is_password("is_password", false),
@@ -134,6 +137,9 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mIgnoreArrowKeys( FALSE ),
mIgnoreTab( p.ignore_tab ),
mDrawAsterixes( p.is_password ),
+ mSpellCheck( p.spellcheck ),
+ mSpellCheckStart(-1),
+ mSpellCheckEnd(-1),
mSelectAllonFocusReceived( p.select_on_focus ),
mSelectAllonCommit( TRUE ),
mPassDelete(FALSE),
@@ -151,7 +157,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mHighlightColor(p.highlight_color()),
mPreeditBgColor(p.preedit_bg_color()),
mGLFont(p.font),
- mContextMenuHandle()
+ mContextMenuHandle(),
+ mAutoreplaceCallback()
{
llassert( mMaxLengthBytes > 0 );
@@ -177,6 +184,12 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
updateTextPadding();
setCursor(mText.length());
+ if (mSpellCheck)
+ {
+ LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this));
+ }
+ mSpellCheckTimer.reset();
+
setPrevalidateInput(p.prevalidate_input_callback());
setPrevalidate(p.prevalidate_callback());
@@ -195,7 +208,6 @@ LLLineEditor::~LLLineEditor()
gFocusMgr.releaseFocusIfNeeded( this );
}
-
void LLLineEditor::onFocusReceived()
{
gEditMenuHandler = this;
@@ -519,6 +531,99 @@ void LLLineEditor::selectAll()
updatePrimary();
}
+bool LLLineEditor::getSpellCheck() const
+{
+ return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
+}
+
+const std::string& LLLineEditor::getSuggestion(U32 index) const
+{
+ return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null;
+}
+
+U32 LLLineEditor::getSuggestionCount() const
+{
+ return mSuggestionList.size();
+}
+
+void LLLineEditor::replaceWithSuggestion(U32 index)
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
+ {
+ deselect();
+
+ // Delete the misspelled word
+ mText.erase(it->first, it->second - it->first);
+
+ // Insert the suggestion in its place
+ LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
+ mText.insert(it->first, suggestion);
+ setCursor(it->first + (S32)suggestion.length());
+
+ break;
+ }
+ }
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
+
+void LLLineEditor::addToDictionary()
+{
+ if (canAddToDictionary())
+ {
+ LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLLineEditor::canAddToDictionary() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
+}
+
+void LLLineEditor::addToIgnore()
+{
+ if (canAddToIgnore())
+ {
+ LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLLineEditor::canAddToIgnore() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
+}
+
+std::string LLLineEditor::getMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first));
+ }
+ }
+ return LLStringUtil::null;
+}
+
+bool LLLineEditor::isMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void LLLineEditor::onSpellCheckSettingsChange()
+{
+ // Recheck the spelling on every change
+ mMisspellRanges.clear();
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
{
@@ -866,6 +971,12 @@ void LLLineEditor::addChar(const llwchar uni_char)
LLUI::reportBadKeystroke();
}
+ if (!mReadOnly && mAutoreplaceCallback != NULL)
+ {
+ // call callback
+ mAutoreplaceCallback(mText, mCursorPos);
+ }
+
getWindow()->hideCursorUntilMouseMove();
}
@@ -1058,9 +1169,8 @@ void LLLineEditor::cut()
LLUI::reportBadKeystroke();
}
else
- if( mKeystrokeCallback )
{
- mKeystrokeCallback( this );
+ onKeystroke();
}
}
}
@@ -1187,9 +1297,8 @@ void LLLineEditor::pasteHelper(bool is_primary)
LLUI::reportBadKeystroke();
}
else
- if( mKeystrokeCallback )
{
- mKeystrokeCallback( this );
+ onKeystroke();
}
}
}
@@ -1442,9 +1551,10 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask )
// Notify owner if requested
if (!need_to_rollback && handled)
{
- if (mKeystrokeCallback)
+ onKeystroke();
+ if ( (!selection_modified) && (KEY_BACKSPACE == key) )
{
- mKeystrokeCallback(this);
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
}
}
@@ -1497,12 +1607,11 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
// Notify owner if requested
if( !need_to_rollback && handled )
{
- if( mKeystrokeCallback )
- {
- // HACK! The only usage of this callback doesn't do anything with the character.
- // We'll have to do something about this if something ever changes! - Doug
- mKeystrokeCallback( this );
- }
+ // HACK! The only usage of this callback doesn't do anything with the character.
+ // We'll have to do something about this if something ever changes! - Doug
+ onKeystroke();
+
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
}
return handled;
@@ -1531,9 +1640,7 @@ void LLLineEditor::doDelete()
if (!prevalidateInput(text_to_delete))
{
- if( mKeystrokeCallback )
- mKeystrokeCallback( this );
-
+ onKeystroke();
return;
}
setCursor(getCursor() + 1);
@@ -1549,10 +1656,9 @@ void LLLineEditor::doDelete()
}
else
{
- if( mKeystrokeCallback )
- {
- mKeystrokeCallback( this );
- }
+ onKeystroke();
+
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
}
}
@@ -1624,6 +1730,10 @@ void LLLineEditor::draw()
background.stretch( -mBorderThickness );
S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2;
+ if (mSpellCheck)
+ {
+ lineeditor_v_pad += 1;
+ }
drawBackground();
@@ -1698,14 +1808,14 @@ void LLLineEditor::draw()
{
S32 select_left;
S32 select_right;
- if( mSelectionStart < getCursor() )
+ if (mSelectionStart < mSelectionEnd)
{
select_left = mSelectionStart;
- select_right = getCursor();
+ select_right = mSelectionEnd;
}
else
{
- select_left = getCursor();
+ select_left = mSelectionEnd;
select_right = mSelectionStart;
}
@@ -1749,7 +1859,7 @@ void LLLineEditor::draw()
if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) )
{
// unselected, right side
- mGLFont->render(
+ rendered_text += mGLFont->render(
mText, mScrollHPos + rendered_text,
rendered_pixels_right, text_bottom,
text_color,
@@ -1763,7 +1873,7 @@ void LLLineEditor::draw()
}
else
{
- mGLFont->render(
+ rendered_text = mGLFont->render(
mText, mScrollHPos,
rendered_pixels_right, text_bottom,
text_color,
@@ -1778,6 +1888,101 @@ void LLLineEditor::draw()
mBorder->setVisible(FALSE); // no more programmatic art.
#endif
+ if ( (getSpellCheck()) && (mText.length() > 2) )
+ {
+ // Calculate start and end indices for the first and last visible word
+ U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text);
+
+ if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) )
+ {
+ const LLWString& text = mText.getWString().substr(start, end);
+
+ // Find the start of the first word
+ U32 word_start = 0, word_end = 0;
+ while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) )
+ {
+ word_start++;
+ }
+
+ // Iterate over all words in the text block and check them one by one
+ mMisspellRanges.clear();
+ while (word_start < text.length())
+ {
+ // Find the end of the current word (special case handling for "'" when it's used as a contraction)
+ word_end = word_start + 1;
+ while ( (word_end < text.length()) &&
+ ((LLWStringUtil::isPartOfWord(text[word_end])) ||
+ ((L'\'' == text[word_end]) && (word_end + 1 < text.length()) &&
+ (LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) )
+ {
+ word_end++;
+ }
+ if (word_end > text.length())
+ {
+ break;
+ }
+
+ // Don't process words shorter than 3 characters
+ std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start));
+ if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) )
+ {
+ mMisspellRanges.push_back(std::pair<U32, U32>(start + word_start, start + word_end));
+ }
+
+ // Find the start of the next word
+ word_start = word_end + 1;
+ while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) )
+ {
+ word_start++;
+ }
+ }
+
+ mSpellCheckStart = start;
+ mSpellCheckEnd = end;
+ }
+
+ // Draw squiggly lines under any (visible) misspelled words
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ // Skip over words that aren't (partially) visible
+ if ( ((it->first < start) && (it->second < start)) || (it->first > end) )
+ {
+ continue;
+ }
+
+ // Skip the current word if the user is still busy editing it
+ if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
+ {
+ continue;
+ }
+
+ S32 pxWidth = getRect().getWidth();
+ S32 pxStart = findPixelNearestPos(it->first - getCursor());
+ if (pxStart > pxWidth)
+ {
+ continue;
+ }
+ S32 pxEnd = findPixelNearestPos(it->second - getCursor());
+ if (pxEnd > pxWidth)
+ {
+ pxEnd = pxWidth;
+ }
+
+ S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight());
+
+ gGL.color4ub(255, 0, 0, 200);
+ while (pxStart + 1 < pxEnd)
+ {
+ gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2);
+ if (pxStart + 3 < pxEnd)
+ {
+ gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1);
+ }
+ pxStart += 4;
+ }
+ }
+ }
+
// If we're editing...
if( hasFocus())
{
@@ -2109,6 +2314,15 @@ void LLLineEditor::setSelectAllonFocusReceived(BOOL b)
mSelectAllonFocusReceived = b;
}
+void LLLineEditor::onKeystroke()
+{
+ if (mKeystrokeCallback)
+ {
+ mKeystrokeCallback(this);
+ }
+
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data)
{
@@ -2231,10 +2445,9 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string,
// Update of the preedit should be caused by some key strokes.
mKeystrokeTimer.reset();
- if( mKeystrokeCallback )
- {
- mKeystrokeCallback( this );
- }
+ onKeystroke();
+
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
BOOL LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const
@@ -2386,7 +2599,38 @@ void LLLineEditor::showContextMenu(S32 x, S32 y)
S32 screen_x, screen_y;
localPointToScreen(x, y, &screen_x, &screen_y);
- menu->show(screen_x, screen_y);
+
+ setCursorAtLocalPos(x);
+ if (hasSelection())
+ {
+ if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
+ {
+ deselect();
+ }
+ else
+ {
+ setCursor(llmax(mSelectionStart, mSelectionEnd));
+ }
+ }
+
+ bool use_spellcheck = getSpellCheck(), is_misspelled = false;
+ if (use_spellcheck)
+ {
+ mSuggestionList.clear();
+
+ // If the cursor is on a misspelled word, retrieve suggestions for it
+ std::string misspelled_word = getMisspelledWord(mCursorPos);
+ if ((is_misspelled = !misspelled_word.empty()) == true)
+ {
+ LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList);
+ }
+ }
+
+ menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty()));
+ menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled));
+ menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled));
+ menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled));
+ menu->show(screen_x, screen_y, this);
}
}
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index 2518dbe3c7..71dd53f608 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -40,6 +40,7 @@
#include "llframetimer.h"
#include "lleditmenuhandler.h"
+#include "llspellcheckmenuhandler.h"
#include "lluictrl.h"
#include "lluiimage.h"
#include "lluistring.h"
@@ -54,7 +55,7 @@ class LLButton;
class LLContextMenu;
class LLLineEditor
-: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor
+: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor, public LLSpellCheckMenuHandler
{
public:
@@ -86,6 +87,7 @@ public:
Optional<bool> select_on_focus,
revert_on_esc,
+ spellcheck,
commit_on_focus_lost,
ignore_tab,
is_password;
@@ -146,6 +148,24 @@ public:
virtual void deselect();
virtual BOOL canDeselect() const;
+ // LLSpellCheckMenuHandler overrides
+ /*virtual*/ bool getSpellCheck() const;
+
+ /*virtual*/ const std::string& getSuggestion(U32 index) const;
+ /*virtual*/ U32 getSuggestionCount() const;
+ /*virtual*/ void replaceWithSuggestion(U32 index);
+
+ /*virtual*/ void addToDictionary();
+ /*virtual*/ bool canAddToDictionary() const;
+
+ /*virtual*/ void addToIgnore();
+ /*virtual*/ bool canAddToIgnore() const;
+
+ // Spell checking helper functions
+ std::string getMisspelledWord(U32 pos) const;
+ bool isMisspelledWord(U32 pos) const;
+ void onSpellCheckSettingsChange();
+
// view overrides
virtual void draw();
virtual void reshape(S32 width,S32 height,BOOL called_from_parent=TRUE);
@@ -169,6 +189,9 @@ public:
virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text );
virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text );
+ typedef boost::function<void(LLUIString&, S32&)> autoreplace_callback_t;
+ autoreplace_callback_t mAutoreplaceCallback;
+ void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; }
void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; }
const std::string& getLabel() { return mLabel.getString(); }
@@ -223,6 +246,7 @@ public:
void setSelectAllonFocusReceived(BOOL b);
void setSelectAllonCommit(BOOL b) { mSelectAllonCommit = b; }
+ void onKeystroke();
typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t;
void setKeystrokeCallback(callback_t callback, void* user_data);
@@ -322,6 +346,13 @@ protected:
S32 mLastSelectionStart;
S32 mLastSelectionEnd;
+ bool mSpellCheck;
+ S32 mSpellCheckStart;
+ S32 mSpellCheckEnd;
+ LLTimer mSpellCheckTimer;
+ std::list<std::pair<U32, U32> > mMisspellRanges;
+ std::vector<std::string> mSuggestionList;
+
LLTextValidate::validate_func_t mPrevalidateFunc;
LLTextValidate::validate_func_t mPrevalidateInputFunc;
diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp
index 32e5cdd556..5182a8cea1 100644
--- a/indra/llui/llmenugl.cpp
+++ b/indra/llui/llmenugl.cpp
@@ -3854,7 +3854,7 @@ void LLContextMenu::setVisible(BOOL visible)
}
// Takes cursor position in screen space?
-void LLContextMenu::show(S32 x, S32 y)
+void LLContextMenu::show(S32 x, S32 y, LLView* spawning_view)
{
if (getChildList()->empty())
{
@@ -3908,6 +3908,14 @@ void LLContextMenu::show(S32 x, S32 y)
setRect(rect);
arrange();
+ if (spawning_view)
+ {
+ mSpawningViewHandle = spawning_view->getHandle();
+ }
+ else
+ {
+ mSpawningViewHandle.markDead();
+ }
LLView::setVisible(TRUE);
}
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index c6ee5434b0..a9de3ef937 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -668,7 +668,7 @@ public:
// can't set visibility directly, must call show or hide
virtual void setVisible (BOOL visible);
- virtual void show (S32 x, S32 y);
+ virtual void show (S32 x, S32 y, LLView* spawning_view = NULL);
virtual void hide ();
virtual BOOL handleHover ( S32 x, S32 y, MASK mask );
@@ -681,10 +681,14 @@ public:
LLHandle<LLContextMenu> getHandle() { return getDerivedHandle<LLContextMenu>(); }
+ LLView* getSpawningView() const { return mSpawningViewHandle.get(); }
+ void setSpawningView(LLHandle<LLView> spawning_view) { mSpawningViewHandle = spawning_view; }
+
protected:
BOOL mHoveredAnyItem;
LLMenuItemGL* mHoverItem;
LLRootHandle<LLContextMenu> mHandle;
+ LLHandle<LLView> mSpawningViewHandle;
};
diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp
index 6f0e691f10..e80799b16d 100644
--- a/indra/llui/llmultifloater.cpp
+++ b/indra/llui/llmultifloater.cpp
@@ -349,7 +349,7 @@ void LLMultiFloater::setVisible(BOOL visible)
BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask)
{
- if (key == 'W' && mask == (MASK_CONTROL|MASK_SHIFT))
+ if (key == 'W' && mask == MASK_CONTROL)
{
LLFloater* floater = getActiveFloater();
// is user closeable and is system closeable
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 7e1e2c3c9b..ed59c607a7 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -398,6 +398,7 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par
: mName(p.name),
mType(p.type),
mMessage(p.value),
+ mFooter(p.footer.value),
mLabel(p.label),
mIcon(p.icon),
mURL(p.url.value),
@@ -852,6 +853,16 @@ std::string LLNotification::getMessage() const
return message;
}
+std::string LLNotification::getFooter() const
+{
+ if (!mTemplatep)
+ return std::string();
+
+ std::string footer = mTemplatep->mFooter;
+ LLStringUtil::format(footer, mSubstitutions);
+ return footer;
+}
+
std::string LLNotification::getLabel() const
{
std::string label = mTemplatep->mLabel;
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 12479f0788..19b30b8973 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -514,6 +514,7 @@ public:
std::string getType() const;
std::string getMessage() const;
+ std::string getFooter() const;
std::string getLabel() const;
std::string getURL() const;
S32 getURLOption() const;
diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h
index ca9c4294c1..c9e9d0f32a 100644
--- a/indra/llui/llnotificationtemplate.h
+++ b/indra/llui/llnotificationtemplate.h
@@ -181,6 +181,17 @@ struct LLNotificationTemplate
{}
};
+ struct Footer : public LLInitParam::Block<Footer>
+ {
+ Mandatory<std::string> value;
+
+ Footer()
+ : value("value")
+ {
+ addSynonym(value, "");
+ }
+ };
+
struct Params : public LLInitParam::Block<Params>
{
Mandatory<std::string> name;
@@ -201,7 +212,8 @@ struct LLNotificationTemplate
Optional<FormRef> form_ref;
Optional<ENotificationPriority,
NotificationPriorityValues> priority;
- Multiple<Tag> tags;
+ Multiple<Tag> tags;
+ Optional<Footer> footer;
Params()
@@ -222,7 +234,8 @@ struct LLNotificationTemplate
url("url"),
unique("unique"),
form_ref(""),
- tags("tag")
+ tags("tag"),
+ footer("footer")
{}
};
@@ -251,6 +264,8 @@ struct LLNotificationTemplate
// The text used to display the notification. Replaceable parameters
// are enclosed in square brackets like this [].
std::string mMessage;
+ // The text used to display the notification, but under the form.
+ std::string mFooter;
// The label for the notification; used for
// certain classes of notification (those with a window and a window title).
// Also used when a notification pops up underneath the current one.
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index 9b7e30bb04..2fd187a526 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -389,12 +389,11 @@ void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height
{
*show_h_scrollbar = TRUE;
*visible_height -= scrollbar_size;
-
+ // Note: Do *not* recompute *show_v_scrollbar here because with
// The view inside the scroll container should not be extended
// to container's full height to ensure the correct computation
// of *show_v_scrollbar after subtracting horizontal scrollbar_size.
- // Must retest now that visible_height has changed
if( !*show_v_scrollbar && ((doc_height - *visible_height) > 1) )
{
*show_v_scrollbar = TRUE;
diff --git a/indra/llui/llsdparam.cpp b/indra/llui/llsdparam.cpp
deleted file mode 100644
index 54c8389772..0000000000
--- a/indra/llui/llsdparam.cpp
+++ /dev/null
@@ -1,341 +0,0 @@
-/**
- * @file llsdparam.cpp
- * @brief parameter block abstraction for creating complex objects and
- * parsing construction parameters from xml and LLSD
- *
- * $LicenseInfo:firstyear=2008&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "linden_common.h"
-
-// Project includes
-#include "llsdparam.h"
-#include "llsdutil.h"
-
-static LLInitParam::Parser::parser_read_func_map_t sReadFuncs;
-static LLInitParam::Parser::parser_write_func_map_t sWriteFuncs;
-static LLInitParam::Parser::parser_inspect_func_map_t sInspectFuncs;
-static const LLSD NO_VALUE_MARKER;
-
-LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR("LLSD to LLInitParam conversion");
-
-//
-// LLParamSDParser
-//
-LLParamSDParser::LLParamSDParser()
-: Parser(sReadFuncs, sWriteFuncs, sInspectFuncs)
-{
- using boost::bind;
-
- if (sReadFuncs.empty())
- {
- registerParserFuncs<LLInitParam::Flag>(readFlag, &LLParamSDParser::writeFlag);
- registerParserFuncs<S32>(readS32, &LLParamSDParser::writeTypedValue<S32>);
- registerParserFuncs<U32>(readU32, &LLParamSDParser::writeU32Param);
- registerParserFuncs<F32>(readF32, &LLParamSDParser::writeTypedValue<F32>);
- registerParserFuncs<F64>(readF64, &LLParamSDParser::writeTypedValue<F64>);
- registerParserFuncs<bool>(readBool, &LLParamSDParser::writeTypedValue<bool>);
- registerParserFuncs<std::string>(readString, &LLParamSDParser::writeTypedValue<std::string>);
- registerParserFuncs<LLUUID>(readUUID, &LLParamSDParser::writeTypedValue<LLUUID>);
- registerParserFuncs<LLDate>(readDate, &LLParamSDParser::writeTypedValue<LLDate>);
- registerParserFuncs<LLURI>(readURI, &LLParamSDParser::writeTypedValue<LLURI>);
- registerParserFuncs<LLSD>(readSD, &LLParamSDParser::writeTypedValue<LLSD>);
- }
-}
-
-// special case handling of U32 due to ambiguous LLSD::assign overload
-bool LLParamSDParser::writeU32Param(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack)
-{
- LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser);
- if (!sdparser.mWriteRootSD) return false;
-
- parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end());
- LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range);
- sd_to_write.assign((S32)*((const U32*)val_ptr));
-
- return true;
-}
-
-bool LLParamSDParser::writeFlag(LLParamSDParser::parser_t& parser, const void* val_ptr, parser_t::name_stack_t& name_stack)
-{
- LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser);
- if (!sdparser.mWriteRootSD) return false;
-
- parser_t::name_stack_range_t range(name_stack.begin(), name_stack.end());
- LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range);
-
- return true;
-}
-
-void LLParamSDParser::submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack)
-{
- mCurReadSD = &sd;
- block.submitValue(name_stack, *this);
-}
-
-void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent)
-{
- mCurReadSD = NULL;
- mNameStack.clear();
- setParseSilently(silent);
-
- LLParamSDParserUtilities::readSDValues(boost::bind(&LLParamSDParser::submit, this, boost::ref(block), _1, _2), sd, mNameStack);
- //readSDValues(sd, block);
-}
-
-void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block)
-{
- mNameStack.clear();
- mWriteRootSD = &sd;
-
- name_stack_t name_stack;
- block.serializeBlock(*this, name_stack);
-}
-
-/*virtual*/ std::string LLParamSDParser::getCurrentElementName()
-{
- std::string full_name = "sd";
- for (name_stack_t::iterator it = mNameStack.begin();
- it != mNameStack.end();
- ++it)
- {
- full_name += llformat("[%s]", it->first.c_str());
- }
-
- return full_name;
-}
-
-
-bool LLParamSDParser::readFlag(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
- return self.mCurReadSD == &NO_VALUE_MARKER;
-}
-
-
-bool LLParamSDParser::readS32(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((S32*)val_ptr) = self.mCurReadSD->asInteger();
- return true;
-}
-
-bool LLParamSDParser::readU32(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((U32*)val_ptr) = self.mCurReadSD->asInteger();
- return true;
-}
-
-bool LLParamSDParser::readF32(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((F32*)val_ptr) = self.mCurReadSD->asReal();
- return true;
-}
-
-bool LLParamSDParser::readF64(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((F64*)val_ptr) = self.mCurReadSD->asReal();
- return true;
-}
-
-bool LLParamSDParser::readBool(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((bool*)val_ptr) = self.mCurReadSD->asBoolean();
- return true;
-}
-
-bool LLParamSDParser::readString(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((std::string*)val_ptr) = self.mCurReadSD->asString();
- return true;
-}
-
-bool LLParamSDParser::readUUID(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((LLUUID*)val_ptr) = self.mCurReadSD->asUUID();
- return true;
-}
-
-bool LLParamSDParser::readDate(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((LLDate*)val_ptr) = self.mCurReadSD->asDate();
- return true;
-}
-
-bool LLParamSDParser::readURI(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((LLURI*)val_ptr) = self.mCurReadSD->asURI();
- return true;
-}
-
-bool LLParamSDParser::readSD(Parser& parser, void* val_ptr)
-{
- LLParamSDParser& self = static_cast<LLParamSDParser&>(parser);
-
- *((LLSD*)val_ptr) = *self.mCurReadSD;
- return true;
-}
-
-// static
-LLSD& LLParamSDParserUtilities::getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range)
-{
- LLSD* sd_to_write = &input;
-
- for (LLInitParam::Parser::name_stack_t::iterator it = name_stack_range.first;
- it != name_stack_range.second;
- ++it)
- {
- bool new_traversal = it->second;
-
- LLSD* child_sd;
- if (it->first.empty())
- {
- child_sd = sd_to_write;
- if (child_sd->isUndefined())
- {
- *child_sd = LLSD::emptyArray();
- }
- if (new_traversal)
- {
- // write to new element at end
- sd_to_write = &(*child_sd)[child_sd->size()];
- }
- else
- {
- // write to last of existing elements, or first element if empty
- sd_to_write = &(*child_sd)[llmax(0, child_sd->size() - 1)];
- }
- }
- else
- {
- sd_to_write = &(*sd_to_write)[it->first];
- }
- it->second = false;
- }
-
- return *sd_to_write;
-}
-
-//static
-void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack)
-{
- if (sd.isMap())
- {
- for (LLSD::map_const_iterator it = sd.beginMap();
- it != sd.endMap();
- ++it)
- {
- stack.push_back(make_pair(it->first, true));
- readSDValues(cb, it->second, stack);
- stack.pop_back();
- }
- }
- else if (sd.isArray())
- {
- for (LLSD::array_const_iterator it = sd.beginArray();
- it != sd.endArray();
- ++it)
- {
- stack.push_back(make_pair(std::string(), true));
- readSDValues(cb, *it, stack);
- stack.pop_back();
- }
- }
- else if (sd.isUndefined())
- {
- if (!cb.empty())
- {
- cb(NO_VALUE_MARKER, stack);
- }
- }
- else
- {
- if (!cb.empty())
- {
- cb(sd, stack);
- }
- }
-}
-
-//static
-void LLParamSDParserUtilities::readSDValues(read_sd_cb_t cb, const LLSD& sd)
-{
- LLInitParam::Parser::name_stack_t stack = LLInitParam::Parser::name_stack_t();
- readSDValues(cb, sd, stack);
-}
-namespace LLInitParam
-{
- // LLSD specialization
- // block param interface
- bool ParamValue<LLSD, NOT_BLOCK>::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name)
- {
- if (name_stack.first == name_stack.second
- && p.readValue<LLSD>(mValue))
- {
- return true;
- }
-
- LLSD& sd = LLParamSDParserUtilities::getSDWriteNode(mValue, name_stack);
-
- LLSD::String string;
-
- if (p.readValue<LLSD::String>(string))
- {
- sd = string;
- return true;
- }
- return false;
- }
-
- //static
- void ParamValue<LLSD, NOT_BLOCK>::serializeElement(Parser& p, const LLSD& sd, Parser::name_stack_t& name_stack)
- {
- p.writeValue<LLSD::String>(sd.asString(), name_stack);
- }
-
- void ParamValue<LLSD, NOT_BLOCK>::serializeBlock(Parser& p, Parser::name_stack_t& name_stack, const BaseBlock* diff_block) const
- {
- // attempt to write LLSD out directly
- if (!p.writeValue<LLSD>(mValue, name_stack))
- {
- // otherwise read from LLSD value and serialize out to parser (which could be LLSD, XUI, etc)
- LLParamSDParserUtilities::readSDValues(boost::bind(&serializeElement, boost::ref(p), _1, _2), mValue, name_stack);
- }
- }
-}
diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h
deleted file mode 100644
index 3dfc6d020e..0000000000
--- a/indra/llui/llsdparam.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * @file llsdparam.h
- * @brief parameter block abstraction for creating complex objects and
- * parsing construction parameters from xml and LLSD
- *
- * $LicenseInfo:firstyear=2008&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#ifndef LL_LLSDPARAM_H
-#define LL_LLSDPARAM_H
-
-#include "llinitparam.h"
-#include "boost/function.hpp"
-
-struct LLParamSDParserUtilities
-{
- static LLSD& getSDWriteNode(LLSD& input, LLInitParam::Parser::name_stack_range_t& name_stack_range);
-
- typedef boost::function<void (const LLSD&, LLInitParam::Parser::name_stack_t&)> read_sd_cb_t;
- static void readSDValues(read_sd_cb_t cb, const LLSD& sd, LLInitParam::Parser::name_stack_t& stack);
- static void readSDValues(read_sd_cb_t cb, const LLSD& sd);
-};
-
-class LLParamSDParser
-: public LLInitParam::Parser
-{
-LOG_CLASS(LLParamSDParser);
-
-typedef LLInitParam::Parser parser_t;
-
-public:
- LLParamSDParser();
- void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false);
- void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block);
-
- /*virtual*/ std::string getCurrentElementName();
-
-private:
- void submit(LLInitParam::BaseBlock& block, const LLSD& sd, LLInitParam::Parser::name_stack_t& name_stack);
-
- template<typename T>
- static bool writeTypedValue(Parser& parser, const void* val_ptr, parser_t::name_stack_t& name_stack)
- {
- LLParamSDParser& sdparser = static_cast<LLParamSDParser&>(parser);
- if (!sdparser.mWriteRootSD) return false;
-
- LLInitParam::Parser::name_stack_range_t range(name_stack.begin(), name_stack.end());
- LLSD& sd_to_write = LLParamSDParserUtilities::getSDWriteNode(*sdparser.mWriteRootSD, range);
-
- sd_to_write.assign(*((const T*)val_ptr));
- return true;
- }
-
- static bool writeU32Param(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack);
- static bool writeFlag(Parser& parser, const void* value_ptr, parser_t::name_stack_t& name_stack);
-
- static bool readFlag(Parser& parser, void* val_ptr);
- static bool readS32(Parser& parser, void* val_ptr);
- static bool readU32(Parser& parser, void* val_ptr);
- static bool readF32(Parser& parser, void* val_ptr);
- static bool readF64(Parser& parser, void* val_ptr);
- static bool readBool(Parser& parser, void* val_ptr);
- static bool readString(Parser& parser, void* val_ptr);
- static bool readUUID(Parser& parser, void* val_ptr);
- static bool readDate(Parser& parser, void* val_ptr);
- static bool readURI(Parser& parser, void* val_ptr);
- static bool readSD(Parser& parser, void* val_ptr);
-
- Parser::name_stack_t mNameStack;
- const LLSD* mCurReadSD;
- LLSD* mWriteRootSD;
- LLSD* mCurWriteSD;
-};
-
-
-extern LLFastTimer::DeclareTimer FTM_SD_PARAM_ADAPTOR;
-template<typename T>
-class LLSDParamAdapter : public T
-{
-public:
- LLSDParamAdapter() {}
- LLSDParamAdapter(const LLSD& sd)
- {
- LLFastTimer _(FTM_SD_PARAM_ADAPTOR);
- LLParamSDParser parser;
- // don't spam for implicit parsing of LLSD, as we want to allow arbitrary freeform data and ignore most of it
- bool parse_silently = true;
- parser.readSD(sd, *this, parse_silently);
- }
-
- operator LLSD() const
- {
- LLParamSDParser parser;
- LLSD sd;
- parser.writeSD(sd, *this);
- return sd;
- }
-
- LLSDParamAdapter(const T& val)
- : T(val)
- {
- T::operator=(val);
- }
-};
-
-#endif // LL_LLSDPARAM_H
-
diff --git a/indra/llui/llspellcheck.cpp b/indra/llui/llspellcheck.cpp
new file mode 100644
index 0000000000..a189375fbe
--- /dev/null
+++ b/indra/llui/llspellcheck.cpp
@@ -0,0 +1,505 @@
+/**
+ * @file llspellcheck.cpp
+ * @brief Spell checking functionality
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lldir.h"
+#include "llsdserialize.h"
+
+#include "llspellcheck.h"
+#if LL_WINDOWS
+ #include <hunspell/hunspelldll.h>
+ #pragma comment(lib, "libhunspell.lib")
+#else
+ #include <hunspell/hunspell.hxx>
+#endif
+
+static const std::string DICT_DIR = "dictionaries";
+static const std::string DICT_FILE_CUSTOM = "user_custom.dic";
+static const std::string DICT_FILE_IGNORE = "user_ignore.dic";
+
+static const std::string DICT_FILE_MAIN = "dictionaries.xml";
+static const std::string DICT_FILE_USER = "user_dictionaries.xml";
+
+LLSD LLSpellChecker::sDictMap;
+LLSpellChecker::settings_change_signal_t LLSpellChecker::sSettingsChangeSignal;
+
+LLSpellChecker::LLSpellChecker()
+ : mHunspell(NULL)
+{
+ // Load initial dictionary information
+ refreshDictionaryMap();
+}
+
+LLSpellChecker::~LLSpellChecker()
+{
+ delete mHunspell;
+}
+
+bool LLSpellChecker::checkSpelling(const std::string& word) const
+{
+ if ( (!mHunspell) || (word.length() < 3) || (0 != mHunspell->spell(word.c_str())) )
+ {
+ return true;
+ }
+ if (mIgnoreList.size() > 0)
+ {
+ std::string word_lower(word);
+ LLStringUtil::toLower(word_lower);
+ return (mIgnoreList.end() != std::find(mIgnoreList.begin(), mIgnoreList.end(), word_lower));
+ }
+ return false;
+}
+
+S32 LLSpellChecker::getSuggestions(const std::string& word, std::vector<std::string>& suggestions) const
+{
+ suggestions.clear();
+ if ( (!mHunspell) || (word.length() < 3) )
+ {
+ return 0;
+ }
+
+ char** suggestion_list; int suggestion_cnt = 0;
+ if ( (suggestion_cnt = mHunspell->suggest(&suggestion_list, word.c_str())) != 0 )
+ {
+ for (int suggestion_index = 0; suggestion_index < suggestion_cnt; suggestion_index++)
+ {
+ suggestions.push_back(suggestion_list[suggestion_index]);
+ }
+ mHunspell->free_list(&suggestion_list, suggestion_cnt);
+ }
+ return suggestions.size();
+}
+
+// static
+const LLSD LLSpellChecker::getDictionaryData(const std::string& dict_language)
+{
+ for (LLSD::array_const_iterator it = sDictMap.beginArray(); it != sDictMap.endArray(); ++it)
+ {
+ const LLSD& dict_entry = *it;
+ if (dict_language == dict_entry["language"].asString())
+ {
+ return dict_entry;
+ }
+ }
+ return LLSD();
+}
+
+// static
+bool LLSpellChecker::hasDictionary(const std::string& dict_language, bool check_installed)
+{
+ const LLSD dict_info = getDictionaryData(dict_language);
+ return dict_info.has("language") && ( (!check_installed) || (dict_info["installed"].asBoolean()) );
+}
+
+// static
+void LLSpellChecker::setDictionaryData(const LLSD& dict_info)
+{
+ const std::string dict_language = dict_info["language"].asString();
+ if (dict_language.empty())
+ {
+ return;
+ }
+
+ for (LLSD::array_iterator it = sDictMap.beginArray(); it != sDictMap.endArray(); ++it)
+ {
+ LLSD& dict_entry = *it;
+ if (dict_language == dict_entry["language"].asString())
+ {
+ dict_entry = dict_info;
+ return;
+ }
+ }
+ sDictMap.append(dict_info);
+ return;
+}
+
+// static
+void LLSpellChecker::refreshDictionaryMap()
+{
+ const std::string app_path = getDictionaryAppPath();
+ const std::string user_path = getDictionaryUserPath();
+
+ // Load dictionary information (file name, friendly name, ...)
+ llifstream user_file(user_path + DICT_FILE_MAIN, std::ios::binary);
+ if ( (!user_file.is_open()) || (0 == LLSDSerialize::fromXMLDocument(sDictMap, user_file)) || (0 == sDictMap.size()) )
+ {
+ llifstream app_file(app_path + DICT_FILE_MAIN, std::ios::binary);
+ if ( (!app_file.is_open()) || (0 == LLSDSerialize::fromXMLDocument(sDictMap, app_file)) || (0 == sDictMap.size()) )
+ {
+ return;
+ }
+ }
+
+ // Load user installed dictionary information
+ llifstream custom_file(user_path + DICT_FILE_USER, std::ios::binary);
+ if (custom_file.is_open())
+ {
+ LLSD custom_dict_map;
+ LLSDSerialize::fromXMLDocument(custom_dict_map, custom_file);
+ for (LLSD::array_iterator it = custom_dict_map.beginArray(); it != custom_dict_map.endArray(); ++it)
+ {
+ LLSD& dict_info = *it;
+ dict_info["user_installed"] = true;
+ setDictionaryData(dict_info);
+ }
+ custom_file.close();
+ }
+
+ // Look for installed dictionaries
+ std::string tmp_app_path, tmp_user_path;
+ for (LLSD::array_iterator it = sDictMap.beginArray(); it != sDictMap.endArray(); ++it)
+ {
+ LLSD& sdDict = *it;
+ tmp_app_path = (sdDict.has("name")) ? app_path + sdDict["name"].asString() : LLStringUtil::null;
+ tmp_user_path = (sdDict.has("name")) ? user_path + sdDict["name"].asString() : LLStringUtil::null;
+ sdDict["installed"] =
+ (!tmp_app_path.empty()) && ((gDirUtilp->fileExists(tmp_user_path + ".dic")) || (gDirUtilp->fileExists(tmp_app_path + ".dic")));
+ }
+
+ sSettingsChangeSignal();
+}
+
+void LLSpellChecker::addToCustomDictionary(const std::string& word)
+{
+ if (mHunspell)
+ {
+ mHunspell->add(word.c_str());
+ }
+ addToDictFile(getDictionaryUserPath() + DICT_FILE_CUSTOM, word);
+ sSettingsChangeSignal();
+}
+
+void LLSpellChecker::addToIgnoreList(const std::string& word)
+{
+ std::string word_lower(word);
+ LLStringUtil::toLower(word_lower);
+ if (mIgnoreList.end() == std::find(mIgnoreList.begin(), mIgnoreList.end(), word_lower))
+ {
+ mIgnoreList.push_back(word_lower);
+ addToDictFile(getDictionaryUserPath() + DICT_FILE_IGNORE, word_lower);
+ sSettingsChangeSignal();
+ }
+}
+
+void LLSpellChecker::addToDictFile(const std::string& dict_path, const std::string& word)
+{
+ std::vector<std::string> word_list;
+
+ if (gDirUtilp->fileExists(dict_path))
+ {
+ llifstream file_in(dict_path, std::ios::in);
+ if (file_in.is_open())
+ {
+ std::string word; int line_num = 0;
+ while (getline(file_in, word))
+ {
+ // Skip over the first line since that's just a line count
+ if (0 != line_num)
+ {
+ word_list.push_back(word);
+ }
+ line_num++;
+ }
+ }
+ else
+ {
+ // TODO: show error message?
+ return;
+ }
+ }
+
+ word_list.push_back(word);
+
+ llofstream file_out(dict_path, std::ios::out | std::ios::trunc);
+ if (file_out.is_open())
+ {
+ file_out << word_list.size() << std::endl;
+ for (std::vector<std::string>::const_iterator itWord = word_list.begin(); itWord != word_list.end(); ++itWord)
+ {
+ file_out << *itWord << std::endl;
+ }
+ file_out.close();
+ }
+}
+
+bool LLSpellChecker::isActiveDictionary(const std::string& dict_language) const
+{
+ return
+ (mDictLanguage == dict_language) ||
+ (mDictSecondary.end() != std::find(mDictSecondary.begin(), mDictSecondary.end(), dict_language));
+}
+
+void LLSpellChecker::setSecondaryDictionaries(dict_list_t dict_list)
+{
+ if (!getUseSpellCheck())
+ {
+ return;
+ }
+
+ // Check if we're only adding secondary dictionaries, or removing them
+ dict_list_t dict_add(llmax(dict_list.size(), mDictSecondary.size())), dict_rem(llmax(dict_list.size(), mDictSecondary.size()));
+ dict_list.sort();
+ mDictSecondary.sort();
+ dict_list_t::iterator end_added = std::set_difference(dict_list.begin(), dict_list.end(), mDictSecondary.begin(), mDictSecondary.end(), dict_add.begin());
+ dict_list_t::iterator end_removed = std::set_difference(mDictSecondary.begin(), mDictSecondary.end(), dict_list.begin(), dict_list.end(), dict_rem.begin());
+
+ if (end_removed != dict_rem.begin()) // We can't remove secondary dictionaries so we need to recreate the Hunspell instance
+ {
+ mDictSecondary = dict_list;
+
+ std::string dict_language = mDictLanguage;
+ initHunspell(dict_language);
+ }
+ else if (end_added != dict_add.begin()) // Add the new secondary dictionaries one by one
+ {
+ const std::string app_path = getDictionaryAppPath();
+ const std::string user_path = getDictionaryUserPath();
+ for (dict_list_t::const_iterator it_added = dict_add.begin(); it_added != end_added; ++it_added)
+ {
+ const LLSD dict_entry = getDictionaryData(*it_added);
+ if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) )
+ {
+ continue;
+ }
+
+ const std::string strFileDic = dict_entry["name"].asString() + ".dic";
+ if (gDirUtilp->fileExists(user_path + strFileDic))
+ {
+ mHunspell->add_dic((user_path + strFileDic).c_str());
+ }
+ else if (gDirUtilp->fileExists(app_path + strFileDic))
+ {
+ mHunspell->add_dic((app_path + strFileDic).c_str());
+ }
+ }
+ mDictSecondary = dict_list;
+ sSettingsChangeSignal();
+ }
+}
+
+void LLSpellChecker::initHunspell(const std::string& dict_language)
+{
+ if (mHunspell)
+ {
+ delete mHunspell;
+ mHunspell = NULL;
+ mDictLanguage.clear();
+ mDictFile.clear();
+ mIgnoreList.clear();
+ }
+
+ const LLSD dict_entry = (!dict_language.empty()) ? getDictionaryData(dict_language) : LLSD();
+ if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) || (!dict_entry["is_primary"].asBoolean()))
+ {
+ sSettingsChangeSignal();
+ return;
+ }
+
+ const std::string app_path = getDictionaryAppPath();
+ const std::string user_path = getDictionaryUserPath();
+ if (dict_entry.has("name"))
+ {
+ const std::string filename_aff = dict_entry["name"].asString() + ".aff";
+ const std::string filename_dic = dict_entry["name"].asString() + ".dic";
+ if ( (gDirUtilp->fileExists(user_path + filename_aff)) && (gDirUtilp->fileExists(user_path + filename_dic)) )
+ {
+ mHunspell = new Hunspell((user_path + filename_aff).c_str(), (user_path + filename_dic).c_str());
+ }
+ else if ( (gDirUtilp->fileExists(app_path + filename_aff)) && (gDirUtilp->fileExists(app_path + filename_dic)) )
+ {
+ mHunspell = new Hunspell((app_path + filename_aff).c_str(), (app_path + filename_dic).c_str());
+ }
+ if (!mHunspell)
+ {
+ return;
+ }
+
+ mDictLanguage = dict_language;
+ mDictFile = dict_entry["name"].asString();
+
+ if (gDirUtilp->fileExists(user_path + DICT_FILE_CUSTOM))
+ {
+ mHunspell->add_dic((user_path + DICT_FILE_CUSTOM).c_str());
+ }
+
+ if (gDirUtilp->fileExists(user_path + DICT_FILE_IGNORE))
+ {
+ llifstream file_in(user_path + DICT_FILE_IGNORE, std::ios::in);
+ if (file_in.is_open())
+ {
+ std::string word; int idxLine = 0;
+ while (getline(file_in, word))
+ {
+ // Skip over the first line since that's just a line count
+ if (0 != idxLine)
+ {
+ LLStringUtil::toLower(word);
+ mIgnoreList.push_back(word);
+ }
+ idxLine++;
+ }
+ }
+ }
+
+ for (dict_list_t::const_iterator it = mDictSecondary.begin(); it != mDictSecondary.end(); ++it)
+ {
+ const LLSD dict_entry = getDictionaryData(*it);
+ if ( (!dict_entry.isDefined()) || (!dict_entry["installed"].asBoolean()) )
+ {
+ continue;
+ }
+
+ const std::string filename_dic = dict_entry["name"].asString() + ".dic";
+ if (gDirUtilp->fileExists(user_path + filename_dic))
+ {
+ mHunspell->add_dic((user_path + filename_dic).c_str());
+ }
+ else if (gDirUtilp->fileExists(app_path + filename_dic))
+ {
+ mHunspell->add_dic((app_path + filename_dic).c_str());
+ }
+ }
+ }
+
+ sSettingsChangeSignal();
+}
+
+// static
+const std::string LLSpellChecker::getDictionaryAppPath()
+{
+ std::string dict_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, DICT_DIR, "");
+ return dict_path;
+}
+
+// static
+const std::string LLSpellChecker::getDictionaryUserPath()
+{
+ std::string dict_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, DICT_DIR, "");
+ if (!gDirUtilp->fileExists(dict_path))
+ {
+ LLFile::mkdir(dict_path);
+ }
+ return dict_path;
+}
+
+// static
+bool LLSpellChecker::getUseSpellCheck()
+{
+ return (LLSpellChecker::instanceExists()) && (LLSpellChecker::instance().mHunspell);
+}
+
+// static
+bool LLSpellChecker::canRemoveDictionary(const std::string& dict_language)
+{
+ // Only user-installed inactive dictionaries can be removed
+ const LLSD dict_info = getDictionaryData(dict_language);
+ return
+ (dict_info["user_installed"].asBoolean()) &&
+ ( (!getUseSpellCheck()) || (!LLSpellChecker::instance().isActiveDictionary(dict_language)) );
+}
+
+// static
+void LLSpellChecker::removeDictionary(const std::string& dict_language)
+{
+ if (!canRemoveDictionary(dict_language))
+ {
+ return;
+ }
+
+ LLSD dict_map = loadUserDictionaryMap();
+ for (LLSD::array_const_iterator it = dict_map.beginArray(); it != dict_map.endArray(); ++it)
+ {
+ const LLSD& dict_info = *it;
+ if (dict_info["language"].asString() == dict_language)
+ {
+ const std::string dict_dic = getDictionaryUserPath() + dict_info["name"].asString() + ".dic";
+ if (gDirUtilp->fileExists(dict_dic))
+ {
+ LLFile::remove(dict_dic);
+ }
+ const std::string dict_aff = getDictionaryUserPath() + dict_info["name"].asString() + ".aff";
+ if (gDirUtilp->fileExists(dict_aff))
+ {
+ LLFile::remove(dict_aff);
+ }
+ dict_map.erase(it - dict_map.beginArray());
+ break;
+ }
+ }
+ saveUserDictionaryMap(dict_map);
+
+ refreshDictionaryMap();
+}
+
+// static
+LLSD LLSpellChecker::loadUserDictionaryMap()
+{
+ LLSD dict_map;
+ llifstream dict_file(getDictionaryUserPath() + DICT_FILE_USER, std::ios::binary);
+ if (dict_file.is_open())
+ {
+ LLSDSerialize::fromXMLDocument(dict_map, dict_file);
+ dict_file.close();
+ }
+ return dict_map;
+}
+
+// static
+void LLSpellChecker::saveUserDictionaryMap(const LLSD& dict_map)
+{
+ llofstream dict_file(getDictionaryUserPath() + DICT_FILE_USER, std::ios::trunc);
+ if (dict_file.is_open())
+ {
+ LLSDSerialize::toPrettyXML(dict_map, dict_file);
+ dict_file.close();
+ }
+}
+
+// static
+boost::signals2::connection LLSpellChecker::setSettingsChangeCallback(const settings_change_signal_t::slot_type& cb)
+{
+ return sSettingsChangeSignal.connect(cb);
+}
+
+// static
+void LLSpellChecker::setUseSpellCheck(const std::string& dict_language)
+{
+ if ( (((dict_language.empty()) && (getUseSpellCheck())) || (!dict_language.empty())) &&
+ (LLSpellChecker::instance().mDictLanguage != dict_language) )
+ {
+ LLSpellChecker::instance().initHunspell(dict_language);
+ }
+}
+
+// static
+void LLSpellChecker::initClass()
+{
+ if (sDictMap.isUndefined())
+ {
+ refreshDictionaryMap();
+ }
+}
diff --git a/indra/llui/llspellcheck.h b/indra/llui/llspellcheck.h
new file mode 100644
index 0000000000..4ab80195ea
--- /dev/null
+++ b/indra/llui/llspellcheck.h
@@ -0,0 +1,93 @@
+/**
+ * @file llspellcheck.h
+ * @brief Spell checking functionality
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLSPELLCHECK_H
+#define LLSPELLCHECK_H
+
+#include "llsingleton.h"
+#include "llui.h"
+#include <boost/signals2.hpp>
+
+class Hunspell;
+
+class LLSpellChecker : public LLSingleton<LLSpellChecker>, public LLInitClass<LLSpellChecker>
+{
+ friend class LLSingleton<LLSpellChecker>;
+ friend class LLInitClass<LLSpellChecker>;
+protected:
+ LLSpellChecker();
+ ~LLSpellChecker();
+
+public:
+ void addToCustomDictionary(const std::string& word);
+ void addToIgnoreList(const std::string& word);
+ bool checkSpelling(const std::string& word) const;
+ S32 getSuggestions(const std::string& word, std::vector<std::string>& suggestions) const;
+protected:
+ void addToDictFile(const std::string& dict_path, const std::string& word);
+ void initHunspell(const std::string& dict_language);
+
+public:
+ typedef std::list<std::string> dict_list_t;
+
+ const std::string& getPrimaryDictionary() const { return mDictLanguage; }
+ const dict_list_t& getSecondaryDictionaries() const { return mDictSecondary; }
+ bool isActiveDictionary(const std::string& dict_language) const;
+ void setSecondaryDictionaries(dict_list_t dict_list);
+
+ static bool canRemoveDictionary(const std::string& dict_language);
+ static const std::string getDictionaryAppPath();
+ static const std::string getDictionaryUserPath();
+ static const LLSD getDictionaryData(const std::string& dict_language);
+ static const LLSD& getDictionaryMap() { return sDictMap; }
+ static bool getUseSpellCheck();
+ static bool hasDictionary(const std::string& dict_language, bool check_installed = false);
+ static void refreshDictionaryMap();
+ static void removeDictionary(const std::string& dict_language);
+ static void setUseSpellCheck(const std::string& dict_language);
+protected:
+ static LLSD loadUserDictionaryMap();
+ static void setDictionaryData(const LLSD& dict_info);
+ static void saveUserDictionaryMap(const LLSD& dict_map);
+
+public:
+ typedef boost::signals2::signal<void()> settings_change_signal_t;
+ static boost::signals2::connection setSettingsChangeCallback(const settings_change_signal_t::slot_type& cb);
+protected:
+ static void initClass();
+
+protected:
+ Hunspell* mHunspell;
+ std::string mDictLanguage;
+ std::string mDictFile;
+ dict_list_t mDictSecondary;
+ std::vector<std::string> mIgnoreList;
+
+ static LLSD sDictMap;
+ static settings_change_signal_t sSettingsChangeSignal;
+};
+
+#endif // LLSPELLCHECK_H
diff --git a/indra/llui/llspellcheckmenuhandler.h b/indra/llui/llspellcheckmenuhandler.h
new file mode 100644
index 0000000000..d5c95bad39
--- /dev/null
+++ b/indra/llui/llspellcheckmenuhandler.h
@@ -0,0 +1,46 @@
+/**
+ * @file llspellcheckmenuhandler.h
+ * @brief Interface used by spell check menu handling
+ *
+ * $LicenseInfo:firstyear=2001&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLSPELLCHECKMENUHANDLER_H
+#define LLSPELLCHECKMENUHANDLER_H
+
+class LLSpellCheckMenuHandler
+{
+public:
+ virtual bool getSpellCheck() const { return false; }
+
+ virtual const std::string& getSuggestion(U32 index) const { return LLStringUtil::null; }
+ virtual U32 getSuggestionCount() const { return 0; }
+ virtual void replaceWithSuggestion(U32 index){}
+
+ virtual void addToDictionary() {}
+ virtual bool canAddToDictionary() const { return false; }
+
+ virtual void addToIgnore() {}
+ virtual bool canAddToIgnore() const { return false; }
+};
+
+#endif // LLSPELLCHECKMENUHANDLER_H
diff --git a/indra/llui/llstatbar.cpp b/indra/llui/llstatbar.cpp
index ec4db14790..04cce7878e 100644
--- a/indra/llui/llstatbar.cpp
+++ b/indra/llui/llstatbar.cpp
@@ -272,7 +272,7 @@ LLRect LLStatBar::getRequiredRect()
{
if (mDisplayHistory)
{
- rect.mTop = 67;
+ rect.mTop = 35 + mStatp->getNumBins();
}
else
{
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index a7bc6bbb77..98624f42b9 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -32,6 +32,7 @@
#include "lllocalcliprect.h"
#include "llmenugl.h"
#include "llscrollcontainer.h"
+#include "llspellcheck.h"
#include "llstl.h"
#include "lltextparser.h"
#include "lltextutil.h"
@@ -156,6 +157,7 @@ LLTextBase::Params::Params()
plain_text("plain_text",false),
track_end("track_end", false),
read_only("read_only", false),
+ spellcheck("spellcheck", false),
v_pad("v_pad", 0),
h_pad("h_pad", 0),
clip("clip", true),
@@ -182,6 +184,9 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
mFontShadow(p.font_shadow),
mPopupMenu(NULL),
mReadOnly(p.read_only),
+ mSpellCheck(p.spellcheck),
+ mSpellCheckStart(-1),
+ mSpellCheckEnd(-1),
mCursorColor(p.cursor_color),
mFgColor(p.text_color),
mBorderVisible( p.border_visible ),
@@ -248,6 +253,12 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p)
addChild(mDocumentView);
}
+ if (mSpellCheck)
+ {
+ LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this));
+ }
+ mSpellCheckTimer.reset();
+
createDefaultSegment();
updateRects();
@@ -282,12 +293,23 @@ bool LLTextBase::truncate()
if (getLength() >= S32(mMaxTextByteLength / 4))
{
// Have to check actual byte size
- LLWString text(getWText());
- S32 utf8_byte_size = wstring_utf8_length(text);
+ S32 utf8_byte_size = 0;
+ LLSD value = getViewModel()->getValue();
+ if (value.type() == LLSD::TypeString)
+ {
+ // save a copy for strings.
+ utf8_byte_size = value.size();
+ }
+ else
+ {
+ // non string LLSDs need explicit conversion to string
+ utf8_byte_size = value.asString().size();
+ }
+
if ( utf8_byte_size > mMaxTextByteLength )
{
// Truncate safely in UTF-8
- std::string temp_utf8_text = wstring_to_utf8str(text);
+ std::string temp_utf8_text = value.asString();
temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength );
LLWString text = utf8str_to_wstring( temp_utf8_text );
// remove extra bit of current string, to preserve formatting, etc.
@@ -538,8 +560,92 @@ void LLTextBase::drawText()
return;
}
+ // Perform spell check if needed
+ if ( (getSpellCheck()) && (getWText().length() > 2) )
+ {
+ // Calculate start and end indices for the spell checking range
+ S32 start = line_start, end = getLineEnd(last_line);
+
+ if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) )
+ {
+ const LLWString& wstrText = getWText();
+ mMisspellRanges.clear();
+
+ segment_set_t::const_iterator seg_it = getSegIterContaining(start);
+ while (mSegments.end() != seg_it)
+ {
+ LLTextSegmentPtr text_segment = *seg_it;
+ if ( (text_segment.isNull()) || (text_segment->getStart() >= end) )
+ {
+ break;
+ }
+
+ if (!text_segment->canEdit())
+ {
+ ++seg_it;
+ continue;
+ }
+
+ // Combine adjoining text segments into one
+ U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end);
+ while (mSegments.end() != ++seg_it)
+ {
+ text_segment = *seg_it;
+ if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) )
+ {
+ break;
+ }
+ seg_end = llmin(text_segment->getEnd(), end);
+ }
+
+ // Find the start of the first word
+ U32 word_start = seg_start, word_end = -1;
+ while ( (word_start < wstrText.length()) && (!LLStringOps::isAlpha(wstrText[word_start])) )
+ {
+ word_start++;
+ }
+
+ // Iterate over all words in the text block and check them one by one
+ while (word_start < seg_end)
+ {
+ // Find the end of the current word (special case handling for "'" when it's used as a contraction)
+ word_end = word_start + 1;
+ while ( (word_end < seg_end) &&
+ ((LLWStringUtil::isPartOfWord(wstrText[word_end])) ||
+ ((L'\'' == wstrText[word_end]) &&
+ (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) )
+ {
+ word_end++;
+ }
+ if (word_end > seg_end)
+ {
+ break;
+ }
+
+ // Don't process words shorter than 3 characters
+ std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start));
+ if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) )
+ {
+ mMisspellRanges.push_back(std::pair<U32, U32>(word_start, word_end));
+ }
+
+ // Find the start of the next word
+ word_start = word_end + 1;
+ while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) )
+ {
+ word_start++;
+ }
+ }
+ }
+
+ mSpellCheckStart = start;
+ mSpellCheckEnd = end;
+ }
+ }
+
LLTextSegmentPtr cur_segment = *seg_iter;
+ std::list<std::pair<U32, U32> >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair<U32, U32>(line_start, 0));
for (S32 cur_line = first_line; cur_line < last_line; cur_line++)
{
S32 next_line = cur_line + 1;
@@ -574,7 +680,8 @@ void LLTextBase::drawText()
cur_segment = *seg_iter;
}
- S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - cur_segment->getStart();
+ S32 seg_end = llmin(line_end, cur_segment->getEnd());
+ S32 clipped_end = seg_end - cur_segment->getStart();
if (mUseEllipses // using ellipses
&& clipped_end == line_end // last segment on line
@@ -586,6 +693,46 @@ void LLTextBase::drawText()
text_rect.mRight -= 2;
}
+ // Draw squiggly lines under any visible misspelled words
+ while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) )
+ {
+ // Skip the current word if the user is still busy editing it
+ if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) )
+ {
+ ++misspell_it;
+ continue;
+ }
+
+ U32 misspell_start = llmax<U32>(misspell_it->first, seg_start), misspell_end = llmin<U32>(misspell_it->second, seg_end);
+ S32 squiggle_start = 0, squiggle_end = 0, pony = 0;
+ cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony);
+ cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony);
+ squiggle_start += text_rect.mLeft;
+
+ pony = (squiggle_end + 3) / 6;
+ squiggle_start += squiggle_end / 2 - pony * 3;
+ squiggle_end = squiggle_start + pony * 6;
+
+ S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight();
+
+ gGL.color4ub(255, 0, 0, 200);
+ while (squiggle_start + 1 < squiggle_end)
+ {
+ gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2);
+ if (squiggle_start + 3 < squiggle_end)
+ {
+ gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1);
+ }
+ squiggle_start += 4;
+ }
+
+ if (misspell_it->second > seg_end)
+ {
+ break;
+ }
+ ++misspell_it;
+ }
+
text_rect.mLeft = (S32)(cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect));
seg_start = clipped_end + cur_segment->getStart();
@@ -600,8 +747,7 @@ void LLTextBase::drawText()
S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments )
{
- LLWString text(getWText());
- S32 old_len = text.length(); // length() returns character length
+ S32 old_len = getLength(); // length() returns character length
S32 insert_len = wstr.length();
pos = getEditableIndex(pos, true);
@@ -661,8 +807,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s
}
}
- text.insert(pos, wstr);
- getViewModel()->setDisplay(text);
+ getViewModel()->getEditableDisplay().insert(pos, wstr);
if ( truncate() )
{
@@ -677,7 +822,6 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s
S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
{
- LLWString text(getWText());
segment_set_t::iterator seg_iter = getSegIterContaining(pos);
while(seg_iter != mSegments.end())
{
@@ -723,8 +867,7 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length)
++seg_iter;
}
- text.erase(pos, length);
- getViewModel()->setDisplay(text);
+ getViewModel()->getEditableDisplay().erase(pos, length);
// recreate default segment in case we erased everything
createDefaultSegment();
@@ -741,9 +884,7 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc)
{
return 0;
}
- LLWString text(getWText());
- text[pos] = wc;
- getViewModel()->setDisplay(text);
+ getViewModel()->getEditableDisplay()[pos] = wc;
onValueChange(pos, pos + 1);
needsReflow(pos);
@@ -1111,6 +1252,100 @@ void LLTextBase::deselect()
mIsSelecting = FALSE;
}
+bool LLTextBase::getSpellCheck() const
+{
+ return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck);
+}
+
+const std::string& LLTextBase::getSuggestion(U32 index) const
+{
+ return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null;
+}
+
+U32 LLTextBase::getSuggestionCount() const
+{
+ return mSuggestionList.size();
+}
+
+void LLTextBase::replaceWithSuggestion(U32 index)
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) )
+ {
+ deselect();
+
+ // Delete the misspelled word
+ removeStringNoUndo(it->first, it->second - it->first);
+
+ // Insert the suggestion in its place
+ LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]);
+ insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index]));
+ setCursorPos(it->first + (S32)suggestion.length());
+
+ break;
+ }
+ }
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
+
+void LLTextBase::addToDictionary()
+{
+ if (canAddToDictionary())
+ {
+ LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLTextBase::canAddToDictionary() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
+}
+
+void LLTextBase::addToIgnore()
+{
+ if (canAddToIgnore())
+ {
+ LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos));
+ }
+}
+
+bool LLTextBase::canAddToIgnore() const
+{
+ return (getSpellCheck()) && (isMisspelledWord(mCursorPos));
+}
+
+std::string LLTextBase::getMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first));
+ }
+ }
+ return LLStringUtil::null;
+}
+
+bool LLTextBase::isMisspelledWord(U32 pos) const
+{
+ for (std::list<std::pair<U32, U32> >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it)
+ {
+ if ( (it->first <= pos) && (it->second >= pos) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+void LLTextBase::onSpellCheckSettingsChange()
+{
+ // Recheck the spelling on every change
+ mMisspellRanges.clear();
+ mSpellCheckStart = mSpellCheckEnd = -1;
+}
+
void LLTextBase::onFocusReceived()
{
if (!getLength() && !mLabel.empty())
@@ -1709,6 +1944,8 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name)
}
}
+static LLFastTimer::DeclareTimer FTM_PARSE_HTML("Parse HTML");
+
void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params)
{
LLStyle::Params style_params(input_params);
@@ -1717,15 +1954,13 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
S32 part = (S32)LLTextParser::WHOLE;
if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358).
{
+ LLFastTimer _(FTM_PARSE_HTML);
S32 start=0,end=0;
LLUrlMatch match;
std::string text = new_text;
while ( LLUrlRegistry::instance().findUrl(text, match,
boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) )
{
-
- LLTextUtil::processUrlMatch(&match,this);
-
start = match.getStart();
end = match.getEnd()+1;
@@ -1761,6 +1996,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
}
}
+ LLTextUtil::processUrlMatch(&match,this);
+
// move on to the rest of the text after the Url
if (end < (S32)text.length())
{
@@ -1784,8 +2021,11 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para
}
}
+static LLFastTimer::DeclareTimer FTM_APPEND_TEXT("Append Text");
+
void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params)
{
+ LLFastTimer _(FTM_APPEND_TEXT);
if (new_text.empty())
return;
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
index 412272b352..79662ebd33 100644
--- a/indra/llui/lltextbase.h
+++ b/indra/llui/lltextbase.h
@@ -30,6 +30,7 @@
#include "v4color.h"
#include "lleditmenuhandler.h"
+#include "llspellcheckmenuhandler.h"
#include "llstyle.h"
#include "llkeywords.h"
#include "llpanel.h"
@@ -248,7 +249,8 @@ typedef LLPointer<LLTextSegment> LLTextSegmentPtr;
///
class LLTextBase
: public LLUICtrl,
- protected LLEditMenuHandler
+ protected LLEditMenuHandler,
+ public LLSpellCheckMenuHandler
{
public:
friend class LLTextSegment;
@@ -278,6 +280,7 @@ public:
border_visible,
track_end,
read_only,
+ spellcheck,
allow_scroll,
plain_text,
wrap,
@@ -333,6 +336,24 @@ public:
virtual void onFocusReceived();
virtual void onFocusLost();
+ // LLSpellCheckMenuHandler overrides
+ /*virtual*/ bool getSpellCheck() const;
+
+ /*virtual*/ const std::string& getSuggestion(U32 index) const;
+ /*virtual*/ U32 getSuggestionCount() const;
+ /*virtual*/ void replaceWithSuggestion(U32 index);
+
+ /*virtual*/ void addToDictionary();
+ /*virtual*/ bool canAddToDictionary() const;
+
+ /*virtual*/ void addToIgnore();
+ /*virtual*/ bool canAddToIgnore() const;
+
+ // Spell checking helper functions
+ std::string getMisspelledWord(U32 pos) const;
+ bool isMisspelledWord(U32 pos) const;
+ void onSpellCheckSettingsChange();
+
// used by LLTextSegment layout code
bool getWordWrap() { return mWordWrap; }
bool getUseEllipses() { return mUseEllipses; }
@@ -578,6 +599,14 @@ protected:
BOOL mIsSelecting; // Are we in the middle of a drag-select?
+ // spell checking
+ bool mSpellCheck;
+ S32 mSpellCheckStart;
+ S32 mSpellCheckEnd;
+ LLTimer mSpellCheckTimer;
+ std::list<std::pair<U32, U32> > mMisspellRanges;
+ std::vector<std::string> mSuggestionList;
+
// configuration
S32 mHPad; // padding on left of text
S32 mVPad; // padding above text
diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp
index 6a905b7ec0..11cfa1d263 100644
--- a/indra/llui/lltextbox.cpp
+++ b/indra/llui/lltextbox.cpp
@@ -150,7 +150,7 @@ S32 LLTextBox::getTextPixelHeight()
LLSD LLTextBox::getValue() const
{
- return LLSD(getText());
+ return getViewModel()->getValue();
}
BOOL LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text )
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 4fa6ea085e..d42d6473ed 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -54,6 +54,7 @@
#include "llwindow.h"
#include "lltextparser.h"
#include "llscrollcontainer.h"
+#include "llspellcheck.h"
#include "llpanel.h"
#include "llurlregistry.h"
#include "lltooltip.h"
@@ -77,6 +78,7 @@ template class LLTextEditor* LLView::getChild<class LLTextEditor>(
const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32;
const S32 UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4;
const S32 SPACES_PER_TAB = 4;
+const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on
///////////////////////////////////////////////////////////////////
@@ -1959,7 +1961,38 @@ void LLTextEditor::showContextMenu(S32 x, S32 y)
S32 screen_x, screen_y;
localPointToScreen(x, y, &screen_x, &screen_y);
- mContextMenu->show(screen_x, screen_y);
+
+ setCursorAtLocalPos(x, y, false);
+ if (hasSelection())
+ {
+ if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) )
+ {
+ deselect();
+ }
+ else
+ {
+ setCursorPos(llmax(mSelectionStart, mSelectionEnd));
+ }
+ }
+
+ bool use_spellcheck = getSpellCheck(), is_misspelled = false;
+ if (use_spellcheck)
+ {
+ mSuggestionList.clear();
+
+ // If the cursor is on a misspelled word, retrieve suggestions for it
+ std::string misspelled_word = getMisspelledWord(mCursorPos);
+ if ((is_misspelled = !misspelled_word.empty()) == true)
+ {
+ LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList);
+ }
+ }
+
+ mContextMenu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty()));
+ mContextMenu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled));
+ mContextMenu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled));
+ mContextMenu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled));
+ mContextMenu->show(screen_x, screen_y, this);
}
@@ -2844,6 +2877,9 @@ void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& cal
void LLTextEditor::onKeyStroke()
{
mKeystrokeSignal(this);
+
+ mSpellCheckStart = mSpellCheckEnd = -1;
+ mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY);
}
//virtual
diff --git a/indra/llui/lltrans.cpp b/indra/llui/lltrans.cpp
new file mode 100644
index 0000000000..5388069c24
--- /dev/null
+++ b/indra/llui/lltrans.cpp
@@ -0,0 +1,295 @@
+/**
+ * @file lltrans.cpp
+ * @brief LLTrans implementation
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lltrans.h"
+
+#include "llfasttimer.h" // for call count statistics
+#include "llxuiparser.h"
+#include "llsd.h"
+#include "llxmlnode.h"
+
+#include <map>
+
+LLTrans::template_map_t LLTrans::sStringTemplates;
+LLStringUtil::format_map_t LLTrans::sDefaultArgs;
+
+struct StringDef : public LLInitParam::Block<StringDef>
+{
+ Mandatory<std::string> name;
+ Mandatory<std::string> value;
+
+ StringDef()
+ : name("name"),
+ value("value")
+ {}
+};
+
+struct StringTable : public LLInitParam::Block<StringTable>
+{
+ Multiple<StringDef> strings;
+ StringTable()
+ : strings("string")
+ {}
+};
+
+//static
+bool LLTrans::parseStrings(LLXMLNodePtr &root, const std::set<std::string>& default_args)
+{
+ std::string xml_filename = "(strings file)";
+ if (!root->hasName("strings"))
+ {
+ llerrs << "Invalid root node name in " << xml_filename
+ << ": was " << root->getName() << ", expected \"strings\"" << llendl;
+ }
+
+ StringTable string_table;
+ LLXUIParser parser;
+ parser.readXUI(root, string_table, xml_filename);
+
+ if (!string_table.validateBlock())
+ {
+ llerrs << "Problem reading strings: " << xml_filename << llendl;
+ return false;
+ }
+
+ sStringTemplates.clear();
+ sDefaultArgs.clear();
+
+ for(LLInitParam::ParamIterator<StringDef>::const_iterator it = string_table.strings.begin();
+ it != string_table.strings.end();
+ ++it)
+ {
+ LLTransTemplate xml_template(it->name, it->value);
+ sStringTemplates[xml_template.mName] = xml_template;
+
+ std::set<std::string>::const_iterator iter = default_args.find(xml_template.mName);
+ if (iter != default_args.end())
+ {
+ std::string name = *iter;
+ if (name[0] != '[')
+ name = llformat("[%s]",name.c_str());
+ sDefaultArgs[name] = xml_template.mText;
+ }
+ }
+
+ return true;
+}
+
+
+//static
+bool LLTrans::parseLanguageStrings(LLXMLNodePtr &root)
+{
+ std::string xml_filename = "(language strings file)";
+ if (!root->hasName("strings"))
+ {
+ llerrs << "Invalid root node name in " << xml_filename
+ << ": was " << root->getName() << ", expected \"strings\"" << llendl;
+ }
+
+ StringTable string_table;
+ LLXUIParser parser;
+ parser.readXUI(root, string_table, xml_filename);
+
+ if (!string_table.validateBlock())
+ {
+ llerrs << "Problem reading strings: " << xml_filename << llendl;
+ return false;
+ }
+
+ for(LLInitParam::ParamIterator<StringDef>::const_iterator it = string_table.strings.begin();
+ it != string_table.strings.end();
+ ++it)
+ {
+ // share the same map with parseStrings() so we can search the strings using the same getString() function.- angela
+ LLTransTemplate xml_template(it->name, it->value);
+ sStringTemplates[xml_template.mName] = xml_template;
+ }
+
+ return true;
+}
+
+
+
+static LLFastTimer::DeclareTimer FTM_GET_TRANS("Translate string");
+
+//static
+std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args)
+{
+ // Don't care about time as much as call count. Make sure we're not
+ // calling LLTrans::getString() in an inner loop. JC
+ LLFastTimer timer(FTM_GET_TRANS);
+
+ template_map_t::iterator iter = sStringTemplates.find(xml_desc);
+ if (iter != sStringTemplates.end())
+ {
+ std::string text = iter->second.mText;
+ LLStringUtil::format_map_t args = sDefaultArgs;
+ args.insert(msg_args.begin(), msg_args.end());
+ LLStringUtil::format(text, args);
+
+ return text;
+ }
+ else
+ {
+ LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL;
+ return "MissingString("+xml_desc+")";
+ }
+}
+
+//static
+std::string LLTrans::getString(const std::string &xml_desc, const LLSD& msg_args)
+{
+ // Don't care about time as much as call count. Make sure we're not
+ // calling LLTrans::getString() in an inner loop. JC
+ LLFastTimer timer(FTM_GET_TRANS);
+
+ template_map_t::iterator iter = sStringTemplates.find(xml_desc);
+ if (iter != sStringTemplates.end())
+ {
+ std::string text = iter->second.mText;
+ LLStringUtil::format(text, msg_args);
+ return text;
+ }
+ else
+ {
+ LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL;
+ return "MissingString("+xml_desc+")";
+ }
+}
+
+//static
+bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& msg_args)
+{
+ LLFastTimer timer(FTM_GET_TRANS);
+
+ template_map_t::iterator iter = sStringTemplates.find(xml_desc);
+ if (iter != sStringTemplates.end())
+ {
+ std::string text = iter->second.mText;
+ LLStringUtil::format_map_t args = sDefaultArgs;
+ args.insert(msg_args.begin(), msg_args.end());
+ LLStringUtil::format(text, args);
+ result = text;
+ return true;
+ }
+ else
+ {
+ LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL;
+ return false;
+ }
+}
+
+//static
+bool LLTrans::findString(std::string &result, const std::string &xml_desc, const LLSD& msg_args)
+{
+ LLFastTimer timer(FTM_GET_TRANS);
+
+ template_map_t::iterator iter = sStringTemplates.find(xml_desc);
+ if (iter != sStringTemplates.end())
+ {
+ std::string text = iter->second.mText;
+ LLStringUtil::format(text, msg_args);
+ result = text;
+ return true;
+ }
+ else
+ {
+ LL_WARNS_ONCE("configuration") << "Missing String in strings.xml: [" << xml_desc << "]" << LL_ENDL;
+ return false;
+ }
+}
+
+//static
+std::string LLTrans::getCountString(const std::string& language, const std::string& xml_desc, S32 count)
+{
+ // Compute which string identifier to use
+ const char* form = "";
+ if (language == "ru") // Russian
+ {
+ // From GNU ngettext()
+ // Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
+ if (count % 10 == 1
+ && count % 100 != 11)
+ {
+ // singular, "1 item"
+ form = "A";
+ }
+ else if (count % 10 >= 2
+ && count % 10 <= 4
+ && (count % 100 < 10 || count % 100 >= 20) )
+ {
+ // special case "2 items", "23 items", but not "13 items"
+ form = "B";
+ }
+ else
+ {
+ // English-style plural, "5 items"
+ form = "C";
+ }
+ }
+ else if (language == "fr" || language == "pt") // French, Brazilian Portuguese
+ {
+ // French and Portuguese treat zero as a singular "0 item" not "0 items"
+ if (count == 0 || count == 1)
+ {
+ form = "A";
+ }
+ else
+ {
+ // English-style plural
+ form = "B";
+ }
+ }
+ else // default
+ {
+ // languages like English with 2 forms, singular and plural
+ if (count == 1)
+ {
+ // "1 item"
+ form = "A";
+ }
+ else
+ {
+ // "2 items", also use plural for "0 items"
+ form = "B";
+ }
+ }
+
+ // Translate that string
+ LLStringUtil::format_map_t args;
+ args["[COUNT]"] = llformat("%d", count);
+
+ // Look up "AgeYearsB" or "AgeWeeksC" including the "form"
+ std::string key = llformat("%s%s", xml_desc.c_str(), form);
+ return getString(key, args);
+}
+
+void LLTrans::setDefaultArg(const std::string& name, const std::string& value)
+{
+ sDefaultArgs[name] = value;
+}
diff --git a/indra/llui/lltrans.h b/indra/llui/lltrans.h
new file mode 100644
index 0000000000..128b51d383
--- /dev/null
+++ b/indra/llui/lltrans.h
@@ -0,0 +1,133 @@
+/**
+ * @file lltrans.h
+ * @brief LLTrans definition
+ *
+ * $LicenseInfo:firstyear=2000&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_TRANS_H
+#define LL_TRANS_H
+
+#include <map>
+
+#include "llpointer.h"
+#include "llstring.h"
+
+class LLXMLNode;
+
+class LLSD;
+
+/**
+ * @brief String template loaded from strings.xml
+ */
+class LLTransTemplate
+{
+public:
+ LLTransTemplate(const std::string& name = LLStringUtil::null, const std::string& text = LLStringUtil::null) : mName(name), mText(text) {}
+
+ std::string mName;
+ std::string mText;
+};
+
+/**
+ * @brief Localized strings class
+ * This class is used to retrieve translations of strings used to build larger ones, as well as
+ * strings with a general usage that don't belong to any specific floater. For example,
+ * "Owner:", "Retrieving..." used in the place of a not yet known name, etc.
+ */
+class LLTrans
+{
+public:
+ LLTrans();
+
+ /**
+ * @brief Parses the xml root that holds the strings. Used once on startup
+// *FIXME * @param xml_filename Filename to parse
+ * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE"
+ * @returns true if the file was parsed successfully, true if something went wrong
+ */
+ static bool parseStrings(LLPointer<LLXMLNode> & root, const std::set<std::string>& default_args);
+
+ static bool parseLanguageStrings(LLPointer<LLXMLNode> & root);
+
+ /**
+ * @brief Returns a translated string
+ * @param xml_desc String's description
+ * @param args A list of substrings to replace in the string
+ * @returns Translated string
+ */
+ static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args);
+ static std::string getString(const std::string &xml_desc, const LLSD& args);
+ static bool findString(std::string &result, const std::string &xml_desc, const LLStringUtil::format_map_t& args);
+ static bool findString(std::string &result, const std::string &xml_desc, const LLSD& args);
+
+ // Returns translated string with [COUNT] replaced with a number, following
+ // special per-language logic for plural nouns. For example, some languages
+ // may have different plurals for 0, 1, 2 and > 2.
+ // See "AgeWeeksA", "AgeWeeksB", etc. in strings.xml for examples.
+ static std::string getCountString(const std::string& language, const std::string& xml_desc, S32 count);
+
+ /**
+ * @brief Returns a translated string
+ * @param xml_desc String's description
+ * @returns Translated string
+ */
+ static std::string getString(const std::string &xml_desc)
+ {
+ LLStringUtil::format_map_t empty;
+ return getString(xml_desc, empty);
+ }
+
+ static bool findString(std::string &result, const std::string &xml_desc)
+ {
+ LLStringUtil::format_map_t empty;
+ return findString(result, xml_desc, empty);
+ }
+
+ static std::string getKeyboardString(const char* keystring)
+ {
+ std::string key_str(keystring);
+ std::string trans_str;
+ return findString(trans_str, key_str) ? trans_str : key_str;
+ }
+
+ // get the default args
+ static const LLStringUtil::format_map_t& getDefaultArgs()
+ {
+ return sDefaultArgs;
+ }
+
+ static void setDefaultArg(const std::string& name, const std::string& value);
+
+ // insert default args into an arg list
+ static void getArgs(LLStringUtil::format_map_t& args)
+ {
+ args.insert(sDefaultArgs.begin(), sDefaultArgs.end());
+ }
+
+private:
+ typedef std::map<std::string, LLTransTemplate > template_map_t;
+ static template_map_t sStringTemplates;
+ static LLStringUtil::format_map_t sDefaultArgs;
+};
+
+#endif
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index 8da0d58f51..41a948e545 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -831,7 +831,11 @@ void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LL
gGL.flush();
glLineWidth(2.5f);
- glLineStipple(2, 0x3333 << shift);
+
+ if (!LLGLSLShader::sNoFixedFunction)
+ {
+ glLineStipple(2, 0x3333 << shift);
+ }
gGL.begin(LLRender::LINES);
{
diff --git a/indra/llui/lluicolor.cpp b/indra/llui/lluicolor.cpp
new file mode 100644
index 0000000000..f9bb80f8c5
--- /dev/null
+++ b/indra/llui/lluicolor.cpp
@@ -0,0 +1,87 @@
+/**
+ * @file lluicolor.cpp
+ * @brief brief LLUIColor class implementation file
+ *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lluicolor.h"
+
+LLUIColor::LLUIColor()
+ :mColorPtr(NULL)
+{
+}
+
+
+LLUIColor::LLUIColor(const LLColor4& color)
+: mColor(color),
+ mColorPtr(NULL)
+{
+}
+
+LLUIColor::LLUIColor(const LLUIColor* color)
+: mColorPtr(color)
+{
+}
+
+void LLUIColor::set(const LLColor4& color)
+{
+ mColor = color;
+ mColorPtr = NULL;
+}
+
+void LLUIColor::set(const LLUIColor* color)
+{
+ mColorPtr = color;
+}
+
+const LLColor4& LLUIColor::get() const
+{
+ return (mColorPtr == NULL ? mColor : mColorPtr->get());
+}
+
+LLUIColor::operator const LLColor4& () const
+{
+ return get();
+}
+
+const LLColor4& LLUIColor::operator()() const
+{
+ return get();
+}
+
+bool LLUIColor::isReference() const
+{
+ return mColorPtr != NULL;
+}
+
+namespace LLInitParam
+{
+ // used to detect equivalence with default values on export
+ bool ParamCompare<LLUIColor, false>::equals(const LLUIColor &a, const LLUIColor &b)
+ {
+ // do not detect value equivalence, treat pointers to colors as distinct from color values
+ return (a.mColorPtr == NULL && b.mColorPtr == NULL ? a.mColor == b.mColor : a.mColorPtr == b.mColorPtr);
+ }
+}
diff --git a/indra/llui/lluicolor.h b/indra/llui/lluicolor.h
new file mode 100644
index 0000000000..97ebea854a
--- /dev/null
+++ b/indra/llui/lluicolor.h
@@ -0,0 +1,71 @@
+/**
+ * @file lluicolor.h
+ * @brief brief LLUIColor class header file
+ *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLUICOLOR_H_
+#define LL_LLUICOLOR_H_
+
+#include "v4color.h"
+
+namespace LLInitParam
+{
+ template<typename T, bool>
+ struct ParamCompare;
+}
+
+class LLUIColor
+{
+public:
+ LLUIColor();
+ LLUIColor(const LLColor4& color);
+ LLUIColor(const LLUIColor* color);
+
+ void set(const LLColor4& color);
+ void set(const LLUIColor* color);
+
+ const LLColor4& get() const;
+
+ operator const LLColor4& () const;
+ const LLColor4& operator()() const;
+
+ bool isReference() const;
+
+private:
+ friend struct LLInitParam::ParamCompare<LLUIColor, false>;
+
+ const LLUIColor* mColorPtr;
+ LLColor4 mColor;
+};
+
+namespace LLInitParam
+{
+ template<>
+ struct ParamCompare<LLUIColor, false>
+ {
+ static bool equals(const LLUIColor& a, const LLUIColor& b);
+ };
+}
+
+#endif
diff --git a/indra/llui/llviewmodel.h b/indra/llui/llviewmodel.h
index 763af5d8a2..ef2e314799 100644
--- a/indra/llui/llviewmodel.h
+++ b/indra/llui/llviewmodel.h
@@ -102,6 +102,7 @@ public:
// New functions
/// Get the stored value in string form
const LLWString& getDisplay() const { return mDisplay; }
+ LLWString& getEditableDisplay() { mDirty = true; mUpdateFromDisplay = true; return mDisplay; }
/**
* Set the display string directly (see LLTextEditor). What the user is
diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp
new file mode 100644
index 0000000000..3ad5ad7d42
--- /dev/null
+++ b/indra/llui/llxuiparser.cpp
@@ -0,0 +1,1786 @@
+/**
+ * @file llxuiparser.cpp
+ * @brief Utility functions for handling XUI structures in XML
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llxuiparser.h"
+
+#include "llxmlnode.h"
+
+#ifdef LL_STANDALONE
+#include <expat.h>
+#else
+#include "expat/expat.h"
+#endif
+
+#include <fstream>
+#include <boost/tokenizer.hpp>
+//#include <boost/spirit/include/qi.hpp>
+#include <boost/spirit/include/classic_core.hpp>
+
+#include "lluicolor.h"
+#include "v3math.h"
+using namespace BOOST_SPIRIT_CLASSIC_NS;
+
+const S32 MAX_STRING_ATTRIBUTE_SIZE = 40;
+
+static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs;
+static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs;
+static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs;
+
+static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs;
+static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs;
+static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs;
+
+const char* NO_VALUE_MARKER = "no_value";
+
+const S32 LINE_NUMBER_HERE = 0;
+
+struct MaxOccursValues : public LLInitParam::TypeValuesHelper<U32, MaxOccursValues>
+{
+ static void declareValues()
+ {
+ declare("unbounded", U32_MAX);
+ }
+};
+
+struct Occurs : public LLInitParam::Block<Occurs>
+{
+ Optional<U32> minOccurs;
+ Optional<U32, MaxOccursValues> maxOccurs;
+
+ Occurs()
+ : minOccurs("minOccurs", 0),
+ maxOccurs("maxOccurs", U32_MAX)
+
+ {}
+};
+
+typedef enum
+{
+ USE_REQUIRED,
+ USE_OPTIONAL
+} EUse;
+
+namespace LLInitParam
+{
+ template<>
+ struct TypeValues<EUse> : public TypeValuesHelper<EUse>
+ {
+ static void declareValues()
+ {
+ declare("required", USE_REQUIRED);
+ declare("optional", USE_OPTIONAL);
+ }
+ };
+}
+
+struct Element;
+struct Group;
+struct Sequence;
+
+struct All : public LLInitParam::Block<All, Occurs>
+{
+ Multiple< Lazy<Element, IS_A_BLOCK> > elements;
+
+ All()
+ : elements("element")
+ {
+ maxOccurs = 1;
+ }
+};
+
+struct Attribute : public LLInitParam::Block<Attribute>
+{
+ Mandatory<std::string> name,
+ type;
+ Mandatory<EUse> use;
+
+ Attribute()
+ : name("name"),
+ type("type"),
+ use("use")
+ {}
+};
+
+struct Any : public LLInitParam::Block<Any, Occurs>
+{
+ Optional<std::string> _namespace;
+
+ Any()
+ : _namespace("namespace")
+ {}
+};
+
+struct Choice : public LLInitParam::ChoiceBlock<Choice, Occurs>
+{
+ Alternative< Lazy<Element, IS_A_BLOCK> > element;
+ Alternative< Lazy<Group, IS_A_BLOCK> > group;
+ Alternative< Lazy<Choice, IS_A_BLOCK> > choice;
+ Alternative< Lazy<Sequence, IS_A_BLOCK> > sequence;
+ Alternative< Lazy<Any> > any;
+
+ Choice()
+ : element("element"),
+ group("group"),
+ choice("choice"),
+ sequence("sequence"),
+ any("any")
+ {}
+
+};
+
+struct Sequence : public LLInitParam::ChoiceBlock<Sequence, Occurs>
+{
+ Alternative< Lazy<Element, IS_A_BLOCK> > element;
+ Alternative< Lazy<Group, IS_A_BLOCK> > group;
+ Alternative< Lazy<Choice> > choice;
+ Alternative< Lazy<Sequence, IS_A_BLOCK> > sequence;
+ Alternative< Lazy<Any> > any;
+};
+
+struct GroupContents : public LLInitParam::ChoiceBlock<GroupContents, Occurs>
+{
+ Alternative<All> all;
+ Alternative<Choice> choice;
+ Alternative<Sequence> sequence;
+
+ GroupContents()
+ : all("all"),
+ choice("choice"),
+ sequence("sequence")
+ {}
+};
+
+struct Group : public LLInitParam::Block<Group, GroupContents>
+{
+ Optional<std::string> name,
+ ref;
+
+ Group()
+ : name("name"),
+ ref("ref")
+ {}
+};
+
+struct Restriction : public LLInitParam::Block<Restriction>
+{
+};
+
+struct Extension : public LLInitParam::Block<Extension>
+{
+};
+
+struct SimpleContent : public LLInitParam::ChoiceBlock<SimpleContent>
+{
+ Alternative<Restriction> restriction;
+ Alternative<Extension> extension;
+
+ SimpleContent()
+ : restriction("restriction"),
+ extension("extension")
+ {}
+};
+
+struct SimpleType : public LLInitParam::Block<SimpleType>
+{
+ // TODO
+};
+
+struct ComplexContent : public LLInitParam::Block<ComplexContent, SimpleContent>
+{
+ Optional<bool> mixed;
+
+ ComplexContent()
+ : mixed("mixed", true)
+ {}
+};
+
+struct ComplexTypeContents : public LLInitParam::ChoiceBlock<ComplexTypeContents>
+{
+ Alternative<SimpleContent> simple_content;
+ Alternative<ComplexContent> complex_content;
+ Alternative<Group> group;
+ Alternative<All> all;
+ Alternative<Choice> choice;
+ Alternative<Sequence> sequence;
+
+ ComplexTypeContents()
+ : simple_content("simpleContent"),
+ complex_content("complexContent"),
+ group("group"),
+ all("all"),
+ choice("choice"),
+ sequence("sequence")
+ {}
+};
+
+struct ComplexType : public LLInitParam::Block<ComplexType, ComplexTypeContents>
+{
+ Optional<std::string> name;
+ Optional<bool> mixed;
+
+ Multiple<Attribute> attribute;
+ Multiple< Lazy<Element, IS_A_BLOCK > > elements;
+
+ ComplexType()
+ : name("name"),
+ attribute("xs:attribute"),
+ elements("xs:element"),
+ mixed("mixed")
+ {
+ }
+};
+
+struct ElementContents : public LLInitParam::ChoiceBlock<ElementContents, Occurs>
+{
+ Alternative<SimpleType> simpleType;
+ Alternative<ComplexType> complexType;
+
+ ElementContents()
+ : simpleType("simpleType"),
+ complexType("complexType")
+ {}
+};
+
+struct Element : public LLInitParam::Block<Element, ElementContents>
+{
+ Optional<std::string> name,
+ ref,
+ type;
+
+ Element()
+ : name("xs:name"),
+ ref("xs:ref"),
+ type("xs:type")
+ {}
+};
+
+struct Schema : public LLInitParam::Block<Schema>
+{
+private:
+ Mandatory<std::string> targetNamespace,
+ xmlns,
+ xs;
+
+public:
+ Optional<std::string> attributeFormDefault,
+ elementFormDefault;
+
+ Mandatory<Element> root_element;
+
+ void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;}
+
+ Schema(const std::string& ns = LLStringUtil::null)
+ : attributeFormDefault("attributeFormDefault"),
+ elementFormDefault("elementFormDefault"),
+ xs("xmlns:xs"),
+ targetNamespace("targetNamespace"),
+ xmlns("xmlns"),
+ root_element("xs:element")
+ {
+ attributeFormDefault = "unqualified";
+ elementFormDefault = "qualified";
+ xs = "http://www.w3.org/2001/XMLSchema";
+ if (!ns.empty())
+ {
+ setNameSpace(ns);
+ };
+ }
+};
+
+//
+// LLXSDWriter
+//
+LLXSDWriter::LLXSDWriter()
+: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs)
+{
+ registerInspectFunc<bool>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4));
+ registerInspectFunc<std::string>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4));
+ registerInspectFunc<U8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4));
+ registerInspectFunc<S8>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4));
+ registerInspectFunc<U16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4));
+ registerInspectFunc<S16>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4));
+ registerInspectFunc<U32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4));
+ registerInspectFunc<S32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4));
+ registerInspectFunc<F32>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4));
+ registerInspectFunc<F64>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4));
+ registerInspectFunc<LLColor4>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4));
+ registerInspectFunc<LLUIColor>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4));
+ registerInspectFunc<LLUUID>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4));
+ registerInspectFunc<LLSD>(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4));
+}
+
+void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace)
+{
+ Schema schema(xml_namespace);
+
+ schema.root_element.name = type_name;
+ Choice& choice = schema.root_element.complexType.choice;
+
+ choice.minOccurs = 0;
+ choice.maxOccurs = "unbounded";
+
+ mSchemaNode = node;
+ //node->setName("xs:schema");
+ //node->createChild("attributeFormDefault", true)->setStringValue("unqualified");
+ //node->createChild("elementFormDefault", true)->setStringValue("qualified");
+ //node->createChild("targetNamespace", true)->setStringValue(xml_namespace);
+ //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema");
+ //node->createChild("xmlns", true)->setStringValue(xml_namespace);
+
+ //node = node->createChild("xs:complexType", false);
+ //node->createChild("name", true)->setStringValue(type_name);
+ //node->createChild("mixed", true)->setStringValue("true");
+
+ //mAttributeNode = node;
+ //mElementNode = node->createChild("xs:choice", false);
+ //mElementNode->createChild("minOccurs", true)->setStringValue("0");
+ //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded");
+ block.inspectBlock(*this);
+
+ // duplicate element choices
+ LLXMLNodeList children;
+ mElementNode->getChildren("xs:element", children, FALSE);
+ for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it)
+ {
+ LLXMLNodePtr child_copy = child_it->second->deepCopy();
+ std::string child_name;
+ child_copy->getAttributeString("name", child_name);
+ child_copy->setAttributeString("name", type_name + "." + child_name);
+ mElementNode->addChild(child_copy);
+ }
+
+ LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false);
+ element_declaration_node->createChild("name", true)->setStringValue(type_name);
+ element_declaration_node->createChild("type", true)->setStringValue(type_name);
+}
+
+void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values)
+{
+ name_stack_t non_empty_names;
+ std::string attribute_name;
+ for (name_stack_t::const_iterator it = stack.begin();
+ it != stack.end();
+ ++it)
+ {
+ const std::string& name = it->first;
+ if (!name.empty())
+ {
+ non_empty_names.push_back(*it);
+ }
+ }
+
+ for (name_stack_t::const_iterator it = non_empty_names.begin();
+ it != non_empty_names.end();
+ ++it)
+ {
+ if (!attribute_name.empty())
+ {
+ attribute_name += ".";
+ }
+ attribute_name += it->first;
+ }
+
+ // only flag non-nested attributes as mandatory, nested attributes have variant syntax
+ // that can't be properly constrained in XSD
+ // e.g. <foo mandatory.value="bar"/> vs <foo><mandatory value="bar"/></foo>
+ bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1;
+
+ // don't bother supporting "Multiple" params as xml attributes
+ if (max_count <= 1)
+ {
+ // add compound attribute to root node
+ addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values);
+ }
+
+ // now generated nested elements for compound attributes
+ if (non_empty_names.size() > 1 && !attribute_mandatory)
+ {
+ std::string element_name;
+
+ // traverse all but last element, leaving that as an attribute name
+ name_stack_t::const_iterator end_it = non_empty_names.end();
+ end_it--;
+
+ for (name_stack_t::const_iterator it = non_empty_names.begin();
+ it != end_it;
+ ++it)
+ {
+ if (it != non_empty_names.begin())
+ {
+ element_name += ".";
+ }
+ element_name += it->first;
+ }
+
+ std::string short_attribute_name = non_empty_names.back().first;
+
+ LLXMLNodePtr complex_type_node;
+
+ // find existing element node here, starting at tail of child list
+ if (mElementNode->mChildren.notNull())
+ {
+ for(LLXMLNodePtr element = mElementNode->mChildren->tail;
+ element.notNull();
+ element = element->mPrev)
+ {
+ std::string name;
+ if(element->getAttributeString("name", name) && name == element_name)
+ {
+ complex_type_node = element->mChildren->head;
+ break;
+ }
+ }
+ }
+ //create complex_type node
+ //
+ //<xs:element
+ // maxOccurs="1"
+ // minOccurs="0"
+ // name="name">
+ // <xs:complexType>
+ // </xs:complexType>
+ //</xs:element>
+ if(complex_type_node.isNull())
+ {
+ complex_type_node = mElementNode->createChild("xs:element", false);
+
+ complex_type_node->createChild("minOccurs", true)->setIntValue(min_count);
+ complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count);
+ complex_type_node->createChild("name", true)->setStringValue(element_name);
+ complex_type_node = complex_type_node->createChild("xs:complexType", false);
+ }
+
+ addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values);
+ }
+}
+
+void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values)
+{
+ if (!attribute_name.empty())
+ {
+ LLXMLNodePtr new_enum_type_node;
+ if (possible_values != NULL)
+ {
+ // custom attribute type, for example
+ //<xs:simpleType>
+ // <xs:restriction
+ // base="xs:string">
+ // <xs:enumeration
+ // value="a" />
+ // <xs:enumeration
+ // value="b" />
+ // </xs:restriction>
+ // </xs:simpleType>
+ new_enum_type_node = new LLXMLNode("xs:simpleType", false);
+
+ LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false);
+ restriction_node->createChild("base", true)->setStringValue("xs:string");
+
+ for (std::vector<std::string>::const_iterator it = possible_values->begin();
+ it != possible_values->end();
+ ++it)
+ {
+ LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false);
+ enum_node->createChild("value", true)->setStringValue(*it);
+ }
+ }
+
+ string_set_t& attributes_written = mAttributesWritten[type_declaration_node];
+
+ string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name);
+
+ // attribute not yet declared
+ if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it))
+ {
+ attributes_written.insert(found_it, attribute_name);
+
+ LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false);
+
+ // attribute name
+ attribute_node->createChild("name", true)->setStringValue(attribute_name);
+
+ if (new_enum_type_node.notNull())
+ {
+ attribute_node->addChild(new_enum_type_node);
+ }
+ else
+ {
+ // simple attribute type
+ attribute_node->createChild("type", true)->setStringValue(type);
+ }
+
+ // required or optional
+ attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional");
+ }
+ // attribute exists...handle collision of same name attributes with potentially different types
+ else
+ {
+ LLXMLNodePtr attribute_declaration;
+ if (type_declaration_node.notNull())
+ {
+ for(LLXMLNodePtr node = type_declaration_node->mChildren->tail;
+ node.notNull();
+ node = node->mPrev)
+ {
+ std::string name;
+ if (node->getAttributeString("name", name) && name == attribute_name)
+ {
+ attribute_declaration = node;
+ break;
+ }
+ }
+ }
+
+ bool new_type_is_enum = new_enum_type_node.notNull();
+ bool existing_type_is_enum = !attribute_declaration->hasAttribute("type");
+
+ // either type is enum, revert to string in collision
+ // don't bother to check for enum equivalence
+ if (new_type_is_enum || existing_type_is_enum)
+ {
+ if (attribute_declaration->hasAttribute("type"))
+ {
+ attribute_declaration->setAttributeString("type", "xs:string");
+ }
+ else
+ {
+ attribute_declaration->createChild("type", true)->setStringValue("xs:string");
+ }
+ attribute_declaration->deleteChildren("xs:simpleType");
+ }
+ else
+ {
+ // check for collision of different standard types
+ std::string existing_type;
+ attribute_declaration->getAttributeString("type", existing_type);
+ // if current type is not the same as the new type, revert to strnig
+ if (existing_type != type)
+ {
+ // ...than use most general type, string
+ attribute_declaration->setAttributeString("type", "string");
+ }
+ }
+ }
+ }
+}
+
+//
+// LLXUIXSDWriter
+//
+void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block)
+{
+ std::string file_name(path);
+ file_name += type_name + ".xsd";
+ LLXMLNodePtr root_nodep = new LLXMLNode();
+
+ LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui");
+
+ // add includes for all possible children
+ const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name);
+ const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type);
+
+ // add choices for valid children
+ if (widget_registryp)
+ {
+ // add include declarations for all valid children
+ for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems();
+ it != widget_registryp->currentRegistrar().endItems();
+ ++it)
+ {
+ std::string widget_name = it->first;
+ if (widget_name == type_name)
+ {
+ continue;
+ }
+ LLXMLNodePtr nodep = new LLXMLNode("xs:include", false);
+ nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd");
+
+ // add to front of schema
+ mSchemaNode->addChild(nodep);
+ }
+
+ for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems();
+ it != widget_registryp->currentRegistrar().endItems();
+ ++it)
+ {
+ std::string widget_name = it->first;
+ //<xs:element name="widget_name" type="widget_name">
+ LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false);
+ widget_node->createChild("name", true)->setStringValue(widget_name);
+ widget_node->createChild("type", true)->setStringValue(widget_name);
+ }
+ }
+
+ LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w");
+ LLXMLNode::writeHeaderToFile(xsd_file);
+ root_nodep->writeToFile(xsd_file);
+ fclose(xsd_file);
+}
+
+static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs;
+static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs;
+static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs;
+
+//
+// LLXUIParser
+//
+LLXUIParser::LLXUIParser()
+: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs),
+ mCurReadDepth(0)
+{
+ if (sXUIReadFuncs.empty())
+ {
+ registerParserFuncs<LLInitParam::Flag>(readFlag, writeFlag);
+ registerParserFuncs<bool>(readBoolValue, writeBoolValue);
+ registerParserFuncs<std::string>(readStringValue, writeStringValue);
+ registerParserFuncs<U8>(readU8Value, writeU8Value);
+ registerParserFuncs<S8>(readS8Value, writeS8Value);
+ registerParserFuncs<U16>(readU16Value, writeU16Value);
+ registerParserFuncs<S16>(readS16Value, writeS16Value);
+ registerParserFuncs<U32>(readU32Value, writeU32Value);
+ registerParserFuncs<S32>(readS32Value, writeS32Value);
+ registerParserFuncs<F32>(readF32Value, writeF32Value);
+ registerParserFuncs<F64>(readF64Value, writeF64Value);
+ registerParserFuncs<LLVector3>(readVector3Value, writeVector3Value);
+ registerParserFuncs<LLColor4>(readColor4Value, writeColor4Value);
+ registerParserFuncs<LLUIColor>(readUIColorValue, writeUIColorValue);
+ registerParserFuncs<LLUUID>(readUUIDValue, writeUUIDValue);
+ registerParserFuncs<LLSD>(readSDValue, writeSDValue);
+ }
+}
+
+static LLFastTimer::DeclareTimer FTM_PARSE_XUI("XUI Parsing");
+const LLXMLNodePtr DUMMY_NODE = new LLXMLNode();
+
+void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent)
+{
+ LLFastTimer timer(FTM_PARSE_XUI);
+ mNameStack.clear();
+ mRootNodeName = node->getName()->mString;
+ mCurFileName = filename;
+ mCurReadDepth = 0;
+ setParseSilently(silent);
+
+ if (node.isNull())
+ {
+ parserWarning("Invalid node");
+ }
+ else
+ {
+ readXUIImpl(node, block);
+ }
+}
+
+bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block)
+{
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(".");
+
+ bool values_parsed = false;
+ bool silent = mCurReadDepth > 0;
+
+ if (nodep->getFirstChild().isNull()
+ && nodep->mAttributes.empty()
+ && nodep->getSanitizedValue().empty())
+ {
+ // empty node, just parse as flag
+ mCurReadNode = DUMMY_NODE;
+ return block.submitValue(mNameStack, *this, silent);
+ }
+
+ // submit attributes for current node
+ values_parsed |= readAttributes(nodep, block);
+
+ // treat text contents of xml node as "value" parameter
+ std::string text_contents = nodep->getSanitizedValue();
+ if (!text_contents.empty())
+ {
+ mCurReadNode = nodep;
+ mNameStack.push_back(std::make_pair(std::string("value"), true));
+ // child nodes are not necessarily valid parameters (could be a child widget)
+ // so don't complain once we've recursed
+ if (!block.submitValue(mNameStack, *this, true))
+ {
+ mNameStack.pop_back();
+ block.submitValue(mNameStack, *this, silent);
+ }
+ else
+ {
+ mNameStack.pop_back();
+ }
+ }
+
+ // then traverse children
+ // child node must start with last name of parent node (our "scope")
+ // for example: "<button><button.param nested_param1="foo"><param.nested_param2 nested_param3="bar"/></button.param></button>"
+ // which equates to the following nesting:
+ // button
+ // param
+ // nested_param1
+ // nested_param2
+ // nested_param3
+ mCurReadDepth++;
+ for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();)
+ {
+ std::string child_name(childp->getName()->mString);
+ S32 num_tokens_pushed = 0;
+
+ // for non "dotted" child nodes check to see if child node maps to another widget type
+ // and if not, treat as a child element of the current node
+ // e.g. <button><rect left="10"/></button> will interpret <rect> as "button.rect"
+ // since there is no widget named "rect"
+ if (child_name.find(".") == std::string::npos)
+ {
+ mNameStack.push_back(std::make_pair(child_name, true));
+ num_tokens_pushed++;
+ }
+ else
+ {
+ // parse out "dotted" name into individual tokens
+ tokenizer name_tokens(child_name, sep);
+
+ tokenizer::iterator name_token_it = name_tokens.begin();
+ if(name_token_it == name_tokens.end())
+ {
+ childp = childp->getNextSibling();
+ continue;
+ }
+
+ // check for proper nesting
+ if (mNameStack.empty())
+ {
+ if (*name_token_it != mRootNodeName)
+ {
+ childp = childp->getNextSibling();
+ continue;
+ }
+ }
+ else if(mNameStack.back().first != *name_token_it)
+ {
+ childp = childp->getNextSibling();
+ continue;
+ }
+
+ // now ignore first token
+ ++name_token_it;
+
+ // copy remaining tokens on to our running token list
+ for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push)
+ {
+ mNameStack.push_back(std::make_pair(*token_to_push, true));
+ num_tokens_pushed++;
+ }
+ }
+
+ // recurse and visit children XML nodes
+ if(readXUIImpl(childp, block))
+ {
+ // child node successfully parsed, remove from DOM
+
+ values_parsed = true;
+ LLXMLNodePtr node_to_remove = childp;
+ childp = childp->getNextSibling();
+
+ nodep->deleteChild(node_to_remove);
+ }
+ else
+ {
+ childp = childp->getNextSibling();
+ }
+
+ while(num_tokens_pushed-- > 0)
+ {
+ mNameStack.pop_back();
+ }
+ }
+ mCurReadDepth--;
+ return values_parsed;
+}
+
+bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block)
+{
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(".");
+
+ bool any_parsed = false;
+ bool silent = mCurReadDepth > 0;
+
+ for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin();
+ attribute_it != nodep->mAttributes.end();
+ ++attribute_it)
+ {
+ S32 num_tokens_pushed = 0;
+ std::string attribute_name(attribute_it->first->mString);
+ mCurReadNode = attribute_it->second;
+
+ tokenizer name_tokens(attribute_name, sep);
+ // copy remaining tokens on to our running token list
+ for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push)
+ {
+ mNameStack.push_back(std::make_pair(*token_to_push, true));
+ num_tokens_pushed++;
+ }
+
+ // child nodes are not necessarily valid attributes, so don't complain once we've recursed
+ any_parsed |= block.submitValue(mNameStack, *this, silent);
+
+ while(num_tokens_pushed-- > 0)
+ {
+ mNameStack.pop_back();
+ }
+ }
+
+ return any_parsed;
+}
+
+void LLXUIParser::writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::BaseBlock* diff_block)
+{
+ mWriteRootNode = node;
+ name_stack_t name_stack = Parser::name_stack_t();
+ block.serializeBlock(*this, name_stack, diff_block);
+ mOutNodes.clear();
+}
+
+// go from a stack of names to a specific XML node
+LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack)
+{
+ LLXMLNodePtr out_node = mWriteRootNode;
+
+ name_stack_t::iterator next_it = stack.begin();
+ for (name_stack_t::iterator it = stack.begin();
+ it != stack.end();
+ it = next_it)
+ {
+ ++next_it;
+ bool force_new_node = false;
+
+ if (it->first.empty())
+ {
+ it->second = false;
+ continue;
+ }
+
+ if (next_it != stack.end() && next_it->first.empty() && next_it->second)
+ {
+ force_new_node = true;
+ }
+
+
+ out_nodes_t::iterator found_it = mOutNodes.find(it->first);
+
+ // node with this name not yet written
+ if (found_it == mOutNodes.end() || it->second || force_new_node)
+ {
+ // make an attribute if we are the last element on the name stack
+ bool is_attribute = next_it == stack.end();
+ LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute);
+ out_node->addChild(new_node);
+ mOutNodes[it->first] = new_node;
+ out_node = new_node;
+ it->second = false;
+ }
+ else
+ {
+ out_node = found_it->second;
+ }
+ }
+
+ return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node);
+}
+
+bool LLXUIParser::readFlag(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode == DUMMY_NODE;
+}
+
+bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ // just create node
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ return node.notNull();
+}
+
+bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr)
+{
+ S32 value;
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ bool success = self.mCurReadNode->getBoolValue(1, &value);
+ *((bool*)val_ptr) = (value != FALSE);
+ return success;
+}
+
+bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setBoolValue(*((bool*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue();
+ return true;
+}
+
+bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ const std::string* string_val = reinterpret_cast<const std::string*>(val_ptr);
+ if (string_val->find('\n') != std::string::npos
+ || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE)
+ {
+ // don't write strings with newlines into attributes
+ std::string attribute_name = node->getName()->mString;
+ LLXMLNodePtr parent_node = node->mParent;
+ parent_node->deleteChild(node);
+ // write results in text contents of node
+ if (attribute_name == "value")
+ {
+ // "value" is implicit, just write to parent
+ node = parent_node;
+ }
+ else
+ {
+ // create a child that is not an attribute, but with same name
+ node = parent_node->createChild(attribute_name.c_str(), false);
+ }
+ }
+ node->setStringValue(*string_val);
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode->getByteValue(1, (U8*)val_ptr);
+}
+
+bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setUnsignedValue(*((U8*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ S32 value;
+ if(self.mCurReadNode->getIntValue(1, &value))
+ {
+ *((S8*)val_ptr) = value;
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setIntValue(*((S8*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ U32 value;
+ if(self.mCurReadNode->getUnsignedValue(1, &value))
+ {
+ *((U16*)val_ptr) = value;
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setUnsignedValue(*((U16*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ S32 value;
+ if(self.mCurReadNode->getIntValue(1, &value))
+ {
+ *((S16*)val_ptr) = value;
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setIntValue(*((S16*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr);
+}
+
+bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setUnsignedValue(*((U32*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode->getIntValue(1, (S32*)val_ptr);
+}
+
+bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setIntValue(*((S32*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr);
+}
+
+bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setFloatValue(*((F32*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr);
+}
+
+bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setDoubleValue(*((F64*)val_ptr));
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readVector3Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLVector3* vecp = (LLVector3*)val_ptr;
+ if(self.mCurReadNode->getFloatValue(3, vecp->mV) >= 3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool LLXUIParser::writeVector3Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ LLVector3 vector = *((LLVector3*)val_ptr);
+ node->setFloatValue(3, vector.mV);
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLColor4* colorp = (LLColor4*)val_ptr;
+ if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ LLColor4 color = *((LLColor4*)val_ptr);
+ node->setFloatValue(4, color.mV);
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLUIColor* param = (LLUIColor*)val_ptr;
+ LLColor4 color;
+ bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3;
+ if (success)
+ {
+ param->set(color);
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ LLUIColor color = *((LLUIColor*)val_ptr);
+ //RN: don't write out the color that is represented by a function
+ // rely on param block exporting to get the reference to the color settings
+ if (color.isReference()) return false;
+ node->setFloatValue(4, color.get().mV);
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLUUID temp_id;
+ // LLUUID::set is destructive, so use temporary value
+ if (temp_id.set(self.mCurReadNode->getSanitizedValue()))
+ {
+ *(LLUUID*)(val_ptr) = temp_id;
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ node->setStringValue(((LLUUID*)val_ptr)->asString());
+ return true;
+ }
+ return false;
+}
+
+bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+ *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue());
+ return true;
+}
+
+bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack)
+{
+ LLXUIParser& self = static_cast<LLXUIParser&>(parser);
+
+ LLXMLNodePtr node = self.getNode(stack);
+ if (node.notNull())
+ {
+ std::string string_val = ((LLSD*)val_ptr)->asString();
+ if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE)
+ {
+ // don't write strings with newlines into attributes
+ std::string attribute_name = node->getName()->mString;
+ LLXMLNodePtr parent_node = node->mParent;
+ parent_node->deleteChild(node);
+ // write results in text contents of node
+ if (attribute_name == "value")
+ {
+ // "value" is implicit, just write to parent
+ node = parent_node;
+ }
+ else
+ {
+ node = parent_node->createChild(attribute_name.c_str(), false);
+ }
+ }
+
+ node->setStringValue(string_val);
+ return true;
+ }
+ return false;
+}
+
+/*virtual*/ std::string LLXUIParser::getCurrentElementName()
+{
+ std::string full_name;
+ for (name_stack_t::iterator it = mNameStack.begin();
+ it != mNameStack.end();
+ ++it)
+ {
+ full_name += it->first + "."; // build up dotted names: "button.param.nestedparam."
+ }
+
+ return full_name;
+}
+
+void LLXUIParser::parserWarning(const std::string& message)
+{
+#ifdef LL_WINDOWS
+ // use Visual Studo friendly formatting of output message for easy access to originating xml
+ llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str());
+ utf16str += '\n';
+ OutputDebugString(utf16str.c_str());
+#else
+ Parser::parserWarning(message);
+#endif
+}
+
+void LLXUIParser::parserError(const std::string& message)
+{
+#ifdef LL_WINDOWS
+ llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), mCurReadNode->getLineNumber(), message.c_str()).c_str());
+ utf16str += '\n';
+ OutputDebugString(utf16str.c_str());
+#else
+ Parser::parserError(message);
+#endif
+}
+
+
+//
+// LLSimpleXUIParser
+//
+
+struct ScopedFile
+{
+ ScopedFile( const std::string& filename, const char* accessmode )
+ {
+ mFile = LLFile::fopen(filename, accessmode);
+ }
+
+ ~ScopedFile()
+ {
+ fclose(mFile);
+ mFile = NULL;
+ }
+
+ S32 getRemainingBytes()
+ {
+ if (!isOpen()) return 0;
+
+ S32 cur_pos = ftell(mFile);
+ fseek(mFile, 0L, SEEK_END);
+ S32 file_size = ftell(mFile);
+ fseek(mFile, cur_pos, SEEK_SET);
+ return file_size - cur_pos;
+ }
+
+ bool isOpen() { return mFile != NULL; }
+
+ LLFILE* mFile;
+};
+LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb)
+: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs),
+ mCurReadDepth(0),
+ mElementCB(element_cb)
+{
+ if (sSimpleXUIReadFuncs.empty())
+ {
+ registerParserFuncs<LLInitParam::Flag>(readFlag);
+ registerParserFuncs<bool>(readBoolValue);
+ registerParserFuncs<std::string>(readStringValue);
+ registerParserFuncs<U8>(readU8Value);
+ registerParserFuncs<S8>(readS8Value);
+ registerParserFuncs<U16>(readU16Value);
+ registerParserFuncs<S16>(readS16Value);
+ registerParserFuncs<U32>(readU32Value);
+ registerParserFuncs<S32>(readS32Value);
+ registerParserFuncs<F32>(readF32Value);
+ registerParserFuncs<F64>(readF64Value);
+ registerParserFuncs<LLColor4>(readColor4Value);
+ registerParserFuncs<LLUIColor>(readUIColorValue);
+ registerParserFuncs<LLUUID>(readUUIDValue);
+ registerParserFuncs<LLSD>(readSDValue);
+ }
+}
+
+LLSimpleXUIParser::~LLSimpleXUIParser()
+{
+}
+
+
+bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent)
+{
+ LLFastTimer timer(FTM_PARSE_XUI);
+
+ mParser = XML_ParserCreate(NULL);
+ XML_SetUserData(mParser, this);
+ XML_SetElementHandler( mParser, startElementHandler, endElementHandler);
+ XML_SetCharacterDataHandler( mParser, characterDataHandler);
+
+ mOutputStack.push_back(std::make_pair(&block, 0));
+ mNameStack.clear();
+ mCurFileName = filename;
+ mCurReadDepth = 0;
+ setParseSilently(silent);
+
+ ScopedFile file(filename, "rb");
+ if( !file.isOpen() )
+ {
+ LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL;
+ XML_ParserFree( mParser );
+ return false;
+ }
+
+ S32 bytes_read = 0;
+
+ S32 buffer_size = file.getRemainingBytes();
+ void* buffer = XML_GetBuffer(mParser, buffer_size);
+ if( !buffer )
+ {
+ LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL;
+ XML_ParserFree( mParser );
+ return false;
+ }
+
+ bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile);
+ if( bytes_read <= 0 )
+ {
+ LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL;
+ XML_ParserFree( mParser );
+ return false;
+ }
+
+ mEmptyLeafNode.push_back(false);
+
+ if( !XML_ParseBuffer(mParser, bytes_read, TRUE ) )
+ {
+ LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL;
+ XML_ParserFree( mParser );
+ return false;
+ }
+
+ mEmptyLeafNode.pop_back();
+
+ XML_ParserFree( mParser );
+ return true;
+}
+
+void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts)
+{
+ LLSimpleXUIParser* self = reinterpret_cast<LLSimpleXUIParser*>(userData);
+ self->startElement(name, atts);
+}
+
+void LLSimpleXUIParser::endElementHandler(void *userData, const char *name)
+{
+ LLSimpleXUIParser* self = reinterpret_cast<LLSimpleXUIParser*>(userData);
+ self->endElement(name);
+}
+
+void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len)
+{
+ LLSimpleXUIParser* self = reinterpret_cast<LLSimpleXUIParser*>(userData);
+ self->characterData(s, len);
+}
+
+void LLSimpleXUIParser::characterData(const char *s, int len)
+{
+ mTextContents += std::string(s, len);
+}
+
+void LLSimpleXUIParser::startElement(const char *name, const char **atts)
+{
+ processText();
+
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(".");
+
+ if (mElementCB)
+ {
+ LLInitParam::BaseBlock* blockp = mElementCB(*this, name);
+ if (blockp)
+ {
+ mOutputStack.push_back(std::make_pair(blockp, 0));
+ }
+ }
+
+ mOutputStack.back().second++;
+ S32 num_tokens_pushed = 0;
+ std::string child_name(name);
+
+ if (mOutputStack.back().second == 1)
+ { // root node for this block
+ mScope.push_back(child_name);
+ }
+ else
+ { // compound attribute
+ if (child_name.find(".") == std::string::npos)
+ {
+ mNameStack.push_back(std::make_pair(child_name, true));
+ num_tokens_pushed++;
+ mScope.push_back(child_name);
+ }
+ else
+ {
+ // parse out "dotted" name into individual tokens
+ tokenizer name_tokens(child_name, sep);
+
+ tokenizer::iterator name_token_it = name_tokens.begin();
+ if(name_token_it == name_tokens.end())
+ {
+ return;
+ }
+
+ // check for proper nesting
+ if(!mScope.empty() && *name_token_it != mScope.back())
+ {
+ return;
+ }
+
+ // now ignore first token
+ ++name_token_it;
+
+ // copy remaining tokens on to our running token list
+ for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push)
+ {
+ mNameStack.push_back(std::make_pair(*token_to_push, true));
+ num_tokens_pushed++;
+ }
+ mScope.push_back(mNameStack.back().first);
+ }
+ }
+
+ // parent node is not empty
+ mEmptyLeafNode.back() = false;
+ // we are empty if we have no attributes
+ mEmptyLeafNode.push_back(atts[0] == NULL);
+
+ mTokenSizeStack.push_back(num_tokens_pushed);
+ readAttributes(atts);
+
+}
+
+void LLSimpleXUIParser::endElement(const char *name)
+{
+ bool has_text = processText();
+
+ // no text, attributes, or children
+ if (!has_text && mEmptyLeafNode.back())
+ {
+ // submit this as a valueless name (even though there might be text contents we haven't seen yet)
+ mCurAttributeValueBegin = NO_VALUE_MARKER;
+ mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently);
+ }
+
+ if (--mOutputStack.back().second == 0)
+ {
+ if (mOutputStack.empty())
+ {
+ LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL;
+ }
+ mOutputStack.pop_back();
+ }
+
+ S32 num_tokens_to_pop = mTokenSizeStack.back();
+ mTokenSizeStack.pop_back();
+ while(num_tokens_to_pop-- > 0)
+ {
+ mNameStack.pop_back();
+ }
+ mScope.pop_back();
+ mEmptyLeafNode.pop_back();
+}
+
+bool LLSimpleXUIParser::readAttributes(const char **atts)
+{
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(".");
+
+ bool any_parsed = false;
+ for(S32 i = 0; atts[i] && atts[i+1]; i += 2 )
+ {
+ std::string attribute_name(atts[i]);
+ mCurAttributeValueBegin = atts[i+1];
+
+ S32 num_tokens_pushed = 0;
+ tokenizer name_tokens(attribute_name, sep);
+ // copy remaining tokens on to our running token list
+ for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push)
+ {
+ mNameStack.push_back(std::make_pair(*token_to_push, true));
+ num_tokens_pushed++;
+ }
+
+ // child nodes are not necessarily valid attributes, so don't complain once we've recursed
+ any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently);
+
+ while(num_tokens_pushed-- > 0)
+ {
+ mNameStack.pop_back();
+ }
+ }
+ return any_parsed;
+}
+
+bool LLSimpleXUIParser::processText()
+{
+ if (!mTextContents.empty())
+ {
+ LLStringUtil::trim(mTextContents);
+ if (!mTextContents.empty())
+ {
+ mNameStack.push_back(std::make_pair(std::string("value"), true));
+ mCurAttributeValueBegin = mTextContents.c_str();
+ mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently);
+ mNameStack.pop_back();
+ }
+ mTextContents.clear();
+ return true;
+ }
+ return false;
+}
+
+/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName()
+{
+ std::string full_name;
+ for (name_stack_t::iterator it = mNameStack.begin();
+ it != mNameStack.end();
+ ++it)
+ {
+ full_name += it->first + "."; // build up dotted names: "button.param.nestedparam."
+ }
+
+ return full_name;
+}
+
+void LLSimpleXUIParser::parserWarning(const std::string& message)
+{
+#ifdef LL_WINDOWS
+ // use Visual Studo friendly formatting of output message for easy access to originating xml
+ llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str());
+ utf16str += '\n';
+ OutputDebugString(utf16str.c_str());
+#else
+ Parser::parserWarning(message);
+#endif
+}
+
+void LLSimpleXUIParser::parserError(const std::string& message)
+{
+#ifdef LL_WINDOWS
+ llutf16string utf16str = utf8str_to_utf16str(llformat("%s(%d):\t%s", mCurFileName.c_str(), LINE_NUMBER_HERE, message.c_str()).c_str());
+ utf16str += '\n';
+ OutputDebugString(utf16str.c_str());
+#else
+ Parser::parserError(message);
+#endif
+}
+
+bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return self.mCurAttributeValueBegin == NO_VALUE_MARKER;
+}
+
+bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ if (!strcmp(self.mCurAttributeValueBegin, "true"))
+ {
+ *((bool*)val_ptr) = true;
+ return true;
+ }
+ else if (!strcmp(self.mCurAttributeValueBegin, "false"))
+ {
+ *((bool*)val_ptr) = false;
+ return true;
+ }
+
+ return false;
+}
+
+bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ *((std::string*)val_ptr) = self.mCurAttributeValueBegin;
+ return true;
+}
+
+bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full;
+}
+
+bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ LLColor4 value;
+
+ if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full)
+ {
+ *(LLColor4*)(val_ptr) = value;
+ return true;
+ }
+ return false;
+}
+
+bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ LLColor4 value;
+ LLUIColor* colorp = (LLUIColor*)val_ptr;
+
+ if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full)
+ {
+ colorp->set(value);
+ return true;
+ }
+ return false;
+}
+
+bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ LLUUID temp_id;
+ // LLUUID::set is destructive, so use temporary value
+ if (temp_id.set(std::string(self.mCurAttributeValueBegin)))
+ {
+ *(LLUUID*)(val_ptr) = temp_id;
+ return true;
+ }
+ return false;
+}
+
+bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr)
+{
+ LLSimpleXUIParser& self = static_cast<LLSimpleXUIParser&>(parser);
+ *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin);
+ return true;
+}
diff --git a/indra/llui/llxuiparser.h b/indra/llui/llxuiparser.h
new file mode 100644
index 0000000000..e48663e5cc
--- /dev/null
+++ b/indra/llui/llxuiparser.h
@@ -0,0 +1,244 @@
+/**
+ * @file llxuiparser.h
+ * @brief Utility functions for handling XUI structures in XML
+ *
+ * $LicenseInfo:firstyear=2003&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LLXUIPARSER_H
+#define LLXUIPARSER_H
+
+#include "llinitparam.h"
+#include "llregistry.h"
+#include "llpointer.h"
+
+#include <boost/function.hpp>
+#include <iosfwd>
+#include <stack>
+#include <set>
+
+
+
+class LLView;
+
+
+typedef LLPointer<class LLXMLNode> LLXMLNodePtr;
+
+
+// lookup widget type by name
+class LLWidgetTypeRegistry
+: public LLRegistrySingleton<std::string, const std::type_info*, LLWidgetTypeRegistry>
+{};
+
+
+// global static instance for registering all widget types
+typedef boost::function<LLView* (LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node)> LLWidgetCreatorFunc;
+
+typedef LLRegistry<std::string, LLWidgetCreatorFunc> widget_registry_t;
+
+class LLChildRegistryRegistry
+: public LLRegistrySingleton<const std::type_info*, widget_registry_t, LLChildRegistryRegistry>
+{};
+
+
+
+class LLXSDWriter : public LLInitParam::Parser
+{
+ LOG_CLASS(LLXSDWriter);
+public:
+ void writeXSD(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace);
+
+ /*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; }
+
+ LLXSDWriter();
+
+protected:
+ void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values);
+ void addAttributeToSchema(LLXMLNodePtr nodep, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector<std::string>* possible_values);
+ LLXMLNodePtr mAttributeNode;
+ LLXMLNodePtr mElementNode;
+ LLXMLNodePtr mSchemaNode;
+
+ typedef std::set<std::string> string_set_t;
+ typedef std::map<LLXMLNodePtr, string_set_t> attributes_map_t;
+ attributes_map_t mAttributesWritten;
+};
+
+
+
+// NOTE: DOES NOT WORK YET
+// should support child widgets for XUI
+class LLXUIXSDWriter : public LLXSDWriter
+{
+public:
+ void writeXSD(const std::string& name, const std::string& path, const LLInitParam::BaseBlock& block);
+};
+
+
+class LLXUIParserImpl;
+
+class LLXUIParser : public LLInitParam::Parser
+{
+LOG_CLASS(LLXUIParser);
+
+public:
+ LLXUIParser();
+ typedef LLInitParam::Parser::name_stack_t name_stack_t;
+
+ /*virtual*/ std::string getCurrentElementName();
+ /*virtual*/ void parserWarning(const std::string& message);
+ /*virtual*/ void parserError(const std::string& message);
+
+ void readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename = LLStringUtil::null, bool silent=false);
+ void writeXUI(LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const LLInitParam::BaseBlock* diff_block = NULL);
+
+private:
+ bool readXUIImpl(LLXMLNodePtr node, LLInitParam::BaseBlock& block);
+ bool readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block);
+
+ //reader helper functions
+ static bool readFlag(Parser& parser, void* val_ptr);
+ static bool readBoolValue(Parser& parser, void* val_ptr);
+ static bool readStringValue(Parser& parser, void* val_ptr);
+ static bool readU8Value(Parser& parser, void* val_ptr);
+ static bool readS8Value(Parser& parser, void* val_ptr);
+ static bool readU16Value(Parser& parser, void* val_ptr);
+ static bool readS16Value(Parser& parser, void* val_ptr);
+ static bool readU32Value(Parser& parser, void* val_ptr);
+ static bool readS32Value(Parser& parser, void* val_ptr);
+ static bool readF32Value(Parser& parser, void* val_ptr);
+ static bool readF64Value(Parser& parser, void* val_ptr);
+ static bool readVector3Value(Parser& parser, void* val_ptr);
+ static bool readColor4Value(Parser& parser, void* val_ptr);
+ static bool readUIColorValue(Parser& parser, void* val_ptr);
+ static bool readUUIDValue(Parser& parser, void* val_ptr);
+ static bool readSDValue(Parser& parser, void* val_ptr);
+
+ //writer helper functions
+ static bool writeFlag(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeStringValue(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeU8Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeS8Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeU16Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeS16Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeU32Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeS32Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeF32Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeF64Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeVector3Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t&);
+ static bool writeSDValue(Parser& parser, const void* val_ptr, name_stack_t&);
+
+ LLXMLNodePtr getNode(name_stack_t& stack);
+
+private:
+ Parser::name_stack_t mNameStack;
+ LLXMLNodePtr mCurReadNode;
+ // Root of the widget XML sub-tree, for example, "line_editor"
+ LLXMLNodePtr mWriteRootNode;
+
+ typedef std::map<std::string, LLXMLNodePtr> out_nodes_t;
+ out_nodes_t mOutNodes;
+ LLXMLNodePtr mLastWrittenChild;
+ S32 mCurReadDepth;
+ std::string mCurFileName;
+ std::string mRootNodeName;
+};
+
+// LLSimpleXUIParser is a streamlined SAX-based XUI parser that does not support localization
+// or parsing of a tree of independent param blocks, such as child widgets.
+// Use this for reading non-localized files that only need a single param block as a result.
+//
+// NOTE: In order to support nested block parsing, we need callbacks for start element that
+// push new blocks contexts on the mScope stack.
+// NOTE: To support localization without building a DOM, we need to enforce consistent
+// ordering of child elements from base file to localized diff file. Then we can use a pair
+// of coroutines to perform matching of xml nodes during parsing. Not sure if the overhead
+// of coroutines would offset the gain from SAX parsing
+class LLSimpleXUIParserImpl;
+
+class LLSimpleXUIParser : public LLInitParam::Parser
+{
+LOG_CLASS(LLSimpleXUIParser);
+public:
+ typedef LLInitParam::Parser::name_stack_t name_stack_t;
+ typedef LLInitParam::BaseBlock* (*element_start_callback_t)(LLSimpleXUIParser&, const char* block_name);
+
+ LLSimpleXUIParser(element_start_callback_t element_cb = NULL);
+ virtual ~LLSimpleXUIParser();
+
+ /*virtual*/ std::string getCurrentElementName();
+ /*virtual*/ void parserWarning(const std::string& message);
+ /*virtual*/ void parserError(const std::string& message);
+
+ bool readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent=false);
+
+
+private:
+ //reader helper functions
+ static bool readFlag(Parser&, void* val_ptr);
+ static bool readBoolValue(Parser&, void* val_ptr);
+ static bool readStringValue(Parser&, void* val_ptr);
+ static bool readU8Value(Parser&, void* val_ptr);
+ static bool readS8Value(Parser&, void* val_ptr);
+ static bool readU16Value(Parser&, void* val_ptr);
+ static bool readS16Value(Parser&, void* val_ptr);
+ static bool readU32Value(Parser&, void* val_ptr);
+ static bool readS32Value(Parser&, void* val_ptr);
+ static bool readF32Value(Parser&, void* val_ptr);
+ static bool readF64Value(Parser&, void* val_ptr);
+ static bool readColor4Value(Parser&, void* val_ptr);
+ static bool readUIColorValue(Parser&, void* val_ptr);
+ static bool readUUIDValue(Parser&, void* val_ptr);
+ static bool readSDValue(Parser&, void* val_ptr);
+
+private:
+ static void startElementHandler(void *userData, const char *name, const char **atts);
+ static void endElementHandler(void *userData, const char *name);
+ static void characterDataHandler(void *userData, const char *s, int len);
+
+ void startElement(const char *name, const char **atts);
+ void endElement(const char *name);
+ void characterData(const char *s, int len);
+ bool readAttributes(const char **atts);
+ bool processText();
+
+ Parser::name_stack_t mNameStack;
+ struct XML_ParserStruct* mParser;
+ LLXMLNodePtr mLastWrittenChild;
+ S32 mCurReadDepth;
+ std::string mCurFileName;
+ std::string mTextContents;
+ const char* mCurAttributeValueBegin;
+ std::vector<S32> mTokenSizeStack;
+ std::vector<std::string> mScope;
+ std::vector<bool> mEmptyLeafNode;
+ element_start_callback_t mElementCB;
+
+ std::vector<std::pair<LLInitParam::BaseBlock*, S32> > mOutputStack;
+};
+
+
+#endif //LLXUIPARSER_H
diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp
index 61e30d89d0..ee87d01239 100644
--- a/indra/llui/tests/llurlentry_stub.cpp
+++ b/indra/llui/tests/llurlentry_stub.cpp
@@ -105,30 +105,6 @@ LLStyle::Params::Params()
namespace LLInitParam
{
- Param::Param(BaseBlock* enclosing_block)
- : mIsProvided(false)
- {
- const U8* my_addr = reinterpret_cast<const U8*>(this);
- const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block);
- U32 enclosing_block_offset = 0x7FFFffff & (U32)(my_addr - block_addr);
- mEnclosingBlockOffsetLow = enclosing_block_offset & 0x0000ffff;
- mEnclosingBlockOffsetHigh = (enclosing_block_offset & 0x007f0000) >> 16;
- }
-
- void BlockDescriptor::addParam(const ParamDescriptorPtr in_param, const char* char_name){}
- void BaseBlock::addSynonym(Param& param, const std::string& synonym) {}
- param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;}
-
- void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size)
- {
- descriptor.mCurrentBlockPtr = this;
- }
- bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; }
- void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {}
- bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_value, S32 max_value) const { return true; }
- bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; }
- bool BaseBlock::validateBlock(bool emit_errors) const { return true; }
-
ParamValue<LLUIColor>::ParamValue(const LLUIColor& color)
: super_t(color)
{}
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
index c1fb050206..8f0a48018f 100644
--- a/indra/llui/tests/llurlentry_test.cpp
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -70,21 +70,6 @@ S32 LLUIImage::getHeight() const
return 0;
}
-namespace LLInitParam
-{
- BlockDescriptor::BlockDescriptor() {}
- ParamDescriptor::ParamDescriptor(param_handle_t p,
- merge_func_t merge_func,
- deserialize_func_t deserialize_func,
- serialize_func_t serialize_func,
- validation_func_t validation_func,
- inspect_func_t inspect_func,
- S32 min_count,
- S32 max_count){}
- ParamDescriptor::~ParamDescriptor() {}
-
-}
-
namespace tut
{
struct LLUrlEntryData
diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp
index 97fe5b2eea..109d3ca7bb 100644
--- a/indra/llui/tests/llurlmatch_test.cpp
+++ b/indra/llui/tests/llurlmatch_test.cpp
@@ -63,42 +63,6 @@ S32 LLUIImage::getHeight() const
namespace LLInitParam
{
- BlockDescriptor::BlockDescriptor() {}
- ParamDescriptor::ParamDescriptor(param_handle_t p,
- merge_func_t merge_func,
- deserialize_func_t deserialize_func,
- serialize_func_t serialize_func,
- validation_func_t validation_func,
- inspect_func_t inspect_func,
- S32 min_count,
- S32 max_count){}
- ParamDescriptor::~ParamDescriptor() {}
-
- void BlockDescriptor::addParam(const ParamDescriptorPtr in_param, const char* char_name){}
- param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;}
- void BaseBlock::addSynonym(Param& param, const std::string& synonym) {}
-
- void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size)
- {
- descriptor.mCurrentBlockPtr = this;
- }
-
- Param::Param(BaseBlock* enclosing_block)
- : mIsProvided(false)
- {
- const U8* my_addr = reinterpret_cast<const U8*>(this);
- const U8* block_addr = reinterpret_cast<const U8*>(enclosing_block);
- U32 enclosing_block_offset = 0x7FFFffff & (U32)(my_addr - block_addr);
- mEnclosingBlockOffsetLow = enclosing_block_offset & 0x0000ffff;
- mEnclosingBlockOffsetHigh = (enclosing_block_offset & 0x007f0000) >> 16;
- }
-
- bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack, bool new_name){ return true; }
- void BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t& name_stack, const LLInitParam::BaseBlock* diff_block) const {}
- bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack, S32 min_count, S32 max_count) const { return true; }
- bool BaseBlock::mergeBlock(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; }
- bool BaseBlock::validateBlock(bool emit_errors) const { return true; }
-
ParamValue<LLUIColor>::ParamValue(const LLUIColor& color)
: super_t(color)
{}