diff options
Diffstat (limited to 'indra/newview')
26 files changed, 694 insertions, 489 deletions
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index fd4d1df894..dd9a0100c0 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4674,9 +4674,9 @@ <key>Persist</key> <integer>1</integer> <key>Type</key> - <string>Boolean</string> + <string>S32</string> <key>Value</key> - <integer>1</integer> + <integer>2</integer> </map> <key>LandBrushSize</key> <map> diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp index ff1f819d7d..8de041f983 100644 --- a/indra/newview/llconversationlog.cpp +++ b/indra/newview/llconversationlog.cpp @@ -28,13 +28,14 @@ #include "llagent.h" #include "llavatarnamecache.h" #include "llconversationlog.h" +#include "llnotificationsutil.h" #include "lltrans.h" const int CONVERSATION_LIFETIME = 30; // lifetime of LLConversation is 30 days by spec -struct Conversation_params +struct ConversationParams { - Conversation_params(time_t time) + ConversationParams(time_t time) : mTime(time), mTimestamp(LLConversation::createTimestamp(time)) {} @@ -53,7 +54,7 @@ struct Conversation_params /* LLConversation implementation */ /************************************************************************/ -LLConversation::LLConversation(const Conversation_params& params) +LLConversation::LLConversation(const ConversationParams& params) : mTime(params.mTime), mTimestamp(params.mTimestamp), mConversationType(params.mConversationType), @@ -188,33 +189,24 @@ void LLConversationLogFriendObserver::changed(U32 mask) LLConversationLog::LLConversationLog() : mAvatarNameCacheConnection() { - LLControlVariable* log_instant_message = gSavedPerAccountSettings.getControl("LogInstantMessages").get(); - LLControlVariable* keep_convers_log = gSavedSettings.getControl("KeepConversationLogTranscripts").get(); - bool is_log_message = false; - bool is_keep_log = false; + LLControlVariable * keep_log_ctrlp = gSavedSettings.getControl("KeepConversationLogTranscripts").get(); + S32 log_mode = keep_log_ctrlp->getValue(); - if (log_instant_message) + if (log_mode > 0) { - log_instant_message->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); - is_log_message = log_instant_message->getValue().asBoolean(); + keep_log_ctrlp->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); + enableLogging(log_mode); } - if (keep_convers_log) - { - keep_convers_log->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); - is_keep_log = keep_convers_log->getValue().asBoolean(); - } - - enableLogging(is_log_message && is_keep_log); } -void LLConversationLog::enableLogging(bool enable) +void LLConversationLog::enableLogging(S32 log_mode) { - if (enable) + if (log_mode > 0) { loadFromFile(getFileName()); LLIMMgr::instance().addSessionObserver(this); - newMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1)); + mNewMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1)); mFriendObserver = new LLConversationLogFriendObserver; LLAvatarTracker::instance().addObserver(mFriendObserver); @@ -224,7 +216,7 @@ void LLConversationLog::enableLogging(bool enable) saveToFile(getFileName()); LLIMMgr::instance().removeSessionObserver(this); - newMessageSignalConnection.disconnect(); + mNewMessageSignalConnection.disconnect(); LLAvatarTracker::instance().removeObserver(mFriendObserver); mConversations.clear(); } @@ -377,7 +369,7 @@ void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string void LLConversationLog::cache() { - if (gSavedPerAccountSettings.getBOOL("LogInstantMessages")) + if (gSavedSettings.getS32("KeepConversationLogTranscripts") > 0) { saveToFile(getFileName()); } @@ -450,9 +442,9 @@ bool LLConversationLog::loadFromFile(const std::string& filename) char part_id_buffer[MAX_STRING]; char conv_id_buffer[MAX_STRING]; char history_file_name[MAX_STRING]; - int has_offline_ims; - int stype; - time_t time; + S32 has_offline_ims; + S32 stype; + S64 time; // before CHUI-348 it was a flag of conversation voice state int prereserved_unused; @@ -462,7 +454,7 @@ bool LLConversationLog::loadFromFile(const std::string& filename) part_id_buffer[0] = '\0'; conv_id_buffer[0] = '\0'; - sscanf(buffer, "[%ld] %d %d %d %[^|]| %s %s %[^|]|", + sscanf(buffer, "[%lld] %d %d %d %[^|]| %s %s %[^|]|", &time, &stype, &prereserved_unused, @@ -472,7 +464,7 @@ bool LLConversationLog::loadFromFile(const std::string& filename) conv_id_buffer, history_file_name); - Conversation_params params(time); + ConversationParams params((time_t)time); params.mConversationType = (SessionType)stype; params.mHasOfflineIMs = has_offline_ims; params.mConversationName = std::string(conv_name_buffer); @@ -530,3 +522,17 @@ void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LL mAvatarNameCacheConnection.disconnect(); updateConversationName(session, av_name.getCompleteName()); } + +void LLConversationLog::onClearLog() +{ + LLNotificationsUtil::add("PreferenceChatClearLog", LLSD(), LLSD(), boost::bind(&LLConversationLog::onClearLogResponse, this, _1, _2)); +} + +void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& response) +{ + if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) + { + mConversations.clear(); + notifyObservers(); + } +} diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h index 35462ec3a4..65a18c02e5 100644 --- a/indra/newview/llconversationlog.h +++ b/indra/newview/llconversationlog.h @@ -31,7 +31,7 @@ #include "llimview.h" class LLConversationLogObserver; -struct Conversation_params; +struct ConversationParams; typedef LLIMModel::LLIMSession::SType SessionType; @@ -43,7 +43,7 @@ class LLConversation { public: - LLConversation(const Conversation_params& params); + LLConversation(const ConversationParams& params); LLConversation(const LLIMModel::LLIMSession& session); LLConversation(const LLConversation& conversation); @@ -138,6 +138,9 @@ public: */ void cache(); + void onClearLog(); + void onClearLogResponse(const LLSD& notification, const LLSD& response); + private: LLConversationLog(); @@ -149,7 +152,7 @@ private: } } - void enableLogging(bool enable); + void enableLogging(S32 log_mode); /** * adds conversation to the conversation list and notifies observers @@ -182,7 +185,7 @@ private: LLFriendObserver* mFriendObserver; // Observer of the LLAvatarTracker instance - boost::signals2::connection newMessageSignalConnection; + boost::signals2::connection mNewMessageSignalConnection; boost::signals2::connection mAvatarNameCacheConnection; }; diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index a8da4908ce..7184a70db5 100644 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -35,6 +35,7 @@ #include "llsdutil.h" #include "llconversationmodel.h" #include "llimview.h" //For LLIMModel +#include "lltrans.h" // // Conversation items : common behaviors @@ -461,6 +462,7 @@ LLConversationItemParticipant::LLConversationItemParticipant(std::string display LLConversationItem(display_name,uuid,root_view_model), mIsMuted(false), mIsModerator(false), + mDisplayModeratorLabel(false), mDistToAgent(-1.0) { mDisplayName = display_name; @@ -471,6 +473,7 @@ LLConversationItemParticipant::LLConversationItemParticipant(const LLUUID& uuid, LLConversationItem(uuid,root_view_model), mIsMuted(false), mIsModerator(false), + mDisplayModeratorLabel(false), mDistToAgent(-1.0) { mConvType = CONV_PARTICIPANT; @@ -503,6 +506,12 @@ void LLConversationItemParticipant::updateName(const LLAvatarName& av_name) { mName = av_name.getUserName(); mDisplayName = av_name.getDisplayName(); + + if (mDisplayModeratorLabel) + { + mDisplayName += " " + LLTrans::getString("IM_moderator_label"); + } + renameItem(mDisplayName); if (mParent != NULL) { @@ -541,6 +550,15 @@ void LLConversationItemParticipant::dumpDebugData() llinfos << "Merov debug : participant, uuid = " << mUUID << ", name = " << mName << ", display name = " << mDisplayName << ", muted = " << mIsMuted << ", moderator = " << mIsModerator << llendl; } +void LLConversationItemParticipant::setDisplayModeratorRole(bool displayRole) +{ + if (displayRole != mDisplayModeratorLabel) + { + mDisplayModeratorLabel = displayRole; + updateName(); + } +} + // // LLConversationSort // diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 6aaea041e4..c907d1d6d2 100755 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -205,13 +205,15 @@ public: void dumpDebugData(); void setModeratorOptionsVisible(bool visible) { mDisplayModeratorOptions = visible; } + void setDisplayModeratorRole(bool displayRole); private: void onAvatarNameCache(const LLAvatarName& av_name); // callback used by fetchAvatarName void updateName(const LLAvatarName& av_name); - bool mIsMuted; // default is false - bool mIsModerator; // default is false + bool mIsMuted; // default is false + bool mIsModerator; // default is false + bool mDisplayModeratorLabel; // default is false std::string mDisplayName; F64 mDistToAgent; // Distance to the agent. A negative (meaningless) value means the distance has not been set. boost::signals2::connection mAvatarNameCacheConnection; diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp index a40a000bab..a44ebcf6ab 100644 --- a/indra/newview/llfloaterconversationlog.cpp +++ b/indra/newview/llfloaterconversationlog.cpp @@ -63,13 +63,9 @@ BOOL LLFloaterConversationLog::postBuild() getChild<LLFilterEditor>("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterConversationLog::onFilterEdit, this, _2)); - LLControlVariable* ctrl = gSavedPerAccountSettings.getControl("LogInstantMessages").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLFloaterConversationLog::onCallLoggingEnabledDisabled, this, _2)); - onCallLoggingEnabledDisabled(ctrl->getValue().asBoolean() - && gSavedSettings.getBOOL("KeepConversationLogTranscripts")); - } + LLControlVariable * keep_log_ctrlp = gSavedSettings.getControl("KeepConversationLogTranscripts").get(); + keep_log_ctrlp->getSignal()->connect(boost::bind(&LLFloaterConversationLog::onCallLoggingEnabledDisabled, this, _2)); + onCallLoggingEnabledDisabled(keep_log_ctrlp->getValue()); return LLFloater::postBuild(); } @@ -139,8 +135,8 @@ bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata) return false; } -void LLFloaterConversationLog::onCallLoggingEnabledDisabled(bool enabled) +void LLFloaterConversationLog::onCallLoggingEnabledDisabled(S32 log_mode) { - std::string no_items_msg = enabled ? "" : getString("logging_calls_disabled"); + std::string no_items_msg = log_mode > 0 ? "" : getString("logging_calls_disabled"); mConversationLogList->setNoItemsCommentText(no_items_msg); } diff --git a/indra/newview/llfloaterconversationlog.h b/indra/newview/llfloaterconversationlog.h index 9e79cbd7d8..aa0f480aae 100644 --- a/indra/newview/llfloaterconversationlog.h +++ b/indra/newview/llfloaterconversationlog.h @@ -49,7 +49,7 @@ private: bool isActionEnabled(const LLSD& userdata); bool isActionChecked(const LLSD& userdata); - void onCallLoggingEnabledDisabled(bool enabled); + void onCallLoggingEnabledDisabled(S32 log_mode); LLConversationLogList* mConversationLogList; }; diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index ff6234fa27..54e5085490 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -224,7 +224,7 @@ BOOL LLFloaterIMContainer::postBuild() collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed")); - LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate)); + LLAvatarNameCache::addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); mMicroChangedSignal = LLVoiceClient::getInstance()->MicroChangedCallback(boost::bind(&LLFloaterIMContainer::updateSpeakBtnState, this)); if (! mMessagesPane->isCollapsed()) { @@ -1086,7 +1086,10 @@ void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, } else { - doToParticipants(command, selectedIDS); + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + doToParticipants(command, selectedIDS); + } } } } @@ -1139,7 +1142,7 @@ bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) if ("conversation_log" == item) { - return gSavedSettings.getBOOL("KeepConversationLogTranscripts"); + return gSavedSettings.getS32("KeepConversationLogTranscripts") > 0; } //Enable Chat history item for ad-hoc and group conversations @@ -1790,7 +1793,7 @@ void LLFloaterIMContainer::updateSpeakBtnState() bool LLFloaterIMContainer::isConversationLoggingAllowed() { - return gSavedSettings.getBOOL("KeepConversationLogTranscripts"); + return gSavedSettings.getS32("KeepConversationLogTranscripts") > 0; } void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes) diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index 73eb822036..a01578fa22 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -150,8 +150,14 @@ void LLFloaterIMNearbyChat::refresh() } } -void LLFloaterIMNearbyChat::reloadMessages() +void LLFloaterIMNearbyChat::reloadMessages(bool clean_messages/* = false*/) { + if (clean_messages) + { + mMessageArchive.clear(); + loadHistory(); + } + mChatHistory->clear(); LLSD do_not_log; @@ -176,11 +182,11 @@ void LLFloaterIMNearbyChat::loadHistory() { const LLSD& msg = *it; - std::string from = msg[IM_FROM]; + std::string from = msg[LL_IM_FROM]; LLUUID from_id; - if (msg[IM_FROM_ID].isDefined()) + if (msg[LL_IM_FROM_ID].isDefined()) { - from_id = msg[IM_FROM_ID].asUUID(); + from_id = msg[LL_IM_FROM_ID].asUUID(); } else { @@ -191,8 +197,8 @@ void LLFloaterIMNearbyChat::loadHistory() LLChat chat; chat.mFromName = from; chat.mFromID = from_id; - chat.mText = msg[IM_TEXT].asString(); - chat.mTimeStr = msg[IM_TIME].asString(); + chat.mText = msg[LL_IM_TEXT].asString(); + chat.mTimeStr = msg[LL_IM_TIME].asString(); chat.mChatStyle = CHAT_STYLE_HISTORY; chat.mSourceType = CHAT_SOURCE_AGENT; @@ -521,20 +527,21 @@ void LLFloaterIMNearbyChat::sendChat( EChatType type ) } } -void LLFloaterIMNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) +void LLFloaterIMNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) { appendMessage(chat, args); if(archive) { mMessageArchive.push_back(chat); - if(mMessageArchive.size()>200) + if(mMessageArchive.size() > 200) + { mMessageArchive.erase(mMessageArchive.begin()); + } } // logging - if (!args["do_not_log"].asBoolean() - && gSavedPerAccountSettings.getBOOL("LogNearbyChat")) + if (!args["do_not_log"].asBoolean() && gSavedSettings.getS32("KeepConversationLogTranscripts") > 1) { std::string from_name = chat.mFromName; diff --git a/indra/newview/llfloaterimnearbychat.h b/indra/newview/llfloaterimnearbychat.h index f4213eda5a..14c7d01ecd 100644 --- a/indra/newview/llfloaterimnearbychat.h +++ b/indra/newview/llfloaterimnearbychat.h @@ -56,7 +56,7 @@ public: /*virtual*/ void setVisible(BOOL visible); void loadHistory(); - void reloadMessages(); + void reloadMessages(bool clean_messages = false); void removeScreenChat(); void show(); diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp index f64cfd0245..8870d54cd2 100644 --- a/indra/newview/llfloaterimnearbychathandler.cpp +++ b/indra/newview/llfloaterimnearbychathandler.cpp @@ -604,6 +604,14 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, toast_msg = chat_msg.mText; } + //Don't show nearby toast, if conversation is visible but not focused + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(LLUUID()); + if (session_floater + && session_floater->isInVisibleChain() && !session_floater->isMinimized() + && !(session_floater->getHost() && session_floater->getHost()->isMinimized())) + { + return; + } //Will show toast when chat preference is set if(gSavedSettings.getString("NotificationNearbyChatOptions") == "toast") diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index a2c7bacb5d..7ed683d701 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -847,8 +847,18 @@ void LLFloaterIMSession::updateMessages() } } -void LLFloaterIMSession::reloadMessages() +void LLFloaterIMSession::reloadMessages(bool clean_messages/* = false*/) { + if (clean_messages) + { + LLIMModel::LLIMSession * sessionp = LLIMModel::instance().findIMSession(mSessionID); + + if (NULL != sessionp) + { + sessionp->loadHistory(); + } + } + mChatHistory->clear(); mLastMessageIndex = -1; updateMessages(); diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h index 2049cedfd7..e7fd6f9ff3 100644 --- a/indra/newview/llfloaterimsession.h +++ b/indra/newview/llfloaterimsession.h @@ -86,7 +86,7 @@ public: // get new messages from LLIMModel /*virtual*/ void updateMessages(); - void reloadMessages(); + void reloadMessages(bool clean_messages = false); static void onSendMsg(LLUICtrl*, void*); void sendMsgFromInputEditor(); void sendMsg(const std::string& msg); diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 37404ab716..f52cf3b8f0 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -497,6 +497,28 @@ void LLFloaterIMSessionTab::refreshConversation() } updateSessionName(session_name); } + + LLParticipantList* participant_list = getParticipantList(); + if (participant_list) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = participant_list->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = participant_list->getChildrenEnd(); + LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast<LLConversationItemParticipant*>(*current_participant_model); + if (speaker_mgr && participant_model) + { + LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID()); + LLSpeaker *agent_speaker = speaker_mgr->findSpeaker(gAgentID); + if (participant_speaker && agent_speaker) + { + participant_model->setDisplayModeratorRole(agent_speaker->mIsModerator && participant_speaker->mIsModerator); + } + } + current_participant_model++; + } + } mConversationViewModel.requestSortAll(); if(mConversationsRoot != NULL) @@ -680,7 +702,7 @@ void LLFloaterIMSessionTab::showTranslationCheckbox(BOOL show) } // static -void LLFloaterIMSessionTab::processChatHistoryStyleUpdate() +void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* = false*/) { LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); @@ -689,14 +711,14 @@ void LLFloaterIMSessionTab::processChatHistoryStyleUpdate() LLFloaterIMSession* floater = dynamic_cast<LLFloaterIMSession*>(*iter); if (floater) { - floater->reloadMessages(); + floater->reloadMessages(clean_messages); } } LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat"); if (nearby_chat) { - nearby_chat->reloadMessages(); + nearby_chat->reloadMessages(clean_messages); } } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index beaffc14a6..e90fcbb806 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -53,7 +53,7 @@ public: ~LLFloaterIMSessionTab(); // reload all message with new settings of visual modes - static void processChatHistoryStyleUpdate(); + static void processChatHistoryStyleUpdate(bool clean_messages = false); /** * Returns true if chat is displayed in multi tabbed floater diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 3d4a1c44d8..7742e5b3c3 100755 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -306,7 +306,8 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mAvatarDataInitialized(false), mClickActionDirty(false) { - + LLConversationLog::instance().addObserver(this); + //Build Floater is now Called from LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPreference>); static bool registered_dialog = false; @@ -329,8 +330,6 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.VoiceSetKey", boost::bind(&LLFloaterPreference::onClickSetKey, this)); mCommitCallbackRegistrar.add("Pref.VoiceSetMiddleMouse", boost::bind(&LLFloaterPreference::onClickSetMiddleMouse, this)); mCommitCallbackRegistrar.add("Pref.SetSounds", boost::bind(&LLFloaterPreference::onClickSetSounds, this)); -// mCommitCallbackRegistrar.add("Pref.ClickSkipDialogs", boost::bind(&LLFloaterPreference::onClickSkipDialogs, this)); -// mCommitCallbackRegistrar.add("Pref.ClickResetDialogs", boost::bind(&LLFloaterPreference::onClickResetDialogs, this)); mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", boost::bind(&LLFloaterPreference::onClickEnablePopup, this)); mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", boost::bind(&LLFloaterPreference::onClickDisablePopup, this)); mCommitCallbackRegistrar.add("Pref.LogPath", boost::bind(&LLFloaterPreference::onClickLogPath, this)); @@ -351,13 +350,16 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) sSkin = gSavedSettings.getString("SkinCurrent"); - mCommitCallbackRegistrar.add("Pref.ClickActionChange", boost::bind(&LLFloaterPreference::onClickActionChange, this)); + mCommitCallbackRegistrar.add("Pref.ClickActionChange", boost::bind(&LLFloaterPreference::onClickActionChange, this)); gSavedSettings.getControl("NameTagShowUsernames")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); gSavedSettings.getControl("NameTagShowFriends")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); gSavedSettings.getControl("UseDisplayNames")->getCommitSignal()->connect(boost::bind(&handleDisplayNamesOptionChanged, _2)); LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + + mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); + mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); } void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) @@ -425,7 +427,7 @@ void LLFloaterPreference::saveAvatarProperties( void ) BOOL LLFloaterPreference::postBuild() { - gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate)); + gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLViewerChat::signalChatFontChanged)); @@ -455,9 +457,24 @@ BOOL LLFloaterPreference::postBuild() gSavedPerAccountSettings.setString("DoNotDisturbModeResponse", LLTrans::getString("DoNotDisturbModeResponseDefault")); } + // set 'enable' property for 'Clear log...' button + changed(); + + // set 'enable' property for 'Delete transcripts...' button + updateDeleteTranscriptsButton(); + + LLLogChat::setSaveHistorySignal(boost::bind(&LLFloaterPreference::onLogChatHistorySaved, this)); + return TRUE; } +void LLFloaterPreference::updateDeleteTranscriptsButton() +{ + std::vector<std::string> list_of_transcriptions_file_names; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions_file_names); + getChild<LLButton>("delete_transcripts")->setEnabled(list_of_transcriptions_file_names.size() > 0); +} + void LLFloaterPreference::onDoNotDisturbResponseChanged() { // set "DoNotDisturbResponseChanged" TRUE if user edited message differs from default, FALSE otherwise @@ -476,6 +493,8 @@ LLFloaterPreference::~LLFloaterPreference() { ctrl_window_size->setCurrentByIndex(i); } + + LLConversationLog::instance().removeObserver(this); } void LLFloaterPreference::draw() @@ -833,12 +852,12 @@ void LLFloaterPreference::onBtnCancel() } // static -void LLFloaterPreference::updateUserInfo(const std::string& visibility, bool im_via_email, const std::string& email) +void LLFloaterPreference::updateUserInfo(const std::string& visibility, bool im_via_email) { LLFloaterPreference* instance = LLFloaterReg::findTypedInstance<LLFloaterPreference>("preferences"); if (instance) { - instance->setPersonalInfo(visibility, im_via_email, email); + instance->setPersonalInfo(visibility, im_via_email); } } @@ -1425,10 +1444,21 @@ void LLFloaterPreference::onClickLogPath() return; //Canceled! } - gSavedPerAccountSettings.setString("InstantMessageLogPath", picker.getDirName()); + std::string dir_name = picker.getDirName(); + gSavedPerAccountSettings.setString("InstantMessageLogPath", dir_name); + + gDirUtilp->setChatLogsDir(dir_name); + gDirUtilp->updatePerAccountChatLogsDir(); + LLFile::mkdir(gDirUtilp->getPerAccountChatLogsDir()); + + // refresh IM floaters with new logs from files from new selected directory + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); + + // enable/disable 'Delete transcripts button + updateDeleteTranscriptsButton(); } -void LLFloaterPreference::setPersonalInfo(const std::string& visibility, bool im_via_email, const std::string& email) +void LLFloaterPreference::setPersonalInfo(const std::string& visibility, bool im_via_email) { mGotPersonalInfo = true; mOriginalIMViaEmail = im_via_email; @@ -1450,32 +1480,14 @@ void LLFloaterPreference::setPersonalInfo(const std::string& visibility, bool im } getChild<LLUICtrl>("online_searchresults")->setEnabled(TRUE); - - getChildView("include_im_in_chat_history")->setEnabled(TRUE); - getChildView("show_timestamps_check_im")->setEnabled(TRUE); getChildView("friends_online_notify_checkbox")->setEnabled(TRUE); - getChild<LLUICtrl>("online_visibility")->setValue(mOriginalHideOnlineStatus); getChild<LLUICtrl>("online_visibility")->setLabelArg("[DIR_VIS]", mDirectoryVisibility); getChildView("send_im_to_email")->setEnabled(TRUE); getChild<LLUICtrl>("send_im_to_email")->setValue(im_via_email); - getChildView("log_instant_messages")->setEnabled(TRUE); -// getChildView("log_chat")->setEnabled(TRUE); -// getChildView("log_instant_messages_timestamp")->setEnabled(TRUE); -// getChildView("log_chat_timestamp")->setEnabled(TRUE); - getChildView("log_chat_IM")->setEnabled(TRUE); - getChildView("log_date_timestamp")->setEnabled(TRUE); - getChildView("favorites_on_login_check")->setEnabled(TRUE); - getChildView("log_nearby_chat")->setEnabled(TRUE); - getChildView("log_instant_messages")->setEnabled(TRUE); - getChildView("show_timestamps_check_im")->setEnabled(TRUE); getChildView("log_path_string")->setEnabled(FALSE);// LineEditor becomes readonly in this case. getChildView("log_path_button")->setEnabled(TRUE); - childEnable("logfile_name_datestamp"); - std::string display_email(email); - getChild<LLUICtrl>("email_address")->setValue(display_email); - } void LLFloaterPreference::onUpdateSliderText(LLUICtrl* ctrl, const LLSD& name) @@ -1566,6 +1578,35 @@ void LLFloaterPreference::onClickActionChange() mClickActionDirty = true; } +void LLFloaterPreference::onDeleteTranscripts() +{ + LLNotificationsUtil::add("PreferenceChatDeleteTranscripts", LLSD(), LLSD(), boost::bind(&LLFloaterPreference::onDeleteTranscriptsResponse, this, _1, _2)); +} + +void LLFloaterPreference::onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response) +{ + if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) + { + gDirUtilp->deleteFilesInDir(gDirUtilp->getPerAccountChatLogsDir(), "*." + LL_TRANSCRIPT_FILE_EXTENSION); + + std::vector<std::string> list_of_transcriptions_file_names; + LLLogChat::getListOfTranscriptFiles(list_of_transcriptions_file_names); + getChild<LLButton>("delete_transcripts")->setEnabled(list_of_transcriptions_file_names.size() > 0); + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); + } +} + +void LLFloaterPreference::onLogChatHistorySaved() +{ + LLButton * delete_transcripts_buttonp = getChild<LLButton>("delete_transcripts"); + + if (!delete_transcripts_buttonp->getEnabled()) + { + delete_transcripts_buttonp->setEnabled(true); + } +} + void LLFloaterPreference::updateClickActionSettings() { const int single_clk_action = getChild<LLComboBox>("single_click_action_combo")->getValue().asInteger(); @@ -1624,6 +1665,11 @@ void LLFloaterPreference::selectChatPanel() selectPanel("chat"); } +void LLFloaterPreference::changed() +{ + getChild<LLButton>("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); +} + //------------------------------Updater--------------------------------------- static bool handleBandwidthChanged(const LLSD& newvalue) @@ -1717,11 +1763,6 @@ BOOL LLPanelPreference::postBuild() } - if (hasChild("online_visibility") && hasChild("send_im_to_email")) - { - getChild<LLUICtrl>("email_address")->setValue(getString("log_in_to_change") ); - } - //////////////////////PanelPrivacy /////////////////// if (hasChild("media_enabled")) { @@ -1898,7 +1939,7 @@ public: for (control_values_map_t::iterator it = mSavedValues.begin(); it != mSavedValues.end(); ) { const std::string setting = it->first->getName(); - if (std::find(mAccountIndependentSettings.begin(), + if (find(mAccountIndependentSettings.begin(), mAccountIndependentSettings.end(), setting) == mAccountIndependentSettings.end()) { mSavedValues.erase(it++); @@ -2187,4 +2228,4 @@ void LLFloaterPreferenceProxy::onChangeSocksSettings() otherHttpProxy->selectFirstItem(); } -}; +} diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 37a531e99e..f9f1d52244 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -35,7 +35,9 @@ #include "llfloater.h" #include "llavatarpropertiesprocessor.h" +#include "llconversationlog.h" +class LLConversationLogObserver; class LLPanelPreference; class LLPanelLCD; class LLPanelDebug; @@ -58,7 +60,7 @@ typedef enum // Floater to control preferences (display, audio, bandwidth, general. -class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver +class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, public LLConversationLogObserver { public: LLFloaterPreference(const LLSD& key); @@ -70,9 +72,11 @@ public: /*virtual*/ BOOL postBuild(); /*virtual*/ void onOpen(const LLSD& key); /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void changed(); + /*virtual*/ void changed(const LLUUID& session_id, U32 mask) {}; // static data update, called from message handler - static void updateUserInfo(const std::string& visibility, bool im_via_email, const std::string& email); + static void updateUserInfo(const std::string& visibility, bool im_via_email); // refresh all the graphics preferences menus static void refreshEnabledGraphics(); @@ -134,15 +138,13 @@ public: void setKey(KEY key); void onClickSetMiddleMouse(); void onClickSetSounds(); -// void onClickSkipDialogs(); -// void onClickResetDialogs(); void onClickEnablePopup(); void onClickDisablePopup(); void resetAllIgnored(); void setAllIgnored(); void onClickLogPath(); void enableHistory(); - void setPersonalInfo(const std::string& visibility, bool im_via_email, const std::string& email); + void setPersonalInfo(const std::string& visibility, bool im_via_email); void refreshEnabledState(); void disableUnavailableSettings(); void onCommitWindowedMode(); @@ -166,12 +168,17 @@ public: void onClickSpellChecker(); void applyUIColor(LLUICtrl* ctrl, const LLSD& param); void getUIColor(LLUICtrl* ctrl, const LLSD& param); - + void onLogChatHistorySaved(); void buildPopupLists(); static void refreshSkin(void* data); void selectPanel(const LLSD& name); private: + + void onDeleteTranscripts(); + void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response); + void updateDeleteTranscriptsButton(); + static std::string sSkin; notifications_map mNotificationOptions; bool mClickActionDirty; ///< Set to true when the click/double-click options get changed by user. diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index cb03c1d234..37c9ee8c99 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -204,19 +204,16 @@ void on_new_message(const LLSD& msg) bool conversation_floater_not_focused = conversation_floater_is_closed || !im_box->hasFocus(); + // Skip toasting and flashing if we have open window of IM with this session id + if (session_floater + && session_floater->isInVisibleChain() + && !session_floater->isMinimized() + && !(session_floater->getHost() && session_floater->getHost()->isMinimized())) + { + return; + } if ("toast" == action) { - // Skip toasting and flashing if we have open window of IM with this session id - if (session_floater - && session_floater->isInVisibleChain() - && session_floater->hasFocus() - && !session_floater->isMinimized() - && !(session_floater->getHost() && session_floater->getHost()->isMinimized()) - ) - { - return; - } - //User is not focused on conversation containing the message if(session_floater_not_focused) { @@ -381,15 +378,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& } buildHistoryFileName(); - - if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) - { - std::list<LLSD> chat_history; - - //involves parsing of a chat history - LLLogChat::loadChatHistory(mHistoryFileName, chat_history); - addMessagesFromHistory(chat_history); - } + loadHistory(); // Localizing name of ad-hoc session. STORM-153 // Changing name should happen here- after the history file was created, so that @@ -582,11 +571,11 @@ void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list<LLSD>& histo { const LLSD& msg = *it; - std::string from = msg[IM_FROM]; + std::string from = msg[LL_IM_FROM]; LLUUID from_id; - if (msg[IM_FROM_ID].isDefined()) + if (msg[LL_IM_FROM_ID].isDefined()) { - from_id = msg[IM_FROM_ID].asUUID(); + from_id = msg[LL_IM_FROM_ID].asUUID(); } else { @@ -595,8 +584,8 @@ void LLIMModel::LLIMSession::addMessagesFromHistory(const std::list<LLSD>& histo gCacheName->getUUID(legacy_name, from_id); } - std::string timestamp = msg[IM_TIME]; - std::string text = msg[IM_TEXT]; + std::string timestamp = msg[LL_IM_TIME]; + std::string text = msg[LL_IM_TEXT]; addMessage(from, from_id, text, timestamp, true); @@ -620,6 +609,20 @@ void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const } } +void LLIMModel::LLIMSession::loadHistory() +{ + mMsgs.clear(); + + if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) + { + std::list<LLSD> chat_history; + + //involves parsing of a chat history + LLLogChat::loadChatHistory(mHistoryFileName, chat_history); + addMessagesFromHistory(chat_history); + } +} + LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const { return get_if_there(mId2SessionMap, session_id, @@ -924,8 +927,7 @@ bool LLIMModel::addToHistory(const LLUUID& session_id, const std::string& from, bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) { - if (gSavedPerAccountSettings.getBOOL("LogInstantMessages") - && gSavedSettings.getBOOL("KeepConversationLogTranscripts")) + if (gSavedSettings.getS32("KeepConversationLogTranscripts") > 1) { std::string from_name = from; diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 8578fa8c06..da6039a3ae 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -101,6 +101,8 @@ public: /** ad-hoc sessions involve sophisticated chat history file naming schemes */ void buildHistoryFileName(); + void loadHistory(); + LLUUID mSessionID; std::string mName; EInstantMessage mType; diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 3692658e9e..545b44ef92 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -58,10 +58,11 @@ const S32 LOG_RECALL_SIZE = 2048; -const std::string IM_TIME("time"); -const std::string IM_TEXT("message"); -const std::string IM_FROM("from"); -const std::string IM_FROM_ID("from_id"); +const std::string LL_IM_TIME("time"); +const std::string LL_IM_TEXT("message"); +const std::string LL_IM_FROM("from"); +const std::string LL_IM_FROM_ID("from_id"); +const std::string LL_TRANSCRIPT_FILE_EXTENSION("ll.txt"); const static std::string IM_SEPARATOR(": "); const static std::string NEW_LINE("\n"); @@ -116,6 +117,15 @@ const static int IDX_TEXT = 3; using namespace boost::posix_time; using namespace boost::gregorian; +void append_to_last_message(std::list<LLSD>& messages, const std::string& line) +{ + if (!messages.size()) return; + + std::string im_text = messages.back()[LL_IM_TEXT].asString(); + im_text.append(line); + messages.back()[LL_IM_TEXT] = im_text; +} + class LLLogChatTimeScanner: public LLSingleton<LLLogChatTimeScanner> { public: @@ -191,15 +201,17 @@ private: std::stringstream mTimeStream; }; +LLLogChat::save_history_signal_t * LLLogChat::sSaveHistorySignal = NULL; + //static std::string LLLogChat::makeLogFileName(std::string filename) { /** - * Testing for in bound and out bound ad-hoc file names - * if it is then skip date stamping. - **/ - //LL_INFOS("") << "Befor:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - boost::match_results<std::string::const_iterator> matches; + * Testing for in bound and out bound ad-hoc file names + * if it is then skip date stamping. + **/ + + boost::match_results<std::string::const_iterator> matches; bool inboundConf = boost::regex_match(filename, matches, INBOUND_CONFERENCE); bool outboundConf = boost::regex_match(filename, matches, OUTBOUND_CONFERENCE); if (!(inboundConf || outboundConf)) @@ -220,17 +232,17 @@ std::string LLLogChat::makeLogFileName(std::string filename) filename += dbuffer; } } - //LL_INFOS("") << "After:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ + filename = cleanFileName(filename); - filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS,filename); - filename += ".txt"; - //LL_INFOS("") << "Full:" << filename << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ + filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); + filename += '.' + LL_TRANSCRIPT_FILE_EXTENSION; + return filename; } std::string LLLogChat::cleanFileName(std::string filename) { - std::string invalidChars = "\"\'\\/?*:.<>|[]{}~"; // Cannot match glob or illegal filename chars + std::string invalidChars = "\"\'\\/?*:.<>|[]{}~"; // Cannot match glob or illegal filename chars std::string::size_type position = filename.find_first_of(invalidChars); while (position != filename.npos) { @@ -242,27 +254,24 @@ std::string LLLogChat::cleanFileName(std::string filename) std::string LLLogChat::timestamp(bool withdate) { - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr; - LLSD substitution; - substitution["datetime"] = (S32) utc_time; - if (withdate) { - timeStr = "["+LLTrans::getString ("TimeYear")+"]/[" - +LLTrans::getString ("TimeMonth")+"]/[" - +LLTrans::getString ("TimeDay")+"] [" - +LLTrans::getString ("TimeHour")+"]:[" - +LLTrans::getString ("TimeMin")+"]"; + timeStr = "[" + LLTrans::getString ("TimeYear") + "]/[" + + LLTrans::getString ("TimeMonth") + "]/[" + + LLTrans::getString ("TimeDay") + "] [" + + LLTrans::getString ("TimeHour") + "]:[" + + LLTrans::getString ("TimeMin") + "]"; } else { timeStr = "[" + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString ("TimeMin")+"]"; + + LLTrans::getString ("TimeMin")+"]"; } + LLSD substitution; + substitution["datetime"] = (S32)time_corrected(); + LLStringUtil::format (timeStr, substitution); return timeStr; } @@ -270,9 +279,9 @@ std::string LLLogChat::timestamp(bool withdate) //static void LLLogChat::saveHistory(const std::string& filename, - const std::string& from, - const LLUUID& from_id, - const std::string& line) + const std::string& from, + const LLUUID& from_id, + const std::string& line) { std::string tmp_filename = filename; LLStringUtil::trim(tmp_filename); @@ -312,6 +321,11 @@ void LLLogChat::saveHistory(const std::string& filename, file << LLChatLogFormatter(item) << std::endl; file.close(); + + if (NULL != sSaveHistorySignal) + { + (*sSaveHistorySignal)(); + } } void LLLogChat::loadHistory(const std::string& filename, void (*callback)(ELogLineType, const LLSD&, void*), void* userdata) @@ -321,7 +335,7 @@ void LLLogChat::loadHistory(const std::string& filename, void (*callback)(ELogLi llwarns << "Filename is Empty!" << llendl; return ; } - + LLFILE* fptr = LLFile::fopen(makeLogFileName(filename), "r"); /*Flawfinder: ignore*/ if (!fptr) { @@ -377,15 +391,6 @@ void LLLogChat::loadHistory(const std::string& filename, void (*callback)(ELogLi } } -void append_to_last_message(std::list<LLSD>& messages, const std::string& line) -{ - if (!messages.size()) return; - - std::string im_text = messages.back()[IM_TEXT].asString(); - im_text.append(line); - messages.back()[IM_TEXT] = im_text; -} - // static void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& messages, const LLSD& load_params) { @@ -397,19 +402,16 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; - //LL_INFOS("") << "Loading:" << file_name << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - //LL_INFOS("") << "Current:" << makeLogFileName(file_name) << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ LLFILE* fptr = LLFile::fopen(makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ if (!fptr) - { + { fptr = LLFile::fopen(oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ - if (!fptr) - { - if (!fptr) return; //No previous conversation with this name. - } + if (!fptr) + { + return; //No previous conversation with this name. + } } - //LL_INFOS("") << "Reading:" << file_name << LL_ENDL; char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ char *bptr; S32 len; @@ -454,7 +456,7 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m LLSD item; if (!LLChatLogParser::parse(line, item, load_params)) { - item[IM_TEXT] = line; + item[LL_IM_TEXT] = line; } messages.push_back(item); } @@ -462,9 +464,78 @@ void LLLogChat::loadChatHistory(const std::string& file_name, std::list<LLSD>& m fclose(fptr); } +// static +std::string LLLogChat::oldLogFileName(std::string filename) +{ + // get Users log directory + std::string directory = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + directory += gDirUtilp->getDirDelimiter(); + + // lest make sure the file name has no invalid characters before making the pattern + filename = cleanFileName(filename); + + // create search pattern + std::string pattern = filename + ( filename == "chat" ? "-???\?-?\?-??.txt" : "-???\?-??.txt"); + + std::vector<std::string> allfiles; + LLDirIterator iter(directory, pattern); + std::string scanResult; + + while (iter.next(scanResult)) + { + allfiles.push_back(scanResult); + } + + if (allfiles.size() == 0) // if no result from date search, return generic filename + { + scanResult = directory + filename + '.' + LL_TRANSCRIPT_FILE_EXTENSION; + } + else + { + sort(allfiles.begin(), allfiles.end()); + scanResult = directory + allfiles.back(); + // this file is now the most recent version of the file. + } + + return scanResult; +} + +// static +void LLLogChat::getListOfTranscriptFiles(std::vector<std::string>& list_of_transcriptions) +{ + // get Users log directory + std::string directory = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + directory += gDirUtilp->getDirDelimiter(); + + // create search pattern + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; + + LLDirIterator iter(directory, pattern); + std::string scanResult; + while (iter.next(scanResult)) + { + list_of_transcriptions.push_back(scanResult); + } +} + +//static +boost::signals2::connection LLLogChat::setSaveHistorySignal(const save_history_signal_t::slot_type& cb) +{ + if (NULL == sSaveHistorySignal) + { + sSaveHistorySignal = new save_history_signal_t(); + } + + return sSaveHistorySignal->connect(cb); +} + //*TODO mark object's names in a special way so that they will be distinguishable form avatar name //which are more strict by its nature (only firstname and secondname) -//Example, an object's name can be writen like "Object <actual_object's_name>" +//Example, an object's name can be written like "Object <actual_object's_name>" void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const { if (!im.isMap()) @@ -473,19 +544,19 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const return; } - if (im[IM_TIME].isDefined()) + if (im[LL_IM_TIME].isDefined()) { - std::string timestamp = im[IM_TIME].asString(); + std::string timestamp = im[LL_IM_TIME].asString(); boost::trim(timestamp); ostr << '[' << timestamp << ']' << TWO_SPACES; } //*TODO mark object's names in a special way so that they will be distinguishable form avatar name //which are more strict by its nature (only firstname and secondname) - //Example, an object's name can be writen like "Object <actual_object's_name>" - if (im[IM_FROM].isDefined()) + //Example, an object's name can be written like "Object <actual_object's_name>" + if (im[LL_IM_FROM].isDefined()) { - std::string from = im[IM_FROM].asString(); + std::string from = im[LL_IM_FROM].asString(); boost::trim(from); if (from.size()) { @@ -493,9 +564,9 @@ void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const } } - if (im[IM_TEXT].isDefined()) + if (im[LL_IM_TEXT].isDefined()) { - std::string im_text = im[IM_TEXT].asString(); + std::string im_text = im[LL_IM_TEXT].asString(); //multilined text will be saved with prepended spaces boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX); @@ -528,12 +599,12 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); } - im[IM_TIME] = timestamp; + im[LL_IM_TIME] = timestamp; } else { //timestamp is optional - im[IM_TIME] = ""; + im[LL_IM_TIME] = ""; } bool has_stuff = matches[IDX_STUFF].matched; @@ -559,8 +630,8 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params if (!has_name || name == SYSTEM_FROM) { //name is optional too - im[IM_FROM] = SYSTEM_FROM; - im[IM_FROM_ID] = LLUUID::null; + im[LL_IM_FROM] = SYSTEM_FROM; + im[LL_IM_FROM_ID] = LLUUID::null; } //possibly a case of complex object names consisting of 3+ words @@ -569,8 +640,8 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params U32 divider_pos = stuff.find(NAME_TEXT_DIVIDER); if (divider_pos != std::string::npos && divider_pos < (stuff.length() - NAME_TEXT_DIVIDER.length())) { - im[IM_FROM] = stuff.substr(0, divider_pos); - im[IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); + im[LL_IM_FROM] = stuff.substr(0, divider_pos); + im[LL_IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); return true; } } @@ -578,7 +649,7 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params if (!has_name) { //text is mandatory - im[IM_TEXT] = stuff; + im[LL_IM_TEXT] = stuff; return true; //parse as a message from Second Life } @@ -590,45 +661,15 @@ bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params { std::string agent_name; LLAgentUI::buildFullname(agent_name); - im[IM_FROM] = agent_name; - im[IM_FROM_ID] = gAgentID; + im[LL_IM_FROM] = agent_name; + im[LL_IM_FROM_ID] = gAgentID; } else { - im[IM_FROM] = name; + im[LL_IM_FROM] = name; } - im[IM_TEXT] = name_and_text[IDX_TEXT]; + im[LL_IM_TEXT] = name_and_text[IDX_TEXT]; return true; //parsed name and message text, maybe have a timestamp too } -std::string LLLogChat::oldLogFileName(std::string filename) -{ - std::string scanResult; - std::string directory = gDirUtilp->getPerAccountChatLogsDir();/* get Users log directory */ - directory += gDirUtilp->getDirDelimiter();/* add final OS dependent delimiter */ - filename=cleanFileName(filename);/* lest make shure the file name has no invalad charecters befor making the pattern */ - std::string pattern = (filename+(( filename == "chat" ) ? "-???\?-?\?-??.txt" : "-???\?-??.txt"));/* create search pattern*/ - //LL_INFOS("") << "Checking:" << directory << " for " << pattern << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - std::vector<std::string> allfiles; - - LLDirIterator iter(directory, pattern); - while (iter.next(scanResult)) - { - //LL_INFOS("") << "Found :" << scanResult << LL_ENDL; - allfiles.push_back(scanResult); - } - - if (allfiles.size() == 0) // if no result from date search, return generic filename - { - scanResult = directory + filename + ".txt"; - } - else - { - std::sort(allfiles.begin(), allfiles.end()); - scanResult = directory + allfiles.back(); - // thisfile is now the most recent version of the file. - } - //LL_INFOS("") << "Reading:" << scanResult << LL_ENDL;/* uncomment if you want to verify step, delete on commit */ - return scanResult; -} diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index d3e9adcc37..b35a94b4b3 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -49,6 +49,7 @@ public: const std::string& from, const LLUUID& from_id, const std::string& line); + static void getListOfTranscriptFiles(std::vector<std::string>& list); /** @deprecated @see loadChatHistory() */ static void loadHistory(const std::string& filename, @@ -56,8 +57,13 @@ public: void* userdata); static void loadChatHistory(const std::string& file_name, std::list<LLSD>& messages, const LLSD& load_params = LLSD()); + + typedef boost::signals2::signal<void ()> save_history_signal_t; + static boost::signals2::connection setSaveHistorySignal(const save_history_signal_t::slot_type& cb); + private: static std::string cleanFileName(std::string filename); + static save_history_signal_t * sSaveHistorySignal; }; /** @@ -113,9 +119,10 @@ protected: }; // LLSD map lookup constants -extern const std::string IM_TIME; //("time"); -extern const std::string IM_TEXT; //("message"); -extern const std::string IM_FROM; //("from"); -extern const std::string IM_FROM_ID; //("from_id"); +extern const std::string LL_IM_TIME; //("time"); +extern const std::string LL_IM_TEXT; //("message"); +extern const std::string LL_IM_FROM; //("from"); +extern const std::string LL_IM_FROM_ID; //("from_id"); +extern const std::string LL_TRANSCRIPT_FILE_EXTENSION; //("ll.txt"); #endif diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 8489e92d15..f141419c43 100755 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -6943,7 +6943,7 @@ void process_user_info_reply(LLMessageSystem* msg, void**) std::string dir_visibility; msg->getString( "UserData", "DirectoryVisibility", dir_visibility); - LLFloaterPreference::updateUserInfo(dir_visibility, im_via_email, email); + LLFloaterPreference::updateUserInfo(dir_visibility, im_via_email); LLFloaterSnapshot::setAgentEmail(email); } diff --git a/indra/newview/skins/default/xui/en/menu_url_group.xml b/indra/newview/skins/default/xui/en/menu_url_group.xml index 2cb125ce09..c5eaf94d22 100644 --- a/indra/newview/skins/default/xui/en/menu_url_group.xml +++ b/indra/newview/skins/default/xui/en/menu_url_group.xml @@ -7,7 +7,7 @@ layout="topleft" name="show_group"> <menu_item_call.on_click - function="Url.ShowProfile" /> + function="Url.Execute" /> </menu_item_call> <menu_item_separator layout="topleft" /> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 6a464ccdc4..9c36873141 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -8316,4 +8316,30 @@ Attempt cancelled. yestext="Yes"/> </notification> + <notification + icon="alertmodal.tga" + name="PreferenceChatClearLog" + type="alertmodal"> + This will delete the log of previous conversations, and all transcripts of those conversations. Proceed? + <tag>confirm</tag> + <usetemplate + ignoretext="Confirm before I delete the log of previous conversations." + name="okcancelignore" + notext="Cancel" + yestext="OK"/> + </notification> + + <notification + icon="alertmodal.tga" + name="PreferenceChatDeleteTranscripts" + type="alertmodal"> + This will delete transcripts for all previous conversations. The list of conversations will not be affected. Proceed? + <tag>confirm</tag> + <usetemplate + ignoretext="Confirm before I delete transcripts." + name="okcancelignore" + notext="Cancel" + yestext="OK"/> + </notification> + </notifications> diff --git a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml index 37bfbae991..4f33699aa6 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_chat.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_chat.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel border="true" - follows="left|top|right|bottom" + has_border="true" height="408" label="Text Chat" layout="topleft" @@ -12,8 +12,7 @@ <panel border="false" - follows="left|top" - height="90" + height="60" layout="topleft" top="10" left="13" @@ -27,7 +26,9 @@ layout="topleft" top="0" name="play_typing_animation" - width="330" /> + width="330"> + </check_box> + <check_box enabled="false" height="16" @@ -35,26 +36,21 @@ layout="topleft" name="send_im_to_email" top_pad="6" - width="330" /> - <check_box - control_name="KeepConversationLogTranscripts" - height="16" - label="Keep a conversation log and transcripts" - layout="topleft" - name="keep_convo_log_and_transcripts" - top_pad="6" - width="330" /> + width="330"> + </check_box> + <check_box - control_name="UseChatBubbles" - follows="left|top" + control_name="VoiceCallsFriendsOnly" height="16" - label="Bubble Chat" + label="Only friends and groups can call or IM me" layout="topleft" + name="voice_call_friends_only_check" top_pad="6" - name="bubble_text_chat" - width="330" /> + width="350"> + <check_box.label_text text_color="White" /> + </check_box> + <text - follows="left|top" layout="topleft" left="345" height="12" @@ -63,67 +59,65 @@ top="0"> Font size: </text> - <radio_group - height="90" - layout="topleft" - left="352" + + <combo_box control_name="ChatFontSize" + height="23" + layout="topleft" + left="341" name="chat_font_size" - top_pad="0" - width="50"> - <radio_item - height="16" + top_pad="5" + width="100"> + <item label="Small" - layout="topleft" - name="radio" - value="0" - top="10" - width="125" /> - <radio_item - height="16" + name="Small" + value="0"/> + <item label="Medium" - layout="topleft" - name="radio2" - value="1" - top_pad="6" - width="125" /> - <radio_item - height="16" + name="Medium" + value="1"/> + <item label="Large" - layout="topleft" - name="radio3" - value="2" - top_pad="6" - width="125" /> - </radio_group> - - </panel> + name="Large" + value="2"/> + <combo_box.drop_down_button label_color="White" /> + </combo_box> + + <check_box + control_name="UseChatBubbles" + height="16" + label="Bubble Chat" + layout="topleft" + top_pad="4" + name="bubble_text_chat" + width="330"> + </check_box> + + </panel> <panel border="false" - follows="left|top" - height="198" + height="165" layout="topleft" left="13" width="517"> <text - follows="left|top" layout="topleft" height="12" name="notifications" left="0" + text_color="White" width="120"> - Notifications: + Notifications </text> <text - follows="left|top" layout="topleft" height="12" name="friend_ims" width="145" left="0" - top_pad="15"> + top_pad="13"> Friend IMs: </text> <combo_box @@ -134,31 +128,30 @@ top_delta="-6" name="FriendIMOptions" width="223"> - <combo_box.item + <item label="Open Conversations window" name="OpenConversationsWindow" value="openconversations"/> - <combo_box.item + <item label="Pop up the message" name="PopUpMessage" value="toast"/> - <combo_box.item + <item label="Flash toolbar button" name="FlashToolbarButton" value="flash"/> - <combo_box.item + <item label="None" name="None" value="none"/> </combo_box> <text - follows="left|top" layout="topleft" height="12" name="non_friend_ims" width="145" left="0" - top_pad="15"> + top_pad="9"> Non-friend IMs: </text> <combo_box @@ -169,31 +162,30 @@ top_delta="-6" name="NonFriendIMOptions" width="223"> - <combo_box.item + <item label="Open Conversations window" name="OpenConversationsWindow" value="openconversations"/> - <combo_box.item + <item label="Pop up the message" name="PopUpMessage" value="toast"/> - <combo_box.item + <item label="Flash toolbar button" name="FlashToolbarButton" value="flash"/> - <combo_box.item + <item label="None" name="None" value="none"/> </combo_box> <text - follows="left|top" layout="topleft" left="0" height="13" name="conference_ims" width="145" - top_pad="14"> + top_pad="9"> Conference IMs: </text> <combo_box @@ -204,31 +196,30 @@ top_delta="-6" name="ConferenceIMOptions" width="223"> - <combo_box.item + <item label="Open Conversations window" name="OpenConversationsWindow" value="openconversations"/> - <combo_box.item + <item label="Pop up the message" name="PopUpMessage" value="toast"/> - <combo_box.item + <item label="Flash toolbar button" name="FlashToolbarButton" value="flash"/> - <combo_box.item + <item label="None" name="None" value="none"/> </combo_box> <text - follows="left|top" layout="topleft" left="0" height="13" name="group_chat" width="145" - top_pad="14"> + top_pad="9"> Group chat: </text> <combo_box @@ -239,31 +230,30 @@ top_delta="-6" name="GroupChatOptions" width="223"> - <combo_box.item + <item label="Open Conversations window" name="OpenConversationsWindow" value="openconversations"/> - <combo_box.item + <item label="Pop up the message" name="PopUpMessage" value="toast"/> - <combo_box.item + <item label="Flash toolbar button" name="FlashToolbarButton" value="flash"/> - <combo_box.item + <item label="None" name="None" value="none"/> </combo_box> <text - follows="left|top" layout="topleft" left="0" height="12" name="nearby_chat" width="145" - top_pad="14"> + top_pad="9"> Nearby chat: </text> <combo_box @@ -274,31 +264,30 @@ top_delta="-6" name="NearbyChatOptions" width="223"> - <combo_box.item + <item label="Open Conversations window" name="OpenConversationsWindow" value="openconversations"/> - <combo_box.item + <item label="Pop up the message" name="PopUpMessage" value="toast"/> - <combo_box.item + <item label="Flash toolbar button" name="FlashToolBarButton" value="flash"/> - <combo_box.item + <item label="None" name="None" value="none"/> </combo_box> <text - follows="left|top" layout="topleft" left="0" height="13" name="notifications_alert" width="500" - top_pad="11" + top_pad="9" visible="true" text_color="DrYellow"> To temporarily stop all notifications, use Communicate > Do Not Disturb. @@ -308,14 +297,13 @@ <panel border="false" - follows="left|top" - height="67" + height="50" layout="topleft" left="13" + top_pad="10" width="517"> <text - follows="left|top" layout="topleft" left="0" name="play_sound" @@ -360,9 +348,123 @@ width="150" /> </panel> + + <view_border + bevel_style="none" + height="0" + layout="topleft" + left="13" + name="cost_text_border" + top_pad="5" + width="495"/> + + <panel + height="50" + layout="topleft" + left="13" + top_pad="10" + width="505"> + + <text + layout="topleft" + left="0" + text_color="White" + height="12" + top="5" + width="55"> + Save: + </text> + + <combo_box + control_name="KeepConversationLogTranscripts" + height="23" + layout="topleft" + left_pad="5" + name="chat_font_size" + top="0" + width="165"> + <item + label="Log and transcripts" + value="2"/> + <item + label="Log only" + value="1"/> + <item + label="No log or transcripts" + value="0"/> + <drop_down_button label_color="White" /> + </combo_box> + + <button + enabled="false" + height="23" + label="Clear log..." + label_color="White" + layout="topleft" + left_pad="5" + top="0" + name="clear_log" + width="110"> + <commit_callback + function="Pref.ClearLog" /> + </button> + + <button + enabled="false" + height="23" + label="Delete transcripts..." + label_color="White" + layout="topleft" + left_pad="5" + top="0" + name="delete_transcripts" + width="147"> + <button.commit_callback + function="Pref.DeleteTranscripts" /> + </button> + + <text + layout="topleft" + left="0" + text_color="White" + height="12" + top_pad="15" + width="55"> + Location: + </text> + + <line_editor + control_name="InstantMessageLogPath" + border_style="line" + border_thickness="1" + font="SansSerif" + height="23" + layout="topleft" + left_pad="55" + max_length="4096" + name="log_path_string" + top_delta="-5" + width="185"> + </line_editor> + + <button + enabled="false" + follows="left|top" + height="23" + label="Browse..." + label_color="White" + label_selected="Browse" + layout="topleft" + left_pad="5" + name="log_path_button" + top_delta="0" + width="112"> + <commit_callback function="Pref.LogPath" /> + </button> + + </panel> <button - follows="left|top" height="23" label="Translation..." layout="topleft" @@ -370,11 +472,10 @@ name="ok_btn" top="-29" width="170"> - <button.commit_callback + <commit_callback function="Pref.TranslationSettings" /> </button> <button - follows="top|left" height="23" layout="topleft" top_pad="-23" @@ -385,7 +486,6 @@ width="150"> </button> <button - follows="top|left" height="23" layout="topleft" top_pad="-23" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml b/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml index 587c461bee..78743d26bb 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_privacy.xml @@ -1,72 +1,69 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel - border="true" - follows="left|top|right|bottom" - height="408" - label="Communication" - layout="topleft" - left="102" - name="im" - top="1" - width="517"> - <panel.string - name="log_in_to_change"> - log in to change - </panel.string> - <button - follows="left|bottom" - height="23" - label="Clear History" - tool_tip="Clear login image, last location, teleport history, web, and texture cache" - layout="topleft" - left="30" - name="clear_cache" - top="10" - width="145"> - <button.commit_callback - function="Pref.WebClearCache" /> - </button> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_pad="10" - mouse_opaque="false" - name="cache_size_label_l" - top_delta="3" - text_color="LtGray_50" - width="300"> - (Locations, images, web, search history) - </text> - <check_box - height="16" - enabled="false" - label="Show me in Search results" - layout="topleft" - left="30" - name="online_searchresults" - top_pad="20" - width="350" /> - <check_box - height="16" - enabled="false" - label="Only friends and groups know I'm online" - layout="topleft" - left="30" - name="online_visibility" - top_pad="30" - width="350" /> - <check_box - control_name="VoiceCallsFriendsOnly" - height="16" - label="Only friends and groups can call or IM me" - layout="topleft" - left="30" - name="voice_call_friends_only_check" - top_pad="10" - width="350" /> + border="true" + follows="left|top|right|bottom" + height="408" + label="Communication" + layout="topleft" + left="102" + name="im" + top="1" + width="517"> + + <panel.string + name="log_in_to_change"> + log in to change + </panel.string> + + <button + follows="left|bottom" + height="23" + label="Clear History" + tool_tip="Clear login image, last location, teleport history, web, and texture cache" + layout="topleft" + left="30" + name="clear_cache" + top="10" + width="145"> + <button.commit_callback + function="Pref.WebClearCache" /> + </button> + + <text + type="string" + length="1" + follows="left|top" + height="10" + layout="topleft" + left_pad="10" + mouse_opaque="false" + name="cache_size_label_l" + top_delta="3" + text_color="LtGray_50" + width="300"> + (Locations, images, web, search history) + </text> + + <check_box + height="16" + enabled="false" + label="Show me in Search results" + layout="topleft" + left="30" + name="online_searchresults" + top_pad="20" + width="350" /> + + <check_box + height="16" + enabled="false" + label="Only friends and groups know I'm online" + layout="topleft" + left="30" + name="online_visibility" + top_pad="30" + width="350" /> + <check_box enabled_control="EnableVoiceChat" control_name="AutoDisengageMic" @@ -87,100 +84,7 @@ name="favorites_on_login_check" top_pad="10" width="350" /> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left="30" - mouse_opaque="false" - name="Logs:" - top_pad="20" - width="350"> - Chat Logs: - </text> - <check_box - enabled="false" - control_name="LogNearbyChat" - height="16" - label="Save nearby chat logs on my computer" - layout="topleft" - left="30" - name="log_nearby_chat" - top_pad="10" - width="350"> - </check_box> - <check_box - enabled="false" - control_name="LogInstantMessages" - height="16" - label="Save IM logs on my computer" - layout="topleft" - left="30" - name="log_instant_messages" - top_pad="10" - width="350"> - </check_box> - <check_box - control_name="LogTimestamp" - enabled="false" - height="16" - label="Add timestamp to each line in chat log" - layout="topleft" - left_delta="0" - name="show_timestamps_check_im" - top_pad="10" - width="237" /> - <check_box - control_name="LogFileNamewithDate" - enabled="false" - height="16" - label="Add datestamp to log file name." - layout="topleft" - left_delta="5" - name="logfile_name_datestamp" - top_pad="10" - width="350"/> - <text - type="string" - length="1" - follows="left|top" - height="10" - layout="topleft" - left_delta="0" - mouse_opaque="false" - name="log_path_desc" - top_pad="30" - width="128"> - Location of logs: - </text> - <line_editor - bottom="366" - control_name="InstantMessageLogPath" - follows="top|left|right" - halign="right" - height="23" - layout="topleft" - left_delta="0" - mouse_opaque="false" - name="log_path_string" - top_pad="5" - width="250"/> - <button - enabled="false" - follows="right|bottom" - height="23" - label="Browse" - label_selected="Browse" - layout="topleft" - left_pad="5" - name="log_path_button" - top_delta="0" - width="145"> - <button.commit_callback - function="Pref.LogPath" /> - </button> + <button follows="left|bottom" height="23" |