From dd9bfb6d859c5848b5ff6c8fdebbc34ab65f92e7 Mon Sep 17 00:00:00 2001 From: James Cook Date: Fri, 22 Jan 2010 16:52:53 -0800 Subject: LLCacheName and LLUrlEntryAgent now understand SLIDs - accounts with "Resident" as last name should not display that name. Also added test display names. --- indra/llui/llurlentry.cpp | 65 +++++++++++++++++++++++++++++++++--- indra/llui/tests/llurlentry_stub.cpp | 7 ++++ 2 files changed, 68 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 4927e57a52..326f1cec29 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -310,13 +310,70 @@ LLUrlEntryAgent::LLUrlEntryAgent() mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); } +// IDEVO demo code +static std::string clean_name(const std::string& first, const std::string& last) +{ + std::string displayname; + if (first == "miyazaki23") // IDEVO demo code + { + // miyazaki + displayname += (char)(0xE5); + displayname += (char)(0xAE); + displayname += (char)(0xAE); + displayname += (char)(0xE5); + displayname += (char)(0xB4); + displayname += (char)(0x8E); + // hayao + displayname += (char)(0xE9); + displayname += (char)(0xA7); + displayname += (char)(0xBF); + // san + displayname += (char)(0xE3); + displayname += (char)(0x81); + displayname += (char)(0x95); + displayname += (char)(0xE3); + displayname += (char)(0x82); + displayname += (char)(0x93); + } + else if (first == "Jim") + { + displayname = "Jos"; + displayname += (char)(0xC3); + displayname += (char)(0xA9); + displayname += " Sanchez"; + } + else if (first == "James") + { + displayname = "James Cook"; + } + + std::string fullname = first; + if (!last.empty()) + { + fullname += ' '; + fullname += last; + } + + std::string final; + if (!displayname.empty()) + { + final = displayname + " (" + fullname + ")"; + } + else + { + final = fullname; + } + return final; +} + void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group) { + std::string final = clean_name(first, last); // received the agent name from the server - tell our observers - callObservers(id.asString(), first + " " + last); + callObservers(id.asString(), final); } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -335,14 +392,14 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } LLUUID agent_id(agent_id_string); - std::string full_name; + std::string first, last; if (agent_id.isNull()) { return LLTrans::getString("AvatarNameNobody"); } - else if (gCacheName->getFullName(agent_id, full_name)) + else if (gCacheName->getName(agent_id, first, last)) { - return full_name; + return clean_name(first, last); } else { diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 26d1f2e067..3731800adf 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -36,6 +36,13 @@ BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) return TRUE; } +BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& last) +{ + first = "Lynx"; + last = "Linden"; + return TRUE; +} + BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) { group = "My Group"; -- cgit v1.2.3 From 40907986d4044e2be75f862fac1153f9a14f13ad Mon Sep 17 00:00:00 2001 From: James Cook Date: Fri, 22 Jan 2010 18:29:29 -0800 Subject: Pull llurlentryagent into newview so I can compile/link faster --- indra/llui/llurlentry.cpp | 114 +---------------------------------- indra/llui/llurlentry.h | 14 +---- indra/llui/llurlregistry.cpp | 11 +++- indra/llui/llurlregistry.h | 4 +- indra/llui/tests/llurlentry_test.cpp | 35 +++++------ 5 files changed, 31 insertions(+), 147 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 326f1cec29..d04cb8c7ff 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -297,119 +297,7 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const return url.substr(pos, url.size() - pos); } -// -// LLUrlEntryAgent Describes a Second Life agent Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -// -LLUrlEntryAgent::LLUrlEntryAgent() -{ - mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/\\w+", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_agent.xml"; - mIcon = "Generic_Person"; - mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); -} - -// IDEVO demo code -static std::string clean_name(const std::string& first, const std::string& last) -{ - std::string displayname; - if (first == "miyazaki23") // IDEVO demo code - { - // miyazaki - displayname += (char)(0xE5); - displayname += (char)(0xAE); - displayname += (char)(0xAE); - displayname += (char)(0xE5); - displayname += (char)(0xB4); - displayname += (char)(0x8E); - // hayao - displayname += (char)(0xE9); - displayname += (char)(0xA7); - displayname += (char)(0xBF); - // san - displayname += (char)(0xE3); - displayname += (char)(0x81); - displayname += (char)(0x95); - displayname += (char)(0xE3); - displayname += (char)(0x82); - displayname += (char)(0x93); - } - else if (first == "Jim") - { - displayname = "Jos"; - displayname += (char)(0xC3); - displayname += (char)(0xA9); - displayname += " Sanchez"; - } - else if (first == "James") - { - displayname = "James Cook"; - } - - std::string fullname = first; - if (!last.empty()) - { - fullname += ' '; - fullname += last; - } - - std::string final; - if (!displayname.empty()) - { - final = displayname + " (" + fullname + ")"; - } - else - { - final = fullname; - } - return final; -} - -void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id, - const std::string& first, - const std::string& last, - BOOL is_group) -{ - std::string final = clean_name(first, last); - // received the agent name from the server - tell our observers - callObservers(id.asString(), final); -} - -std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - if (!gCacheName) - { - // probably at the login screen, use short string for layout - return LLTrans::getString("LoadingData"); - } - - std::string agent_id_string = getIDStringFromUrl(url); - if (agent_id_string.empty()) - { - // something went wrong, just give raw url - return unescapeUrl(url); - } - - LLUUID agent_id(agent_id_string); - std::string first, last; - if (agent_id.isNull()) - { - return LLTrans::getString("AvatarNameNobody"); - } - else if (gCacheName->getName(agent_id, first, last)) - { - return clean_name(first, last); - } - else - { - gCacheName->get(agent_id, FALSE, - boost::bind(&LLUrlEntryAgent::onAgentNameReceived, - this, _1, _2, _3, _4)); - addObserver(agent_id_string, url, cb); - return LLTrans::getString("LoadingData"); - } -} +// LLUrlEntryAgent temporarily moved to newview IDEVO // // LLUrlEntryGroup Describes a Second Life group Url, e.g., diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 4adffde99c..33ec9d82a9 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -156,19 +156,7 @@ public: /*virtual*/ std::string getLocation(const std::string &url) const; }; -/// -/// LLUrlEntryAgent Describes a Second Life agent Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -/// -class LLUrlEntryAgent : public LLUrlEntryBase -{ -public: - LLUrlEntryAgent(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -private: - void onAgentNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, BOOL is_group); -}; +/// IDEVO LLUrlEntryAgent temporarily moved to newview /// /// LLUrlEntryGroup Describes a Second Life group Url, e.g., diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index ad5c0911f8..4fbdae9d38 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -43,11 +43,13 @@ void LLUrlRegistryNullCallback(const std::string &url, const std::string &label) LLUrlRegistry::LLUrlRegistry() { + mUrlEntry.reserve(16); + // Urls are matched in the order that they were registered registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); - registerUrl(new LLUrlEntryAgent()); + // IDEVO registerUrl(new LLUrlEntryAgent()); registerUrl(new LLUrlEntryGroup()); registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); @@ -73,11 +75,14 @@ LLUrlRegistry::~LLUrlRegistry() } } -void LLUrlRegistry::registerUrl(LLUrlEntryBase *url) +void LLUrlRegistry::registerUrl(LLUrlEntryBase *url, bool force_front) { if (url) { - mUrlEntry.push_back(url); + if (force_front) // IDEVO + mUrlEntry.insert(mUrlEntry.begin(), url); + else + mUrlEntry.push_back(url); } } diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 399ee0a988..6d47c2c2a2 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -70,7 +70,9 @@ public: ~LLUrlRegistry(); /// add a new Url handler to the registry (will be freed on destruction) - void registerUrl(LLUrlEntryBase *url); + /// optionally force it to the front of the list, making it take + /// priority over other regular expression matches for URLs + void registerUrl(LLUrlEntryBase *url, bool force_front = false); /// get the next Url in an input string, starting at a given character offset /// your callback is invoked if the matched Url's label changes in the future diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 80be8fcbf7..9200708784 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -266,28 +266,29 @@ namespace tut // // test LLUrlEntryAgent - secondlife://app/agent Urls // - LLUrlEntryAgent url; - boost::regex r = url.getPattern(); + // IDEVO - moved temporarily into newview, not available for test + //LLUrlEntryAgent url; + //boost::regex r = url.getPattern(); - testRegex("Invalid Agent Url", r, - "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", - ""); + //testRegex("Invalid Agent Url", r, + // "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", + // ""); - testRegex("Agent Url ", r, - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + //testRegex("Agent Url ", r, + // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("Agent Url in text", r, - "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + //testRegex("Agent Url in text", r, + // "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", + // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("Agent Url multicase", r, - "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", - "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); + //testRegex("Agent Url multicase", r, + // "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", + // "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); - testRegex("Agent Url alternate command", r, - "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", - "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); + //testRegex("Agent Url alternate command", r, + // "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", + // "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); } -- cgit v1.2.3 From 146e9d5e4d9a9a4f33d9ccd47a901980972b7ab9 Mon Sep 17 00:00:00 2001 From: James Cook Date: Mon, 1 Feb 2010 17:06:18 -0800 Subject: Support returning full_name (and SLID) for LLCacheName::get() calls Changed callback signature to full_name instead of first_name,last_name Eliminated all calls to legacy (non-signal/non-boost-bind) lookup mechanism Change Pay dialog names to SLURL links Tweaked layout of Pay Resident and Pay via Object floaters to make SLURLs fit Consolidate name first + " " + last concatenation in LLCacheName::buildFullName() Reviewed with Kelly --- indra/llui/lltextbase.cpp | 2 +- indra/llui/llurlentry.cpp | 11 +++++------ indra/llui/llurlentry.h | 3 +-- indra/llui/tests/llurlentry_stub.cpp | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 17aecaf32f..790240ab48 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2493,7 +2493,7 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin LLUIImagePtr image = mStyle->getImage(); if( image.notNull()) { - num_pixels -= image->getWidth(); + num_pixels = llmax(0, num_pixels - image->getWidth()); } // search for newline and if found, truncate there diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index d04cb8c7ff..40f8c27431 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -315,12 +315,11 @@ LLUrlEntryGroup::LLUrlEntryGroup() } void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, - const std::string& first, - const std::string& last, - BOOL is_group) + const std::string& name, + bool is_group) { // received the group name from the server - tell our observers - callObservers(id.asString(), first); + callObservers(id.asString(), name); } std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -350,9 +349,9 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa } else { - gCacheName->get(group_id, TRUE, + gCacheName->get(group_id, true, boost::bind(&LLUrlEntryGroup::onGroupNameReceived, - this, _1, _2, _3, _4)); + this, _1, _2, _3)); addObserver(group_id_string, url, cb); return LLTrans::getString("LoadingData"); } diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 33ec9d82a9..6d875a40c7 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -168,8 +168,7 @@ public: LLUrlEntryGroup(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); private: - void onGroupNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, BOOL is_group); + void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); }; /// diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 3731800adf..30bab1eb91 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -49,7 +49,7 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) return TRUE; } -boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) +boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback) { return boost::signals2::connection(); } -- cgit v1.2.3 From d489216b6ef41f0d22f3a9062ba6daacc67bc038 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 3 Feb 2010 12:48:40 -0800 Subject: Moved LLUrlEntryAgent back into llui library in prep for merge --- indra/llui/llurlentry.cpp | 127 ++++++++++++++++++++++++++++++++++- indra/llui/llurlentry.h | 12 +++- indra/llui/llurlregistry.cpp | 2 +- indra/llui/tests/llurlentry_test.cpp | 36 +++++----- 4 files changed, 155 insertions(+), 22 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 40f8c27431..7a62ca5098 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -297,7 +297,132 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const return url.substr(pos, url.size() - pos); } -// LLUrlEntryAgent temporarily moved to newview IDEVO +// +// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// +LLUrlEntryAgent::LLUrlEntryAgent() +{ + mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/\\w+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_agent.xml"; + mIcon = "Generic_Person"; + mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); +} + +// IDEVO demo code +static std::string clean_name(const std::string& full_name) +{ + std::string displayname; + if (full_name == "miyazaki23") // IDEVO demo code + { + // miyazaki + displayname += (char)(0xE5); + displayname += (char)(0xAE); + displayname += (char)(0xAE); + displayname += (char)(0xE5); + displayname += (char)(0xB4); + displayname += (char)(0x8E); + // hayao + displayname += (char)(0xE9); + displayname += (char)(0xA7); + displayname += (char)(0xBF); + // san + displayname += (char)(0xE3); + displayname += (char)(0x81); + displayname += (char)(0x95); + displayname += (char)(0xE3); + displayname += (char)(0x82); + displayname += (char)(0x93); + } + else if (full_name == "Jim Linden") + { + displayname = "Jos"; + displayname += (char)(0xC3); + displayname += (char)(0xA9); + displayname += " Sanchez"; + } + else if (full_name == "James Linden") + { + displayname = "James Cook"; + } + else if (full_name == "Hamilton Linden") + { + displayname = "Hamilton Hitchings"; + } + else if (full_name == "Rome Linden") + { + displayname = "Rome Portlock"; + } + else if (full_name == "M Linden") + { + displayname = "Mark Kingdon"; + } + else if (full_name == "T Linden") + { + displayname = "Tom Hale"; + } + else if (full_name == "Callen Linden") + { + displayname = "Christina Allen"; + } + + std::string final; + if (!displayname.empty()) + { + final = displayname + " (" + full_name + ")"; + } + else + { + final = full_name; + } + return final; +} + +void LLUrlEntryAgent::onNameCache(const LLUUID& id, + const std::string& full_name, + bool is_group) +{ + std::string final = clean_name(full_name); + // received the agent name from the server - tell our observers + callObservers(id.asString(), final); +} + +std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID agent_id(agent_id_string); + std::string full_name; + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + else if (gCacheName->getFullName(agent_id, full_name)) + { + return clean_name(full_name); + } + else + { + gCacheName->get(agent_id, false, + boost::bind(&LLUrlEntryAgent::onNameCache, + this, _1, _2, _3)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + // // LLUrlEntryGroup Describes a Second Life group Url, e.g., diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 6d875a40c7..4fc2eb5e05 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -156,7 +156,17 @@ public: /*virtual*/ std::string getLocation(const std::string &url) const; }; -/// IDEVO LLUrlEntryAgent temporarily moved to newview +/// +/// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +class LLUrlEntryAgent : public LLUrlEntryBase +{ +public: + LLUrlEntryAgent(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +private: + void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); +}; /// /// LLUrlEntryGroup Describes a Second Life group Url, e.g., diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 4fbdae9d38..419d2322f9 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -49,7 +49,7 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); - // IDEVO registerUrl(new LLUrlEntryAgent()); + registerUrl(new LLUrlEntryAgent()); registerUrl(new LLUrlEntryGroup()); registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 9200708784..30b59859d3 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -266,30 +266,28 @@ namespace tut // // test LLUrlEntryAgent - secondlife://app/agent Urls // - // IDEVO - moved temporarily into newview, not available for test - //LLUrlEntryAgent url; - //boost::regex r = url.getPattern(); - - //testRegex("Invalid Agent Url", r, - // "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", - // ""); + LLUrlEntryAgent url; + boost::regex r = url.getPattern(); - //testRegex("Agent Url ", r, - // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", - // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + testRegex("Invalid Agent Url", r, + "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", + ""); - //testRegex("Agent Url in text", r, - // "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", - // "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + testRegex("Agent Url ", r, + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - //testRegex("Agent Url multicase", r, - // "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", - // "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); + testRegex("Agent Url in text", r, + "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - //testRegex("Agent Url alternate command", r, - // "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", - // "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); + testRegex("Agent Url multicase", r, + "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", + "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); + testRegex("Agent Url alternate command", r, + "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", + "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); } template<> template<> -- cgit v1.2.3 From 33e613531f929b5bce04c11a355085fe5ea92997 Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 4 Feb 2010 22:19:15 -0800 Subject: Fix placement of icon next to linked agent/group names. Suppresses rendering of text in LLNormalTextSegment that represent an icon, properly computes vertical spacing based on font height, and adds padding to the right of the icon. --- indra/llui/lltextbase.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 2b1e2b8226..e923e60db4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1574,8 +1574,10 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c { LLStyle::Params icon; icon.image = image; - // HACK: fix spacing of images and remove the fixed char spacing - appendAndHighlightText(" ", prepend_newline, part, icon); + // Text will be replaced during rendering with the icon, + // but string cannot be empty or the segment won't be + // added (or drawn). + appendAndHighlightText(" ", prepend_newline, part, icon); prepend_newline = false; } } @@ -2296,14 +2298,21 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec { if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart)) { + // ...for images, only render the image, not the underlying text, + // which is only a placeholder space LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; LLUIImagePtr image = mStyle->getImage(); S32 style_image_height = image->getHeight(); S32 style_image_width = image->getWidth(); - // Center the image vertically - S32 image_bottom = draw_rect.getCenterY() - (style_image_height/2); + // Text is drawn from the top of the draw_rect downward + S32 text_center = draw_rect.mTop - (mFontHeight / 2); + // Align image to center of text + S32 image_bottom = text_center - (style_image_height / 2); image->draw(draw_rect.mLeft, image_bottom, style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 2; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; } return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); -- cgit v1.2.3 From 42176145c9e81dd99b65f88152df50a7b3ccf834 Mon Sep 17 00:00:00 2001 From: James Cook Date: Mon, 8 Feb 2010 14:55:30 -0800 Subject: Converted names build from avatar object LLNameValue pairs to SLID Added temporary getDisplayName() to LLCacheName Moved temporary placeholder display names into LLCacheName Eliminated rarely used LLAgentUI::buildName in favor of buildFullName Standardized buildFullName capitalization --- indra/llui/llurlentry.cpp | 65 ++++-------------------------------- indra/llui/llurlentry.h | 1 + indra/llui/tests/llurlentry_stub.cpp | 7 ++-- 3 files changed, 10 insertions(+), 63 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 0bbf1fe084..0b68b66ff9 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -312,66 +312,13 @@ LLUrlEntryAgent::LLUrlEntryAgent() } // IDEVO demo code -static std::string clean_name(const std::string& full_name) +std::string LLUrlEntryAgent::buildName(const LLUUID& id, const std::string& full_name) { - std::string displayname; - if (full_name == "miyazaki23") // IDEVO demo code - { - // miyazaki - displayname += (char)(0xE5); - displayname += (char)(0xAE); - displayname += (char)(0xAE); - displayname += (char)(0xE5); - displayname += (char)(0xB4); - displayname += (char)(0x8E); - // hayao - displayname += (char)(0xE9); - displayname += (char)(0xA7); - displayname += (char)(0xBF); - // san - displayname += (char)(0xE3); - displayname += (char)(0x81); - displayname += (char)(0x95); - displayname += (char)(0xE3); - displayname += (char)(0x82); - displayname += (char)(0x93); - } - else if (full_name == "Jim Linden") - { - displayname = "Jos"; - displayname += (char)(0xC3); - displayname += (char)(0xA9); - displayname += " Sanchez"; - } - else if (full_name == "James Linden") - { - displayname = "James Cook"; - } - else if (full_name == "Hamilton Linden") - { - displayname = "Hamilton Hitchings"; - } - else if (full_name == "Rome Linden") - { - displayname = "Rome Portlock"; - } - else if (full_name == "M Linden") - { - displayname = "Mark Kingdon"; - } - else if (full_name == "T Linden") - { - displayname = "Tom Hale"; - } - else if (full_name == "Callen Linden") - { - displayname = "Christina Allen"; - } - std::string final; - if (!displayname.empty()) + std::string display_name; + if (gCacheName->getDisplayName(id, display_name)) { - final = displayname + " (" + full_name + ")"; + final = display_name + " (" + full_name + ")"; } else { @@ -384,7 +331,7 @@ void LLUrlEntryAgent::onNameCache(const LLUUID& id, const std::string& full_name, bool is_group) { - std::string final = clean_name(full_name); + std::string final = buildName(id, full_name); // received the agent name from the server - tell our observers callObservers(id.asString(), final); } @@ -412,7 +359,7 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } else if (gCacheName->getFullName(agent_id, full_name)) { - return clean_name(full_name); + return buildName(agent_id, full_name); } else { diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index e6844b595c..77802957a3 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -170,6 +170,7 @@ public: /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); private: void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); + std::string buildName(const LLUUID& id, const std::string& full_name); }; /// diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 30bab1eb91..35c49fc37f 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -36,11 +36,10 @@ BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) return TRUE; } -BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& last) +bool LLCacheName::getDisplayName(const LLUUID& id, std::string& display_name) { - first = "Lynx"; - last = "Linden"; - return TRUE; + display_name = ""; + return false; } BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) -- cgit v1.2.3 From c2373fb5a6b08b2c32e5f93fa67b25f669e8b47f Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 11 Feb 2010 16:39:15 -0800 Subject: Created stub LLAvatarNameCache for display name lookup, as well as LLAvatarName base data object. Reviewed with Kelly. --- indra/llui/llurlentry.cpp | 22 ++++++++-------------- indra/llui/llurlentry.h | 3 ++- indra/llui/tests/llurlentry_stub.cpp | 7 +++++++ 3 files changed, 17 insertions(+), 15 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 0b68b66ff9..dae2c44f08 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -35,6 +35,7 @@ #include "llurlentry.h" #include "lluri.h" +#include "llavatarnamecache.h" #include "llcachename.h" #include "lltrans.h" #include "lluicolortable.h" @@ -312,26 +313,19 @@ LLUrlEntryAgent::LLUrlEntryAgent() } // IDEVO demo code -std::string LLUrlEntryAgent::buildName(const LLUUID& id, const std::string& full_name) +std::string LLUrlEntryAgent::buildName(const LLUUID& id) { - std::string final; - std::string display_name; - if (gCacheName->getDisplayName(id, display_name)) - { - final = display_name + " (" + full_name + ")"; - } - else - { - final = full_name; - } - return final; + // JAMESDEBUG HACK: assume name is there + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + return av_name.mDisplayName + " (" + av_name.mSLID + ")"; } void LLUrlEntryAgent::onNameCache(const LLUUID& id, const std::string& full_name, bool is_group) { - std::string final = buildName(id, full_name); + std::string final = buildName(id); // received the agent name from the server - tell our observers callObservers(id.asString(), final); } @@ -359,7 +353,7 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } else if (gCacheName->getFullName(agent_id, full_name)) { - return buildName(agent_id, full_name); + return buildName(agent_id); } else { diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 77802957a3..2bc21eb989 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -168,9 +168,10 @@ class LLUrlEntryAgent : public LLUrlEntryBase public: LLUrlEntryAgent(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + private: void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); - std::string buildName(const LLUUID& id, const std::string& full_name); + std::string buildName(const LLUUID& id); }; /// diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 35c49fc37f..016969451e 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -22,11 +22,18 @@ #include "llstring.h" #include "llfile.h" +#include "llavatarnamecache.h" #include "llcachename.h" #include "lluuid.h" #include +// Stub for LLAvatarNameCache +bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name) +{ + return false; +} + // // Stub implementation for LLCacheName // -- cgit v1.2.3 From 3581d0001e2506389f41ce46f5007cc6f40f2d6a Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 11 Feb 2010 16:46:07 -0800 Subject: Fixed test build problem --- indra/llui/tests/llurlentry_stub.cpp | 6 ------ 1 file changed, 6 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 016969451e..1f8c4d2f22 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -43,12 +43,6 @@ BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) return TRUE; } -bool LLCacheName::getDisplayName(const LLUUID& id, std::string& display_name) -{ - display_name = ""; - return false; -} - BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) { group = "My Group"; -- cgit v1.2.3 From c16591c046fa76fc5d13387efa3bcaec3422e593 Mon Sep 17 00:00:00 2001 From: James Cook Date: Fri, 12 Feb 2010 16:12:12 -0800 Subject: Per-avatar customizable icons next to name links in text Changed LLUrlEntryAgent callbacks to handle both link label and icon Eliminated legacy LLNameCache file loading Reviewed with Kelly --- indra/llui/lltextbase.cpp | 47 +++++++++++++++++++++++++++++----- indra/llui/lltextbase.h | 5 +++- indra/llui/llurlentry.cpp | 61 +++++++++++++++++++++++++++++++++----------- indra/llui/llurlentry.h | 9 ++++--- indra/llui/llurlregistry.cpp | 4 +-- indra/llui/llurlregistry.h | 4 ++- 6 files changed, 100 insertions(+), 30 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index ef422dbdc5..075f54ed30 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1527,6 +1527,20 @@ std::string LLTextBase::getText() const return getViewModel()->getValue().asString(); } +// IDEVO - icons can be UI image names or UUID sent from +// server with avatar display name +static LLUIImagePtr image_from_icon_name(const std::string& icon_name) +{ + if (LLUUID::validate(icon_name)) + { + return LLUI::getUIImageByID( LLUUID(icon_name) ); + } + else + { + return LLUI::getUIImage(icon_name); + } +} + void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) { LLStyle::Params style_params(input_params); @@ -1539,7 +1553,7 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c LLUrlMatch match; std::string text = new_text; while ( LLUrlRegistry::instance().findUrl(text, match, - boost::bind(&LLTextBase::replaceUrlLabel, this, _1, _2)) ) + boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3)) ) { start = match.getStart(); end = match.getEnd()+1; @@ -1570,15 +1584,18 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c // output an optional icon before the Url if (! match.getIcon().empty()) { - LLUIImagePtr image = LLUI::getUIImage(match.getIcon()); + LLUIImagePtr image = image_from_icon_name( match.getIcon() ); if (image) { - LLStyle::Params icon; - icon.image = image; + LLStyle::Params icon_params; + icon_params.image = image; + // must refer to our link so we can update the icon later + // after name/group data is looked up + icon_params.link_href = match.getUrl(); // Text will be replaced during rendering with the icon, // but string cannot be empty or the segment won't be // added (or drawn). - appendAndHighlightText(" ", prepend_newline, part, icon); + appendAndHighlightText(" ", prepend_newline, part, icon_params); prepend_newline = false; } } @@ -1728,8 +1745,9 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen } -void LLTextBase::replaceUrlLabel(const std::string &url, - const std::string &label) +void LLTextBase::replaceUrl(const std::string &url, + const std::string &label, + const std::string &icon) { // get the full (wide) text for the editor so we can change it LLWString text = getWText(); @@ -1759,6 +1777,21 @@ void LLTextBase::replaceUrlLabel(const std::string &url, modified = true; } + // Icon might be updated when more avatar or group info + // becomes available + if (style->isImage() && style->getLinkHREF() == url) + { + LLUIImagePtr image = image_from_icon_name( icon ); + if (image) + { + LLStyle::Params icon_params; + icon_params.image = image; + LLStyleConstSP new_style(new LLStyle(icon_params)); + seg->setStyle(new_style); + modified = true; + } + } + // work out the character offset for the next segment seg_start = seg->getEnd(); } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 3dda6f4cc8..dc8bc0dca0 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -309,7 +309,10 @@ protected: // misc void updateRects(); void needsScroll() { mScrollNeeded = TRUE; } - void replaceUrlLabel(const std::string &url, const std::string &label); + + // Replace a URL with a new icon and label, for example, when + // avatar names are looked up. + void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); protected: // text segmentation and flow diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index dae2c44f08..dbfac63a72 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -55,6 +55,12 @@ std::string LLUrlEntryBase::getUrl(const std::string &string) const return escapeUrl(string); } +//virtual +std::string LLUrlEntryBase::getIcon(const std::string &url) const +{ + return mIcon; +} + std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const { // return the id from a SLURL in the format /app/{cmd}/{id}/about @@ -133,7 +139,9 @@ void LLUrlEntryBase::addObserver(const std::string &id, } } -void LLUrlEntryBase::callObservers(const std::string &id, const std::string &label) +void LLUrlEntryBase::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) { // notify all callbacks waiting on the given uuid std::multimap::iterator it; @@ -141,7 +149,7 @@ void LLUrlEntryBase::callObservers(const std::string &id, const std::string &lab { // call the callback - give it the new label LLUrlEntryObserver &observer = it->second; - (*observer.signal)(it->second.url, label); + (*observer.signal)(it->second.url, label, icon); // then remove the signal - we only need to call it once delete observer.signal; mObservers.erase(it++); @@ -312,22 +320,18 @@ LLUrlEntryAgent::LLUrlEntryAgent() mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); } -// IDEVO demo code -std::string LLUrlEntryAgent::buildName(const LLUUID& id) -{ - // JAMESDEBUG HACK: assume name is there - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - return av_name.mDisplayName + " (" + av_name.mSLID + ")"; -} - void LLUrlEntryAgent::onNameCache(const LLUUID& id, const std::string& full_name, bool is_group) { - std::string final = buildName(id); + // IDEVO demo code + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; + // use custom icon if available + std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); // received the agent name from the server - tell our observers - callObservers(id.asString(), final); + callObservers(id.asString(), label, icon); } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -353,7 +357,10 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } else if (gCacheName->getFullName(agent_id, full_name)) { - return buildName(agent_id); + LLAvatarName av_name; + LLAvatarNameCache::get(agent_id, &av_name); + std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; + return label; } else { @@ -366,6 +373,30 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } +std::string LLUrlEntryAgent::getIcon(const std::string &url) const +{ + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + return mIcon; + } + + LLUUID agent_id(agent_id_string); + if (agent_id.isNull()) + { + return mIcon; + } + + LLAvatarName av_name; + LLAvatarNameCache::get(agent_id, &av_name); + if (av_name.mBadge.empty()) + { + return mIcon; + } + + return av_name.mBadge; +} + // // LLUrlEntryGroup Describes a Second Life group Url, e.g., // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about @@ -386,7 +417,7 @@ void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, bool is_group) { // received the group name from the server - tell our observers - callObservers(id.asString(), name); + callObservers(id.asString(), name, mIcon); } std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCallback &cb) diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 2bc21eb989..1f0caeacf5 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -42,7 +42,8 @@ #include typedef boost::signals2::signal LLUrlLabelSignal; + const std::string& label, + const std::string& icon)> LLUrlLabelSignal; typedef LLUrlLabelSignal::slot_type LLUrlLabelCallback; /// @@ -77,7 +78,7 @@ public: virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } /// Return an icon that can be displayed next to Urls of this type - std::string getIcon() const { return mIcon; } + virtual std::string getIcon(const std::string &url) const; /// Return the color to render the displayed text LLUIColor getColor() const { return mColor; } @@ -101,7 +102,7 @@ protected: std::string getLabelFromWikiLink(const std::string &url) const; std::string getUrlFromWikiLink(const std::string &string) const; void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); - void callObservers(const std::string &id, const std::string &label); + void callObservers(const std::string &id, const std::string &label, const std::string& icon); typedef struct { std::string url; @@ -168,10 +169,10 @@ class LLUrlEntryAgent : public LLUrlEntryBase public: LLUrlEntryAgent(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getIcon(const std::string &url) const; private: void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); - std::string buildName(const LLUUID& id); }; /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 5db1f46b8d..1ee87a01de 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -37,7 +37,7 @@ #include // default dummy callback that ignores any label updates from the server -void LLUrlRegistryNullCallback(const std::string &url, const std::string &label) +void LLUrlRegistryNullCallback(const std::string &url, const std::string &label, const std::string& icon) { } @@ -180,7 +180,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getUrl(url), match_entry->getLabel(url, cb), match_entry->getTooltip(), - match_entry->getIcon(), + match_entry->getIcon(url), match_entry->getColor(), match_entry->getMenuName(), match_entry->getLocation(url), diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 6d47c2c2a2..dffbd9d4bf 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -43,7 +43,9 @@ #include /// This default callback for findUrl() simply ignores any label updates -void LLUrlRegistryNullCallback(const std::string &url, const std::string &label); +void LLUrlRegistryNullCallback(const std::string &url, + const std::string &label, + const std::string &icon); /// /// LLUrlRegistry is a singleton that contains a set of Url types that -- cgit v1.2.3 From 65d388d162dc14f5b8b3e0bfff1ae1125736f564 Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 16 Feb 2010 11:58:21 -0800 Subject: Fix display of SLID-only accounts - prototype display name code was putting duplicate name in parentheses. --- indra/llui/llurlentry.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index dbfac63a72..ef48f6cd4f 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -326,12 +326,18 @@ void LLUrlEntryAgent::onNameCache(const LLUUID& id, { // IDEVO demo code LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; - // use custom icon if available - std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); - // received the agent name from the server - tell our observers - callObservers(id.asString(), label, icon); + if (LLAvatarNameCache::get(id, &av_name)) + { + std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; + // use custom icon if available + std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, icon); + } + else + { + callObservers(id.asString(), full_name, mIcon); + } } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -358,9 +364,10 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa else if (gCacheName->getFullName(agent_id, full_name)) { LLAvatarName av_name; - LLAvatarNameCache::get(agent_id, &av_name); - std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; - return label; + if (LLAvatarNameCache::get(agent_id, &av_name)) + return av_name.mDisplayName + " (" + av_name.mSLID + ")"; + else + return full_name; } else { -- cgit v1.2.3 From 55bfb4435e1a2ffc34578bf30877eb24a5edaa29 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 17 Feb 2010 16:55:58 -0800 Subject: Look up display names via a web service Also removed unused LLComboBox globals to reduce llappviewer.cpp include file burden. Review pending --- indra/llui/llcombobox.cpp | 2 -- indra/llui/llcombobox.h | 3 --- 2 files changed, 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 9d23daf56d..1aa44f6b96 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -58,8 +58,6 @@ #include "lltooltip.h" // Globals -S32 LLCOMBOBOX_HEIGHT = 0; -S32 LLCOMBOBOX_WIDTH = 0; S32 MAX_COMBO_WIDTH = 500; static LLDefaultChildRegistry::Register register_combo_box("combo_box"); diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 4f27588467..58e29dcb30 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -49,9 +49,6 @@ class LLFontGL; class LLViewBorder; -extern S32 LLCOMBOBOX_HEIGHT; -extern S32 LLCOMBOBOX_WIDTH; - class LLComboBox : public LLUICtrl, public LLCtrlListInterface { -- cgit v1.2.3 From ee81adb01b8619302898471fdbba5c93f716d10b Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 18 Feb 2010 18:46:41 -0800 Subject: Preference to set what fields name tags display, and first attempt at "use display names" menu item which doesn't work yet --- indra/llui/llurlentry.cpp | 6 ++++-- indra/llui/tests/llurlentry_stub.cpp | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index ef48f6cd4f..74b9468c26 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -326,7 +326,8 @@ void LLUrlEntryAgent::onNameCache(const LLUUID& id, { // IDEVO demo code LLAvatarName av_name; - if (LLAvatarNameCache::get(id, &av_name)) + if (LLAvatarNameCache::useDisplayNames() + && LLAvatarNameCache::get(id, &av_name)) { std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; // use custom icon if available @@ -364,7 +365,8 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa else if (gCacheName->getFullName(agent_id, full_name)) { LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_id, &av_name)) + if (LLAvatarNameCache::useDisplayNames() + && LLAvatarNameCache::get(agent_id, &av_name)) return av_name.mDisplayName + " (" + av_name.mSLID + ")"; else return full_name; diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 1f8c4d2f22..280554b954 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -34,6 +34,11 @@ bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name) return false; } +bool LLAvatarNameCache::useDisplayNames() +{ + return false; +} + // // Stub implementation for LLCacheName // -- cgit v1.2.3 From 75cc897c062ffb4d59649a7af760d5e89396a3f8 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 3 Mar 2010 10:10:55 -0800 Subject: Underlined display names now update reliably. --- indra/llui/llurlentry.cpp | 61 ++++++++++++++++++++++-------------- indra/llui/llurlentry.h | 3 ++ indra/llui/tests/llurlentry_stub.cpp | 5 +++ 3 files changed, 45 insertions(+), 24 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 22920d1656..5a2956311e 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -323,22 +323,19 @@ LLUrlEntryAgent::LLUrlEntryAgent() void LLUrlEntryAgent::onNameCache(const LLUUID& id, const std::string& full_name, bool is_group) +{ + callObservers(id.asString(), full_name, mIcon); +} + +void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) { // IDEVO demo code - LLAvatarName av_name; - if (LLAvatarNameCache::useDisplayNames() - && LLAvatarNameCache::get(id, &av_name)) - { - std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; - // use custom icon if available - std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); - // received the agent name from the server - tell our observers - callObservers(id.asString(), label, icon); - } - else - { - callObservers(id.asString(), full_name, mIcon); - } + std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; + // use custom icon if available + std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, icon); } std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) @@ -357,27 +354,43 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } LLUUID agent_id(agent_id_string); - std::string full_name; if (agent_id.isNull()) { return LLTrans::getString("AvatarNameNobody"); } - else if (gCacheName->getFullName(agent_id, full_name)) + + if (LLAvatarNameCache::useDisplayNames()) { LLAvatarName av_name; - if (LLAvatarNameCache::useDisplayNames() - && LLAvatarNameCache::get(agent_id, &av_name)) + if (LLAvatarNameCache::get(agent_id, &av_name)) + { return av_name.mDisplayName + " (" + av_name.mSLID + ")"; + } else - return full_name; + { + LLAvatarNameCache::get(agent_id, + boost::bind(&LLUrlEntryAgent::onAvatarNameCache, + this, _1, _2)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } } else { - gCacheName->get(agent_id, false, - boost::bind(&LLUrlEntryAgent::onNameCache, - this, _1, _2, _3)); - addObserver(agent_id_string, url, cb); - return LLTrans::getString("LoadingData"); + // ...no display names + std::string full_name; + if (gCacheName->getFullName(agent_id, full_name)) + { + return full_name; + } + else + { + gCacheName->get(agent_id, false, + boost::bind(&LLUrlEntryAgent::onNameCache, + this, _1, _2, _3)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } } } diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 1f0caeacf5..c075ccc30e 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -41,6 +41,8 @@ #include #include +class LLAvatarName; + typedef boost::signals2::signal LLUrlLabelSignal; @@ -173,6 +175,7 @@ public: private: void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); }; /// diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index 280554b954..e984f5cf81 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -34,6 +34,11 @@ bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name) return false; } +void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot) +{ + return; +} + bool LLAvatarNameCache::useDisplayNames() { return false; -- cgit v1.2.3 From 8fae11ee368e5b1dce858cdd698714384430858b Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 28 Apr 2010 17:02:50 -0700 Subject: Remove prototype support for badges next to avatar names Easy to reimplement if we decide we want to do it. --- indra/llui/llurlentry.cpp | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index a79a257c09..d59bd0cb71 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -334,10 +334,8 @@ void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, { // IDEVO demo code std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; - // use custom icon if available - std::string icon = (!av_name.mBadge.empty() ? av_name.mBadge : mIcon); // received the agent name from the server - tell our observers - callObservers(id.asString(), label, icon); + callObservers(id.asString(), label, mIcon); } std::string LLUrlEntryAgent::getTooltip(const std::string &string) const @@ -456,26 +454,9 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa std::string LLUrlEntryAgent::getIcon(const std::string &url) const { - std::string agent_id_string = getIDStringFromUrl(url); - if (agent_id_string.empty()) - { - return mIcon; - } - - LLUUID agent_id(agent_id_string); - if (agent_id.isNull()) - { - return mIcon; - } - - LLAvatarName av_name; - LLAvatarNameCache::get(agent_id, &av_name); - if (av_name.mBadge.empty()) - { - return mIcon; - } - - return av_name.mBadge; + // *NOTE: Could look up a badge here by calling getIDStringFromUrl() + // and looking up the badge for the agent. + return mIcon; } // -- cgit v1.2.3 From 781c76e85c732ba710e655afcdd5c5441d10b341 Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 29 Apr 2010 12:34:22 -0700 Subject: DEV-49489 Display name support in URL-style name widgets, also Fixed problem where labels like "Mute John Smith" were not being applied when the name was not in cache. --- indra/llui/llurlentry.cpp | 122 +++++++++++++++++++++++++--------------------- indra/llui/llurlentry.h | 5 +- 2 files changed, 70 insertions(+), 57 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index d59bd0cb71..e1daa0ab19 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -42,6 +42,10 @@ #include "lltrans.h" #include "lluicolortable.h" +// Utility functions +std::string localize_slapp_label(const std::string& url, const std::string& full_name); + + LLUrlEntryBase::LLUrlEntryBase() : mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), mDisabledLink(false) @@ -140,7 +144,8 @@ void LLUrlEntryBase::addObserver(const std::string &id, mObservers.insert(std::pair(id, observer)); } } - + +// *NOTE: See also LLUrlEntryAgent::callObservers() void LLUrlEntryBase::callObservers(const std::string &id, const std::string &label, const std::string &icon) @@ -322,18 +327,33 @@ LLUrlEntryAgent::LLUrlEntryAgent() mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); } -void LLUrlEntryAgent::onNameCache(const LLUUID& id, - const std::string& full_name, - bool is_group) +// virtual +void LLUrlEntryAgent::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) { - callObservers(id.asString(), full_name, mIcon); + // notify all callbacks waiting on the given uuid + std::multimap::iterator it; + for (it = mObservers.find(id); it != mObservers.end();) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + std::string final_label = localize_slapp_label(observer.url, label); + (*observer.signal)(observer.url, final_label, icon); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } } void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) { - // IDEVO demo code - std::string label = av_name.mDisplayName + " (" + av_name.mSLID + ")"; + std::string label = av_name.mDisplayName; + if (!av_name.mSLID.empty()) + { + label += " (" + av_name.mSLID + ")"; + } // received the agent name from the server - tell our observers callObservers(id.asString(), label, mIcon); } @@ -391,64 +411,56 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa return LLTrans::getString("AvatarNameNobody"); } - if (LLAvatarNameCache::useDisplayNames()) + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) { - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_id, &av_name)) - { - return av_name.mDisplayName + " (" + av_name.mSLID + ")"; - } - else + std::string label = av_name.mDisplayName; + if (!av_name.mSLID.empty()) { - LLAvatarNameCache::get(agent_id, - boost::bind(&LLUrlEntryAgent::onAvatarNameCache, - this, _1, _2)); - addObserver(agent_id_string, url, cb); - return LLTrans::getString("LoadingData"); + label += " (" + av_name.mSLID + ")"; } + // handle suffixes like /mute or /offerteleport + label = localize_slapp_label(url, label); + return label; } else { - // ...no display names - std::string full_name; - if (gCacheName->getFullName(agent_id, full_name)) - { - // customize label string based on agent SLapp suffix - if (LLStringUtil::endsWith(url, "/mute")) - { - return LLTrans::getString("SLappAgentMute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/unmute")) - { - return LLTrans::getString("SLappAgentUnmute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/im")) - { - return LLTrans::getString("SLappAgentIM") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/pay")) - { - return LLTrans::getString("SLappAgentPay") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/offerteleport")) - { - return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/requestfriend")) - { - return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; - } - return full_name; - } - else - { - gCacheName->get(agent_id, false, - boost::bind(&LLUrlEntryAgent::onNameCache, - this, _1, _2, _3)); + LLAvatarNameCache::get(agent_id, + boost::bind(&LLUrlEntryAgent::onAvatarNameCache, + this, _1, _2)); addObserver(agent_id_string, url, cb); return LLTrans::getString("LoadingData"); } } + +std::string localize_slapp_label(const std::string& url, const std::string& full_name) +{ + // customize label string based on agent SLapp suffix + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("SLappAgentMute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("SLappAgentUnmute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("SLappAgentIM") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("SLappAgentPay") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; + } + return full_name; } diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index d5d4d9ee24..b6e422f09e 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -104,7 +104,7 @@ protected: std::string getLabelFromWikiLink(const std::string &url) const; std::string getUrlFromWikiLink(const std::string &string) const; void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); - void callObservers(const std::string &id, const std::string &label, const std::string& icon); + virtual void callObservers(const std::string &id, const std::string &label, const std::string& icon); typedef struct { std::string url; @@ -173,8 +173,9 @@ public: /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); /*virtual*/ std::string getIcon(const std::string &url) const; /*virtual*/ std::string getTooltip(const std::string &string) const; +protected: + /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); private: - void onNameCache(const LLUUID& id, const std::string& full_name, bool is_group); void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); }; -- cgit v1.2.3 From 31a0d236d59de5eaaa9e33ed99e6971c1ee01ad7 Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 11 May 2010 17:26:29 -0700 Subject: Clean up merge-fight about getIcon() const-ness --- indra/llui/llurlentry.cpp | 4 ++-- indra/llui/llurlentry.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index f3e1a07fbc..f9e5bd76d0 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -62,7 +62,7 @@ std::string LLUrlEntryBase::getUrl(const std::string &string) const } //virtual -std::string LLUrlEntryBase::getIcon(const std::string &url) const +std::string LLUrlEntryBase::getIcon(const std::string &url) { return mIcon; } @@ -464,7 +464,7 @@ std::string localize_slapp_label(const std::string& url, const std::string& full } -std::string LLUrlEntryAgent::getIcon(const std::string &url) const +std::string LLUrlEntryAgent::getIcon(const std::string &url) { // *NOTE: Could look up a badge here by calling getIDStringFromUrl() // and looking up the badge for the agent. diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index d89f647a55..0abf7a603f 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -80,7 +80,7 @@ public: virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } /// Return an icon that can be displayed next to Urls of this type - virtual std::string getIcon(const std::string &url) const; + virtual std::string getIcon(const std::string &url); /// Return the color to render the displayed text LLUIColor getColor() const { return mColor; } @@ -171,7 +171,7 @@ class LLUrlEntryAgent : public LLUrlEntryBase public: LLUrlEntryAgent(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url) const; + /*virtual*/ std::string getIcon(const std::string &url); /*virtual*/ std::string getTooltip(const std::string &string) const; protected: /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); @@ -308,7 +308,7 @@ public: LLUrlEntryIcon(); /*virtual*/ std::string getUrl(const std::string &string) const; /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url) const; + /*virtual*/ std::string getIcon(const std::string &url); }; -- cgit v1.2.3 From e2deaf5a14a01020afe4240f33e438a0676ba0ec Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 11 May 2010 17:26:55 -0700 Subject: Clean up merge about name cache callback prototype --- indra/llui/llnotifications.cpp | 6 ++++-- indra/llui/llnotifications.h | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 7b8f51ae3c..23f79aad36 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1488,9 +1488,11 @@ std::ostream& operator<<(std::ostream& s, const LLNotification& notification) return s; } -void LLPostponedNotification::onCachedNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, bool is_group) +void LLPostponedNotification::onCachedNameReceived(const LLUUID& id, + const std::string& full_name, + bool is_group) { + // *TODO: This is dumb, just use full_name as given gCacheName->getFullName(id, mName); modifyNotificationParams(); LLNotifications::instance().add(mParams); diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index c942a32512..f818b386b5 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -1005,12 +1005,11 @@ public: gCacheName->get(id, is_group, boost::bind( &LLPostponedNotification::onCachedNameReceived, thiz, _1, _2, - _3, _4)); + _3)); } private: - void onCachedNameReceived(const LLUUID& id, const std::string& first, - const std::string& last, bool is_group); + void onCachedNameReceived(const LLUUID& id, const std::string& full_name, bool is_group); void cleanup() { -- cgit v1.2.3 From f4148502e484d516b42c4a88603eee6889d45697 Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 18 May 2010 16:00:45 -0700 Subject: Rename mSLID to mUsername to match the name of the field in the UI Product made a late-breaking request to change the name of this field. The wire protocol for People API has not yet changed. --- indra/llui/llurlentry.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index d3c866a854..aeb02aef1c 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -353,9 +353,9 @@ void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) { std::string label = av_name.mDisplayName; - if (!av_name.mSLID.empty()) + if (!av_name.mUsername.empty()) { - label += " (" + av_name.mSLID + ")"; + label += " (" + av_name.mUsername + ")"; } // received the agent name from the server - tell our observers callObservers(id.asString(), label, mIcon); @@ -418,9 +418,9 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa if (LLAvatarNameCache::get(agent_id, &av_name)) { std::string label = av_name.mDisplayName; - if (!av_name.mSLID.empty()) + if (!av_name.mUsername.empty()) { - label += " (" + av_name.mSLID + ")"; + label += " (" + av_name.mUsername + ")"; } // handle suffixes like /mute or /offerteleport label = localize_slapp_label(url, label); -- cgit v1.2.3 From 0df5c0e879ef05116565a79cdf344de5c45ee19b Mon Sep 17 00:00:00 2001 From: James Cook Date: Thu, 20 May 2010 14:15:27 -0700 Subject: DEV-50013 Tooltip for SLAPP agent inspect links Reviewed with Erica (language) / Leyla (code) --- indra/llui/llurlentry.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index aeb02aef1c..36b644484d 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -366,6 +366,10 @@ std::string LLUrlEntryAgent::getTooltip(const std::string &string) const // return a tooltip corresponding to the URL type instead of the generic one std::string url = getUrl(string); + if (LLStringUtil::endsWith(url, "/inspect")) + { + return LLTrans::getString("TooltipAgentInspect"); + } if (LLStringUtil::endsWith(url, "/mute")) { return LLTrans::getString("TooltipAgentMute"); -- cgit v1.2.3 From 934dc6e2d0fabb935c75bb611ceb543426e485eb Mon Sep 17 00:00:00 2001 From: James Cook Date: Mon, 24 May 2010 16:59:05 -0700 Subject: DEV-50013 Inventory offer notifications use new name system Reviewed with Richard --- indra/llui/llnotifications.cpp | 42 +++++++++++++++++++++++++++++++++++++----- indra/llui/llnotifications.h | 16 ++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 23f79aad36..4a776f577f 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -34,6 +34,8 @@ #include "llnotifications.h" +#include "llavatarnamecache.h" +#include "llcachename.h" #include "llxmlnode.h" #include "lluictrl.h" #include "lluictrlfactory.h" @@ -1488,12 +1490,42 @@ std::ostream& operator<<(std::ostream& s, const LLNotification& notification) return s; } -void LLPostponedNotification::onCachedNameReceived(const LLUUID& id, - const std::string& full_name, - bool is_group) +//static +void LLPostponedNotification::lookupName(LLPostponedNotification* thiz, + const LLUUID& id, + bool is_group) +{ + if (is_group) + { + gCacheName->get(id, is_group, + boost::bind(&LLPostponedNotification::onGroupNameCache, + thiz, _1, _2, _3)); + } + else + { + LLAvatarNameCache::get(id, + boost::bind(&LLPostponedNotification::onAvatarNameCache, + thiz, _1, _2)); + } +} + +void LLPostponedNotification::onGroupNameCache(const LLUUID& id, + const std::string& full_name, + bool is_group) +{ + finalizeName(full_name); +} + +void LLPostponedNotification::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + std::string name = av_name.getCompleteName(); + finalizeName(name); +} + +void LLPostponedNotification::finalizeName(const std::string& name) { - // *TODO: This is dumb, just use full_name as given - gCacheName->getFullName(id, mName); + mName = name; modifyNotificationParams(); LLNotifications::instance().add(mParams); cleanup(); diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index f818b386b5..73357daaef 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -104,8 +104,8 @@ #include "llinitparam.h" #include "llnotificationslistener.h" #include "llnotificationptr.h" -#include "llcachename.h" +class LLAvatarName; typedef enum e_notification_priority { @@ -1000,16 +1000,20 @@ public: { // upcast T to the base type to restrict T derivation from LLPostponedNotification LLPostponedNotification* thiz = new T(); - thiz->mParams = params; - gCacheName->get(id, is_group, boost::bind( - &LLPostponedNotification::onCachedNameReceived, thiz, _1, _2, - _3)); + // Avoid header file dependency on llcachename.h + lookupName(thiz, id, is_group); } private: - void onCachedNameReceived(const LLUUID& id, const std::string& full_name, bool is_group); + static void lookupName(LLPostponedNotification* thiz, const LLUUID& id, bool is_group); + // only used for groups + void onGroupNameCache(const LLUUID& id, const std::string& full_name, bool is_group); + // only used for avatars + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + // used for both group and avatar names + void finalizeName(const std::string& name); void cleanup() { -- cgit v1.2.3 From 4ea7d2de9fcab4dd4694fcbbd2fece1c57f41d2c Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Mon, 24 May 2010 17:25:35 -0700 Subject: DEV-50271 FIX SLURL support for non-clickable display names reviewed by James --- indra/llui/lltextbase.cpp | 4 +- indra/llui/llurlentry.cpp | 145 +++++++++++++++++++++++++++++++++-- indra/llui/llurlentry.h | 66 +++++++++++++++- indra/llui/llurlmatch.cpp | 4 +- indra/llui/llurlmatch.h | 8 +- indra/llui/llurlregistry.cpp | 9 ++- indra/llui/tests/llurlentry_stub.cpp | 103 +++++++++++++++++++++++++ indra/llui/tests/llurlmatch_test.cpp | 132 +++++++++++++++++++++++++++---- 8 files changed, 433 insertions(+), 38 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 78312eba73..c93e6c7257 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1583,9 +1583,7 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c end = match.getEnd()+1; LLStyle::Params link_params = style_params; - link_params.color = match.getColor(); - link_params.readonly_color = match.getColor(); - link_params.font.style("UNDERLINE"); + link_params.overwriteFrom(match.getStyle()); link_params.link_href = match.getUrl(); // output the text before the Url diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 36b644484d..7ba569408a 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -49,9 +49,11 @@ std::string localize_slapp_label(const std::string& url, const std::string& full LLUrlEntryBase::LLUrlEntryBase() : - mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), mDisabledLink(false) { + mStyle.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + mStyle.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + mStyle.font.style("UNDERLINE"); } LLUrlEntryBase::~LLUrlEntryBase() @@ -327,7 +329,8 @@ LLUrlEntryAgent::LLUrlEntryAgent() boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; - mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); + mStyle.color = LLUIColorTable::instance().getColor("AgentLinkColor"); + mStyle.readonly_color = LLUIColorTable::instance().getColor("AgentLinkColor"); } // virtual @@ -421,11 +424,8 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa LLAvatarName av_name; if (LLAvatarNameCache::get(agent_id, &av_name)) { - std::string label = av_name.mDisplayName; - if (!av_name.mUsername.empty()) - { - label += " (" + av_name.mUsername + ")"; - } + std::string label = av_name.getCompleteName(); + // handle suffixes like /mute or /offerteleport label = localize_slapp_label(url, label); return label; @@ -478,6 +478,134 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) return mIcon; } +// +// LLUrlEntryAgentName describes a Second Life agent name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// +LLUrlEntryAgentName::LLUrlEntryAgentName() +{ + mDisabledLink = true; + mStyle.color.setProvided(false); + mStyle.readonly_color.setProvided(false); + mStyle.font.setProvided(false); +} + +// virtual +void LLUrlEntryAgentName::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) +{ + // notify all callbacks waiting on the given uuid + std::multimap::iterator it; + for (it = mObservers.find(id); it != mObservers.end();) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + (*observer.signal)(observer.url, label, icon); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } +} + +void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) +{ + std::string label = getName(av_name); + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, mIcon); +} + +std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID agent_id(agent_id_string); + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) + { + return getName(av_name); + } + else + { + LLAvatarNameCache::get(agent_id, + boost::bind(&LLUrlEntryAgentCompleteName::onAvatarNameCache, + this, _1, _2)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +std::string LLUrlEntryAgentName::getUrl(const std::string &url) const +{ + return LLStringUtil::null; +} + +// +// LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// +LLUrlEntryAgentCompleteName::LLUrlEntryAgentCompleteName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/completename", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentCompleteName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getCompleteName(); +} + +// +// LLUrlEntryAgentDisplayName describes a Second Life agent display name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// +LLUrlEntryAgentDisplayName::LLUrlEntryAgentDisplayName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/displayname", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentDisplayName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.mDisplayName; +} + +// +// LLUrlEntryAgentUserName describes a Second Life agent user name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// +LLUrlEntryAgentUserName::LLUrlEntryAgentUserName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/username", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentUserName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.mUsername.empty() ? avatar_name.getLegacyName() : avatar_name.mUsername; +} + // // LLUrlEntryGroup Describes a Second Life group Url, e.g., // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about @@ -491,7 +619,8 @@ LLUrlEntryGroup::LLUrlEntryGroup() mMenuName = "menu_url_group.xml"; mIcon = "Generic_Group"; mTooltip = LLTrans::getString("TooltipGroupUrl"); - mColor = LLUIColorTable::instance().getColor("GroupLinkColor"); + mStyle.color = LLUIColorTable::instance().getColor("GroupLinkColor"); + mStyle.readonly_color = LLUIColorTable::instance().getColor("GroupLinkColor"); } void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 0abf7a603f..21d4c29b2b 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -36,6 +36,7 @@ #include "lluuid.h" #include "lluicolor.h" +#include "llstyle.h" #include #include #include @@ -82,8 +83,8 @@ public: /// Return an icon that can be displayed next to Urls of this type virtual std::string getIcon(const std::string &url); - /// Return the color to render the displayed text - LLUIColor getColor() const { return mColor; } + /// Return the style to render the displayed text + LLStyle::Params getStyle() const { return mStyle; } /// Given a matched Url, return a tooltip string for the hyperlink virtual std::string getTooltip(const std::string &string) const { return mTooltip; } @@ -115,7 +116,7 @@ protected: std::string mIcon; std::string mMenuName; std::string mTooltip; - LLUIColor mColor; + LLStyle::Params mStyle; std::multimap mObservers; bool mDisabledLink; }; @@ -179,6 +180,65 @@ private: void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); }; +/// +/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +/// that displays various forms of user name +/// This is a base class for the various implementations of name display +class LLUrlEntryAgentName : public LLUrlEntryBase +{ +public: + LLUrlEntryAgentName(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; +protected: + /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); + // override this to pull out relevant name fields + virtual std::string getName(const LLAvatarName& avatar_name) = 0; +private: + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); +}; + + +/// +/// LLUrlEntryAgentCompleteName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +/// that displays the full display name + user name for an avatar +/// such as "James Linden (james.linden)" +class LLUrlEntryAgentCompleteName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentCompleteName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentDisplayName Describes a Second Life agent display name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +/// that displays the just the display name for an avatar +/// such as "James Linden" +class LLUrlEntryAgentDisplayName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentDisplayName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentUserName Describes a Second Life agent username Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +/// that displays the just the display name for an avatar +/// such as "james.linden" +class LLUrlEntryAgentUserName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentUserName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + /// /// LLUrlEntryGroup Describes a Second Life group Url, e.g., /// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 72a199c220..8a7e2e162b 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -49,7 +49,7 @@ LLUrlMatch::LLUrlMatch() : void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, - const std::string &icon, const LLUIColor& color, + const std::string &icon, const LLStyle::Params& style, const std::string &menu, const std::string &location, bool disabled_link) { @@ -59,7 +59,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mLabel = label; mTooltip = tooltip; mIcon = icon; - mColor = color; + mStyle = style; mMenuName = menu; mLocation = location; mDisabledLink = disabled_link; diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index e86762548b..92a87c3d4c 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -38,7 +38,7 @@ #include #include -#include "lluicolor.h" +#include "llstyle.h" /// /// LLUrlMatch describes a single Url that was matched within a string by @@ -75,7 +75,7 @@ public: std::string getIcon() const { return mIcon; } /// Return the color to render the displayed text - LLUIColor getColor() const { return mColor; } + LLStyle::Params getStyle() const { return mStyle; } /// Return the name of a XUI file containing the context menu items std::string getMenuName() const { return mMenuName; } @@ -89,7 +89,7 @@ public: /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, - const LLUIColor& color, const std::string &menu, + const LLStyle::Params& style, const std::string &menu, const std::string &location, bool disabled_link); private: @@ -101,7 +101,7 @@ private: std::string mIcon; std::string mMenuName; std::string mLocation; - LLUIColor mColor; + LLStyle::Params mStyle; bool mDisabledLink; }; diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 7a866f44c2..f61603545f 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -51,6 +51,11 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); + registerUrl(new LLUrlEntryAgentCompleteName()); + registerUrl(new LLUrlEntryAgentDisplayName()); + registerUrl(new LLUrlEntryAgentUserName()); + // LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since + // LLUrlEntryAgent is a less specific (catchall for agent urls) registerUrl(new LLUrlEntryAgent()); registerUrl(new LLUrlEntryGroup()); registerUrl(new LLUrlEntryParcel()); @@ -185,7 +190,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getLabel(url, cb), match_entry->getTooltip(url), match_entry->getIcon(url), - match_entry->getColor(), + match_entry->getStyle(), match_entry->getMenuName(), match_entry->getLocation(url), match_entry->isLinkDisabled()); @@ -219,7 +224,7 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.getLabel(), match.getTooltip(), match.getIcon(), - match.getColor(), + match.getStyle(), match.getMenuName(), match.getLocation(), match.isLinkDisabled()); diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index e984f5cf81..5c57ac786e 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -79,3 +79,106 @@ std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil:: { return std::string(); } + +// +// Stub implementation for LLStyle::Params::Params +// + +LLStyle::Params::Params() +{ +} + +// +// Stub implementations for various LLInitParam classes +// + +namespace LLInitParam +{ + BaseBlock::BaseBlock() {} + BaseBlock::~BaseBlock() {} + Param::Param(BaseBlock* enclosing_block) + : mIsProvided(false) + { + const U8* my_addr = reinterpret_cast(this); + const U8* block_addr = reinterpret_cast(enclosing_block); + mEnclosingBlockOffset = (U16)(my_addr - block_addr); + } + void BaseBlock::setLastChangedParam(const Param& last_param, bool user_provided) {} + + void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptor& in_param, const char* char_name){} + param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} + + void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) + { + mBlockDescriptor = &descriptor; + descriptor.mCurrentBlockPtr = this; + } + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack){ return true; } + bool BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t name_stack, const LLInitParam::BaseBlock* diff_block) const { return true; } + bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack) const { return true; } + bool BaseBlock::merge(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } + bool BaseBlock::validateBlock(bool emit_errors) const { return true; } + + TypedParam::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count) + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + void TypeValues::declareValues() + {} + + bool ParamCompare::equals(const LLFontGL* a, const LLFontGL* b) + { + return false; + } + + TypedParam::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, _name, value, func, min_count, max_count) + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + void TypeValues::declareValues() + {} + + void TypeValues::declareValues() + {} + + void TypeValues::declareValues() + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + + bool ParamCompare::equals( + LLUIImage* const &a, + LLUIImage* const &b) + { + return false; + } + + bool ParamCompare::equals(const LLUIColor &a, const LLUIColor &b) + { + return false; + } + +} + +//static +LLFontGL* LLFontGL::getFontDefault() +{ + return NULL; +} diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index 24a32de268..ab5770dbf2 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -23,11 +23,111 @@ #include "../llurlmatch.h" #include "lltut.h" -// link seam +// link seams + LLUIColor::LLUIColor() : mColorPtr(NULL) {} +LLStyle::Params::Params() +{ +} + +namespace LLInitParam +{ + BaseBlock::BaseBlock() {} + BaseBlock::~BaseBlock() {} + + void BaseBlock::setLastChangedParam(const Param& last_param, bool user_provided) {} + + void BaseBlock::addParam(BlockDescriptor& block_data, const ParamDescriptor& in_param, const char* char_name){} + param_handle_t BaseBlock::getHandleFromParam(const Param* param) const {return 0;} + + void BaseBlock::init(BlockDescriptor& descriptor, BlockDescriptor& base_descriptor, size_t block_size) + { + mBlockDescriptor = &descriptor; + descriptor.mCurrentBlockPtr = this; + } + + Param::Param(BaseBlock* enclosing_block) + : mIsProvided(false) + { + const U8* my_addr = reinterpret_cast(this); + const U8* block_addr = reinterpret_cast(enclosing_block); + mEnclosingBlockOffset = (U16)(my_addr - block_addr); + } + + bool BaseBlock::deserializeBlock(Parser& p, Parser::name_stack_range_t name_stack){ return true; } + bool BaseBlock::serializeBlock(Parser& parser, Parser::name_stack_t name_stack, const LLInitParam::BaseBlock* diff_block) const { return true; } + bool BaseBlock::inspectBlock(Parser& parser, Parser::name_stack_t name_stack) const { return true; } + bool BaseBlock::merge(BlockDescriptor& block_data, const BaseBlock& other, bool overwrite) { return true; } + bool BaseBlock::validateBlock(bool emit_errors) const { return true; } + + TypedParam::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count) + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + void TypeValues::declareValues() + {} + + bool ParamCompare::equals(const LLFontGL* a, const LLFontGL* b) + { + return false; + } + + TypedParam::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, _name, value, func, min_count, max_count) + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + void TypeValues::declareValues() + {} + + void TypeValues::declareValues() + {} + + void TypeValues::declareValues() + {} + + void TypedParam::setValueFromBlock() const + {} + + void TypedParam::setBlockFromValue() + {} + + + bool ParamCompare::equals( + LLUIImage* const &a, + LLUIImage* const &b) + { + return false; + } + + bool ParamCompare::equals(const LLUIColor &a, const LLUIColor &b) + { + return false; + } + +} + +//static +LLFontGL* LLFontGL::getFontDefault() +{ + return NULL; +} + + namespace tut { struct LLUrlMatchData @@ -54,7 +154,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", "", false); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", false); ensure("! empty()", ! match.empty()); } @@ -67,7 +167,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -80,7 +180,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -93,10 +193,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -109,10 +209,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", false); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -125,10 +225,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", false); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -141,10 +241,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", false); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -157,10 +257,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", false); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -173,10 +273,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", false); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure("getLocation() empty (2)", match.getLocation().empty()); } } -- cgit v1.2.3 From f682c996d28c3841b6709f8fc67ba443bfcd1926 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Mon, 24 May 2010 18:29:29 -0700 Subject: DEV-50271 FIX SLURL support for non-clickable display names --- indra/llui/lltextbase.cpp | 33 ++++++++----------- indra/llui/llurlentry.cpp | 64 +++++++++++++++++++++++------------- indra/llui/llurlentry.h | 11 +++---- indra/llui/llurlmatch.cpp | 8 ++--- indra/llui/llurlmatch.h | 6 +--- indra/llui/llurlregistry.cpp | 6 ++-- indra/llui/tests/llurlmatch_test.cpp | 30 ++++++++--------- 7 files changed, 81 insertions(+), 77 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c93e6c7257..9a07712757 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1582,9 +1582,8 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c start = match.getStart(); end = match.getEnd()+1; - LLStyle::Params link_params = style_params; + LLStyle::Params link_params(style_params); link_params.overwriteFrom(match.getStyle()); - link_params.link_href = match.getUrl(); // output the text before the Url if (start > 0) @@ -1622,26 +1621,20 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c } } - // output the styled Url (unless we've been asked to suppress hyperlinking) - if (match.isLinkDisabled()) - { - appendAndHighlightText(match.getLabel(), prepend_newline, part, style_params); - } - else - { - appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); + // output the styled Url + appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); - // set the tooltip for the Url label - if (! match.getTooltip().empty()) - { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) - { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); - } - } + // set the tooltip for the Url label + if (! match.getTooltip().empty()) + { + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(match.getTooltip()); + } } + prepend_newline = false; // move on to the rest of the text after the Url diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 7ba569408a..2dda64c434 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -48,13 +48,8 @@ std::string localize_slapp_label(const std::string& url, const std::string& full_name); -LLUrlEntryBase::LLUrlEntryBase() : - mDisabledLink(false) -{ - mStyle.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - mStyle.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - mStyle.font.style("UNDERLINE"); -} +LLUrlEntryBase::LLUrlEntryBase() +{} LLUrlEntryBase::~LLUrlEntryBase() { @@ -71,6 +66,16 @@ std::string LLUrlEntryBase::getIcon(const std::string &url) return mIcon; } +LLStyle::Params LLUrlEntryBase::getStyle() const +{ + LLStyle::Params style_params; + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.font.style = "UNDERLINE"; + return style_params; +} + + std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const { // return the id from a SLURL in the format /app/{cmd}/{id}/about @@ -329,8 +334,6 @@ LLUrlEntryAgent::LLUrlEntryAgent() boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; - mStyle.color = LLUIColorTable::instance().getColor("AgentLinkColor"); - mStyle.readonly_color = LLUIColorTable::instance().getColor("AgentLinkColor"); } // virtual @@ -355,11 +358,8 @@ void LLUrlEntryAgent::callObservers(const std::string &id, void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) { - std::string label = av_name.mDisplayName; - if (!av_name.mUsername.empty()) - { - label += " (" + av_name.mUsername + ")"; - } + std::string label = av_name.getCompleteName(); + // received the agent name from the server - tell our observers callObservers(id.asString(), label, mIcon); } @@ -440,6 +440,14 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } } +LLStyle::Params LLUrlEntryAgent::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("AgentLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("AgentLinkColor"); + return style_params; +} + std::string localize_slapp_label(const std::string& url, const std::string& full_name) { // customize label string based on agent SLapp suffix @@ -485,10 +493,6 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) // LLUrlEntryAgentName::LLUrlEntryAgentName() { - mDisabledLink = true; - mStyle.color.setProvided(false); - mStyle.readonly_color.setProvided(false); - mStyle.font.setProvided(false); } // virtual @@ -558,6 +562,11 @@ std::string LLUrlEntryAgentName::getUrl(const std::string &url) const return LLStringUtil::null; } +LLStyle::Params LLUrlEntryAgentName::getStyle() const +{ + return LLStyle::Params(); +} + // // LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename @@ -619,8 +628,6 @@ LLUrlEntryGroup::LLUrlEntryGroup() mMenuName = "menu_url_group.xml"; mIcon = "Generic_Group"; mTooltip = LLTrans::getString("TooltipGroupUrl"); - mStyle.color = LLUIColorTable::instance().getColor("GroupLinkColor"); - mStyle.readonly_color = LLUIColorTable::instance().getColor("GroupLinkColor"); } void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, @@ -666,6 +673,15 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa } } +LLStyle::Params LLUrlEntryGroup::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("GroupLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("GroupLinkColor"); + return style_params; +} + + // // LLUrlEntryInventory Describes a Second Life inventory Url, e.g., // secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select @@ -953,7 +969,6 @@ LLUrlEntryNoLink::LLUrlEntryNoLink() { mPattern = boost::regex("[^<]*", boost::regex::perl|boost::regex::icase); - mDisabledLink = true; } std::string LLUrlEntryNoLink::getUrl(const std::string &url) const @@ -967,6 +982,12 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC return getUrl(url); } +LLStyle::Params LLUrlEntryNoLink::getStyle() const +{ + return LLStyle::Params(); +} + + // // LLUrlEntryIcon describes an icon with ... tags // @@ -974,7 +995,6 @@ LLUrlEntryIcon::LLUrlEntryIcon() { mPattern = boost::regex("\\s*([^<]*)?\\s*", boost::regex::perl|boost::regex::icase); - mDisabledLink = true; } std::string LLUrlEntryIcon::getUrl(const std::string &url) const diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 21d4c29b2b..cd93d1ac41 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -84,7 +84,7 @@ public: virtual std::string getIcon(const std::string &url); /// Return the style to render the displayed text - LLStyle::Params getStyle() const { return mStyle; } + virtual LLStyle::Params getStyle() const; /// Given a matched Url, return a tooltip string for the hyperlink virtual std::string getTooltip(const std::string &string) const { return mTooltip; } @@ -95,9 +95,6 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - protected: std::string getIDStringFromUrl(const std::string &url) const; std::string escapeUrl(const std::string &url) const; @@ -116,9 +113,7 @@ protected: std::string mIcon; std::string mMenuName; std::string mTooltip; - LLStyle::Params mStyle; std::multimap mObservers; - bool mDisabledLink; }; /// @@ -174,6 +169,7 @@ public: /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); /*virtual*/ std::string getIcon(const std::string &url); /*virtual*/ std::string getTooltip(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; protected: /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); private: @@ -191,6 +187,7 @@ public: LLUrlEntryAgentName(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; protected: /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); // override this to pull out relevant name fields @@ -248,6 +245,7 @@ class LLUrlEntryGroup : public LLUrlEntryBase public: LLUrlEntryGroup(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ LLStyle::Params getStyle() const; private: void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); }; @@ -357,6 +355,7 @@ public: LLUrlEntryNoLink(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; }; /// diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 8a7e2e162b..51fca6d7c0 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -42,16 +42,14 @@ LLUrlMatch::LLUrlMatch() : mTooltip(""), mIcon(""), mMenuName(""), - mLocation(""), - mDisabledLink(false) + mLocation("") { } void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, - const std::string &menu, const std::string &location, - bool disabled_link) + const std::string &menu, const std::string &location) { mStart = start; mEnd = end; @@ -60,7 +58,7 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mTooltip = tooltip; mIcon = icon; mStyle = style; + mStyle.link_href = url; mMenuName = menu; mLocation = location; - mDisabledLink = disabled_link; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 92a87c3d4c..43bd9da14a 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -83,14 +83,11 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, const std::string &menu, - const std::string &location, bool disabled_link); + const std::string &location); private: U32 mStart; @@ -102,7 +99,6 @@ private: std::string mMenuName; std::string mLocation; LLStyle::Params mStyle; - bool mDisabledLink; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index f61603545f..f119233f8f 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -192,8 +192,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getIcon(url), match_entry->getStyle(), match_entry->getMenuName(), - match_entry->getLocation(url), - match_entry->isLinkDisabled()); + match_entry->getLocation(url)); return true; } @@ -226,8 +225,7 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.getIcon(), match.getStyle(), match.getMenuName(), - match.getLocation(), - match.isLinkDisabled()); + match.getLocation()); return true; } return false; diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index ab5770dbf2..85318196e0 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -154,7 +154,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", false); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", ""); ensure("! empty()", ! match.empty()); } @@ -167,7 +167,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -180,7 +180,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -193,10 +193,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -209,10 +209,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", ""); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -225,10 +225,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", ""); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -241,10 +241,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", ""); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -257,10 +257,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", ""); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -273,10 +273,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris"); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure("getLocation() empty (2)", match.getLocation().empty()); } } -- cgit v1.2.3 From 77e6fcd4230f7540c9f4d6f3d503737c6eaf408c Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 25 May 2010 11:39:06 -0700 Subject: DEV-50013 WIP gCacheName->getGroup() to distinguish from av lookups Helps with global finds looking for remaining calls to patch. Reviewed with Leyla. --- indra/llui/llnotifications.cpp | 2 +- indra/llui/llurlentry.cpp | 2 +- indra/llui/tests/llurlentry_stub.cpp | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 4a776f577f..27e59a0475 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1497,7 +1497,7 @@ void LLPostponedNotification::lookupName(LLPostponedNotification* thiz, { if (is_group) { - gCacheName->get(id, is_group, + gCacheName->getGroup(id, boost::bind(&LLPostponedNotification::onGroupNameCache, thiz, _1, _2, _3)); } diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 36b644484d..792e34f1e7 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -529,7 +529,7 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa } else { - gCacheName->get(group_id, true, + gCacheName->getGroup(group_id, boost::bind(&LLUrlEntryGroup::onGroupNameReceived, this, _1, _2, _3)); addObserver(group_id_string, url, cb); diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp index e984f5cf81..7566fd1af7 100644 --- a/indra/llui/tests/llurlentry_stub.cpp +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -64,6 +64,11 @@ boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, co return boost::signals2::connection(); } +boost::signals2::connection LLCacheName::getGroup(const LLUUID& id, const LLCacheNameCallback& callback) +{ + return boost::signals2::connection(); +} + LLCacheName* gCacheName = NULL; // -- cgit v1.2.3 From d539145367d74c0881bcf47daefaca4820f04c72 Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Tue, 25 May 2010 18:52:24 -0700 Subject: DEV-50271 FIX SLURL support for non-clickable display names revived mDisabledLink to make url replacement logic work again --- indra/llui/llurlentry.cpp | 28 +++++----------------------- indra/llui/llurlentry.h | 6 ++++-- indra/llui/llurlmatch.cpp | 7 +++++-- indra/llui/llurlmatch.h | 6 +++++- indra/llui/llurlregistry.cpp | 6 ++++-- indra/llui/tests/llurlmatch_test.cpp | 30 +++++++++++++++--------------- 6 files changed, 38 insertions(+), 45 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 866c228a12..60566f457d 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -49,6 +49,7 @@ std::string localize_slapp_label(const std::string& url, const std::string& full LLUrlEntryBase::LLUrlEntryBase() +: mDisabledLink(false) {} LLUrlEntryBase::~LLUrlEntryBase() @@ -493,24 +494,7 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) // LLUrlEntryAgentName::LLUrlEntryAgentName() { -} - -// virtual -void LLUrlEntryAgentName::callObservers(const std::string &id, - const std::string &label, - const std::string &icon) -{ - // notify all callbacks waiting on the given uuid - std::multimap::iterator it; - for (it = mObservers.find(id); it != mObservers.end();) - { - // call the callback - give it the new label - LLUrlEntryObserver &observer = it->second; - (*observer.signal)(observer.url, label, icon); - // then remove the signal - we only need to call it once - delete observer.signal; - mObservers.erase(it++); - } + mDisabledLink = true; } void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, @@ -557,13 +541,9 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab } } -std::string LLUrlEntryAgentName::getUrl(const std::string &url) const -{ - return LLStringUtil::null; -} - LLStyle::Params LLUrlEntryAgentName::getStyle() const { + // don't override default colors return LLStyle::Params(); } @@ -969,6 +949,7 @@ LLUrlEntryNoLink::LLUrlEntryNoLink() { mPattern = boost::regex("[^<]*", boost::regex::perl|boost::regex::icase); + mDisabledLink = true; } std::string LLUrlEntryNoLink::getUrl(const std::string &url) const @@ -995,6 +976,7 @@ LLUrlEntryIcon::LLUrlEntryIcon() { mPattern = boost::regex("\\s*([^<]*)?\\s*", boost::regex::perl|boost::regex::icase); + mDisabledLink = true; } std::string LLUrlEntryIcon::getUrl(const std::string &url) const diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index cd93d1ac41..ca4562cee0 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -95,6 +95,9 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } + /// is this a match for a URL that should not be hyperlinked? + bool isLinkDisabled() const { return mDisabledLink; } + protected: std::string getIDStringFromUrl(const std::string &url) const; std::string escapeUrl(const std::string &url) const; @@ -114,6 +117,7 @@ protected: std::string mMenuName; std::string mTooltip; std::multimap mObservers; + bool mDisabledLink; }; /// @@ -186,10 +190,8 @@ class LLUrlEntryAgentName : public LLUrlEntryBase public: LLUrlEntryAgentName(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; /*virtual*/ LLStyle::Params getStyle() const; protected: - /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); // override this to pull out relevant name fields virtual std::string getName(const LLAvatarName& avatar_name) = 0; private: diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 51fca6d7c0..dcfdd70b42 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -42,14 +42,16 @@ LLUrlMatch::LLUrlMatch() : mTooltip(""), mIcon(""), mMenuName(""), - mLocation("") + mLocation(""), + mDisabledLink(false) { } void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, - const std::string &menu, const std::string &location) + const std::string &menu, const std::string &location, + bool disabled_link) { mStart = start; mEnd = end; @@ -61,4 +63,5 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mStyle.link_href = url; mMenuName = menu; mLocation = location; + mDisabledLink = disabled_link; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 43bd9da14a..92a87c3d4c 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -83,11 +83,14 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } + /// is this a match for a URL that should not be hyperlinked? + bool isLinkDisabled() const { return mDisabledLink; } + /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, const std::string &menu, - const std::string &location); + const std::string &location, bool disabled_link); private: U32 mStart; @@ -99,6 +102,7 @@ private: std::string mMenuName; std::string mLocation; LLStyle::Params mStyle; + bool mDisabledLink; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index f119233f8f..f61603545f 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -192,7 +192,8 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getIcon(url), match_entry->getStyle(), match_entry->getMenuName(), - match_entry->getLocation(url)); + match_entry->getLocation(url), + match_entry->isLinkDisabled()); return true; } @@ -225,7 +226,8 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.getIcon(), match.getStyle(), match.getMenuName(), - match.getLocation()); + match.getLocation(), + match.isLinkDisabled()); return true; } return false; diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index 85318196e0..ab5770dbf2 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -154,7 +154,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", ""); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", false); ensure("! empty()", ! match.empty()); } @@ -167,7 +167,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -180,7 +180,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -193,10 +193,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -209,10 +209,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", false); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -225,10 +225,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", false); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -241,10 +241,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", false); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -257,10 +257,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", ""); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", false); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -273,10 +273,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris"); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", false); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); ensure("getLocation() empty (2)", match.getLocation().empty()); } } -- cgit v1.2.3 From 640e3a2e9800bdcc772504953af17bd020c1c232 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 26 May 2010 09:24:06 -0700 Subject: Fix broken Mac build on llurlmatch_test.cpp --- indra/llui/tests/llurlmatch_test.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index ab5770dbf2..e5d4c5b09d 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -21,6 +21,7 @@ */ #include "../llurlmatch.h" +//#include "../lluiimage.h" #include "lltut.h" // link seams @@ -33,6 +34,26 @@ LLStyle::Params::Params() { } +LLUIImage::LLUIImage(const std::string& name, LLPointer image) +{ +} + +LLUIImage::~LLUIImage() +{ +} + +//virtual +S32 LLUIImage::getWidth() const +{ + return 0; +} + +//virtual +S32 LLUIImage::getHeight() const +{ + return 0; +} + namespace LLInitParam { BaseBlock::BaseBlock() {} @@ -105,7 +126,6 @@ namespace LLInitParam void TypedParam::setBlockFromValue() {} - bool ParamCompare::equals( LLUIImage* const &a, -- cgit v1.2.3 From 779e199c2906d81a5fdad7dba21ba026534c8da7 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 26 May 2010 09:30:31 -0700 Subject: Fix mac build for llurlentry_test.cpp --- indra/llui/tests/llurlentry_test.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 4463b6cc6f..009d82ed99 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -25,6 +25,7 @@ #include "llurlentry_stub.cpp" #include "lltut.h" #include "../lluicolortable.h" +#include "../lluiimage.h" #include @@ -35,6 +36,26 @@ LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& defa LLUIColor::LLUIColor() : mColorPtr(NULL) {} +LLUIImage::LLUIImage(const std::string& name, LLPointer image) +{ +} + +LLUIImage::~LLUIImage() +{ +} + +//virtual +S32 LLUIImage::getWidth() const +{ + return 0; +} + +//virtual +S32 LLUIImage::getHeight() const +{ + return 0; +} + namespace tut { struct LLUrlEntryData -- cgit v1.2.3 From 703cd608005a60a55963bb5920fd3bec113c7384 Mon Sep 17 00:00:00 2001 From: James Cook Date: Wed, 26 May 2010 10:48:23 -0700 Subject: Clean up llurlmatch test headers --- indra/llui/llurlmatch.h | 2 +- indra/llui/tests/llurlmatch_test.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 92a87c3d4c..293935e251 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -34,7 +34,7 @@ #ifndef LL_LLURLMATCH_H #define LL_LLURLMATCH_H -#include "linden_common.h" +//#include "linden_common.h" #include #include diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index e5d4c5b09d..10c44a9073 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -20,8 +20,10 @@ * $/LicenseInfo$ */ +#include "linden_common.h" + #include "../llurlmatch.h" -//#include "../lluiimage.h" +#include "../lluiimage.h" #include "lltut.h" // link seams -- cgit v1.2.3 From 7435ff08b5f548d6e5c61129179c70d99177cc45 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Wed, 26 May 2010 13:14:47 -0700 Subject: DEV-50271 FIX SLURL support for non-clickable display names separate mIsLink from mLink to support non-clickable urls (e.g. secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename) --- indra/llui/llstyle.cpp | 6 ++++-- indra/llui/llstyle.h | 2 ++ indra/llui/lltextbase.cpp | 2 +- indra/llui/lltextbase.h | 1 + indra/llui/llurlentry.cpp | 9 ++------- indra/llui/llurlentry.h | 4 ---- indra/llui/llurlmatch.cpp | 7 ++----- indra/llui/llurlmatch.h | 6 +----- indra/llui/llurlregistry.cpp | 6 ++---- indra/llui/tests/llurlmatch_test.cpp | 30 +++++++++++++++--------------- 10 files changed, 30 insertions(+), 43 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index b8f93b6a0e..5965ca6fb5 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -44,7 +44,8 @@ LLStyle::Params::Params() color("color", LLColor4::black), font("font", LLFontGL::getFontMonospace()), image("image"), - link_href("href") + link_href("href"), + is_link("is_link") {} @@ -57,6 +58,7 @@ LLStyle::LLStyle(const LLStyle::Params& p) mReadOnlyColor(p.readonly_color()), mFont(p.font()), mLink(p.link_href), + mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()), mDropShadow(p.drop_shadow), mImagep(p.image()) {} @@ -79,7 +81,7 @@ void LLStyle::setLinkHREF(const std::string& href) BOOL LLStyle::isLink() const { - return mLink.size(); + return mIsLink; } BOOL LLStyle::isVisible() const diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 2067e8e8be..0ca1f2027e 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -51,6 +51,7 @@ public: Optional font; Optional image; Optional link_href; + Optional is_link; Params(); }; LLStyle(const Params& p = Params()); @@ -113,6 +114,7 @@ private: std::string mFontName; const LLFontGL* mFont; // cached for performance std::string mLink; + bool mIsLink; LLUIImagePtr mImagep; }; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 915b0427ba..8fc6f16702 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1794,7 +1794,7 @@ void LLTextBase::replaceUrl(const std::string &url, seg->setEnd(seg_start + seg_length); // if we find a link with our Url, then replace the label - if (style->isLink() && style->getLinkHREF() == url) + if (style->getLinkHREF() == url) { S32 start = seg->getStart(); S32 end = seg->getEnd(); diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 3a3a5d0e20..ddf604c195 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -315,6 +315,7 @@ protected: void updateRects(); void needsScroll() { mScrollNeeded = TRUE; } + struct URLLabelCallback; // Replace a URL with a new icon and label, for example, when // avatar names are looked up. void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 60566f457d..13fd20faf8 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -49,7 +49,6 @@ std::string localize_slapp_label(const std::string& url, const std::string& full LLUrlEntryBase::LLUrlEntryBase() -: mDisabledLink(false) {} LLUrlEntryBase::~LLUrlEntryBase() @@ -493,9 +492,7 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) // x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) // LLUrlEntryAgentName::LLUrlEntryAgentName() -{ - mDisabledLink = true; -} +{} void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) @@ -544,7 +541,7 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab LLStyle::Params LLUrlEntryAgentName::getStyle() const { // don't override default colors - return LLStyle::Params(); + return LLStyle::Params().is_link(false); } // @@ -949,7 +946,6 @@ LLUrlEntryNoLink::LLUrlEntryNoLink() { mPattern = boost::regex("[^<]*", boost::regex::perl|boost::regex::icase); - mDisabledLink = true; } std::string LLUrlEntryNoLink::getUrl(const std::string &url) const @@ -976,7 +972,6 @@ LLUrlEntryIcon::LLUrlEntryIcon() { mPattern = boost::regex("\\s*([^<]*)?\\s*", boost::regex::perl|boost::regex::icase); - mDisabledLink = true; } std::string LLUrlEntryIcon::getUrl(const std::string &url) const diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index ca4562cee0..5be3c6a45b 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -95,9 +95,6 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - protected: std::string getIDStringFromUrl(const std::string &url) const; std::string escapeUrl(const std::string &url) const; @@ -117,7 +114,6 @@ protected: std::string mMenuName; std::string mTooltip; std::multimap mObservers; - bool mDisabledLink; }; /// diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index dcfdd70b42..51fca6d7c0 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -42,16 +42,14 @@ LLUrlMatch::LLUrlMatch() : mTooltip(""), mIcon(""), mMenuName(""), - mLocation(""), - mDisabledLink(false) + mLocation("") { } void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, - const std::string &menu, const std::string &location, - bool disabled_link) + const std::string &menu, const std::string &location) { mStart = start; mEnd = end; @@ -63,5 +61,4 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mStyle.link_href = url; mMenuName = menu; mLocation = location; - mDisabledLink = disabled_link; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 293935e251..3b15a156a8 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -83,14 +83,11 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } - /// is this a match for a URL that should not be hyperlinked? - bool isLinkDisabled() const { return mDisabledLink; } - /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, const LLStyle::Params& style, const std::string &menu, - const std::string &location, bool disabled_link); + const std::string &location); private: U32 mStart; @@ -102,7 +99,6 @@ private: std::string mMenuName; std::string mLocation; LLStyle::Params mStyle; - bool mDisabledLink; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index f61603545f..f119233f8f 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -192,8 +192,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getIcon(url), match_entry->getStyle(), match_entry->getMenuName(), - match_entry->getLocation(url), - match_entry->isLinkDisabled()); + match_entry->getLocation(url)); return true; } @@ -226,8 +225,7 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.getIcon(), match.getStyle(), match.getMenuName(), - match.getLocation(), - match.isLinkDisabled()); + match.getLocation()); return true; } return false; diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index 10c44a9073..d6ef5132c8 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -176,7 +176,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", false); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", ""); ensure("! empty()", ! match.empty()); } @@ -189,7 +189,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -202,7 +202,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -215,10 +215,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -231,10 +231,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", ""); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -247,10 +247,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", ""); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -263,10 +263,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", ""); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -279,10 +279,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", ""); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -295,10 +295,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", false); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris"); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", false); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", ""); ensure("getLocation() empty (2)", match.getLocation().empty()); } } -- cgit v1.2.3 From 0f86f811c269b7c45baae2ac9bc72df70ba6b520 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Wed, 26 May 2010 14:47:48 -0700 Subject: DEV-50271 FIX SLURL support for non-clickable display names std::multimap::find does *not* return an iterator just to the matching results, use std::multimap::equal_range instead --- indra/llui/llurlentry.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 13fd20faf8..472c15a240 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -160,8 +160,9 @@ void LLUrlEntryBase::callObservers(const std::string &id, const std::string &icon) { // notify all callbacks waiting on the given uuid - std::multimap::iterator it; - for (it = mObservers.find(id); it != mObservers.end();) + typedef std::multimap::iterator observer_it; + std::pair matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) { // call the callback - give it the new label LLUrlEntryObserver &observer = it->second; @@ -342,8 +343,9 @@ void LLUrlEntryAgent::callObservers(const std::string &id, const std::string &icon) { // notify all callbacks waiting on the given uuid - std::multimap::iterator it; - for (it = mObservers.find(id); it != mObservers.end();) + typedef std::multimap::iterator observer_it; + std::pair matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) { // call the callback - give it the new label LLUrlEntryObserver &observer = it->second; -- cgit v1.2.3 From fbf72790169e979908ec206e6cb705dcc41ad97f Mon Sep 17 00:00:00 2001 From: Richard Linden Date: Thu, 3 Jun 2010 14:37:02 -0700 Subject: merge fixes --- indra/llui/lltextbase.cpp | 4 ++-- indra/llui/llurlmatch.cpp | 4 ++-- indra/llui/llurlmatch.h | 2 +- indra/llui/tests/llurlmatch_test.cpp | 30 +++++++++++++++--------------- 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 38b35020b5..cfa341ea23 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1597,7 +1597,7 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name) } } -void LLTextBase::appendTextImpl(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) +void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) { LLStyle::Params style_params(input_params); style_params.fillFrom(getDefaultStyleParams()); @@ -1647,7 +1647,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, bool prepend_newlin // Text will be replaced during rendering with the icon, // but string cannot be empty or the segment won't be // added (or drawn). - appendImageSegment(part, icon); + appendImageSegment(part, icon_params); } } diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 73d880522b..55ef07da36 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -48,9 +48,9 @@ LLUrlMatch::LLUrlMatch() : void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, - const std::string &icon, const LLUIColor& color, + const std::string &icon, const LLStyle::Params& style, const std::string &menu, const std::string &location, - bool disabled_link) + const LLUUID& id) { mStart = start; mEnd = end; diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 458a27d01f..1ffac6ea0f 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -86,7 +86,7 @@ public: /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, const std::string &tooltip, const std::string &icon, - const LLUIColor& color, const std::string &menu, + const LLStyle::Params& style, const std::string &menu, const std::string &location, const LLUUID& id); const LLUUID& getID() const { return mID; } diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index e8e1ea118b..93fd11d999 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -176,7 +176,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("! empty()", ! match.empty()); } @@ -189,7 +189,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -202,7 +202,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -215,10 +215,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -231,10 +231,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "Label", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -247,10 +247,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "Info", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -263,10 +263,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -279,10 +279,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "", LLUUID::null); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -295,10 +295,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris", LLUUID::null); + match.setValues(10, 20, "", "", "", "Icon", LLStyle::Params(), "xui_file.xml", "Paris", LLUUID::null); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", LLUUID::null); + match.setValues(10, 20, "", "", "", "", LLStyle::Params(), "", "", LLUUID::null); ensure("getLocation() empty (2)", match.getLocation().empty()); } } -- cgit v1.2.3 From 86ba458ebcc979bdd03b490842311bd5621fb0b4 Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Mon, 21 Jun 2010 15:04:12 -0700 Subject: merge fix --- indra/llui/lltextbase.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 4d835b869f..66c5af85c8 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1649,15 +1649,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para } // output the styled Url - // output the styled Url (unless we've been asked to suppress hyperlinking) - if (match.isLinkDisabled()) - { - appendAndHighlightText(match.getLabel(), part, style_params); - } - else - { - appendAndHighlightText(match.getLabel(), part, link_params); - + appendAndHighlightTextImpl(match.getLabel(), part, link_params); + // set the tooltip for the Url label if (! match.getTooltip().empty()) { -- cgit v1.2.3 From 5546f78fff25e59b57eb0a62d9ecacdd5c904b3d Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Tue, 6 Jul 2010 17:55:12 -0700 Subject: fixing windows line endings --- indra/llui/lltextbase.cpp | 5678 ++++++++++++++++++++++----------------------- 1 file changed, 2839 insertions(+), 2839 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index c72cdba263..c62d699c7e 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1,2839 +1,2839 @@ -/** - * @file lltextbase.cpp - * @author Martin Reddy - * @brief The base class of text box/editor, providing Url handling support - * - * $LicenseInfo:firstyear=2009&license=viewergpl$ - * - * Copyright (c) 2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include "lltextbase.h" - -#include "lllocalcliprect.h" -#include "llmenugl.h" -#include "llscrollcontainer.h" -#include "llstl.h" -#include "lltextparser.h" -#include "lltextutil.h" -#include "lltooltip.h" -#include "lluictrl.h" -#include "llurlaction.h" -#include "llurlregistry.h" -#include "llview.h" -#include "llwindow.h" -#include - -const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds -const S32 CURSOR_THICKNESS = 2; - -LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) -: mDocIndexStart(index_start), - mDocIndexEnd(index_end), - mRect(rect), - mLineNum(line_num) -{} - -bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const -{ - // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11) - if (a->getEnd() == b->getEnd()) - { - return a->getStart() < b->getStart(); - } - return a->getEnd() < b->getEnd(); -} - - -// helper functors -struct LLTextBase::compare_bottom -{ - bool operator()(const S32& a, const LLTextBase::line_info& b) const - { - return a > b.mRect.mBottom; // bottom of a is higher than bottom of b - } - - bool operator()(const LLTextBase::line_info& a, const S32& b) const - { - return a.mRect.mBottom > b; // bottom of a is higher than bottom of b - } - - bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const - { - return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b - } - -}; - -// helper functors -struct LLTextBase::compare_top -{ - bool operator()(const S32& a, const LLTextBase::line_info& b) const - { - return a > b.mRect.mTop; // top of a is higher than top of b - } - - bool operator()(const LLTextBase::line_info& a, const S32& b) const - { - return a.mRect.mTop > b; // top of a is higher than top of b - } - - bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const - { - return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b - } -}; - -struct LLTextBase::line_end_compare -{ - bool operator()(const S32& pos, const LLTextBase::line_info& info) const - { - return (pos < info.mDocIndexEnd); - } - - bool operator()(const LLTextBase::line_info& info, const S32& pos) const - { - return (info.mDocIndexEnd < pos); - } - - bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const - { - return (a.mDocIndexEnd < b.mDocIndexEnd); - } - -}; - -////////////////////////////////////////////////////////////////////////// -// -// LLTextBase -// - -// register LLTextBase::Params under name "textbase" -static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase"); - -LLTextBase::LineSpacingParams::LineSpacingParams() -: multiple("multiple", 1.f), - pixels("pixels", 0) -{ -} - - -LLTextBase::Params::Params() -: cursor_color("cursor_color"), - text_color("text_color"), - text_readonly_color("text_readonly_color"), - bg_visible("bg_visible", false), - border_visible("border_visible", false), - bg_readonly_color("bg_readonly_color"), - bg_writeable_color("bg_writeable_color"), - bg_focus_color("bg_focus_color"), - allow_scroll("allow_scroll", true), - plain_text("plain_text",false), - track_end("track_end", false), - read_only("read_only", false), - v_pad("v_pad", 0), - h_pad("h_pad", 0), - clip_partial("clip_partial", true), - line_spacing("line_spacing"), - max_text_length("max_length", 255), - font_shadow("font_shadow"), - wrap("wrap"), - use_ellipses("use_ellipses", false), - allow_html("allow_html", false), - parse_highlights("parse_highlights", false) -{ - addSynonym(track_end, "track_bottom"); - addSynonym(wrap, "word_wrap"); -} - - -LLTextBase::LLTextBase(const LLTextBase::Params &p) -: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), - mURLClickSignal(), - mMaxTextByteLength( p.max_text_length ), - mDefaultFont(p.font), - mFontShadow(p.font_shadow), - mPopupMenu(NULL), - mReadOnly(p.read_only), - mCursorColor(p.cursor_color), - mFgColor(p.text_color), - mBorderVisible( p.border_visible ), - mReadOnlyFgColor(p.text_readonly_color), - mWriteableBgColor(p.bg_writeable_color), - mReadOnlyBgColor(p.bg_readonly_color), - mFocusBgColor(p.bg_focus_color), - mReflowIndex(S32_MAX), - mCursorPos( 0 ), - mScrollNeeded(FALSE), - mDesiredXPixel(-1), - mHPad(p.h_pad), - mVPad(p.v_pad), - mHAlign(p.font_halign), - mVAlign(p.font_valign), - mLineSpacingMult(p.line_spacing.multiple), - mLineSpacingPixels(p.line_spacing.pixels), - mClipPartial(p.clip_partial && !p.allow_scroll), - mTrackEnd( p.track_end ), - mScrollIndex(-1), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mIsSelecting( FALSE ), - mPlainText ( p.plain_text ), - mWordWrap(p.wrap), - mUseEllipses( p.use_ellipses ), - mParseHTML(p.allow_html), - mParseHighlights(p.parse_highlights), - mBGVisible(p.bg_visible), - mScroller(NULL) -{ - if(p.allow_scroll) - { - LLScrollContainer::Params scroll_params; - scroll_params.name = "text scroller"; - scroll_params.rect = getLocalRect(); - scroll_params.follows.flags = FOLLOWS_ALL; - scroll_params.is_opaque = false; - scroll_params.mouse_opaque = false; - scroll_params.min_auto_scroll_rate = 200; - scroll_params.max_auto_scroll_rate = 800; - scroll_params.border_visible = p.border_visible; - mScroller = LLUICtrlFactory::create(scroll_params); - addChild(mScroller); - } - - LLView::Params view_params; - view_params.name = "text_contents"; - view_params.rect = LLRect(0, 500, 500, 0); - view_params.mouse_opaque = false; - - mDocumentView = LLUICtrlFactory::create(view_params); - if (mScroller) - { - mScroller->addChild(mDocumentView); - } - else - { - addChild(mDocumentView); - } - - createDefaultSegment(); - - updateRects(); -} - -LLTextBase::~LLTextBase() -{ - // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder - - mSegments.clear(); -} - -void LLTextBase::initFromParams(const LLTextBase::Params& p) -{ - LLUICtrl::initFromParams(p); - resetDirty(); // Update saved text state - updateSegments(); - - // HACK: work around enabled == readonly design bug -- RN - // setEnabled will modify our read only status, so do this after - // LLTextBase::initFromParams - if (p.read_only.isProvided()) - { - mReadOnly = p.read_only; - } - - // HACK: text editors always need to be enabled so that we can scroll - LLView::setEnabled(true); -} - -bool LLTextBase::truncate() -{ - BOOL did_truncate = FALSE; - - // First rough check - if we're less than 1/4th the size, we're OK - if (getLength() >= S32(mMaxTextByteLength / 4)) - { - // Have to check actual byte size - LLWString text(getWText()); - S32 utf8_byte_size = wstring_utf8_length(text); - if ( utf8_byte_size > mMaxTextByteLength ) - { - // Truncate safely in UTF-8 - std::string temp_utf8_text = wstring_to_utf8str(text); - 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. - removeStringNoUndo(text.size(), getWText().size() - text.size()); - did_truncate = TRUE; - } - } - - return did_truncate; -} - -LLStyle::Params LLTextBase::getDefaultStyleParams() -{ - return LLStyle::Params() - .color(LLUIColor(&mFgColor)) - .readonly_color(LLUIColor(&mReadOnlyFgColor)) - .font(mDefaultFont) - .drop_shadow(mFontShadow); -} - -void LLTextBase::onValueChange(S32 start, S32 end) -{ -} - - -// Draws the black box behind the selected text -void LLTextBase::drawSelectionBackground() -{ - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection() && !mLineInfoList.empty()) - { - std::vector selection_rects; - - S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); - LLRect selection_rect = mVisibleTextRect; - - // Skip through the lines we aren't drawing. - LLRect content_display_rect = getVisibleDocumentRect(); - - // binary search for line that starts before top of visible buffer - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); - line_list_t::const_iterator end_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); - - bool done = false; - - // Find the coordinates of the selected area - for (;line_iter != end_iter && !done; ++line_iter) - { - // is selection visible on this line? - if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right) - { - segment_set_t::iterator segment_iter; - S32 segment_offset; - getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset); - - LLRect selection_rect; - selection_rect.mLeft = line_iter->mRect.mLeft; - selection_rect.mRight = line_iter->mRect.mLeft; - selection_rect.mBottom = line_iter->mRect.mBottom; - selection_rect.mTop = line_iter->mRect.mTop; - - for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0) - { - LLTextSegmentPtr segmentp = *segment_iter; - - S32 segment_line_start = segmentp->getStart() + segment_offset; - S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); - - if (segment_line_start > segment_line_end) break; - - S32 segment_width = 0; - S32 segment_height = 0; - - // if selection after beginning of segment - if(selection_left >= segment_line_start) - { - S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start; - segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); - selection_rect.mLeft += segment_width; - } - - // if selection_right == segment_line_end then that means we are the first character of the next segment - // or first character of the next line, in either case we want to add the length of the current segment - // to the selection rectangle and continue. - // if selection right > segment_line_end then selection spans end of current segment... - if (selection_right >= segment_line_end) - { - // extend selection slightly beyond end of line - // to indicate selection of newline character (use "n" character to determine width) - S32 num_chars = segment_line_end - segment_line_start; - segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); - selection_rect.mRight += segment_width; - } - // else if selection ends on current segment... - else - { - S32 num_chars = selection_right - segment_line_start; - segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); - selection_rect.mRight += segment_width; - - break; - } - } - selection_rects.push_back(selection_rect); - } - } - - // Draw the selection box (we're using a box instead of reversing the colors on the selected text). - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get(); - F32 alpha = hasFocus() ? 0.7f : 0.3f; - alpha *= getDrawContext().mAlpha; - LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); - - for (std::vector::iterator rect_it = selection_rects.begin(); - rect_it != selection_rects.end(); - ++rect_it) - { - LLRect selection_rect = *rect_it; - selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); - gl_rect_2d(selection_rect, selection_color); - } - } -} - -void LLTextBase::drawCursor() -{ - F32 alpha = getDrawContext().mAlpha; - - if( hasFocus() - && gFocusMgr.getAppHasFocus() - && !mReadOnly) - { - const LLWString &wtext = getWText(); - const llwchar* text = wtext.c_str(); - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - cursor_rect.translate(-1, 0); - segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos); - - // take style from last segment - LLTextSegmentPtr segmentp; - - if (seg_it != mSegments.end()) - { - segmentp = *seg_it; - } - else - { - //segmentp = mSegments.back(); - return; - } - - // Draw the cursor - // (Flash the cursor every half second starting a fixed time after the last keystroke) - F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32(); - if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) - { - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - S32 segment_width = 0; - S32 segment_height = 0; - segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); - S32 width = llmax(CURSOR_THICKNESS, segment_width); - cursor_rect.mRight = cursor_rect.mLeft + width; - } - else - { - cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS; - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLColor4 cursor_color = mCursorColor.get() % alpha; - gGL.color4fv( cursor_color.mV ); - - gl_rect_2d(cursor_rect); - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') - { - LLColor4 text_color; - const LLFontGL* fontp; - if (segmentp) - { - text_color = segmentp->getColor(); - fontp = segmentp->getStyle()->getFont(); - } - else if (mReadOnly) - { - text_color = mReadOnlyFgColor.get(); - fontp = mDefaultFont; - } - else - { - text_color = mFgColor.get(); - fontp = mDefaultFont; - } - fontp->render(text, mCursorPos, cursor_rect, - LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), - LLFontGL::LEFT, mVAlign, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - 1); - } - - // Make sure the IME is in the right place - LLRect screen_pos = calcScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) ); - - ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]); - ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); - getWindow()->setLanguageTextInput( ime_pos ); - } - } -} - -void LLTextBase::drawText() -{ - const S32 text_len = getLength(); - if( text_len <= 0 ) - { - return; - } - S32 selection_left = -1; - S32 selection_right = -1; - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection()) - { - selection_left = llmin( mSelectionStart, mSelectionEnd ); - selection_right = llmax( mSelectionStart, mSelectionEnd ); - } - - LLRect scrolled_view_rect = getVisibleDocumentRect(); - std::pair line_range = getVisibleLines(mClipPartial); - S32 first_line = line_range.first; - S32 last_line = line_range.second; - if (first_line >= last_line) - { - return; - } - - S32 line_start = getLineStart(first_line); - // find first text segment that spans top of visible portion of text buffer - segment_set_t::iterator seg_iter = getSegIterContaining(line_start); - if (seg_iter == mSegments.end()) - { - return; - } - - LLTextSegmentPtr cur_segment = *seg_iter; - - for (S32 cur_line = first_line; cur_line < last_line; cur_line++) - { - S32 next_line = cur_line + 1; - line_info& line = mLineInfoList[cur_line]; - - S32 next_start = -1; - S32 line_end = text_len; - - if (next_line < getLineCount()) - { - next_start = getLineStart(next_line); - line_end = next_start; - } - - LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, - line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom, - llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft, - line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom); - - // draw a single line of text - S32 seg_start = line_start; - while( seg_start < line_end ) - { - while( cur_segment->getEnd() <= seg_start ) - { - seg_iter++; - if (seg_iter == mSegments.end()) - { - llwarns << "Ran off the segmentation end!" << llendl; - - return; - } - cur_segment = *seg_iter; - } - - S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - cur_segment->getStart(); - - if (mUseEllipses // using ellipses - && clipped_end == line_end // last segment on line - && next_line == last_line // this is the last visible line - && last_line < (S32)mLineInfoList.size()) // and there is more text to display - { - // more lines of text to go, but we can't fit them - // so shrink text rect to force ellipses - text_rect.mRight -= 2; - } - - 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(); - } - - line_start = next_start; - } -} - -/////////////////////////////////////////////////////////////////// -// Returns change in number of characters in mWText - -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 insert_len = wstr.length(); - - pos = getEditableIndex(pos, true); - - segment_set_t::iterator seg_iter = getSegIterContaining(pos); - - LLTextSegmentPtr default_segment; - - LLTextSegmentPtr segmentp; - if (seg_iter != mSegments.end()) - { - segmentp = *seg_iter; - } - else - { - //segmentp = mSegments.back(); - return pos; - } - - if (segmentp->canEdit()) - { - segmentp->setEnd(segmentp->getEnd() + insert_len); - if (seg_iter != mSegments.end()) - { - ++seg_iter; - } - } - else - { - // create default editable segment to hold new text - LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); - default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); - } - - // shift remaining segments to right - for(;seg_iter != mSegments.end(); ++seg_iter) - { - LLTextSegmentPtr segmentp = *seg_iter; - segmentp->setStart(segmentp->getStart() + insert_len); - segmentp->setEnd(segmentp->getEnd() + insert_len); - } - - // insert new segments - if (segments) - { - if (default_segment.notNull()) - { - // potentially overwritten by segments passed in - insertSegment(default_segment); - } - for (segment_vec_t::iterator seg_iter = segments->begin(); - seg_iter != segments->end(); - ++seg_iter) - { - LLTextSegment* segmentp = *seg_iter; - insertSegment(segmentp); - } - } - - text.insert(pos, wstr); - getViewModel()->setDisplay(text); - - if ( truncate() ) - { - insert_len = getLength() - old_len; - } - - onValueChange(pos, pos + insert_len); - needsReflow(pos); - - return insert_len; -} - -S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) -{ - LLWString text(getWText()); - segment_set_t::iterator seg_iter = getSegIterContaining(pos); - while(seg_iter != mSegments.end()) - { - LLTextSegmentPtr segmentp = *seg_iter; - S32 end = pos + length; - if (segmentp->getStart() < pos) - { - // deleting from middle of segment - if (segmentp->getEnd() > end) - { - segmentp->setEnd(segmentp->getEnd() - length); - } - // truncating segment - else - { - segmentp->setEnd(pos); - } - } - else if (segmentp->getStart() < end) - { - // deleting entire segment - if (segmentp->getEnd() <= end) - { - // remove segment - segmentp->unlinkFromDocument(this); - segment_set_t::iterator seg_to_erase(seg_iter++); - mSegments.erase(seg_to_erase); - continue; - } - // deleting head of segment - else - { - segmentp->setStart(pos); - segmentp->setEnd(segmentp->getEnd() - length); - } - } - else - { - // shifting segments backward to fill deleted portion - segmentp->setStart(segmentp->getStart() - length); - segmentp->setEnd(segmentp->getEnd() - length); - } - ++seg_iter; - } - - text.erase(pos, length); - getViewModel()->setDisplay(text); - - // recreate default segment in case we erased everything - createDefaultSegment(); - - onValueChange(pos, pos); - needsReflow(pos); - - return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length -} - -S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) -{ - if (pos > (S32)getLength()) - { - return 0; - } - LLWString text(getWText()); - text[pos] = wc; - getViewModel()->setDisplay(text); - - onValueChange(pos, pos + 1); - needsReflow(pos); - - return 1; -} - - -void LLTextBase::createDefaultSegment() -{ - // ensures that there is always at least one segment - if (mSegments.empty()) - { - LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); - LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); - mSegments.insert(default_segment); - default_segment->linkToDocument(this); - } -} - -void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) -{ - if (segment_to_insert.isNull()) - { - return; - } - - segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); - S32 reflow_start_index = 0; - - if (cur_seg_iter == mSegments.end()) - { - mSegments.insert(segment_to_insert); - segment_to_insert->linkToDocument(this); - reflow_start_index = segment_to_insert->getStart(); - } - else - { - LLTextSegmentPtr cur_segmentp = *cur_seg_iter; - reflow_start_index = cur_segmentp->getStart(); - if (cur_segmentp->getStart() < segment_to_insert->getStart()) - { - S32 old_segment_end = cur_segmentp->getEnd(); - // split old at start point for new segment - cur_segmentp->setEnd(segment_to_insert->getStart()); - // advance to next segment - // insert remainder of old segment - LLStyleConstSP sp = cur_segmentp->getStyle(); - LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); - mSegments.insert(cur_seg_iter, remainder_segment); - remainder_segment->linkToDocument(this); - // insert new segment before remainder of old segment - mSegments.insert(cur_seg_iter, segment_to_insert); - - segment_to_insert->linkToDocument(this); - // at this point, there will be two overlapping segments owning the text - // associated with the incoming segment - } - else - { - mSegments.insert(cur_seg_iter, segment_to_insert); - segment_to_insert->linkToDocument(this); - } - - // now delete/truncate remaining segments as necessary - // cur_seg_iter points to segment before incoming segment - while(cur_seg_iter != mSegments.end()) - { - cur_segmentp = *cur_seg_iter; - if (cur_segmentp == segment_to_insert) - { - ++cur_seg_iter; - continue; - } - - if (cur_segmentp->getStart() >= segment_to_insert->getStart()) - { - if(cur_segmentp->getEnd() <= segment_to_insert->getEnd()) - { - cur_segmentp->unlinkFromDocument(this); - // grab copy of iterator to erase, and bump it - segment_set_t::iterator seg_to_erase(cur_seg_iter++); - mSegments.erase(seg_to_erase); - continue; - } - else - { - // last overlapping segment, clip to end of incoming segment - // and stop traversal - cur_segmentp->setStart(segment_to_insert->getEnd()); - break; - } - } - ++cur_seg_iter; - } - } - - // layout potentially changed - needsReflow(reflow_start_index); -} - -BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleMouseDown(x, y, mask); -} - -BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMouseUp(x, y, mask)) - { - // Did we just click on a link? - if (cur_segment->getStyle() - && cur_segment->getStyle()->isLink()) - { - // *TODO: send URL here? - mURLClickSignal(this, LLSD() ); - } - return TRUE; - } - - return LLUICtrl::handleMouseUp(x, y, mask); -} - -BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleMiddleMouseDown(x, y, mask); -} - -BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleMiddleMouseUp(x, y, mask); -} - -BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleRightMouseDown(x, y, mask); -} - -BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleRightMouseUp(x, y, mask); -} - -BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleDoubleClick(x, y, mask); -} - -BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleHover(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleHover(x, y, mask); -} - -BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks)) - { - return TRUE; - } - - return LLUICtrl::handleScrollWheel(x, y, clicks); -} - -BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleToolTip(x, y, mask)) - { - return TRUE; - } - - return LLUICtrl::handleToolTip(x, y, mask); -} - - -void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - if (width != getRect().getWidth() || height != getRect().getHeight()) - { - bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; - - LLUICtrl::reshape( width, height, called_from_parent ); - - if (mScroller && scrolled_to_bottom && mTrackEnd) - { - // keep bottom of text buffer visible - // do this here as well as in reflow to handle case - // where shrinking from top, which causes buffer to temporarily - // not be scrolled to the bottom, since the scroll index - // specified the _top_ of the visible document region - mScroller->goToBottom(); - } - - // do this first after reshape, because other things depend on - // up-to-date mVisibleTextRect - updateRects(); - - needsReflow(); - } -} - -void LLTextBase::draw() -{ - // reflow if needed, on demand - reflow(); - - // then update scroll position, as cursor may have moved - if (!mReadOnly) - { - updateScrollFromCursor(); - } - - LLRect doc_rect; - if (mScroller) - { - mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &doc_rect, this); - } - else - { - doc_rect = getLocalRect(); - } - - if (mBGVisible) - { - // clip background rect against extents, if we support scrolling - LLLocalClipRect clip(doc_rect, mScroller != NULL); - - LLColor4 bg_color = mReadOnly - ? mReadOnlyBgColor.get() - : hasFocus() - ? mFocusBgColor.get() - : mWriteableBgColor.get(); - gl_rect_2d(mVisibleTextRect, bg_color, TRUE); - } - - // draw document view - LLUICtrl::draw(); - - { - // only clip if we support scrolling (mScroller != NULL) - LLLocalClipRect clip(doc_rect, mScroller != NULL); - drawSelectionBackground(); - drawText(); - drawCursor(); - } -} - - -//virtual -void LLTextBase::setColor( const LLColor4& c ) -{ - mFgColor = c; -} - -//virtual -void LLTextBase::setReadOnlyColor(const LLColor4 &c) -{ - mReadOnlyFgColor = c; -} - -//virtual -void LLTextBase::handleVisibilityChange( BOOL new_visibility ) -{ - if(!new_visibility && mPopupMenu) - { - mPopupMenu->hide(); - } - LLUICtrl::handleVisibilityChange(new_visibility); -} - -//virtual -void LLTextBase::setValue(const LLSD& value ) -{ - setText(value.asString()); -} - -//virtual -BOOL LLTextBase::canDeselect() const -{ - return hasSelection(); -} - - -//virtual -void LLTextBase::deselect() -{ - mSelectionStart = 0; - mSelectionEnd = 0; - mIsSelecting = FALSE; -} - - -// Sets the scrollbar from the cursor position -void LLTextBase::updateScrollFromCursor() -{ - // Update scroll position even in read-only mode (when there's no cursor displayed) - // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736. - - if (!mScrollNeeded || !mScroller) - { - return; - } - mScrollNeeded = FALSE; - - // scroll so that the cursor is at the top of the page - LLRect scroller_doc_window = getVisibleDocumentRect(); - LLRect cursor_rect_doc = getLocalRectFromDocIndex(mCursorPos); - cursor_rect_doc.translate(scroller_doc_window.mLeft, scroller_doc_window.mBottom); - mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5)); -} - -S32 LLTextBase::getLeftOffset(S32 width) -{ - switch (mHAlign) - { - case LLFontGL::LEFT: - return mHPad; - case LLFontGL::HCENTER: - return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2); - case LLFontGL::RIGHT: - return mVisibleTextRect.getWidth() - width; - default: - return mHPad; - } -} - - -static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow"); -void LLTextBase::reflow() -{ - LLFastTimer ft(FTM_TEXT_REFLOW); - - updateSegments(); - - if (mReflowIndex == S32_MAX) - { - return; - } - - bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible - - // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing - cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; - cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; - - S32 first_line = getFirstVisibleLine(); - - // if scroll anchor not on first line, update it to first character of first line - if (!mLineInfoList.empty() - && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart - || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) - { - mScrollIndex = mLineInfoList[first_line].mDocIndexStart; - } - LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); - // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing - first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; - first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; - - S32 reflow_count = 0; - while(mReflowIndex < S32_MAX) - { - // we can get into an infinite loop if the document height does not monotonically increase - // with decreasing width (embedded ui elements with alternate layouts). In that case, - // we want to stop reflowing after 2 iterations. We use 2, since we need to handle the case - // of introducing a vertical scrollbar causing a reflow with less width. We should also always - // use an even number of iterations to avoid user visible oscillation of the layout - if(++reflow_count > 2) - { - lldebugs << "Breaking out of reflow due to possible infinite loop in " << getName() << llendl; - break; - } - - S32 start_index = mReflowIndex; - mReflowIndex = S32_MAX; - - // shrink document to minimum size (visible portion of text widget) - // to force inlined widgets with follows set to shrink - mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); - - S32 cur_top = 0; - - segment_set_t::iterator seg_iter = mSegments.begin(); - S32 seg_offset = 0; - S32 line_start_index = 0; - const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin - S32 remaining_pixels = text_available_width; - S32 line_count = 0; - - // find and erase line info structs starting at start_index and going to end of document - if (!mLineInfoList.empty()) - { - // find first element whose end comes after start_index - line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare()); - line_start_index = iter->mDocIndexStart; - line_count = iter->mLineNum; - cur_top = iter->mRect.mTop; - getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); - mLineInfoList.erase(iter, mLineInfoList.end()); - } - - S32 line_height = 0; - - while(seg_iter != mSegments.end()) - { - LLTextSegmentPtr segment = *seg_iter; - - // track maximum height of any segment on this line - S32 cur_index = segment->getStart() + seg_offset; - - // ask segment how many character fit in remaining space - S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX, - seg_offset, - cur_index - line_start_index, - S32_MAX); - - S32 segment_width, segment_height; - bool force_newline = segment->getDimensions(seg_offset, character_count, segment_width, segment_height); - // grow line height as necessary based on reported height of this segment - line_height = llmax(line_height, segment_height); - remaining_pixels -= segment_width; - - seg_offset += character_count; - - S32 last_segment_char_on_line = segment->getStart() + seg_offset; - - S32 text_actual_width = text_available_width - remaining_pixels; - S32 text_left = getLeftOffset(text_actual_width); - LLRect line_rect(text_left, - cur_top, - text_left + text_actual_width, - cur_top - line_height); - - // if we didn't finish the current segment... - if (last_segment_char_on_line < segment->getEnd()) - { - // add line info and keep going - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - - line_start_index = segment->getStart() + seg_offset; - cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - remaining_pixels = text_available_width; - line_height = 0; - } - // ...just consumed last segment.. - else if (++segment_set_t::iterator(seg_iter) == mSegments.end()) - { - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - break; - } - // ...or finished a segment and there are segments remaining on this line - else - { - // subtract pixels used and increment segment - if (force_newline) - { - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - line_start_index = segment->getStart() + seg_offset; - cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - line_height = 0; - remaining_pixels = text_available_width; - } - ++seg_iter; - seg_offset = 0; - } - if (force_newline) - { - line_count++; - } - } - - // calculate visible region for diplaying text - updateRects(); - - for (segment_set_t::iterator segment_it = mSegments.begin(); - segment_it != mSegments.end(); - ++segment_it) - { - LLTextSegmentPtr segmentp = *segment_it; - segmentp->updateLayout(*this); - - } - } - - // apply scroll constraints after reflowing text - if (!hasMouseCapture() && mScroller) - { - if (scrolled_to_bottom && mTrackEnd) - { - // keep bottom of text buffer visible - endOfDoc(); - } - else if (hasSelection() && follow_selection) - { - // keep cursor in same vertical position on screen when selecting text - LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); - LLRect old_cursor_rect = cursor_rect; - old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; - old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; - - mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); - } - else - { - // keep first line of text visible - LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); - - // pass in desired rect in the coordinate frame of the document viewport - LLRect old_first_char_rect = first_char_rect; - old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; - old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; - - mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect); - } - } - - // reset desired x cursor position - updateCursorXPos(); - } - -LLRect LLTextBase::getTextBoundingRect() -{ - reflow(); - return mTextBoundingRect; -} - - -void LLTextBase::clearSegments() -{ - mSegments.clear(); - createDefaultSegment(); -} - -S32 LLTextBase::getLineStart( S32 line ) const -{ - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - return mLineInfoList[line].mDocIndexStart; -} - -S32 LLTextBase::getLineEnd( S32 line ) const -{ - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - return mLineInfoList[line].mDocIndexEnd; -} - - - -S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const -{ - if (mLineInfoList.empty()) - { - return 0; - } - else - { - line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare()); - if (include_wordwrap) - { - return iter - mLineInfoList.begin(); - } - else - { - if (iter == mLineInfoList.end()) - { - return mLineInfoList.back().mLineNum; - } - else - { - return iter->mLineNum; - } - } - } -} - -// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. -S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const -{ - if (mLineInfoList.empty()) - { - return startpos; - } - else - { - line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare()); - return startpos - iter->mDocIndexStart; - } -} - -S32 LLTextBase::getFirstVisibleLine() const -{ - LLRect visible_region = getVisibleDocumentRect(); - - // binary search for line that starts before top of visible buffer - line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); - - return iter - mLineInfoList.begin(); -} - -std::pair LLTextBase::getVisibleLines(bool fully_visible) -{ - LLRect visible_region = getVisibleDocumentRect(); - line_list_t::const_iterator first_iter; - line_list_t::const_iterator last_iter; - - // make sure we have an up-to-date mLineInfoList - reflow(); - - if (fully_visible) - { - first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); - last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); - } - else - { - first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); - last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top()); - } - return std::pair(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin()); -} - - - -LLTextViewModel* LLTextBase::getViewModel() const -{ - return (LLTextViewModel*)mViewModel.get(); -} - -void LLTextBase::addDocumentChild(LLView* view) -{ - mDocumentView->addChild(view); -} - -void LLTextBase::removeDocumentChild(LLView* view) -{ - mDocumentView->removeChild(view); -} - - -static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments"); -void LLTextBase::updateSegments() -{ - LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS); - createDefaultSegment(); -} - -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const -{ - *seg_iter = getSegIterContaining(startpos); - if (*seg_iter == mSegments.end()) - { - *offsetp = 0; - } - else - { - *offsetp = startpos - (**seg_iter)->getStart(); - } -} - -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ) -{ - *seg_iter = getSegIterContaining(startpos); - if (*seg_iter == mSegments.end()) - { - *offsetp = 0; - } - else - { - *offsetp = startpos - (**seg_iter)->getStart(); - } -} - -LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) -{ - segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index)); - return it; -} - -LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const -{ - LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(new LLIndexSegment(index)); - return it; -} - -// Finds the text segment (if any) at the give local screen position -LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) -{ - // Find the cursor position at the requested local screen position - S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line); - segment_set_t::iterator seg_iter = getSegIterContaining(offset); - if (seg_iter != mSegments.end()) - { - return *seg_iter; - } - else - { - return LLTextSegmentPtr(); - } -} - -void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) -{ - // work out the XUI menu file to use for this url - LLUrlMatch match; - std::string url = in_url; - if (! LLUrlRegistry::instance().findUrl(url, match)) - { - return; - } - - std::string xui_file = match.getMenuName(); - if (xui_file.empty()) - { - return; - } - - // set up the callbacks for all of the potential menu items, N.B. we - // don't use const ref strings in callbacks in case url goes out of scope - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url)); - registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url)); - registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); - registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url)); - registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); - registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); - registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); - registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); - registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); - - // create and return the context menu from the XUI file - delete mPopupMenu; - mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile(xui_file, LLMenuGL::sMenuContainer, - LLMenuHolderGL::child_registry_t::instance()); - if (mPopupMenu) - { - mPopupMenu->show(x, y); - LLMenuGL::showPopup(this, mPopupMenu, x, y); - } -} - -void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) -{ - // clear out the existing text and segments - getViewModel()->setDisplay(LLWStringUtil::null); - - clearSegments(); -// createDefaultSegment(); - - deselect(); - - // append the new text (supports Url linking) - std::string text(utf8str); - LLStringUtil::removeCRLF(text); - - // appendText modifies mCursorPos... - appendText(text, false, input_params); - // ...so move cursor to top after appending text - startOfDoc(); - - onValueChange(0, getLength()); -} - -//virtual -std::string LLTextBase::getText() const -{ - return getViewModel()->getValue().asString(); -} - -// IDEVO - icons can be UI image names or UUID sent from -// server with avatar display name -static LLUIImagePtr image_from_icon_name(const std::string& icon_name) -{ - if (LLUUID::validate(icon_name)) - { - return LLUI::getUIImageByID( LLUUID(icon_name) ); - } - else - { - return LLUI::getUIImage(icon_name); - } -} - -void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) -{ - LLStyle::Params style_params(input_params); - style_params.fillFrom(getDefaultStyleParams()); - - S32 part = (S32)LLTextParser::WHOLE; - if(mParseHTML) - { - 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; - - LLStyle::Params link_params(style_params); - link_params.overwriteFrom(match.getStyle()); - - // output the text before the Url - if (start > 0) - { - if (part == (S32)LLTextParser::WHOLE || - part == (S32)LLTextParser::START) - { - part = (S32)LLTextParser::START; - } - else - { - part = (S32)LLTextParser::MIDDLE; - } - std::string subtext=text.substr(0,start); - appendAndHighlightText(subtext, part, style_params); - } - - // output the styled Url - appendAndHighlightTextImpl(match.getLabel(), part, link_params); - - // set the tooltip for the Url label - if (! match.getTooltip().empty()) - { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) - { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); - } - } - - // move on to the rest of the text after the Url - if (end < (S32)text.length()) - { - text = text.substr(end,text.length() - end); - end=0; - part=(S32)LLTextParser::END; - } - else - { - break; - } - } - if (part != (S32)LLTextParser::WHOLE) - part=(S32)LLTextParser::END; - if (end < (S32)text.length()) - appendAndHighlightText(text, part, style_params); - } - else - { - appendAndHighlightText(new_text, part, style_params); - } -} - -void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) -{ - if (new_text.empty()) - return; - - if(prepend_newline) - appendLineBreakSegment(input_params); - appendTextImpl(new_text,input_params); -} - -void LLTextBase::needsReflow(S32 index) -{ - lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl; - mReflowIndex = llmin(mReflowIndex, index); -} - -void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) -{ - segment_vec_t segments; - LLStyleConstSP sp(new LLStyle(style_params)); - segments.push_back(new LLLineBreakTextSegment(sp, getLength())); - - insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments); -} - -void LLTextBase::appendImageSegment(const LLStyle::Params& style_params) -{ - if(getPlainText()) - { - return; - } - segment_vec_t segments; - LLStyleConstSP sp(new LLStyle(style_params)); - segments.push_back(new LLImageTextSegment(sp, getLength(),*this)); - - insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments); -} - -void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) -{ - segment_vec_t segments; - LLWString widget_wide_text = utf8str_to_wstring(text); - segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size())); - - insertStringNoUndo(getLength(), widget_wide_text, &segments); -} - -void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params) -{ - // Save old state - S32 selection_start = mSelectionStart; - S32 selection_end = mSelectionEnd; - BOOL was_selecting = mIsSelecting; - S32 cursor_pos = mCursorPos; - S32 old_length = getLength(); - BOOL cursor_was_at_end = (mCursorPos == old_length); - - deselect(); - - setCursorPos(old_length); - - if (mParseHighlights) - { - LLStyle::Params highlight_params(style_params); - - LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); - for (S32 i = 0; i < pieces.size(); i++) - { - LLSD color_llsd = pieces[i]["color"]; - LLColor4 lcolor; - lcolor.setValue(color_llsd); - highlight_params.color = lcolor; - - LLWString wide_text; - wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); - - S32 cur_length = getLength(); - LLStyleConstSP sp(new LLStyle(highlight_params)); - LLTextSegmentPtr segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); - segment_vec_t segments; - segments.push_back(segmentp); - insertStringNoUndo(cur_length, wide_text, &segments); - } - } - else - { - LLWString wide_text; - wide_text = utf8str_to_wstring(new_text); - - segment_vec_t segments; - S32 segment_start = old_length; - S32 segment_end = old_length + wide_text.size(); - LLStyleConstSP sp(new LLStyle(style_params)); - segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); - - insertStringNoUndo(getLength(), wide_text, &segments); - } - - // Set the cursor and scroll position - if( selection_start != selection_end ) - { - mSelectionStart = selection_start; - mSelectionEnd = selection_end; - - mIsSelecting = was_selecting; - setCursorPos(cursor_pos); - } - else if( cursor_was_at_end ) - { - setCursorPos(getLength()); - } - else - { - setCursorPos(cursor_pos); - } -} - -void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params) -{ - if (new_text.empty()) return; - - std::string::size_type start = 0; - std::string::size_type pos = new_text.find("\n",start); - - while(pos!=-1) - { - if(pos!=start) - { - std::string str = std::string(new_text,start,pos-start); - appendAndHighlightTextImpl(str,highlight_part, style_params); - } - appendLineBreakSegment(style_params); - start = pos+1; - pos = new_text.find("\n",start); - } - - std::string str = std::string(new_text,start,new_text.length()-start); - appendAndHighlightTextImpl(str,highlight_part, style_params); -} - - -void LLTextBase::replaceUrl(const std::string &url, - const std::string &label, - const std::string &icon) -{ - // get the full (wide) text for the editor so we can change it - LLWString text = getWText(); - LLWString wlabel = utf8str_to_wstring(label); - bool modified = false; - S32 seg_start = 0; - - // iterate through each segment looking for ones styled as links - segment_set_t::iterator it; - for (it = mSegments.begin(); it != mSegments.end(); ++it) - { - LLTextSegment *seg = *it; - LLStyleConstSP style = seg->getStyle(); - - // update segment start/end length in case we replaced text earlier - S32 seg_length = seg->getEnd() - seg->getStart(); - seg->setStart(seg_start); - seg->setEnd(seg_start + seg_length); - - // if we find a link with our Url, then replace the label - if (style->getLinkHREF() == url) - { - S32 start = seg->getStart(); - S32 end = seg->getEnd(); - text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1); - seg->setEnd(start + wlabel.size()); - modified = true; - } - - // Icon might be updated when more avatar or group info - // becomes available - if (style->isImage() && style->getLinkHREF() == url) - { - LLUIImagePtr image = image_from_icon_name( icon ); - if (image) - { - LLStyle::Params icon_params; - icon_params.image = image; - LLStyleConstSP new_style(new LLStyle(icon_params)); - seg->setStyle(new_style); - modified = true; - } - } - - // work out the character offset for the next segment - seg_start = seg->getEnd(); - } - - // update the editor with the new (wide) text string - if (modified) - { - getViewModel()->setDisplay(text); - deselect(); - setCursorPos(mCursorPos); - needsReflow(); - } -} - - -void LLTextBase::setWText(const LLWString& text) -{ - setText(wstring_to_utf8str(text)); -} - -const LLWString& LLTextBase::getWText() const -{ - return getViewModel()->getDisplay(); -} - -// If round is true, if the position is on the right half of a character, the cursor -// will be put to its right. If round is false, the cursor will always be put to the -// character's left. - -S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const -{ - // Figure out which line we're nearest to. - LLRect visible_region = getVisibleDocumentRect(); - - // binary search for line that starts before local_y - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom()); - - if (line_iter == mLineInfoList.end()) - { - return getLength(); // past the end - } - - S32 pos = getLength(); - S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.mLeft - visible_region.mLeft; - - segment_set_t::iterator line_seg_iter; - S32 line_seg_offset; - for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); - line_seg_iter != mSegments.end(); - ++line_seg_iter, line_seg_offset = 0) - { - const LLTextSegmentPtr segmentp = *line_seg_iter; - - S32 segment_line_start = segmentp->getStart() + line_seg_offset; - S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; - S32 text_width, text_height; - bool newline = segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); - - if(newline) - { - pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); - break; - } - - // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line - if (hit_past_end_of_line && local_y - mVisibleTextRect.mBottom + visible_region.mBottom > line_iter->mRect.mTop) - { - pos = segment_line_start; - break; - } - if (local_x < start_x + text_width) // cursor to left of right edge of text - { - // Figure out which character we're nearest to. - S32 offset; - if (!segmentp->canEdit()) - { - S32 segment_width, segment_height; - segmentp->getDimensions(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height); - if (round && local_x - start_x > segment_width / 2) - { - offset = segment_line_length; - } - else - { - offset = 0; - } - } - else - { - offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); - } - pos = segment_line_start + offset; - break; - } - else if (hit_past_end_of_line && segmentp->getEnd() > line_iter->mDocIndexEnd - 1) - { - // segment wraps to next line, so just set doc pos to the end of the line - // segment wraps to next line, so just set doc pos to start of next line (represented by mDocIndexEnd) - pos = llmin(getLength(), line_iter->mDocIndexEnd); - break; - } - start_x += text_width; - } - - return pos; -} - -// returns rectangle of insertion caret -// in document coordinate frame from given index into text -LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const -{ - if (mLineInfoList.empty()) - { - return LLRect(); - } - - LLRect doc_rect; - - // clamp pos to valid values - pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); - - // find line that contains cursor - line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); - - doc_rect.mLeft = line_iter->mRect.mLeft; - doc_rect.mBottom = line_iter->mRect.mBottom; - doc_rect.mTop = line_iter->mRect.mTop; - - segment_set_t::iterator line_seg_iter; - S32 line_seg_offset; - segment_set_t::iterator cursor_seg_iter; - S32 cursor_seg_offset; - getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); - getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset); - - while(line_seg_iter != mSegments.end()) - { - const LLTextSegmentPtr segmentp = *line_seg_iter; - - if (line_seg_iter == cursor_seg_iter) - { - // cursor advanced to right based on difference in offset of cursor to start of line - S32 segment_width, segment_height; - segmentp->getDimensions(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); - doc_rect.mLeft += segment_width; - - break; - } - else - { - // add remainder of current text segment to cursor position - S32 segment_width, segment_height; - segmentp->getDimensions(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); - doc_rect.mLeft += segment_width; - // offset will be 0 for all segments after the first - line_seg_offset = 0; - // go to next text segment on this line - ++line_seg_iter; - } - } - - // set rect to 0 width - doc_rect.mRight = doc_rect.mLeft; - - return doc_rect; -} - -LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const -{ - LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - if (mBorderVisible) - { - content_window_rect.stretch(-1); - } - - LLRect local_rect; - - if (mLineInfoList.empty()) - { - // return default height rect in upper left - local_rect = content_window_rect; - local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight()); - return local_rect; - } - - // get the rect in document coordinates - LLRect doc_rect = getDocRectFromDocIndex(pos); - - // compensate for scrolled, inset view of doc - LLRect scrolled_view_rect = getVisibleDocumentRect(); - local_rect = doc_rect; - local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft, - content_window_rect.mBottom - scrolled_view_rect.mBottom); - - return local_rect; -} - -void LLTextBase::updateCursorXPos() -{ - // reset desired x cursor position - mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft; -} - - -void LLTextBase::startOfLine() -{ - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - setCursorPos(mCursorPos - offset); -} - -void LLTextBase::endOfLine() -{ - S32 line = getLineNumFromDocIndex(mCursorPos); - S32 num_lines = getLineCount(); - if (line + 1 >= num_lines) - { - setCursorPos(getLength()); - } - else - { - setCursorPos( getLineStart(line + 1) - 1 ); - } -} - -void LLTextBase::startOfDoc() -{ - setCursorPos(0); - if (mScroller) - { - mScroller->goToTop(); - } -} - -void LLTextBase::endOfDoc() -{ - setCursorPos(getLength()); - if (mScroller) - { - mScroller->goToBottom(); - } -} - -void LLTextBase::changePage( S32 delta ) -{ - const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10; - if (delta == 0 || !mScroller) return; - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - - if( delta == -1 ) - { - mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE); - } - else - if( delta == 1 ) - { - mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE); - } - - if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect) - { - // cursor didn't change apparent position, so move to top or bottom of document, respectively - if (delta < 0) - { - startOfDoc(); - } - else - { - endOfDoc(); - } - } - else - { - setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false); - } -} - -// Picks a new cursor position based on the screen size of text being drawn. -void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset ) -{ - setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset); -} - - -void LLTextBase::changeLine( S32 delta ) -{ - S32 line = getLineNumFromDocIndex(mCursorPos); - - S32 new_line = line; - if( (delta < 0) && (line > 0 ) ) - { - new_line = line - 1; - } - else if( (delta > 0) && (line < (getLineCount() - 1)) ) - { - new_line = line + 1; - } - - LLRect visible_region = getVisibleDocumentRect(); - - S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); - setCursorPos(new_cursor_pos, true); -} - -bool LLTextBase::scrolledToStart() -{ - return mScroller->isAtTop(); -} - -bool LLTextBase::scrolledToEnd() -{ - return mScroller->isAtBottom(); -} - - -bool LLTextBase::setCursor(S32 row, S32 column) -{ - if (0 <= row && row < (S32)mLineInfoList.size()) - { - S32 doc_pos = mLineInfoList[row].mDocIndexStart; - column = llclamp(column, 0, mLineInfoList[row].mDocIndexEnd - mLineInfoList[row].mDocIndexStart - 1); - doc_pos += column; - updateCursorXPos(); - - return setCursorPos(doc_pos); - } - return false; -} - - -bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset) -{ - S32 new_cursor_pos = cursor_pos; - if (new_cursor_pos != mCursorPos) - { - new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos); - } - - mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength()); - needsScroll(); - if (!keep_cursor_offset) - updateCursorXPos(); - // did we get requested position? - return new_cursor_pos == cursor_pos; -} - -// constraint cursor to editable segments of document -S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) -{ - segment_set_t::iterator segment_iter; - S32 offset; - getSegmentAndOffset(index, &segment_iter, &offset); - if (segment_iter == mSegments.end()) - { - return 0; - } - - LLTextSegmentPtr segmentp = *segment_iter; - - if (segmentp->canEdit()) - { - return segmentp->getStart() + offset; - } - else if (segmentp->getStart() < index && index < segmentp->getEnd()) - { - // bias towards document end - if (increasing_direction) - { - return segmentp->getEnd(); - } - // bias towards document start - else - { - return segmentp->getStart(); - } - } - else - { - return index; - } -} - -void LLTextBase::updateRects() -{ - if (mLineInfoList.empty()) - { - mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); - } - else - { - mTextBoundingRect = mLineInfoList.begin()->mRect; - for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); - line_iter != mLineInfoList.end(); - ++line_iter) - { - mTextBoundingRect.unionWith(line_iter->mRect); - } - - mTextBoundingRect.mTop += mVPad; - // subtract a pixel off the bottom to deal with rounding errors in measuring font height - mTextBoundingRect.mBottom -= 1; - - S32 delta_pos = -mTextBoundingRect.mBottom; - // move line segments to fit new document rect - for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) - { - it->mRect.translate(0, delta_pos); - } - mTextBoundingRect.translate(0, delta_pos); - } - - // update document container dimensions according to text contents - LLRect doc_rect = mTextBoundingRect; - // use old mVisibleTextRect constraint document to width of viewable region - doc_rect.mLeft = 0; - - // allow horizontal scrolling? - // if so, use entire width of text contents - // otherwise, stop at width of mVisibleTextRect - doc_rect.mRight = mScroller - ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) - : mVisibleTextRect.getWidth(); - - mDocumentView->setShape(doc_rect); - - //update mVisibleTextRect *after* mDocumentView has been resized - // so that scrollbars are added if document needs to scroll - // since mVisibleTextRect does not include scrollbars - LLRect old_text_rect = mVisibleTextRect; - mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - //FIXME: replace border with image? - if (mBorderVisible) - { - mVisibleTextRect.stretch(-1); - } - if (mVisibleTextRect != old_text_rect) - { - needsReflow(); - } - - // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) - doc_rect.mRight = mScroller - ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) - : mVisibleTextRect.getWidth(); - mDocumentView->setShape(doc_rect); -} - - -void LLTextBase::startSelection() -{ - if( !mIsSelecting ) - { - mIsSelecting = TRUE; - mSelectionStart = mCursorPos; - mSelectionEnd = mCursorPos; - } -} - -void LLTextBase::endSelection() -{ - if( mIsSelecting ) - { - mIsSelecting = FALSE; - mSelectionEnd = mCursorPos; - } -} - -// get portion of document that is visible in text editor -LLRect LLTextBase::getVisibleDocumentRect() const -{ - if (mScroller) - { - return mScroller->getVisibleContentRect(); - } - else - { - // entire document rect is visible when not scrolling - // but offset according to height of widget - LLRect doc_rect = mDocumentView->getLocalRect(); - doc_rect.mLeft -= mDocumentView->getRect().mLeft; - // adjust for height of text above widget baseline - doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight(); - return doc_rect; - } -} - -// -// LLTextSegment -// - -LLTextSegment::~LLTextSegment() -{} - -bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; return false;} -S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; } -S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; } -void LLTextSegment::updateLayout(const LLTextBase& editor) {} -F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { return draw_rect.mLeft; } -bool LLTextSegment::canEdit() const { return false; } -void LLTextSegment::unlinkFromDocument(LLTextBase*) {} -void LLTextSegment::linkToDocument(LLTextBase*) {} -const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; } -//void LLTextSegment::setColor(const LLColor4 &color) {} -LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } -void LLTextSegment::setStyle(LLStyleConstSP style) {} -void LLTextSegment::setToken( LLKeywordToken* token ) {} -LLKeywordToken* LLTextSegment::getToken() const { return NULL; } -void LLTextSegment::setToolTip( const std::string &msg ) {} -void LLTextSegment::dump() const {} -BOOL LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return FALSE; } -BOOL LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return FALSE; } -BOOL LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return FALSE; } -std::string LLTextSegment::getName() const { return ""; } -void LLTextSegment::onMouseCaptureLost() {} -void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} -void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} -BOOL LLTextSegment::hasMouseCapture() { return FALSE; } - -// -// LLNormalTextSegment -// - -LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) -: LLTextSegment(start, end), - mStyle( style ), - mToken(NULL), - mEditor(editor) -{ - mFontHeight = llceil(mStyle->getFont()->getLineHeight()); - - LLUIImagePtr image = mStyle->getImage(); - if (image.notNull()) - { - mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); - } -} - -LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) -: LLTextSegment(start, end), - mToken(NULL), - mEditor(editor) -{ - mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); - - mFontHeight = llceil(mStyle->getFont()->getLineHeight()); -} - -LLNormalTextSegment::~LLNormalTextSegment() -{ - mImageLoadedConnection.disconnect(); -} - - -F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) -{ - if( end - start > 0 ) - { - return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); - } - return draw_rect.mLeft; -} - -// Draws a single text segment, reversing the color for selection if needed. -F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRect rect) -{ - F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; - - const LLWString &text = mEditor.getWText(); - - F32 right_x = rect.mLeft; - if (!mStyle->isVisible()) - { - return right_x; - } - - const LLFontGL* font = mStyle->getFont(); - - LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; - - if( selection_start > seg_start ) - { - // Draw normally - S32 start = seg_start; - S32 end = llmin( selection_start, seg_end ); - S32 length = end - start; - font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses()); - } - rect.mLeft = (S32)ceil(right_x); - - if( (selection_start < seg_end) && (selection_end > seg_start) ) - { - // Draw reversed - S32 start = llmax( selection_start, seg_start ); - S32 end = llmin( selection_end, seg_end ); - S32 length = end - start; - - font->render(text, start, - rect, - LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - length, - &right_x, - mEditor.getUseEllipses()); - } - rect.mLeft = (S32)ceil(right_x); - if( selection_end < seg_end ) - { - // Draw normally - S32 start = llmax( selection_end, seg_start ); - S32 end = seg_end; - S32 length = end - start; - font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses()); - } - return right_x; -} - -BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - LLUI::getWindow()->setCursor(UI_CURSOR_HAND); - return TRUE; - } - } - return FALSE; -} - -BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); - return TRUE; - } - } - return FALSE; -} - -BOOL LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - // eat mouse down event on hyperlinks, so we get the mouse up - return TRUE; - } - } - - return FALSE; -} - -BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - LLUrlAction::clickAction(getStyle()->getLinkHREF()); - return TRUE; - } - } - - return FALSE; -} - -BOOL LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask) -{ - std::string msg; - // do we have a tooltip for a loaded keyword (for script editor)? - if (mToken && !mToken->getToolTip().empty()) - { - const LLWString& wmsg = mToken->getToolTip(); - LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg)); - return TRUE; - } - // or do we have an explicitly set tooltip (e.g., for Urls) - if (!mTooltip.empty()) - { - LLToolTipMgr::instance().show(mTooltip); - return TRUE; - } - - return FALSE; -} - -void LLNormalTextSegment::setToolTip(const std::string& tooltip) -{ - // we cannot replace a keyword tooltip that's loaded from a file - if (mToken) - { - llwarns << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << llendl; - return; - } - mTooltip = tooltip; -} - -bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const -{ - height = 0; - width = 0; - if (num_chars > 0) - { - height = mFontHeight; - const LLWString &text = mEditor.getWText(); - // if last character is a newline, then return true, forcing line break - width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); - } - return false; -} - -S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const -{ - const LLWString &text = mEditor.getWText(); - return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, - (F32)segment_local_x_coord, - F32_MAX, - num_chars, - round); -} - -S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const -{ - const LLWString &text = mEditor.getWText(); - - LLUIImagePtr image = mStyle->getImage(); - if( image.notNull()) - { - num_pixels = llmax(0, num_pixels - image->getWidth()); - } - - S32 last_char = mEnd; - - // set max characters to length of segment, or to first newline - max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); - - // if no character yet displayed on this line, don't require word wrapping since - // we can just move to the next line, otherwise insist on it so we make forward progress - LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) - ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE - : LLFontGL::ONLY_WORD_BOUNDARIES; - S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, - (F32)num_pixels, - max_chars, - word_wrap_style); - - if (num_chars == 0 - && line_offset == 0 - && max_chars > 0) - { - // If at the beginning of a line, and a single character won't fit, draw it anyway - num_chars = 1; - } - - // include *either* the EOF or newline character in this run of text - // but not both - S32 last_char_in_run = mStart + segment_offset + num_chars; - // check length first to avoid indexing off end of string - if (last_char_in_run < mEnd - && (last_char_in_run >= mEditor.getLength() )) - { - num_chars++; - } - return num_chars; -} - -void LLNormalTextSegment::dump() const -{ - llinfos << "Segment [" << -// mColor.mV[VX] << ", " << -// mColor.mV[VY] << ", " << -// mColor.mV[VZ] << "]\t[" << - mStart << ", " << - getEnd() << "]" << - llendl; -} - - -// -// LLInlineViewSegment -// - -LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) -: LLTextSegment(start, end), - mView(p.view), - mForceNewLine(p.force_newline), - mLeftPad(p.left_pad), - mRightPad(p.right_pad), - mTopPad(p.top_pad), - mBottomPad(p.bottom_pad) -{ -} - -LLInlineViewSegment::~LLInlineViewSegment() -{ - mView->die(); -} - -bool LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const -{ - if (first_char == 0 && num_chars == 0) - { - // we didn't fit on a line, the widget will fall on the next line - // so dimensions here are 0 - width = 0; - height = 0; - } - else - { - width = mLeftPad + mRightPad + mView->getRect().getWidth(); - height = mBottomPad + mTopPad + mView->getRect().getHeight(); - } - - return false; -} - -S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const -{ - // if putting a widget anywhere but at the beginning of a line - // and the widget doesn't fit or mForceNewLine is true - // then return 0 chars for that line, and all characters for the next - if (line_offset != 0 - && (mForceNewLine || num_pixels < mView->getRect().getWidth())) - { - return 0; - } - else - { - return mEnd - mStart; - } -} - -void LLInlineViewSegment::updateLayout(const LLTextBase& editor) -{ - LLRect start_rect = editor.getDocRectFromDocIndex(mStart); - mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad); -} - -F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) -{ - // return padded width of widget - // widget is actually drawn during mDocumentView's draw() - return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad); -} - -void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor) -{ - editor->removeDocumentChild(mView); -} - -void LLInlineViewSegment::linkToDocument(LLTextBase* editor) -{ - editor->addDocumentChild(mView); -} - -LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) -{ - LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); - - mFontHeight = llceil(s->getFont()->getLineHeight()); -} -LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) -{ - mFontHeight = llceil(style->getFont()->getLineHeight()); -} -LLLineBreakTextSegment::~LLLineBreakTextSegment() -{ -} -bool LLLineBreakTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const -{ - width = 0; - height = mFontHeight; - - return true; -} -S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const -{ - return 1; -} -F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) -{ - return draw_rect.mLeft; -} - -LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor) - :LLTextSegment(pos,pos+1) - ,mStyle( style ) - ,mEditor(editor) -{ -} - -LLImageTextSegment::~LLImageTextSegment() -{ -} - -static const S32 IMAGE_HPAD = 3; - -bool LLImageTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const -{ - width = 0; - height = llceil(mStyle->getFont()->getLineHeight());; - - LLUIImagePtr image = mStyle->getImage(); - if( num_chars>0 && image.notNull()) - { - width += image->getWidth() + IMAGE_HPAD; - height = llmax(height, image->getHeight() + IMAGE_HPAD ); - } - return false; -} - -S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const -{ - LLUIImagePtr image = mStyle->getImage(); - S32 image_width = image->getWidth(); - if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) - { - return 1; - } - return 0; -} - -F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) -{ - if ( (start >= 0) && (end <= mEnd - mStart)) - { - LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; - LLUIImagePtr image = mStyle->getImage(); - S32 style_image_height = image->getHeight(); - S32 style_image_width = image->getWidth(); - // Text is drawn from the top of the draw_rect downward - - S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); - // Align image to center of draw rect - S32 image_bottom = text_center - (style_image_height / 2); - image->draw(draw_rect.mLeft, image_bottom, - style_image_width, style_image_height, color); - - const S32 IMAGE_HPAD = 3; - return draw_rect.mLeft + style_image_width + IMAGE_HPAD; - } - return 0.0; -} - +/** + * @file lltextbase.cpp + * @author Martin Reddy + * @brief The base class of text box/editor, providing Url handling support + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltextbase.h" + +#include "lllocalcliprect.h" +#include "llmenugl.h" +#include "llscrollcontainer.h" +#include "llstl.h" +#include "lltextparser.h" +#include "lltextutil.h" +#include "lltooltip.h" +#include "lluictrl.h" +#include "llurlaction.h" +#include "llurlregistry.h" +#include "llview.h" +#include "llwindow.h" +#include + +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 CURSOR_THICKNESS = 2; + +LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) +: mDocIndexStart(index_start), + mDocIndexEnd(index_end), + mRect(rect), + mLineNum(line_num) +{} + +bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const +{ + // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11) + if (a->getEnd() == b->getEnd()) + { + return a->getStart() < b->getStart(); + } + return a->getEnd() < b->getEnd(); +} + + +// helper functors +struct LLTextBase::compare_bottom +{ + bool operator()(const S32& a, const LLTextBase::line_info& b) const + { + return a > b.mRect.mBottom; // bottom of a is higher than bottom of b + } + + bool operator()(const LLTextBase::line_info& a, const S32& b) const + { + return a.mRect.mBottom > b; // bottom of a is higher than bottom of b + } + + bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const + { + return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b + } + +}; + +// helper functors +struct LLTextBase::compare_top +{ + bool operator()(const S32& a, const LLTextBase::line_info& b) const + { + return a > b.mRect.mTop; // top of a is higher than top of b + } + + bool operator()(const LLTextBase::line_info& a, const S32& b) const + { + return a.mRect.mTop > b; // top of a is higher than top of b + } + + bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const + { + return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b + } +}; + +struct LLTextBase::line_end_compare +{ + bool operator()(const S32& pos, const LLTextBase::line_info& info) const + { + return (pos < info.mDocIndexEnd); + } + + bool operator()(const LLTextBase::line_info& info, const S32& pos) const + { + return (info.mDocIndexEnd < pos); + } + + bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const + { + return (a.mDocIndexEnd < b.mDocIndexEnd); + } + +}; + +////////////////////////////////////////////////////////////////////////// +// +// LLTextBase +// + +// register LLTextBase::Params under name "textbase" +static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase"); + +LLTextBase::LineSpacingParams::LineSpacingParams() +: multiple("multiple", 1.f), + pixels("pixels", 0) +{ +} + + +LLTextBase::Params::Params() +: cursor_color("cursor_color"), + text_color("text_color"), + text_readonly_color("text_readonly_color"), + bg_visible("bg_visible", false), + border_visible("border_visible", false), + bg_readonly_color("bg_readonly_color"), + bg_writeable_color("bg_writeable_color"), + bg_focus_color("bg_focus_color"), + allow_scroll("allow_scroll", true), + plain_text("plain_text",false), + track_end("track_end", false), + read_only("read_only", false), + v_pad("v_pad", 0), + h_pad("h_pad", 0), + clip_partial("clip_partial", true), + line_spacing("line_spacing"), + max_text_length("max_length", 255), + font_shadow("font_shadow"), + wrap("wrap"), + use_ellipses("use_ellipses", false), + allow_html("allow_html", false), + parse_highlights("parse_highlights", false) +{ + addSynonym(track_end, "track_bottom"); + addSynonym(wrap, "word_wrap"); +} + + +LLTextBase::LLTextBase(const LLTextBase::Params &p) +: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), + mURLClickSignal(), + mMaxTextByteLength( p.max_text_length ), + mDefaultFont(p.font), + mFontShadow(p.font_shadow), + mPopupMenu(NULL), + mReadOnly(p.read_only), + mCursorColor(p.cursor_color), + mFgColor(p.text_color), + mBorderVisible( p.border_visible ), + mReadOnlyFgColor(p.text_readonly_color), + mWriteableBgColor(p.bg_writeable_color), + mReadOnlyBgColor(p.bg_readonly_color), + mFocusBgColor(p.bg_focus_color), + mReflowIndex(S32_MAX), + mCursorPos( 0 ), + mScrollNeeded(FALSE), + mDesiredXPixel(-1), + mHPad(p.h_pad), + mVPad(p.v_pad), + mHAlign(p.font_halign), + mVAlign(p.font_valign), + mLineSpacingMult(p.line_spacing.multiple), + mLineSpacingPixels(p.line_spacing.pixels), + mClipPartial(p.clip_partial && !p.allow_scroll), + mTrackEnd( p.track_end ), + mScrollIndex(-1), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mIsSelecting( FALSE ), + mPlainText ( p.plain_text ), + mWordWrap(p.wrap), + mUseEllipses( p.use_ellipses ), + mParseHTML(p.allow_html), + mParseHighlights(p.parse_highlights), + mBGVisible(p.bg_visible), + mScroller(NULL) +{ + if(p.allow_scroll) + { + LLScrollContainer::Params scroll_params; + scroll_params.name = "text scroller"; + scroll_params.rect = getLocalRect(); + scroll_params.follows.flags = FOLLOWS_ALL; + scroll_params.is_opaque = false; + scroll_params.mouse_opaque = false; + scroll_params.min_auto_scroll_rate = 200; + scroll_params.max_auto_scroll_rate = 800; + scroll_params.border_visible = p.border_visible; + mScroller = LLUICtrlFactory::create(scroll_params); + addChild(mScroller); + } + + LLView::Params view_params; + view_params.name = "text_contents"; + view_params.rect = LLRect(0, 500, 500, 0); + view_params.mouse_opaque = false; + + mDocumentView = LLUICtrlFactory::create(view_params); + if (mScroller) + { + mScroller->addChild(mDocumentView); + } + else + { + addChild(mDocumentView); + } + + createDefaultSegment(); + + updateRects(); +} + +LLTextBase::~LLTextBase() +{ + // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder + + mSegments.clear(); +} + +void LLTextBase::initFromParams(const LLTextBase::Params& p) +{ + LLUICtrl::initFromParams(p); + resetDirty(); // Update saved text state + updateSegments(); + + // HACK: work around enabled == readonly design bug -- RN + // setEnabled will modify our read only status, so do this after + // LLTextBase::initFromParams + if (p.read_only.isProvided()) + { + mReadOnly = p.read_only; + } + + // HACK: text editors always need to be enabled so that we can scroll + LLView::setEnabled(true); +} + +bool LLTextBase::truncate() +{ + BOOL did_truncate = FALSE; + + // First rough check - if we're less than 1/4th the size, we're OK + if (getLength() >= S32(mMaxTextByteLength / 4)) + { + // Have to check actual byte size + LLWString text(getWText()); + S32 utf8_byte_size = wstring_utf8_length(text); + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = wstring_to_utf8str(text); + 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. + removeStringNoUndo(text.size(), getWText().size() - text.size()); + did_truncate = TRUE; + } + } + + return did_truncate; +} + +LLStyle::Params LLTextBase::getDefaultStyleParams() +{ + return LLStyle::Params() + .color(LLUIColor(&mFgColor)) + .readonly_color(LLUIColor(&mReadOnlyFgColor)) + .font(mDefaultFont) + .drop_shadow(mFontShadow); +} + +void LLTextBase::onValueChange(S32 start, S32 end) +{ +} + + +// Draws the black box behind the selected text +void LLTextBase::drawSelectionBackground() +{ + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection() && !mLineInfoList.empty()) + { + std::vector selection_rects; + + S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); + LLRect selection_rect = mVisibleTextRect; + + // Skip through the lines we aren't drawing. + LLRect content_display_rect = getVisibleDocumentRect(); + + // binary search for line that starts before top of visible buffer + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); + line_list_t::const_iterator end_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); + + bool done = false; + + // Find the coordinates of the selected area + for (;line_iter != end_iter && !done; ++line_iter) + { + // is selection visible on this line? + if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right) + { + segment_set_t::iterator segment_iter; + S32 segment_offset; + getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset); + + LLRect selection_rect; + selection_rect.mLeft = line_iter->mRect.mLeft; + selection_rect.mRight = line_iter->mRect.mLeft; + selection_rect.mBottom = line_iter->mRect.mBottom; + selection_rect.mTop = line_iter->mRect.mTop; + + for(;segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0) + { + LLTextSegmentPtr segmentp = *segment_iter; + + S32 segment_line_start = segmentp->getStart() + segment_offset; + S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); + + if (segment_line_start > segment_line_end) break; + + S32 segment_width = 0; + S32 segment_height = 0; + + // if selection after beginning of segment + if(selection_left >= segment_line_start) + { + S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start; + segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mLeft += segment_width; + } + + // if selection_right == segment_line_end then that means we are the first character of the next segment + // or first character of the next line, in either case we want to add the length of the current segment + // to the selection rectangle and continue. + // if selection right > segment_line_end then selection spans end of current segment... + if (selection_right >= segment_line_end) + { + // extend selection slightly beyond end of line + // to indicate selection of newline character (use "n" character to determine width) + S32 num_chars = segment_line_end - segment_line_start; + segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mRight += segment_width; + } + // else if selection ends on current segment... + else + { + S32 num_chars = selection_right - segment_line_start; + segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mRight += segment_width; + + break; + } + } + selection_rects.push_back(selection_rect); + } + } + + // Draw the selection box (we're using a box instead of reversing the colors on the selected text). + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + const LLColor4& color = mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get(); + F32 alpha = hasFocus() ? 0.7f : 0.3f; + alpha *= getDrawContext().mAlpha; + LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); + + for (std::vector::iterator rect_it = selection_rects.begin(); + rect_it != selection_rects.end(); + ++rect_it) + { + LLRect selection_rect = *rect_it; + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + gl_rect_2d(selection_rect, selection_color); + } + } +} + +void LLTextBase::drawCursor() +{ + F32 alpha = getDrawContext().mAlpha; + + if( hasFocus() + && gFocusMgr.getAppHasFocus() + && !mReadOnly) + { + const LLWString &wtext = getWText(); + const llwchar* text = wtext.c_str(); + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + cursor_rect.translate(-1, 0); + segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos); + + // take style from last segment + LLTextSegmentPtr segmentp; + + if (seg_it != mSegments.end()) + { + segmentp = *seg_it; + } + else + { + //segmentp = mSegments.back(); + return; + } + + // Draw the cursor + // (Flash the cursor every half second starting a fixed time after the last keystroke) + F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + S32 segment_width = 0; + S32 segment_height = 0; + segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); + S32 width = llmax(CURSOR_THICKNESS, segment_width); + cursor_rect.mRight = cursor_rect.mLeft + width; + } + else + { + cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS; + } + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLColor4 cursor_color = mCursorColor.get() % alpha; + gGL.color4fv( cursor_color.mV ); + + gl_rect_2d(cursor_rect); + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') + { + LLColor4 text_color; + const LLFontGL* fontp; + if (segmentp) + { + text_color = segmentp->getColor(); + fontp = segmentp->getStyle()->getFont(); + } + else if (mReadOnly) + { + text_color = mReadOnlyFgColor.get(); + fontp = mDefaultFont; + } + else + { + text_color = mFgColor.get(); + fontp = mDefaultFont; + } + fontp->render(text, mCursorPos, cursor_rect, + LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), + LLFontGL::LEFT, mVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + 1); + } + + // Make sure the IME is in the right place + LLRect screen_pos = calcScreenRect(); + LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) ); + + ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]); + ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); + getWindow()->setLanguageTextInput( ime_pos ); + } + } +} + +void LLTextBase::drawText() +{ + const S32 text_len = getLength(); + if( text_len <= 0 ) + { + return; + } + S32 selection_left = -1; + S32 selection_right = -1; + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection()) + { + selection_left = llmin( mSelectionStart, mSelectionEnd ); + selection_right = llmax( mSelectionStart, mSelectionEnd ); + } + + LLRect scrolled_view_rect = getVisibleDocumentRect(); + std::pair line_range = getVisibleLines(mClipPartial); + S32 first_line = line_range.first; + S32 last_line = line_range.second; + if (first_line >= last_line) + { + return; + } + + S32 line_start = getLineStart(first_line); + // find first text segment that spans top of visible portion of text buffer + segment_set_t::iterator seg_iter = getSegIterContaining(line_start); + if (seg_iter == mSegments.end()) + { + return; + } + + LLTextSegmentPtr cur_segment = *seg_iter; + + for (S32 cur_line = first_line; cur_line < last_line; cur_line++) + { + S32 next_line = cur_line + 1; + line_info& line = mLineInfoList[cur_line]; + + S32 next_start = -1; + S32 line_end = text_len; + + if (next_line < getLineCount()) + { + next_start = getLineStart(next_line); + line_end = next_start; + } + + LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, + line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom, + llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft, + line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom); + + // draw a single line of text + S32 seg_start = line_start; + while( seg_start < line_end ) + { + while( cur_segment->getEnd() <= seg_start ) + { + seg_iter++; + if (seg_iter == mSegments.end()) + { + llwarns << "Ran off the segmentation end!" << llendl; + + return; + } + cur_segment = *seg_iter; + } + + S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - cur_segment->getStart(); + + if (mUseEllipses // using ellipses + && clipped_end == line_end // last segment on line + && next_line == last_line // this is the last visible line + && last_line < (S32)mLineInfoList.size()) // and there is more text to display + { + // more lines of text to go, but we can't fit them + // so shrink text rect to force ellipses + text_rect.mRight -= 2; + } + + 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(); + } + + line_start = next_start; + } +} + +/////////////////////////////////////////////////////////////////// +// Returns change in number of characters in mWText + +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 insert_len = wstr.length(); + + pos = getEditableIndex(pos, true); + + segment_set_t::iterator seg_iter = getSegIterContaining(pos); + + LLTextSegmentPtr default_segment; + + LLTextSegmentPtr segmentp; + if (seg_iter != mSegments.end()) + { + segmentp = *seg_iter; + } + else + { + //segmentp = mSegments.back(); + return pos; + } + + if (segmentp->canEdit()) + { + segmentp->setEnd(segmentp->getEnd() + insert_len); + if (seg_iter != mSegments.end()) + { + ++seg_iter; + } + } + else + { + // create default editable segment to hold new text + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); + } + + // shift remaining segments to right + for(;seg_iter != mSegments.end(); ++seg_iter) + { + LLTextSegmentPtr segmentp = *seg_iter; + segmentp->setStart(segmentp->getStart() + insert_len); + segmentp->setEnd(segmentp->getEnd() + insert_len); + } + + // insert new segments + if (segments) + { + if (default_segment.notNull()) + { + // potentially overwritten by segments passed in + insertSegment(default_segment); + } + for (segment_vec_t::iterator seg_iter = segments->begin(); + seg_iter != segments->end(); + ++seg_iter) + { + LLTextSegment* segmentp = *seg_iter; + insertSegment(segmentp); + } + } + + text.insert(pos, wstr); + getViewModel()->setDisplay(text); + + if ( truncate() ) + { + insert_len = getLength() - old_len; + } + + onValueChange(pos, pos + insert_len); + needsReflow(pos); + + return insert_len; +} + +S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) +{ + LLWString text(getWText()); + segment_set_t::iterator seg_iter = getSegIterContaining(pos); + while(seg_iter != mSegments.end()) + { + LLTextSegmentPtr segmentp = *seg_iter; + S32 end = pos + length; + if (segmentp->getStart() < pos) + { + // deleting from middle of segment + if (segmentp->getEnd() > end) + { + segmentp->setEnd(segmentp->getEnd() - length); + } + // truncating segment + else + { + segmentp->setEnd(pos); + } + } + else if (segmentp->getStart() < end) + { + // deleting entire segment + if (segmentp->getEnd() <= end) + { + // remove segment + segmentp->unlinkFromDocument(this); + segment_set_t::iterator seg_to_erase(seg_iter++); + mSegments.erase(seg_to_erase); + continue; + } + // deleting head of segment + else + { + segmentp->setStart(pos); + segmentp->setEnd(segmentp->getEnd() - length); + } + } + else + { + // shifting segments backward to fill deleted portion + segmentp->setStart(segmentp->getStart() - length); + segmentp->setEnd(segmentp->getEnd() - length); + } + ++seg_iter; + } + + text.erase(pos, length); + getViewModel()->setDisplay(text); + + // recreate default segment in case we erased everything + createDefaultSegment(); + + onValueChange(pos, pos); + needsReflow(pos); + + return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length +} + +S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) +{ + if (pos > (S32)getLength()) + { + return 0; + } + LLWString text(getWText()); + text[pos] = wc; + getViewModel()->setDisplay(text); + + onValueChange(pos, pos + 1); + needsReflow(pos); + + return 1; +} + + +void LLTextBase::createDefaultSegment() +{ + // ensures that there is always at least one segment + if (mSegments.empty()) + { + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); + mSegments.insert(default_segment); + default_segment->linkToDocument(this); + } +} + +void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) +{ + if (segment_to_insert.isNull()) + { + return; + } + + segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); + S32 reflow_start_index = 0; + + if (cur_seg_iter == mSegments.end()) + { + mSegments.insert(segment_to_insert); + segment_to_insert->linkToDocument(this); + reflow_start_index = segment_to_insert->getStart(); + } + else + { + LLTextSegmentPtr cur_segmentp = *cur_seg_iter; + reflow_start_index = cur_segmentp->getStart(); + if (cur_segmentp->getStart() < segment_to_insert->getStart()) + { + S32 old_segment_end = cur_segmentp->getEnd(); + // split old at start point for new segment + cur_segmentp->setEnd(segment_to_insert->getStart()); + // advance to next segment + // insert remainder of old segment + LLStyleConstSP sp = cur_segmentp->getStyle(); + LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); + mSegments.insert(cur_seg_iter, remainder_segment); + remainder_segment->linkToDocument(this); + // insert new segment before remainder of old segment + mSegments.insert(cur_seg_iter, segment_to_insert); + + segment_to_insert->linkToDocument(this); + // at this point, there will be two overlapping segments owning the text + // associated with the incoming segment + } + else + { + mSegments.insert(cur_seg_iter, segment_to_insert); + segment_to_insert->linkToDocument(this); + } + + // now delete/truncate remaining segments as necessary + // cur_seg_iter points to segment before incoming segment + while(cur_seg_iter != mSegments.end()) + { + cur_segmentp = *cur_seg_iter; + if (cur_segmentp == segment_to_insert) + { + ++cur_seg_iter; + continue; + } + + if (cur_segmentp->getStart() >= segment_to_insert->getStart()) + { + if(cur_segmentp->getEnd() <= segment_to_insert->getEnd()) + { + cur_segmentp->unlinkFromDocument(this); + // grab copy of iterator to erase, and bump it + segment_set_t::iterator seg_to_erase(cur_seg_iter++); + mSegments.erase(seg_to_erase); + continue; + } + else + { + // last overlapping segment, clip to end of incoming segment + // and stop traversal + cur_segmentp->setStart(segment_to_insert->getEnd()); + break; + } + } + ++cur_seg_iter; + } + } + + // layout potentially changed + needsReflow(reflow_start_index); +} + +BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleMouseDown(x, y, mask); +} + +BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseUp(x, y, mask)) + { + // Did we just click on a link? + if (cur_segment->getStyle() + && cur_segment->getStyle()->isLink()) + { + // *TODO: send URL here? + mURLClickSignal(this, LLSD() ); + } + return TRUE; + } + + return LLUICtrl::handleMouseUp(x, y, mask); +} + +BOOL LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleMiddleMouseDown(x, y, mask); +} + +BOOL LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleMiddleMouseUp(x, y, mask); +} + +BOOL LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleRightMouseDown(x, y, mask); +} + +BOOL LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleRightMouseUp(x, y, mask); +} + +BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +BOOL LLTextBase::handleHover(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleHover(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleHover(x, y, mask); +} + +BOOL LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks)) + { + return TRUE; + } + + return LLUICtrl::handleScrollWheel(x, y, clicks); +} + +BOOL LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleToolTip(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleToolTip(x, y, mask); +} + + +void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + if (width != getRect().getWidth() || height != getRect().getHeight()) + { + bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; + + LLUICtrl::reshape( width, height, called_from_parent ); + + if (mScroller && scrolled_to_bottom && mTrackEnd) + { + // keep bottom of text buffer visible + // do this here as well as in reflow to handle case + // where shrinking from top, which causes buffer to temporarily + // not be scrolled to the bottom, since the scroll index + // specified the _top_ of the visible document region + mScroller->goToBottom(); + } + + // do this first after reshape, because other things depend on + // up-to-date mVisibleTextRect + updateRects(); + + needsReflow(); + } +} + +void LLTextBase::draw() +{ + // reflow if needed, on demand + reflow(); + + // then update scroll position, as cursor may have moved + if (!mReadOnly) + { + updateScrollFromCursor(); + } + + LLRect doc_rect; + if (mScroller) + { + mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &doc_rect, this); + } + else + { + doc_rect = getLocalRect(); + } + + if (mBGVisible) + { + // clip background rect against extents, if we support scrolling + LLLocalClipRect clip(doc_rect, mScroller != NULL); + + LLColor4 bg_color = mReadOnly + ? mReadOnlyBgColor.get() + : hasFocus() + ? mFocusBgColor.get() + : mWriteableBgColor.get(); + gl_rect_2d(mVisibleTextRect, bg_color, TRUE); + } + + // draw document view + LLUICtrl::draw(); + + { + // only clip if we support scrolling (mScroller != NULL) + LLLocalClipRect clip(doc_rect, mScroller != NULL); + drawSelectionBackground(); + drawText(); + drawCursor(); + } +} + + +//virtual +void LLTextBase::setColor( const LLColor4& c ) +{ + mFgColor = c; +} + +//virtual +void LLTextBase::setReadOnlyColor(const LLColor4 &c) +{ + mReadOnlyFgColor = c; +} + +//virtual +void LLTextBase::handleVisibilityChange( BOOL new_visibility ) +{ + if(!new_visibility && mPopupMenu) + { + mPopupMenu->hide(); + } + LLUICtrl::handleVisibilityChange(new_visibility); +} + +//virtual +void LLTextBase::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +//virtual +BOOL LLTextBase::canDeselect() const +{ + return hasSelection(); +} + + +//virtual +void LLTextBase::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +// Sets the scrollbar from the cursor position +void LLTextBase::updateScrollFromCursor() +{ + // Update scroll position even in read-only mode (when there's no cursor displayed) + // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736. + + if (!mScrollNeeded || !mScroller) + { + return; + } + mScrollNeeded = FALSE; + + // scroll so that the cursor is at the top of the page + LLRect scroller_doc_window = getVisibleDocumentRect(); + LLRect cursor_rect_doc = getLocalRectFromDocIndex(mCursorPos); + cursor_rect_doc.translate(scroller_doc_window.mLeft, scroller_doc_window.mBottom); + mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5)); +} + +S32 LLTextBase::getLeftOffset(S32 width) +{ + switch (mHAlign) + { + case LLFontGL::LEFT: + return mHPad; + case LLFontGL::HCENTER: + return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2); + case LLFontGL::RIGHT: + return mVisibleTextRect.getWidth() - width; + default: + return mHPad; + } +} + + +static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow"); +void LLTextBase::reflow() +{ + LLFastTimer ft(FTM_TEXT_REFLOW); + + updateSegments(); + + if (mReflowIndex == S32_MAX) + { + return; + } + + bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible + + // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing + cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; + cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; + + S32 first_line = getFirstVisibleLine(); + + // if scroll anchor not on first line, update it to first character of first line + if (!mLineInfoList.empty() + && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart + || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) + { + mScrollIndex = mLineInfoList[first_line].mDocIndexStart; + } + LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); + // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing + first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; + first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; + + S32 reflow_count = 0; + while(mReflowIndex < S32_MAX) + { + // we can get into an infinite loop if the document height does not monotonically increase + // with decreasing width (embedded ui elements with alternate layouts). In that case, + // we want to stop reflowing after 2 iterations. We use 2, since we need to handle the case + // of introducing a vertical scrollbar causing a reflow with less width. We should also always + // use an even number of iterations to avoid user visible oscillation of the layout + if(++reflow_count > 2) + { + lldebugs << "Breaking out of reflow due to possible infinite loop in " << getName() << llendl; + break; + } + + S32 start_index = mReflowIndex; + mReflowIndex = S32_MAX; + + // shrink document to minimum size (visible portion of text widget) + // to force inlined widgets with follows set to shrink + mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); + + S32 cur_top = 0; + + segment_set_t::iterator seg_iter = mSegments.begin(); + S32 seg_offset = 0; + S32 line_start_index = 0; + const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin + S32 remaining_pixels = text_available_width; + S32 line_count = 0; + + // find and erase line info structs starting at start_index and going to end of document + if (!mLineInfoList.empty()) + { + // find first element whose end comes after start_index + line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare()); + line_start_index = iter->mDocIndexStart; + line_count = iter->mLineNum; + cur_top = iter->mRect.mTop; + getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); + mLineInfoList.erase(iter, mLineInfoList.end()); + } + + S32 line_height = 0; + + while(seg_iter != mSegments.end()) + { + LLTextSegmentPtr segment = *seg_iter; + + // track maximum height of any segment on this line + S32 cur_index = segment->getStart() + seg_offset; + + // ask segment how many character fit in remaining space + S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, remaining_pixels) : S32_MAX, + seg_offset, + cur_index - line_start_index, + S32_MAX); + + S32 segment_width, segment_height; + bool force_newline = segment->getDimensions(seg_offset, character_count, segment_width, segment_height); + // grow line height as necessary based on reported height of this segment + line_height = llmax(line_height, segment_height); + remaining_pixels -= segment_width; + + seg_offset += character_count; + + S32 last_segment_char_on_line = segment->getStart() + seg_offset; + + S32 text_actual_width = text_available_width - remaining_pixels; + S32 text_left = getLeftOffset(text_actual_width); + LLRect line_rect(text_left, + cur_top, + text_left + text_actual_width, + cur_top - line_height); + + // if we didn't finish the current segment... + if (last_segment_char_on_line < segment->getEnd()) + { + // add line info and keep going + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + + line_start_index = segment->getStart() + seg_offset; + cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + remaining_pixels = text_available_width; + line_height = 0; + } + // ...just consumed last segment.. + else if (++segment_set_t::iterator(seg_iter) == mSegments.end()) + { + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + break; + } + // ...or finished a segment and there are segments remaining on this line + else + { + // subtract pixels used and increment segment + if (force_newline) + { + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + line_start_index = segment->getStart() + seg_offset; + cur_top -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + line_height = 0; + remaining_pixels = text_available_width; + } + ++seg_iter; + seg_offset = 0; + } + if (force_newline) + { + line_count++; + } + } + + // calculate visible region for diplaying text + updateRects(); + + for (segment_set_t::iterator segment_it = mSegments.begin(); + segment_it != mSegments.end(); + ++segment_it) + { + LLTextSegmentPtr segmentp = *segment_it; + segmentp->updateLayout(*this); + + } + } + + // apply scroll constraints after reflowing text + if (!hasMouseCapture() && mScroller) + { + if (scrolled_to_bottom && mTrackEnd) + { + // keep bottom of text buffer visible + endOfDoc(); + } + else if (hasSelection() && follow_selection) + { + // keep cursor in same vertical position on screen when selecting text + LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); + LLRect old_cursor_rect = cursor_rect; + old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; + old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; + + mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); + } + else + { + // keep first line of text visible + LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); + + // pass in desired rect in the coordinate frame of the document viewport + LLRect old_first_char_rect = first_char_rect; + old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; + old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; + + mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect); + } + } + + // reset desired x cursor position + updateCursorXPos(); + } + +LLRect LLTextBase::getTextBoundingRect() +{ + reflow(); + return mTextBoundingRect; +} + + +void LLTextBase::clearSegments() +{ + mSegments.clear(); + createDefaultSegment(); +} + +S32 LLTextBase::getLineStart( S32 line ) const +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + + line = llclamp(line, 0, num_lines-1); + return mLineInfoList[line].mDocIndexStart; +} + +S32 LLTextBase::getLineEnd( S32 line ) const +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + + line = llclamp(line, 0, num_lines-1); + return mLineInfoList[line].mDocIndexEnd; +} + + + +S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const +{ + if (mLineInfoList.empty()) + { + return 0; + } + else + { + line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare()); + if (include_wordwrap) + { + return iter - mLineInfoList.begin(); + } + else + { + if (iter == mLineInfoList.end()) + { + return mLineInfoList.back().mLineNum; + } + else + { + return iter->mLineNum; + } + } + } +} + +// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. +S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const +{ + if (mLineInfoList.empty()) + { + return startpos; + } + else + { + line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare()); + return startpos - iter->mDocIndexStart; + } +} + +S32 LLTextBase::getFirstVisibleLine() const +{ + LLRect visible_region = getVisibleDocumentRect(); + + // binary search for line that starts before top of visible buffer + line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); + + return iter - mLineInfoList.begin(); +} + +std::pair LLTextBase::getVisibleLines(bool fully_visible) +{ + LLRect visible_region = getVisibleDocumentRect(); + line_list_t::const_iterator first_iter; + line_list_t::const_iterator last_iter; + + // make sure we have an up-to-date mLineInfoList + reflow(); + + if (fully_visible) + { + first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); + last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); + } + else + { + first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); + last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top()); + } + return std::pair(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin()); +} + + + +LLTextViewModel* LLTextBase::getViewModel() const +{ + return (LLTextViewModel*)mViewModel.get(); +} + +void LLTextBase::addDocumentChild(LLView* view) +{ + mDocumentView->addChild(view); +} + +void LLTextBase::removeDocumentChild(LLView* view) +{ + mDocumentView->removeChild(view); +} + + +static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments"); +void LLTextBase::updateSegments() +{ + LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS); + createDefaultSegment(); +} + +void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const +{ + *seg_iter = getSegIterContaining(startpos); + if (*seg_iter == mSegments.end()) + { + *offsetp = 0; + } + else + { + *offsetp = startpos - (**seg_iter)->getStart(); + } +} + +void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ) +{ + *seg_iter = getSegIterContaining(startpos); + if (*seg_iter == mSegments.end()) + { + *offsetp = 0; + } + else + { + *offsetp = startpos - (**seg_iter)->getStart(); + } +} + +LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) +{ + segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index)); + return it; +} + +LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const +{ + LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(new LLIndexSegment(index)); + return it; +} + +// Finds the text segment (if any) at the give local screen position +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) +{ + // Find the cursor position at the requested local screen position + S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line); + segment_set_t::iterator seg_iter = getSegIterContaining(offset); + if (seg_iter != mSegments.end()) + { + return *seg_iter; + } + else + { + return LLTextSegmentPtr(); + } +} + +void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) +{ + // work out the XUI menu file to use for this url + LLUrlMatch match; + std::string url = in_url; + if (! LLUrlRegistry::instance().findUrl(url, match)) + { + return; + } + + std::string xui_file = match.getMenuName(); + if (xui_file.empty()) + { + return; + } + + // set up the callbacks for all of the potential menu items, N.B. we + // don't use const ref strings in callbacks in case url goes out of scope + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url)); + registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url)); + registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); + registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url)); + registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); + registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); + registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); + registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); + registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); + + // create and return the context menu from the XUI file + delete mPopupMenu; + mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile(xui_file, LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + if (mPopupMenu) + { + mPopupMenu->show(x, y); + LLMenuGL::showPopup(this, mPopupMenu, x, y); + } +} + +void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) +{ + // clear out the existing text and segments + getViewModel()->setDisplay(LLWStringUtil::null); + + clearSegments(); +// createDefaultSegment(); + + deselect(); + + // append the new text (supports Url linking) + std::string text(utf8str); + LLStringUtil::removeCRLF(text); + + // appendText modifies mCursorPos... + appendText(text, false, input_params); + // ...so move cursor to top after appending text + startOfDoc(); + + onValueChange(0, getLength()); +} + +//virtual +std::string LLTextBase::getText() const +{ + return getViewModel()->getValue().asString(); +} + +// IDEVO - icons can be UI image names or UUID sent from +// server with avatar display name +static LLUIImagePtr image_from_icon_name(const std::string& icon_name) +{ + if (LLUUID::validate(icon_name)) + { + return LLUI::getUIImageByID( LLUUID(icon_name) ); + } + else + { + return LLUI::getUIImage(icon_name); + } +} + +void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) +{ + LLStyle::Params style_params(input_params); + style_params.fillFrom(getDefaultStyleParams()); + + S32 part = (S32)LLTextParser::WHOLE; + if(mParseHTML) + { + 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; + + LLStyle::Params link_params(style_params); + link_params.overwriteFrom(match.getStyle()); + + // output the text before the Url + if (start > 0) + { + if (part == (S32)LLTextParser::WHOLE || + part == (S32)LLTextParser::START) + { + part = (S32)LLTextParser::START; + } + else + { + part = (S32)LLTextParser::MIDDLE; + } + std::string subtext=text.substr(0,start); + appendAndHighlightText(subtext, part, style_params); + } + + // output the styled Url + appendAndHighlightTextImpl(match.getLabel(), part, link_params); + + // set the tooltip for the Url label + if (! match.getTooltip().empty()) + { + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(match.getTooltip()); + } + } + + // move on to the rest of the text after the Url + if (end < (S32)text.length()) + { + text = text.substr(end,text.length() - end); + end=0; + part=(S32)LLTextParser::END; + } + else + { + break; + } + } + if (part != (S32)LLTextParser::WHOLE) + part=(S32)LLTextParser::END; + if (end < (S32)text.length()) + appendAndHighlightText(text, part, style_params); + } + else + { + appendAndHighlightText(new_text, part, style_params); + } +} + +void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) +{ + if (new_text.empty()) + return; + + if(prepend_newline) + appendLineBreakSegment(input_params); + appendTextImpl(new_text,input_params); +} + +void LLTextBase::needsReflow(S32 index) +{ + lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl; + mReflowIndex = llmin(mReflowIndex, index); +} + +void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) +{ + segment_vec_t segments; + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLLineBreakTextSegment(sp, getLength())); + + insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments); +} + +void LLTextBase::appendImageSegment(const LLStyle::Params& style_params) +{ + if(getPlainText()) + { + return; + } + segment_vec_t segments; + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLImageTextSegment(sp, getLength(),*this)); + + insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments); +} + +void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) +{ + segment_vec_t segments; + LLWString widget_wide_text = utf8str_to_wstring(text); + segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size())); + + insertStringNoUndo(getLength(), widget_wide_text, &segments); +} + +void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params) +{ + // Save old state + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + BOOL was_selecting = mIsSelecting; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + BOOL cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + if (mParseHighlights) + { + LLStyle::Params highlight_params(style_params); + + LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); + for (S32 i = 0; i < pieces.size(); i++) + { + LLSD color_llsd = pieces[i]["color"]; + LLColor4 lcolor; + lcolor.setValue(color_llsd); + highlight_params.color = lcolor; + + LLWString wide_text; + wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); + + S32 cur_length = getLength(); + LLStyleConstSP sp(new LLStyle(highlight_params)); + LLTextSegmentPtr segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); + segment_vec_t segments; + segments.push_back(segmentp); + insertStringNoUndo(cur_length, wide_text, &segments); + } + } + else + { + LLWString wide_text; + wide_text = utf8str_to_wstring(new_text); + + segment_vec_t segments; + S32 segment_start = old_length; + S32 segment_end = old_length + wide_text.size(); + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); + + insertStringNoUndo(getLength(), wide_text, &segments); + } + + // Set the cursor and scroll position + if( selection_start != selection_end ) + { + mSelectionStart = selection_start; + mSelectionEnd = selection_end; + + mIsSelecting = was_selecting; + setCursorPos(cursor_pos); + } + else if( cursor_was_at_end ) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params) +{ + if (new_text.empty()) return; + + std::string::size_type start = 0; + std::string::size_type pos = new_text.find("\n",start); + + while(pos!=-1) + { + if(pos!=start) + { + std::string str = std::string(new_text,start,pos-start); + appendAndHighlightTextImpl(str,highlight_part, style_params); + } + appendLineBreakSegment(style_params); + start = pos+1; + pos = new_text.find("\n",start); + } + + std::string str = std::string(new_text,start,new_text.length()-start); + appendAndHighlightTextImpl(str,highlight_part, style_params); +} + + +void LLTextBase::replaceUrl(const std::string &url, + const std::string &label, + const std::string &icon) +{ + // get the full (wide) text for the editor so we can change it + LLWString text = getWText(); + LLWString wlabel = utf8str_to_wstring(label); + bool modified = false; + S32 seg_start = 0; + + // iterate through each segment looking for ones styled as links + segment_set_t::iterator it; + for (it = mSegments.begin(); it != mSegments.end(); ++it) + { + LLTextSegment *seg = *it; + LLStyleConstSP style = seg->getStyle(); + + // update segment start/end length in case we replaced text earlier + S32 seg_length = seg->getEnd() - seg->getStart(); + seg->setStart(seg_start); + seg->setEnd(seg_start + seg_length); + + // if we find a link with our Url, then replace the label + if (style->getLinkHREF() == url) + { + S32 start = seg->getStart(); + S32 end = seg->getEnd(); + text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1); + seg->setEnd(start + wlabel.size()); + modified = true; + } + + // Icon might be updated when more avatar or group info + // becomes available + if (style->isImage() && style->getLinkHREF() == url) + { + LLUIImagePtr image = image_from_icon_name( icon ); + if (image) + { + LLStyle::Params icon_params; + icon_params.image = image; + LLStyleConstSP new_style(new LLStyle(icon_params)); + seg->setStyle(new_style); + modified = true; + } + } + + // work out the character offset for the next segment + seg_start = seg->getEnd(); + } + + // update the editor with the new (wide) text string + if (modified) + { + getViewModel()->setDisplay(text); + deselect(); + setCursorPos(mCursorPos); + needsReflow(); + } +} + + +void LLTextBase::setWText(const LLWString& text) +{ + setText(wstring_to_utf8str(text)); +} + +const LLWString& LLTextBase::getWText() const +{ + return getViewModel()->getDisplay(); +} + +// If round is true, if the position is on the right half of a character, the cursor +// will be put to its right. If round is false, the cursor will always be put to the +// character's left. + +S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const +{ + // Figure out which line we're nearest to. + LLRect visible_region = getVisibleDocumentRect(); + + // binary search for line that starts before local_y + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom()); + + if (line_iter == mLineInfoList.end()) + { + return getLength(); // past the end + } + + S32 pos = getLength(); + S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.mLeft - visible_region.mLeft; + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + line_seg_iter != mSegments.end(); + ++line_seg_iter, line_seg_offset = 0) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + S32 segment_line_start = segmentp->getStart() + line_seg_offset; + S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; + S32 text_width, text_height; + bool newline = segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); + + if(newline) + { + pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + break; + } + + // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line + if (hit_past_end_of_line && local_y - mVisibleTextRect.mBottom + visible_region.mBottom > line_iter->mRect.mTop) + { + pos = segment_line_start; + break; + } + if (local_x < start_x + text_width) // cursor to left of right edge of text + { + // Figure out which character we're nearest to. + S32 offset; + if (!segmentp->canEdit()) + { + S32 segment_width, segment_height; + segmentp->getDimensions(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height); + if (round && local_x - start_x > segment_width / 2) + { + offset = segment_line_length; + } + else + { + offset = 0; + } + } + else + { + offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + } + pos = segment_line_start + offset; + break; + } + else if (hit_past_end_of_line && segmentp->getEnd() > line_iter->mDocIndexEnd - 1) + { + // segment wraps to next line, so just set doc pos to the end of the line + // segment wraps to next line, so just set doc pos to start of next line (represented by mDocIndexEnd) + pos = llmin(getLength(), line_iter->mDocIndexEnd); + break; + } + start_x += text_width; + } + + return pos; +} + +// returns rectangle of insertion caret +// in document coordinate frame from given index into text +LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const +{ + if (mLineInfoList.empty()) + { + return LLRect(); + } + + LLRect doc_rect; + + // clamp pos to valid values + pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); + + // find line that contains cursor + line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); + + doc_rect.mLeft = line_iter->mRect.mLeft; + doc_rect.mBottom = line_iter->mRect.mBottom; + doc_rect.mTop = line_iter->mRect.mTop; + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + segment_set_t::iterator cursor_seg_iter; + S32 cursor_seg_offset; + getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset); + + while(line_seg_iter != mSegments.end()) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + if (line_seg_iter == cursor_seg_iter) + { + // cursor advanced to right based on difference in offset of cursor to start of line + S32 segment_width, segment_height; + segmentp->getDimensions(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); + doc_rect.mLeft += segment_width; + + break; + } + else + { + // add remainder of current text segment to cursor position + S32 segment_width, segment_height; + segmentp->getDimensions(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); + doc_rect.mLeft += segment_width; + // offset will be 0 for all segments after the first + line_seg_offset = 0; + // go to next text segment on this line + ++line_seg_iter; + } + } + + // set rect to 0 width + doc_rect.mRight = doc_rect.mLeft; + + return doc_rect; +} + +LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const +{ + LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (mBorderVisible) + { + content_window_rect.stretch(-1); + } + + LLRect local_rect; + + if (mLineInfoList.empty()) + { + // return default height rect in upper left + local_rect = content_window_rect; + local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->getLineHeight()); + return local_rect; + } + + // get the rect in document coordinates + LLRect doc_rect = getDocRectFromDocIndex(pos); + + // compensate for scrolled, inset view of doc + LLRect scrolled_view_rect = getVisibleDocumentRect(); + local_rect = doc_rect; + local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft, + content_window_rect.mBottom - scrolled_view_rect.mBottom); + + return local_rect; +} + +void LLTextBase::updateCursorXPos() +{ + // reset desired x cursor position + mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft; +} + + +void LLTextBase::startOfLine() +{ + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + setCursorPos(mCursorPos - offset); +} + +void LLTextBase::endOfLine() +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + S32 num_lines = getLineCount(); + if (line + 1 >= num_lines) + { + setCursorPos(getLength()); + } + else + { + setCursorPos( getLineStart(line + 1) - 1 ); + } +} + +void LLTextBase::startOfDoc() +{ + setCursorPos(0); + if (mScroller) + { + mScroller->goToTop(); + } +} + +void LLTextBase::endOfDoc() +{ + setCursorPos(getLength()); + if (mScroller) + { + mScroller->goToBottom(); + } +} + +void LLTextBase::changePage( S32 delta ) +{ + const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10; + if (delta == 0 || !mScroller) return; + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + + if( delta == -1 ) + { + mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + else + if( delta == 1 ) + { + mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + + if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect) + { + // cursor didn't change apparent position, so move to top or bottom of document, respectively + if (delta < 0) + { + startOfDoc(); + } + else + { + endOfDoc(); + } + } + else + { + setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false); + } +} + +// Picks a new cursor position based on the screen size of text being drawn. +void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset ) +{ + setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset); +} + + +void LLTextBase::changeLine( S32 delta ) +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + + S32 new_line = line; + if( (delta < 0) && (line > 0 ) ) + { + new_line = line - 1; + } + else if( (delta > 0) && (line < (getLineCount() - 1)) ) + { + new_line = line + 1; + } + + LLRect visible_region = getVisibleDocumentRect(); + + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); + setCursorPos(new_cursor_pos, true); +} + +bool LLTextBase::scrolledToStart() +{ + return mScroller->isAtTop(); +} + +bool LLTextBase::scrolledToEnd() +{ + return mScroller->isAtBottom(); +} + + +bool LLTextBase::setCursor(S32 row, S32 column) +{ + if (0 <= row && row < (S32)mLineInfoList.size()) + { + S32 doc_pos = mLineInfoList[row].mDocIndexStart; + column = llclamp(column, 0, mLineInfoList[row].mDocIndexEnd - mLineInfoList[row].mDocIndexStart - 1); + doc_pos += column; + updateCursorXPos(); + + return setCursorPos(doc_pos); + } + return false; +} + + +bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset) +{ + S32 new_cursor_pos = cursor_pos; + if (new_cursor_pos != mCursorPos) + { + new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos); + } + + mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength()); + needsScroll(); + if (!keep_cursor_offset) + updateCursorXPos(); + // did we get requested position? + return new_cursor_pos == cursor_pos; +} + +// constraint cursor to editable segments of document +S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) +{ + segment_set_t::iterator segment_iter; + S32 offset; + getSegmentAndOffset(index, &segment_iter, &offset); + if (segment_iter == mSegments.end()) + { + return 0; + } + + LLTextSegmentPtr segmentp = *segment_iter; + + if (segmentp->canEdit()) + { + return segmentp->getStart() + offset; + } + else if (segmentp->getStart() < index && index < segmentp->getEnd()) + { + // bias towards document end + if (increasing_direction) + { + return segmentp->getEnd(); + } + // bias towards document start + else + { + return segmentp->getStart(); + } + } + else + { + return index; + } +} + +void LLTextBase::updateRects() +{ + if (mLineInfoList.empty()) + { + mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); + } + else + { + mTextBoundingRect = mLineInfoList.begin()->mRect; + for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); + line_iter != mLineInfoList.end(); + ++line_iter) + { + mTextBoundingRect.unionWith(line_iter->mRect); + } + + mTextBoundingRect.mTop += mVPad; + // subtract a pixel off the bottom to deal with rounding errors in measuring font height + mTextBoundingRect.mBottom -= 1; + + S32 delta_pos = -mTextBoundingRect.mBottom; + // move line segments to fit new document rect + for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) + { + it->mRect.translate(0, delta_pos); + } + mTextBoundingRect.translate(0, delta_pos); + } + + // update document container dimensions according to text contents + LLRect doc_rect = mTextBoundingRect; + // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mLeft = 0; + + // allow horizontal scrolling? + // if so, use entire width of text contents + // otherwise, stop at width of mVisibleTextRect + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + + mDocumentView->setShape(doc_rect); + + //update mVisibleTextRect *after* mDocumentView has been resized + // so that scrollbars are added if document needs to scroll + // since mVisibleTextRect does not include scrollbars + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + //FIXME: replace border with image? + if (mBorderVisible) + { + mVisibleTextRect.stretch(-1); + } + if (mVisibleTextRect != old_text_rect) + { + needsReflow(); + } + + // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + mDocumentView->setShape(doc_rect); +} + + +void LLTextBase::startSelection() +{ + if( !mIsSelecting ) + { + mIsSelecting = TRUE; + mSelectionStart = mCursorPos; + mSelectionEnd = mCursorPos; + } +} + +void LLTextBase::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = FALSE; + mSelectionEnd = mCursorPos; + } +} + +// get portion of document that is visible in text editor +LLRect LLTextBase::getVisibleDocumentRect() const +{ + if (mScroller) + { + return mScroller->getVisibleContentRect(); + } + else + { + // entire document rect is visible when not scrolling + // but offset according to height of widget + LLRect doc_rect = mDocumentView->getLocalRect(); + doc_rect.mLeft -= mDocumentView->getRect().mLeft; + // adjust for height of text above widget baseline + doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight(); + return doc_rect; + } +} + +// +// LLTextSegment +// + +LLTextSegment::~LLTextSegment() +{} + +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; return false;} +S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; } +S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; } +void LLTextSegment::updateLayout(const LLTextBase& editor) {} +F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { return draw_rect.mLeft; } +bool LLTextSegment::canEdit() const { return false; } +void LLTextSegment::unlinkFromDocument(LLTextBase*) {} +void LLTextSegment::linkToDocument(LLTextBase*) {} +const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; } +//void LLTextSegment::setColor(const LLColor4 &color) {} +LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } +void LLTextSegment::setStyle(LLStyleConstSP style) {} +void LLTextSegment::setToken( LLKeywordToken* token ) {} +LLKeywordToken* LLTextSegment::getToken() const { return NULL; } +void LLTextSegment::setToolTip( const std::string &msg ) {} +void LLTextSegment::dump() const {} +BOOL LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return FALSE; } +BOOL LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return FALSE; } +BOOL LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return FALSE; } +std::string LLTextSegment::getName() const { return ""; } +void LLTextSegment::onMouseCaptureLost() {} +void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} +void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} +BOOL LLTextSegment::hasMouseCapture() { return FALSE; } + +// +// LLNormalTextSegment +// + +LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) +: LLTextSegment(start, end), + mStyle( style ), + mToken(NULL), + mEditor(editor) +{ + mFontHeight = llceil(mStyle->getFont()->getLineHeight()); + + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); + } +} + +LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) +: LLTextSegment(start, end), + mToken(NULL), + mEditor(editor) +{ + mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); + + mFontHeight = llceil(mStyle->getFont()->getLineHeight()); +} + +LLNormalTextSegment::~LLNormalTextSegment() +{ + mImageLoadedConnection.disconnect(); +} + + +F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) +{ + if( end - start > 0 ) + { + return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); + } + return draw_rect.mLeft; +} + +// Draws a single text segment, reversing the color for selection if needed. +F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRect rect) +{ + F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; + + const LLWString &text = mEditor.getWText(); + + F32 right_x = rect.mLeft; + if (!mStyle->isVisible()) + { + return right_x; + } + + const LLFontGL* font = mStyle->getFont(); + + LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; + + if( selection_start > seg_start ) + { + // Draw normally + S32 start = seg_start; + S32 end = llmin( selection_start, seg_end ); + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses()); + } + rect.mLeft = (S32)ceil(right_x); + + if( (selection_start < seg_end) && (selection_end > seg_start) ) + { + // Draw reversed + S32 start = llmax( selection_start, seg_start ); + S32 end = llmin( selection_end, seg_end ); + S32 length = end - start; + + font->render(text, start, + rect, + LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, + &right_x, + mEditor.getUseEllipses()); + } + rect.mLeft = (S32)ceil(right_x); + if( selection_end < seg_end ) + { + // Draw normally + S32 start = llmax( selection_end, seg_start ); + S32 end = seg_end; + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses()); + } + return right_x; +} + +BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUI::getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } + } + return FALSE; +} + +BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); + return TRUE; + } + } + return FALSE; +} + +BOOL LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + // eat mouse down event on hyperlinks, so we get the mouse up + return TRUE; + } + } + + return FALSE; +} + +BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUrlAction::clickAction(getStyle()->getLinkHREF()); + return TRUE; + } + } + + return FALSE; +} + +BOOL LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string msg; + // do we have a tooltip for a loaded keyword (for script editor)? + if (mToken && !mToken->getToolTip().empty()) + { + const LLWString& wmsg = mToken->getToolTip(); + LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg)); + return TRUE; + } + // or do we have an explicitly set tooltip (e.g., for Urls) + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return TRUE; + } + + return FALSE; +} + +void LLNormalTextSegment::setToolTip(const std::string& tooltip) +{ + // we cannot replace a keyword tooltip that's loaded from a file + if (mToken) + { + llwarns << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << llendl; + return; + } + mTooltip = tooltip; +} + +bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + height = 0; + width = 0; + if (num_chars > 0) + { + height = mFontHeight; + const LLWString &text = mEditor.getWText(); + // if last character is a newline, then return true, forcing line break + width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); + } + return false; +} + +S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const +{ + const LLWString &text = mEditor.getWText(); + return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, + (F32)segment_local_x_coord, + F32_MAX, + num_chars, + round); +} + +S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const +{ + const LLWString &text = mEditor.getWText(); + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + num_pixels = llmax(0, num_pixels - image->getWidth()); + } + + S32 last_char = mEnd; + + // set max characters to length of segment, or to first newline + max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); + + // if no character yet displayed on this line, don't require word wrapping since + // we can just move to the next line, otherwise insist on it so we make forward progress + LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) + ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE + : LLFontGL::ONLY_WORD_BOUNDARIES; + S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, + (F32)num_pixels, + max_chars, + word_wrap_style); + + if (num_chars == 0 + && line_offset == 0 + && max_chars > 0) + { + // If at the beginning of a line, and a single character won't fit, draw it anyway + num_chars = 1; + } + + // include *either* the EOF or newline character in this run of text + // but not both + S32 last_char_in_run = mStart + segment_offset + num_chars; + // check length first to avoid indexing off end of string + if (last_char_in_run < mEnd + && (last_char_in_run >= mEditor.getLength() )) + { + num_chars++; + } + return num_chars; +} + +void LLNormalTextSegment::dump() const +{ + llinfos << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + llendl; +} + + +// +// LLInlineViewSegment +// + +LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) +: LLTextSegment(start, end), + mView(p.view), + mForceNewLine(p.force_newline), + mLeftPad(p.left_pad), + mRightPad(p.right_pad), + mTopPad(p.top_pad), + mBottomPad(p.bottom_pad) +{ +} + +LLInlineViewSegment::~LLInlineViewSegment() +{ + mView->die(); +} + +bool LLInlineViewSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + if (first_char == 0 && num_chars == 0) + { + // we didn't fit on a line, the widget will fall on the next line + // so dimensions here are 0 + width = 0; + height = 0; + } + else + { + width = mLeftPad + mRightPad + mView->getRect().getWidth(); + height = mBottomPad + mTopPad + mView->getRect().getHeight(); + } + + return false; +} + +S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const +{ + // if putting a widget anywhere but at the beginning of a line + // and the widget doesn't fit or mForceNewLine is true + // then return 0 chars for that line, and all characters for the next + if (line_offset != 0 + && (mForceNewLine || num_pixels < mView->getRect().getWidth())) + { + return 0; + } + else + { + return mEnd - mStart; + } +} + +void LLInlineViewSegment::updateLayout(const LLTextBase& editor) +{ + LLRect start_rect = editor.getDocRectFromDocIndex(mStart); + mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad); +} + +F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) +{ + // return padded width of widget + // widget is actually drawn during mDocumentView's draw() + return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad); +} + +void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor) +{ + editor->removeDocumentChild(mView); +} + +void LLInlineViewSegment::linkToDocument(LLTextBase* editor) +{ + editor->addDocumentChild(mView); +} + +LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) +{ + LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); + + mFontHeight = llceil(s->getFont()->getLineHeight()); +} +LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) +{ + mFontHeight = llceil(style->getFont()->getLineHeight()); +} +LLLineBreakTextSegment::~LLLineBreakTextSegment() +{ +} +bool LLLineBreakTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + width = 0; + height = mFontHeight; + + return true; +} +S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const +{ + return 1; +} +F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) +{ + return draw_rect.mLeft; +} + +LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor) + :LLTextSegment(pos,pos+1) + ,mStyle( style ) + ,mEditor(editor) +{ +} + +LLImageTextSegment::~LLImageTextSegment() +{ +} + +static const S32 IMAGE_HPAD = 3; + +bool LLImageTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + width = 0; + height = llceil(mStyle->getFont()->getLineHeight());; + + LLUIImagePtr image = mStyle->getImage(); + if( num_chars>0 && image.notNull()) + { + width += image->getWidth() + IMAGE_HPAD; + height = llmax(height, image->getHeight() + IMAGE_HPAD ); + } + return false; +} + +S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const +{ + LLUIImagePtr image = mStyle->getImage(); + S32 image_width = image->getWidth(); + if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) + { + return 1; + } + return 0; +} + +F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) +{ + if ( (start >= 0) && (end <= mEnd - mStart)) + { + LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; + LLUIImagePtr image = mStyle->getImage(); + S32 style_image_height = image->getHeight(); + S32 style_image_width = image->getWidth(); + // Text is drawn from the top of the draw_rect downward + + S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); + // Align image to center of draw rect + S32 image_bottom = text_center - (style_image_height / 2); + image->draw(draw_rect.mLeft, image_bottom, + style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; + } + return 0.0; +} + -- cgit v1.2.3 From 17bf41415bb9e347137ae01e7784b75910c0f146 Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Tue, 27 Jul 2010 16:35:37 -0700 Subject: merge fix --- indra/llui/llnotifications.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 4fa4da3f01..768a014081 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1496,6 +1496,7 @@ void LLPostponedNotification::lookupName(LLPostponedNotification* thiz, gCacheName->getGroup(id, boost::bind(&LLPostponedNotification::onGroupNameCache, thiz, _1, _2, _3)); + } else { LLAvatarNameCache::get(id, -- cgit v1.2.3 From ae623c8068145f968f09ccf9bbdd90e993ca4283 Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Thu, 16 Sep 2010 09:38:05 -0700 Subject: Set display name field now clamps to 31 unicode characters Added ability to set max length in line editors by characters in addition to bytes left other widgets (comboboxes/spinners) using the bytes, but it can easily be changed over reviewed by Richard --- indra/llui/llcombobox.cpp | 2 +- indra/llui/lllineeditor.cpp | 37 +++++++++++++++++++++++++++++++++++-- indra/llui/lllineeditor.h | 14 ++++++++++++-- indra/llui/llmultisliderctrl.cpp | 2 +- indra/llui/llspinctrl.cpp | 2 +- 5 files changed, 50 insertions(+), 7 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index f91b4ad025..e749ff9daf 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -484,7 +484,7 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) LLLineEditor::Params params = p.combo_editor; params.rect(text_entry_rect); params.default_text(LLStringUtil::null); - params.max_length_bytes(mMaxChars); + params.max_length.bytes(mMaxChars); params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); params.commit_on_focus_lost(false); diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 2759167d04..3646228097 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -78,7 +78,7 @@ template class LLLineEditor* LLView::getChild( // LLLineEditor::Params::Params() -: max_length_bytes("max_length", 254), +: max_length(""), keystroke_callback("keystroke_callback"), prevalidate_callback("prevalidate_callback"), background_image("background_image"), @@ -108,7 +108,8 @@ LLLineEditor::Params::Params() LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) : LLUICtrl(p), - mMaxLengthBytes(p.max_length_bytes), + mMaxLengthBytes(p.max_length.bytes), + mMaxLengthChars(p.max_length.chars), mCursorPos( 0 ), mScrollHPos( 0 ), mTextPadLeft(p.text_pad_left), @@ -313,6 +314,12 @@ void LLLineEditor::setMaxTextLength(S32 max_text_length) mMaxLengthBytes = max_len; } +void LLLineEditor::setMaxTextChars(S32 max_text_chars) +{ + S32 max_chars = llmax(0, max_text_chars); + mMaxLengthChars = max_chars; +} + void LLLineEditor::getTextPadding(S32 *left, S32 *right) { *left = mTextPadLeft; @@ -358,6 +365,16 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) } mText.assign(truncated_utf8); + if (mMaxLengthChars) + { + LLWString truncated_wstring = utf8str_to_wstring(truncated_utf8); + if (truncated_wstring.size() > (U32)mMaxLengthChars) + { + truncated_wstring = truncated_wstring.substr(0, mMaxLengthChars); + } + mText.assign(wstring_to_utf8str(truncated_wstring)); + } + if (all_selected) { // ...keep whole thing selected @@ -798,6 +815,7 @@ void LLLineEditor::addChar(const llwchar uni_char) } S32 cur_bytes = mText.getString().size(); + S32 new_bytes = wchar_utf8_length(new_c); BOOL allow_char = TRUE; @@ -807,6 +825,14 @@ void LLLineEditor::addChar(const llwchar uni_char) { allow_char = FALSE; } + else if (mMaxLengthChars) + { + S32 wide_chars = mText.getWString().size(); + if ((wide_chars + 1) > mMaxLengthChars) + { + allow_char = FALSE; + } + } if (allow_char) { @@ -1107,6 +1133,13 @@ void LLLineEditor::pasteHelper(bool is_primary) clean_string = clean_string.substr(0, wchars_that_fit); LLUI::reportBadKeystroke(); } + + U32 available_chars = mMaxLengthChars - mText.getWString().size(); + + if (available_chars < clean_string.size()) + { + clean_string = clean_string.substr(0, available_chars); + } mText.insert(getCursor(), clean_string); setCursor( getCursor() + (S32)clean_string.length() ); diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 76d0187712..b988bc412e 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -59,11 +59,19 @@ public: typedef boost::function keystroke_callback_t; + struct MaxLength : public LLInitParam::Choice + { + Alternative bytes, chars; + + MaxLength() : bytes("max_length_bytes", 254), + chars("max_length_chars", 0) + {} + }; + struct Params : public LLInitParam::Block { Optional default_text; - Optional max_length_bytes; - + Optional max_length; Optional keystroke_callback; Optional prevalidate_callback; @@ -214,6 +222,7 @@ public: void setKeystrokeCallback(callback_t callback, void* user_data); void setMaxTextLength(S32 max_text_length); + void setMaxTextChars(S32 max_text_chars); // Manipulate left and right padding for text void getTextPadding(S32 *left, S32 *right); void setTextPadding(S32 left, S32 right); @@ -277,6 +286,7 @@ protected: LLViewBorder* mBorder; const LLFontGL* mGLFont; S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes + S32 mMaxLengthChars; // Maximum number of characters in the string S32 mCursorPos; // I-beam is just after the mCursorPos-th character. S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. LLFrameTimer mScrollTimer; diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index bd65625f53..91e5b6b9de 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -130,7 +130,7 @@ LLMultiSliderCtrl::LLMultiSliderCtrl(const LLMultiSliderCtrl::Params& p) params.name("MultiSliderCtrl Editor"); params.rect(text_rect); params.font(p.font); - params.max_length_bytes(MAX_STRING_LENGTH); + params.max_length.bytes(MAX_STRING_LENGTH); params.commit_callback.function(LLMultiSliderCtrl::onEditorCommit); params.prevalidate_callback(&LLTextValidate::validateFloat); params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 9decfa0b25..6b4e9cf923 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -119,7 +119,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) { params.font(p.font); } - params.max_length_bytes(MAX_STRING_LENGTH); + params.max_length.bytes(MAX_STRING_LENGTH); params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); if( mPrecision>0 )//should accept float numbers -- cgit v1.2.3 From 97f11e0e39a10156597c0f82c8a1bead17621b4c Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Wed, 29 Sep 2010 10:56:47 -0700 Subject: added max_length_chars to notification params --- indra/llui/llnotifications.cpp | 1 + indra/llui/llnotifications.h | 1 + 2 files changed, 2 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 5ca2cc1cc4..929f93dd85 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -81,6 +81,7 @@ LLNotificationForm::FormButton::FormButton() LLNotificationForm::FormInput::FormInput() : type("type"), + max_length_chars("max_length_chars"), width("width", 0) {} diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index f84d9e3921..e52875665e 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -194,6 +194,7 @@ public: { Mandatory type; Optional width; + Optional max_length_chars; FormInput(); }; -- cgit v1.2.3 From a4617ca0ea7b0125ca20e732027a0bc3f6f394ed Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Wed, 29 Sep 2010 16:17:35 -0700 Subject: DN-114 Paste function into text boxes not working in Viewer --- indra/llui/lllineeditor.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 48c1b9fa6b..5f5fe851bb 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1134,13 +1134,18 @@ void LLLineEditor::pasteHelper(bool is_primary) LLUI::reportBadKeystroke(); } - U32 available_chars = mMaxLengthChars - mText.getWString().size(); - - if (available_chars < clean_string.size()) + if (mMaxLengthChars) { - clean_string = clean_string.substr(0, available_chars); + U32 available_chars = mMaxLengthChars - mText.getWString().size(); + + if (available_chars < clean_string.size()) + { + clean_string = clean_string.substr(0, available_chars); + } + + LLUI::reportBadKeystroke(); } - + mText.insert(getCursor(), clean_string); setCursor( getCursor() + (S32)clean_string.length() ); deselect(); -- cgit v1.2.3 From 7ffc9b06c820d70724af2aef361ff9964e584e73 Mon Sep 17 00:00:00 2001 From: Leyla Farazha Date: Fri, 8 Oct 2010 16:56:02 -0700 Subject: Created a floater for setting display name --- indra/llui/llnotifications.cpp | 3 ++- indra/llui/llnotifications.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 929f93dd85..5ce04408a1 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -82,7 +82,8 @@ LLNotificationForm::FormButton::FormButton() LLNotificationForm::FormInput::FormInput() : type("type"), max_length_chars("max_length_chars"), - width("width", 0) + width("width", 0), + value("value") {} LLNotificationForm::FormElement::FormElement() diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index e52875665e..3ae3f158e4 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -196,6 +196,7 @@ public: Optional width; Optional max_length_chars; + Optional value; FormInput(); }; -- cgit v1.2.3 From 401fdbcfac1d7e8b6b91c958a23ec6705dfe4e07 Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Wed, 13 Oct 2010 00:54:57 +0300 Subject: STORM-294 FIXED keyboard navigation in Favorites bar overflow menu. The menu items can now be scrolled cyclically with a keyboard even if not all items are visible at once. --- indra/llui/llmenugl.cpp | 205 +++++++++++++++++++++++++++++++----------------- indra/llui/llmenugl.h | 12 ++- 2 files changed, 142 insertions(+), 75 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 6d590cf54e..a6cf86d9b8 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -1848,89 +1848,104 @@ BOOL LLMenuGL::isOpen() } } -void LLMenuGL::scrollItemsUp() + + +bool LLMenuGL::scrollItems(EScrollingDirection direction) { - // Slowing down the items scrolling when arrow button is held + // Slowing down items scrolling when arrow button is held if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) { mScrollItemsTimer.setTimerExpirySec(.033f); } else { - return; + return false; } - item_list_t::iterator cur_item_iter; - item_list_t::iterator prev_item_iter; - for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + switch (direction) { - if( (*cur_item_iter) == mFirstVisibleItem) + case SD_UP: + { + item_list_t::iterator cur_item_iter; + item_list_t::iterator prev_item_iter; + for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) { - break; + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + if ((*cur_item_iter)->getVisible()) + { + prev_item_iter = cur_item_iter; + } } - if ((*cur_item_iter)->getVisible()) + + if ((*prev_item_iter)->getVisible()) { - prev_item_iter = cur_item_iter; + mFirstVisibleItem = *prev_item_iter; } + break; } - - if ((*prev_item_iter)->getVisible()) - { - mFirstVisibleItem = *prev_item_iter; - } - - mNeedsArrange = TRUE; - arrangeAndClear(); -} - -void LLMenuGL::scrollItemsDown() -{ - // Slowing down the items scrolling when arrow button is held - if (mScrollItemsTimer.hasExpired()) - { - mScrollItemsTimer.setTimerExpirySec(.033f); - } - else - { - return; - } - - if (NULL == mFirstVisibleItem) - { - mFirstVisibleItem = *mItems.begin(); - } - - item_list_t::iterator cur_item_iter; - - for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + case SD_DOWN: { - if( (*cur_item_iter) == mFirstVisibleItem) + if (NULL == mFirstVisibleItem) { - break; + mFirstVisibleItem = *mItems.begin(); } - } - item_list_t::iterator next_item_iter; + item_list_t::iterator cur_item_iter; - if (cur_item_iter != mItems.end()) - { - for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) { - if( (*next_item_iter)->getVisible()) + if( (*cur_item_iter) == mFirstVisibleItem) { break; } } - - if (next_item_iter != mItems.end() && - (*next_item_iter)->getVisible()) + + item_list_t::iterator next_item_iter; + + if (cur_item_iter != mItems.end()) { - mFirstVisibleItem = *next_item_iter; + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + { + if( (*next_item_iter)->getVisible()) + { + break; + } + } + + if (next_item_iter != mItems.end() && + (*next_item_iter)->getVisible()) + { + mFirstVisibleItem = *next_item_iter; + } } + break; } - + case SD_BEGIN: + { + mFirstVisibleItem = *mItems.begin(); + break; + } + case SD_END: + { + item_list_t::reverse_iterator first_visible_item_iter = mItems.rend(); + + // Advance by mMaxScrollableItems back from the end of the list + // to make the last item visible. + std::advance(first_visible_item_iter, mMaxScrollableItems); + mFirstVisibleItem = *first_visible_item_iter; + break; + } + default: + llwarns << "Unknown scrolling direction: " << direction << llendl; + } + mNeedsArrange = TRUE; arrangeAndClear(); + + return true; } // rearrange the child rects so they fit the shape of the menu. @@ -2162,7 +2177,7 @@ void LLMenuGL::arrange( void ) LLMenuScrollItem::Params item_params; item_params.name(ARROW_UP); item_params.arrow_type(LLMenuScrollItem::ARROW_UP); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItemsUp, this)); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP)); mArrowUpItem = LLUICtrlFactory::create(item_params); LLUICtrl::addChild(mArrowUpItem); @@ -2173,7 +2188,7 @@ void LLMenuGL::arrange( void ) LLMenuScrollItem::Params item_params; item_params.name(ARROW_DOWN); item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItemsDown, this)); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN)); mArrowDownItem = LLUICtrlFactory::create(item_params); LLUICtrl::addChild(mArrowDownItem); @@ -2603,14 +2618,8 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa ((LLFloater*)getParent())->setFocus(TRUE); } - item_list_t::iterator cur_item_iter; - for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); ++cur_item_iter) - { - if( (*cur_item_iter) == cur_item) - { - break; - } - } + // Current item position in the items list + item_list_t::iterator cur_item_iter = std::find(mItems.begin(), mItems.end(), cur_item); item_list_t::iterator next_item_iter; if (cur_item_iter == mItems.end()) @@ -2621,9 +2630,37 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa { next_item_iter = cur_item_iter; next_item_iter++; + + // First visible item position in the items list + item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem); + if (next_item_iter == mItems.end()) { next_item_iter = mItems.begin(); + + // If current item is the last in the list, the menu is scrolled to the beginning + // and the first item is highlighted. + if (mScrollable && !scrollItems(SD_BEGIN)) + { + return NULL; + } + } + // If current item is the last visible, the menu is scrolled one item down + // and the next item is highlighted. + else if (mScrollable && + (U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems) + { + // Call highlightNextItem() recursively only if the menu was successfully scrolled down. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_DOWN)) + { + return highlightNextItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -2681,14 +2718,8 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa ((LLFloater*)getParent())->setFocus(TRUE); } - item_list_t::reverse_iterator cur_item_iter; - for (cur_item_iter = mItems.rbegin(); cur_item_iter != mItems.rend(); ++cur_item_iter) - { - if( (*cur_item_iter) == cur_item) - { - break; - } - } + // Current item reverse position from the end of the list + item_list_t::reverse_iterator cur_item_iter = std::find(mItems.rbegin(), mItems.rend(), cur_item); item_list_t::reverse_iterator prev_item_iter; if (cur_item_iter == mItems.rend()) @@ -2699,9 +2730,37 @@ LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, BOOL skip_disa { prev_item_iter = cur_item_iter; prev_item_iter++; + + // First visible item reverse position in the items list + item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem); + if (prev_item_iter == mItems.rend()) { prev_item_iter = mItems.rbegin(); + + // If current item is the first in the list, the menu is scrolled to the end + // and the last item is highlighted. + if (mScrollable && !scrollItems(SD_END)) + { + return NULL; + } + } + // If current item is the first visible, the menu is scrolled one item up + // and the previous item is highlighted. + else if (mScrollable && + std::distance(first_visible_item_iter, cur_item_iter) <= 0) + { + // Call highlightNextItem() only if the menu was successfully scrolled up. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_UP)) + { + return highlightPrevItem(cur_item, skip_disabled); + } + else + { + return NULL; + } } } @@ -2872,12 +2931,12 @@ BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) if( clicks > 0 ) { while( clicks-- ) - scrollItemsDown(); + scrollItems(SD_DOWN); } else { while( clicks++ ) - scrollItemsUp(); + scrollItems(SD_UP); } return TRUE; diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 19b738312e..35544402f4 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -397,6 +397,15 @@ public: static const std::string ARROW_UP; static const std::string ARROW_DOWN; + // for scrollable menus + typedef enum e_scrolling_direction + { + SD_UP = 0, + SD_DOWN = 1, + SD_BEGIN = 2, + SD_END = 3 + } EScrollingDirection; + protected: LLMenuGL(const LLMenuGL::Params& p); friend class LLUICtrlFactory; @@ -503,8 +512,7 @@ public: S32 getShortcutPad() { return mShortcutPad; } - void scrollItemsUp(); - void scrollItemsDown(); + bool scrollItems(EScrollingDirection direction); BOOL isScrollable() const { return mScrollable; } static class LLMenuHolderGL* sMenuContainer; -- cgit v1.2.3 From c12c60df4a28b3cb91870ae0666eb6b3422ff96b Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Fri, 15 Oct 2010 00:10:55 +0300 Subject: STORM-263 FIXED popup menu of Cog button in lower-left of sidebar panel closing on second click - Changed type of gear menu buttons from LLButton to LLMenuButton in all sidebar panels where gear menu button is used. - Added setMenuPosition(), setMenu() and updateMenuOrigin() to the LLMenuButton. - Moved actions common for displaying a context menu to LLMenuButton::toggleMenu(). - In all sidebar panels where LLButton was replaced with LLMenuButton the following steps were taken: 1. setting gearMenu and its position relative to the menuButton with LLMenuButton::setMenu() 2. setting mouse down callback for the menuButton if needed. 3. calculating the menu origin point with LLMenuButton::updateMenuOrigin() in mouse down callback --- indra/llui/llmenubutton.cpp | 108 ++++++++++++++++++++++++++++---------------- indra/llui/llmenubutton.h | 26 +++++++++-- 2 files changed, 92 insertions(+), 42 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llmenubutton.cpp b/indra/llui/llmenubutton.cpp index 3df05f4d3f..c1b5efaa72 100644 --- a/indra/llui/llmenubutton.cpp +++ b/indra/llui/llmenubutton.cpp @@ -45,7 +45,8 @@ LLMenuButton::Params::Params() LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) : LLButton(p), mMenu(NULL), - mMenuVisibleLastFrame(false) + mMenuVisibleLastFrame(false), + mMenuPosition(MP_BOTTOM_LEFT) { std::string menu_filename = p.menu_filename; @@ -57,38 +58,51 @@ LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) llwarns << "Error loading menu_button menu" << llendl; } } + + updateMenuOrigin(); } -void LLMenuButton::toggleMenu() +boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) { - if(!mMenu) - return; + return LLUICtrl::setMouseDownCallback(cb); +} - if (mMenu->getVisible() || mMenuVisibleLastFrame) - { - mMenu->setVisible(FALSE); - } - else +void LLMenuButton::hideMenu() +{ + if(!mMenu) return; + mMenu->setVisible(FALSE); +} + +void LLMenuButton::setMenu(LLMenuGL* menu, EMenuPosition position /*MP_TOP_LEFT*/) +{ + mMenu = menu; + mMenuPosition = position; +} + +void LLMenuButton::draw() +{ + //we save this off so next frame when we try to close it by + //button click, and it hides menus before we get to it, we know + mMenuVisibleLastFrame = mMenu && mMenu->getVisible(); + + if (mMenuVisibleLastFrame) { - LLRect rect = getRect(); - //mMenu->needsArrange(); //so it recalculates the visible elements - LLMenuGL::showPopup(getParent(), mMenu, rect.mLeft, rect.mBottom); + setForcePressedState(true); } -} + LLButton::draw(); -void LLMenuButton::hideMenu() -{ - if(!mMenu) - return; - mMenu->setVisible(FALSE); + setForcePressedState(false); } - BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) { if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) { + // *HACK: We emit the mouse down signal to fire the callback bound to the + // menu emerging event before actually displaying the menu. See STORM-263. + LLUICtrl::handleMouseDown(-1, -1, MASK_NONE); + toggleMenu(); return TRUE; } @@ -104,34 +118,52 @@ BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) { - if (hasTabStop() && !getIsChrome()) - { - setFocus(TRUE); - } - + LLButton::handleMouseDown(x, y, mask); toggleMenu(); - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } - return TRUE; } -void LLMenuButton::draw() +void LLMenuButton::toggleMenu() { - //we save this off so next frame when we try to close it by - //button click, and it hides menus before we get to it, we know - mMenuVisibleLastFrame = mMenu && mMenu->getVisible(); - - if (mMenuVisibleLastFrame) + if(!mMenu) return; + + if (mMenu->getVisible() || mMenuVisibleLastFrame) { - setForcePressedState(true); + mMenu->setVisible(FALSE); } + else + { + mMenu->buildDrawLabels(); + mMenu->arrangeAndClear(); + mMenu->updateParent(LLMenuGL::sMenuContainer); - LLButton::draw(); + updateMenuOrigin(); - setForcePressedState(false); + //mMenu->needsArrange(); //so it recalculates the visible elements + LLMenuGL::showPopup(getParent(), mMenu, mX, mY); + } } +void LLMenuButton::updateMenuOrigin() +{ + if (!mMenu) return; + + LLRect rect = getRect(); + + switch (mMenuPosition) + { + case MP_TOP_LEFT: + { + mX = rect.mLeft; + mY = rect.mTop + mMenu->getRect().getHeight(); + break; + } + case MP_BOTTOM_LEFT: + { + mX = rect.mLeft; + mY = rect.mBottom; + break; + } + } +} diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 81ca0e047c..81c3592b16 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -42,22 +42,40 @@ public: Optional menu_filename; Params(); - }; + }; + + typedef enum e_menu_position + { + MP_TOP_LEFT, + MP_BOTTOM_LEFT + } EMenuPosition; - void toggleMenu(); + boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); + /*virtual*/ void draw(); /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); + void hideMenu(); + LLMenuGL* getMenu() { return mMenu; } + void setMenu(LLMenuGL* menu, EMenuPosition position = MP_TOP_LEFT); + + void setMenuPosition(EMenuPosition position) { mMenuPosition = position; } protected: friend class LLUICtrlFactory; LLMenuButton(const Params&); + void toggleMenu(); + void updateMenuOrigin(); + private: - LLMenuGL* mMenu; - bool mMenuVisibleLastFrame; + LLMenuGL* mMenu; + bool mMenuVisibleLastFrame; + EMenuPosition mMenuPosition; + S32 mX; + S32 mY; }; -- cgit v1.2.3 From d97355a19f3b88512105965cfd752956f0230b41 Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Tue, 19 Oct 2010 19:55:19 +0300 Subject: STORM-390 FIXED "Place Profile" appeared instead of "Resident Profile" after clicking on user name in a nearby chat toast. Now clicking an avatar name opens avatar profile; clicking an object name opens object inspector. This change rolls back the fix of STORM-358. --- indra/llui/lltextbase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 9adeddca99..758df418e8 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1622,7 +1622,7 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para style_params.fillFrom(getDefaultStyleParams()); S32 part = (S32)LLTextParser::WHOLE; - if(mParseHTML) + if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { S32 start=0,end=0; LLUrlMatch match; -- cgit v1.2.3 From 07353619d0069acc9933bed2772e0e6925636efb Mon Sep 17 00:00:00 2001 From: Vadim ProductEngine Date: Fri, 22 Oct 2010 19:39:38 +0300 Subject: STORM-297 FIXED Fixed "" text appearing in confirmation message if there is "<" symbol in Landmarks name. Modified the "..." clause parsing regexp to allow "<" in the middle. --- indra/llui/llurlentry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index f58c07754f..f49dfec82b 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -972,7 +972,7 @@ std::string LLUrlEntryWorldMap::getLocation(const std::string &url) const // LLUrlEntryNoLink::LLUrlEntryNoLink() { - mPattern = boost::regex("[^<]*", + mPattern = boost::regex(".*", boost::regex::perl|boost::regex::icase); } -- cgit v1.2.3 From 54da19fdcfa3edadbc58a1f55a3503082e66f16e Mon Sep 17 00:00:00 2001 From: Seth ProductEngine Date: Fri, 22 Oct 2010 02:08:26 +0300 Subject: STORM-426 FIXED Menu button no longer looked pressed while its menu is displayed by another control. - LLMenuGL in menu button replaced by LLToggleableMenu that handles visibility change upon clicks inside specific button rect. - Added visibility change signal to LLToggleableMenu to update menu button pressed state. - Added using menu handle in LLMenuButton. --- indra/llui/llmenubutton.cpp | 100 ++++++++++++++++++++++++++-------------- indra/llui/llmenubutton.h | 19 ++++---- indra/llui/lltoggleablemenu.cpp | 18 ++++++++ indra/llui/lltoggleablemenu.h | 5 ++ 4 files changed, 98 insertions(+), 44 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llmenubutton.cpp b/indra/llui/llmenubutton.cpp index c1b5efaa72..ac568a83e4 100644 --- a/indra/llui/llmenubutton.cpp +++ b/indra/llui/llmenubutton.cpp @@ -29,7 +29,7 @@ #include "llmenubutton.h" // Linden library includes -#include "llmenugl.h" +#include "lltoggleablemenu.h" #include "llstring.h" #include "v4color.h" @@ -44,22 +44,26 @@ LLMenuButton::Params::Params() LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) : LLButton(p), - mMenu(NULL), - mMenuVisibleLastFrame(false), + mIsMenuShown(false), mMenuPosition(MP_BOTTOM_LEFT) { std::string menu_filename = p.menu_filename; if (!menu_filename.empty()) { - mMenu = LLUICtrlFactory::getInstance()->createFromFile(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - if (!mMenu) + LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (!menu) { llwarns << "Error loading menu_button menu" << llendl; + return; } - } - updateMenuOrigin(); + menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); + + mMenuHandle = menu->getHandle(); + + updateMenuOrigin(); + } } boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) @@ -69,34 +73,34 @@ boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_sign void LLMenuButton::hideMenu() { - if(!mMenu) return; - mMenu->setVisible(FALSE); + if(mMenuHandle.isDead()) return; + + LLToggleableMenu* menu = dynamic_cast(mMenuHandle.get()); + if (menu) + { + menu->setVisible(FALSE); + } } -void LLMenuButton::setMenu(LLMenuGL* menu, EMenuPosition position /*MP_TOP_LEFT*/) +LLToggleableMenu* LLMenuButton::getMenu() { - mMenu = menu; - mMenuPosition = position; + return dynamic_cast(mMenuHandle.get()); } -void LLMenuButton::draw() +void LLMenuButton::setMenu(LLToggleableMenu* menu, EMenuPosition position /*MP_TOP_LEFT*/) { - //we save this off so next frame when we try to close it by - //button click, and it hides menus before we get to it, we know - mMenuVisibleLastFrame = mMenu && mMenu->getVisible(); - - if (mMenuVisibleLastFrame) - { - setForcePressedState(true); - } + if (!menu) return; - LLButton::draw(); + mMenuHandle = menu->getHandle(); + mMenuPosition = position; - setForcePressedState(false); + menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); } BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) { + if (mMenuHandle.isDead()) return FALSE; + if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) { // *HACK: We emit the mouse down signal to fire the callback bound to the @@ -107,9 +111,10 @@ BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) return TRUE; } - if (mMenu && mMenu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) + LLToggleableMenu* menu = dynamic_cast(mMenuHandle.get()); + if (menu && menu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) { - mMenu->setVisible(FALSE); + menu->setVisible(FALSE); return TRUE; } @@ -119,6 +124,7 @@ BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) { LLButton::handleMouseDown(x, y, mask); + toggleMenu(); return TRUE; @@ -126,28 +132,38 @@ BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) void LLMenuButton::toggleMenu() { - if(!mMenu) return; + if(mMenuHandle.isDead()) return; + + LLToggleableMenu* menu = dynamic_cast(mMenuHandle.get()); + if (!menu) return; - if (mMenu->getVisible() || mMenuVisibleLastFrame) + // Store the button rectangle to toggle menu visibility if a mouse event + // occurred inside or outside the button rect. + menu->setButtonRect(this); + + if (!menu->toggleVisibility() && mIsMenuShown) { - mMenu->setVisible(FALSE); + setForcePressedState(false); + mIsMenuShown = false; } else { - mMenu->buildDrawLabels(); - mMenu->arrangeAndClear(); - mMenu->updateParent(LLMenuGL::sMenuContainer); + menu->buildDrawLabels(); + menu->arrangeAndClear(); + menu->updateParent(LLMenuGL::sMenuContainer); updateMenuOrigin(); - //mMenu->needsArrange(); //so it recalculates the visible elements - LLMenuGL::showPopup(getParent(), mMenu, mX, mY); + LLMenuGL::showPopup(getParent(), menu, mX, mY); + + setForcePressedState(true); + mIsMenuShown = true; } } void LLMenuButton::updateMenuOrigin() { - if (!mMenu) return; + if (mMenuHandle.isDead()) return; LLRect rect = getRect(); @@ -156,7 +172,7 @@ void LLMenuButton::updateMenuOrigin() case MP_TOP_LEFT: { mX = rect.mLeft; - mY = rect.mTop + mMenu->getRect().getHeight(); + mY = rect.mTop + mMenuHandle.get()->getRect().getHeight(); break; } case MP_BOTTOM_LEFT: @@ -167,3 +183,17 @@ void LLMenuButton::updateMenuOrigin() } } } + +void LLMenuButton::onMenuVisibilityChange(const LLSD& param) +{ + bool new_visibility = param["visibility"].asBoolean(); + bool is_closed_by_button_click = param["closed_by_button_click"].asBoolean(); + + // Reset the button "pressed" state only if the menu is shown by this particular + // menu button (not any other control) and is not being closed by a click on the button. + if (!new_visibility && !is_closed_by_button_click && mIsMenuShown) + { + setForcePressedState(false); + mIsMenuShown = false; + } +} diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 81c3592b16..9e91b9e99d 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -29,7 +29,7 @@ #include "llbutton.h" -class LLMenuGL; +class LLToggleableMenu; class LLMenuButton : public LLButton @@ -52,14 +52,13 @@ public: boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); - /*virtual*/ void draw(); /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); void hideMenu(); - LLMenuGL* getMenu() { return mMenu; } - void setMenu(LLMenuGL* menu, EMenuPosition position = MP_TOP_LEFT); + LLToggleableMenu* getMenu(); + void setMenu(LLToggleableMenu* menu, EMenuPosition position = MP_TOP_LEFT); void setMenuPosition(EMenuPosition position) { mMenuPosition = position; } @@ -70,12 +69,14 @@ protected: void toggleMenu(); void updateMenuOrigin(); + void onMenuVisibilityChange(const LLSD& param); + private: - LLMenuGL* mMenu; - bool mMenuVisibleLastFrame; - EMenuPosition mMenuPosition; - S32 mX; - S32 mY; + LLHandle mMenuHandle; + bool mIsMenuShown; + EMenuPosition mMenuPosition; + S32 mX; + S32 mY; }; diff --git a/indra/llui/lltoggleablemenu.cpp b/indra/llui/lltoggleablemenu.cpp index 0eb2dc1387..d29260750f 100644 --- a/indra/llui/lltoggleablemenu.cpp +++ b/indra/llui/lltoggleablemenu.cpp @@ -35,10 +35,22 @@ static LLDefaultChildRegistry::Register r("toggleable_menu"); LLToggleableMenu::LLToggleableMenu(const LLToggleableMenu::Params& p) : LLMenuGL(p), mButtonRect(), + mVisibilityChangeSignal(NULL), mClosedByButtonClick(false) { } +LLToggleableMenu::~LLToggleableMenu() +{ + delete mVisibilityChangeSignal; +} + +boost::signals2::connection LLToggleableMenu::setVisibilityChangeCallback(const commit_signal_t::slot_type& cb) +{ + if (!mVisibilityChangeSignal) mVisibilityChangeSignal = new commit_signal_t(); + return mVisibilityChangeSignal->connect(cb); +} + // virtual void LLToggleableMenu::handleVisibilityChange (BOOL curVisibilityIn) { @@ -49,6 +61,12 @@ void LLToggleableMenu::handleVisibilityChange (BOOL curVisibilityIn) { mClosedByButtonClick = true; } + + if (mVisibilityChangeSignal) + { + (*mVisibilityChangeSignal)(this, + LLSD().with("visibility", curVisibilityIn).with("closed_by_button_click", mClosedByButtonClick)); + } } void LLToggleableMenu::setButtonRect(const LLRect& rect, LLView* current_view) diff --git a/indra/llui/lltoggleablemenu.h b/indra/llui/lltoggleablemenu.h index f036cdfffb..2094bd776f 100644 --- a/indra/llui/lltoggleablemenu.h +++ b/indra/llui/lltoggleablemenu.h @@ -41,6 +41,10 @@ protected: LLToggleableMenu(const Params&); friend class LLUICtrlFactory; public: + ~LLToggleableMenu(); + + boost::signals2::connection setVisibilityChangeCallback( const commit_signal_t::slot_type& cb ); + virtual void handleVisibilityChange (BOOL curVisibilityIn); const LLRect& getButtonRect() const { return mButtonRect; } @@ -57,6 +61,7 @@ public: protected: bool mClosedByButtonClick; LLRect mButtonRect; + commit_signal_t* mVisibilityChangeSignal; }; #endif // LL_LLTOGGLEABLEMENU_H -- cgit v1.2.3