/** * @file llpanelgroupnotices.cpp * @brief A panel to display group notices. * * $LicenseInfo:firstyear=2006&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llpanelgroupnotices.h" #include "llview.h" #include "llavatarnamecache.h" #include "llinventory.h" #include "llviewerinventory.h" #include "llinventorydefines.h" #include "llinventoryfunctions.h" #include "llinventoryicon.h" #include "llinventorymodel.h" #include "llagent.h" #include "llagentui.h" #include "lllineeditor.h" #include "lltexteditor.h" #include "llbutton.h" #include "lliconctrl.h" #include "llcheckboxctrl.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "lltextbox.h" #include "lltrans.h" #include "roles_constants.h" #include "llviewerwindow.h" #include "llviewermessage.h" #include "llnotificationsutil.h" #include "llgiveinventory.h" static LLPanelInjector t_panel_group_notices("panel_group_notices"); ///////////////////////// // LLPanelGroupNotices // ///////////////////////// //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLDropTarget // // This handy class is a simple way to drop something on another // view. It handles drop events, always setting itself to the size of // its parent. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLGroupDropTarget : public LLView { public: struct Params : public LLInitParam::Block { // *NOTE: These parameters logically Mandatory, but are not // specified in XML files, hence Optional Optional panel; Optional group_id; Params() : panel("panel"), group_id("group_id") { changeDefault(mouse_opaque, false); changeDefault(follows.flags, FOLLOWS_ALL); } }; LLGroupDropTarget(const Params&); ~LLGroupDropTarget() {}; // // LLView functionality virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); void setPanel (LLPanelGroupNotices* panel) {mGroupNoticesPanel = panel;}; void setGroup (LLUUID group) {mGroupID = group;}; protected: LLPanelGroupNotices* mGroupNoticesPanel; LLUUID mGroupID; }; static LLDefaultChildRegistry::Register r("group_drop_target"); LLGroupDropTarget::LLGroupDropTarget(const LLGroupDropTarget::Params& p) : LLView(p), mGroupNoticesPanel(p.panel), mGroupID(p.group_id) {} bool LLGroupDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { bool handled = false; if (!gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND)) { *accept = ACCEPT_NO; return true; } if(getParent()) { // check if inside //LLRect parent_rect = mParentView->getRect(); //getRect().set(0, parent_rect.getHeight(), parent_rect.getWidth(), 0); handled = true; // check the type switch(cargo_type) { case DAD_TEXTURE: case DAD_SOUND: case DAD_LANDMARK: case DAD_SCRIPT: case DAD_OBJECT: case DAD_NOTECARD: case DAD_CLOTHING: case DAD_BODYPART: case DAD_ANIMATION: case DAD_GESTURE: case DAD_CALLINGCARD: case DAD_MESH: case DAD_SETTINGS: case DAD_MATERIAL: { LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data; if(gInventory.getItem(inv_item->getUUID()) && LLGiveInventory::isInventoryGroupGiveAcceptable(inv_item)) { // *TODO: get multiple object transfers working *accept = ACCEPT_YES_COPY_SINGLE; if(drop) { mGroupNoticesPanel->setItem(inv_item); } } else { // It's not in the user's inventory (it's probably // in an object's contents), so disallow dragging // it here. You can't give something you don't // yet have. *accept = ACCEPT_NO; } break; } case DAD_CATEGORY: default: *accept = ACCEPT_NO; break; } } return handled; } //----------------------------------------------------------------------------- // LLPanelGroupNotices //----------------------------------------------------------------------------- std::string build_notice_date(const U32& the_time) { // ISO 8601 date format time_t t = (time_t)the_time; if (!t) { time(&t); } std::string dateStr = "["+ LLTrans::getString("LTimeYear") + "]/[" + LLTrans::getString("LTimeMthNum") + "]/[" + LLTrans::getString("LTimeDay") + "] [" + LLTrans::getString("LTimeHour") + "]:[" + LLTrans::getString("LTimeMin") + "]:[" + LLTrans::getString("LTimeSec") + "]"; LLSD substitution; substitution["datetime"] = (S32) t; LLStringUtil::format (dateStr, substitution); return dateStr; } LLPanelGroupNotices::LLPanelGroupNotices() : LLPanelGroupTab(), mInventoryItem(NULL), mInventoryOffer(NULL) { } LLPanelGroupNotices::~LLPanelGroupNotices() { sInstances.erase(mGroupID); if (mInventoryOffer) { // Cancel the inventory offer. mInventoryOffer->forceResponse(IOR_DECLINE); mInventoryOffer = NULL; } } BOOL LLPanelGroupNotices::isVisibleByAgent(LLAgent* agentp) { return mAllowEdit && agentp->hasPowerInGroup(mGroupID, GP_NOTICES_SEND | GP_NOTICES_RECEIVE); } bool LLPanelGroupNotices::postBuild() { constexpr bool recurse = true; mNoticesList = getChild("notice_list",recurse); mNoticesList->setCommitOnSelectionChange(TRUE); mNoticesList->setCommitCallback(onSelectNotice, this); mBtnNewMessage = getChild("create_new_notice",recurse); mBtnNewMessage->setClickedCallback(onClickNewMessage, this); mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); mBtnGetPastNotices = getChild("refresh_notices",recurse); mBtnGetPastNotices->setClickedCallback(onClickRefreshNotices, this); // Create mCreateSubject = getChild("create_subject",recurse); mCreateMessage = getChild("create_message",recurse); mCreateInventoryName = getChild("create_inventory_name",recurse); mCreateInventoryName->setTabStop(false); mCreateInventoryName->setEnabled(false); mCreateInventoryIcon = getChild("create_inv_icon",recurse); mCreateInventoryIcon->setVisible(false); mBtnSendMessage = getChild("send_notice",recurse); mBtnSendMessage->setClickedCallback(onClickSendMessage, this); mBtnRemoveAttachment = getChild("remove_attachment",recurse); mBtnRemoveAttachment->setClickedCallback(onClickRemoveAttachment, this); mBtnRemoveAttachment->setEnabled(false); // View mViewSubject = getChild("view_subject",recurse); mViewMessage = getChild("view_message",recurse); mViewInventoryName = getChild("view_inventory_name",recurse); mViewInventoryName->setTabStop(false); mViewInventoryName->setEnabled(false); mViewInventoryIcon = getChild("view_inv_icon",recurse); mViewInventoryIcon->setVisible(false); mBtnOpenAttachment = getChild("open_attachment",recurse); mBtnOpenAttachment->setClickedCallback(onClickOpenAttachment, this); mNoNoticesStr = getString("no_notices_text"); mPanelCreateNotice = getChild("panel_create_new_notice",recurse); mPanelViewNotice = getChild("panel_view_past_notice",recurse); LLGroupDropTarget* target = getChild ("drop_target"); target->setPanel (this); target->setGroup (mGroupID); arrangeNoticeView(VIEW_PAST_NOTICE); return LLPanelGroupTab::postBuild(); } void LLPanelGroupNotices::activate() { if (mNoticesList) { mNoticesList->deleteAllItems(); mKnownNoticeIds.clear(); } mPrevSelectedNotice = LLUUID(); BOOL can_send = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND); BOOL can_receive = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_RECEIVE); mPanelViewNotice->setEnabled(can_receive); mPanelCreateNotice->setEnabled(can_send); // Always disabled to stop direct editing of attachment names mCreateInventoryName->setEnabled(FALSE); mViewInventoryName->setEnabled(FALSE); // If we can receive notices, grab them right away. if (can_receive) { onClickRefreshNotices(this); } } void LLPanelGroupNotices::setItem(LLPointer inv_item) { mInventoryItem = inv_item; BOOL item_is_multi = FALSE; if ( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS ) { item_is_multi = TRUE; }; std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), inv_item->getInventoryType(), inv_item->getFlags(), item_is_multi ); mCreateInventoryIcon->setValue(icon_name); mCreateInventoryIcon->setVisible(TRUE); std::stringstream ss; ss << " " << mInventoryItem->getName(); mCreateInventoryName->setText(ss.str()); mBtnRemoveAttachment->setEnabled(TRUE); } void LLPanelGroupNotices::onClickRemoveAttachment(void* data) { LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; self->mInventoryItem = NULL; self->mCreateInventoryName->clear(); self->mCreateInventoryIcon->setVisible(FALSE); self->mBtnRemoveAttachment->setEnabled(FALSE); } //static void LLPanelGroupNotices::onClickOpenAttachment(void* data) { LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; self->mInventoryOffer->forceResponse(IOR_ACCEPT); self->mInventoryOffer = NULL; self->mBtnOpenAttachment->setEnabled(FALSE); } void LLPanelGroupNotices::onClickSendMessage(void* data) { LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; if (self->mCreateSubject->getText().empty()) { // Must supply a subject LLNotificationsUtil::add("MustSpecifyGroupNoticeSubject"); return; } send_group_notice( self->mGroupID, self->mCreateSubject->getText(), self->mCreateMessage->getText(), self->mInventoryItem); //instantly add new notice. actual notice will be added after ferreshNotices call LLUUID id = LLUUID::generateNewID(); std::string subj = self->mCreateSubject->getText(); std::string name ; LLAgentUI::buildFullname(name); U32 timestamp = 0; LLSD row; row["id"] = id; row["columns"][0]["column"] = "icon"; row["columns"][1]["column"] = "subject"; row["columns"][1]["value"] = subj; row["columns"][2]["column"] = "from"; row["columns"][2]["value"] = name; row["columns"][3]["column"] = "date"; row["columns"][3]["value"] = build_notice_date(timestamp); row["columns"][4]["column"] = "sort"; row["columns"][4]["value"] = llformat( "%u", timestamp); self->mNoticesList->addElement(row, ADD_BOTTOM); self->mKnownNoticeIds.insert(id); self->mCreateMessage->clear(); self->mCreateSubject->clear(); onClickRemoveAttachment(data); self->arrangeNoticeView(VIEW_PAST_NOTICE); } //static void LLPanelGroupNotices::onClickNewMessage(void* data) { LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; self->arrangeNoticeView(CREATE_NEW_NOTICE); if (self->mInventoryOffer) { self->mInventoryOffer->forceResponse(IOR_DECLINE); self->mInventoryOffer = NULL; } self->mCreateSubject->clear(); self->mCreateMessage->clear(); if (self->mInventoryItem) onClickRemoveAttachment(self); self->mNoticesList->deselectAllItems(TRUE); // TRUE == don't commit on chnage } void LLPanelGroupNotices::refreshNotices() { onClickRefreshNotices(this); } void LLPanelGroupNotices::clearNoticeList() { mPrevSelectedNotice = mNoticesList->getStringUUIDSelectedItem(); mNoticesList->deleteAllItems(); mKnownNoticeIds.clear(); } void LLPanelGroupNotices::onClickRefreshNotices(void* data) { LL_DEBUGS() << "LLPanelGroupNotices::onClickGetPastNotices" << LL_ENDL; LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; self->clearNoticeList(); LLMessageSystem* msg = gMessageSystem; msg->newMessage("GroupNoticesListRequest"); msg->nextBlock("AgentData"); msg->addUUID("AgentID",gAgent.getID()); msg->addUUID("SessionID",gAgent.getSessionID()); msg->nextBlock("Data"); msg->addUUID("GroupID",self->mGroupID); gAgent.sendReliableMessage(); } //static std::map LLPanelGroupNotices::sInstances; // static void LLPanelGroupNotices::processGroupNoticesListReply(LLMessageSystem* msg, void** data) { LLUUID group_id; msg->getUUID("AgentData", "GroupID", group_id); std::map::iterator it = sInstances.find(group_id); if (it == sInstances.end()) { LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." << LL_ENDL; return; } LLPanelGroupNotices* selfp = it->second; if(!selfp) { LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." << LL_ENDL; return; } selfp->processNotices(msg); } void LLPanelGroupNotices::processNotices(LLMessageSystem* msg) { LLUUID id; std::string subj; std::string name; U32 timestamp; bool has_attachment; U8 asset_type; S32 i=0; S32 count = msg->getNumberOfBlocks("Data"); mNoticesList->setEnabled(TRUE); //save sort state and set unsorted state to prevent unnecessary //sorting while adding notices bool save_sort = mNoticesList->isSorted(); mNoticesList->setNeedsSort(false); for (;igetUUID("Data","NoticeID",id,i); if (1 == count && id.isNull()) { // Only one entry, the dummy entry. mNoticesList->setCommentText(mNoNoticesStr); mNoticesList->setEnabled(FALSE); return; } // Due to some network delays we can receive notice list more than once... // So add only unique notices if (mKnownNoticeIds.find(id) != mKnownNoticeIds.end()) { // If items with this ID already in the list - skip it continue; } msg->getString("Data","Subject",subj,i); msg->getString("Data","FromName",name,i); msg->getBOOL("Data","HasAttachment",has_attachment,i); msg->getU8("Data","AssetType",asset_type,i); msg->getU32("Data","Timestamp",timestamp,i); // we only have the legacy name here, convert it to a username name = LLCacheName::buildUsername(name); LLSD row; row["id"] = id; row["columns"][0]["column"] = "icon"; if (has_attachment) { std::string icon_name = LLInventoryIcon::getIconName( (LLAssetType::EType)asset_type, LLInventoryType::IT_NONE); row["columns"][0]["type"] = "icon"; row["columns"][0]["value"] = icon_name; } row["columns"][1]["column"] = "subject"; row["columns"][1]["value"] = subj; row["columns"][2]["column"] = "from"; row["columns"][2]["value"] = name; row["columns"][3]["column"] = "date"; row["columns"][3]["value"] = build_notice_date(timestamp); row["columns"][4]["column"] = "sort"; row["columns"][4]["value"] = llformat( "%u", timestamp); mNoticesList->addElement(row, ADD_BOTTOM); mKnownNoticeIds.insert(id); } mNoticesList->setNeedsSort(save_sort); mNoticesList->updateSort(); if (mPanelViewNotice->getVisible()) { if (!mNoticesList->selectByID(mPrevSelectedNotice)) { mNoticesList->selectFirstItem(); } } } void LLPanelGroupNotices::onSelectNotice(LLUICtrl* ctrl, void* data) { LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; if(!self) return; LLScrollListItem* item = self->mNoticesList->getFirstSelected(); if (!item) return; LLMessageSystem* msg = gMessageSystem; msg->newMessage("GroupNoticeRequest"); msg->nextBlock("AgentData"); msg->addUUID("AgentID",gAgent.getID()); msg->addUUID("SessionID",gAgent.getSessionID()); msg->nextBlock("Data"); msg->addUUID("GroupNoticeID",item->getUUID()); gAgent.sendReliableMessage(); LL_DEBUGS() << "Item " << item->getUUID() << " selected." << LL_ENDL; } void LLPanelGroupNotices::showNotice(const std::string& subject, const std::string& message, const bool& has_inventory, const std::string& inventory_name, LLOfferInfo* inventory_offer) { arrangeNoticeView(VIEW_PAST_NOTICE); if(mViewSubject) mViewSubject->setText(subject); if(mViewMessage) mViewMessage->setText(message); if (mInventoryOffer) { // Cancel the inventory offer for the previously viewed notice mInventoryOffer->forceResponse(IOR_DECLINE); mInventoryOffer = NULL; } if (inventory_offer) { mInventoryOffer = inventory_offer; std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType, LLInventoryType::IT_TEXTURE); mViewInventoryIcon->setValue(icon_name); mViewInventoryIcon->setVisible(TRUE); std::stringstream ss; ss << " " << inventory_name; mViewInventoryName->setText(ss.str()); mBtnOpenAttachment->setEnabled(TRUE); } else { mViewInventoryName->clear(); mViewInventoryIcon->setVisible(FALSE); mBtnOpenAttachment->setEnabled(FALSE); } } void LLPanelGroupNotices::arrangeNoticeView(ENoticeView view_type) { if (CREATE_NEW_NOTICE == view_type) { mPanelCreateNotice->setVisible(TRUE); mPanelViewNotice->setVisible(FALSE); } else { mPanelCreateNotice->setVisible(FALSE); mPanelViewNotice->setVisible(TRUE); mBtnOpenAttachment->setEnabled(FALSE); } } void LLPanelGroupNotices::setGroupID(const LLUUID& id) { sInstances.erase(mGroupID); LLPanelGroupTab::setGroupID(id); sInstances[mGroupID] = this; mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); LLGroupDropTarget* target = getChild ("drop_target"); target->setPanel (this); target->setGroup (mGroupID); if(mViewMessage) mViewMessage->clear(); if(mViewInventoryName) mViewInventoryName->clear(); activate(); }