/** * RobloxTeamCreateWidget.cpp * Copyright (c) 2015 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "RobloxTeamCreateWidget.h" #include #include #include #include #include #include #include #include #include "V8DataModel/DataModel.h" #include "Network/Players.h" #include "Network/Player.h" #include "V8Xml/WebParser.h" #include "util/RobloxGoogleAnalytics.h" #include "PlayersDataManager.h" #include "AuthenticationHelper.h" #include "RobloxUser.h" #include "RobloxIDEDoc.h" #include "UpdateUIManager.h" #include "RobloxMainWindow.h" #include "RobloxStudioVerbs.h" #include "RobloxSettings.h" #include "RobloxGameExplorer.h" #include "QtUtilities.h" #include "RobloxContextualHelp.h" static const int kPlayerAvatarSize = 32; static const int kVerticalMargin = 4; static const int kHorizontalMargin = 8; FASTFLAGVARIABLE(KickUsersWhenTheyLoseAccessToCloudEdit, true) FASTFLAGVARIABLE(DoNotCheckCanCloudEditWhenNotLoggedIn, true) FASTFLAGVARIABLE(TeamCreateEnableContextHelp, true) FASTFLAGVARIABLE(TeamCreateEnableCompleterFix, true) FASTFLAGVARIABLE(TeamCreateEnablePublishOnLogin, true) FASTFLAGVARIABLE(TeamCreateCheckInvalidUniverse, true) //-------------------------------------------------------------------------------------------- // PlayersWidgetDelegate //-------------------------------------------------------------------------------------------- class CreatorsWidgetDelegate: public QStyledItemDelegate { public: CreatorsWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) { } enum Role { DecorationStyle = Qt::UserRole + 1, OwnershipRole, SubLabelRole }; private: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size = QStyledItemDelegate::sizeHint(option, index); return QSize(size.width(), kPlayerAvatarSize + 2*kVerticalMargin); } void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { painter->save(); QStyleOptionViewItemV4 optionV4 = option; initStyleOption(&optionV4, index); // DecorationStyle - 0 (circular avatar, selectable) // - 1 (square avatar, non-selectable) if (!index.data(DecorationStyle).toInt()) { optionV4.text = ""; optionV4.icon = QIcon(); CreatorsListWidget* pListWidget = static_cast(parent()); pListWidget->style()->drawControl(QStyle::CE_ItemViewItem, &optionV4, painter, pListWidget); } QRect adjustedRect = option.rect.adjusted(kHorizontalMargin, kVerticalMargin, -kHorizontalMargin, -kVerticalMargin); QImage icon(qvariant_cast(index.data(Qt::DecorationRole))); if (!icon.isNull()) { painter->save(); painter->setRenderHints(QPainter::Antialiasing); if (!index.data(DecorationStyle).toInt()) { QPainterPath path; path.addEllipse(adjustedRect.topLeft().x(), adjustedRect.topLeft().y(), kPlayerAvatarSize, kPlayerAvatarSize); painter->setClipPath(path); } painter->drawImage(QPoint(adjustedRect.topLeft().x(), adjustedRect.topLeft().y()), icon); adjustedRect.setLeft(adjustedRect.left() + kPlayerAvatarSize + kHorizontalMargin); painter->restore(); } if (index.data(OwnershipRole).toInt()) { painter->setPen(QColor(Qt::gray)); painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight() + QPoint(1, 0)); } QString subLabel = index.data(SubLabelRole).toString(); if (!subLabel.isEmpty()) { painter->setPen(QColor(Qt::black)); painter->drawText(adjustedRect, Qt::AlignLeft|Qt::AlignTop|Qt::ElideRight, index.data(Qt::DisplayRole).toString()); QFont updatedFont(option.font); updatedFont.setPointSize(option.font.pointSize() - 2); painter->setFont(updatedFont); painter->setPen(QColor(Qt::gray)); painter->drawText(adjustedRect, Qt::AlignLeft|Qt::AlignBottom|Qt::ElideRight, subLabel); } else { painter->setPen(QColor(Qt::black)); painter->drawText(adjustedRect, Qt::AlignLeft|Qt::AlignVCenter|Qt::ElideRight, index.data(Qt::DisplayRole).toString()); } painter->restore(); } }; //-------------------------------------------------------------------------------------------- // PlayersWidgetDelegate //-------------------------------------------------------------------------------------------- class CompleterPopupDelegate: public QStyledItemDelegate { public: CompleterPopupDelegate(QObject* parent) : QStyledItemDelegate(parent) { } private: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QListView* pListWidget(static_cast(parent())); QStyleOptionViewItemV4 optionV4 = option; initStyleOption(&optionV4, index); optionV4.text = ""; pListWidget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter, pListWidget); QRect adjustedRect(optionV4.rect); adjustedRect.setRight(adjustedRect.right() - 20); painter->setPen(QColor(Qt::black)); QString userName(index.data(Qt::DisplayRole).toString()); painter->drawText(optionV4.rect, Qt::AlignLeft|Qt::AlignVCenter|Qt::ElideRight, userName.left(userName.indexOf(':'))); static QImage addImage(":/16x16/images/Studio 2.0 icons/16x16/team_create_add.png"); static QImage addOnImage(":/16x16/images/Studio 2.0 icons/16x16/team_create_add_ON.png"); painter->drawImage(QPoint(adjustedRect.topRight().x(), adjustedRect.topRight().y() + 2), (option.state & QStyle::State_MouseOver) ? addOnImage : addImage); } }; //-------------------------------------------------------------------------------------------- // GroupItem //-------------------------------------------------------------------------------------------- static void invokeGroupDataLoaded(QPointer pGroupItem, RBX::HttpFuture future) { if (pGroupItem.isNull()) return; QMetaObject::invokeMethod(pGroupItem.data(), "groupDataLoaded", Qt::QueuedConnection, Q_ARG(RBX::HttpFuture, future)); } static void invokeGroupThumbnailLoaded(QPointer pGroupItem, RBX::HttpFuture future) { if (pGroupItem.isNull()) return; QMetaObject::invokeMethod(pGroupItem.data(), "groupThumbnailLoaded", Qt::QueuedConnection, Q_ARG(RBX::HttpFuture, future)); } GroupItem::GroupItem(int groupId) : m_GroupId(groupId) { // request player item update QString str = QString("%1/groups/%2").arg(RobloxSettings::getApiBaseURL()).arg(groupId); RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokeGroupDataLoaded, QPointer(this), _1)); } void GroupItem::groupDataLoaded(RBX::HttpFuture future) { EntityProperties thumbnailProp; thumbnailProp.setFromJsonFuture(future); boost::optional thumnailUri = thumbnailProp.get("EmblemUrl"); if (thumnailUri.is_initialized() && !thumnailUri.get().empty()) RBX::HttpAsync::get(thumnailUri.get()).then(boost::bind(&invokeGroupThumbnailLoaded, QPointer(this), _1)); } void GroupItem::groupThumbnailLoaded(RBX::HttpFuture future) { std::string imageContent; try { imageContent = future.get(); } catch (const RBX::base_exception&) { } if (!imageContent.empty()) { QImage image; if (image.loadFromData((uchar*)imageContent.c_str(), imageContent.size())) { QImage resultingImage(QSize(32, 32), image.format()); resultingImage.fill(Qt::lightGray); QPainter painter(&resultingImage); painter.drawImage(0, 0, image.scaled(QSize(32, 32), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); setData(0, Qt::DecorationRole, resultingImage); } } } //-------------------------------------------------------------------------------------------- // CreatorItem //-------------------------------------------------------------------------------------------- void CreatorItem::updatePlayerAvatar(const QImage& playerImage) { setData(0, Qt::DecorationRole, playerImage); } void CreatorItem::updatePlayerName(int playerId, const QString& userName) { if (playerId == m_PlayerId) setData(0, Qt::DisplayRole, userName); } //-------------------------------------------------------------------------------------------- // RobloxPlayersListWidget //-------------------------------------------------------------------------------------------- CreatorsListWidget::CreatorsListWidget(QWidget* parent, shared_ptr dataModel, shared_ptr dataManager) : QTreeWidget(parent) , m_pDataModel(dataModel) , m_pPlayersDataManager(dataManager) { RBXASSERT(m_pDataModel); RBXASSERT(m_pPlayersDataManager); setHeaderHidden(true); setRootIsDecorated(false); setUniformRowHeights(true); setAlternatingRowColors(true); setEditTriggers(QAbstractItemView::NoEditTriggers); setFocusPolicy(Qt::NoFocus); // add for custom drawing of the items setItemDelegate(new CreatorsWidgetDelegate(this)); } void CreatorsListWidget::addCreatorItem(int playerId, const QString& playerName, const QString& subLabel) { CreatorItem* pCreatorItem = new CreatorItem(playerId); pCreatorItem->setText(0, playerName); if (!subLabel.isEmpty()) pCreatorItem->setData(0, CreatorsWidgetDelegate::SubLabelRole, subLabel); addTopLevelItem(pCreatorItem); // request player item update m_pPlayersDataManager->getPlayerAvatar(playerId, pCreatorItem, "updatePlayerAvatar"); } void CreatorsListWidget::onPlayerAdded(boost::shared_ptr player) { int playerId = RBX::Instance::fastDynamicCast(player.get())->getUserID(); if (playerId <= 0) return; QMetaObject::invokeMethod(this, "onPlayerAddRequsted", Qt::QueuedConnection, Q_ARG(int, playerId)); } void CreatorsListWidget::onPlayerRemoving(boost::shared_ptr player) { int playerId = RBX::Instance::fastDynamicCast(player.get())->getUserID(); if (playerId <= 0) return; QMetaObject::invokeMethod(this, "onPlayerRemoveRequested", Qt::QueuedConnection, Q_ARG(int, playerId)); } bool CreatorsListWidget::isPlayerPresent(int playerId) { if (!m_pDataModel) return false; RBX::DataModel::LegacyLock lock(m_pDataModel.get(), RBX::DataModelJob::Write); RBX::Network::Players* players = m_pDataModel->find(); return (players && players->getPlayerByID(playerId)); } QString CreatorsListWidget::getPlayerName(int playerId) { if (!m_pDataModel) return false; RBX::DataModel::LegacyLock lock(m_pDataModel.get(), RBX::DataModelJob::Write); RBX::Network::Players* players = m_pDataModel->find(); if (players) { boost::shared_ptr player = players->getPlayerByID(playerId); if (player) return QString::fromStdString(player->getName()); } return QString(); } CreatorItem* CreatorsListWidget::getCreatorItem(int playerId) { int currentCount = 0; CreatorItem* pCreatorItem = NULL; while (currentCount < topLevelItemCount()) { pCreatorItem = dynamic_cast(topLevelItem(currentCount)); if (pCreatorItem && (pCreatorItem->getPlayerId() == playerId)) return pCreatorItem; currentCount++; } return NULL; } CreatorItem* CreatorsListWidget::getCreatorItem(const QString& playerName) { int currentCount = 0; CreatorItem* pCreatorItem = NULL; while (currentCount < topLevelItemCount()) { pCreatorItem = dynamic_cast(topLevelItem(currentCount)); if (pCreatorItem && (QString::compare(pCreatorItem->text(0), playerName, Qt::CaseInsensitive) == 0)) return pCreatorItem; currentCount++; } return NULL; } void CreatorsListWidget::mousePressEvent(QMouseEvent *evt) { if (!itemAt(evt->pos())) setCurrentItem(NULL); else QTreeWidget::mousePressEvent(evt); } //-------------------------------------------------------------------------------------------- // UserPlaceCreatorsListWidget //-------------------------------------------------------------------------------------------- UserPlaceCreatorsListWidget::UserPlaceCreatorsListWidget(QWidget* parent, shared_ptr dataModel, shared_ptr dataManager) : CreatorsListWidget(parent, dataModel, dataManager) , m_PlaceOwnerId(-1) { RBXASSERT(dataModel->getUniverseId()); m_pCreatorsListLoader = new CreatorsListLoader(this, dataModel->getUniverseId()); connect(m_pCreatorsListLoader, SIGNAL(finishedLoading()), this, SLOT(onCreatorsListLoaded())); // connect to data model RBX::DataModel::LegacyLock lock(m_pDataModel.get(), RBX::DataModelJob::Write); RBX::Network::Players* players = m_pDataModel->find(); if (players) { m_cPlayerAdded = players->playerAddedSignal.connect(boost::bind(&UserPlaceCreatorsListWidget::onPlayerAdded, this, _1)); m_cPlayerRemoving = players->playerRemovingSignal.connect(boost::bind(&UserPlaceCreatorsListWidget::onPlayerRemoving, this, _1)); players->visitChildren(boost::bind(&UserPlaceCreatorsListWidget::onPlayerAdded, this, _1)); } } void UserPlaceCreatorsListWidget::addOwnerDetails(const PlaceCreatorDetails& creatorDetails) { CreatorItem* pCreatorItem = new CreatorItem(creatorDetails.ownerId); pCreatorItem->setText(0, creatorDetails.ownerName); pCreatorItem->setData(0, CreatorsWidgetDelegate::SubLabelRole, "Place Owner (Offline)"); pCreatorItem->setData(0, CreatorsWidgetDelegate::OwnershipRole, 1); insertTopLevelItem(0, pCreatorItem); m_pPlayersDataManager->getPlayerAvatar(creatorDetails.ownerId, pCreatorItem, "updatePlayerAvatar"); m_PlaceOwnerId = creatorDetails.ownerId; } void UserPlaceCreatorsListWidget::onPlayerAddRequsted(int playerId) { CreatorItem* pCreatorItem = getCreatorItem(playerId); if (pCreatorItem) { QString subLabel = pCreatorItem->data(0, CreatorsWidgetDelegate::SubLabelRole).toString(); if (subLabel == "Offline") { subLabel = ""; } else { subLabel.replace(" (Offline)", ""); } pCreatorItem->setData(0, CreatorsWidgetDelegate::SubLabelRole, subLabel); } else { // add item addCreatorItem(playerId, getPlayerName(playerId), ""); // request creators list reload, just in case if there are other creators added m_pCreatorsListLoader->reload(); } } void UserPlaceCreatorsListWidget::onPlayerRemoveRequested(int playerId) { CreatorItem* pCreatorItem = getCreatorItem(playerId); if (pCreatorItem) { QString subLabel = pCreatorItem->data(0, CreatorsWidgetDelegate::SubLabelRole).toString(); if (subLabel.isEmpty()) { subLabel = "Offline"; } else { subLabel.append(" (Offline)"); } pCreatorItem->setData(0, CreatorsWidgetDelegate::SubLabelRole, subLabel); } } void UserPlaceCreatorsListWidget::onCreatorsListLoaded() { // first remove the removed creators removeRemovedCreators(); // now added the added creators addAddedCreators(); Q_EMIT finishedLoading(); } void UserPlaceCreatorsListWidget::contextMenuEvent(QContextMenuEvent * evt) { if (!canDeleteSelectedCreator()) return; QMenu menu; QAction* deleteAction = menu.addAction("Delete"); deleteAction->setIcon(QIcon(":/16x16/images/Studio 2.0 icons/16x16/delete_x_16_h.png")); QAction* result = menu.exec(evt->globalPos()); if (result && (result == deleteAction)) { QList items = selectedItems(); if (items.size() > 0) { RBXASSERT(items.size() == 1); CreatorItem* pCreatorItem = dynamic_cast(items.at(0)); if (pCreatorItem) Q_EMIT removeCloudEditorRequested(pCreatorItem->getPlayerId()); } } } bool UserPlaceCreatorsListWidget::canDeleteSelectedCreator() { // do not show context menu for non owner if (m_PlaceOwnerId > 0 && (RobloxUser::singleton().getUserId() == m_PlaceOwnerId)) { QList items = selectedItems(); if (items.size() == 1) { CreatorItem* pCreatorItem = dynamic_cast(items.at(0)); if (pCreatorItem && pCreatorItem->getPlayerId() != m_PlaceOwnerId) return true; } } return false; } void UserPlaceCreatorsListWidget::removeRemovedCreators() { CreatorItem* pCreatorItem = NULL; QList itemsToDelete; const CreatorsCollection& creators = m_pCreatorsListLoader->getCreators(); for (int currentCount = 0; currentCount < topLevelItemCount(); ++currentCount) { pCreatorItem = dynamic_cast(topLevelItem(currentCount)); if (pCreatorItem && (pCreatorItem->getPlayerId() != m_PlaceOwnerId) && !creators.contains(pCreatorItem->getPlayerId())) itemsToDelete.append(pCreatorItem); } while (!itemsToDelete.empty()) delete itemsToDelete.takeFirst(); } void UserPlaceCreatorsListWidget::addAddedCreators() { int playerId = 0; bool playerPresent = false; const CreatorsCollection& creators = m_pCreatorsListLoader->getCreators(); for (CreatorsCollection::const_iterator iter = creators.constBegin(); iter != creators.constEnd(); ++iter) { playerId = iter.key(); if (playerId > 0 && !getCreatorItem(playerId)) { playerPresent = isPlayerPresent(playerId); addCreatorItem(playerId, playerPresent ? getPlayerName(playerId) : "", playerPresent ? "" : "Offline"); if (!playerPresent) { CreatorItem* pCreatorItem = getCreatorItem(playerId); if (pCreatorItem) m_pPlayersDataManager->getPlayerName(playerId, pCreatorItem, "updatePlayerName"); } } } } //-------------------------------------------------------------------------------------------- // GroupPlaceCreatorsListWidget //-------------------------------------------------------------------------------------------- GroupPlaceCreatorsListWidget::GroupPlaceCreatorsListWidget(QWidget* parent, shared_ptr dataModel, shared_ptr dataManager) : CreatorsListWidget(parent, dataModel, dataManager) { RBX::Network::Players* players = m_pDataModel->find(); if (players) { m_cPlayerAdded = players->playerAddedSignal.connect(boost::bind(&GroupPlaceCreatorsListWidget::onPlayerAdded, this, _1)); m_cPlayerRemoving = players->playerRemovingSignal.connect(boost::bind(&GroupPlaceCreatorsListWidget::onPlayerRemoving, this, _1)); players->visitChildren(boost::bind(&GroupPlaceCreatorsListWidget::onPlayerAdded, this, _1)); } Q_EMIT finishedLoading(); } void GroupPlaceCreatorsListWidget::addOwnerDetails(const PlaceCreatorDetails& creatorDetails) { GroupItem* pGroupItem = new GroupItem(creatorDetails.ownerId); pGroupItem->setText(0, creatorDetails.ownerName); pGroupItem->setData(0, CreatorsWidgetDelegate::SubLabelRole, "Group owned place"); pGroupItem->setData(0, CreatorsWidgetDelegate::OwnershipRole, 1); pGroupItem->setData(0, CreatorsWidgetDelegate::DecorationStyle, 1); insertTopLevelItem(0, pGroupItem); } void GroupPlaceCreatorsListWidget::onPlayerAddRequsted(int playerId) { // if name is empty meaning player is not present QString playerName = getPlayerName(playerId); if (playerName.isEmpty()) return; CreatorItem* pCreatorItem = new CreatorItem(playerId); pCreatorItem->setText(0, playerName); addTopLevelItem(pCreatorItem); // request player item update m_pPlayersDataManager->getPlayerAvatar(playerId, pCreatorItem, "updatePlayerAvatar"); } void GroupPlaceCreatorsListWidget::onPlayerRemoveRequested(int playerId) { CreatorItem* pCreatorItem = getCreatorItem(playerId); if (pCreatorItem) delete pCreatorItem; } //-------------------------------------------------------------------------------------------- // RobloxTeamCreateWidget //-------------------------------------------------------------------------------------------- RobloxTeamCreateWidget::RobloxTeamCreateWidget(QWidget* parent) : QWidget(parent) , m_pCreatorsListWidget(NULL) , m_pMainStackedWidgetAnimator(NULL) , m_pFriendsInfoLoader(NULL) , m_bCanManageCurrentPlace(false) , m_bLoginButtonClicked(false) { setupUi(this); connect(loginButton, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); connect(publishButton, SIGNAL(clicked()), this, SLOT(onPublishButtonClicked())); connect(groupPlaceTurnOnButton, SIGNAL(clicked()), this, SLOT(onTurnOnButtonClicked())); connect(userPlaceTurnOnButton, SIGNAL(clicked()), this, SLOT(onTurnOnButtonClicked())); connect(bottomMoreButton, SIGNAL(clicked()), this, SLOT(onBottomMoreButtonClicked())); bottomMoreButton->setToolTip(tr("Team Create Options")); connect(userRemoveButton, SIGNAL(clicked()), this, SLOT(onRemoveCloudEditConfirm())); connect(userRemoveCancelButton, SIGNAL(clicked()), this, SLOT(onRemoveCloudEditCancel())); connect(friendsInviteEdit, SIGNAL(returnPressed()), this, SLOT(onFriendsInviteEditReturnPressed())); m_pMainStackedWidgetAnimator = new QtUtilities::StackedWidgetAnimator(mainStackedWidget); updateCurrentWidgetInStack(); } void RobloxTeamCreateWidget::setDataModel(boost::shared_ptr dataModel, boost::shared_ptr pPlayersDataManager) { if (m_pDataModel == dataModel) return; m_bLoginButtonClicked = false; disconnect(&AuthenticationHelper::Instance(), SIGNAL(authenticationChanged(bool)), this, SLOT(onAuthenticationChanged(bool))); m_PlaceCreatorDetails = PlaceCreatorDetails(); m_PlaceDetailsQuery = boost::shared_future(); m_PlaceManageStatusQuery = boost::shared_future(); m_cPlaceIdChange.disconnect(); if (m_pFriendsInfoLoader) delete m_pFriendsInfoLoader; m_pFriendsInfoLoader = NULL; if (m_pCreatorsListWidget) { delete m_pCreatorsListWidget; m_pCreatorsListWidget = NULL; } m_pDataModel = dataModel; m_pPlayersDataManager = pPlayersDataManager; if (m_pDataModel) { connect(&AuthenticationHelper::Instance(), SIGNAL(authenticationChanged(bool)), this, SLOT(onAuthenticationChanged(bool))); m_cPlaceIdChange = m_pDataModel->propertyChangedSignal.connect(boost::bind(&RobloxTeamCreateWidget::onPlaceIdChanged, this, _1) ); } QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } PlaceCreatorDetails RobloxTeamCreateWidget::getPlaceCreatorDetails() { canManageCurrentPlace(); if (m_PlaceDetailsQuery.future_ && !m_PlaceDetailsQuery.is_ready()) m_PlaceDetailsQuery.get(); return m_PlaceCreatorDetails; } void RobloxTeamCreateWidget::updateCurrentWidgetInStack() { m_bLoginButtonClicked = false; // default state if (!m_pDataModel) { m_pMainStackedWidgetAnimator->setCurrentIndex(EMPTY_PAGE); } // if user is not logged in else if (RobloxUser::singleton().getUserId() <= 0) { optionsStackedWidget->setCurrentIndex(LOGIN_PAGE); m_pMainStackedWidgetAnimator->setCurrentIndex(OPTIONS_PAGE); } // if we are launching cloud edit else if (RobloxIDEDoc::getIsCloudEditSession()) { if (m_PlaceCreatorDetails.ownerId < 0) return; bool canManage = canManageCurrentPlace(); eTeamType creatorType = getCreatorType(); if (!m_pCreatorsListWidget) addCreatorsListWidget(creatorType); if (creatorType == TEAM_USERS) { optionsStackedWidget->setCurrentIndex(USER_PLACE_OPTIONS_PAGE); userPlaceStackedWidget->setCurrentIndex(USER_PLACE_CREATORS_LIST_PAGE); enableFriendsInviteEdit(canManage); } else { optionsStackedWidget->setCurrentIndex(GROUP_PLACE_OPTIONS_PAGE); groupPlaceStackedWidget->setCurrentIndex(GROUP_PLACE_CREATORS_LIST_PAGE); } m_pMainStackedWidgetAnimator->setCurrentIndex(OPTIONS_PAGE); } // if we have a published place else if (m_pDataModel && m_pDataModel->getPlaceID() > 0) { bool canManage = canManageCurrentPlace(); eTeamType creatorType = getCreatorType(); if (creatorType == TEAM_GROUP) { optionsStackedWidget->setCurrentIndex(GROUP_PLACE_OPTIONS_PAGE); if (canManage) { groupPlaceStackedWidget->setCurrentIndex(GROUP_PLACE_TURN_ON_PAGE); } else { groupPlaceStackedWidget->setCurrentIndex(GROUP_PLACE_NON_ADMIN_MESSAGE_PAGE); } } else if (canManage) { optionsStackedWidget->setCurrentIndex(USER_PLACE_OPTIONS_PAGE); userPlaceStackedWidget->setCurrentIndex(USER_PLACE_TURN_ON_PAGE); enableFriendsInviteEdit(true); } else { m_pMainStackedWidgetAnimator->setCurrentIndex(PUBLISH_PAGE); return; } m_pMainStackedWidgetAnimator->setCurrentIndex(OPTIONS_PAGE); } // if user is logged in but having an unpublished place else { m_pMainStackedWidgetAnimator->setCurrentIndex(PUBLISH_PAGE); } } void RobloxTeamCreateWidget::addCreatorsListWidget(eTeamType creatorType) { QWidget* pParentWidget = creatorType == TEAM_GROUP ? groupPlaceCreatorsListPage : userPlaceCreatorsListPage; QLayout* pExistingLayout = pParentWidget->layout(); if (pExistingLayout) delete pExistingLayout; QGridLayout* pLayout = new QGridLayout; pLayout->setMargin(0); pLayout->setSpacing(0); if (creatorType == TEAM_GROUP) { m_pCreatorsListWidget = new GroupPlaceCreatorsListWidget(this, m_pDataModel, m_pPlayersDataManager); } else { m_pCreatorsListWidget = new UserPlaceCreatorsListWidget(this, m_pDataModel, m_pPlayersDataManager); if (canManageCurrentPlace()) { connect(m_pCreatorsListWidget, SIGNAL(removeCloudEditorRequested(int)), this, SLOT(onRemoveCloudEditorRequested(int))); connect(m_pCreatorsListWidget, SIGNAL(finishedLoading()), this, SLOT(updatePlayerFriendsInfo())); } } m_pCreatorsListWidget->addOwnerDetails(getPlaceCreatorDetails()); pLayout->addWidget(m_pCreatorsListWidget, 0, 0, 1, 1); pParentWidget->setLayout(pLayout); } void RobloxTeamCreateWidget::onAuthenticationChanged(bool) { // TODO: shall we reset the widget if user changes? IRobloxDoc* pDoc = RobloxDocManager::Instance().getPlayDoc(); if (FFlag::TeamCreateEnablePublishOnLogin && m_bLoginButtonClicked && pDoc) { // bring play doc to the front RobloxDocManager::Instance().setCurrentDoc(pDoc); // invoke publish slot, must be done queued as user might not have been authenticated by now QMetaObject::invokeMethod(this, "onPublishButtonClicked", Qt::QueuedConnection); } else { // this has to be delayed as user RBX::User authentication SLOT might not have been called by now QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } } void RobloxTeamCreateWidget::onLoginButtonClicked() { UpdateUIManager::Instance().getMainWindow().openStartPage(true, "showlogin=True"); UpdateUIManager::Instance().getMainWindow().setFocus(); if (FFlag::TeamCreateEnablePublishOnLogin) m_bLoginButtonClicked = true; RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "User login"); } void RobloxTeamCreateWidget::onPublishButtonClicked() { bool loginButtonClicked = m_bLoginButtonClicked; m_bLoginButtonClicked = false; if (FFlag::TeamCreateEnablePublishOnLogin && loginButtonClicked) { // if there's no user, then just update the widget stack if (RobloxUser::singleton().getUserId() <= 0) { QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); return; } } // get dialog and re-enable the previous page PublishToRobloxAsVerb* publishToRobloxVerb = dynamic_cast(m_pDataModel->getVerb("PublishToRobloxAsVerb")); if(publishToRobloxVerb) { publishToRobloxVerb->doIt(m_pDataModel.get()); QDialog* pDialog = publishToRobloxVerb->getPublishDialog(); if (pDialog) { m_pMainStackedWidgetAnimator->setCurrentIndex(PUBLISHING_MESSAGE_PAGE); connect(pDialog, SIGNAL(finished(int)), this, SLOT(onPublishDialogFinished(int))); } } RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Publish invoked"); } void RobloxTeamCreateWidget::onTurnOnButtonClicked() { RBXASSERT(m_pDataModel->getUniverseId() > 0); eTeamType creatorType = getCreatorType(); if (creatorType == TEAM_USERS) { optionsStackedWidget->setCurrentIndex(USER_PLACE_OPTIONS_PAGE); userPlaceStackedWidget->setCurrentIndex(USER_PLACE_SAVE_MESSAGE_PAGE); } else { optionsStackedWidget->setCurrentIndex(GROUP_PLACE_OPTIONS_PAGE); groupPlaceStackedWidget->setCurrentIndex(GROUP_PLACE_SAVE_MESSAGE_PAGE); } m_pMainStackedWidgetAnimator->setCurrentIndex(OPTIONS_PAGE); // publish place UpdateUIManager::Instance().getMainWindow().publishToRobloxAction->trigger(); // turn on cloud edit QString postUrl = QString("%1/universes/%2/enablecloudedit").arg(RobloxSettings::getApiBaseURL()).arg(m_pDataModel->getUniverseId()); std::string result = doPost(postUrl.toStdString()); if (result != "{}") { QString label = QString("Cloud Edit enable failed for %1").arg(creatorType == TEAM_USERS ? "userPlace" : "groupPlace"); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", qPrintable(label)); QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); return; } // launch cloud edit QMetaObject::invokeMethod(&(UpdateUIManager::Instance().getMainWindow()), "cloudEditStatusChanged", Qt::QueuedConnection, Q_ARG(int, m_pDataModel->getPlaceID()), Q_ARG(int, m_pDataModel->getUniverseId())); QString label = QString("Cloud Edit turned on for %1").arg(creatorType == TEAM_USERS ? "userPlace" : "groupPlace"); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", qPrintable(label)); } void RobloxTeamCreateWidget::onBottomMoreButtonClicked() { QMenu menu(this); QAction* cloudEditTurnOffAction = NULL; QAction* deleteUserAction = NULL; if (RobloxIDEDoc::getIsCloudEditSession() && canManageCurrentPlace()) { cloudEditTurnOffAction = menu.addAction(tr("Turn OFF Team Create")); if (getCreatorType() == TEAM_USERS) { deleteUserAction = menu.addAction(tr("Delete User")); deleteUserAction->setIcon(QIcon(":/16x16/images/Studio 2.0 icons/16x16/delete_x_16_h.png")); deleteUserAction->setEnabled(m_pCreatorsListWidget->canDeleteSelectedCreator()); } } QAction* helpAction = menu.addAction(tr("Help")); const int kButtonHeight = 20; const int kMenuMargin = 2; QPoint bottomRight = mapToGlobal(rect().bottomRight()); QSize menuSize = menu.sizeHint(); QAction* resultAction = menu.exec(QPoint(bottomRight.x() - (menuSize.width()+kMenuMargin), bottomRight.y() - (menuSize.height()+kButtonHeight+kMenuMargin))); if (!resultAction) return; if (resultAction == cloudEditTurnOffAction) { QtUtilities::RBXMessageBox msgBox; msgBox.setIcon(QMessageBox::Question); msgBox.setText(tr("Please confirm you want to turn Team Create off.")); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); msgBox.button(QMessageBox::Yes)->setText(tr("Confirm")); msgBox.setDefaultButton(QMessageBox::No); //user has pressed NO or close button, do not close the document if ( msgBox.exec() != QMessageBox::Yes ) return; RBXASSERT(m_pDataModel->getUniverseId()); QString postUrl = QString("%1/universes/%2/disablecloudedit").arg(RobloxSettings::getApiBaseURL()).arg(m_pDataModel->getUniverseId()); std::string result = doPost(postUrl.toStdString()); if (result == "{}") { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Cloud Edit turned off for userPlace"); if (FFlag::KickUsersWhenTheyLoseAccessToCloudEdit) { UpdateUIManager::Instance().getMainWindow().shutDownCloudEditServer(); } else { QMetaObject::invokeMethod(&(UpdateUIManager::Instance().getMainWindow()), "cloudEditStatusChanged", Qt::QueuedConnection, Q_ARG(int, m_pDataModel->getPlaceID()), Q_ARG(int, m_pDataModel->getUniverseId())); } } else { RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Cloud Edit turned off failed for userPlace"); } } else if (resultAction == helpAction) { if (FFlag::TeamCreateEnableContextHelp) { UpdateUIManager::Instance().setDockVisibility(eDW_CONTEXTUAL_HELP, true); QMetaObject::invokeMethod(&RobloxContextualHelpService::singleton(), "onHelpTopicChanged", Q_ARG(QString, QString("Team_Create"))); } else { UpdateUIManager::Instance().getMainWindow().onlineHelpAction->trigger(); } RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Help invoked"); } else if (resultAction == deleteUserAction) { QList items = m_pCreatorsListWidget->selectedItems(); if (items.size() > 0) { RBXASSERT(items.size() == 1); CreatorItem* pCreatorItem = dynamic_cast(items.at(0)); if (pCreatorItem) onRemoveCloudEditorRequested(pCreatorItem->getPlayerId()); } } } void RobloxTeamCreateWidget::onPublishDialogFinished(int status) { disconnect(qobject_cast(sender()), SIGNAL(finished(int)), this, SLOT(onPublishDialogFinished(int))); QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } void RobloxTeamCreateWidget::onPlaceIdChanged(const RBX::Reflection::PropertyDescriptor* desc) { if (!desc || desc->name != "PlaceId") return; QString canManageUrl = QString("%1/users/%2/canmanage/%3").arg(RobloxSettings::getApiBaseURL()).arg(RobloxUser::singleton().getUserId()).arg(m_pDataModel->getPlaceID()); m_PlaceManageStatusQuery = RBX::HttpAsync::get(canManageUrl.toStdString()).then(boost::bind(&RobloxTeamCreateWidget::updatePlaceManageStatus, this, _1)); } eTeamType RobloxTeamCreateWidget::getCreatorType() { if (m_PlaceDetailsQuery.future_ && !m_PlaceDetailsQuery.is_ready()) m_PlaceDetailsQuery.get(); return m_PlaceCreatorDetails.teamType; } void RobloxTeamCreateWidget::updatePlaceManageStatus(RBX::HttpFuture future) { EntityProperties placeManageProp; placeManageProp.setFromJsonFuture(future); m_bCanManageCurrentPlace = placeManageProp.get("CanManage").get_value_or(false); // if user can manage place and cloud edit is not running currently then launch cloud edit int universeId = -1; if (m_bCanManageCurrentPlace && !RobloxIDEDoc::getIsCloudEditSession() && canLaunchCloudEdit(universeId)) { // if user publishes on existing cloud edit enabled place, then launch cloud edit QMetaObject::invokeMethod(&(UpdateUIManager::Instance().getMainWindow()), "cloudEditStatusChanged", Qt::QueuedConnection, Q_ARG(int, m_pDataModel->getPlaceID()), Q_ARG(int, universeId)); return; } if (FFlag::TeamCreateCheckInvalidUniverse && universeId <= 0) { // reset data model, we will not be handling any updates for this place QMetaObject::invokeMethod(this, "resetDataModel", Qt::QueuedConnection); return; } int placeId = m_pDataModel->getPlaceID(); if (placeId <= 0) { // ideally flow must not come here... RBXASSERT(placeId); QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); return; } QString url = m_bCanManageCurrentPlace ? QString("%1/places/%2/settings").arg(RobloxSettings::getBaseURL()).arg(placeId) : QString("%1/Marketplace/ProductInfo?assetId=%2").arg(RobloxSettings::getApiBaseURL()).arg(placeId); m_PlaceDetailsQuery = RBX::HttpAsync::get(url.toStdString()).then(boost::bind(&RobloxTeamCreateWidget::updatePlaceDetails, this, _1)); } void RobloxTeamCreateWidget::updatePlaceDetails(RBX::HttpFuture future) { typedef RBX::Reflection::ValueTable JsonTable; typedef shared_ptr JsonTableRef; EntityProperties placeSettingsProp; placeSettingsProp.setFromJsonFuture(future); JsonTableRef placeCreatorTable = placeSettingsProp.get("Creator").get_value_or(JsonTableRef()); if (placeCreatorTable) { EntityProperties placeCreatorProp; placeCreatorProp.setFromValueTable(placeCreatorTable); if (canManageCurrentPlace()) { int creatorType = placeCreatorProp.get("CreatorType").get_value_or(0); m_PlaceCreatorDetails.teamType = creatorType ? TEAM_GROUP : TEAM_USERS; m_PlaceCreatorDetails.ownerId = placeCreatorProp.get("CreatorTargetId").get_value_or(-1); } else { m_PlaceCreatorDetails.teamType = TEAM_USERS; m_PlaceCreatorDetails.ownerId = placeCreatorProp.get("Id").get_value_or(-1); } std::string ownerName = placeCreatorProp.get("Name").get_value_or(""); if (!ownerName.empty()) m_PlaceCreatorDetails.ownerName = QString::fromStdString(ownerName); } QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } bool RobloxTeamCreateWidget::canManageCurrentPlace() { if (m_PlaceManageStatusQuery.future_ && !m_PlaceManageStatusQuery.is_ready()) m_PlaceManageStatusQuery.get(); return m_bCanManageCurrentPlace; } void RobloxTeamCreateWidget::enableFriendsInviteEdit(bool state) { friendsInviteEdit->setVisible(state); if (state) { loadFriendsList(); QCompleter* pCompleter = friendsInviteEdit->completer(); if (!pCompleter) { pCompleter = new QCompleter(friendsInviteEdit); pCompleter->setCaseSensitivity(Qt::CaseInsensitive); friendsInviteEdit->setCompleter(pCompleter); if (FFlag::TeamCreateEnableCompleterFix) connect(pCompleter->popup(), SIGNAL(clicked(const QModelIndex &)), this, SLOT(onCompleterPopupMenuItemClicked(const QModelIndex&))); } } } void RobloxTeamCreateWidget::loadFriendsList() { if (m_pFriendsInfoLoader) return; m_pFriendsInfoLoader = new FriendsInfoLoader(this, RobloxUser::singleton().getUserId()); connect(m_pFriendsInfoLoader, SIGNAL(finishedLoading()), this, SLOT(updatePlayerFriendsInfo())); } void RobloxTeamCreateWidget::updatePlayerFriendsInfo() { if (!canManageCurrentPlace()) return; QCompleter* pCompleter = friendsInviteEdit->completer(); RBXASSERT(pCompleter); PlayersInfoCollection friendsInfo = m_pFriendsInfoLoader->getFriendsInfo(); QStringList friendsList; QMapIterator iter(friendsInfo); while (iter.hasNext()) { iter.next(); if (!m_pCreatorsListWidget || !m_pCreatorsListWidget->getCreatorItem(iter.key())) friendsList.append(iter.value()); } // update friends list pCompleter->setModel(new QStringListModel(friendsList)); // setting model destroys popup, so need to reinitialize delegate and connection pCompleter->popup()->setItemDelegate(new CompleterPopupDelegate(pCompleter->popup())); if (!FFlag::TeamCreateEnableCompleterFix) connect(pCompleter->popup(), SIGNAL(clicked(const QModelIndex &)), this, SLOT(onCompleterPopupMenuItemClicked(const QModelIndex&))); } void RobloxTeamCreateWidget::onFriendsInviteEditReturnPressed() { QString playerName = friendsInviteEdit->text(); if (playerName.isEmpty()) return; if (m_pCreatorsListWidget && m_pCreatorsListWidget->getCreatorItem(playerName)) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Requested creator '%s' already exists.", qPrintable(playerName)); return; } QApplication::setOverrideCursor(Qt::BusyCursor); EntityProperties prop; prop.setFromJsonFuture(RBX::HttpAsync::get(QString("%1/users/get-by-username?username=%2").arg(RobloxSettings::getApiBaseURL()).arg(playerName).toStdString())); if (boost::optional userIdOpt = prop.get("Id")) { int playerId = userIdOpt.get(); if (playerId > 0 && addCloudEditor(playerId)) { if (m_pCreatorsListWidget) m_pCreatorsListWidget->addCreatorItem(playerId, playerName, "Offline"); if (FFlag::TeamCreateEnableCompleterFix) QMetaObject::invokeMethod(friendsInviteEdit, "clear", Qt::QueuedConnection); else friendsInviteEdit->clear(); updatePlayerFriendsInfo(); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Creator added from editor"); } } else { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Requested creator '%s' does not exist.", qPrintable(playerName)); } QApplication::restoreOverrideCursor(); } void RobloxTeamCreateWidget::onCompleterPopupMenuItemClicked(const QModelIndex& clickedIndex) { QString playerName = clickedIndex.data(Qt::DisplayRole).toString(); int playerId = m_pFriendsInfoLoader ? m_pFriendsInfoLoader->getFriendsInfo().key(playerName, 0) : 0; QApplication::setOverrideCursor(Qt::BusyCursor); if (playerId > 0 && addCloudEditor(playerId)) { if (m_pCreatorsListWidget) m_pCreatorsListWidget->addCreatorItem(playerId, playerName, "Offline"); updatePlayerFriendsInfo(); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Creator added from popup"); } QApplication::restoreOverrideCursor(); friendsInviteEdit->clear(); } void RobloxTeamCreateWidget::onRemoveCloudEditorRequested(int playerId) { userRemoveButton->setProperty("userIdToDelete", playerId); m_pMainStackedWidgetAnimator->setCurrentIndex(USER_REMOVE_CONFIRM_PAGE); } void RobloxTeamCreateWidget::onRemoveCloudEditConfirm() { QApplication::setOverrideCursor(Qt::BusyCursor); int playerId = userRemoveButton->property("userIdToDelete").toInt(); if (playerId > 0 && removeCloudEditor(playerId)) { CreatorItem* pCreatorItem = m_pCreatorsListWidget->getCreatorItem(playerId); if (pCreatorItem) { delete pCreatorItem; updatePlayerFriendsInfo(); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Creator removed"); } if (FFlag::KickUsersWhenTheyLoseAccessToCloudEdit) { RBXASSERT(m_pDataModel); if (m_pDataModel) { RBX::DataModel::LegacyLock lock(m_pDataModel.get(), RBX::DataModelJob::Write); if (RBX::Network::Players* players = m_pDataModel->find()) { RBX::Network::Players::event_requestCloudEditKick.replicateEvent(players, playerId); } } } } QApplication::restoreOverrideCursor(); userRemoveButton->setProperty("userIdToDelete", QVariant()); QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } void RobloxTeamCreateWidget::onRemoveCloudEditCancel() { userRemoveButton->setProperty("userIdToDelete", QVariant()); QMetaObject::invokeMethod(this, "updateCurrentWidgetInStack", Qt::QueuedConnection); } bool RobloxTeamCreateWidget::addCloudEditor(int playerId) { // if player id is owner, then do not add as editor if (playerId == m_PlaceCreatorDetails.ownerId) return false; int universeId = m_pDataModel->getUniverseId(); RBXASSERT(universeId); RBXASSERT(playerId); bool canAddEditor = true; if (!RobloxIDEDoc::getIsCloudEditSession()) { QString postUrl = QString("%1/universes/%2/enablecloudedit").arg(RobloxSettings::getApiBaseURL()).arg(universeId); if (doPost(postUrl.toStdString()) == "{}") { // launch cloud edit QMetaObject::invokeMethod(&(UpdateUIManager::Instance().getMainWindow()), "cloudEditStatusChanged", Qt::QueuedConnection, Q_ARG(int, m_pDataModel->getPlaceID()), Q_ARG(int, m_pDataModel->getUniverseId())); } else { canAddEditor = false; RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Error while turning on cloud edit"); } } if (canAddEditor) { QString postUrl = QString("%1/universes/%2/addcloudeditor?userId=%3").arg(RobloxSettings::getApiBaseURL()).arg(universeId).arg(playerId); std::string result = doPost(postUrl.toStdString()); return result == "{}"; } return false; } bool RobloxTeamCreateWidget::removeCloudEditor(int playerId) { int universeId = m_pDataModel->getUniverseId(); RBXASSERT(universeId); RBXASSERT(playerId); QString postUrl = QString("%1/universes/%2/removecloudeditor?userId=%3").arg(RobloxSettings::getApiBaseURL()).arg(universeId).arg(playerId); std::string result = doPost(postUrl.toStdString()); return result == "{}"; } bool RobloxTeamCreateWidget::canLaunchCloudEdit(int &universeId) { if (FFlag::DoNotCheckCanCloudEditWhenNotLoggedIn && RobloxUser::singleton().getUserId() <= 0) { return false; } universeId = m_pDataModel->getUniverseId(); // in case universe id hasn't been set in datamodel, query for universe id if (universeId <= 0) { int placeId = m_pDataModel->getPlaceID(); RBXASSERT(placeId > 0); EntityProperties universeIdProp; QString getUniverseUrl = QString("%1//universes/get-universe-containing-place?placeId=%2").arg(RobloxSettings::getApiBaseURL()).arg(placeId); universeIdProp.setFromJsonFuture(RBX::HttpAsync::get(getUniverseUrl.toStdString())); universeId = universeIdProp.get("UniverseId").get_value_or(-1); } if (universeId > 0) { EntityProperties cloudEditEnabledProp; QString cloudEditEnabledUrl = QString("%1/universes/%2/cloudeditenabled").arg(RobloxSettings::getApiBaseURL()).arg(universeId); cloudEditEnabledProp.setFromJsonFuture(RBX::HttpAsync::get(cloudEditEnabledUrl.toStdString())); return cloudEditEnabledProp.get("enabled").get_value_or(false); } return false; } void RobloxTeamCreateWidget::resetDataModel() { setDataModel(shared_ptr(), shared_ptr()); } std::string RobloxTeamCreateWidget::doPost(const std::string& postUrl) { std::string result; try { RBX::HttpPostData d("", RBX::Http::kContentTypeDefaultUnspecified, false); result = RBX::HttpAsync::post(postUrl, d).get(); } catch (const RBX::base_exception& e) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Error: %s", e.what()); RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Team Create", "Post error"); result = e.what(); } return result; } //-------------------------------------------------------------------------------------------- // FriendsInfoLoader //-------------------------------------------------------------------------------------------- static void invokeFriendsDataLoaded(QPointer friendsInfoLoader, int pageLoaded, RBX::HttpFuture future) { if (friendsInfoLoader.isNull()) return; QMetaObject::invokeMethod(friendsInfoLoader.data(), "friendsDataLoaded", Qt::QueuedConnection, Q_ARG(int, pageLoaded), Q_ARG(RBX::HttpFuture, future)); } FriendsInfoLoader::FriendsInfoLoader(QObject* parent, int playerId) : QObject(parent) , m_PlayerId(playerId) , m_bFinishedLoading(false) { loadPage(1); } void FriendsInfoLoader::loadPage(int pageToLoad) { QString str = QString("%1/users/%2/friends?page=%3").arg(RobloxSettings::getApiBaseURL()).arg(m_PlayerId).arg(pageToLoad); RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokeFriendsDataLoaded, QPointer(this), pageToLoad, _1)); } void FriendsInfoLoader::friendsDataLoaded(int pageLoaded, RBX::HttpFuture future) { try { boost::shared_ptr table; if (!RBX::WebParser::parseJSONArray(future.get(), table) || table->size() <= 0) { // we get empty table once the friends list is complete doneLoading(); return; } boost::optional friendName; boost::optional friendId; EntityProperties friendsProp; for (RBX::Reflection::ValueArray::const_iterator iter = table->begin(); iter != table->end(); ++iter) { if (iter->isType >()) { friendsProp.setFromValueTable(iter->cast >()); friendName = friendsProp.get("Username"); friendId = friendsProp.get("Id"); if (friendName.is_initialized() && !friendName.get().empty() && friendId.is_initialized()) m_FriendsInfo.insert(friendId.get(), friendName.get().c_str()); } } } catch (const RBX::base_exception& ) { doneLoading(); return; } loadPage(pageLoaded+1); } void FriendsInfoLoader::doneLoading() { m_bFinishedLoading = true; Q_EMIT finishedLoading(); } //-------------------------------------------------------------------------------------------- // CreatorsListLoader //-------------------------------------------------------------------------------------------- static void invokeCreatorsListLoaded(QPointer creatorsListLoader, int pageLoaded, RBX::HttpFuture future) { if (creatorsListLoader.isNull()) return; QMetaObject::invokeMethod(creatorsListLoader.data(), "creatorsListLoaded", Qt::QueuedConnection, Q_ARG(int, pageLoaded), Q_ARG(RBX::HttpFuture, future)); } CreatorsListLoader::CreatorsListLoader(QObject* parent, int universeId) : QObject(parent) , m_UniverseId(universeId) , m_bFinishedLoading(false) { loadPage(1); } void CreatorsListLoader::reload() { if (!m_bFinishedLoading) return; m_Creators.clear(); loadPage(1); } void CreatorsListLoader::loadPage(int pageToLoad) { QString str = QString("%1/universes/%2/listcloudeditors?page=%3").arg(RobloxSettings::getApiBaseURL()).arg(m_UniverseId).arg(pageToLoad); RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokeCreatorsListLoaded, QPointer(this), pageToLoad, _1)); } void CreatorsListLoader::creatorsListLoaded(int pageLoaded, RBX::HttpFuture future) { typedef RBX::Reflection::ValueArray JsonArray; typedef shared_ptr JsonArryRef; EntityProperties creatorsListProp; creatorsListProp.setFromJsonFuture(future); bool finalPage = creatorsListProp.get("finalPage").get_value_or(true); JsonArryRef creatorsList = creatorsListProp.get("users").get_value_or(JsonArryRef()); if (creatorsList) { int userId = -1; bool isAdmin = false; EntityProperties creatorsDetails; for (JsonArray::const_iterator iter = creatorsList->begin(); iter != creatorsList->end(); ++iter) { if (iter->isType >()) { creatorsDetails.setFromValueTable(iter->cast >()); userId = creatorsDetails.get("userId").get_value_or(-1); isAdmin = creatorsDetails.get("isAdmin").get_value_or(false); if (userId > 0) m_Creators.insert(userId, isAdmin); } } } if (!finalPage) loadPage(pageLoaded+1); else doneLoading(); } void CreatorsListLoader::doneLoading() { m_bFinishedLoading = true; Q_EMIT finishedLoading(); }