mirror of
https://github.com/RetroShare/RetroShare.git
synced 2025-12-16 09:03:51 -05:00
1004 lines
35 KiB
C++
1004 lines
35 KiB
C++
/*******************************************************************************
|
|
* retroshare-gui/src/gui/gxschannels/PostedListWidgetWithModel.cpp *
|
|
* *
|
|
* Copyright 2013 by Robert Fernie <retroshare.project@gmail.com> *
|
|
* *
|
|
* 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 <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
*******************************************************************************/
|
|
|
|
#include <QDateTime>
|
|
#include <QMenu>
|
|
#include <QSignalMapper>
|
|
#include <QPainter>
|
|
#include <QClipboard>
|
|
#include <QMessageBox>
|
|
|
|
#include "retroshare/rsgxscircles.h"
|
|
|
|
#include "ui_PostedListWidgetWithModel.h"
|
|
#include "gui/feeds/GxsChannelPostItem.h"
|
|
#include "gui/gxs/GxsIdDetails.h"
|
|
#include "gui/gxs/GxsGroupFrameDialog.h"
|
|
#include "gui/gxs/GxsCommentDialog.h"
|
|
#include "util/misc.h"
|
|
#include "gui/Posted/PostedCreatePostDialog.h"
|
|
#include "gui/common/UIStateHelper.h"
|
|
#include "gui/common/RSTabWidget.h"
|
|
#include "gui/settings/rsharesettings.h"
|
|
#include "gui/RsGUIEventManager.h"
|
|
#include "gui/feeds/SubFileItem.h"
|
|
#include "gui/Identity/IdDialog.h"
|
|
#include "gui/RetroShareLink.h"
|
|
#include "util/HandleRichText.h"
|
|
#include "util/DateTime.h"
|
|
#include "util/qtthreadsutils.h"
|
|
#include "gui/common/FilesDefs.h"
|
|
#include "util/RsQtVersion.h"
|
|
|
|
#include "gui/MainWindow.h"
|
|
|
|
#include "PostedListWidgetWithModel.h"
|
|
#include "PostedPostsModel.h"
|
|
#include "BoardPostDisplayWidget.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#define ROLE_PUBLISH FEED_TREEWIDGET_SORTROLE
|
|
|
|
// number of posts to show at once.
|
|
#define POSTS_CHUNK_SIZE 25
|
|
|
|
//#define DEBUG_POSTED
|
|
|
|
static const int POSTED_TABS_POSTS = 1;
|
|
|
|
/* 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"
|
|
#define IMAGE_AUTHOR ":/images/user/personal64.png"
|
|
#define IMAGE_COPYHTTP ":/images/emblem-web.png"
|
|
|
|
Q_DECLARE_METATYPE(RsPostedPost);
|
|
|
|
// Delegate used to paint into the table of thumbnails
|
|
|
|
//===================================================================================================================================
|
|
//== PostedPostDelegate ==
|
|
//===================================================================================================================================
|
|
|
|
std::ostream& operator<<(std::ostream& o,const QSize& s) { return o << s.width() << " x " << s.height() ; }
|
|
|
|
void PostedPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
|
|
{
|
|
#ifdef DEBUG_POSTED
|
|
if(option.state & QStyle::State_Selected) RS_DBG("Selected");
|
|
#endif
|
|
|
|
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<RsPostedPost>() ;
|
|
|
|
painter->save();
|
|
|
|
painter->fillRect( option.rect, option.palette.window());
|
|
painter->restore();
|
|
|
|
QPixmap pixmap(option.rect.size());
|
|
pixmap.fill(option.palette.alternateBase().color()); // use base() instead to have all widgets the same background
|
|
|
|
if(mDisplayMode == BoardPostDisplayWidget_compact::DISPLAY_MODE_COMPACT)
|
|
{
|
|
BoardPostDisplayWidget_compact w(post,displayFlags(post.mMeta.mMsgId),nullptr);
|
|
|
|
w.setFixedSize(option.rect.size());
|
|
w.updateGeometry();
|
|
w.adjustSize();
|
|
|
|
w.render(&pixmap,QPoint(0,0),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background
|
|
}
|
|
else
|
|
{
|
|
BoardPostDisplayWidget_card w(post,displayFlags(post.mMeta.mMsgId),nullptr);
|
|
|
|
w.setFixedSize(option.rect.size());
|
|
w.updateGeometry();
|
|
w.adjustSize();
|
|
|
|
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();
|
|
painter->drawPixmap(option.rect.topLeft(), pixmap /*,.scaled(option.rect.width(),option.rect.width()*w.height()/(float)w.width(),Qt::KeepAspectRatio,Qt::SmoothTransformation)*/);
|
|
#ifdef DEBUG_POSTED
|
|
painter->drawText(option.rect.bottomLeft(),QString::number(time(nullptr)));
|
|
RS_DBG("DisplayMode=", mDisplayMode == BoardPostDisplayWidget_compact::DISPLAY_MODE_COMPACT? "Compact":"Card", " Title:", post.mMeta.mMsgName.c_str());
|
|
#endif
|
|
painter->restore();
|
|
}
|
|
|
|
QSize PostedPostDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex& index) const
|
|
{
|
|
// This is the only place where we actually set the size of cells
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
if(mDisplayMode == BoardPostDisplayWidget_compact::DISPLAY_MODE_COMPACT)
|
|
{
|
|
BoardPostDisplayWidget_compact w(post,displayFlags(post.mMeta.mMsgId),nullptr);
|
|
w.adjustSize();
|
|
return w.size();
|
|
}
|
|
else
|
|
{
|
|
BoardPostDisplayWidget_card w(post,displayFlags(post.mMeta.mMsgId),nullptr);
|
|
w.adjustSize();
|
|
return w.size();
|
|
}
|
|
}
|
|
void PostedPostDelegate::expandItem(RsGxsMessageId msgId,bool expanded)
|
|
{
|
|
std::cerr << __PRETTY_FUNCTION__ << ": received expandItem signal. b=" << expanded << std::endl;
|
|
if(expanded)
|
|
mExpandedItems.insert(msgId);
|
|
else
|
|
mExpandedItems.erase(msgId);
|
|
|
|
mPostListWidget->redraw();
|
|
}
|
|
|
|
uint8_t PostedPostDelegate::displayFlags(const RsGxsMessageId &id) const
|
|
{
|
|
uint8_t flags=0;
|
|
|
|
if(mExpandedItems.find(id) != mExpandedItems.end())
|
|
flags |= BoardPostDisplayWidget_compact::SHOW_NOTES;
|
|
|
|
if(mShowCommentItems.find(id) != mShowCommentItems.end())
|
|
flags |= BoardPostDisplayWidget_compact::SHOW_COMMENTS;
|
|
|
|
return flags;
|
|
}
|
|
|
|
QWidget *PostedPostDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const
|
|
{
|
|
|
|
if (!index.isValid())
|
|
return nullptr;
|
|
|
|
if(index.column() != RsPostedPostsModel::COLUMN_POSTS)
|
|
return nullptr;
|
|
|
|
QWidget *w ;
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
#ifdef DEBUG_POSTED
|
|
RS_DBG("Title:", post.mMeta.mMsgName.c_str());
|
|
#endif
|
|
|
|
if(mDisplayMode==BoardPostDisplayWidget_compact::DISPLAY_MODE_COMPACT)
|
|
w = new BoardPostDisplayWidget_compact(post,displayFlags(post.mMeta.mMsgId),parent);
|
|
else
|
|
w = new BoardPostDisplayWidget_card(post,displayFlags(post.mMeta.mMsgId),parent);
|
|
|
|
QObject::connect(w,SIGNAL(vote(RsGxsGrpMsgIdPair,bool)),mPostListWidget,SLOT(voteMsg(RsGxsGrpMsgIdPair,bool)));
|
|
QObject::connect(w,SIGNAL(expand(RsGxsMessageId,bool)),this,SLOT(expandItem(RsGxsMessageId,bool)));
|
|
QObject::connect(w,SIGNAL(commentsRequested(RsGxsMessageId,bool)),mPostListWidget,SLOT(openComments(RsGxsMessageId)));
|
|
QObject::connect(w,SIGNAL(changeReadStatusRequested(RsGxsMessageId,bool)),mPostListWidget,SLOT(changeReadStatus(RsGxsMessageId,bool)));
|
|
|
|
// All other interactions with the widget should cause the msg to be set as read.
|
|
QObject::connect(w,SIGNAL(thumbnailOpenned()),mPostListWidget,SLOT(markCurrentPostAsRead()));
|
|
QObject::connect(w,SIGNAL(vote(RsGxsGrpMsgIdPair,bool)),mPostListWidget,SLOT(markCurrentPostAsRead()));
|
|
QObject::connect(w,SIGNAL(expand(RsGxsMessageId,bool)),this,SLOT(markCurrentPostAsRead()));
|
|
QObject::connect(w,SIGNAL(commentsRequested(RsGxsMessageId,bool)),mPostListWidget,SLOT(markCurrentPostAsRead()));
|
|
QObject::connect(w,SIGNAL(shareButtonClicked()),mPostListWidget,SLOT(markCurrentPostAsRead()));
|
|
|
|
w->setGeometry(option.rect);
|
|
w->adjustSize();
|
|
w->updateGeometry();
|
|
w->adjustSize();
|
|
|
|
return w;
|
|
}
|
|
|
|
void PostedPostDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
|
|
{
|
|
editor->setGeometry(option.rect);
|
|
}
|
|
|
|
//===================================================================================================================================
|
|
//== PostedListWidgetWithModel ==
|
|
//===================================================================================================================================
|
|
|
|
/** 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(POSTS_CHUNK_SIZE));
|
|
ui->postsTree->setItemDelegate(mPostedPostsDelegate = new PostedPostDelegate(this));
|
|
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);
|
|
|
|
ui->idChooser->setFlags(IDCHOOSER_ID_REQUIRED);
|
|
|
|
connect(ui->tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
|
|
ui->tabWidget->hideCloseButton(0);
|
|
ui->tabWidget->hideCloseButton(1);
|
|
|
|
connect(ui->sortStrategy_CB,SIGNAL(currentIndexChanged(int)),this,SLOT(updateSorting(int)));
|
|
connect(ui->nextButton,SIGNAL(clicked()),this,SLOT(nextPosts()));
|
|
connect(ui->prevButton,SIGNAL(clicked()),this,SLOT(prevPosts()));
|
|
|
|
connect(ui->viewModeButton,SIGNAL(clicked()),this,SLOT(switchDisplayMode()));
|
|
|
|
connect(mPostedPostsModel,SIGNAL(boardPostsLoaded()),this,SLOT(postPostLoad()));
|
|
|
|
/* 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(RsGUIEventManager::getInstance(), SIGNAL(settingsChanged()),this, SLOT(settingsChanged()));
|
|
|
|
/* add filter actions */
|
|
ui->postsTree->setPlaceholderText(tr("No posts available in this board"));
|
|
//ui->postsTree->setMinimumWidth(COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font()).height()+1);
|
|
|
|
connect(ui->postsTree,SIGNAL(sizeChanged(QSize)),this,SLOT(handlePostsTreeSizeChange(QSize)));
|
|
|
|
ui->postsTree->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
//QObject::connect(this,SIGNAL(copylinkClicked()),this,SLOT(copyMessageLink()));
|
|
connect(ui->postsTree,SIGNAL(customContextMenuRequested(const QPoint&)),this,SLOT(postContextMenu(const QPoint&)));
|
|
|
|
/* 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);
|
|
|
|
mPostedPostsDelegate->setDisplayMode(BoardPostDisplayWidget_compact::DISPLAY_MODE_CARD);
|
|
|
|
switchDisplayMode(); // makes everything consistent and chooses classic view as default
|
|
updateSorting(ui->sortStrategy_CB->currentIndex());
|
|
|
|
mEventHandlerId = 0;
|
|
// Needs to be asynced because this function is called by another thread!
|
|
rsEvents->registerEventsHandler( [this](std::shared_ptr<const RsEvent> event)
|
|
{
|
|
RsQThreadUtils::postToObject([=](){ handleEvent_main_thread(event); }, this );
|
|
}, mEventHandlerId, RsEventType::GXS_POSTED );
|
|
}
|
|
|
|
void PostedListWidgetWithModel::postContextMenu(const QPoint& point)
|
|
{
|
|
QMenu menu(this);
|
|
|
|
// 1 - check that we are clicking on a post
|
|
|
|
QModelIndex index = ui->postsTree->indexAt(point);
|
|
|
|
if(!index.isValid())
|
|
return;
|
|
|
|
// 2 - generate the menu for that post.
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink()))->setData(index);
|
|
|
|
QByteArray urlarray(post.mLink.c_str());
|
|
QUrl url = QUrl::fromEncoded(urlarray.trimmed());
|
|
|
|
std::cerr << "Using link: \"" << post.mLink << "\"" << std::endl;
|
|
|
|
if(url.scheme()=="http" || url.scheme()=="https")
|
|
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYHTTP), tr("Copy http Link"), this, SLOT(copyHttpLink()))->setData(index);
|
|
|
|
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_AUTHOR), tr("Show author in People tab"), this, SLOT(showAuthorInPeople()))->setData(index);
|
|
|
|
#ifdef TODO
|
|
// This feature is not implemented yet in libretroshare.
|
|
|
|
if(IS_GROUP_PUBLISHER(mGroup.mMeta.mSubscribeFlags))
|
|
menu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/edit_16.png"), tr("Edit"), this, SLOT(editPost()));
|
|
#endif
|
|
|
|
menu.exec(QCursor::pos());
|
|
}
|
|
|
|
void PostedListWidgetWithModel::switchDisplayMode()
|
|
{
|
|
if(mPostedPostsDelegate->getDisplayMode() == BoardPostDisplayWidget_compact::DISPLAY_MODE_CARD)
|
|
{
|
|
ui->viewModeButton->setIcon(FilesDefs::getIconFromQtResourcePath(":images/classic.png"));
|
|
ui->viewModeButton->setToolTip(tr("Click to switch to card view"));
|
|
|
|
mPostedPostsDelegate->setDisplayMode(BoardPostDisplayWidget_compact::DISPLAY_MODE_COMPACT);
|
|
}
|
|
else
|
|
{
|
|
ui->viewModeButton->setIcon(FilesDefs::getIconFromQtResourcePath(":images/card.png"));
|
|
ui->viewModeButton->setToolTip(tr("Click to switch to compact view"));
|
|
|
|
mPostedPostsDelegate->setDisplayMode(BoardPostDisplayWidget_compact::DISPLAY_MODE_CARD);
|
|
}
|
|
mPostedPostsModel->triggerRedraw();
|
|
}
|
|
|
|
void PostedListWidgetWithModel::updateSorting(int s)
|
|
{
|
|
switch(s)
|
|
{
|
|
default:
|
|
case 0: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_NEW_SCORE); break;
|
|
case 1: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_TOP_SCORE); break;
|
|
case 2: mPostedPostsModel->setSortingStrategy(RsPostedPostsModel::SORT_HOT_SCORE); break;
|
|
}
|
|
}
|
|
|
|
void PostedListWidgetWithModel::handlePostsTreeSizeChange(QSize size)
|
|
{
|
|
std::cerr << "resizing!"<< std::endl;
|
|
mPostedPostsDelegate->setCellWidth(size.width());
|
|
mPostedPostsModel->triggerRedraw();
|
|
}
|
|
|
|
void PostedListWidgetWithModel::updateShowLabel()
|
|
{
|
|
if (mPostedPostsModel->rowCount() > 0)
|
|
whileBlocking(ui->showLabel)->setText(QString::number(mPostedPostsModel->displayedStartPostIndex()+1)
|
|
+" - "+QString::number(mPostedPostsModel->displayedStartPostIndex()+mPostedPostsModel->rowCount()));
|
|
else
|
|
whileBlocking(ui->showLabel)->setText(tr("Empty"));
|
|
|
|
ui->nextButton->setEnabled(mPostedPostsModel->displayedStartPostIndex() + POSTS_CHUNK_SIZE < mPostedPostsModel->filteredPostsCount());
|
|
ui->prevButton->setEnabled(mPostedPostsModel->displayedStartPostIndex() > 0);
|
|
}
|
|
|
|
void PostedListWidgetWithModel::filterItems(QString text)
|
|
{
|
|
QStringList lst = text.split(" ",QtSkipEmptyParts) ;
|
|
|
|
uint32_t count;
|
|
mPostedPostsModel->setFilter(lst,count) ;
|
|
updateShowLabel();
|
|
}
|
|
|
|
void PostedListWidgetWithModel::nextPosts()
|
|
{
|
|
if(mPostedPostsModel->displayedStartPostIndex() + POSTS_CHUNK_SIZE < mPostedPostsModel->filteredPostsCount())
|
|
{
|
|
mPostedPostsModel->setPostsInterval(POSTS_CHUNK_SIZE+mPostedPostsModel->displayedStartPostIndex(),POSTS_CHUNK_SIZE);
|
|
updateShowLabel();
|
|
}
|
|
}
|
|
|
|
void PostedListWidgetWithModel::prevPosts()
|
|
{
|
|
if((int)mPostedPostsModel->displayedStartPostIndex() > 0)
|
|
{
|
|
mPostedPostsModel->setPostsInterval((int)mPostedPostsModel->displayedStartPostIndex()-POSTS_CHUNK_SIZE,POSTS_CHUNK_SIZE);
|
|
updateShowLabel();
|
|
}
|
|
}
|
|
|
|
void PostedListWidgetWithModel::showAuthorInPeople()
|
|
{
|
|
QModelIndex index = qobject_cast<QAction*>(QObject::sender())->data().toModelIndex();
|
|
|
|
if(!index.isValid())
|
|
throw std::runtime_error("No post under mouse!");
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
if(post.mMeta.mMsgId.isNull())
|
|
throw std::runtime_error("Post has empty MsgId!");
|
|
|
|
if(post.mMeta.mAuthorId.isNull())
|
|
{
|
|
std::cerr << "(EE) GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data...";
|
|
std::cerr << std::endl;
|
|
}
|
|
|
|
/* window will destroy itself! */
|
|
IdDialog *idDialog = dynamic_cast<IdDialog*>(MainWindow::getPage(MainWindow::People));
|
|
|
|
if (!idDialog)
|
|
return ;
|
|
|
|
MainWindow::showWindow(MainWindow::People);
|
|
idDialog->navigate(RsGxsId(post.mMeta.mAuthorId));
|
|
}
|
|
void PostedListWidgetWithModel::copyHttpLink()
|
|
{
|
|
try
|
|
{
|
|
if (groupId().isNull())
|
|
throw std::runtime_error("No channel currently selected!");
|
|
|
|
QModelIndex index = qobject_cast<QAction*>(QObject::sender())->data().toModelIndex();
|
|
|
|
if(!index.isValid())
|
|
throw std::runtime_error("No post under mouse!");
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
if(post.mMeta.mMsgId.isNull())
|
|
throw std::runtime_error("Post has empty MsgId!");
|
|
|
|
QApplication::clipboard()->setText(QString::fromStdString(post.mLink)) ;
|
|
QMessageBox::information(NULL,tr("information"),tr("The Retrohare link was copied to your clipboard.")) ;
|
|
}
|
|
catch(std::exception& e)
|
|
{
|
|
QMessageBox::critical(NULL,tr("Link creation error"),tr("Link could not be created: ")+e.what());
|
|
}
|
|
}
|
|
void PostedListWidgetWithModel::copyMessageLink()
|
|
{
|
|
try
|
|
{
|
|
if (groupId().isNull())
|
|
throw std::runtime_error("No channel currently selected!");
|
|
|
|
QModelIndex index = qobject_cast<QAction*>(QObject::sender())->data().toModelIndex();
|
|
|
|
if(!index.isValid())
|
|
throw std::runtime_error("No post under mouse!");
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
|
|
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<RetroShareLink> urls;
|
|
urls.push_back(link);
|
|
RSLinkClipboard::copyLinks(urls);
|
|
QMessageBox::information(NULL,tr("information"),tr("The Retrohare link was copied to your clipboard.")) ;
|
|
}
|
|
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<RsPostedPost>() ;
|
|
|
|
CreatePostedMsg *msgDialog = new CreatePostedMsg(post.mMeta.mGroupId,post.mMeta.mMsgId);
|
|
msgDialog->show();
|
|
}
|
|
#endif
|
|
|
|
void PostedListWidgetWithModel::handleEvent_main_thread(std::shared_ptr<const RsEvent> event)
|
|
{
|
|
const RsGxsPostedEvent *e = dynamic_cast<const RsGxsPostedEvent*>(event.get());
|
|
|
|
if(!e)
|
|
return;
|
|
|
|
switch(e->mPostedEventCode)
|
|
{
|
|
case RsPostedEventCode::NEW_COMMENT: [[fallthrough]];
|
|
case RsPostedEventCode::NEW_VOTE:
|
|
{
|
|
// special treatment here because the message might be a comment, so we need to refresh the comment tab if openned
|
|
|
|
for(int i=2;i<ui->tabWidget->count();++i)
|
|
{
|
|
auto *t = dynamic_cast<GxsCommentDialog*>(ui->tabWidget->widget(i));
|
|
|
|
if(t->groupId() == e->mPostedGroupId)
|
|
t->refresh();
|
|
}
|
|
}
|
|
[[clang::fallthrough]];
|
|
case RsPostedEventCode::NEW_MESSAGE: [[fallthrough]];
|
|
case RsPostedEventCode::NEW_POSTED_GROUP: [[fallthrough]];
|
|
case RsPostedEventCode::UPDATED_POSTED_GROUP: [[fallthrough]];
|
|
case RsPostedEventCode::UPDATED_MESSAGE: [[fallthrough]];
|
|
case RsPostedEventCode::BOARD_DELETED: [[fallthrough]];
|
|
case RsPostedEventCode::SYNC_PARAMETERS_UPDATED:
|
|
{
|
|
if(e->mPostedGroupId == groupId())
|
|
updateDisplay(true);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PostedListWidgetWithModel::updateGroupData()
|
|
{
|
|
if(groupId().isNull())
|
|
return;
|
|
|
|
RsThread::async([this]()
|
|
{
|
|
std::vector<RsPostedGroup> groups;
|
|
|
|
if(!rsPosted->getBoardsInfo(std::list<RsGxsGroupId>{ 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]()
|
|
{
|
|
bool group_changed = (groups[0].mMeta.mGroupId!=mGroup.mMeta.mGroupId);
|
|
|
|
mGroup = groups[0];
|
|
mPostedPostsModel->updateBoard(groupId());
|
|
|
|
insertBoardDetails(mGroup);
|
|
|
|
if(group_changed)
|
|
while(ui->tabWidget->widget(2) != nullptr)
|
|
tabCloseRequested(2);
|
|
|
|
emit groupDataLoaded();
|
|
emit groupChanged(this); // signals the parent widget to e.g. update the group tab name
|
|
} );
|
|
});
|
|
}
|
|
|
|
void PostedListWidgetWithModel::postPostLoad()
|
|
{
|
|
#ifdef DEBUG_POSTED
|
|
std::cerr << "Post channel load..." << std::endl;
|
|
#endif
|
|
whileBlocking(ui->filter_LE)->setText(QString()); //Clear it before navigate, as it will update it.
|
|
|
|
if (!mNavigatePendingMsgId.isNull())
|
|
navigate(mNavigatePendingMsgId);
|
|
#ifdef TO_REMOVE
|
|
else if( (mLastSelectedPosts.count(groupId()) > 0)
|
|
&& !mLastSelectedPosts[groupId()].isNull())
|
|
{
|
|
QModelIndex index = mPostedPostsModel->getIndexOfMessage(mLastSelectedPosts[groupId()]);
|
|
|
|
std::cerr << "Setting current index to " << index.row() << ","<< index.column() << " for current post "
|
|
<< mLastSelectedPosts[groupId()].toStdString() << std::endl;
|
|
|
|
ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect);
|
|
ui->postsTree->scrollTo(index);//May change if model reloaded
|
|
ui->postsTree->setFocus();
|
|
}
|
|
#endif
|
|
#ifdef DEBUG_POSTED
|
|
else
|
|
std::cerr << "No pre-selected channel post." << std::endl;
|
|
#endif
|
|
|
|
updateShowLabel();
|
|
}
|
|
|
|
void PostedListWidgetWithModel::forceRedraw()
|
|
{
|
|
if(mPostedPostsModel)
|
|
mPostedPostsModel->deepUpdate();
|
|
}
|
|
void PostedListWidgetWithModel::redraw()
|
|
{
|
|
if(mPostedPostsModel)
|
|
mPostedPostsModel->triggerRedraw();
|
|
}
|
|
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("BoardPostsWidget"));
|
|
|
|
if (load)
|
|
{
|
|
// state of ID Chooser combobox
|
|
RsGxsId gxs_id(Settings->value("IDChooser", QString::fromStdString(RsGxsId().toStdString())).toString().toStdString());
|
|
|
|
if(!gxs_id.isNull() && rsIdentity->isOwnId(gxs_id))
|
|
ui->idChooser->setChosenId(gxs_id);
|
|
}
|
|
else
|
|
{
|
|
// state of ID Chooser combobox
|
|
RsGxsId id;
|
|
|
|
if(ui->idChooser->getChosenId(id))
|
|
Settings->setValue("IDChooser", QString::fromStdString(id.toStdString()));
|
|
}
|
|
|
|
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_compact::DEFAULT_BOARD_IMAGE);
|
|
|
|
return QIcon(postedImage);
|
|
}
|
|
|
|
void PostedListWidgetWithModel::setAllMessagesReadDo(bool read)
|
|
{
|
|
if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags))
|
|
return;
|
|
|
|
//QModelIndex src_index;
|
|
|
|
mPostedPostsModel->setAllMsgReadStatus(read);
|
|
|
|
}
|
|
|
|
void PostedListWidgetWithModel::openComments(const RsGxsMessageId& msgId)
|
|
{
|
|
QModelIndex index = mPostedPostsModel->getIndexOfMessage(msgId);
|
|
|
|
if(!index.isValid())
|
|
return;
|
|
|
|
RsGxsId current_author;
|
|
ui->idChooser->getChosenId(current_author);
|
|
|
|
RsPostedPost post = index.data(Qt::UserRole).value<RsPostedPost>() ;
|
|
auto *commentDialog = new GxsCommentDialog(this,current_author,rsPosted);
|
|
|
|
std::set<RsGxsMessageId> msg_versions({post.mMeta.mMsgId});
|
|
commentDialog->commentLoad(post.mMeta.mGroupId, msg_versions, post.mMeta.mMsgId);
|
|
|
|
QString title = QString::fromUtf8(post.mMeta.mMsgName.c_str());
|
|
if(title.length() > 30)
|
|
title = title.left(27) + "...";
|
|
|
|
ui->tabWidget->addTab(commentDialog,title);
|
|
}
|
|
|
|
void PostedListWidgetWithModel::markCurrentPostAsRead()
|
|
{
|
|
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
|
|
|
|
if(!index.isValid())
|
|
throw std::runtime_error("No post under mouse!");
|
|
|
|
mPostedPostsModel->setMsgReadStatus(index,true);
|
|
}
|
|
|
|
void PostedListWidgetWithModel::changeReadStatus(const RsGxsMessageId& msgId,bool b)
|
|
{
|
|
QModelIndex index=mPostedPostsModel->getIndexOfMessage(msgId);
|
|
mPostedPostsModel->setMsgReadStatus(index, b);
|
|
}
|
|
void PostedListWidgetWithModel::tabCloseRequested(int index)
|
|
{
|
|
std::cerr << "GxsCommentContainer::tabCloseRequested(" << index << ")";
|
|
std::cerr << std::endl;
|
|
|
|
if (index != 0)
|
|
{
|
|
QWidget *comments = ui->tabWidget->widget(index);
|
|
ui->tabWidget->removeTab(index);
|
|
delete comments;
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "GxsCommentContainer::tabCloseRequested() Not closing First Tab";
|
|
std::cerr << std::endl;
|
|
}
|
|
}
|
|
|
|
void PostedListWidgetWithModel::createMsg()
|
|
{
|
|
if (groupId().isNull()) {
|
|
return;
|
|
}
|
|
|
|
if (!IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) {
|
|
return;
|
|
}
|
|
RsGxsId author_id;
|
|
ui->idChooser->getChosenId(author_id);
|
|
std::cerr << "Chosing default ID " << author_id<< std::endl;
|
|
PostedCreatePostDialog *msgDialog = new PostedCreatePostDialog(rsPosted,groupId(),author_id);
|
|
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_compact::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(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags));
|
|
|
|
ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags));
|
|
ui->subscribeToolButton->setEnabled(true);
|
|
|
|
ui->syncPeriodLabel->setVisible(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags));
|
|
ui->syncPeriodTitleLabel->setVisible(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags));
|
|
|
|
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));
|
|
ui->poplabel->setText(QString::number(group.mMeta.mPop));
|
|
|
|
if(group.mMeta.mLastPost==0)
|
|
ui->infoLastPost->setText(tr("Never"));
|
|
else
|
|
ui->infoLastPost->setText(DateTime::formatLongDateTime(group.mMeta.mLastPost));
|
|
|
|
uint32_t current_sync_time = GxsGroupFrameDialog::checkDelay(rsPosted->getSyncPeriod(group.mMeta.mGroupId))/86400 ;
|
|
|
|
QString sync_string;
|
|
switch(current_sync_time)
|
|
{
|
|
case 5: sync_string = tr("5 days"); break;
|
|
case 15: sync_string = tr("2 weeks"); break;
|
|
case 30: sync_string = tr("1 month"); break;
|
|
case 90: sync_string = tr("3 months"); break;
|
|
case 180: sync_string = tr("6 months"); break;
|
|
case 365: sync_string = tr("1 year"); break;
|
|
case 1095: sync_string = tr("3 years"); break;
|
|
case 1825: sync_string = tr("5 years"); break;
|
|
case 0: sync_string = tr("indefinitly"); break;
|
|
default:
|
|
sync_string = tr("Unknown");
|
|
}
|
|
|
|
auto sync_period = rsPosted->getSyncPeriod(group.mMeta.mGroupId) ;
|
|
|
|
if(sync_period > 0 && group.mMeta.mLastPost > 0 && group.mMeta.mLastPost + sync_period < time(NULL) && IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
|
|
sync_string += " (Warning: will not allow posts to sync)";
|
|
|
|
ui->syncPeriodLabel->setText(sync_string);
|
|
|
|
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->trans_Description->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);
|
|
}
|
|
|
|
void PostedListWidgetWithModel::blank()
|
|
{
|
|
groupNameChanged(QString());
|
|
}
|
|
|
|
bool PostedListWidgetWithModel::navigate(const RsGxsMessageId& msgId)
|
|
{
|
|
ui->filter_LE->setText("ID:" + QString::fromStdString(msgId.toStdString()));
|
|
|
|
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;
|
|
|
|
mNavigatePendingMsgId = 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->setFocus();
|
|
|
|
ui->tabWidget->setCurrentIndex(POSTED_TABS_POSTS);
|
|
|
|
mNavigatePendingMsgId.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void PostedListWidgetWithModel::subscribeGroup(bool subscribe)
|
|
{
|
|
RsGxsGroupId grpId(groupId());
|
|
if (grpId.isNull()) return;
|
|
|
|
RsThread::async([=]()
|
|
{
|
|
rsPosted->subscribeToBoard(grpId, subscribe);
|
|
} );
|
|
}
|
|
|
|
void PostedListWidgetWithModel::voteMsg(RsGxsGrpMsgIdPair msg,bool up_or_down)
|
|
{
|
|
RsGxsId voter_id ;
|
|
if(ui->idChooser->getChosenId(voter_id) != GxsIdChooser::KnowId)
|
|
{
|
|
std::cerr << "(EE) No id returned by GxsIdChooser. Somthing's wrong?" << std::endl;
|
|
return;
|
|
}
|
|
|
|
RsGxsVoteType tvote = up_or_down?(RsGxsVoteType::UP):(RsGxsVoteType::DOWN);
|
|
|
|
std::string error_str;
|
|
RsGxsMessageId vote_id;
|
|
if(!rsPosted->voteForPost(msg.first,msg.second,voter_id,tvote,vote_id,error_str))
|
|
QMessageBox::critical(nullptr,tr("Could not vote"), tr("Error occured while voting: ")+QString::fromStdString(error_str));
|
|
else
|
|
updateDisplay(true);
|
|
}
|
|
|