/** * @file llsyswellwindow.cpp * @brief // TODO * $LicenseInfo:firstyear=2000&license=viewergpl$ * * Copyright (c) 2000-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" // must be first include #include "llflatlistview.h" #include "llsyswellwindow.h" #include "llbottomtray.h" #include "llviewercontrol.h" #include "llviewerwindow.h" #include "llchiclet.h" #include "lltoastpanel.h" #include "llnotificationmanager.h" // IM session ID can be the same as Avatar UUID. (See LLIMMgr::computeSessionID) // Probably notification ID also can be the same as Avatar UUID. // In case when session ID & notification ID are the same it will be impossible to add both // appropriate Items into Flat List. // Functions below are intended to wrap passed LLUUID into LLSD value with different "type". // Use them anywhere you need to add, get, remove items via the list inline LLSD get_notification_value(const LLUUID& notification_id) { return LLSD() .insert("type", "notification") .insert("uuid", notification_id); } inline LLSD get_session_value(const LLUUID& session_id) { return LLSD() .insert("type", "im_chiclet") .insert("uuid", session_id); } //--------------------------------------------------------------------------------- LLSysWellWindow::LLSysWellWindow(const LLSD& key) : LLDockableFloater(NULL, key), mChannel(NULL), mMessageList(NULL), mSeparator(NULL) { LLIMMgr::getInstance()->addSessionObserver(this); LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLSysWellWindow::findIMChiclet, this, _1)); mTypedItemsCount[IT_NOTIFICATION] = 0; mTypedItemsCount[IT_INSTANT_MESSAGE] = 0; } //--------------------------------------------------------------------------------- BOOL LLSysWellWindow::postBuild() { mMessageList = getChild("notification_list"); // init connections to the list's update events connectListUpdaterToSignal("notify"); connectListUpdaterToSignal("groupnotify"); // get a corresponding channel initChannel(); LLPanel::Params params; mSeparator = LLUICtrlFactory::create(params); LLUICtrlFactory::instance().buildPanel(mSeparator, "panel_separator.xml"); LLRect rc = mSeparator->getRect(); rc.setOriginAndSize(0, 0, mMessageList->getItemsRect().getWidth(), rc.getHeight()); mSeparator->setRect(rc); mSeparator->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP); mSeparator->setVisible(FALSE); mMessageList->addItem(mSeparator); return LLDockableFloater::postBuild(); } //--------------------------------------------------------------------------------- void LLSysWellWindow::setMinimized(BOOL minimize) { setVisible(!minimize); LLFloater::setMinimized(minimize); } //--------------------------------------------------------------------------------- void LLSysWellWindow::connectListUpdaterToSignal(std::string notification_type) { LLNotificationsUI::LLNotificationManager* manager = LLNotificationsUI::LLNotificationManager::getInstance(); LLNotificationsUI::LLEventHandler* n_handler = manager->getHandlerForNotification(notification_type); if(n_handler) { n_handler->setNotificationIDCallback(boost::bind(&LLSysWellWindow::removeItemByID, this, _1)); } else { llwarns << "LLSysWellWindow::connectListUpdaterToSignal() - could not get a handler for '" << notification_type <<"' type of notifications" << llendl; } } //--------------------------------------------------------------------------------- void LLSysWellWindow::onChicletClick() { // 1 - remove StartUp toast and channel if present if(!LLNotificationsUI::LLScreenChannel::getStartUpToastShown()) { LLNotificationsUI::LLChannelManager::getInstance()->onStartUpToastClose(); } // 2 - toggle instance of SysWell's chiclet-window toggleWindow(); } //--------------------------------------------------------------------------------- LLSysWellWindow::~LLSysWellWindow() { LLIMMgr::getInstance()->removeSessionObserver(this); } //--------------------------------------------------------------------------------- void LLSysWellWindow::addItem(LLSysWellItem::Params p) { LLSD value = get_notification_value(p.notification_id); // do not add clones if( mMessageList->getItemByValue(value)) return; LLSysWellItem* new_item = new LLSysWellItem(p); if (mMessageList->addItem(new_item, value, ADD_TOP)) { handleItemAdded(IT_NOTIFICATION); reshapeWindow(); new_item->setOnItemCloseCallback(boost::bind(&LLSysWellWindow::onItemClose, this, _1)); new_item->setOnItemClickCallback(boost::bind(&LLSysWellWindow::onItemClick, this, _1)); } else { llwarns << "Unable to add Notification into the list, notification ID: " << p.notification_id << ", title: " << p.title << llendl; new_item->die(); } } //--------------------------------------------------------------------------------- void LLSysWellWindow::clear() { mMessageList->clear(); } //--------------------------------------------------------------------------------- void LLSysWellWindow::removeItemByID(const LLUUID& id) { if(mMessageList->removeItemByValue(get_notification_value(id))) { handleItemRemoved(IT_NOTIFICATION); reshapeWindow(); } else { llwarns << "Unable to remove notification from the list, ID: " << id << llendl; } // hide chiclet window if there are no items left if(isWindowEmpty()) { setVisible(FALSE); } } //--------------------------------------------------------------------------------- void LLSysWellWindow::onItemClick(LLSysWellItem* item) { LLUUID id = item->getID(); if(mChannel) mChannel->loadStoredToastByNotificationIDToChannel(id); } //--------------------------------------------------------------------------------- void LLSysWellWindow::onItemClose(LLSysWellItem* item) { LLUUID id = item->getID(); removeItemByID(id); if(mChannel) mChannel->killToastByNotificationID(id); } //-------------------------------------------------------------------------- void LLSysWellWindow::onStoreToast(LLPanel* info_panel, LLUUID id) { LLSysWellItem::Params p; p.notification_id = id; p.title = static_cast(info_panel)->getTitle(); addItem(p); } //--------------------------------------------------------------------------------- void LLSysWellWindow::initChannel() { LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( LLUUID(gSavedSettings.getString("NotificationChannelUUID"))); mChannel = dynamic_cast(channel); if(mChannel) { mChannel->setOnStoreToastCallback(boost::bind(&LLSysWellWindow::onStoreToast, this, _1, _2)); } else { llwarns << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << llendl; } } //--------------------------------------------------------------------------------- void LLSysWellWindow::getAllowedRect(LLRect& rect) { rect = gViewerWindow->getWorldViewRect(); } //--------------------------------------------------------------------------------- void LLSysWellWindow::toggleWindow() { if (getDockControl() == NULL) { setDockControl(new LLDockControl( LLBottomTray::getInstance()->getSysWell(), this, getDockTongue(), LLDockControl::TOP, boost::bind(&LLSysWellWindow::getAllowedRect, this, _1))); } if(!getVisible()) { if(mChannel) { mChannel->removeAndStoreAllStorableToasts(); } if(isWindowEmpty()) { return; } setVisible(TRUE); } else { setVisible(FALSE); } //set window in foreground setFocus(getVisible()); } //--------------------------------------------------------------------------------- void LLSysWellWindow::setVisible(BOOL visible) { if(visible) { if (LLBottomTray::instanceExists()) { LLBottomTray::getInstance()->getSysWell()->setToggleState(TRUE); } } else { if (LLBottomTray::instanceExists()) { LLBottomTray::getInstance()->getSysWell()->setToggleState(FALSE); } } LLDockableFloater::setVisible(visible); // update notification channel state if(mChannel) { mChannel->updateShowToastsState(); } } //--------------------------------------------------------------------------------- void LLSysWellWindow::setDocked(bool docked, bool pop_on_undock) { LLDockableFloater::setDocked(docked, pop_on_undock); // update notification channel state if(mChannel) { mChannel->updateShowToastsState(); } } //--------------------------------------------------------------------------------- void LLSysWellWindow::reshapeWindow() { // save difference between floater height and the list height to take it into account while calculating new window height // it includes height from floater top to list top and from floater bottom and list bottom static S32 parent_list_delta_height = getRect().getHeight() - mMessageList->getRect().getHeight(); S32 notif_list_height = mMessageList->getItemsRect().getHeight() + 2 * mMessageList->getBorderWidth(); LLRect curRect = getRect(); S32 new_window_height = notif_list_height + parent_list_delta_height; if (new_window_height > MAX_WINDOW_HEIGHT) { new_window_height = MAX_WINDOW_HEIGHT; } S32 newY = curRect.mTop + new_window_height - curRect.getHeight(); curRect.setLeftTopAndSize(curRect.mLeft, newY, MIN_WINDOW_WIDTH, new_window_height); reshape(curRect.getWidth(), curRect.getHeight(), TRUE); setRect(curRect); } //--------------------------------------------------------------------------------- LLChiclet* LLSysWellWindow::findIMChiclet(const LLUUID& sessionId) { LLChiclet* res = NULL; RowPanel* panel = mMessageList->getTypedItemByValue(get_session_value(sessionId)); if (panel != NULL) { res = panel->mChiclet; } return res; } //--------------------------------------------------------------------------------- void LLSysWellWindow::addIMRow(const LLUUID& sessionId, S32 chicletCounter, const std::string& name, const LLUUID& otherParticipantId) { RowPanel* item = new RowPanel(this, sessionId, chicletCounter, name, otherParticipantId); if (mMessageList->insertItemAfter(mSeparator, item, get_session_value(sessionId))) { handleItemAdded(IT_INSTANT_MESSAGE); } else { llwarns << "Unable to add IM Row into the list, sessionID: " << sessionId << ", name: " << name << ", other participant ID: " << otherParticipantId << llendl; item->die(); } } //--------------------------------------------------------------------------------- void LLSysWellWindow::delIMRow(const LLUUID& sessionId) { if (mMessageList->removeItemByValue(get_session_value(sessionId))) { handleItemRemoved(IT_INSTANT_MESSAGE); } else { llwarns << "Unable to remove IM Row from the list, sessionID: " << sessionId << llendl; } // remove all toasts that belong to this session from a screen if(mChannel) mChannel->removeToastsBySessionID(sessionId); // hide chiclet window if there are no items left if(isWindowEmpty()) { setVisible(FALSE); } } //--------------------------------------------------------------------------------- bool LLSysWellWindow::isWindowEmpty() { // keep in mind, mSeparator is always in the list return mMessageList->size() == 1; } //--------------------------------------------------------------------------------- //virtual void LLSysWellWindow::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) { //*TODO get rid of get_session_value, session_id's are unique, cause performance degradation with lots chiclets (IB) if (mMessageList->getItemByValue(get_session_value(session_id)) == NULL) { S32 chicletCounter = LLIMModel::getInstance()->getNumUnread(session_id); if (chicletCounter > -1) { addIMRow(session_id, chicletCounter, name, other_participant_id); reshapeWindow(); } } } //--------------------------------------------------------------------------------- //virtual void LLSysWellWindow::sessionRemoved(const LLUUID& sessionId) { delIMRow(sessionId); reshapeWindow(); LLBottomTray::getInstance()->getSysWell()->updateUreadIMNotifications(); } void LLSysWellWindow::handleItemAdded(EItemType added_item_type) { bool should_be_shown = ++mTypedItemsCount[added_item_type] == 1 && anotherTypeExists(added_item_type); if (should_be_shown && !mSeparator->getVisible()) { mSeparator->setVisible(TRUE); // refresh list to recalculate mSeparator position mMessageList->reshape(mMessageList->getRect().getWidth(), mMessageList->getRect().getHeight()); } } void LLSysWellWindow::handleItemRemoved(EItemType removed_item_type) { bool should_be_hidden = --mTypedItemsCount[removed_item_type] == 0; if (should_be_hidden && mSeparator->getVisible()) { mSeparator->setVisible(FALSE); // refresh list to recalculate mSeparator position mMessageList->reshape(mMessageList->getRect().getWidth(), mMessageList->getRect().getHeight()); } } bool LLSysWellWindow::anotherTypeExists(EItemType item_type) { bool exists = false; switch(item_type) { case IT_INSTANT_MESSAGE: if (mTypedItemsCount[IT_NOTIFICATION] > 0) { exists = true; } break; case IT_NOTIFICATION: if (mTypedItemsCount[IT_INSTANT_MESSAGE] > 0) { exists = true; } break; } return exists; } //--------------------------------------------------------------------------------- LLSysWellWindow::RowPanel::RowPanel(const LLSysWellWindow* parent, const LLUUID& sessionId, S32 chicletCounter, const std::string& name, const LLUUID& otherParticipantId) : LLPanel(LLPanel::Params()), mChiclet(NULL), mParent(parent) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_activeim_row.xml", NULL); // Choose which of the pre-created chiclets (IM/group) to use. // The other one gets hidden. LLIMChiclet::EType im_chiclet_type = LLIMChiclet::getIMSessionType(sessionId); switch (im_chiclet_type) { case LLIMChiclet::TYPE_GROUP: case LLIMChiclet::TYPE_AD_HOC: mChiclet = getChild("group_chiclet"); childSetVisible("p2p_chiclet", false); break; case LLIMChiclet::TYPE_UNKNOWN: // assign mChiclet a non-null value anyway case LLIMChiclet::TYPE_IM: mChiclet = getChild("p2p_chiclet"); childSetVisible("group_chiclet", false); break; } // Initialize chiclet. mChiclet->setCounter(chicletCounter); mChiclet->setSessionId(sessionId); mChiclet->setIMSessionName(name); mChiclet->setOtherParticipantId(otherParticipantId); LLTextBox* contactName = getChild("contact_name"); contactName->setValue(name); mCloseBtn = getChild("hide_btn"); mCloseBtn->setCommitCallback(boost::bind(&LLSysWellWindow::RowPanel::onClose, this)); } //--------------------------------------------------------------------------------- LLSysWellWindow::RowPanel::~RowPanel() { } //--------------------------------------------------------------------------------- void LLSysWellWindow::RowPanel::onClose() { gIMMgr->removeSession(mChiclet->getSessionId()); // This row panel will be removed from the list in LLSysWellWindow::sessionRemoved(). } //--------------------------------------------------------------------------------- void LLSysWellWindow::RowPanel::onMouseEnter(S32 x, S32 y, MASK mask) { setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemSelected")); } //--------------------------------------------------------------------------------- void LLSysWellWindow::RowPanel::onMouseLeave(S32 x, S32 y, MASK mask) { setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemUnselected")); } //--------------------------------------------------------------------------------- // virtual BOOL LLSysWellWindow::RowPanel::handleMouseDown(S32 x, S32 y, MASK mask) { // Pass the mouse down event to the chiclet (EXT-596). if (!mChiclet->pointInView(x, y) && !mCloseBtn->getRect().pointInRect(x, y)) // prevent double call of LLIMChiclet::onMouseDown() mChiclet->onMouseDown(); return LLPanel::handleMouseDown(x, y, mask); } // EOF