diff --git a/retroshare-gui/src/gui/Posted/PostedListWidgetWithModel.cpp b/retroshare-gui/src/gui/Posted/PostedListWidgetWithModel.cpp new file mode 100644 index 000000000..3df9a3d76 --- /dev/null +++ b/retroshare-gui/src/gui/Posted/PostedListWidgetWithModel.cpp @@ -0,0 +1,998 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/PostedListWidgetWithModel.cpp * + * * + * Copyright 2013 by Robert Fernie * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. * + * * + * You should have received a copy of the GNU Affero General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +#include +#include +#include +#include +#include + +#include "retroshare/rsgxscircles.h" + +#include "ui_PostedListWidgetWithModel.h" +#include "gui/feeds/GxsChannelPostItem.h" +#include "gui/gxs/GxsIdDetails.h" +#include "util/misc.h" +#include "gui/Posted/PostedCreatePostDialog.h" +#include "gui/common/UIStateHelper.h" +#include "gui/settings/rsharesettings.h" +#include "gui/feeds/SubFileItem.h" +#include "gui/notifyqt.h" +#include "gui/RetroShareLink.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" +#include "util/qtthreadsutils.h" +#include "gui/common/FilesDefs.h" + +#include "PostedListWidgetWithModel.h" +#include "PostedPostsModel.h" +#include "PostedCardView.h" +#include "BoardPostDisplayWidget.h" + +#include + +#define ROLE_PUBLISH FEED_TREEWIDGET_SORTROLE + +/**** + * #define DEBUG_POSTED + ***/ + +/* View mode */ +#define VIEW_MODE_FEEDS 1 +#define VIEW_MODE_FILES 2 + +// Determine the Shape and size of cells as a factor of the font height. An aspect ratio of 3/4 is what's needed +// for the image, so it's important that the height is a bit larger so as to leave some room for the text. +// +// +#define IMAGE_COPYLINK ":/images/copyrslink.png" + +// Delegate used to paint into the table of thumbnails + +void PostedPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + if((option.state & QStyle::State_Selected)) // Avoids double display. The selected widget is never exactly the size of the rendered one, + return; // so when selected, we only draw the selected one. + + // prepare + painter->save(); + painter->setClipRect(option.rect); + + RsPostedPost post = index.data(Qt::UserRole).value() ; + + painter->save(); + + painter->fillRect( option.rect, option.backgroundBrush); + painter->restore(); + + BoardPostDisplayWidget w(post); + w.setMaximumWidth(mCellWidthPix); + w.setMinimumWidth(mCellWidthPix); + w.adjustSize(); + + QPixmap pixmap(option.rect.size()); + + std::cerr << "pixmap.depth=" << pixmap.depth() << std::endl; + +#ifdef SUSPENDED_CODE + if((option.state & QStyle::State_Selected) && post.mMeta.mPublishTs > 0) // check if post is selected and is not empty (end of last row) + pixmap.fill(QRgb(0xff308dc7)); // I dont know how to grab the backgroud color for selected objects automatically. + else +#endif + pixmap.fill(QRgb(0x00f0f0f0)); // choose a fully transparent background + +#ifdef TESTING + { + QPainter W(&pixmap); + QLinearGradient m_gradient(0,0,pixmap.width(),0); + m_gradient.setColorAt(0.0, QRgb(0xff390e90)); + m_gradient.setColorAt(1.0, QRgb(0xff09feff)); + W.fillRect(pixmap.rect(), m_gradient); + + w.render(&W,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background + } +#endif + + w.render(&pixmap,QPoint(0,0),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background + +#ifdef TODO + if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus)) + { + QPainter p(&pixmap); + QFontMetricsF fm(option.font); + p.drawPixmap(mZoom*QPoint(6.2*fm.height(),6.9*fm.height()),FilesDefs::getPixmapFromQtResourcePath(STAR_OVERLAY_IMAGE).scaled(mZoom*7*fm.height(),mZoom*7*fm.height(),Qt::KeepAspectRatio,Qt::SmoothTransformation)); + } +#endif + + // debug + if(index.row()==0 && index.column()==0) + { + QFile file("yourFile.png"); + file.open(QIODevice::WriteOnly); + pixmap.save(&file, "PNG"); + std::cerr << "Saved pxmap to png" << std::endl; + } + //std::cerr << "option.rect = " << option.rect.width() << "x" << option.rect.height() << ". fm.height()=" << QFontMetricsF(option.font).height() << std::endl; + + painter->save(); + + // Is this necessary? + // + // painter->setRenderHint( QPainter::Antialiasing,true); + // painter->setRenderHint( QPainter::TextAntialiasing,true); + // painter->setRenderHint( QPainter::SmoothPixmapTransform,true); + // painter->setRenderHint( QPainter::HighQualityAntialiasing,true); + + painter->drawPixmap(option.rect.topLeft(), pixmap /*,.scaled(option.rect.width(),option.rect.width()*w.height()/(float)w.width(),Qt::KeepAspectRatio,Qt::SmoothTransformation)*/); + painter->restore(); +} + +QSize PostedPostDelegate::cellSize(const QSize& w) const +{ + return QSize(mCellWidthPix,0 + w.height() * mCellWidthPix/(float)w.width()); +} + +QSize PostedPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + // This is the only place where we actually set the size of cells + + RsPostedPost post = index.data(Qt::UserRole).value() ; + + BoardPostDisplayWidget w(post); + w.setMinimumWidth(mCellWidthPix); + w.setMaximumWidth(mCellWidthPix); + w.adjustSize(); + + QSize ss = cellSize(w.size()) + QSize(0,5); + + std::cerr << "w =" << w.width() << " x " << w.height() << " new size = " << ss.width() << " x " << ss.height() << std::endl; + + return ss; +} + +QWidget *PostedPostDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const +{ + RsPostedPost post = index.data(Qt::UserRole).value() ; + + if(index.column() == RsPostedPostsModel::COLUMN_POSTS) + { + QWidget *w = new BoardPostDisplayWidget(post,parent); + w->setMinimumWidth(mCellWidthPix); + w->setMaximumWidth(mCellWidthPix); + w->adjustSize(); + return w; + } + else + return NULL; +} +void PostedPostDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + +/** Constructor */ +PostedListWidgetWithModel::PostedListWidgetWithModel(const RsGxsGroupId& postedId, QWidget *parent) : + GxsMessageFrameWidget(rsPosted, parent), + ui(new Ui::PostedListWidgetWithModel) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui->setupUi(this); + + ui->postsTree->setModel(mPostedPostsModel = new RsPostedPostsModel()); + ui->postsTree->setItemDelegate(mPostedPostsDelegate = new PostedPostDelegate()); + ui->postsTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // prevents bug on w10, since row size depends on widget width + ui->postsTree->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);// more beautiful if we scroll at pixel level + ui->postsTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + ui->postsTree->setPlaceholderText(tr("No files in this post, or no post selected")); + ui->postsTree->setSortingEnabled(true); + ui->postsTree->sortByColumn(0, Qt::AscendingOrder); + ui->postsTree->setAutoSelect(true); + + connect(ui->sortStrategy_CB,SIGNAL(currentIndexChanged(int)),this,SLOT(updateSorting(int))); + connect(ui->nextButton,SIGNAL(clicked()),this,SLOT(next10Posts())); + connect(ui->prevButton,SIGNAL(clicked()),this,SLOT(prev10Posts())); + + //connect(ui->channelPostFiles_TV->header(),SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(sortColumnPostFiles(int,Qt::SortOrder))); + //connect(ui->channelFiles_TV->header(),SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(sortColumnFiles(int,Qt::SortOrder))); + + connect(ui->postsTree,SIGNAL(customContextMenuRequested(const QPoint&)),this,SLOT(postContextMenu(const QPoint&))); + + connect(mPostedPostsModel,SIGNAL(boardPostsLoaded()),this,SLOT(postPostLoad())); + + QFontMetricsF fm(font()); + + /* Setup UI helper */ + + /* Connect signals */ + connect(ui->submitPostButton, SIGNAL(clicked()), this, SLOT(createMsg())); + connect(ui->subscribeToolButton, SIGNAL(subscribe(bool)), this, SLOT(subscribeGroup(bool))); + connect(ui->filter_LE, SIGNAL(textChanged(QString)), this, SLOT(filterItems(QString))); + connect(NotifyQt::getInstance(), SIGNAL(settingsChanged()),this, SLOT(settingsChanged())); + + /* add filter actions */ + ui->postsTree->setPlaceholderText(tr("Thumbnails")); + //ui->postsTree->setMinimumWidth(COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font()).height()+1); + + connect(ui->postsTree,SIGNAL(sizeChanged(QSize)),this,SLOT(handlePostsTreeSizeChange(QSize))); + + /* load settings */ + processSettings(true); + + /* Initialize subscribe button */ + QIcon icon; + icon.addPixmap(QPixmap(":/images/redled.png"), QIcon::Normal, QIcon::On); + icon.addPixmap(QPixmap(":/images/start.png"), QIcon::Normal, QIcon::Off); + + // ui->commentsDialog->setTokenService(rsPosted->getTokenService(),rsPosted); + + /* Initialize GUI */ + // setAutoDownload(false); + + settingsChanged(); + setGroupId(postedId); + + mPostedPostsModel->updateBoard(postedId); + + mEventHandlerId = 0; + // Needs to be asynced because this function is called by another thread! + rsEvents->registerEventsHandler( [this](std::shared_ptr event) + { + RsQThreadUtils::postToObject([=](){ handleEvent_main_thread(event); }, this ); + }, mEventHandlerId, RsEventType::GXS_POSTED ); +} + +void PostedListWidgetWithModel::updateSorting(int s) +{ + switch(s) + { + default: + case 0: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_HOT_SCORE); break; + case 1: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_NEW_SCORE); break; + case 2: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_TOP_SCORE); break; + } +} + +void PostedListWidgetWithModel::handlePostsTreeSizeChange(QSize size) +{ + mPostedPostsDelegate->setCellWidth(size.width()); +} + +void PostedListWidgetWithModel::filterItems(QString text) +{ + QStringList lst = text.split(" ",QString::SkipEmptyParts) ; + + uint32_t count; + mPostedPostsModel->setFilter(lst,count) ; + + ui->showLabel->setText(QString::number(mPostedPostsModel->displayedStartPostIndex()+1)+" - "+QString::number(std::min(mPostedPostsModel->filteredPostsCount(),mPostedPostsModel->displayedStartPostIndex()+10+1))); +} + +void PostedListWidgetWithModel::next10Posts() +{ + if(mPostedPostsModel->displayedStartPostIndex() + 10 < mPostedPostsModel->filteredPostsCount()) + { + mPostedPostsModel->setPostsInterval(10+mPostedPostsModel->displayedStartPostIndex(),10); + ui->showLabel->setText(QString::number(mPostedPostsModel->displayedStartPostIndex()+1)+" - "+QString::number(std::min(mPostedPostsModel->filteredPostsCount(),mPostedPostsModel->displayedStartPostIndex()+10+1))); + } +} + +void PostedListWidgetWithModel::prev10Posts() +{ + if((int)mPostedPostsModel->displayedStartPostIndex() - 10 >= 0) + { + mPostedPostsModel->setPostsInterval(mPostedPostsModel->displayedStartPostIndex()-10,10); + ui->showLabel->setText(QString::number(mPostedPostsModel->displayedStartPostIndex()+1)+" - "+QString::number(std::min(mPostedPostsModel->filteredPostsCount(),mPostedPostsModel->displayedStartPostIndex()+10+1))); + } +} + +void PostedListWidgetWithModel::postContextMenu(const QPoint&) +{ + QMenu menu(this); + + menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); + + if(IS_GROUP_PUBLISHER(mGroup.mMeta.mSubscribeFlags)) + menu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/edit_16.png"), tr("Edit"), this, SLOT(editPost())); + + menu.exec(QCursor::pos()); +} + +void PostedListWidgetWithModel::copyMessageLink() +{ + try + { + if (groupId().isNull()) + throw std::runtime_error("No channel currently selected!"); + + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + + if(!index.isValid()) + throw std::runtime_error("No post under mouse!"); + + RsPostedPost post = index.data(Qt::UserRole).value() ; + + if(post.mMeta.mMsgId.isNull()) + throw std::runtime_error("Post has empty MsgId!"); + + RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_POSTED, groupId(), post.mMeta.mMsgId, QString::fromUtf8(post.mMeta.mMsgName.c_str())); + + if (!link.valid()) + throw std::runtime_error("Link is not valid"); + + QList urls; + urls.push_back(link); + RSLinkClipboard::copyLinks(urls); + } + catch(std::exception& e) + { + QMessageBox::critical(NULL,tr("Link creation error"),tr("Link could not be created: ")+e.what()); + } +} + +#ifdef TODO +void PostedListWidgetWithModel::editPost() +{ + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + RsPostedPost post = index.data(Qt::UserRole).value() ; + + CreatePostedMsg *msgDialog = new CreatePostedMsg(post.mMeta.mGroupId,post.mMeta.mMsgId); + msgDialog->show(); +} +#endif + +void PostedListWidgetWithModel::handleEvent_main_thread(std::shared_ptr event) +{ + const RsGxsPostedEvent *e = dynamic_cast(event.get()); + + if(!e) + return; + + switch(e->mPostedEventCode) + { + case RsPostedEventCode::NEW_POSTED_GROUP: // [[fallthrough]]; + case RsPostedEventCode::UPDATED_POSTED_GROUP: // [[fallthrough]]; + case RsPostedEventCode::NEW_MESSAGE: // [[fallthrough]]; + case RsPostedEventCode::UPDATED_MESSAGE: + { + if(e->mPostedGroupId == groupId()) + updateDisplay(true); + } + + default: + break; + } +} + +#ifdef TO_REMOVE +void PostedListWidgetWithModel::showPostDetails() +{ + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + RsPostedPost post = index.data(Qt::UserRole).value() ; + + QTextDocument doc; + doc.setHtml(post.mMsg.c_str()); + + if(post.mMeta.mPublishTs == 0) + { + ui->postDetails_TE->clear(); + ui->postLogo_LB->hide(); + ui->postName_LB->hide(); + ui->postTime_LB->hide(); + mChannelPostFilesModel->clear(); + + return; + } + + ui->postLogo_LB->show(); + ui->postName_LB->show(); + ui->postTime_LB->show(); + + if(index.row()==0 && index.column()==0) + std::cerr << "here" << std::endl; + + std::cerr << "showPostDetails: setting mSelectedPost to current post Id " << post.mMeta.mMsgId << ". Previous value: " << mSelectedPost << std::endl; + mSelectedPost = post.mMeta.mMsgId; + + std::list files; + for(auto& file:post.mFiles) + files.push_back(ChannelPostFileInfo(file,post.mMeta.mPublishTs)); + + mChannelPostFilesModel->setFiles(files); + + auto all_msgs_versions(post.mOlderVersions); + all_msgs_versions.insert(post.mMeta.mMsgId); + + ui->commentsDialog->commentLoad(post.mMeta.mGroupId, all_msgs_versions, post.mMeta.mMsgId); + + std::cerr << "Showing details about selected index : "<< index.row() << "," << index.column() << std::endl; + + ui->postDetails_TE->setText(RsHtml().formatText(NULL, QString::fromUtf8(post.mMsg.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS)); + + QPixmap postImage; + + if (post.mThumbnail.mData != NULL) + GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, postImage,GxsIdDetails::ORIGINAL); + else + postImage = FilesDefs::getPixmapFromQtResourcePath(ChannelPostThumbnailView::CHAN_DEFAULT_IMAGE); + + int W = QFontMetricsF(font()).height() * 8; + + // Using fixed width so that the post will not displace the text when we browse. + + ui->postLogo_LB->setPixmap(postImage); + ui->postLogo_LB->setFixedSize(W,postImage.height()/(float)postImage.width()*W); + + ui->postName_LB->setText(QString::fromUtf8(post.mMeta.mMsgName.c_str())); + ui->postName_LB->setFixedWidth(W); + ui->postTime_LB->setText(QDateTime::fromMSecsSinceEpoch(post.mMeta.mPublishTs*1000).toString("MM/dd/yyyy, hh:mm")); + ui->postTime_LB->setFixedWidth(W); + + //ui->channelPostFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_FILE); + //ui->channelPostFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE); + ui->channelPostFiles_TV->setAutoSelect(true); + + // Now also set the post as read + + if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus)) + { + RsGxsGrpMsgIdPair postId; + postId.second = post.mMeta.mMsgId; + postId.first = post.mMeta.mGroupId; + + RsThread::async([postId]() { rsGxsChannels->markRead(postId, true) ; } ); + } +} +#endif + +void PostedListWidgetWithModel::updateGroupData() +{ + if(groupId().isNull()) + return; + + RsThread::async([this]() + { + std::vector groups; + + if(!rsPosted->getBoardsInfo(std::list{ groupId() }, groups)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to get boards group value for group: " << groupId() << std::endl; + return; + } + + if(groups.size() != 1) + { + RsErr() << __PRETTY_FUNCTION__ << " cannot retrieve posted group data for group ID " << groupId() << ": ERROR." << std::endl; + return; + } + + RsQThreadUtils::postToObject( [this,groups]() + { + mGroup = groups[0]; + mPostedPostsModel->updateBoard(groupId()); + + insertBoardDetails(mGroup); + + emit groupDataLoaded(); + emit groupChanged(this); // signals the parent widget to e.g. update the group tab name + } ); + }); +} + +void PostedListWidgetWithModel::postPostLoad() +{ + std::cerr << "Post channel load..." << std::endl; + + if(!mSelectedPost.isNull()) + { + QModelIndex index = mPostedPostsModel->getIndexOfMessage(mSelectedPost); + + std::cerr << "Setting current index to " << index.row() << ","<< index.column() << " for current post " + << mSelectedPost.toStdString() << std::endl; + + ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect); + ui->postsTree->scrollTo(index);//May change if model reloaded + ui->postsTree->setFocus(); + } + else + std::cerr << "No pre-selected channel post." << std::endl; + + whileBlocking(ui->showLabel)->setText(QString::number(mPostedPostsModel->displayedStartPostIndex()+1)+" - "+QString::number(std::min(mPostedPostsModel->filteredPostsCount(),mPostedPostsModel->displayedStartPostIndex()+10+1))); + whileBlocking(ui->filter_LE)->setText(QString()); +} + +void PostedListWidgetWithModel::updateDisplay(bool complete) +{ +#ifdef DEBUG_CHANNEL + std::cerr << "udateDisplay: groupId()=" << groupId()<< std::endl; +#endif + if(groupId().isNull()) + { +#ifdef DEBUG_CHANNEL + std::cerr << " group_id=0. Return!"<< std::endl; +#endif + return; + } + + if(mGroup.mMeta.mGroupId.isNull() && !groupId().isNull()) + { +#ifdef DEBUG_FORUMS + std::cerr << " inconsistent group data. Reloading!"<< std::endl; +#endif + complete = true; + } + if(complete) // need to update the group data, reload the messages etc. + { + updateGroupData(); + + return; + } +} +PostedListWidgetWithModel::~PostedListWidgetWithModel() +{ + rsEvents->unregisterEventsHandler(mEventHandlerId); + // save settings + processSettings(false); + + delete ui; +} + +void PostedListWidgetWithModel::processSettings(bool load) +{ + Settings->beginGroup(QString("ChannelPostsWidget")); + + if (load) + { + } + else + { + } + + Settings->endGroup(); +} + +void PostedListWidgetWithModel::settingsChanged() +{ +} + +QString PostedListWidgetWithModel::groupName(bool) +{ + return QString::fromUtf8(mGroup.mMeta.mGroupName.c_str()); +} + +void PostedListWidgetWithModel::groupNameChanged(const QString &name) +{ +} + +QIcon PostedListWidgetWithModel::groupIcon() +{ + /* CHANNEL IMAGE */ + QPixmap postedImage; + if (mGroup.mGroupImage.mData != NULL) + GxsIdDetails::loadPixmapFromData(mGroup.mGroupImage.mData, mGroup.mGroupImage.mSize, postedImage,GxsIdDetails::ORIGINAL); + else + postedImage = FilesDefs::getPixmapFromQtResourcePath(BoardPostDisplayWidget::DEFAULT_BOARD_IMAGE); + + return QIcon(postedImage); +} + +void PostedListWidgetWithModel::setAllMessagesReadDo(bool read, uint32_t &token) +{ + std::cerr << __PRETTY_FUNCTION__ << ": not implemented" << std::endl; +} + +void PostedListWidgetWithModel::openComments(uint32_t /*type*/, const RsGxsGroupId &groupId, const QVector& msg_versions,const RsGxsMessageId &msgId, const QString &title) +{ + emit loadComment(groupId, msg_versions,msgId, title); +} + +void PostedListWidgetWithModel::createMsg() +{ + if (groupId().isNull()) { + return; + } + + if (!IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) { + return; + } + + PostedCreatePostDialog *msgDialog = new PostedCreatePostDialog(rsPosted,groupId()); + msgDialog->show(); + + /* window will destroy itself! */ +} + +void PostedListWidgetWithModel::insertBoardDetails(const RsPostedGroup& group) +{ + // save selection if needed + + /* IMAGE */ + QPixmap chanImage; + if (group.mGroupImage.mData != NULL) { + GxsIdDetails::loadPixmapFromData(group.mGroupImage.mData, group.mGroupImage.mSize, chanImage,GxsIdDetails::ORIGINAL); + } else { + chanImage = QPixmap(BoardPostDisplayWidget::DEFAULT_BOARD_IMAGE); + } + if(group.mMeta.mGroupName.empty()) + ui->namelabel->setText(tr("[No name]")); + else + ui->namelabel->setText(QString::fromUtf8(group.mMeta.mGroupName.c_str())); + + ui->logoLabel->setPixmap(chanImage); + ui->logoLabel->setFixedSize(QSize(ui->logoLabel->height()*chanImage.width()/(float)chanImage.height(),ui->logoLabel->height())); // make the logo have the same aspect ratio than the original image + + ui->submitPostButton->setEnabled(bool(group.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_PUBLISH)); + + ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags)); + ui->subscribeToolButton->setEnabled(true); + + RetroShareLink link; + + if (IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags)) + ui->subscribeToolButton->setText(tr("Subscribed") + " " + QString::number(group.mMeta.mPop) ); + else + ui->subscribeToolButton->setText(tr("Subscribe")); + + ui->infoPosts->setText(QString::number(group.mMeta.mVisibleMsgCount)); + + if(group.mMeta.mLastPost==0) + ui->infoLastPost->setText(tr("Never")); + else + ui->infoLastPost->setText(DateTime::formatLongDateTime(group.mMeta.mLastPost)); + QString formatDescription = QString::fromUtf8(group.mDescription.c_str()); + + unsigned int formatFlag = RSHTML_FORMATTEXT_EMBED_LINKS; + + // embed smileys ? + if (Settings->valueFromGroup(QString("ChannelPostsWidget"), QString::fromUtf8("Emoteicons_ChannelDecription"), true).toBool()) + formatFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS; + + formatDescription = RsHtml().formatText(NULL, formatDescription, formatFlag); + + ui->infoDescription->setText(formatDescription); + ui->infoAdministrator->setId(group.mMeta.mAuthorId) ; + + link = RetroShareLink::createMessage(group.mMeta.mAuthorId, ""); + ui->infoAdministrator->setText(link.toHtml()); + + ui->createdinfolabel->setText(DateTime::formatLongDateTime(group.mMeta.mPublishTs)); + + QString distrib_string ( "[unknown]" ); + + switch(group.mMeta.mCircleType) + { + case GXS_CIRCLE_TYPE_PUBLIC: distrib_string = tr("Public") ; + break ; + case GXS_CIRCLE_TYPE_EXTERNAL: + { + RsGxsCircleDetails det ; + + // !! What we need here is some sort of CircleLabel, which loads the circle and updates the label when done. + + if(rsGxsCircles->getCircleDetails(group.mMeta.mCircleId,det)) + distrib_string = tr("Restricted to members of circle \"")+QString::fromUtf8(det.mCircleName.c_str()) +"\""; + else + distrib_string = tr("Restricted to members of circle ")+QString::fromStdString(group.mMeta.mCircleId.toStdString()) ; + } + break ; + case GXS_CIRCLE_TYPE_YOUR_EYES_ONLY: distrib_string = tr("Your eyes only"); + break ; + case GXS_CIRCLE_TYPE_LOCAL: distrib_string = tr("You and your friend nodes"); + break ; + default: + std::cerr << "(EE) badly initialised group distribution ID = " << group.mMeta.mCircleType << std::endl; + } + + ui->infoDistribution->setText(distrib_string); +#ifdef TODO + ui->infoWidget->show(); + ui->feedWidget->hide(); + ui->fileWidget->hide(); + + //ui->feedToolButton->setEnabled(false); + //ui->fileToolButton->setEnabled(false); +#endif +} + +#ifdef TODO +int PostedListWidgetWithModel::viewMode() +{ + if (ui->feedToolButton->isChecked()) { + return VIEW_MODE_FEEDS; + } else if (ui->fileToolButton->isChecked()) { + return VIEW_MODE_FILES; + } + + /* Default */ + return VIEW_MODE_FEEDS; +} +#endif + +void PostedListWidgetWithModel::setViewMode(int viewMode) +{ +#ifdef TODO + switch (viewMode) { + case VIEW_MODE_FEEDS: + ui->feedWidget->show(); + ui->fileWidget->hide(); + + ui->feedToolButton->setChecked(true); + ui->fileToolButton->setChecked(false); + + break; + case VIEW_MODE_FILES: + ui->feedWidget->hide(); + ui->fileWidget->show(); + + ui->feedToolButton->setChecked(false); + ui->fileToolButton->setChecked(true); + + break; + default: + setViewMode(VIEW_MODE_FEEDS); + return; + } +#endif +} + +#ifdef TODO +/*static*/ bool PostedListWidgetWithModel::filterItem(FeedItem *feedItem, const QString &text, int filter) +{ + GxsChannelPostItem *item = dynamic_cast(feedItem); + if (!item) { + return true; + } + + bool bVisible = text.isEmpty(); + + if (!bVisible) + { + switch(filter) + { + case FILTER_TITLE: + bVisible = item->getTitleLabel().contains(text,Qt::CaseInsensitive); + break; + case FILTER_MSG: + bVisible = item->getMsgLabel().contains(text,Qt::CaseInsensitive); + break; + case FILTER_FILE_NAME: + { + std::list fileItems = item->getFileItems(); + std::list::iterator lit; + for(lit = fileItems.begin(); lit != fileItems.end(); ++lit) + { + SubFileItem *fi = *lit; + QString fileName = QString::fromUtf8(fi->FileName().c_str()); + bVisible = (bVisible || fileName.contains(text,Qt::CaseInsensitive)); + } + break; + } + default: + bVisible = true; + break; + } + } + + return bVisible; +} + +void PostedListWidget::createPostItemFromMetaData(const RsGxsMsgMetaData& meta,bool related) +{ + GxsChannelPostItem *item = NULL; + RsGxsChannelPost post; + + if(!meta.mOrigMsgId.isNull()) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mOrigMsgId)) ; + item = dynamic_cast(feedItem); + + if(item) + { + post = feedItem->post(); + ui->feedWidget->removeFeedItem(item) ; + + post.mOlderVersions.insert(post.mMeta.mMsgId); + + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, post, true, false,post.mOlderVersions); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(post.mMeta.mPublishTs)); + + return ; + } + } + + if (related) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mMsgId)) ; + item = dynamic_cast(feedItem); + } + if (item) + { + item->setPost(post); + ui->feedWidget->setSort(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + else + { + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, meta.mGroupId,meta.mMsgId, true, true); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(post.mMeta.mPublishTs)); + } +#ifdef TODO + ui->fileWidget->addFiles(post, related); +#endif +} + +void PostedListWidget::createPostItem(const RsGxsChannelPost& post, bool related) +{ + GxsChannelPostItem *item = NULL; + + const RsMsgMetaData& meta(post.mMeta); + + if(!meta.mOrigMsgId.isNull()) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mOrigMsgId)) ; + item = dynamic_cast(feedItem); + + if(item) + { + std::set older_versions(item->olderVersions()); // we make a copy because the item will be deleted + ui->feedWidget->removeFeedItem(item) ; + + older_versions.insert(meta.mMsgId); + + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, mGroup.mMeta,meta.mMsgId, true, false,older_versions); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + + return ; + } + } + + if (related) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mMsgId)) ; + item = dynamic_cast(feedItem); + } + if (item) + { + item->setPost(post); + ui->feedWidget->setSort(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + else + { + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, mGroup.mMeta,meta.mMsgId, true, true); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + + ui->fileWidget->addFiles(post, related); +} + +void PostedListWidget::fillThreadCreatePost(const QVariant &post, bool related, int current, int count) +{ + /* show fill progress */ + if (count) { + ui->progressBar->setValue(current * ui->progressBar->maximum() / count); + } + + if (!post.canConvert()) { + return; + } + + createPostItem(post.value(), related); +} +#endif + +void PostedListWidgetWithModel::blank() +{ +#ifdef TODO + ui->postButton->setEnabled(false); + ui->subscribeToolButton->setEnabled(false); + + ui->channelName_LB->setText(tr("No Channel Selected")); + ui->logoLabel->setPixmap(FilesDefs::getPixmapFromQtResourcePath(":/icons/png/channels.png")); + ui->infoPosts->setText(""); + ui->infoLastPost->setText(""); + ui->infoAdministrator->setText(""); + ui->infoDistribution->setText(""); + ui->infoCreated->setText(""); + ui->infoDescription->setText(""); + + mChannelPostsModel->clear(); + mChannelPostFilesModel->clear(); + ui->postDetails_TE->clear(); + ui->postLogo_LB->hide(); + ui->postName_LB->hide(); + ui->postTime_LB->hide(); +#endif + groupNameChanged(QString()); +} + +bool PostedListWidgetWithModel::navigate(const RsGxsMessageId& msgId) +{ + QModelIndex index = mPostedPostsModel->getIndexOfMessage(msgId); + + if(!index.isValid()) + { + std::cerr << "(EE) Cannot navigate to msg " << msgId << " in board " << mGroup.mMeta.mGroupId << ": index unknown. Setting mNavigatePendingMsgId." << std::endl; + + mSelectedPost = msgId; // not found. That means the forum may not be loaded yet. So we keep that post in mind, for after loading. + return true; // we have to return true here, otherwise the caller will intepret the async loading as an error. + } + + ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect); + ui->postsTree->scrollTo(index);//May change if model reloaded + ui->postsTree->setFocus(); + + return true; +} + +void PostedListWidgetWithModel::subscribeGroup(bool subscribe) +{ + RsGxsGroupId grpId(groupId()); + if (grpId.isNull()) return; + + RsThread::async([=]() + { + uint32_t token; + rsPosted->subscribeToGroup(token,grpId, subscribe); + } ); +} + +#ifdef TODO +class PostedPostsReadData +{ +public: + PostedPostsReadData(bool read) + { + mRead = read; + mLastToken = 0; + } + +public: + bool mRead; + uint32_t mLastToken; +}; + +static void setAllMessagesReadCallback(FeedItem *feedItem, void *data) +{ + GxsChannelPostItem *channelPostItem = dynamic_cast(feedItem); + if (!channelPostItem) { + return; + } + + GxsChannelPostsReadData *readData = (GxsChannelPostsReadData*) data; + bool isRead = !channelPostItem->isUnread() ; + + if(channelPostItem->isLoaded() && (isRead == readData->mRead)) + return ; + + RsGxsGrpMsgIdPair msgPair = std::make_pair(channelPostItem->groupId(), channelPostItem->messageId()); + rsGxsChannels->setMessageReadStatus(readData->mLastToken, msgPair, readData->mRead); +} + +void PostedListWidgetWithModel::setAllMessagesReadDo(bool read, uint32_t &token) +{ + if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) { + return; + } + + GxsChannelPostsReadData data(read); + //ui->feedWidget->withAll(setAllMessagesReadCallback, &data); + + token = data.mLastToken; +} + +#endif