/**************************************************************** * RetroShare is distributed under the following license: * * Copyright (C) 2008 Robert Fernie * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. ****************************************************************/ #include #include #include "GxsForumsDialog.h" #include "gxs/GxsForumGroupDialog.h" #include "gxsforums/GxsForumThreadWidget.h" #include "settings/rsharesettings.h" #include "RetroShareLink.h" #include "channels/ShareKey.h" #include "notifyqt.h" // These should be in retroshare/ folder. #include "gxs/rsgxsflags.h" //#define DEBUG_FORUMS /* Images for TreeWidget */ #define IMAGE_FOLDER ":/images/folder16.png" #define IMAGE_FOLDERGREEN ":/images/folder_green.png" #define IMAGE_FOLDERRED ":/images/folder_red.png" #define IMAGE_FOLDERYELLOW ":/images/folder_yellow.png" #define IMAGE_FORUM ":/images/konversation.png" #define IMAGE_SUBSCRIBE ":/images/edit_add24.png" #define IMAGE_UNSUBSCRIBE ":/images/cancel.png" #define IMAGE_INFO ":/images/info16.png" #define IMAGE_NEWFORUM ":/images/new_forum16.png" #define IMAGE_FORUMAUTHD ":/images/konv_message2.png" #define IMAGE_COPYLINK ":/images/copyrslink.png" /* * Transformation Notes: * there are still a couple of things that the new forums differ from Old version. * these will need to be addressed in the future. * -> Missing Messages are not handled yet. * -> Child TS (for sorting) is not handled by GXS, this will probably have to be done in the GUI. * -> Need to handle IDs properly. * -> Popularity not handled in GXS yet. * -> Much more to do. */ /** Constructor */ GxsForumsDialog::GxsForumsDialog(QWidget *parent) : RsAutoUpdatePage(1000,parent) { /* Invoke the Qt Designer generated object setup routine */ ui.setupUi(this); /* Setup Queue */ mForumQueue = new TokenQueue(rsGxsForums->getTokenService(), this); mTokenGroupSummary = 0; mRequestGroupSummary = false; connect(ui.forumTreeWidget, SIGNAL(treeCustomContextMenuRequested(QPoint)), this, SLOT(forumListCustomPopupMenu(QPoint))); connect(ui.newForumButton, SIGNAL(clicked()), this, SLOT(newforum())); connect(ui.forumTreeWidget, SIGNAL(treeItemClicked(QString)), this, SLOT(changedForum(QString))); connect(ui.threadTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(threadTabCloseRequested(int))); connect(NotifyQt::getInstance(), SIGNAL(forumMsgReadSatusChanged(QString,QString,int)), this, SLOT(forumMsgReadSatusChanged(QString,QString,int))); // HACK - TEMPORARY HIJACKING THIS BUTTON FOR REFRESH. connect(ui.refreshButton, SIGNAL(clicked()), this, SLOT(forceUpdateDisplay())); /* Set initial size the splitter */ QList sizes; sizes << 300 << width(); // Qt calculates the right sizes ui.splitter->setSizes(sizes); /* create forum tree */ yourForums = ui.forumTreeWidget->addCategoryItem(tr("Your Forums"), QIcon(IMAGE_FOLDER), true); subscribedForums = ui.forumTreeWidget->addCategoryItem(tr("Subscribed Forums"), QIcon(IMAGE_FOLDERRED), true); popularForums = ui.forumTreeWidget->addCategoryItem(tr("Popular Forums"), QIcon(IMAGE_FOLDERGREEN), false); otherForums = ui.forumTreeWidget->addCategoryItem(tr("Other Forums"), QIcon(IMAGE_FOLDERYELLOW), false); // load settings processSettings(true); /* Hide platform specific features */ #ifdef Q_WS_WIN #endif } GxsForumsDialog::~GxsForumsDialog() { // save settings processSettings(false); delete(mForumQueue); } //#AFTER MERGE UserNotify *GxsForumsDialog::getUserNotify(QObject *parent) //{ // return new GxsForumUserNotify(parent); //} void GxsForumsDialog::processSettings(bool bLoad) { Settings->beginGroup(QString("GxsForumsDialog")); if (bLoad) { // load settings // state of splitter ui.splitter->restoreState(Settings->value("Splitter").toByteArray()); } else { // save settings // state of splitter Settings->setValue("Splitter", ui.splitter->saveState()); } ui.forumTreeWidget->processSettings(Settings, bLoad); Settings->endGroup(); } void GxsForumsDialog::forumListCustomPopupMenu(QPoint /*point*/) { int subscribeFlags = ui.forumTreeWidget->subscribeFlags(QString::fromStdString(mForumId)); QMenu contextMnu(this); QAction *action = contextMnu.addAction(QIcon(IMAGE_SUBSCRIBE), tr("Subscribe to Forum"), this, SLOT(subscribeToForum())); action->setDisabled (mForumId.empty() || IS_GROUP_SUBSCRIBED(subscribeFlags)); action = contextMnu.addAction(QIcon(IMAGE_UNSUBSCRIBE), tr("Unsubscribe to Forum"), this, SLOT(unsubscribeToForum())); action->setEnabled (!mForumId.empty() && IS_GROUP_SUBSCRIBED(subscribeFlags)); contextMnu.addSeparator(); contextMnu.addAction(QIcon(IMAGE_NEWFORUM), tr("New Forum"), this, SLOT(newforum())); action = contextMnu.addAction(QIcon(IMAGE_INFO), tr("Show Forum Details"), this, SLOT(showForumDetails())); action->setEnabled (!mForumId.empty ()); action = contextMnu.addAction(QIcon(":/images/settings16.png"), tr("Edit Forum Details"), this, SLOT(editForumDetails())); action->setEnabled (!mForumId.empty () && IS_GROUP_ADMIN(subscribeFlags)); QAction *shareKeyAct = new QAction(QIcon(":/images/gpgp_key_generate.png"), tr("Share Forum"), &contextMnu); connect( shareKeyAct, SIGNAL( triggered() ), this, SLOT( shareKey() ) ); shareKeyAct->setEnabled(!mForumId.empty() && IS_GROUP_ADMIN(subscribeFlags)); contextMnu.addAction( shareKeyAct); QAction *restoreKeysAct = new QAction(QIcon(":/images/settings16.png"), tr("Restore Publish Rights for Forum" ), &contextMnu); connect( restoreKeysAct , SIGNAL( triggered() ), this, SLOT( restoreForumKeys() ) ); restoreKeysAct->setEnabled(!mForumId.empty() && !IS_GROUP_ADMIN(subscribeFlags)); contextMnu.addAction( restoreKeysAct); action = contextMnu.addAction(QIcon(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyForumLink())); action->setEnabled(!mForumId.empty()); contextMnu.addSeparator(); action = contextMnu.addAction(QIcon(":/images/message-mail-read.png"), tr("Mark all as read"), this, SLOT(markMsgAsReadAll())); action->setEnabled (!mForumId.empty () && IS_GROUP_SUBSCRIBED(subscribeFlags)); action = contextMnu.addAction(QIcon(":/images/message-mail.png"), tr("Mark all as unread"), this, SLOT(markMsgAsUnreadAll())); action->setEnabled (!mForumId.empty () && IS_GROUP_SUBSCRIBED(subscribeFlags)); #ifdef DEBUG_FORUMS contextMnu.addSeparator(); action = contextMnu.addAction("Generate mass data", this, SLOT(generateMassData())); action->setEnabled (!mCurrForumId.empty() && IS_GROUP_SUBSCRIBED(mSubscribeFlags)); #endif contextMnu.exec(QCursor::pos()); } void GxsForumsDialog::restoreForumKeys(void) { QMessageBox::warning(this, "RetroShare", "ToDo"); #ifdef TOGXS rsGxsForums->groupRestoreKeys(mCurrForumId); #endif } void GxsForumsDialog::updateDisplay() { std::list forumIds; std::list::iterator it; if (!rsGxsForums) return; #if 0 // TODO groupsChanged... HACK XXX. if ((rsGxsForums->groupsChanged(forumIds)) || (rsGxsForums->updated())) { /* update Forums List */ insertForums(); it = std::find(forumIds.begin(), forumIds.end(), mCurrForumId); if (it != forumIds.end()) { /* update threads as well */ insertThreads(); } } #endif /* The proper version (above) can be done with a data request -> TODO */ if (rsGxsForums->updated()) { /* update Forums List */ insertForums(); } } // HACK until update works. void GxsForumsDialog::forceUpdateDisplay() { std::cerr << "GxsForumsDialog::forceUpdateDisplay()"; std::cerr << std::endl; /* update Forums List */ insertForums(); } void GxsForumsDialog::forumInfoToGroupItemInfo(const RsGroupMetaData &forumInfo, GroupItemInfo &groupItemInfo) { groupItemInfo.id = QString::fromStdString(forumInfo.mGroupId); groupItemInfo.name = QString::fromUtf8(forumInfo.mGroupName.c_str()); //groupItemInfo.description = QString::fromUtf8(forumInfo.forumDesc); groupItemInfo.popularity = forumInfo.mPop; groupItemInfo.lastpost = QDateTime::fromTime_t(forumInfo.mLastPost); groupItemInfo.subscribeFlags = forumInfo.mSubscribeFlags; #if TOGXS if (forumInfo.mGroupFlags & RS_DISTRIB_AUTHEN_REQ) { groupItemInfo.name += " (" + tr("AUTHD") + ")"; groupItemInfo.icon = QIcon(IMAGE_FORUMAUTHD); } else #endif { groupItemInfo.icon = QIcon(IMAGE_FORUM); } } /***** INSERT FORUM LISTS *****/ void GxsForumsDialog::insertForumsData(const std::list &forumList) { std::list::const_iterator it; QList adminList; QList subList; QList popList; QList otherList; std::multimap popMap; for (it = forumList.begin(); it != forumList.end(); it++) { /* sort it into Publish (Own), Subscribed, Popular and Other */ uint32_t flags = it->mSubscribeFlags; GroupItemInfo groupItemInfo; forumInfoToGroupItemInfo(*it, groupItemInfo); if (IS_GROUP_ADMIN(flags)) { adminList.push_back(groupItemInfo); } else if (IS_GROUP_SUBSCRIBED(flags)) { /* subscribed forum */ subList.push_back(groupItemInfo); } else { /* rate the others by popularity */ popMap.insert(std::make_pair(it->mPop, groupItemInfo)); } } /* iterate backwards through popMap - take the top 5 or 10% of list */ uint32_t popCount = 5; if (popCount < popMap.size() / 10) { popCount = popMap.size() / 10; } uint32_t i = 0; uint32_t popLimit = 0; std::multimap::reverse_iterator rit; for(rit = popMap.rbegin(); ((rit != popMap.rend()) && (i < popCount)); rit++, i++) ; if (rit != popMap.rend()) { popLimit = rit->first; } for (rit = popMap.rbegin(); rit != popMap.rend(); rit++) { if (rit->second.popularity < (int) popLimit) { otherList.append(rit->second); } else { popList.append(rit->second); } } /* now we can add them in as a tree! */ ui.forumTreeWidget->fillGroupItems(yourForums, adminList); ui.forumTreeWidget->fillGroupItems(subscribedForums, subList); ui.forumTreeWidget->fillGroupItems(popularForums, popList); ui.forumTreeWidget->fillGroupItems(otherForums, otherList); updateMessageSummaryList(""); } void GxsForumsDialog::changedForum(const QString &id) { mForumId = id.toStdString(); if (mForumId.empty()) { return; } // requestGroupSummary_CurrentForum(mForumId); /* search exisiting tab */ GxsForumThreadWidget *threadWidget = NULL; int tabCount = ui.threadTabWidget->count(); for (int index = 0; index < tabCount; ++index) { GxsForumThreadWidget *childWidget = dynamic_cast(ui.threadTabWidget->widget(index)); if (childWidget && childWidget->forumId() == id.toStdString()) { threadWidget = childWidget; break; } } if (!threadWidget) { /* create a thread widget */ threadWidget = new GxsForumThreadWidget(id.toStdString()); int index = ui.threadTabWidget->addTab(threadWidget, threadWidget->forumName()); ui.threadTabWidget->setTabIcon(index, threadWidget->forumIcon()); connect(threadWidget, SIGNAL(forumChanged(QWidget*)), this, SLOT(threadTabChanged(QWidget*))); } ui.threadTabWidget->setCurrentWidget(threadWidget); } void GxsForumsDialog::threadTabCloseRequested(int index) { GxsForumThreadWidget *threadWidget = dynamic_cast(ui.threadTabWidget->widget(index)); if (threadWidget) { delete(threadWidget); } } void GxsForumsDialog::threadTabChanged(QWidget *widget) { int index = ui.threadTabWidget->indexOf(widget); if (index < 0) { return; } GxsForumThreadWidget *threadWidget = dynamic_cast(ui.threadTabWidget->widget(index)); if (!threadWidget) { return; } ui.threadTabWidget->setTabText(index, threadWidget->forumName()); ui.threadTabWidget->setTabIcon(index, threadWidget->forumIcon()); } QString GxsForumsDialog::titleFromInfo(const RsMsgMetaData &meta) { // NOTE - NOTE SURE HOW THIS WILL WORK! #ifdef TOGXS if (meta.mMsgStatus & RS_DISTRIB_MISSING_MSG) { return QApplication::translate("GxsForumsDialog", "[ ... Missing Message ... ]"); } #endif return QString::fromUtf8(meta.mMsgName.c_str()); } QString GxsForumsDialog::messageFromInfo(const RsGxsForumMsg &msg) { #ifdef TOGXS if (msg.mMeta.mMsgStatus & RS_DISTRIB_MISSING_MSG) { return QApplication::translate("GxsForumsDialog", "Placeholder for missing Message"); } #endif return QString::fromUtf8(msg.mMsg.c_str()); } void GxsForumsDialog::copyForumLink() { if (mForumId.empty()) { return; } // THIS CODE CALLS getForumInfo() to verify that the Ids are valid. // As we are switching to Request/Response this is now harder to do... // So not bothering any more - shouldn't be necessary. // IF we get errors - fix them, rather than patching here. #if 0 ForumInfo fi; if (rsGxsForums->getForumInfo(mCurrForumId, fi)) { RetroShareLink link; if (link.createForum(fi.forumId, "")) { QList urls; urls.push_back(link); RSLinkClipboard::copyLinks(urls); } } #endif QMessageBox::warning(this, "RetroShare", "ToDo"); } void GxsForumsDialog::newforum() { GxsForumGroupDialog cf(mForumQueue, this); cf.exec (); } void GxsForumsDialog::subscribeToForum() { forumSubscribe(true); } void GxsForumsDialog::unsubscribeToForum() { forumSubscribe(false); } void GxsForumsDialog::forumSubscribe(bool subscribe) { if (mForumId.empty()) { return; } uint32_t token; rsGxsForums->subscribeToGroup(token, mForumId, subscribe); } void GxsForumsDialog::showForumDetails() { if (mForumId.empty()) { return; } RsGxsForumGroup grp; grp.mMeta.mGroupId = mForumId; GxsForumGroupDialog cf(grp, GxsGroupDialog::MODE_SHOW, this); cf.exec (); } void GxsForumsDialog::editForumDetails() { if (mForumId.empty()) { return; } RsGxsForumGroup grp; grp.mMeta.mGroupId = mForumId; GxsForumGroupDialog cf(grp, GxsGroupDialog::MODE_EDIT, this); cf.exec (); } void GxsForumsDialog::shareKey() { ShareKey shareUi(this, 0, mForumId, FORUM_KEY_SHARE); shareUi.exec(); } void GxsForumsDialog::updateMessageSummaryList(std::string forumId) { QTreeWidgetItem *items[2] = { yourForums, subscribedForums }; for (int item = 0; item < 2; item++) { int child; int childCount = items[item]->childCount(); for (child = 0; child < childCount; child++) { QTreeWidgetItem *childItem = items[item]->child(child); std::string childId = ui.forumTreeWidget->itemId(childItem).toStdString(); if (childId.empty()) { continue; } if (forumId.empty() || childId == forumId) { /* calculate unread messages */ unsigned int newMessageCount = 0; unsigned int unreadMessageCount = 0; //#TODO rsGxsForums->getMessageCount(childId, newMessageCount, unreadMessageCount); std::cerr << "IMPLEMENT rsGxsForums->getMessageCount()"; std::cerr << std::endl; ui.forumTreeWidget->setUnreadCount(childItem, unreadMessageCount); if (forumId.empty() == false) { /* Calculate only this forum */ break; } } } } } bool GxsForumsDialog::navigate(const std::string& forumId, const std::string& msgId) { if (forumId.empty()) { return false; } if (ui.forumTreeWidget->activateId(QString::fromStdString(forumId), msgId.empty()) == NULL) { return false; } /* Threads are filled in changedForum */ if (mForumId != forumId) { return false; } if (msgId.empty()) { return true; } //#TODO // if (mThreadLoading) { // mThreadLoad.FocusMsgId = msgId; // return true; // } /* Search exisiting item */ // QTreeWidgetItemIterator itemIterator(ui.threadTreeWidget); // QTreeWidgetItem *item = NULL; // while ((item = *itemIterator) != NULL) { // itemIterator++; // if (item->data(COLUMN_THREAD_DATA, ROLE_THREAD_MSGID).toString().toStdString() == msgId) { // ui.threadTreeWidget->setCurrentItem(item); // ui.threadTreeWidget->setFocus(); // return true; // } // } return false; } void GxsForumsDialog::generateMassData() { #ifdef DEBUG_FORUMS if (mCurrForumId.empty ()) { return; } if (QMessageBox::question(this, "Generate mass data", "Do you really want to generate mass data ?", QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::No) { return; } for (int thread = 1; thread < 1000; thread++) { ForumMsgInfo threadInfo; threadInfo.forumId = mCurrForumId; threadInfo.title = QString("Test %1").arg(thread, 3, 10, QChar('0')).toStdWString(); threadInfo.msg = QString("That is only a test").toStdWString(); if (rsGxsForums->ForumMessageSend(threadInfo) == false) { return; } for (int msg = 1; msg < 3; msg++) { ForumMsgInfo msgInfo; msgInfo.forumId = mCurrForumId; msgInfo.threadId = threadInfo.msgId; msgInfo.parentId = threadInfo.msgId; msgInfo.title = threadInfo.title; msgInfo.msg = threadInfo.msg; if (rsGxsForums->ForumMessageSend(msgInfo) == false) { return; } } } #endif } /*********************** **** **** **** ***********************/ /** Request / Response of Data ********************************/ /*********************** **** **** **** ***********************/ #define FORUMSV2DIALOG_LISTING 1 //#define FORUMSV2DIALOG_CURRENTFORUM 2 void GxsForumsDialog::insertForums() { requestGroupSummary(); } void GxsForumsDialog::requestGroupSummary() { std::cerr << "GxsForumsDialog::requestGroupSummary()"; std::cerr << std::endl; if (mRequestGroupSummary) { std::cerr << "GxsForumsDialog::requestGroupSummary() Canceling Request: " << mTokenGroupSummary; std::cerr << std::endl; mForumQueue->cancelRequest(mTokenGroupSummary); mTokenGroupSummary = 0; mRequestGroupSummary = false; } RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_GROUP_META; mForumQueue->requestGroupInfo(mTokenGroupSummary, RS_TOKREQ_ANSTYPE_SUMMARY, opts, FORUMSV2DIALOG_LISTING); mRequestGroupSummary = true; } void GxsForumsDialog::loadGroupSummary(const uint32_t &token) { std::cerr << "GxsForumsDialog::loadGroupSummary()"; std::cerr << std::endl; if (!mRequestGroupSummary) { std::cerr << "GxsForumsDialog::loadGroupSummary()) No waiting request got token: " << token; std::cerr << std::endl; return; } if (token != mTokenGroupSummary) { std::cerr << "GxsForumsDialog::loadGroupSummary()) Wrong token - want: " << mTokenGroupSummary << " got: " << token; std::cerr << std::endl; return; } std::list groupInfo; rsGxsForums->getGroupSummary(token, groupInfo); if (groupInfo.size() > 0) { insertForumsData(groupInfo); } else { std::cerr << "GxsForumsDialog::loadGroupSummary() ERROR No Groups..."; std::cerr << std::endl; } mTokenGroupSummary = 0; mRequestGroupSummary = false; } /*********************** **** **** **** ***********************/ /*********************** **** **** **** ***********************/ //void GxsForumsDialog::requestGroupSummary_CurrentForum(const std::string &forumId) //{ // RsTokReqOptions opts; // opts.mReqType = GXS_REQUEST_TYPE_GROUP_META; // std::list grpIds; // grpIds.push_back(forumId); // std::cerr << "GxsForumsDialog::requestGroupSummary_CurrentForum(" << forumId << ")"; // std::cerr << std::endl; // uint32_t token; // mForumQueue->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_SUMMARY, opts, grpIds, FORUMSV2DIALOG_CURRENTFORUM); //} //void GxsForumsDialog::loadGroupSummary_CurrentForum(const uint32_t &token) //{ // std::cerr << "GxsForumsDialog::loadGroupSummary_CurrentForum()"; // std::cerr << std::endl; // std::list groupInfo; // rsGxsForums->getGroupSummary(token, groupInfo); // if (groupInfo.size() == 1) // { // RsGroupMetaData fi = groupInfo.front(); // mSubscribeFlags = fi.mSubscribeFlags; // } // else // { // resetData(); // std::cerr << "GxsForumsDialog::loadGroupSummary_CurrentForum() ERROR Invalid Number of Groups..."; // std::cerr << std::endl; // } // setValid(true); //} /*********************** **** **** **** ***********************/ /*********************** **** **** **** ***********************/ void GxsForumsDialog::loadRequest(const TokenQueue *queue, const TokenRequest &req) { std::cerr << "GxsForumsDialog::loadRequest() UserType: " << req.mUserType; std::cerr << std::endl; if (queue == mForumQueue) { /* now switch on req */ switch(req.mUserType) { case FORUMSV2DIALOG_LISTING: loadGroupSummary(req.mToken); break; // case FORUMSV2DIALOG_CURRENTFORUM: // loadGroupSummary_CurrentForum(req.mToken); // break; default: std::cerr << "GxsForumsDialog::loadRequest() ERROR: INVALID TYPE"; std::cerr << std::endl; break; } } }