Add "Add Participant" button to IM windows as seen on CHUI; Also allow DaD of cargo type DAD_PERSON into the window

meow-7.2.2
Ansariel 2015-06-29 13:32:57 +02:00
parent 9b9a688dd4
commit 3d9a7bc529
8 changed files with 429 additions and 64 deletions

View File

@ -49,6 +49,7 @@
#include "llchiclet.h"
#include "llchicletbar.h"
#include "llfloaterabout.h" // for sysinfo button -Zi
#include "llfloateravatarpicker.h"
#include "llfloaterreg.h"
#include "llgroupactions.h"
#include "llhttpclient.h"
@ -105,13 +106,10 @@ FSFloaterIM::FSFloaterIM(const LLUUID& session_id)
mUnreadMessagesNotificationPanel(NULL),
mUnreadMessagesNotificationTextBox(NULL)
{
LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionID);
if (im_session)
initIMSession(session_id);
switch (mDialog)
{
mSessionInitialized = im_session->mSessionInitialized;
mDialog = im_session->mType;
switch(mDialog){
case IM_NOTHING_SPECIAL:
case IM_SESSION_P2P_INVITE:
mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelIMControl, this);
@ -123,7 +121,7 @@ FSFloaterIM::FSFloaterIM(const LLUUID& session_id)
setCanSnooze(TRUE);
mFactoryMap["panel_im_control_panel"] = LLCallbackMap(createPanelGroupControl, this);
break;
case IM_SESSION_INVITE:
case IM_SESSION_INVITE:
if (gAgent.isInGroup(mSessionID))
{
setCanSnooze(TRUE);
@ -135,9 +133,8 @@ FSFloaterIM::FSFloaterIM(const LLUUID& session_id)
}
break;
default: break;
}
}
mCommitCallbackRegistrar.add("IMSession.Menu.Action", boost::bind(&FSFloaterIM::doToSelected, this, _2));
mEnableCallbackRegistrar.add("IMSession.Menu.Enable", boost::bind(&FSFloaterIM::checkEnabled, this, _2));
@ -523,6 +520,8 @@ void FSFloaterIM::doToSelected(const LLSD& userdata)
LLAvatarActions::teleportRequest(mOtherParticipantUUID);
else if (command == "share")
LLAvatarActions::share(mOtherParticipantUUID);
else if (command == "add_participant")
onAddButtonClicked();
else if (command == "pay")
LLAvatarActions::pay(mOtherParticipantUUID);
else if (command == "show_profile")
@ -743,6 +742,7 @@ BOOL FSFloaterIM::postBuild()
getChild<LLLayoutPanel>("pay_panel")->setVisible(false);
getChild<LLLayoutPanel>("end_call_btn_panel")->setVisible(false);
getChild<LLLayoutPanel>("voice_ctrls_btn_panel")->setVisible(false);
getChild<LLLayoutPanel>("add_participant_panel")->setVisible(false);
LL_DEBUGS("FSFloaterIM") << "LLIMModel::LLIMSession::GROUP_SESSION end" << LL_ENDL;
break;
@ -972,6 +972,13 @@ void FSFloaterIM::draw()
mOtherTyping = false;
}
// add people who were added via dropPerson()
if (!mPendingParticipants.empty())
{
addSessionParticipants(mPendingParticipants);
mPendingParticipants.clear();
}
LLTransientDockableFloater::draw();
}
@ -1260,14 +1267,17 @@ void FSFloaterIM::sessionInitReplyReceived(const LLUUID& im_session_id)
//need to send delayed messaged collected while waiting for session initialization
if (!mQueuedMsgsForInit.size()) return;
LLSD::array_iterator iter;
for ( iter = mQueuedMsgsForInit.beginArray();
iter != mQueuedMsgsForInit.endArray();
++iter)
if (mQueuedMsgsForInit.size())
{
LLIMModel::sendMessage(iter->asString(), mSessionID,
mOtherParticipantUUID, mDialog);
LLSD::array_iterator iter;
for ( iter = mQueuedMsgsForInit.beginArray();
iter != mQueuedMsgsForInit.endArray(); ++iter)
{
LLIMModel::sendMessage(iter->asString(), mSessionID,
mOtherParticipantUUID, mDialog);
}
mQueuedMsgsForInit.clear();
}
}
@ -1489,31 +1499,68 @@ void FSFloaterIM::processIMTyping(const LLIMInfo* im_info, BOOL typing)
void FSFloaterIM::processAgentListUpdates(const LLSD& body)
{
if ( !body.isMap() ) return;
loadInitialInvitedIDs();
if ( body.has("agent_updates") && body["agent_updates"].isMap() )
uuid_vec_t joined_uuids;
if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap())
{
LLSD agent_data = body["agent_updates"].get(gAgentID.asString());
if (agent_data.isMap() && agent_data.has("info"))
LLSD::map_const_iterator update_it;
for(update_it = body["agent_updates"].beginMap();
update_it != body["agent_updates"].endMap();
++update_it)
{
LLSD agent_info = agent_data["info"];
LLUUID agent_id(update_it->first);
LLSD agent_data = update_it->second;
if (agent_info.has("mutes"))
if (agent_data.isMap())
{
BOOL moderator_muted_text = agent_info["mutes"]["text"].asBoolean();
mInputEditor->setEnabled(!moderator_muted_text);
std::string label;
if (moderator_muted_text)
label = LLTrans::getString("IM_muted_text_label");
else
label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID);
mInputEditor->setLabel(label);
// store the new participants in joined_uuids
if (agent_data.has("transition") && agent_data["transition"].asString() == "ENTER")
{
joined_uuids.push_back(agent_id);
}
if (moderator_muted_text)
LLNotificationsUtil::add("TextChatIsMutedByModerator");
// process the moderator mutes
if (agent_id == gAgentID && agent_data.has("info") && agent_data["info"].has("mutes"))
{
BOOL moderator_muted_text = agent_data["info"]["mutes"]["text"].asBoolean();
mInputEditor->setEnabled(!moderator_muted_text);
std::string label;
if (moderator_muted_text)
label = LLTrans::getString("IM_muted_text_label");
else
label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID);
mInputEditor->setLabel(label);
if (moderator_muted_text)
LLNotificationsUtil::add("TextChatIsMutedByModerator");
}
}
}
}
// the vectors need to be sorted for computing the intersection and difference
std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end());
std::sort(joined_uuids.begin(), joined_uuids.end());
uuid_vec_t intersection; // uuids of invited residents who have joined the conversation
std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(),
joined_uuids.begin(), joined_uuids.end(),
std::back_inserter(intersection));
if (intersection.size() > 0)
{
sendParticipantsAddedNotification(intersection);
}
// Remove all joined participants from invited array.
// The difference between the two vectors (the elements in mInvitedParticipants which are not in joined_uuids)
// is placed at the beginning of mInvitedParticipants, then all other elements are erased.
mInvitedParticipants.erase(std::set_difference(mInvitedParticipants.begin(), mInvitedParticipants.end(),
joined_uuids.begin(), joined_uuids.end(),
mInvitedParticipants.begin()),
mInvitedParticipants.end());
}
void FSFloaterIM::sendParticipantsAddedNotification(const uuid_vec_t& uuids)
@ -1583,15 +1630,26 @@ BOOL FSFloaterIM::handleDragAndDrop(S32 x, S32 y, MASK mask,
void *cargo_data, EAcceptance *accept,
std::string& tooltip_msg)
{
if (cargo_type == DAD_PERSON)
{
if (dropPerson(static_cast<LLUUID*>(cargo_data), drop))
{
*accept = ACCEPT_YES_MULTI;
}
else
{
*accept = ACCEPT_NO;
}
}
if (mDialog == IM_NOTHING_SPECIAL)
else if (mDialog == IM_NOTHING_SPECIAL)
{
LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop,
cargo_type, cargo_data, accept);
}
// handle case for dropping calling cards (and folders of calling cards) onto invitation panel for invites
else if (isInviteAllowed())
else if (isInviteAllowed() && !mIsP2PChat)
{
*accept = ACCEPT_NO;
@ -1613,30 +1671,37 @@ BOOL FSFloaterIM::handleDragAndDrop(S32 x, S32 y, MASK mask,
return TRUE;
}
BOOL FSFloaterIM::dropCallingCard(LLInventoryItem* item, BOOL drop)
bool FSFloaterIM::dropCallingCard(LLInventoryItem* item, bool drop)
{
BOOL rv = isInviteAllowed();
if(rv && item && item->getCreatorUUID().notNull())
bool rv = true;
if(item && item->getCreatorUUID().notNull())
{
if(drop)
uuid_vec_t ids;
ids.push_back(item->getCreatorUUID());
if (canAddSelectedToChat(ids))
{
uuid_vec_t ids;
ids.push_back(item->getCreatorUUID());
inviteToSession(ids);
if (drop)
{
mPendingParticipants.push_back(item->getCreatorUUID());
}
}
else
{
rv = false;
}
}
else
{
// set to false if creator uuid is null.
rv = FALSE;
rv = false;
}
return rv;
}
BOOL FSFloaterIM::dropCategory(LLInventoryCategory* category, BOOL drop)
bool FSFloaterIM::dropCategory(LLInventoryCategory* category, bool drop)
{
BOOL rv = isInviteAllowed();
if(rv && category)
bool rv = true;
if(category)
{
LLInventoryModel::cat_array_t cats;
LLInventoryModel::item_array_t items;
@ -1649,9 +1714,9 @@ BOOL FSFloaterIM::dropCategory(LLInventoryCategory* category, BOOL drop)
S32 count = items.size();
if(count == 0)
{
rv = FALSE;
rv = false;
}
else if(drop)
else
{
uuid_vec_t ids;
ids.reserve(count);
@ -1659,17 +1724,48 @@ BOOL FSFloaterIM::dropCategory(LLInventoryCategory* category, BOOL drop)
{
ids.push_back(items.at(i)->getCreatorUUID());
}
inviteToSession(ids);
if (canAddSelectedToChat(ids))
{
if (drop)
{
mPendingParticipants.insert(mPendingParticipants.end(), ids.begin(), ids.end());
}
}
else
{
rv = false;
}
}
}
return rv;
}
bool FSFloaterIM::dropPerson(LLUUID* person_id, bool drop)
{
bool res = person_id && person_id->notNull();
if(res)
{
uuid_vec_t ids;
ids.push_back(*person_id);
res = canAddSelectedToChat(ids);
if(res && drop)
{
// these people will be added during the next draw() call
// (so they can be added all at once)
mPendingParticipants.push_back(*person_id);
}
}
return res;
}
BOOL FSFloaterIM::isInviteAllowed() const
{
return ((IM_SESSION_CONFERENCE_START == mDialog) ||
(IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID)));
return ( (IM_SESSION_CONFERENCE_START == mDialog)
|| (IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID))
|| mIsP2PChat);
}
class FSSessionInviteResponder : public LLHTTPClient::Responder
@ -1964,6 +2060,7 @@ void FSFloaterIM::initIMSession(const LLUUID& session_id)
if (session)
{
mIsP2PChat = session->isP2PSessionType();
mSessionInitialized = session->mSessionInitialized;
mDialog = session->mType;
}
@ -1991,3 +2088,158 @@ void FSFloaterIM::updateUnreadMessageNotification(S32 unread_messages)
mUnreadMessagesNotificationPanel->setVisible(TRUE);
}
}
void FSFloaterIM::onAddButtonClicked()
{
LLView* button = findChild<LLButton>("add_participant_btn");
LLFloater* root_floater = this;
LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&FSFloaterIM::addSessionParticipants, this, _1), TRUE, TRUE, FALSE, root_floater->getName(), button);
if (!picker)
{
return;
}
// Need to disable 'ok' button when selected users are already in conversation.
picker->setOkBtnEnableCb(boost::bind(&FSFloaterIM::canAddSelectedToChat, this, _1));
if (root_floater)
{
root_floater->addDependentFloater(picker);
}
}
bool FSFloaterIM::canAddSelectedToChat(const uuid_vec_t& uuids)
{
if (!LLIMModel::instance().findIMSession(mSessionID)
|| mDialog == IM_SESSION_GROUP_START
|| (mDialog == IM_SESSION_INVITE && gAgent.isInGroup(mSessionID)))
{
return false;
}
if (mIsP2PChat)
{
// For a P2P session just check if we are not adding the other participant.
for (uuid_vec_t::const_iterator id = uuids.begin();
id != uuids.end(); ++id)
{
if (*id == mOtherParticipantUUID)
{
return false;
}
}
}
else
{
// For a conference session we need to check against the list from LLSpeakerMgr,
// because this list may change when participants join or leave the session.
LLSpeakerMgr::speaker_list_t speaker_list;
LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID);
if (speaker_mgr)
{
speaker_mgr->getSpeakerList(&speaker_list, true);
}
for (uuid_vec_t::const_iterator id = uuids.begin();
id != uuids.end(); ++id)
{
for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin();
it != speaker_list.end(); ++it)
{
const LLPointer<LLSpeaker>& speaker = *it;
if (*id == speaker->mID)
{
return false;
}
}
}
}
return true;
}
void FSFloaterIM::addSessionParticipants(const uuid_vec_t& uuids)
{
if (mIsP2PChat)
{
LLSD payload;
LLSD args;
LLNotificationsUtil::add("ConfirmAddingChatParticipants", args, payload,
boost::bind(&FSFloaterIM::addP2PSessionParticipants, this, _1, _2, uuids));
}
else
{
if(findInstance(mSessionID))
{
// remember whom we have invited, to notify others later, when the invited ones actually join
mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end());
}
inviteToSession(uuids);
}
}
void FSFloaterIM::addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (option != 0)
{
return;
}
LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID);
// first check whether this is a voice session
bool is_voice_call = voice_channel != NULL && voice_channel->isActive();
uuid_vec_t temp_ids;
uuid_vec_t invited_ids;
// Add the initial participant of a P2P session
temp_ids.push_back(mOtherParticipantUUID);
temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end());
LLUUID session_id = mSessionID;
// then we can close the current session
if(findInstance(mSessionID))
{
// remember whom we have invited, to notify others later, when the invited ones actually join
mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end());
invited_ids.insert(invited_ids.end(), mInvitedParticipants.begin(), mInvitedParticipants.end());
// Ansariel: This will result in the floater actually being closed as opposed in CHUI!
onClose(false);
}
// Start a new ad hoc voice call if we invite new participants to a P2P call,
// or start a text chat otherwise.
if (is_voice_call)
{
session_id = LLAvatarActions::startAdhocCall(temp_ids, session_id);
}
else
{
session_id = LLAvatarActions::startConference(temp_ids, session_id);
}
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id);
if (session)
{
session->mInitialInvitedIDs = invited_ids;
}
}
void FSFloaterIM::loadInitialInvitedIDs()
{
LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSessionID);
if (session && !session->mInitialInvitedIDs.empty())
{
mInvitedParticipants = session->mInitialInvitedIDs;
session->mInitialInvitedIDs.clear();
}
}

View File

@ -150,6 +150,8 @@ public:
void updateUnreadMessageNotification(S32 unread_messages);
void loadInitialInvitedIDs();
protected:
/* virtual */
void onClickCloseBtn(bool app_quitting = false);
@ -174,8 +176,9 @@ private:
void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name);
void fetchAvatarName(LLUUID& agent_id);
BOOL dropCallingCard(LLInventoryItem* item, BOOL drop);
BOOL dropCategory(LLInventoryCategory* category, BOOL drop);
bool dropCallingCard(LLInventoryItem* item, bool drop);
bool dropCategory(LLInventoryCategory* category, bool drop);
bool dropPerson(LLUUID* person_id, bool drop);
BOOL isInviteAllowed() const;
BOOL inviteToSession(const uuid_vec_t& agent_ids);
@ -217,6 +220,11 @@ private:
void snoozeDurationCallback(const LLSD& notification, const LLSD& response);
void snooze(S32 duration = -1);
void onAddButtonClicked();
bool canAddSelectedToChat(const uuid_vec_t& uuids);
void addSessionParticipants(const uuid_vec_t& uuids);
void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids);
FSPanelChatControlPanel* mControlPanel;
LLUUID mSessionID;
S32 mLastMessageIndex;
@ -244,12 +252,17 @@ private:
bool mSessionInitialized;
LLSD mQueuedMsgsForInit;
bool mIsP2PChat;
LLVoiceChannel* mVoiceChannel;
S32 mInputEditorPad;
S32 mChatLayoutPanelHeight;
S32 mFloaterHeight;
uuid_vec_t mInvitedParticipants;
uuid_vec_t mPendingParticipants;
boost::signals2::connection mAvatarNameCacheConnection;
};

View File

@ -300,11 +300,17 @@ void LLAvatarActions::startCall(const LLUUID& id)
}
// static
void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id)
// <FS:Ansariel> [FS Communication UI]
//void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id)
const LLUUID LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id)
// </FS:Ansariel>
{
if (ids.size() == 0)
{
return;
// <FS:Ansariel> [FS Communication UI]
//return;
return LLUUID::null;
// </FS:Ansariel>
}
// convert vector into std::vector for addSession
@ -318,7 +324,7 @@ void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floate
{
make_ui_sound("UISndInvalidOp");
RlvUtil::notifyBlocked(RLV_STRING_BLOCKED_STARTCONF);
return;
return LLUUID::null;
}
id_array.push_back(idAgent);
// [/RLVa:KB]
@ -331,12 +337,18 @@ void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floate
ids[0], id_array, true, floater_id);
if (session_id == LLUUID::null)
{
return;
// <FS:Ansariel> [FS Communication UI]
//return;
return session_id;
// </FS:Ansariel>
}
gIMMgr->autoStartCallOnStartup(session_id);
make_ui_sound("UISndStartIM");
// <FS:Ansariel> [FS Communication UI]
return session_id;
}
/* AD *TODO: Is this function needed any more?
@ -361,7 +373,10 @@ bool LLAvatarActions::canCall()
}
// static
void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& floater_id)
// <FS:Ansariel> [FS Communication UI]
//void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& floater_id)
const LLUUID LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& floater_id)
// </FS:Ansariel>
{
// *HACK: Copy into dynamic array
std::vector<LLUUID> id_array;
@ -375,7 +390,7 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float
{
make_ui_sound("UISndInvalidOp");
RlvUtil::notifyBlocked(RLV_STRING_BLOCKED_STARTCONF);
return;
return LLUUID::null;
}
id_array.push_back(idAgent);
// [/RLVa:KB]
@ -386,7 +401,10 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float
if (session_id == LLUUID::null)
{
return;
// <FS:Ansariel> [FS Communication UI]
//return;
return session_id;
// </FS:Ansariel>
}
// <FS:Ansariel> [FS communication UI]
@ -395,6 +413,9 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float
// </FS:Ansariel> [FS communication UI]
make_ui_sound("UISndStartIM");
// <FS:Ansariel> [FS Communication UI]
return session_id;
}
static const char* get_profile_floater_name(const LLUUID& avatar_id)

View File

@ -84,12 +84,18 @@ public:
/**
* Start an ad-hoc conference voice call with multiple users in a specific IM floater.
*/
static void startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
// <FS:Ansariel> [FS Communication UI]
//static void startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
static const LLUUID startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
// </FS:Ansariel>
/**
* Start conference chat with the given avatars in a specific IM floater.
*/
static void startConference(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
// <FS:Ansariel> [FS Communication UI]
//static void startConference(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
static const LLUUID startConference(const uuid_vec_t& ids, const LLUUID& floater_id = LLUUID::null);
// </FS:Ansariel>
/**
* Show avatar profile.

View File

@ -120,6 +120,8 @@ public:
// [/SL:KB]
LLUUID mOtherParticipantID;
uuid_vec_t mInitialTargetIDs;
// <FS:Ansariel> Needed to store IDs of initially invited agents; required for FS Communication UI, as original IM floater gets destroyed
uuid_vec_t mInitialInvitedIDs;
std::string mHistoryFileName;
// connection to voice channel state change signal

View File

@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<floater name="panel_im">
<floater.string name="participant_added" value="[NAME] wurde zur Unterhaltung eingeladen."/>
<floater.string name="multiple_participants_added" value="[NAME] wurden zur Unterhaltung eingeladen."/>
<layout_stack name="ls_control_panel">
<layout_panel name="gprofile_panel">
<button name="group_info_btn" tool_tip="Zeige Gruppenprofil"/>
@ -34,6 +36,9 @@
<layout_panel name="im_history_btn_panel">
<button name="im_history_btn" tool_tip="Gesprächsverlauf öffnen"/>
</layout_panel>
<layout_panel name="add_participant_panel">
<button name="add_participant_btn" tool_tip="Jemanden zu dieser Unterhaltung hinzufügen"/>
</layout_panel>
<layout_panel name="send_sysinfo_btn_panel">
<button name="send_sysinfo_btn" tool_tip="System-Informationen übermitteln"/>
</layout_panel>

View File

@ -20,7 +20,13 @@
min_width="250"
save_rect="true"
min_height="150">
<floater.string
name="participant_added"
value="[NAME] was invited to the conversation."/>
<floater.string
name="multiple_participants_added"
value="[NAME] were invited to the conversation."/>
<layout_stack
border_size="0"
animate="false"
@ -343,6 +349,33 @@
parameter="history"/>
</button>
</layout_panel>
<layout_panel
name="add_participant_panel"
auto_resize="false"
user_resize="false"
top="0"
layout="topleft"
height="20"
width="26"
follows="left|top">
<button
follows="left|top"
top="0"
height="20"
left_delta="2"
image_overlay="Conv_toolbar_add_person"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
tool_tip="Add someone to this conversation"
name="add_participant_btn"
width="24">
<button.commit_callback
function="IMSession.Menu.Action"
parameter="add_participant"/>
</button>
</layout_panel>
<layout_panel
name="send_sysinfo_btn_panel"

View File

@ -20,6 +20,12 @@
min_width="250"
save_rect="true"
min_height="150">
<floater.string
name="participant_added"
value="[NAME] was invited to the conversation."/>
<floater.string
name="multiple_participants_added"
value="[NAME] were invited to the conversation."/>
<layout_stack
border_size="0"
@ -329,6 +335,33 @@
parameter="history"/>
</button>
</layout_panel>
<layout_panel
name="add_participant_panel"
auto_resize="false"
user_resize="false"
top="0"
layout="topleft"
height="20"
width="26"
follows="left|top">
<button
follows="left|top"
top="0"
height="20"
left_delta="2"
image_overlay="Conv_toolbar_add_person"
image_hover_unselected="Toolbar_Middle_Over"
image_selected="Toolbar_Middle_Selected"
image_unselected="Toolbar_Middle_Off"
tool_tip="Add someone to this conversation"
name="add_participant_btn"
width="24">
<button.commit_callback
function="IMSession.Menu.Action"
parameter="add_participant"/>
</button>
</layout_panel>
<layout_panel
name="send_sysinfo_btn_panel"