/** * @file llfloatertwitter.cpp * @brief Implementation of llfloatertwitter * @author cho@lindenlab.com * * $LicenseInfo:firstyear=2013&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2013, 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 "llfloatertwitter.h" #include "llagent.h" #include "llagentui.h" #include "llcheckboxctrl.h" #include "llcombobox.h" #include "lltwitterconnect.h" #include "llfloaterreg.h" #include "lliconctrl.h" #include "llresmgr.h" // LLLocale #include "llsdserialize.h" #include "llloadingindicator.h" #include "llplugincookiestore.h" #include "llslurl.h" #include "lltrans.h" #include "llsnapshotlivepreview.h" #include "llviewerregion.h" #include "llviewercontrol.h" #include "llviewermedia.h" #include "lltabcontainer.h" static LLRegisterPanelClassWrapper t_panel_photo("lltwitterphotopanel"); static LLRegisterPanelClassWrapper t_panel_account("lltwitteraccountpanel"); const S32 MAX_POSTCARD_DATASIZE = 1024 * 1024; // one megabyte const std::string DEFAULT_PHOTO_QUERY_PARAMETERS = "?sourceid=slshare_photo&utm_source=twitter&utm_medium=photo&utm_campaign=slshare"; /////////////////////////// //LLTwitterPhotoPanel/////// /////////////////////////// LLTwitterPhotoPanel::LLTwitterPhotoPanel() : mSnapshotPanel(NULL), mResolutionComboBox(NULL), mRefreshBtn(NULL), mWorkingLabel(NULL), mThumbnailPlaceholder(NULL), mStatusTextBox(NULL), mPhotoCheckbox(NULL), mPostButton(NULL) { mCommitCallbackRegistrar.add("SocialSharing.SendPhoto", boost::bind(&LLTwitterPhotoPanel::onSend, this)); mCommitCallbackRegistrar.add("SocialSharing.RefreshPhoto", boost::bind(&LLTwitterPhotoPanel::onClickNewSnapshot, this)); } LLTwitterPhotoPanel::~LLTwitterPhotoPanel() { if(mPreviewHandle.get()) { mPreviewHandle.get()->die(); } } BOOL LLTwitterPhotoPanel::postBuild() { setVisibleCallback(boost::bind(&LLTwitterPhotoPanel::onVisibilityChange, this, _2)); mSnapshotPanel = getChild("snapshot_panel"); mResolutionComboBox = getChild("resolution_combobox"); mResolutionComboBox->setCommitCallback(boost::bind(&LLTwitterPhotoPanel::updateResolution, this, TRUE)); mRefreshBtn = getChild("new_snapshot_btn"); mWorkingLabel = getChild("working_lbl"); mThumbnailPlaceholder = getChild("thumbnail_placeholder"); mStatusTextBox = getChild("photo_status"); mPhotoCheckbox = getChild("add_photo_cb"); mPostButton = getChild("post_photo_btn"); mCancelButton = getChild("cancel_photo_btn"); return LLPanel::postBuild(); } void LLTwitterPhotoPanel::draw() { LLSnapshotLivePreview * previewp = static_cast(mPreviewHandle.get()); // Enable interaction only if no transaction with the service is on-going (prevent duplicated posts) bool no_ongoing_connection = !(LLTwitterConnect::instance().isTransactionOngoing()); mCancelButton->setEnabled(no_ongoing_connection); mStatusTextBox->setEnabled(no_ongoing_connection); mResolutionComboBox->setEnabled(no_ongoing_connection && mPhotoCheckbox->getValue().asBoolean()); mRefreshBtn->setEnabled(no_ongoing_connection && mPhotoCheckbox->getValue().asBoolean()); mPhotoCheckbox->setEnabled(no_ongoing_connection); bool add_photo = mPhotoCheckbox->getValue().asBoolean(); // Display the preview if one is available if (previewp && previewp->getThumbnailImage()) { const LLRect& thumbnail_rect = mThumbnailPlaceholder->getRect(); const S32 thumbnail_w = previewp->getThumbnailWidth(); const S32 thumbnail_h = previewp->getThumbnailHeight(); // calc preview offset within the preview rect const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; // calc preview offset within the floater rect // Hack : To get the full offset, we need to take into account each and every offset of each widgets up to the floater. // This is almost as arbitrary as using a fixed offset so that's what we do here for the sake of simplicity. // *TODO : Get the offset looking through the hierarchy of widgets, should be done in postBuild() so to avoid traversing the hierarchy each time. S32 offset_x = thumbnail_rect.mLeft + local_offset_x - 1; S32 offset_y = thumbnail_rect.mBottom + local_offset_y - 39; mSnapshotPanel->localPointToOtherView(offset_x, offset_y, &offset_x, &offset_y, getParentByType()); gGL.matrixMode(LLRender::MM_MODELVIEW); // Apply floater transparency to the texture unless the floater is focused. F32 alpha = (add_photo ? (getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency()) : 0.5f); LLColor4 color = LLColor4::white; gl_draw_scaled_image(offset_x, offset_y, thumbnail_w, thumbnail_h, previewp->getThumbnailImage(), color % alpha); previewp->drawPreviewRect(offset_x, offset_y) ; } // Update the visibility of the working (computing preview) label mWorkingLabel->setVisible(!(previewp && previewp->getSnapshotUpToDate())); // Enable Post if we have a preview to send and no on going connection being processed mPostButton->setEnabled(no_ongoing_connection && ((add_photo && previewp && previewp->getSnapshotUpToDate()) || !mStatusTextBox->getValue().asString().empty())); // Draw the rest of the panel on top of it LLPanel::draw(); } LLSnapshotLivePreview* LLTwitterPhotoPanel::getPreviewView() { LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); return previewp; } void LLTwitterPhotoPanel::onVisibilityChange(const LLSD& new_visibility) { bool visible = new_visibility.asBoolean(); if (visible) { if (mPreviewHandle.get()) { LLSnapshotLivePreview* preview = getPreviewView(); if(preview) { lldebugs << "opened, updating snapshot" << llendl; preview->updateSnapshot(TRUE); } } else { LLRect full_screen_rect = getRootView()->getRect(); LLSnapshotLivePreview::Params p; p.rect(full_screen_rect); LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); mPreviewHandle = previewp->getHandle(); previewp->setSnapshotType(previewp->SNAPSHOT_WEB); previewp->setSnapshotFormat(LLFloaterSnapshot::SNAPSHOT_FORMAT_JPEG); //previewp->setSnapshotQuality(98); previewp->setThumbnailPlaceholderRect(mThumbnailPlaceholder->getRect()); updateControls(); } } } void LLTwitterPhotoPanel::onClickNewSnapshot() { LLSnapshotLivePreview* previewp = getPreviewView(); if (previewp) { //setStatus(Impl::STATUS_READY); lldebugs << "updating snapshot" << llendl; previewp->updateSnapshot(TRUE); } } void LLTwitterPhotoPanel::onSend() { LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterPhotoPanel"); // just in case it is already listening LLEventPumps::instance().obtain("TwitterConnectState").listen("LLTwitterPhotoPanel", boost::bind(&LLTwitterPhotoPanel::onTwitterConnectStateChange, this, _1)); // Connect to Twitter if necessary and then post if (LLTwitterConnect::instance().isConnected()) { sendPhoto(); } else { LLTwitterConnect::instance().checkConnectionToTwitter(true); } } bool LLTwitterPhotoPanel::onTwitterConnectStateChange(const LLSD& data) { switch (data.get("enum").asInteger()) { case LLTwitterConnect::TWITTER_CONNECTED: sendPhoto(); break; case LLTwitterConnect::TWITTER_POSTED: LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterPhotoPanel"); clearAndClose(); break; } return false; } void LLTwitterPhotoPanel::sendPhoto() { // Get the status text std::string status = mStatusTextBox->getValue().asString(); // Add the photo if required bool add_photo = mPhotoCheckbox->getValue().asBoolean(); if (add_photo) { // Get the image LLSnapshotLivePreview* previewp = getPreviewView(); // Post to Twitter LLTwitterConnect::instance().uploadPhoto(previewp->getFormattedImage(), status); } else { // Just post the status to Twitter LLTwitterConnect::instance().updateStatus(status); } updateControls(); } void LLTwitterPhotoPanel::clearAndClose() { mStatusTextBox->setValue(""); LLFloater* floater = getParentByType(); if (floater) { floater->closeFloater(); } } void LLTwitterPhotoPanel::updateControls() { LLSnapshotLivePreview* previewp = getPreviewView(); BOOL got_bytes = previewp && previewp->getDataSize() > 0; BOOL got_snap = previewp && previewp->getSnapshotUpToDate(); LLSnapshotLivePreview::ESnapshotType shot_type = (previewp ? previewp->getSnapshotType() : LLSnapshotLivePreview::SNAPSHOT_POSTCARD); // *TODO: Separate maximum size for Web images from postcards lldebugs << "Is snapshot up-to-date? " << got_snap << llendl; LLLocale locale(LLLocale::USER_LOCALE); std::string bytes_string; if (got_snap) { LLResMgr::getInstance()->getIntegerString(bytes_string, (previewp->getDataSize()) >> 10 ); } //getChild("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : getString("unknown")); <---uses localized string getChild("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : "unknown"); getChild("file_size_label")->setColor( shot_type == LLSnapshotLivePreview::SNAPSHOT_POSTCARD && got_bytes && previewp->getDataSize() > MAX_POSTCARD_DATASIZE ? LLUIColor(LLColor4::red) : LLUIColorTable::instance().getColor( "LabelTextColor" )); updateResolution(FALSE); } void LLTwitterPhotoPanel::updateResolution(BOOL do_update) { LLComboBox* combobox = static_cast(mResolutionComboBox); std::string sdstring = combobox->getSelectedValue(); LLSD sdres; std::stringstream sstream(sdstring); LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); S32 width = sdres[0]; S32 height = sdres[1]; LLSnapshotLivePreview * previewp = static_cast(mPreviewHandle.get()); if (previewp && combobox->getCurrentIndex() >= 0) { S32 original_width = 0 , original_height = 0 ; previewp->getSize(original_width, original_height) ; if (width == 0 || height == 0) { // take resolution from current window size lldebugs << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << llendl; previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); } else { // use the resolution from the selected pre-canned drop-down choice lldebugs << "Setting preview res selected from combo: " << width << "x" << height << llendl; previewp->setSize(width, height); } checkAspectRatio(width); previewp->getSize(width, height); if(original_width != width || original_height != height) { previewp->setSize(width, height); // hide old preview as the aspect ratio could be wrong lldebugs << "updating thumbnail" << llendl; previewp->updateSnapshot(FALSE, TRUE); if(do_update) { lldebugs << "Will update controls" << llendl; updateControls(); LLTwitterPhotoPanel::onClickNewSnapshot(); } } } } void LLTwitterPhotoPanel::checkAspectRatio(S32 index) { LLSnapshotLivePreview *previewp = getPreviewView() ; BOOL keep_aspect = FALSE; if (0 == index) // current window size { keep_aspect = TRUE; } else // predefined resolution { keep_aspect = FALSE; } if (previewp) { previewp->mKeepAspectRatio = keep_aspect; } } LLUICtrl* LLTwitterPhotoPanel::getRefreshBtn() { return mRefreshBtn; } /////////////////////////// //LLTwitterAccountPanel////// /////////////////////////// LLTwitterAccountPanel::LLTwitterAccountPanel() : mAccountCaptionLabel(NULL), mAccountNameLabel(NULL), mPanelButtons(NULL), mConnectButton(NULL), mDisconnectButton(NULL) { mCommitCallbackRegistrar.add("SocialSharing.Connect", boost::bind(&LLTwitterAccountPanel::onConnect, this)); mCommitCallbackRegistrar.add("SocialSharing.Disconnect", boost::bind(&LLTwitterAccountPanel::onDisconnect, this)); setVisibleCallback(boost::bind(&LLTwitterAccountPanel::onVisibilityChange, this, _2)); } BOOL LLTwitterAccountPanel::postBuild() { mAccountCaptionLabel = getChild("account_caption_label"); mAccountNameLabel = getChild("account_name_label"); mPanelButtons = getChild("panel_buttons"); mConnectButton = getChild("connect_btn"); mDisconnectButton = getChild("disconnect_btn"); return LLPanel::postBuild(); } void LLTwitterAccountPanel::draw() { LLTwitterConnect::EConnectionState connection_state = LLTwitterConnect::instance().getConnectionState(); //Disable the 'disconnect' button and the 'use another account' button when disconnecting in progress bool disconnecting = connection_state == LLTwitterConnect::TWITTER_DISCONNECTING; mDisconnectButton->setEnabled(!disconnecting); //Disable the 'connect' button when a connection is in progress bool connecting = connection_state == LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS; mConnectButton->setEnabled(!connecting); LLPanel::draw(); } void LLTwitterAccountPanel::onVisibilityChange(const LLSD& new_visibility) { bool visible = new_visibility.asBoolean(); if(visible) { LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterAccountPanel"); LLEventPumps::instance().obtain("TwitterConnectState").listen("LLTwitterAccountPanel", boost::bind(&LLTwitterAccountPanel::onTwitterConnectStateChange, this, _1)); LLEventPumps::instance().obtain("TwitterConnectInfo").stopListening("LLTwitterAccountPanel"); LLEventPumps::instance().obtain("TwitterConnectInfo").listen("LLTwitterAccountPanel", boost::bind(&LLTwitterAccountPanel::onTwitterConnectInfoChange, this)); //Connected if(LLTwitterConnect::instance().isConnected()) { showConnectedLayout(); } //Check if connected (show disconnected layout in meantime) else { showDisconnectedLayout(); } if ((LLTwitterConnect::instance().getConnectionState() == LLTwitterConnect::TWITTER_NOT_CONNECTED) || (LLTwitterConnect::instance().getConnectionState() == LLTwitterConnect::TWITTER_CONNECTION_FAILED)) { LLTwitterConnect::instance().checkConnectionToTwitter(); } } else { LLEventPumps::instance().obtain("TwitterConnectState").stopListening("LLTwitterAccountPanel"); LLEventPumps::instance().obtain("TwitterConnectInfo").stopListening("LLTwitterAccountPanel"); } } bool LLTwitterAccountPanel::onTwitterConnectStateChange(const LLSD& data) { if(LLTwitterConnect::instance().isConnected()) { //In process of disconnecting so leave the layout as is if(data.get("enum").asInteger() != LLTwitterConnect::TWITTER_DISCONNECTING) { showConnectedLayout(); } } else { showDisconnectedLayout(); } return false; } bool LLTwitterAccountPanel::onTwitterConnectInfoChange() { LLSD info = LLTwitterConnect::instance().getInfo(); std::string clickable_name; //Strings of format [http://www.somewebsite.com Click Me] become clickable text if(info.has("link") && info.has("name")) { clickable_name = "[" + info["link"].asString() + " " + info["name"].asString() + "]"; } mAccountNameLabel->setText(clickable_name); return false; } void LLTwitterAccountPanel::showConnectButton() { if(!mConnectButton->getVisible()) { mConnectButton->setVisible(TRUE); mDisconnectButton->setVisible(FALSE); } } void LLTwitterAccountPanel::hideConnectButton() { if(mConnectButton->getVisible()) { mConnectButton->setVisible(FALSE); mDisconnectButton->setVisible(TRUE); } } void LLTwitterAccountPanel::showDisconnectedLayout() { mAccountCaptionLabel->setText(getString("twitter_disconnected")); mAccountNameLabel->setText(std::string("")); showConnectButton(); } void LLTwitterAccountPanel::showConnectedLayout() { LLTwitterConnect::instance().loadTwitterInfo(); mAccountCaptionLabel->setText(getString("twitter_connected")); hideConnectButton(); } void LLTwitterAccountPanel::onConnect() { LLTwitterConnect::instance().checkConnectionToTwitter(true); //Clear only the twitter browser cookies so that the twitter login screen appears LLViewerMedia::getCookieStore()->removeCookiesByDomain(".twitter.com"); } void LLTwitterAccountPanel::onDisconnect() { LLTwitterConnect::instance().disconnectFromTwitter(); LLViewerMedia::getCookieStore()->removeCookiesByDomain(".twitter.com"); } //////////////////////// //LLFloaterTwitter/////// //////////////////////// LLFloaterTwitter::LLFloaterTwitter(const LLSD& key) : LLFloater(key), mSocialPhotoPanel(NULL), mStatusErrorText(NULL), mStatusLoadingText(NULL), mStatusLoadingIndicator(NULL) { mCommitCallbackRegistrar.add("SocialSharing.Cancel", boost::bind(&LLFloaterTwitter::onCancel, this)); } void LLFloaterTwitter::onCancel() { closeFloater(); } BOOL LLFloaterTwitter::postBuild() { // Keep tab of the Photo Panel mSocialPhotoPanel = static_cast(getChild("panel_twitter_photo")); // Connection status widgets mStatusErrorText = getChild("connection_error_text"); mStatusLoadingText = getChild("connection_loading_text"); mStatusLoadingIndicator = getChild("connection_loading_indicator"); return LLFloater::postBuild(); } void LLFloaterTwitter::showPhotoPanel() { LLTabContainer* parent = dynamic_cast(mSocialPhotoPanel->getParent()); if (!parent) { llwarns << "Cannot find panel container" << llendl; return; } parent->selectTabPanel(mSocialPhotoPanel); } // static void LLFloaterTwitter::preUpdate() { LLFloaterTwitter* instance = LLFloaterReg::findTypedInstance("twitter"); if (instance) { //Will set file size text to 'unknown' instance->mSocialPhotoPanel->updateControls(); } } // static void LLFloaterTwitter::postUpdate() { LLFloaterTwitter* instance = LLFloaterReg::findTypedInstance("twitter"); if (instance) { //Will set the file size text instance->mSocialPhotoPanel->updateControls(); // The refresh button is initially hidden. We show it after the first update, // i.e. after snapshot is taken LLUICtrl * refresh_button = instance->mSocialPhotoPanel->getRefreshBtn(); if (!refresh_button->getVisible()) { refresh_button->setVisible(true); } } } void LLFloaterTwitter::draw() { if (mStatusErrorText && mStatusLoadingText && mStatusLoadingIndicator) { mStatusErrorText->setVisible(false); mStatusLoadingText->setVisible(false); mStatusLoadingIndicator->setVisible(false); LLTwitterConnect::EConnectionState connection_state = LLTwitterConnect::instance().getConnectionState(); std::string status_text; switch (connection_state) { case LLTwitterConnect::TWITTER_NOT_CONNECTED: // No status displayed when first opening the panel and no connection done case LLTwitterConnect::TWITTER_CONNECTED: // When successfully connected, no message is displayed case LLTwitterConnect::TWITTER_POSTED: // No success message to show since we actually close the floater after successful posting completion break; case LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS: // Connection loading indicator mStatusLoadingText->setVisible(true); status_text = LLTrans::getString("SocialTwitterConnecting"); mStatusLoadingText->setValue(status_text); mStatusLoadingIndicator->setVisible(true); break; case LLTwitterConnect::TWITTER_POSTING: // Posting indicator mStatusLoadingText->setVisible(true); status_text = LLTrans::getString("SocialTwitterPosting"); mStatusLoadingText->setValue(status_text); mStatusLoadingIndicator->setVisible(true); break; case LLTwitterConnect::TWITTER_CONNECTION_FAILED: // Error connecting to the service mStatusErrorText->setVisible(true); status_text = LLTrans::getString("SocialTwitterErrorConnecting"); mStatusErrorText->setValue(status_text); break; case LLTwitterConnect::TWITTER_POST_FAILED: // Error posting to the service mStatusErrorText->setVisible(true); status_text = LLTrans::getString("SocialTwitterErrorPosting"); mStatusErrorText->setValue(status_text); break; case LLTwitterConnect::TWITTER_DISCONNECTING: // Disconnecting loading indicator mStatusLoadingText->setVisible(true); status_text = LLTrans::getString("SocialTwitterDisconnecting"); mStatusLoadingText->setValue(status_text); mStatusLoadingIndicator->setVisible(true); break; case LLTwitterConnect::TWITTER_DISCONNECT_FAILED: // Error disconnecting from the service mStatusErrorText->setVisible(true); status_text = LLTrans::getString("SocialTwitterErrorDisconnecting"); mStatusErrorText->setValue(status_text); break; } } LLFloater::draw(); }