merged upstream

This commit is contained in:
csoler 2020-09-11 22:37:26 +02:00
commit 5915c27b9f
59 changed files with 2690 additions and 927 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
#!/bin/bash
<<LICENSE
Copyright (C) 2020 Gioacchino Mazzurco <gio@eigenlab.org>
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 <http://www.gnu.org/licenses/>.
LICENSE
<<README
Generate a clean DHT bootstrap node list.
Feeds on previously known nodes from standard input and a few well known
mainline DHT bootstrap nodes. Prints only nodes that appears to be active to a
quick nmap check. Make sure your internet connection is working well before
using this.
Example usage:
--------------------------------
cat bdboot.txt | bdboot_generate.sh | tee /tmp/bdboot_generated.txt
cat /tmp/bdboot_generated.txt | sort -u > bdboot.txt
--------------------------------
README
function check_dht_host()
{
mHost="$1"
mPort="$2"
sudo nmap -oG - -sU -p $mPort $mHost | grep open | \
awk '{print $2" "$5}' | awk -F/ '{print $1}'
}
cat | while read line; do
hostIP="$(echo $line | awk '{print $1}')"
hostPort="$(echo $line | awk '{print $2}')"
check_dht_host $hostIP $hostPort
done
check_dht_host router.utorrent.com 6881
check_dht_host router.bittorrent.com 6881
check_dht_host dht.libtorrent.org 25401
check_dht_host dht.transmissionbt.com 6881

View File

@ -388,7 +388,12 @@ void p3discovery2::recvOwnContactInfo(const RsPeerId &fromId, const RsDiscContac
setPeerVersion(fromId, item->version); setPeerVersion(fromId, item->version);
updatePeerAddresses(item); // Hidden nodes do not need IP information. So that information is dropped.
// However, that doesn't mean hidden nodes do not know that information. Normally
// normal nodes should not send it, but old nodes still do.
if(!mPeerMgr->isHiddenNode(rsPeers->getOwnId()))
updatePeerAddresses(item);
// if the peer is not validated, we stop the exchange here // if the peer is not validated, we stop the exchange here

View File

@ -61,7 +61,7 @@ static const uint32_t INDEX_AUTHEN_ADMIN = 0x00000040; // admin key
#define GXS_MASK "GXS_MASK_HACK" #define GXS_MASK "GXS_MASK_HACK"
//#define GEN_EXCH_DEBUG 1 #define GEN_EXCH_DEBUG 1
static const uint32_t MSG_CLEANUP_PERIOD = 60*59; // 59 minutes static const uint32_t MSG_CLEANUP_PERIOD = 60*59; // 59 minutes
static const uint32_t INTEGRITY_CHECK_PERIOD = 60*31; // 31 minutes static const uint32_t INTEGRITY_CHECK_PERIOD = 60*31; // 31 minutes
@ -282,7 +282,7 @@ bool RsGenExchange::messagePublicationTest(const RsGxsMsgMetaData& meta)
rstime_t storageTimeLimit = meta.mPublishTs + st; rstime_t storageTimeLimit = meta.mPublishTs + st;
return meta.mMsgStatus & GXS_SERV::GXS_MSG_STATUS_KEEP || st == 0 || storageTimeLimit >= time(NULL); return meta.mMsgStatus & GXS_SERV::GXS_MSG_STATUS_KEEP_FOREVER || st == 0 || storageTimeLimit >= time(NULL);
} }
bool RsGenExchange::acknowledgeTokenMsg(const uint32_t& token, bool RsGenExchange::acknowledgeTokenMsg(const uint32_t& token,

View File

@ -196,7 +196,6 @@ bool RsGxsGrpMetaData::deserialise(void *data, uint32_t &pktsize)
return ok; return ok;
} }
int RsGxsMsgMetaData::refcount = 0;
RsGxsMsgMetaData::RsGxsMsgMetaData(){ RsGxsMsgMetaData::RsGxsMsgMetaData(){
clear(); clear();

View File

@ -99,8 +99,6 @@ public:
void clear(); void clear();
void operator =(const RsMsgMetaData& rMeta); void operator =(const RsMsgMetaData& rMeta);
static int refcount;
//Sort data in same order than serialiser and deserializer //Sort data in same order than serialiser and deserializer
RsGxsGroupId mGroupId; RsGxsGroupId mGroupId;
RsGxsMessageId mMsgId; RsGxsMessageId mMsgId;

View File

@ -167,7 +167,12 @@ public:
class RsGxsNetTunnelTurtleSearchGroupDataItem: public RsGxsNetTunnelItem class RsGxsNetTunnelTurtleSearchGroupDataItem: public RsGxsNetTunnelItem
{ {
public: public:
explicit RsGxsNetTunnelTurtleSearchGroupDataItem(): RsGxsNetTunnelItem(RS_PKT_SUBTYPE_GXS_NET_TUNNEL_TURTLE_SEARCH_GROUP_DATA) {} explicit RsGxsNetTunnelTurtleSearchGroupDataItem()
: RsGxsNetTunnelItem(RS_PKT_SUBTYPE_GXS_NET_TUNNEL_TURTLE_SEARCH_GROUP_DATA),
encrypted_group_data(NULL),
encrypted_group_data_len(0)
{}
virtual ~RsGxsNetTunnelTurtleSearchGroupDataItem() {} virtual ~RsGxsNetTunnelTurtleSearchGroupDataItem() {}
uint16_t service ; uint16_t service ;

View File

@ -107,7 +107,7 @@ bool RsGxsMessageCleanUp::clean()
bool remove = store_period > 0 && ((meta->mPublishTs + store_period) < now) && !have_kids; bool remove = store_period > 0 && ((meta->mPublishTs + store_period) < now) && !have_kids;
// check client does not want the message kept regardless of age // check client does not want the message kept regardless of age
remove &= !(meta->mMsgStatus & GXS_SERV::GXS_MSG_STATUS_KEEP); remove &= !(meta->mMsgStatus & GXS_SERV::GXS_MSG_STATUS_KEEP_FOREVER);
// if not subscribed remove messages (can optimise this really) // if not subscribed remove messages (can optimise this really)
remove = remove || (grpMeta->mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_NOT_SUBSCRIBED); remove = remove || (grpMeta->mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_NOT_SUBSCRIBED);

View File

@ -111,27 +111,6 @@ typedef t_RsGxsGenericDataTemporaryMapVector<RsNxsMsg> RsNxsMsgDa
typedef t_RsGxsGenericDataTemporaryList<RsNxsGrp> RsNxsGrpDataTemporaryList ; typedef t_RsGxsGenericDataTemporaryList<RsNxsGrp> RsNxsGrpDataTemporaryList ;
typedef t_RsGxsGenericDataTemporaryList<RsNxsMsg> RsNxsMsgDataTemporaryList ; typedef t_RsGxsGenericDataTemporaryList<RsNxsMsg> RsNxsMsgDataTemporaryList ;
#ifdef UNUSED
template<class T>
class RsGxsMetaDataTemporaryMapVector: public std::vector<T*>
{
public:
virtual ~RsGxsMetaDataTemporaryMapVector()
{
clear() ;
}
virtual void clear()
{
for(typename RsGxsMetaDataTemporaryMapVector<T>::iterator it = this->begin();it!=this->end();++it)
if(it->second != NULL)
delete it->second ;
std::vector<T*>::clear() ;
}
};
#endif
inline RsGxsGrpMsgIdPair getMsgIdPair(RsNxsMsg& msg) inline RsGxsGrpMsgIdPair getMsgIdPair(RsNxsMsg& msg)
{ {
return RsGxsGrpMsgIdPair(std::make_pair(msg.grpId, msg.msgId)); return RsGxsGrpMsgIdPair(std::make_pair(msg.grpId, msg.msgId));
@ -146,7 +125,7 @@ inline RsGxsGrpMsgIdPair getMsgIdPair(RsGxsMsgItem& msg)
* Does message clean up based on individual group expirations first * Does message clean up based on individual group expirations first
* if avialable. If not then deletion s * if avialable. If not then deletion s
*/ */
class RsGxsMessageCleanUp //: public RsThread class RsGxsMessageCleanUp
{ {
public: public:
@ -166,11 +145,6 @@ public:
*/ */
bool clean(); bool clean();
/*!
* TODO: Rather than manual progressions consider running through a thread
*/
//virtual void data_tick(){}
private: private:
RsGeneralDataService* const mDs; RsGeneralDataService* const mDs;

View File

@ -195,8 +195,6 @@ public:
/** /**
* @brief Post event to the event queue. * @brief Post event to the event queue.
* @param[in] event * @param[in] event
* @param[out] errorMessage Optional storage for error messsage, meaningful
* only on failure.
* @return Success or error details. * @return Success or error details.
*/ */
virtual std::error_condition postEvent( virtual std::error_condition postEvent(
@ -206,8 +204,6 @@ public:
* @brief Send event directly to handlers. Blocking API * @brief Send event directly to handlers. Blocking API
* The handlers get exectuded on the caller thread. * The handlers get exectuded on the caller thread.
* @param[in] event * @param[in] event
* @param[out] errorMessage Optional storage for error messsage, meaningful
* only on failure.
* @return Success or error details. * @return Success or error details.
*/ */
virtual std::error_condition sendEvent( virtual std::error_condition sendEvent(

View File

@ -107,7 +107,8 @@ namespace GXS_SERV {
static const uint32_t GXS_MSG_STATUS_UNPROCESSED = 0x00000001; // Flags to store the read/process status of group messages. static const uint32_t GXS_MSG_STATUS_UNPROCESSED = 0x00000001; // Flags to store the read/process status of group messages.
static const uint32_t GXS_MSG_STATUS_GUI_UNREAD = 0x00000002; // The actual meaning may depend on the type of service. static const uint32_t GXS_MSG_STATUS_GUI_UNREAD = 0x00000002; // The actual meaning may depend on the type of service.
static const uint32_t GXS_MSG_STATUS_GUI_NEW = 0x00000004; // static const uint32_t GXS_MSG_STATUS_GUI_NEW = 0x00000004; //
static const uint32_t GXS_MSG_STATUS_KEEP = 0x00000008; // /** Do not delete message even if older then group maximum storage time */
static const uint32_t GXS_MSG_STATUS_KEEP_FOREVER = 0x00000008;
static const uint32_t GXS_MSG_STATUS_DELETE = 0x00000020; // static const uint32_t GXS_MSG_STATUS_DELETE = 0x00000020; //
/** END GXS Msg status flags **/ /** END GXS Msg status flags **/

View File

@ -363,12 +363,25 @@ public:
* @param[in] parentId id of the post of which child posts (aka replies) * @param[in] parentId id of the post of which child posts (aka replies)
* are requested. * are requested.
* @param[out] childPosts storage for the child posts * @param[out] childPosts storage for the child posts
* @return false if something failed, true otherwhise * @return Success or error details
*/ */
virtual std::error_condition getChildPosts( virtual std::error_condition getChildPosts(
const RsGxsGroupId& forumId, const RsGxsMessageId& parentId, const RsGxsGroupId& forumId, const RsGxsMessageId& parentId,
std::vector<RsGxsForumMsg>& childPosts ) = 0; std::vector<RsGxsForumMsg>& childPosts ) = 0;
/**
* @brief Set keep forever flag on a post so it is not deleted even if older
* then group maximum storage time
* @jsonapi{development}
* @param[in] forumId id of the forum of which the post pertain
* @param[in] postId id of the post on which to set the flag
* @param[in] keepForever true to set the flag, false to unset it
* @return Success or error details
*/
virtual std::error_condition setPostKeepForever(
const RsGxsGroupId& forumId, const RsGxsMessageId& postId,
bool keepForever ) = 0;
/** /**
* @brief Create forum. Blocking API. * @brief Create forum. Blocking API.
* @jsonapi{development} * @jsonapi{development}

View File

@ -237,11 +237,10 @@ RsGenExchange::ServiceCreate_Return p3GxsChannels::service_CreateGroup(RsGxsGrpI
void p3GxsChannels::notifyChanges(std::vector<RsGxsNotify *> &changes) void p3GxsChannels::notifyChanges(std::vector<RsGxsNotify *> &changes)
{ {
#ifdef GXSCHANNELS_DEBUG #ifdef GXSCHANNEL_DEBUG
std::cerr << "p3GxsChannels::notifyChanges() : " << changes.size() << "changes to notify" << std::endl; RsDbg() << " Processing " << changes.size() << " channel changes..." << std::endl;
#endif #endif
/* iterate through and grab any new messages */
/* iterate through and grab any new messages */
std::set<RsGxsGroupId> unprocessedGroups; std::set<RsGxsGroupId> unprocessedGroups;
std::vector<RsGxsNotify *>::iterator it; std::vector<RsGxsNotify *>::iterator it;
@ -295,6 +294,10 @@ void p3GxsChannels::notifyChanges(std::vector<RsGxsNotify *> &changes)
if (grpChange && rsEvents) if (grpChange && rsEvents)
{ {
#ifdef GXSCHANNEL_DEBUG
RsDbg() << " Grp Change Event or type " << grpChange->getType() << ":" << std::endl;
#endif
switch (grpChange->getType()) switch (grpChange->getType())
{ {
case RsGxsNotify::TYPE_PROCESSED: // happens when the group is subscribed case RsGxsNotify::TYPE_PROCESSED: // happens when the group is subscribed
@ -322,18 +325,27 @@ void p3GxsChannels::notifyChanges(std::vector<RsGxsNotify *> &changes)
RS_STACK_MUTEX(mKnownChannelsMutex); RS_STACK_MUTEX(mKnownChannelsMutex);
if(mKnownChannels.find(grpChange->mGroupId) == mKnownChannels.end()) #ifdef GXSCHANNEL_DEBUG
RsDbg() << " Type = Published/New " << std::endl;
#endif
if(mKnownChannels.find(grpChange->mGroupId) == mKnownChannels.end())
{ {
mKnownChannels.insert(std::make_pair(grpChange->mGroupId,time(NULL))) ; #ifdef GXSCHANNEL_DEBUG
RsDbg() << " Status: unknown. Sending notification event." << std::endl;
#endif
mKnownChannels.insert(std::make_pair(grpChange->mGroupId,time(NULL))) ;
IndicateConfigChanged(); IndicateConfigChanged();
auto ev = std::make_shared<RsGxsChannelEvent>(); auto ev = std::make_shared<RsGxsChannelEvent>();
ev->mChannelGroupId = grpChange->mGroupId; ev->mChannelGroupId = grpChange->mGroupId;
ev->mChannelEventCode = RsChannelEventCode::NEW_CHANNEL; ev->mChannelEventCode = RsChannelEventCode::NEW_CHANNEL;
rsEvents->postEvent(ev); rsEvents->postEvent(ev);
} }
#ifdef GXSCHANNEL_DEBUG
else else
std::cerr << "(II) Not notifying already known channel " << grpChange->mGroupId << std::endl; RsDbg() << " Not notifying already known channel " << grpChange->mGroupId << std::endl;
#endif
} }
break; break;

View File

@ -919,6 +919,9 @@ void p3GxsForums::setMessageReadStatus(uint32_t& token, const RsGxsGrpMsgIdPair&
setMsgStatusFlags(token, msgId, status, mask); setMsgStatusFlags(token, msgId, status, mask);
/* WARNING: The event may be received before the operation is completed!
* TODO: move notification to blocking method markRead(...) which wait the
* operation to complete */
if (rsEvents) if (rsEvents)
{ {
auto ev = std::make_shared<RsGxsForumEvent>(); auto ev = std::make_shared<RsGxsForumEvent>();
@ -933,6 +936,37 @@ void p3GxsForums::setMessageReadStatus(uint32_t& token, const RsGxsGrpMsgIdPair&
/********************************************************************************************/ /********************************************************************************************/
/********************************************************************************************/ /********************************************************************************************/
std::error_condition p3GxsForums::setPostKeepForever(
const RsGxsGroupId& forumId, const RsGxsMessageId& postId,
bool keepForever )
{
if(forumId.isNull() || postId.isNull()) return std::errc::invalid_argument;
uint32_t mask = GXS_SERV::GXS_MSG_STATUS_KEEP_FOREVER;
uint32_t status = keepForever ? GXS_SERV::GXS_MSG_STATUS_KEEP_FOREVER : 0;
uint32_t token;
setMsgStatusFlags(token, RsGxsGrpMsgIdPair(forumId, postId), status, mask);
switch(waitToken(token))
{
case RsTokenService::PENDING: // [[fallthrough]];
case RsTokenService::PARTIAL: return std::errc::timed_out;
case RsTokenService::COMPLETE: // [[fallthrough]];
case RsTokenService::DONE:
{
auto ev = std::make_shared<RsGxsForumEvent>();
ev->mForumGroupId = forumId;
ev->mForumMsgId = postId;
ev->mForumEventCode = RsForumEventCode::UPDATED_MESSAGE;
rsEvents->postEvent(ev);
return std::error_condition();
}
case RsTokenService::CANCELLED: return std::errc::operation_canceled;
default: return std::errc::bad_message;
}
}
/* so we need the same tick idea as wiki for generating dummy forums /* so we need the same tick idea as wiki for generating dummy forums
*/ */

View File

@ -137,6 +137,11 @@ public:
const RsGxsGroupId& forumId, const RsGxsMessageId& parentId, const RsGxsGroupId& forumId, const RsGxsMessageId& parentId,
std::vector<RsGxsForumMsg>& childPosts ) override; std::vector<RsGxsForumMsg>& childPosts ) override;
/// @see RsGxsForums
std::error_condition setPostKeepForever(
const RsGxsGroupId& forumId, const RsGxsMessageId& postId,
bool keepForever );
/// implementation of rsGxsGorums /// implementation of rsGxsGorums
/// ///
bool getGroupData(const uint32_t &token, std::vector<RsGxsForumGroup> &groups) override; bool getGroupData(const uint32_t &token, std::vector<RsGxsForumGroup> &groups) override;

View File

@ -40,10 +40,7 @@
</size> </size>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QFrame#frame{border: 2px solid #CCCCCC; <string notr="true"/>
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #EEEEEE, stop: 1 #CCCCCC);
border-radius: 10px}</string>
</property> </property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>

View File

@ -40,10 +40,7 @@
</size> </size>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QFrame#frame{border: 2px solid #CCCCCC; <string notr="true"/>
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #EEEEEE, stop: 1 #CCCCCC);
border-radius: 10px}</string>
</property> </property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>

View File

@ -40,10 +40,7 @@
</size> </size>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QFrame#frame{border: 2px solid #CCCCCC; <string notr="true"/>
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #EEEEEE, stop: 1 #CCCCCC);
border-radius: 10px}</string>
</property> </property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::StyledPanel</enum>

View File

@ -527,6 +527,8 @@ void GroupTreeWidget::fillGroupItems(QTreeWidgetItem *categoryItem, const QList<
tooltip += "\n" + tr("Description") + ": " + itemInfo.description; tooltip += "\n" + tr("Description") + ": " + itemInfo.description;
tooltip += "\n" + tr("Id") + ": " + itemInfo.id;
item->setToolTip(COLUMN_NAME, tooltip); item->setToolTip(COLUMN_NAME, tooltip);
item->setToolTip(COLUMN_UNREAD, tooltip); item->setToolTip(COLUMN_UNREAD, tooltip);
item->setToolTip(COLUMN_POPULARITY, tooltip); item->setToolTip(COLUMN_POPULARITY, tooltip);

View File

@ -52,6 +52,7 @@ void GxsCommentDialog::init()
connect(ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh())); connect(ui->refreshButton, SIGNAL(clicked()), this, SLOT(refresh()));
connect(ui->idChooser, SIGNAL(currentIndexChanged( int )), this, SLOT(voterSelectionChanged( int ))); connect(ui->idChooser, SIGNAL(currentIndexChanged( int )), this, SLOT(voterSelectionChanged( int )));
connect(ui->idChooser, SIGNAL(idsLoaded()), this, SLOT(idChooserReady())); connect(ui->idChooser, SIGNAL(idsLoaded()), this, SLOT(idChooserReady()));
connect(ui->treeWidget,SIGNAL(commentsLoaded(int)),this,SLOT(notifyCommentsLoaded(int)));
connect(ui->commentButton, SIGNAL(clicked()), ui->treeWidget, SLOT(makeComment())); connect(ui->commentButton, SIGNAL(clicked()), ui->treeWidget, SLOT(makeComment()));
connect(ui->sortBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sortComments(int))); connect(ui->sortBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sortComments(int)));
@ -95,6 +96,11 @@ void GxsCommentDialog::commentLoad(const RsGxsGroupId &grpId, const std::set<RsG
ui->treeWidget->requestComments(mGrpId,msg_versions,most_recent_msgId); ui->treeWidget->requestComments(mGrpId,msg_versions,most_recent_msgId);
} }
void GxsCommentDialog::notifyCommentsLoaded(int n)
{
emit commentsLoaded(n);
}
void GxsCommentDialog::refresh() void GxsCommentDialog::refresh()
{ {
std::cerr << "GxsCommentDialog::refresh()"; std::cerr << "GxsCommentDialog::refresh()";

View File

@ -48,6 +48,10 @@ private slots:
void idChooserReady(); void idChooserReady();
void voterSelectionChanged( int index ); void voterSelectionChanged( int index );
void sortComments(int); void sortComments(int);
void notifyCommentsLoaded(int n);
signals:
void commentsLoaded(int);
private: private:
void init(); void init();

View File

@ -228,8 +228,10 @@ void GxsCommentTreeWidget::customPopUpMenu(const QPoint& /*point*/)
void GxsCommentTreeWidget::voteUp() void GxsCommentTreeWidget::voteUp()
{ {
std::cerr << "GxsCommentTreeWidget::voteUp()"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::voteUp()";
std::cerr << std::endl; std::cerr << std::endl;
#endif
vote(mGroupId, mLatestMsgId, mCurrentCommentMsgId, mVoterId, true); vote(mGroupId, mLatestMsgId, mCurrentCommentMsgId, mVoterId, true);
} }
@ -237,8 +239,10 @@ void GxsCommentTreeWidget::voteUp()
void GxsCommentTreeWidget::voteDown() void GxsCommentTreeWidget::voteDown()
{ {
std::cerr << "GxsCommentTreeWidget::voteDown()"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::voteDown()";
std::cerr << std::endl; std::cerr << std::endl;
#endif
vote(mGroupId, mLatestMsgId, mCurrentCommentMsgId, mVoterId, false); vote(mGroupId, mLatestMsgId, mCurrentCommentMsgId, mVoterId, false);
} }
@ -246,8 +250,10 @@ void GxsCommentTreeWidget::voteDown()
void GxsCommentTreeWidget::setVoteId(const RsGxsId &voterId) void GxsCommentTreeWidget::setVoteId(const RsGxsId &voterId)
{ {
mVoterId = voterId; mVoterId = voterId;
std::cerr << "GxsCommentTreeWidget::setVoterId(" << mVoterId << ")"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::setVoterId(" << mVoterId << ")";
std::cerr << std::endl; std::cerr << std::endl;
#endif
} }
@ -270,6 +276,7 @@ void GxsCommentTreeWidget::vote(const RsGxsGroupId &groupId, const RsGxsMessageI
vote.mVoteType = GXS_VOTE_DOWN; vote.mVoteType = GXS_VOTE_DOWN;
} }
#ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::vote()"; std::cerr << "GxsCommentTreeWidget::vote()";
std::cerr << std::endl; std::cerr << std::endl;
@ -277,6 +284,7 @@ void GxsCommentTreeWidget::vote(const RsGxsGroupId &groupId, const RsGxsMessageI
std::cerr << "ThreadId : " << vote.mMeta.mThreadId << std::endl; std::cerr << "ThreadId : " << vote.mMeta.mThreadId << std::endl;
std::cerr << "ParentId : " << vote.mMeta.mParentId << std::endl; std::cerr << "ParentId : " << vote.mMeta.mParentId << std::endl;
std::cerr << "AuthorId : " << vote.mMeta.mAuthorId << std::endl; std::cerr << "AuthorId : " << vote.mMeta.mAuthorId << std::endl;
#endif
uint32_t token; uint32_t token;
mCommentService->createNewVote(token, vote); mCommentService->createNewVote(token, vote);
@ -361,13 +369,17 @@ void GxsCommentTreeWidget::requestComments(const RsGxsGroupId& group, const std:
void GxsCommentTreeWidget::service_requestComments(const RsGxsGroupId& group_id,const std::set<RsGxsMessageId> & msgIds) void GxsCommentTreeWidget::service_requestComments(const RsGxsGroupId& group_id,const std::set<RsGxsMessageId> & msgIds)
{ {
/* request comments */ /* request comments */
std::cerr << "GxsCommentTreeWidget::service_requestComments for group " << group_id << std::endl; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::service_requestComments for group " << group_id << std::endl;
#endif
std::vector<RsGxsGrpMsgIdPair> ids_to_ask; std::vector<RsGxsGrpMsgIdPair> ids_to_ask;
for(std::set<RsGxsMessageId>::const_iterator it(msgIds.begin());it!=msgIds.end();++it) for(std::set<RsGxsMessageId>::const_iterator it(msgIds.begin());it!=msgIds.end();++it)
{ {
std::cerr << " asking for msg " << *it << std::endl; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << " asking for msg " << *it << std::endl;
#endif
ids_to_ask.push_back(std::make_pair(group_id,*it)); ids_to_ask.push_back(std::make_pair(group_id,*it));
} }
@ -399,14 +411,18 @@ void GxsCommentTreeWidget::completeItems()
std::map<RsGxsMessageId, QTreeWidgetItem *>::iterator lit; std::map<RsGxsMessageId, QTreeWidgetItem *>::iterator lit;
std::multimap<RsGxsMessageId, QTreeWidgetItem *>::iterator pit; std::multimap<RsGxsMessageId, QTreeWidgetItem *>::iterator pit;
std::cerr << "GxsCommentTreeWidget::completeItems() " << mPendingInsertMap.size(); #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::completeItems() " << mPendingInsertMap.size();
std::cerr << " PendingItems"; std::cerr << " PendingItems";
std::cerr << std::endl; std::cerr << std::endl;
#endif
for(pit = mPendingInsertMap.begin(); pit != mPendingInsertMap.end(); ++pit) for(pit = mPendingInsertMap.begin(); pit != mPendingInsertMap.end(); ++pit)
{ {
std::cerr << "GxsCommentTreeWidget::completeItems() item->parent: " << pit->first; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::completeItems() item->parent: " << pit->first;
std::cerr << std::endl; std::cerr << std::endl;
#endif
if (pit->first != parentId) if (pit->first != parentId)
{ {
@ -425,15 +441,19 @@ void GxsCommentTreeWidget::completeItems()
if (parent) if (parent)
{ {
std::cerr << "GxsCommentTreeWidget::completeItems() Added to Parent"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::completeItems() Added to Parent";
std::cerr << std::endl; std::cerr << std::endl;
#endif
parent->addChild(pit->second); parent->addChild(pit->second);
} }
else if (mMsgVersions.find(parentId) != mMsgVersions.end()) else if (mMsgVersions.find(parentId) != mMsgVersions.end())
{ {
std::cerr << "GxsCommentTreeWidget::completeItems() Added to topLevelItems"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::completeItems() Added to topLevelItems";
std::cerr << std::endl; std::cerr << std::endl;
#endif
topLevelItems.append(pit->second); topLevelItems.append(pit->second);
} }
@ -443,8 +463,10 @@ void GxsCommentTreeWidget::completeItems()
/* missing parent -> insert At Top Level */ /* missing parent -> insert At Top Level */
QTreeWidgetItem *missingItem = service_createMissingItem(pit->first); QTreeWidgetItem *missingItem = service_createMissingItem(pit->first);
std::cerr << "GxsCommentTreeWidget::completeItems() Added MissingItem"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::completeItems() Added MissingItem";
std::cerr << std::endl; std::cerr << std::endl;
#endif
parent = missingItem; parent = missingItem;
parent->addChild(pit->second); parent->addChild(pit->second);
@ -464,9 +486,11 @@ void GxsCommentTreeWidget::completeItems()
void GxsCommentTreeWidget::addItem(RsGxsMessageId itemId, RsGxsMessageId parentId, QTreeWidgetItem *item) void GxsCommentTreeWidget::addItem(RsGxsMessageId itemId, RsGxsMessageId parentId, QTreeWidgetItem *item)
{ {
std::cerr << "GxsCommentTreeWidget::addItem() Id: " << itemId; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::addItem() Id: " << itemId;
std::cerr << " ParentId: " << parentId; std::cerr << " ParentId: " << parentId;
std::cerr << std::endl; std::cerr << std::endl;
#endif
/* store in map -> for children */ /* store in map -> for children */
mLoadingMap[itemId] = item; mLoadingMap[itemId] = item;
@ -475,20 +499,48 @@ void GxsCommentTreeWidget::addItem(RsGxsMessageId itemId, RsGxsMessageId parentI
it = mLoadingMap.find(parentId); it = mLoadingMap.find(parentId);
if (it != mLoadingMap.end()) if (it != mLoadingMap.end())
{ {
std::cerr << "GxsCommentTreeWidget::addItem() Added to Parent"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::addItem() Added to Parent";
std::cerr << std::endl; std::cerr << std::endl;
#endif
it->second->addChild(item); it->second->addChild(item);
} }
else else
{ {
std::cerr << "GxsCommentTreeWidget::addItem() Added to Pending List"; #ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::addItem() Added to Pending List";
std::cerr << std::endl; std::cerr << std::endl;
#endif
mPendingInsertMap.insert(std::make_pair(parentId, item)); mPendingInsertMap.insert(std::make_pair(parentId, item));
} }
} }
int treeCount(QTreeWidget *tree, QTreeWidgetItem *parent = 0)
{
int count = 0;
if (parent == 0) {
int topCount = tree->topLevelItemCount();
for (int i = 0; i < topCount; i++) {
QTreeWidgetItem *item = tree->topLevelItem(i);
if (item->isExpanded()) {
count += treeCount(tree, item);
}
}
count += topCount;
} else {
int childCount = parent->childCount();
for (int i = 0; i < childCount; i++) {
QTreeWidgetItem *item = parent->child(i);
if (item->isExpanded()) {
count += treeCount(tree, item);
}
}
count += childCount;
}
return count;
}
void GxsCommentTreeWidget::loadThread(const uint32_t &token) void GxsCommentTreeWidget::loadThread(const uint32_t &token)
{ {
clearItems(); clearItems();
@ -496,6 +548,8 @@ void GxsCommentTreeWidget::loadThread(const uint32_t &token)
service_loadThread(token); service_loadThread(token);
completeItems(); completeItems();
emit commentsLoaded(treeCount(this));
} }
void GxsCommentTreeWidget::acknowledgeComment(const uint32_t &token) void GxsCommentTreeWidget::acknowledgeComment(const uint32_t &token)
@ -615,8 +669,10 @@ QTreeWidgetItem *GxsCommentTreeWidget::service_createMissingItem(const RsGxsMess
void GxsCommentTreeWidget::loadRequest(const TokenQueue *queue, const TokenRequest &req) void GxsCommentTreeWidget::loadRequest(const TokenQueue *queue, const TokenRequest &req)
{ {
#ifdef DEBUG_GXSCOMMENT_TREEWIDGET
std::cerr << "GxsCommentTreeWidget::loadRequest() UserType: " << req.mUserType; std::cerr << "GxsCommentTreeWidget::loadRequest() UserType: " << req.mUserType;
std::cerr << std::endl; std::cerr << std::endl;
#endif
if (queue != mTokenQueue) if (queue != mTokenQueue)
{ {

View File

@ -80,6 +80,9 @@ public slots:
void markSpammer(); void markSpammer();
void banUser(); void banUser();
signals:
void commentsLoaded(int);
protected: protected:
void vote(const RsGxsGroupId &groupId, const RsGxsMessageId &threadId, void vote(const RsGxsGroupId &groupId, const RsGxsMessageId &threadId,

View File

@ -312,6 +312,12 @@ void GxsGroupFrameDialog::updateSearchResults(const TurtleRequestId& sid)
auto it2 = mSearchGroupsItems.find(sid); auto it2 = mSearchGroupsItems.find(sid);
if(it2 == mSearchGroupsItems.end())
{
std::cerr << "(EE) received a channel group turtle search result with ID " << sid << " but no item is known for this search" << std::endl;
return;
}
QList<GroupItemInfo> group_items ; QList<GroupItemInfo> group_items ;
for(auto it3(group_infos.begin());it3!=group_infos.end();++it3) for(auto it3(group_infos.begin());it3!=group_infos.end();++it3)

View File

@ -78,10 +78,19 @@ CreateGxsChannelMsg::CreateGxsChannelMsg(const RsGxsGroupId &cId, RsGxsMessageId
connect(thumbNailCb, SIGNAL(toggled(bool)), this, SLOT(allowAutoMediaThumbNail(bool))); connect(thumbNailCb, SIGNAL(toggled(bool)), this, SLOT(allowAutoMediaThumbNail(bool)));
connect(stackedWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint))); connect(stackedWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint)));
connect(generateCheckBox, SIGNAL(toggled(bool)), generateSpinBox, SLOT(setEnabled(bool))); connect(generateCheckBox, SIGNAL(toggled(bool)), generateSpinBox, SLOT(setEnabled(bool)));
connect(aspectRatio_CB,SIGNAL(currentIndexChanged(int)),this,SLOT(changeAspectRatio(int)));
aspectRatio_CB->setItemIcon(0,FilesDefs::getIconFromQtResourcePath(":/icons/svg/ratio-auto.svg"));
aspectRatio_CB->setItemIcon(1,FilesDefs::getIconFromQtResourcePath(":/icons/svg/ratio-1-1.svg"));
aspectRatio_CB->setItemIcon(2,FilesDefs::getIconFromQtResourcePath(":/icons/svg/ratio-3-4.svg"));
aspectRatio_CB->setItemIcon(3,FilesDefs::getIconFromQtResourcePath(":/icons/svg/ratio-16-9.svg"));
generateSpinBox->setEnabled(false); generateSpinBox->setEnabled(false);
thumbNailCb->setVisible(false); preview_W->setPixmap(FilesDefs::getPixmapFromQtResourcePath(ChannelPostThumbnailView::CHAN_DEFAULT_IMAGE),true);
preview_W->setText("[Text preview]");
thumbNailCb->setVisible(false);
thumbNailCb->setEnabled(false); thumbNailCb->setEnabled(false);
#ifdef CHANNELS_FRAME_CATCHER #ifdef CHANNELS_FRAME_CATCHER
fCatcher = new framecatcher(); fCatcher = new framecatcher();
@ -107,6 +116,19 @@ CreateGxsChannelMsg::~CreateGxsChannelMsg()
#endif #endif
} }
void CreateGxsChannelMsg::changeAspectRatio(int s)
{
switch(s)
{
case 0: break;
case 1: preview_W->setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_1_1);
break;
case 2: preview_W->setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_2_3);
break;
case 3: preview_W->setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_16_9);
break;
}
}
void CreateGxsChannelMsg::contextMenu(QPoint /*point*/) void CreateGxsChannelMsg::contextMenu(QPoint /*point*/)
{ {
QList<RetroShareLink> links ; QList<RetroShareLink> links ;
@ -688,7 +710,7 @@ void CreateGxsChannelMsg::sendMessage(const std::string &subject, const std::str
// send chan image // send chan image
buffer.open(QIODevice::WriteOnly); buffer.open(QIODevice::WriteOnly);
picture.save(&buffer, "PNG"); // writes image into ba in PNG format preview_W->getCroppedScaledPicture().save(&buffer, "PNG"); // writes image into ba in PNG format
post.mThumbnail.copy((uint8_t *) ba.data(), ba.size()); post.mThumbnail.copy((uint8_t *) ba.data(), ba.size());
} }
@ -723,7 +745,7 @@ void CreateGxsChannelMsg::sendMessage(const std::string &subject, const std::str
void CreateGxsChannelMsg::addThumbnail() void CreateGxsChannelMsg::addThumbnail()
{ {
QPixmap img = misc::getOpenThumbnailedPicture(this, tr("Load thumbnail picture"), 107,156); // these absolute sizes are terrible QPixmap img = misc::getOpenThumbnailedPicture(this, tr("Load thumbnail picture"), 0,0); // 0,0 means: no scale.
if (img.isNull()) if (img.isNull())
return; return;
@ -731,7 +753,8 @@ void CreateGxsChannelMsg::addThumbnail()
picture = img; picture = img;
// to show the selected // to show the selected
preview_W->setPixmap(picture); preview_W->setPixmap(picture, aspectRatio_CB->currentIndex()==0);
} }
void CreateGxsChannelMsg::loadOriginalChannelPostInfo() void CreateGxsChannelMsg::loadOriginalChannelPostInfo()
@ -775,7 +798,7 @@ void CreateGxsChannelMsg::loadOriginalChannelPostInfo()
if(post.mThumbnail.mData != NULL) if(post.mThumbnail.mData != NULL)
{ {
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData,post.mThumbnail.mSize,picture,GxsIdDetails::ORIGINAL); GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData,post.mThumbnail.mSize,picture,GxsIdDetails::ORIGINAL);
preview_W->setPixmap(picture); preview_W->setPixmap(picture,true);
} }

View File

@ -64,6 +64,7 @@ private slots:
void sendMsg(); void sendMsg();
void pasteLink() ; void pasteLink() ;
void contextMenu(QPoint) ; void contextMenu(QPoint) ;
void changeAspectRatio(int s);
void addThumbnail(); void addThumbnail();
void allowAutoMediaThumbNail(bool); void allowAutoMediaThumbNail(bool);

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>736</width> <width>881</width>
<height>271</height> <height>383</height>
</rect> </rect>
</property> </property>
<property name="acceptDrops"> <property name="acceptDrops">
@ -79,32 +79,35 @@
<number>0</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="stackedWidgetPage1"> <widget class="QWidget" name="stackedWidgetPage1">
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item> <item>
<widget class="ChannelPostThumbnailView" name="preview_W" native="true"/> <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="ChannelPostThumbnailView" name="preview_W" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -168,7 +171,7 @@ p, li { white-space: pre-wrap; }
<string>Add Channel Thumbnail</string> <string>Add Channel Thumbnail</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../icons.qrc"> <iconset>
<normaloff>:/icons/png/add-image.png</normaloff>:/icons/png/add-image.png</iconset> <normaloff>:/icons/png/add-image.png</normaloff>:/icons/png/add-image.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -179,13 +182,43 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QComboBox" name="aspectRatio_CB">
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<item>
<property name="text">
<string>Auto</string>
</property>
</item>
<item>
<property name="text">
<string>1:1</string>
</property>
</item>
<item>
<property name="text">
<string>3:4</string>
</property>
</item>
<item>
<property name="text">
<string>16:9</string>
</property>
</item>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="addfilepushButton"> <widget class="QPushButton" name="addfilepushButton">
<property name="text"> <property name="text">
<string>Add File to Attach</string> <string>Add File to Attach</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../icons.qrc"> <iconset>
<normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset> <normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -285,7 +318,7 @@ p, li { white-space: pre-wrap; }
<string/> <string/>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../icons.qrc"> <iconset>
<normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset> <normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -309,7 +342,7 @@ p, li { white-space: pre-wrap; }
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>81</width> <width>84</width>
<height>24</height> <height>24</height>
</rect> </rect>
</property> </property>
@ -389,7 +422,7 @@ p, li { white-space: pre-wrap; }
<string>Channel Post</string> <string>Channel Post</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../icons.qrc"> <iconset>
<normaloff>:/icons/png/comment.png</normaloff>:/icons/png/comment.png</iconset> <normaloff>:/icons/png/comment.png</normaloff>:/icons/png/comment.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -419,7 +452,7 @@ p, li { white-space: pre-wrap; }
<string>Attachments</string> <string>Attachments</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../icons.qrc"> <iconset>
<normaloff>:/icons/png/attachements.png</normaloff>:/icons/png/attachements.png</iconset> <normaloff>:/icons/png/attachements.png</normaloff>:/icons/png/attachements.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">
@ -491,7 +524,6 @@ p, li { white-space: pre-wrap; }
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../images.qrc"/> <include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -35,9 +35,9 @@ public:
/** Default Destructor */ /** Default Destructor */
~GxsChannelDialog(); ~GxsChannelDialog();
virtual QIcon iconPixmap() const { return QIcon(IMAGE_GXSCHANNELS) ; } //MainPage virtual QIcon iconPixmap() const override { return QIcon(IMAGE_GXSCHANNELS) ; } //MainPage
virtual QString pageName() const { return tr("Channels") ; } //MainPage virtual QString pageName() const override { return tr("Channels") ; } //MainPage
virtual QString helpText() const { return ""; } //MainPage virtual QString helpText() const override { return ""; } //MainPage
void shareOnChannel(const RsGxsGroupId& channel_id, const QList<RetroShareLink>& file_link) ; void shareOnChannel(const RsGxsGroupId& channel_id, const QList<RetroShareLink>& file_link) ;
@ -69,17 +69,17 @@ private slots:
private: private:
/* GxsGroupFrameDialog */ /* GxsGroupFrameDialog */
virtual QString text(TextType type); virtual QString text(TextType type) override;
virtual QString icon(IconType type); virtual QString icon(IconType type) override;
virtual QString settingsGroupName() { return "ChannelDialog"; } virtual QString settingsGroupName() override { return "ChannelDialog"; }
virtual GxsGroupDialog *createNewGroupDialog(); virtual GxsGroupDialog *createNewGroupDialog() override;
virtual GxsGroupDialog *createGroupDialog(GxsGroupDialog::Mode mode, RsGxsGroupId groupId); virtual GxsGroupDialog *createGroupDialog(GxsGroupDialog::Mode mode, RsGxsGroupId groupId) override;
virtual int shareKeyType(); virtual int shareKeyType() override;
virtual GxsMessageFrameWidget *createMessageFrameWidget(const RsGxsGroupId &groupId); virtual GxsMessageFrameWidget *createMessageFrameWidget(const RsGxsGroupId &groupId) override;
virtual void groupTreeCustomActions(RsGxsGroupId grpId, int subscribeFlags, QList<QAction*> &actions); virtual void groupTreeCustomActions(RsGxsGroupId grpId, int subscribeFlags, QList<QAction*> &actions) override;
virtual RsGxsCommentService *getCommentService(); virtual RsGxsCommentService *getCommentService() override;
virtual QWidget *createCommentHeaderWidget(const RsGxsGroupId &grpId, const RsGxsMessageId &msgId); virtual QWidget *createCommentHeaderWidget(const RsGxsGroupId &grpId, const RsGxsMessageId &msgId) override;
virtual uint32_t requestGroupSummaryType() { return GXS_REQUEST_TYPE_GROUP_DATA; } // request complete group data virtual uint32_t requestGroupSummaryType() override { return GXS_REQUEST_TYPE_GROUP_DATA; } // request complete group data
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event); void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);

View File

@ -306,7 +306,7 @@ void RsGxsChannelPostFilesModel::setFilter(const QStringList& strings, uint32_t&
if(strings.empty()) if(strings.empty())
{ {
mFilteredFiles.clear(); mFilteredFiles.clear();
for(int i=0;i<mFiles.size();++i) for(uint32_t i=0;i<mFiles.size();++i)
mFilteredFiles.push_back(i); mFilteredFiles.push_back(i);
} }
else else
@ -314,7 +314,7 @@ void RsGxsChannelPostFilesModel::setFilter(const QStringList& strings, uint32_t&
mFilteredFiles.clear(); mFilteredFiles.clear();
//mFilteredPosts.push_back(0); //mFilteredPosts.push_back(0);
for(int i=0;i<mFiles.size();++i) for(uint32_t i=0;i<mFiles.size();++i)
{ {
bool passes_strings = true; bool passes_strings = true;

View File

@ -0,0 +1,324 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.cpp *
* *
* Copyright 2020 by Retroshare Team <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 <QWheelEvent>
#include <QDateTime>
#include "gui/common/FilesDefs.h"
#include "gui/gxschannels/GxsChannelPostThumbnail.h"
const float ChannelPostThumbnailView::DEFAULT_SIZE_IN_FONT_HEIGHT = 5.0;
const float ChannelPostThumbnailView::FONT_SCALE_FACTOR = 1.5;
ChannelPostThumbnailView::ChannelPostThumbnailView(const RsGxsChannelPost& post,uint32_t flags,QWidget *parent)
: QWidget(parent),mPostTitle(nullptr),mFlags(flags), mAspectRatio(ASPECT_RATIO_2_3)
{
// now fill the data
init(post);
}
ChannelPostThumbnailView::ChannelPostThumbnailView(QWidget *parent,uint32_t flags)
: QWidget(parent),mFlags(flags), mAspectRatio(ASPECT_RATIO_2_3)
{
init(RsGxsChannelPost());
}
ChannelPostThumbnailView::~ChannelPostThumbnailView()
{
delete mPostImage;
}
void ChannelPostThumbnailView::setText(const QString& s)
{
if(mPostTitle == NULL)
{
std::cerr << "(EE) calling setText on a ChannelPostThumbnailView without SHOW_TEXT flag!"<< std::endl;
return;
}
QString ss;
if(s.length() > 30)
ss = s.left(30)+"...";
else
ss =s;
mPostTitle->setText(ss);
}
void ChannelPostThumbnailView::setPixmap(const QPixmap& p, bool guess_aspect_ratio)
{
mPostImage->setPicture(p);
if(guess_aspect_ratio)// aspect ratio is automatically guessed.
{
// compute closest aspect ratio
float r = p.width()/(float)p.height();
if(r < 0.8)
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_2_3);
else if(r < 1.15)
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_1_1);
else
setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_16_9);
}
}
void ChannelPostThumbnailView::setAspectRatio(AspectRatio r)
{
mAspectRatio = r;
QFontMetricsF fm(font());
int W = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_w() * fm.height() ;
int H = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_h() * fm.height() ;
mPostImage->setFixedSize(W,H);
mPostImage->reset();
mPostImage->updateView();
}
void ChannelPostThumbnailView::init(const RsGxsChannelPost& post)
{
QString msg = QString::fromUtf8(post.mMeta.mMsgName.c_str());
bool is_msg_new = IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus);
QPixmap thumbnail;
if(post.mThumbnail.mSize > 0)
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, thumbnail,GxsIdDetails::ORIGINAL);
else if(post.mMeta.mPublishTs > 0) // this is for testing that the post is not an empty post (happens at the end of the last row)
thumbnail = FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE);
mPostImage = new ZoomableLabel(this);
mPostImage->setEnableZoom(mFlags & FLAG_ALLOW_PAN);
mPostImage->setScaledContents(true);
mPostImage->setPicture(thumbnail);
if(mFlags & FLAG_ALLOW_PAN)
mPostImage->setToolTip(tr("Use mouse to center and zoom into the image"));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(mPostImage);
QFontMetricsF fm(font());
int W = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_w() * fm.height() ;
int H = THUMBNAIL_OVERSAMPLE_FACTOR * thumbnail_h() * fm.height() ;
mPostImage->setFixedSize(W,H);
if(mFlags & FLAG_SHOW_TEXT)
{
mPostTitle = new QLabel(this);
layout->addWidget(mPostTitle);
QString ss = (msg.length() > 30)? (msg.left(30)+"..."):msg;
mPostTitle->setText(ss);
QFont font = mPostTitle->font();
if(mFlags & ChannelPostThumbnailView::FLAG_SCALE_FONT)
font.setPointSizeF(FONT_SCALE_FACTOR * DEFAULT_SIZE_IN_FONT_HEIGHT / 5.0 * font.pointSizeF());
else
font.setPointSizeF(DEFAULT_SIZE_IN_FONT_HEIGHT / 5.0 * font.pointSizeF());
if(is_msg_new)
font.setBold(true);
mPostTitle->setFont(font);
mPostTitle->setMaximumWidth(W);
mPostTitle->setWordWrap(true);
}
setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding);
layout->addStretch();
setLayout(layout);
adjustSize();
update();
}
ChannelPostThumbnailView::AspectRatio ChannelPostThumbnailView::bestAspectRatio()
{
if(mPostImage->originalImage().isNull())
return ASPECT_RATIO_1_1;
float as = mPostImage->originalImage().height() / (float)mPostImage->originalImage().width() ;
if(as <= 0.8)
return ASPECT_RATIO_16_9;
else if(as < 1.15)
return ASPECT_RATIO_1_1;
else
return ASPECT_RATIO_2_3;
}
QSize ChannelPostThumbnailView::actualSize() const
{
QFontMetricsF fm(font());
if(mPostTitle != nullptr)
{
QMargins cm = layout()->contentsMargins();
return QSize(width(),
mPostTitle->height() + mPostImage->height() + 0.5*fm.height()
+ cm.top() + cm.bottom() + layout()->spacing());
}
else
return size();
}
float ChannelPostThumbnailView::thumbnail_w() const
{
switch(mAspectRatio)
{
default:
case ASPECT_RATIO_1_1:
case ASPECT_RATIO_UNKNOWN: return DEFAULT_SIZE_IN_FONT_HEIGHT;
case ASPECT_RATIO_2_3: return DEFAULT_SIZE_IN_FONT_HEIGHT;
case ASPECT_RATIO_16_9: return DEFAULT_SIZE_IN_FONT_HEIGHT;
}
}
float ChannelPostThumbnailView::thumbnail_h() const
{
switch(mAspectRatio)
{
default:
case ASPECT_RATIO_1_1:
case ASPECT_RATIO_UNKNOWN: return DEFAULT_SIZE_IN_FONT_HEIGHT;
case ASPECT_RATIO_2_3: return DEFAULT_SIZE_IN_FONT_HEIGHT * 3.0/2.0;
case ASPECT_RATIO_16_9: return DEFAULT_SIZE_IN_FONT_HEIGHT * 9.0/16.0;
}
}
void ZoomableLabel::reset()
{
mCenterX = mFullImage.width()/2.0;
mCenterY = mFullImage.height()/2.0;
mZoomFactor = 1.0/std::max(width() / (float)mFullImage.width(), height()/(float)mFullImage.height());
updateView();
}
void ZoomableLabel::mouseMoveEvent(QMouseEvent *me)
{
if(!mZoomEnabled)
return;
float new_center_x = mCenterX - (me->x() - mLastX);
float new_center_y = mCenterY - (me->y() - mLastY);
mLastX = me->x();
mLastY = me->y();
if(new_center_x - 0.5 * width()*mZoomFactor < 0) return;
if(new_center_y - 0.5 *height()*mZoomFactor < 0) return;
if(new_center_x + 0.5 * width()*mZoomFactor >= mFullImage.width()) return;
if(new_center_y + 0.5 *height()*mZoomFactor >=mFullImage.height()) return;
mCenterX = new_center_x;
mCenterY = new_center_y;
updateView();
}
void ZoomableLabel::mousePressEvent(QMouseEvent *me)
{
mMoving = true;
mLastX = me->x();
mLastY = me->y();
}
void ZoomableLabel::mouseReleaseEvent(QMouseEvent *)
{
mMoving = false;
}
void ZoomableLabel::wheelEvent(QWheelEvent *me)
{
if(!mZoomEnabled)
return;
float new_zoom_factor = (me->delta() > 0)?(mZoomFactor*1.05):(mZoomFactor/1.05);
float new_center_x = mCenterX;
float new_center_y = mCenterY;
// Try to find centerX and centerY so that the crop does not overlap the original image
float min_x = 0.5 * width()*new_zoom_factor;
float max_x = mFullImage.width() - 0.5 * width()*new_zoom_factor;
float min_y = 0.5 * height()*new_zoom_factor;
float max_y = mFullImage.height() - 0.5 * height()*new_zoom_factor;
if(min_x >= max_x) return;
if(min_y >= max_y) return;
if(new_center_x < min_x) new_center_x = min_x;
if(new_center_y < min_y) new_center_y = min_y;
if(new_center_x > max_x) new_center_x = max_x;
if(new_center_y > max_y) new_center_y = max_y;
mZoomFactor = new_zoom_factor;
mCenterX = new_center_x;
mCenterY = new_center_y;
updateView();
}
QPixmap ZoomableLabel::extractCroppedScaledPicture() const
{
QRect rect(mCenterX - 0.5 * width()*mZoomFactor, mCenterY - 0.5 * height()*mZoomFactor, width()*mZoomFactor, height()*mZoomFactor);
QPixmap pix = mFullImage.copy(rect).scaledToHeight(height(),Qt::SmoothTransformation);
return pix;
}
void ZoomableLabel::setPicture(const QPixmap& pix)
{
mFullImage = pix;
reset();
updateView();
}
void ZoomableLabel::resizeEvent(QResizeEvent *e)
{
QLabel::resizeEvent(e);
updateView();
}
void ZoomableLabel::updateView()
{
// The new image will be cropped from the original image, using the following rules:
// - first the cropped image size is computed
// - then center is calculated so that
// - the original center is preferred
// - if the crop overlaps the image border, the center is moved.
QRect rect(mCenterX - 0.5 * width()*mZoomFactor, mCenterY - 0.5 * height()*mZoomFactor, width()*mZoomFactor, height()*mZoomFactor);
QLabel::setPixmap(mFullImage.copy(rect));
}

View File

@ -32,6 +32,39 @@
#include "gui/gxs/GxsIdDetails.h" #include "gui/gxs/GxsIdDetails.h"
#include "gui/common/FilesDefs.h" #include "gui/common/FilesDefs.h"
// Class to provide a label in which the image can be zoomed/moved. The widget size is fixed by the GUI and the user can move/zoom the image
// inside the window formed by the widget. When happy, the view-able part of the image can be extracted.
class ZoomableLabel: public QLabel
{
public:
ZoomableLabel(QWidget *parent): QLabel(parent),mZoomFactor(1.0),mCenterX(0.0),mCenterY(0.0),mZoomEnabled(true) {}
void setPicture(const QPixmap& pix);
void setEnableZoom(bool b) { mZoomEnabled = b; }
void reset();
QPixmap extractCroppedScaledPicture() const;
void updateView();
const QPixmap& originalImage() const { return mFullImage ; }
protected:
void mousePressEvent(QMouseEvent *ev) override;
void mouseReleaseEvent(QMouseEvent *ev) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void resizeEvent(QResizeEvent *ev) override;
void wheelEvent(QWheelEvent *me) override;
QPixmap mFullImage;
float mCenterX;
float mCenterY;
float mZoomFactor;
int mLastX,mLastY;
bool mMoving;
bool mZoomEnabled;
};
// Class to paint the thumbnails with title // Class to paint the thumbnails with title
class ChannelPostThumbnailView: public QWidget class ChannelPostThumbnailView: public QWidget
@ -39,8 +72,20 @@ class ChannelPostThumbnailView: public QWidget
Q_OBJECT Q_OBJECT
public: public:
typedef enum {
ASPECT_RATIO_UNKNOWN = 0x00,
ASPECT_RATIO_2_3 = 0x01,
ASPECT_RATIO_1_1 = 0x02,
ASPECT_RATIO_16_9 = 0x03,
} AspectRatio;
// This variable determines the zoom factor on the text below thumbnails. 2.0 is mostly correct for all screen. // This variable determines the zoom factor on the text below thumbnails. 2.0 is mostly correct for all screen.
static constexpr float THUMBNAIL_OVERSAMPLE_FACTOR = 2.0; static constexpr float THUMBNAIL_OVERSAMPLE_FACTOR = 2.0;
static constexpr uint32_t FLAG_NONE = 0x00;
static constexpr uint32_t FLAG_SHOW_TEXT = 0x01;
static constexpr uint32_t FLAG_ALLOW_PAN = 0x02;
static constexpr uint32_t FLAG_SCALE_FONT= 0x04;
// Size of thumbnails as a function of the height of the font. An aspect ratio of 3/4 is good. // Size of thumbnails as a function of the height of the font. An aspect ratio of 3/4 is good.
@ -49,77 +94,40 @@ public:
static constexpr char *CHAN_DEFAULT_IMAGE = ":images/thumb-default-video.png"; static constexpr char *CHAN_DEFAULT_IMAGE = ":images/thumb-default-video.png";
virtual ~ChannelPostThumbnailView() virtual ~ChannelPostThumbnailView();
{ ChannelPostThumbnailView(QWidget *parent=NULL,uint32_t flags=FLAG_ALLOW_PAN | FLAG_SHOW_TEXT | FLAG_SCALE_FONT);
delete lb; ChannelPostThumbnailView(const RsGxsChannelPost& post,uint32_t flags,QWidget *parent=NULL);
delete lt;
}
ChannelPostThumbnailView(QWidget *parent=NULL): QWidget(parent) void init(const RsGxsChannelPost& post);
{
init(FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE), QString("New post"),false);
}
ChannelPostThumbnailView(const RsGxsChannelPost& post,QWidget *parent=NULL) void setAspectRatio(AspectRatio r);
: QWidget(parent) void setPixmap(const QPixmap& p,bool guess_aspect_ratio) ;
{ QPixmap getCroppedScaledPicture() const { return mPostImage->extractCroppedScaledPicture() ; }
// now fill the data
QPixmap thumbnail; void setText(const QString& s);
if(post.mThumbnail.mSize > 0) // This is used to allow to render the widget into a pixmap without the white space that Qt adds vertically. There is *no way* apparently
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, thumbnail,GxsIdDetails::ORIGINAL); // to get rid of that bloody space. It depends on the aspect ratio of the image and it only shows up when the text label is shown.
else if(post.mMeta.mPublishTs > 0) // this is for testing that the post is not an empty post (happens at the end of the last row) // The label however has a correct size. It seems that Qt doesn't like widgets with horizontal aspect ratio and forces the size accordingly.
thumbnail = FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE);
init(thumbnail, QString::fromUtf8(post.mMeta.mMsgName.c_str()), IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus) ); QSize actualSize() const ;
}
void init(const QPixmap& thumbnail,const QString& msg,bool is_msg_new)
{
QVBoxLayout *layout = new QVBoxLayout(this);
lb = new QLabel(this);
lb->setScaledContents(true);
layout->addWidget(lb);
lt = new QLabel(this);
layout->addWidget(lt);
setLayout(layout);
setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum);
QFontMetricsF fm(font());
int W = THUMBNAIL_OVERSAMPLE_FACTOR * THUMBNAIL_W * fm.height() ;
int H = THUMBNAIL_OVERSAMPLE_FACTOR * THUMBNAIL_H * fm.height() ;
lb->setFixedSize(W,H);
lb->setPixmap(thumbnail);
lt->setText(msg);
QFont font = lt->font();
if(is_msg_new)
{
font.setBold(true);
lt->setFont(font);
}
lt->setMaximumWidth(W);
lt->setWordWrap(true);
adjustSize();
update();
}
void setPixmap(const QPixmap& p) { lb->setPixmap(p); }
void setText(const QString& s) { lt->setText(s); }
/*!
* \brief bestAspectRatio
* Computes the preferred aspect ratio for the image in the post. The default is 1:1.
* \return the prefered aspect ratio
*/
AspectRatio bestAspectRatio() ;
private: private:
QLabel *lb; static const float DEFAULT_SIZE_IN_FONT_HEIGHT ;
QLabel *lt; static const float FONT_SCALE_FACTOR ;
float thumbnail_w() const;
float thumbnail_h() const;
ZoomableLabel *mPostImage;
QLabel *mPostTitle;
uint32_t mFlags;
AspectRatio mAspectRatio;
}; };

View File

@ -44,7 +44,7 @@ Q_DECLARE_METATYPE(RsGxsChannelPost)
std::ostream& operator<<(std::ostream& o, const QModelIndex& i);// defined elsewhere std::ostream& operator<<(std::ostream& o, const QModelIndex& i);// defined elsewhere
RsGxsChannelPostsModel::RsGxsChannelPostsModel(QObject *parent) RsGxsChannelPostsModel::RsGxsChannelPostsModel(QObject *parent)
: QAbstractItemModel(parent), mTreeMode(TREE_MODE_PLAIN), mColumns(6) : QAbstractItemModel(parent), mTreeMode(RsGxsChannelPostsModel::TREE_MODE_GRID), mColumns(6)
{ {
initEmptyHierarchy(); initEmptyHierarchy();
@ -62,6 +62,16 @@ RsGxsChannelPostsModel::~RsGxsChannelPostsModel()
rsEvents->unregisterEventsHandler(mEventHandlerId); rsEvents->unregisterEventsHandler(mEventHandlerId);
} }
void RsGxsChannelPostsModel::setMode(TreeMode mode)
{
mTreeMode = mode;
if(mode == TREE_MODE_LIST)
setNumColumns(2);
triggerViewUpdate();
}
void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEvent> event) void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEvent> event)
{ {
const RsGxsChannelEvent *e = dynamic_cast<const RsGxsChannelEvent*>(event.get()); const RsGxsChannelEvent *e = dynamic_cast<const RsGxsChannelEvent*>(event.get());
@ -106,7 +116,7 @@ void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr<const RsEve
{ {
mPosts[j] = posts[i]; mPosts[j] = posts[i];
emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredPosts.size(),mColumns-1,(void*)NULL)); triggerViewUpdate();
} }
} }
},this); },this);
@ -124,25 +134,23 @@ void RsGxsChannelPostsModel::initEmptyHierarchy()
mPosts.clear(); mPosts.clear();
mFilteredPosts.clear(); mFilteredPosts.clear();
// mPosts.resize(1); // adds a sentinel item
// mPosts[0].mMeta.mMsgName = "Root sentinel post" ;
// mFilteredPosts.resize(1);
// mFilteredPosts[0] = 1;
postMods(); postMods();
} }
void RsGxsChannelPostsModel::preMods() void RsGxsChannelPostsModel::preMods()
{ {
//emit layoutAboutToBeChanged(); //Generate SIGSEGV when click on button move next/prev.
beginResetModel(); beginResetModel();
} }
void RsGxsChannelPostsModel::postMods() void RsGxsChannelPostsModel::postMods()
{ {
endResetModel(); endResetModel();
emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredPosts.size(),mColumns-1,(void*)NULL)); triggerViewUpdate();
}
void RsGxsChannelPostsModel::triggerViewUpdate()
{
emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(rowCount()-1,mColumns-1,(void*)NULL));
} }
void RsGxsChannelPostsModel::getFilesList(std::list<ChannelPostFileInfo>& files) void RsGxsChannelPostsModel::getFilesList(std::list<ChannelPostFileInfo>& files)
@ -161,35 +169,30 @@ void RsGxsChannelPostsModel::getFilesList(std::list<ChannelPostFileInfo>& files)
files.push_back(it.second); files.push_back(it.second);
} }
void RsGxsChannelPostsModel::setFilter(const QStringList& strings, uint32_t& count) void RsGxsChannelPostsModel::setFilter(const QStringList& strings,bool only_unread, uint32_t& count)
{ {
preMods(); preMods();
beginRemoveRows(QModelIndex(),0,rowCount()-1); beginRemoveRows(QModelIndex(),0,rowCount()-1);
endRemoveRows(); endRemoveRows();
if(strings.empty()) mFilteredPosts.clear();
//mFilteredPosts.push_back(0);
for(size_t i=0;i<mPosts.size();++i)
{ {
mFilteredPosts.clear(); bool passes_strings = true;
for(size_t i=0;i<mPosts.size();++i)
for(auto& s:strings)
passes_strings = passes_strings && QString::fromStdString(mPosts[i].mMeta.mMsgName).contains(s,Qt::CaseInsensitive);
if(strings.empty())
passes_strings = true;
if(passes_strings && (!only_unread || (IS_MSG_UNREAD(mPosts[i].mMeta.mMsgStatus) || IS_MSG_NEW(mPosts[i].mMeta.mMsgStatus))))
mFilteredPosts.push_back(i); mFilteredPosts.push_back(i);
} }
else
{
mFilteredPosts.clear();
//mFilteredPosts.push_back(0);
for(size_t i=0;i<mPosts.size();++i)
{
bool passes_strings = true;
for(auto& s:strings)
passes_strings = passes_strings && QString::fromStdString(mPosts[i].mMeta.mMsgName).contains(s,Qt::CaseInsensitive);
if(passes_strings)
mFilteredPosts.push_back(i);
}
}
count = mFilteredPosts.size(); count = mFilteredPosts.size();
std::cerr << "After filtering: " << count << " posts remain." << std::endl; std::cerr << "After filtering: " << count << " posts remain." << std::endl;
@ -209,7 +212,12 @@ int RsGxsChannelPostsModel::rowCount(const QModelIndex& parent) const
return 0; return 0;
if(!parent.isValid()) if(!parent.isValid())
return (mFilteredPosts.size() + mColumns-1)/mColumns; // mFilteredPosts always has an item at 0, so size()>=1, and mColumn>=1 {
if(mTreeMode == TREE_MODE_GRID)
return (mFilteredPosts.size() + mColumns-1)/mColumns; // mFilteredPosts always has an item at 0, so size()>=1, and mColumn>=1
else
return mFilteredPosts.size();
}
RsErr() << __PRETTY_FUNCTION__ << " rowCount cannot figure out the porper number of rows." << std::endl; RsErr() << __PRETTY_FUNCTION__ << " rowCount cannot figure out the porper number of rows." << std::endl;
return 0; return 0;
@ -217,7 +225,10 @@ int RsGxsChannelPostsModel::rowCount(const QModelIndex& parent) const
int RsGxsChannelPostsModel::columnCount(const QModelIndex &/*parent*/) const int RsGxsChannelPostsModel::columnCount(const QModelIndex &/*parent*/) const
{ {
return std::min((int)mFilteredPosts.size(),(int)mColumns) ; if(mTreeMode == TREE_MODE_GRID)
return std::min((int)mFilteredPosts.size(),(int)mColumns) ;
else
return 2;
} }
bool RsGxsChannelPostsModel::getPostData(const QModelIndex& i,RsGxsChannelPost& fmpe) const bool RsGxsChannelPostsModel::getPostData(const QModelIndex& i,RsGxsChannelPost& fmpe) const
@ -284,7 +295,7 @@ QModelIndex RsGxsChannelPostsModel::index(int row, int column, const QModelIndex
if(row < 0 || column < 0 || column >= (int)mColumns) if(row < 0 || column < 0 || column >= (int)mColumns)
return QModelIndex(); return QModelIndex();
quintptr ref = getChildRef(parent.internalId(),column + row*mColumns); quintptr ref = getChildRef(parent.internalId(),(mTreeMode == TREE_MODE_GRID)?(column + row*mColumns):row);
#ifdef DEBUG_CHANNEL_MODEL #ifdef DEBUG_CHANNEL_MODEL
std::cerr << "index-3(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl; std::cerr << "index-3(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl;
@ -363,8 +374,6 @@ quintptr RsGxsChannelPostsModel::getParentRow(quintptr ref,int& row) const
int RsGxsChannelPostsModel::getChildrenCount(quintptr ref) const int RsGxsChannelPostsModel::getChildrenCount(quintptr ref) const
{ {
uint32_t entry = 0 ;
if(ref == quintptr(0)) if(ref == quintptr(0))
return rowCount()-1; return rowCount()-1;
@ -421,7 +430,7 @@ QVariant RsGxsChannelPostsModel::data(const QModelIndex &index, int role) const
} }
} }
QVariant RsGxsChannelPostsModel::sizeHintRole(int col) const QVariant RsGxsChannelPostsModel::sizeHintRole(int /* col */) const
{ {
float factor = QFontMetricsF(QApplication::font()).height()/14.0f ; float factor = QFontMetricsF(QApplication::font()).height()/14.0f ;
@ -493,7 +502,7 @@ void RsGxsChannelPostsModel::setPosts(const RsGxsChannelGroup& group, std::vecto
std::sort(mPosts.begin(),mPosts.end()); std::sort(mPosts.begin(),mPosts.end());
mFilteredPosts.clear(); mFilteredPosts.clear();
for(int i=0;i<mPosts.size();++i) for(uint32_t i=0;i<mPosts.size();++i)
mFilteredPosts.push_back(i); mFilteredPosts.push_back(i);
#ifdef DEBUG_CHANNEL_MODEL #ifdef DEBUG_CHANNEL_MODEL
@ -564,8 +573,6 @@ void RsGxsChannelPostsModel::update_posts(const RsGxsGroupId& group_id)
}); });
} }
static bool decreasing_time_comp(const std::pair<time_t,RsGxsMessageId>& e1,const std::pair<time_t,RsGxsMessageId>& e2) { return e2.first < e1.first ; }
void RsGxsChannelPostsModel::createPostsArray(std::vector<RsGxsChannelPost>& posts) void RsGxsChannelPostsModel::createPostsArray(std::vector<RsGxsChannelPost>& posts)
{ {
// collect new versions of posts if any // collect new versions of posts if any
@ -689,10 +696,25 @@ void RsGxsChannelPostsModel::setAllMsgReadStatus(bool read_status)
// No need to call preMods()/postMods() here because we're not changing the model // No need to call preMods()/postMods() here because we're not changing the model
// All operations below are done async // All operations below are done async
RsThread::async([this, read_status]() // 1 - copy all msg/grp id groups, so that changing the mPosts list while calling the async method will not break the work
std::vector<RsGxsGrpMsgIdPair> pairs;
for(uint32_t i=0;i<mPosts.size();++i)
{ {
for(uint32_t i=0;i<mPosts.size();++i) bool post_status = !((IS_MSG_UNREAD(mPosts[i].mMeta.mMsgStatus) || IS_MSG_NEW(mPosts[i].mMeta.mMsgStatus)));
rsGxsChannels->markRead(RsGxsGrpMsgIdPair(mPosts[i].mMeta.mGroupId,mPosts[i].mMeta.mMsgId),read_status);
if(post_status != read_status)
pairs.push_back(RsGxsGrpMsgIdPair(mPosts[i].mMeta.mGroupId,mPosts[i].mMeta.mMsgId));
}
// 2 - then call the async methods
RsThread::async([pairs, read_status]()
{
for(uint32_t i=0;i<pairs.size();++i)
if(!rsGxsChannels->markRead(pairs[i],read_status))
RsErr() << "setAllMsgReadStatus: failed to change status of msg " << pairs[i].first << " in group " << pairs[i].second << " to status " << read_status << std::endl;
}); });
} }
@ -720,15 +742,18 @@ QModelIndex RsGxsChannelPostsModel::getIndexOfMessage(const RsGxsMessageId& mid)
for(uint32_t i=0;i<mFilteredPosts.size();++i) for(uint32_t i=0;i<mFilteredPosts.size();++i)
{ {
// First look into msg versions, in case the msg is a version of an existing message // First look into msg versions, in case the msg is a version of an existing message
for(auto& msg_id:mPosts[mFilteredPosts[i]].mOlderVersions) for(auto& msg_id:mPosts[mFilteredPosts[i]].mOlderVersions)
if(msg_id == postId) if(msg_id == postId)
{ {
quintptr ref ; quintptr ref ;
convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab
return createIndex(i/mColumns,i%mColumns, ref); if(mTreeMode == TREE_MODE_GRID)
return createIndex(i/mColumns,i%mColumns, ref);
else
return createIndex(i,0, ref);
} }
if(mPosts[mFilteredPosts[i]].mMeta.mMsgId == postId) if(mPosts[mFilteredPosts[i]].mMeta.mMsgId == postId)
@ -736,7 +761,10 @@ QModelIndex RsGxsChannelPostsModel::getIndexOfMessage(const RsGxsMessageId& mid)
quintptr ref ; quintptr ref ;
convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab
return createIndex(i/mColumns,i%mColumns, ref); if(mTreeMode == TREE_MODE_GRID)
return createIndex(i/mColumns,i%mColumns, ref);
else
return createIndex(i,0, ref);
} }
} }

View File

@ -92,8 +92,8 @@ public:
#endif #endif
enum TreeMode{ TREE_MODE_UNKWN = 0x00, enum TreeMode{ TREE_MODE_UNKWN = 0x00,
TREE_MODE_PLAIN = 0x01, TREE_MODE_GRID = 0x01,
TREE_MODE_FILES = 0x02, TREE_MODE_LIST = 0x02,
}; };
#ifdef TODO #ifdef TODO
@ -107,11 +107,18 @@ public:
std::vector<std::pair<time_t,RsGxsMessageId> > getPostVersions(const RsGxsMessageId& mid) const; std::vector<std::pair<time_t,RsGxsMessageId> > getPostVersions(const RsGxsMessageId& mid) const;
uint32_t getNumberOfPosts() { return mPosts.size() ; }
const RsGxsChannelPost& post(uint32_t i) const { return mPosts[i]; }
// This method will asynchroneously update the data // This method will asynchroneously update the data
void updateChannel(const RsGxsGroupId& channel_group_id); void updateChannel(const RsGxsGroupId& channel_group_id);
const RsGxsGroupId& currentGroupId() const; const RsGxsGroupId& currentGroupId() const;
void triggerViewUpdate();
void setNumColumns(int n); void setNumColumns(int n);
void setMode(TreeMode mode);
TreeMode getMode() const { return mTreeMode; }
// Retrieve the full list of files for all posts. // Retrieve the full list of files for all posts.
@ -130,7 +137,7 @@ public:
void setMsgReadStatus(const QModelIndex &i, bool read_status); void setMsgReadStatus(const QModelIndex &i, bool read_status);
void setAllMsgReadStatus(bool read_status); void setAllMsgReadStatus(bool read_status);
void setFilter(const QStringList &strings, uint32_t &count) ; void setFilter(const QStringList &strings, bool only_unread,uint32_t &count) ;
#ifdef TODO #ifdef TODO
void setAuthorOpinion(const QModelIndex& indx,RsOpinion op); void setAuthorOpinion(const QModelIndex& indx,RsOpinion op);
@ -228,8 +235,6 @@ private:
std::vector<int> mFilteredPosts; // stores the list of displayes indices due to filtering. std::vector<int> mFilteredPosts; // stores the list of displayes indices due to filtering.
std::vector<RsGxsChannelPost> mPosts ; // store the list of posts updated from rsForums. std::vector<RsGxsChannelPost> mPosts ; // store the list of posts updated from rsForums.
//std::map<RsGxsMessageId,std::vector<std::pair<time_t,RsGxsMessageId> > > mPostVersions; // stores versions of posts
QColor mTextColorRead ; QColor mTextColorRead ;
QColor mTextColorUnread ; QColor mTextColorUnread ;
QColor mTextColorUnreadChildren; QColor mTextColorUnreadChildren;

View File

@ -61,6 +61,8 @@ static const int CHANNEL_TABS_DETAILS= 0;
static const int CHANNEL_TABS_POSTS = 1; static const int CHANNEL_TABS_POSTS = 1;
static const int CHANNEL_TABS_FILES = 2; static const int CHANNEL_TABS_FILES = 2;
QColor SelectedColor = QRgb(0xff308dc7);
/* View mode */ /* View mode */
#define VIEW_MODE_FEEDS 1 #define VIEW_MODE_FEEDS 1
#define VIEW_MODE_FILES 2 #define VIEW_MODE_FILES 2
@ -74,14 +76,23 @@ static const int CHANNEL_TABS_FILES = 2;
#define STAR_OVERLAY_IMAGE ":icons/star_overlay_128.png" #define STAR_OVERLAY_IMAGE ":icons/star_overlay_128.png"
#define IMAGE_COPYLINK ":/images/copyrslink.png" #define IMAGE_COPYLINK ":/images/copyrslink.png"
#define IMAGE_GRID_VIEW ":icons/png/menu.png"
#define IMAGE_DOWNLOAD ":icons/png/download.png"
Q_DECLARE_METATYPE(ChannelPostFileInfo) Q_DECLARE_METATYPE(ChannelPostFileInfo)
// Delegate used to paint into the table of thumbnails // Delegate used to paint into the table of thumbnails
int ChannelPostDelegate::cellSize(const QFont& font) const //===============================================================================================================================================//
//=== ChannelPostDelegate ===//
//===============================================================================================================================================//
int ChannelPostDelegate::cellSize(int col,const QFont& font,uint32_t parent_width) const
{ {
return mZoom*COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font).height(); if(mUseGrid || col==0)
return mZoom*COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font).height();
else
return 0.8*parent_width - mZoom*COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font).height();
} }
void ChannelPostDelegate::zoom(bool zoom_or_unzoom) void ChannelPostDelegate::zoom(bool zoom_or_unzoom)
@ -91,53 +102,106 @@ void ChannelPostDelegate::zoom(bool zoom_or_unzoom)
else else
mZoom /= 1.02; mZoom /= 1.02;
std::cerr << "zoom factor: " << mZoom << std::endl; if(mZoom < 0.5)
mZoom = 0.5;
if(mZoom > 2.0)
mZoom = 2.0;
} }
void ChannelPostDelegate::setAspectRatio(ChannelPostThumbnailView::AspectRatio r)
{
mAspectRatio = r;
}
void ChannelPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const void ChannelPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{ {
// prepare // prepare
painter->save(); painter->save();
painter->setClipRect(option.rect); painter->setClipRect(option.rect);
RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ; RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ;
painter->fillRect( option.rect, option.backgroundBrush); painter->fillRect( option.rect, option.backgroundBrush);
painter->restore(); painter->restore();
ChannelPostThumbnailView w(post); if(mUseGrid || index.column()==0)
{
// Draw a thumbnail
QPixmap pixmap(w.size()); uint32_t flags = (mUseGrid)?(ChannelPostThumbnailView::FLAG_SHOW_TEXT | ChannelPostThumbnailView::FLAG_SCALE_FONT):0;
ChannelPostThumbnailView w(post,flags);
w.setAspectRatio(mAspectRatio);
w.updateGeometry();
w.adjustSize();
if((option.state & QStyle::State_Selected) && post.mMeta.mPublishTs > 0) // check if post is selected and is not empty (end of last row) QPixmap pixmap(w.size());
pixmap.fill(QRgb(0xff308dc7)); // I dont know how to grab the backgroud color for selected objects automatically.
else
pixmap.fill(QRgb(0x00ffffff)); // choose a fully transparent background
w.render(&pixmap,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background 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(SelectedColor); // I dont know how to grab the backgroud color for selected objects automatically.
else
pixmap.fill(QRgb(0x00ffffff)); // choose a fully transparent background
if(mZoom != 1.0) w.render(&pixmap,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background
pixmap = pixmap.scaled(mZoom*pixmap.size(),Qt::KeepAspectRatio,Qt::SmoothTransformation);
if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus)) // We extract from the pixmap the part of the widget that we want. Saddly enough, Qt adds some white space
{ // below the widget and there is no way to control that.
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));
}
// debug pixmap = pixmap.copy(QRect(0,0,w.actualSize().width(),w.actualSize().height()));
// 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->drawPixmap(option.rect.topLeft(), // if(index.row()==0 && index.column()==0)
pixmap.scaled(option.rect.width(),option.rect.width()*w.height()/(float)w.width(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation)); // {
// QFile file("yourFile.png");
// file.open(QIODevice::WriteOnly);
// pixmap.save(&file, "PNG");
// file.close();
// }
if(mUseGrid || index.column()==0)
{
if(mZoom != 1.0)
pixmap = pixmap.scaled(mZoom*pixmap.size(),Qt::KeepAspectRatio,Qt::SmoothTransformation);
if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus))
{
QPainter p(&pixmap);
QFontMetricsF fm(option.font);
p.drawPixmap(mZoom*QPoint(0.1*fm.height(),-3.6*fm.height()),FilesDefs::getPixmapFromQtResourcePath(STAR_OVERLAY_IMAGE).scaled(mZoom*7*fm.height(),mZoom*7*fm.height(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
}
}
painter->drawPixmap(option.rect.topLeft(),
pixmap.scaled(option.rect.width(),option.rect.width()*pixmap.height()/(float)pixmap.width(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation));
}
else
{
// We're drawing the text on the second column
uint32_t font_height = QFontMetricsF(option.font).height();
QPoint p = option.rect.topLeft();
float y = p.y() + font_height;
painter->save();
if((option.state & QStyle::State_Selected) && post.mMeta.mPublishTs > 0) // check if post is selected and is not empty (end of last row)
painter->setPen(SelectedColor);
if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus))
{
QFont font(option.font);
font.setBold(true);
painter->setFont(font);
}
painter->drawText(QPoint(p.x()+0.5*font_height,y),QString::fromUtf8(post.mMeta.mMsgName.c_str()));
y += font_height;
painter->drawText(QPoint(p.x()+0.5*font_height,y),QDateTime::fromSecsSinceEpoch(post.mMeta.mPublishTs).toString(Qt::DefaultLocaleShortDate));
y += font_height;
painter->drawText(QPoint(p.x()+0.5*font_height,y),QString::number(post.mCount)+ " " +((post.mCount>1)?tr("files"):tr("file")) + " (" + QString::number(post.mSize) + " " + tr("bytes") + ")" );
y += font_height;
painter->restore();
}
} }
QSize ChannelPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const QSize ChannelPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
@ -146,9 +210,36 @@ QSize ChannelPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QM
QFontMetricsF fm(option.font); QFontMetricsF fm(option.font);
return QSize(mZoom*COLUMN_SIZE_FONT_FACTOR_W*fm.height(),mZoom*COLUMN_SIZE_FONT_FACTOR_H*fm.height()); RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ;
uint32_t flags = (mUseGrid)?(ChannelPostThumbnailView::FLAG_SHOW_TEXT | ChannelPostThumbnailView::FLAG_SCALE_FONT):0;
ChannelPostThumbnailView w(post,flags);
w.setAspectRatio(mAspectRatio);
w.updateGeometry();
w.adjustSize();
//std::cerr << "w.size(): " << w.width() << " x " << w.height() << ". Actual size: " << w.actualSize().width() << " x " << w.actualSize().height() << std::endl;
float aspect_ratio = w.actualSize().height()/(float)w.actualSize().width();
float cell_width = mZoom*COLUMN_SIZE_FONT_FACTOR_W*fm.height();
float cell_height = mZoom*COLUMN_SIZE_FONT_FACTOR_W*fm.height()*aspect_ratio;
if(mUseGrid || index.column()==0)
return QSize(cell_width,cell_height);
else
return QSize(option.rect.width()-cell_width,cell_height);
} }
void ChannelPostDelegate::setWidgetGrid(bool use_grid)
{
mUseGrid = use_grid;
}
//===============================================================================================================================================//
//=== ChannelPostFilesDelegate ===//
//===============================================================================================================================================//
QWidget *ChannelPostFilesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const QWidget *ChannelPostFilesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const
{ {
ChannelPostFileInfo file = index.data(Qt::UserRole).value<ChannelPostFileInfo>() ; ChannelPostFileInfo file = index.data(Qt::UserRole).value<ChannelPostFileInfo>() ;
@ -222,6 +313,10 @@ QSize ChannelPostFilesDelegate::sizeHint(const QStyleOptionViewItem& option, con
} }
} }
//===============================================================================================================================================//
//=== GxsChannelPostWidgetWithModel ===//
//===============================================================================================================================================//
/** Constructor */ /** Constructor */
GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupId &channelId, QWidget *parent) : GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupId &channelId, QWidget *parent) :
GxsMessageFrameWidget(rsGxsChannels, parent), GxsMessageFrameWidget(rsGxsChannels, parent),
@ -230,13 +325,25 @@ GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupI
/* Invoke the Qt Designer generated object setup routine */ /* Invoke the Qt Designer generated object setup routine */
ui->setupUi(this); ui->setupUi(this);
ui->postsTree->setModel(mChannelPostsModel = new RsGxsChannelPostsModel()); ui->viewType_TB->setIcon(FilesDefs::getIconFromQtResourcePath(":icons/svg/gridlayout.svg"));
ui->viewType_TB->setToolTip(tr("Click to switch to list view"));
connect(ui->viewType_TB,SIGNAL(clicked()),this,SLOT(switchView()));
ui->showUnread_TB->setIcon(FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png"));
ui->showUnread_TB->setChecked(false);
ui->showUnread_TB->setToolTip(tr("Show unread posts only"));
connect(ui->showUnread_TB,SIGNAL(toggled(bool)),this,SLOT(switchOnlyUnread(bool)));
ui->postsTree->setModel(mChannelPostsModel = new RsGxsChannelPostsModel());
ui->postsTree->setItemDelegate(mChannelPostsDelegate = new ChannelPostDelegate()); ui->postsTree->setItemDelegate(mChannelPostsDelegate = new ChannelPostDelegate());
ui->postsTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // prevents bug on w10, since row size depends on widget width 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->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);// more beautiful if we scroll at pixel level
ui->postsTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); ui->postsTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
mChannelPostsDelegate->setAspectRatio(ChannelPostThumbnailView::ASPECT_RATIO_16_9);
connect(ui->postsTree,SIGNAL(zoomRequested(bool)),this,SLOT(updateZoomFactor(bool))); connect(ui->postsTree,SIGNAL(zoomRequested(bool)),this,SLOT(updateZoomFactor(bool)));
connect(ui->commentsDialog,SIGNAL(commentsLoaded(int)),this,SLOT(updateCommentsCount(int)));
ui->channelPostFiles_TV->setModel(mChannelPostFilesModel = new RsGxsChannelPostFilesModel(this)); ui->channelPostFiles_TV->setModel(mChannelPostFilesModel = new RsGxsChannelPostFilesModel(this));
ui->channelPostFiles_TV->setItemDelegate(new ChannelPostFilesDelegate()); ui->channelPostFiles_TV->setItemDelegate(new ChannelPostFilesDelegate());
@ -262,7 +369,7 @@ GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupI
ui->postTime_LB->hide(); ui->postTime_LB->hide();
ui->postLogo_LB->hide(); ui->postLogo_LB->hide();
ui->postDetails_TE->setPlaceholderText(tr("No post selected")); ui->postDetails_TE->setPlaceholderText(tr("No text to display"));
// Set initial size of the splitter // Set initial size of the splitter
ui->splitter->setStretchFactor(0, 1); ui->splitter->setStretchFactor(0, 1);
@ -270,8 +377,9 @@ GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupI
QFontMetricsF fm(font()); QFontMetricsF fm(font());
for(int i=0;i<mChannelPostsModel->columnCount();++i) if(mChannelPostsModel->getMode() == RsGxsChannelPostsModel::TREE_MODE_GRID)
ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(font())); for(int i=0;i<mChannelPostsModel->columnCount();++i)
ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(i,font(),ui->postsTree->width()));
/* Setup UI helper */ /* Setup UI helper */
@ -339,12 +447,11 @@ void GxsChannelPostsWidgetWithModel::updateZoomFactor(bool zoom_or_unzoom)
mChannelPostsDelegate->zoom(zoom_or_unzoom); mChannelPostsDelegate->zoom(zoom_or_unzoom);
for(int i=0;i<mChannelPostsModel->columnCount();++i) for(int i=0;i<mChannelPostsModel->columnCount();++i)
ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(font())); ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(i,font(),ui->postsTree->width()));
QSize s = ui->postsTree->size(); QSize s = ui->postsTree->size();
int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(font())))); int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(0,font(),s.width()))));
std::cerr << "nb columns: " << n_columns << std::endl;
mChannelPostsModel->setNumColumns(n_columns); // forces the update mChannelPostsModel->setNumColumns(n_columns); // forces the update
@ -366,7 +473,28 @@ void GxsChannelPostsWidgetWithModel::postContextMenu(const QPoint&)
{ {
QMenu menu(this); QMenu menu(this);
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); #ifdef TO_REMOVE
if(mChannelPostsModel->getMode() == RsGxsChannelPostsModel::TREE_MODE_GRID)
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_GRID_VIEW), tr("Switch to list view"), this, SLOT(switchView()));
else
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_GRID_VIEW), tr("Switch to grid view"), this, SLOT(switchView()));
menu.addSeparator();
#endif
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
if(index.isValid())
{
RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ;
if(!post.mFiles.empty())
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_DOWNLOAD), tr("Download files"), this, SLOT(download()));
if(!IS_MSG_UNREAD(post.mMeta.mMsgStatus) && !IS_MSG_NEW(post.mMeta.mMsgStatus))
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Mark as unread"), this, SLOT(markMessageUnread()));
}
menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink()));
if(IS_GROUP_PUBLISHER(mGroup.mMeta.mSubscribeFlags)) if(IS_GROUP_PUBLISHER(mGroup.mMeta.mSubscribeFlags))
menu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/edit_16.png"), tr("Edit"), this, SLOT(editPost())); menu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/edit_16.png"), tr("Edit"), this, SLOT(editPost()));
@ -374,6 +502,70 @@ void GxsChannelPostsWidgetWithModel::postContextMenu(const QPoint&)
menu.exec(QCursor::pos()); menu.exec(QCursor::pos());
} }
void GxsChannelPostsWidgetWithModel::markMessageUnread()
{
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
mChannelPostsModel->setMsgReadStatus(index,false);
}
RsGxsMessageId GxsChannelPostsWidgetWithModel::getCurrentItemId() const
{
RsGxsMessageId selected_msg_id ;
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
if(index.isValid())
selected_msg_id = index.data(Qt::UserRole).value<RsGxsChannelPost>().mMeta.mMsgId ;
return selected_msg_id;
}
void GxsChannelPostsWidgetWithModel::selectItem(const RsGxsMessageId& msg_id)
{
auto index = mChannelPostsModel->getIndexOfMessage(msg_id);
ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect);
ui->postsTree->scrollTo(index);//May change if model reloaded
}
void GxsChannelPostsWidgetWithModel::switchView()
{
auto msg_id = getCurrentItemId();
if(mChannelPostsModel->getMode() == RsGxsChannelPostsModel::TREE_MODE_GRID)
{
ui->viewType_TB->setIcon(FilesDefs::getIconFromQtResourcePath(":icons/svg/listlayout.svg"));
ui->viewType_TB->setToolTip(tr("Click to switch to grid view"));
ui->postsTree->setSelectionBehavior(QAbstractItemView::SelectRows);
mChannelPostsDelegate->setWidgetGrid(false);
mChannelPostsModel->setMode(RsGxsChannelPostsModel::TREE_MODE_LIST);
}
else
{
ui->viewType_TB->setIcon(FilesDefs::getIconFromQtResourcePath(":icons/svg/gridlayout.svg"));
ui->viewType_TB->setToolTip(tr("Click to switch to list view"));
ui->postsTree->setSelectionBehavior(QAbstractItemView::SelectItems);
mChannelPostsDelegate->setWidgetGrid(true);
mChannelPostsModel->setMode(RsGxsChannelPostsModel::TREE_MODE_GRID);
handlePostsTreeSizeChange(ui->postsTree->size(),true);
}
for(int i=0;i<mChannelPostsModel->columnCount();++i)
ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(i,font(),ui->postsTree->width()));
selectItem(msg_id);
ui->postsTree->setFocus();
mChannelPostsModel->triggerViewUpdate(); // This is already called by setMode(), but the model cannot know how many
// columns is actually has until we call handlePostsTreeSizeChange(), so
// we have to call it again here.
}
void GxsChannelPostsWidgetWithModel::copyMessageLink() void GxsChannelPostsWidgetWithModel::copyMessageLink()
{ {
try try
@ -405,6 +597,30 @@ void GxsChannelPostsWidgetWithModel::copyMessageLink()
QMessageBox::critical(NULL,tr("Link creation error"),tr("Link could not be created: ")+e.what()); QMessageBox::critical(NULL,tr("Link creation error"),tr("Link could not be created: ")+e.what());
} }
} }
void GxsChannelPostsWidgetWithModel::download()
{
QModelIndex index = ui->postsTree->selectionModel()->currentIndex();
RsGxsChannelPost post = index.data(Qt::UserRole).value<RsGxsChannelPost>() ;
std::string destination;
rsGxsChannels->getChannelDownloadDirectory(mGroup.mMeta.mGroupId,destination);
for(auto file:post.mFiles)
{
std::list<RsPeerId> sources;
std::string destination;
// Add possible direct sources.
FileInfo fileInfo;
rsFiles->FileDetails(file.mHash, RS_FILE_HINTS_REMOTE, fileInfo);
for(std::vector<TransferInfo>::const_iterator it = fileInfo.peers.begin(); it != fileInfo.peers.end(); ++it) {
sources.push_back((*it).peerId);
}
rsFiles->FileRequest(file.mName, file.mHash, file.mSize, destination, RS_FILE_REQ_ANONYMOUS_ROUTING, sources);
}
}
void GxsChannelPostsWidgetWithModel::editPost() void GxsChannelPostsWidgetWithModel::editPost()
{ {
@ -415,12 +631,15 @@ void GxsChannelPostsWidgetWithModel::editPost()
msgDialog->show(); msgDialog->show();
} }
void GxsChannelPostsWidgetWithModel::handlePostsTreeSizeChange(QSize s) void GxsChannelPostsWidgetWithModel::handlePostsTreeSizeChange(QSize s,bool force)
{ {
int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(font())))); if(mChannelPostsModel->getMode() != RsGxsChannelPostsModel::TREE_MODE_GRID)
std::cerr << "nb columns: " << n_columns << std::endl; return;
if(n_columns != mChannelPostsModel->columnCount()) int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(0,font(),ui->postsTree->width()))));
std::cerr << "nb columns: " << n_columns << " current count=" << mChannelPostsModel->columnCount() << std::endl;
if(force || (n_columns != mChannelPostsModel->columnCount()))
mChannelPostsModel->setNumColumns(n_columns); mChannelPostsModel->setNumColumns(n_columns);
} }
@ -461,10 +680,12 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
ui->postLogo_LB->hide(); ui->postLogo_LB->hide();
ui->postName_LB->hide(); ui->postName_LB->hide();
ui->postTime_LB->hide(); ui->postTime_LB->hide();
mChannelPostFilesModel->clear(); mChannelPostFilesModel->clear();
ui->details_TW->setEnabled(false);
return; return;
} }
ui->details_TW->setEnabled(true);
ui->postLogo_LB->show(); ui->postLogo_LB->show();
ui->postName_LB->show(); ui->postName_LB->show();
@ -506,7 +727,7 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
ui->postLogo_LB->setFixedSize(W,postImage.height()/(float)postImage.width()*W); 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->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->setText(QDateTime::fromMSecsSinceEpoch(post.mMeta.mPublishTs*1000).toString("MM/dd/yyyy, hh:mm"));
ui->postTime_LB->setFixedWidth(W); ui->postTime_LB->setFixedWidth(W);
@ -526,6 +747,13 @@ void GxsChannelPostsWidgetWithModel::showPostDetails()
} }
} }
void GxsChannelPostsWidgetWithModel::updateCommentsCount(int n)
{
if(n > 0)
ui->details_TW->setTabText(2,tr("Comments (%1)").arg(n));
else
ui->details_TW->setTabText(2,tr("Comments"));
}
void GxsChannelPostsWidgetWithModel::updateGroupData() void GxsChannelPostsWidgetWithModel::updateGroupData()
{ {
if(groupId().isNull()) if(groupId().isNull())
@ -553,6 +781,8 @@ void GxsChannelPostsWidgetWithModel::updateGroupData()
{ {
mGroup = group; mGroup = group;
mChannelPostsModel->updateChannel(groupId()); mChannelPostsModel->updateChannel(groupId());
whileBlocking(ui->filterLineEdit)->clear();
whileBlocking(ui->showUnread_TB)->setChecked(false);
insertChannelDetails(mGroup); insertChannelDetails(mGroup);
@ -585,10 +815,30 @@ void GxsChannelPostsWidgetWithModel::postChannelPostLoad()
mChannelPostsModel->getFilesList(files); mChannelPostsModel->getFilesList(files);
mChannelFilesModel->setFiles(files); mChannelFilesModel->setFiles(files);
//ui->channelFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_FILE);
//ui->channelFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE);
ui->channelFiles_TV->setAutoSelect(true); ui->channelFiles_TV->setAutoSelect(true);
ui->channelFiles_TV->sortByColumn(0, Qt::AscendingOrder);
ui->infoPosts->setText(QString::number(mChannelPostsModel->getNumberOfPosts()) + " / " + QString::number(mGroup.mMeta.mVisibleMsgCount));
// now compute aspect ratio for posts. We do that by looking at the 5 latest posts and compute the best aspect ratio for them.
std::vector<uint32_t> ar_votes(4,0);
for(uint32_t i=0;i<std::min(mChannelPostsModel->getNumberOfPosts(),5u);++i)
{
const RsGxsChannelPost& post = mChannelPostsModel->post(i);
ChannelPostThumbnailView v(post,ChannelPostThumbnailView::FLAG_SHOW_TEXT | ChannelPostThumbnailView::FLAG_SCALE_FONT);
++ar_votes[ static_cast<uint32_t>( v.bestAspectRatio() )];
}
int best=0;
for(uint32_t i=0;i<4;++i)
if(ar_votes[i] > ar_votes[best])
best = i;
mChannelPostsDelegate->setAspectRatio(static_cast<ChannelPostThumbnailView::AspectRatio>(best));
mChannelPostsModel->triggerViewUpdate();
handlePostsTreeSizeChange(ui->postsTree->size(),true); // force the update
} }
void GxsChannelPostsWidgetWithModel::updateDisplay(bool complete) void GxsChannelPostsWidgetWithModel::updateDisplay(bool complete)
@ -769,8 +1019,6 @@ void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGrou
rsGxsChannels->getChannelAutoDownload(group.mMeta.mGroupId,autoDownload); rsGxsChannels->getChannelAutoDownload(group.mMeta.mGroupId,autoDownload);
setAutoDownload(autoDownload); setAutoDownload(autoDownload);
RetroShareLink link;
if (IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags)) if (IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags))
{ {
ui->subscribeToolButton->setText(tr("Subscribed") + " " + QString::number(group.mMeta.mPop) ); ui->subscribeToolButton->setText(tr("Subscribed") + " " + QString::number(group.mMeta.mPop) );
@ -789,6 +1037,7 @@ void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGrou
ui->infoPosts->setText(QString::number(group.mMeta.mVisibleMsgCount)); ui->infoPosts->setText(QString::number(group.mMeta.mVisibleMsgCount));
if(group.mMeta.mLastPost==0) if(group.mMeta.mLastPost==0)
ui->infoLastPost->setText(tr("Never")); ui->infoLastPost->setText(tr("Never"));
else else
@ -808,8 +1057,11 @@ void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGrou
ui->infoAdministrator->setId(group.mMeta.mAuthorId) ; ui->infoAdministrator->setId(group.mMeta.mAuthorId) ;
link = RetroShareLink::createMessage(group.mMeta.mAuthorId, ""); if(!group.mMeta.mAuthorId.isNull())
ui->infoAdministrator->setText(link.toHtml()); {
RetroShareLink link = RetroShareLink::createMessage(group.mMeta.mAuthorId, "");
ui->infoAdministrator->setText(link.toHtml());
}
ui->infoCreated->setText(DateTime::formatLongDateTime(group.mMeta.mPublishTs)); ui->infoCreated->setText(DateTime::formatLongDateTime(group.mMeta.mPublishTs));
@ -850,6 +1102,7 @@ void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGrou
#endif #endif
ui->subscribeToolButton->setText(tr("Subscribe ") + " " + QString::number(group.mMeta.mPop) ); ui->subscribeToolButton->setText(tr("Subscribe ") + " " + QString::number(group.mMeta.mPop) );
showPostDetails();
} }
#ifdef TODO #ifdef TODO
@ -893,15 +1146,17 @@ void GxsChannelPostsWidgetWithModel::setViewMode(int viewMode)
#endif #endif
} }
void GxsChannelPostsWidgetWithModel::switchOnlyUnread(bool)
{
filterChanged(ui->filterLineEdit->text());
std::cerr << "Switched to unread/read"<< std::endl;
}
void GxsChannelPostsWidgetWithModel::filterChanged(QString s) void GxsChannelPostsWidgetWithModel::filterChanged(QString s)
{ {
QStringList ql = s.split(' ',QString::SkipEmptyParts); QStringList ql = s.split(' ',QString::SkipEmptyParts);
uint32_t count; uint32_t count;
mChannelPostsModel->setFilter(ql,count); mChannelPostsModel->setFilter(ql,ui->showUnread_TB->isChecked(),count);
mChannelFilesModel->setFilter(ql,count); mChannelFilesModel->setFilter(ql,count);
//mChannelPostFilesProxyModel->setFilterKeyColumn(RsGxsChannelPostFilesModel::COLUMN_FILES_NAME);
//mChannelPostFilesProxyModel->setFilterRegExp(s) ;// triggers a re-display. s is actually not used.
} }
#ifdef TODO #ifdef TODO
@ -1148,25 +1403,6 @@ public:
uint32_t mLastToken; uint32_t mLastToken;
}; };
#ifdef TO_REMOVE
static void setAllMessagesReadCallback(FeedItem *feedItem, void *data)
{
GxsChannelPostItem *channelPostItem = dynamic_cast<GxsChannelPostItem*>(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);
}
#endif
void GxsChannelPostsWidgetWithModel::setAllMessagesReadDo(bool read, uint32_t& /*token*/) void GxsChannelPostsWidgetWithModel::setAllMessagesReadDo(bool read, uint32_t& /*token*/)
{ {
if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags))

View File

@ -28,6 +28,8 @@
#include "gui/gxs/GxsMessageFramePostWidget.h" #include "gui/gxs/GxsMessageFramePostWidget.h"
#include "gui/feeds/FeedHolder.h" #include "gui/feeds/FeedHolder.h"
#include "GxsChannelPostThumbnail.h"
namespace Ui { namespace Ui {
class GxsChannelPostsWidgetWithModel; class GxsChannelPostsWidgetWithModel;
} }
@ -60,21 +62,26 @@ class ChannelPostDelegate: public QAbstractItemDelegate
Q_OBJECT Q_OBJECT
public: public:
ChannelPostDelegate(QObject *parent=0) : QAbstractItemDelegate(parent), mZoom(1.0){} ChannelPostDelegate(QObject *parent=0) : QAbstractItemDelegate(parent), mZoom(1.0), mUseGrid(true){}
virtual ~ChannelPostDelegate(){} virtual ~ChannelPostDelegate(){}
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
int cellSize(const QFont& font) const; int cellSize(int col, const QFont& font, uint32_t parent_width) const;
void zoom(bool zoom_or_unzoom) ; void zoom(bool zoom_or_unzoom) ;
private: void setWidgetGrid(bool use_grid) ;
void setAspectRatio(ChannelPostThumbnailView::AspectRatio r) ;
private:
static constexpr float IMAGE_MARGIN_FACTOR = 1.0; static constexpr float IMAGE_MARGIN_FACTOR = 1.0;
static constexpr float IMAGE_SIZE_FACTOR_W = 4.0 ; static constexpr float IMAGE_SIZE_FACTOR_W = 4.0 ;
static constexpr float IMAGE_SIZE_FACTOR_H = 6.0 ; static constexpr float IMAGE_SIZE_FACTOR_H = 6.0 ;
static constexpr float IMAGE_ZOOM_FACTOR = 1.0; static constexpr float IMAGE_ZOOM_FACTOR = 1.0;
float mZoom; // zoom factor for the whole thumbnail float mZoom; // zoom factor for the whole thumbnail
bool mUseGrid; // wether we use the grid widget or the list widget
ChannelPostThumbnailView::AspectRatio mAspectRatio;
}; };
class GxsChannelPostsWidgetWithModel: public GxsMessageFrameWidget class GxsChannelPostsWidgetWithModel: public GxsMessageFrameWidget
@ -135,25 +142,32 @@ protected:
private slots: private slots:
void showPostDetails(); void showPostDetails();
void updateGroupData(); void updateGroupData();
void createMsg(); void download();
void createMsg();
void toggleAutoDownload(); void toggleAutoDownload();
void subscribeGroup(bool subscribe); void subscribeGroup(bool subscribe);
void filterChanged(QString); void filterChanged(QString);
void setViewMode(int viewMode); void setViewMode(int viewMode);
void settingsChanged(); void settingsChanged();
void handlePostsTreeSizeChange(QSize s); void handlePostsTreeSizeChange(QSize s, bool force=false);
void postChannelPostLoad(); void postChannelPostLoad();
void editPost(); void editPost();
void postContextMenu(const QPoint&); void postContextMenu(const QPoint&);
void copyMessageLink(); void copyMessageLink();
void updateZoomFactor(bool zoom_or_unzoom); void updateZoomFactor(bool zoom_or_unzoom);
void switchView();
void switchOnlyUnread(bool b);
void markMessageUnread();
public slots: public slots:
void sortColumnFiles(int col,Qt::SortOrder so); void sortColumnFiles(int col,Qt::SortOrder so);
void sortColumnPostFiles(int col,Qt::SortOrder so); void sortColumnPostFiles(int col,Qt::SortOrder so);
void updateCommentsCount(int n);
private: private:
void processSettings(bool load); void processSettings(bool load);
RsGxsMessageId getCurrentItemId() const;
void selectItem(const RsGxsMessageId& msg_id);
void setAutoDownload(bool autoDl); void setAutoDownload(bool autoDl);
static bool filterItem(FeedItem *feedItem, const QString &text, int filter); static bool filterItem(FeedItem *feedItem, const QString &text, int filter);

View File

@ -120,6 +120,26 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="QToolButton" name="showUnread_TB">
<property name="text">
<string>...</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="viewType_TB">
<property name="text">
<string>...</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="viewModeLayout"> <layout class="QHBoxLayout" name="viewModeLayout">
<property name="spacing"> <property name="spacing">
@ -161,7 +181,7 @@
<item> <item>
<widget class="QTabWidget" name="channel_TW"> <widget class="QTabWidget" name="channel_TW">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="tab_3"> <widget class="QWidget" name="tab_3">
<attribute name="title"> <attribute name="title">
@ -199,26 +219,6 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="3">
<widget class="QLabel" name="infoLastPost">
<property name="text">
<string notr="true">unknown</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Created:</string>
</property>
</widget>
</item>
<item row="2" column="3"> <item row="2" column="3">
<widget class="GxsIdLabel" name="infoAdministrator"> <widget class="GxsIdLabel" name="infoAdministrator">
<property name="text"> <property name="text">
@ -229,26 +229,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Distribution:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="infoPosts">
<property name="text">
<string notr="true">0</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2"> <item row="1" column="1" colspan="2">
<widget class="QLabel" name="infoLastPostLabel"> <widget class="QLabel" name="infoLastPostLabel">
<property name="sizePolicy"> <property name="sizePolicy">
@ -268,8 +248,8 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="3"> <item row="4" column="3">
<widget class="QLabel" name="infoDistribution"> <widget class="QLabel" name="infoCreated">
<property name="text"> <property name="text">
<string>unknown</string> <string>unknown</string>
</property> </property>
@ -288,8 +268,48 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="3"> <item row="1" column="3">
<widget class="QLabel" name="infoCreated"> <widget class="QLabel" name="infoLastPost">
<property name="text">
<string notr="true">unknown</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Distribution:</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Created:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="infoPosts">
<property name="text">
<string notr="true">0</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLabel" name="infoDistribution">
<property name="text"> <property name="text">
<string>unknown</string> <string>unknown</string>
</property> </property>
@ -309,8 +329,11 @@
<bold>true</bold> <bold>true</bold>
</font> </font>
</property> </property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;justify&quot;&gt;The number of posts at friends is progressively updated.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;The new numbers will show next time you load this channel.&lt;/p&gt;&lt;p align=&quot;justify&quot;&gt;The two numbers differ depending on your friends synchronization&lt;/p&gt;&lt;p&gt;time limits as well as yours.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text"> <property name="text">
<string>Posts:</string> <string>Posts (locally / at friends):</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -412,9 +435,6 @@ p, li { white-space: pre-wrap; }
<string>Details</string> <string>Details</string>
</attribute> </attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="bottomMargin">
<number>3</number>
</property>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
@ -433,16 +453,6 @@ p, li { white-space: pre-wrap; }
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="postName_LB">
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="postTime_LB"> <widget class="QLabel" name="postTime_LB">
<property name="text"> <property name="text">
@ -466,11 +476,22 @@ p, li { white-space: pre-wrap; }
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTextBrowser" name="postDetails_TE"> <layout class="QVBoxLayout" name="verticalLayout_6">
<property name="openExternalLinks"> <item>
<bool>true</bool> <widget class="QLabel" name="postName_LB">
</property> <property name="text">
</widget> <string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QTextBrowser" name="postDetails_TE">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -526,7 +547,7 @@ p, li { white-space: pre-wrap; }
</widget> </widget>
<widget class="QWidget" name="tab_5"> <widget class="QWidget" name="tab_5">
<attribute name="title"> <attribute name="title">
<string>Files</string> <string>All files</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>

View File

@ -445,10 +445,13 @@ QVariant RsGxsForumModel::textColorRole(const ForumModelPostEntry& fmpe,int /*co
if( (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING)) if( (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_MISSING))
return QVariant(mTextColorMissing); return QVariant(mTextColorMissing);
if(IS_MSG_UNREAD(fmpe.mMsgStatus) || (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)) if(IS_MSG_UNREAD(fmpe.mMsgStatus))
return QVariant(mTextColorUnread); return QVariant(mTextColorUnread);
else else
return QVariant(mTextColorRead); if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
return QVariant(mTextColorPinned);
else
return QVariant(mTextColorRead);
return QVariant(); return QVariant();
} }
@ -593,10 +596,10 @@ QVariant RsGxsForumModel::pinnedRole(const ForumModelPostEntry& fmpe,int /*colum
QVariant RsGxsForumModel::backgroundRole(const ForumModelPostEntry& fmpe,int /*column*/) const QVariant RsGxsForumModel::backgroundRole(const ForumModelPostEntry& fmpe,int /*column*/) const
{ {
if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED) if(fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_IS_PINNED)
return QVariant(QBrush(QColor(255,200,180))); return QVariant(QBrush(mBackgroundColorPinned));
if(mFilteringEnabled && (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_PASSES_FILTER)) if(mFilteringEnabled && (fmpe.mPostFlags & ForumModelPostEntry::FLAG_POST_PASSES_FILTER))
return QVariant(QBrush(QColor(255,240,210))); return QVariant(QBrush(mBackgroundColorFiltered));
return QVariant(); return QVariant();
} }

View File

@ -116,6 +116,10 @@ public:
void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color;} void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color;}
void setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color;} void setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color;}
void setTextColorMissing (QColor color) { mTextColorMissing = color;} void setTextColorMissing (QColor color) { mTextColorMissing = color;}
void setTextColorPinned (QColor color) { mTextColorPinned = color;}
void setBackgroundColorPinned (QColor color) { mBackgroundColorPinned = color;}
void setBackgroundColorFiltered (QColor color) { mBackgroundColorFiltered = color;}
void setMsgReadStatus(const QModelIndex &i, bool read_status, bool with_children); void setMsgReadStatus(const QModelIndex &i, bool read_status, bool with_children);
void setFilter(int column, const QStringList &strings, uint32_t &count) ; void setFilter(int column, const QStringList &strings, uint32_t &count) ;
@ -202,6 +206,10 @@ private:
QColor mTextColorUnreadChildren; QColor mTextColorUnreadChildren;
QColor mTextColorNotSubscribed ; QColor mTextColorNotSubscribed ;
QColor mTextColorMissing ; QColor mTextColorMissing ;
QColor mTextColorPinned ;
QColor mBackgroundColorPinned;
QColor mBackgroundColorFiltered;
friend class const_iterator; friend class const_iterator;
}; };

View File

@ -230,6 +230,11 @@ void GxsForumThreadWidget::setTextColorUnread (QColor color) { mTextColor
void GxsForumThreadWidget::setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color; mThreadModel->setTextColorUnreadChildren(color);} void GxsForumThreadWidget::setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color; mThreadModel->setTextColorUnreadChildren(color);}
void GxsForumThreadWidget::setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color; mThreadModel->setTextColorNotSubscribed (color);} void GxsForumThreadWidget::setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color; mThreadModel->setTextColorNotSubscribed (color);}
void GxsForumThreadWidget::setTextColorMissing (QColor color) { mTextColorMissing = color; mThreadModel->setTextColorMissing (color);} void GxsForumThreadWidget::setTextColorMissing (QColor color) { mTextColorMissing = color; mThreadModel->setTextColorMissing (color);}
// Suppose to be different from unread one
void GxsForumThreadWidget::setTextColorPinned (QColor color) { mTextColorPinned = color; mThreadModel->setTextColorPinned (color);}
void GxsForumThreadWidget::setBackgroundColorPinned (QColor color) { mBackgroundColorPinned = color; mThreadModel->setBackgroundColorPinned (color);}
void GxsForumThreadWidget::setBackgroundColorFiltered(QColor color) { mBackgroundColorFiltered = color; mThreadModel->setBackgroundColorFiltered (color);}
GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent) : GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent) :
GxsMessageFrameWidget(rsGxsForums, parent), GxsMessageFrameWidget(rsGxsForums, parent),
@ -331,6 +336,9 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget
ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_MSGID); ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_MSGID);
ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_DATA); ttheader->hideSection (RsGxsForumModel::COLUMN_THREAD_DATA);
ttheader->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ttheader, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint)));
ui->progressBar->hide(); ui->progressBar->hide();
ui->progressText->hide(); ui->progressText->hide();
@ -343,6 +351,8 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget
// load settings // load settings
processSettings(true); processSettings(true);
mDisplayBannedText = false;
blankPost(); blankPost();
ui->subscribeToolButton->setToolTip(tr( "<p>Subscribing to the forum will gather \ ui->subscribeToolButton->setToolTip(tr( "<p>Subscribing to the forum will gather \
@ -611,6 +621,11 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
QAction* expandAll = new QAction(tr("Expand all"), &contextMnu); QAction* expandAll = new QAction(tr("Expand all"), &contextMnu);
connect(expandAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(expandAll())); connect(expandAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(expandAll()));
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
QAction* expandSubtree = new QAction(tr("Expand subtree"), &contextMnu);
connect(expandSubtree, SIGNAL(triggered()), this, SLOT(expandSubtree()));
#endif
QAction* collapseAll = new QAction(tr( "Collapse all"), &contextMnu); QAction* collapseAll = new QAction(tr( "Collapse all"), &contextMnu);
connect(collapseAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(collapseAll())); connect(collapseAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(collapseAll()));
@ -629,11 +644,19 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
QAction *showinpeopleAct = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/info16.png"), tr("Show author in people tab"), &contextMnu); QAction *showinpeopleAct = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/info16.png"), tr("Show author in people tab"), &contextMnu);
connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab())); connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab()));
bool has_children = false;
if (has_current_post) {
has_children = !current_post.mChildren.empty();
}
if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags))
{ {
markMsgAsReadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN); markMsgAsReadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_UNREAD_CHILDREN);
markMsgAsUnreadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_READ_CHILDREN); markMsgAsUnreadChildren->setEnabled(current_post.mPostFlags & ForumModelPostEntry::FLAG_POST_HAS_READ_CHILDREN);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
expandSubtree->setEnabled(has_children);
#endif
replyAct->setEnabled (true); replyAct->setEnabled (true);
replyauthorAct->setEnabled (true); replyauthorAct->setEnabled (true);
} }
@ -645,6 +668,22 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
markMsgAsUnreadChildren->setDisabled(true); markMsgAsUnreadChildren->setDisabled(true);
replyAct->setDisabled (true); replyAct->setDisabled (true);
replyauthorAct->setDisabled (true); replyauthorAct->setDisabled (true);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
expandSubtree->setDisabled(true);
expandSubtree->setVisible(false);
#endif
}
// disable visibility for childless
if (has_current_post) {
// still no setEnabled
markMsgAsRead->setVisible(IS_MSG_UNREAD(current_post.mMsgStatus));
markMsgAsUnread->setVisible(!IS_MSG_UNREAD(current_post.mMsgStatus));
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
expandSubtree->setVisible(has_children);
#endif
markMsgAsReadChildren->setVisible(has_children);
markMsgAsUnreadChildren->setVisible(has_children);
} }
if(has_current_post) if(has_current_post)
@ -678,7 +717,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
} }
contextMnu.addAction(replyAct); contextMnu.addAction(replyAct);
contextMnu.addAction(newthreadAct); contextMnu.addAction(newthreadAct);
QAction* action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); QAction* action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink()));
action->setEnabled(!groupId().isNull() && !mThreadId.isNull()); action->setEnabled(!groupId().isNull() && !mThreadId.isNull());
contextMnu.addSeparator(); contextMnu.addSeparator();
@ -688,6 +727,9 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
contextMnu.addAction(markMsgAsUnreadChildren); contextMnu.addAction(markMsgAsUnreadChildren);
contextMnu.addSeparator(); contextMnu.addSeparator();
contextMnu.addAction(expandAll); contextMnu.addAction(expandAll);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
contextMnu.addAction(expandSubtree);
#endif
contextMnu.addAction(collapseAll); contextMnu.addAction(collapseAll);
if(has_current_post) if(has_current_post)
@ -739,6 +781,64 @@ void GxsForumThreadWidget::contextMenuTextBrowser(QPoint point)
delete(contextMnu); delete(contextMnu);
} }
void GxsForumThreadWidget::headerContextMenuRequested(const QPoint &pos)
{
QMenu* header_context_menu = new QMenu(tr("Show column"), this);
QAction* title = header_context_menu->addAction(QIcon(), tr("Title"));
title->setCheckable(true);
title->setChecked(!ui->threadTreeWidget->isColumnHidden(RsGxsForumModel::COLUMN_THREAD_TITLE));
title->setData(RsGxsForumModel::COLUMN_THREAD_TITLE);
connect(title, SIGNAL(toggled(bool)), this, SLOT(changeHeaderColumnVisibility(bool)));
QAction* read = header_context_menu->addAction(QIcon(), tr("Read"));
read->setCheckable(true);
read->setChecked(!ui->threadTreeWidget->isColumnHidden(RsGxsForumModel::COLUMN_THREAD_READ));
read->setData(RsGxsForumModel::COLUMN_THREAD_READ);
connect(read, SIGNAL(toggled(bool)), this, SLOT(changeHeaderColumnVisibility(bool)));
QAction* date = header_context_menu->addAction(QIcon(), tr("Date"));
date->setCheckable(true);
date->setChecked(!ui->threadTreeWidget->isColumnHidden(RsGxsForumModel::COLUMN_THREAD_DATE));
date->setData(RsGxsForumModel::COLUMN_THREAD_DATE);
connect(date, SIGNAL(toggled(bool)), this, SLOT(changeHeaderColumnVisibility(bool)));
QAction* distribution = header_context_menu->addAction(QIcon(), tr("Distribution"));
distribution->setCheckable(true);
distribution->setChecked(!ui->threadTreeWidget->isColumnHidden(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION));
distribution->setData(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION);
connect(distribution, SIGNAL(toggled(bool)), this, SLOT(changeHeaderColumnVisibility(bool)));
// QAction* author = header_context_menu->addAction(QIcon(), tr("Author"));
// author->setCheckable(true);
// author->setChecked(!ui->threadTreeWidget->isColumnHidden(RsGxsForumModel::COLUMN_THREAD_AUTHOR));
// author->setData(RsGxsForumModel::COLUMN_THREAD_AUTHOR);
// connect(author, SIGNAL(toggled(bool)), this, SLOT(changeHeaderColumnVisibility(bool)));
QAction* show_text_from_banned = header_context_menu->addAction(QIcon(), tr("Show text from banned persons"));
show_text_from_banned->setCheckable(true);
show_text_from_banned->setChecked(mDisplayBannedText);
connect(show_text_from_banned, SIGNAL(toggled(bool)), this, SLOT(showBannedText(bool)));
header_context_menu->exec(mapToGlobal(pos));
delete(header_context_menu);
}
void GxsForumThreadWidget::changeHeaderColumnVisibility(bool visibility) {
QAction* the_action = qobject_cast<QAction*>(sender());
if ( !the_action ) {
return;
}
ui->threadTreeWidget->setColumnHidden(the_action->data().toInt(), !visibility);
}
void GxsForumThreadWidget::showBannedText(bool display) {
mDisplayBannedText = display;
if (!mThreadId.isNull()) {
updateMessageData(mThreadId);
}
}
#ifdef TODO #ifdef TODO
bool GxsForumThreadWidget::eventFilter(QObject *obj, QEvent *event) bool GxsForumThreadWidget::eventFilter(QObject *obj, QEvent *event)
{ {
@ -916,6 +1016,9 @@ void GxsForumThreadWidget::updateForumDescription(bool success)
if (!mThreadId.isNull()) if (!mThreadId.isNull())
return; return;
// still call it to not left leftovers from previous post if any
blankPost();
RsIdentityDetails details; RsIdentityDetails details;
rsIdentity->getIdDetails(mForumGroup.mMeta.mAuthorId,details); rsIdentity->getIdDetails(mForumGroup.mMeta.mAuthorId,details);
@ -1042,6 +1145,7 @@ void GxsForumThreadWidget::insertMessage()
/* blank text, incase we get nothing */ /* blank text, incase we get nothing */
blankPost(); blankPost();
ui->nextUnreadButton->setEnabled(true);
// We use this instead of getCurrentIndex() because right here the currentIndex() is not set yet. // We use this instead of getCurrentIndex() because right here the currentIndex() is not set yet.
@ -1157,7 +1261,8 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg)
rsReputations->overallReputationLevel(msg.mMeta.mAuthorId); rsReputations->overallReputationLevel(msg.mMeta.mAuthorId);
bool redacted = bool redacted =
(overall_reputation == RsReputationLevel::LOCALLY_NEGATIVE); (overall_reputation == RsReputationLevel::LOCALLY_NEGATIVE);
// TODO enabled even when there are no new message
ui->nextUnreadButton->setEnabled(true); ui->nextUnreadButton->setEnabled(true);
ui->lineLeft->show(); ui->lineLeft->show();
ui->time_label->setText(DateTime::formatLongDateTime(msg.mMeta.mPublishTs)); ui->time_label->setText(DateTime::formatLongDateTime(msg.mMeta.mPublishTs));
@ -1167,32 +1272,46 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg)
ui->by_label->show(); ui->by_label->show();
ui->threadTreeWidget->setFocus(); ui->threadTreeWidget->setFocus();
QString banned_text_info = "";
if(redacted) { if(redacted) {
QString extraTxt = tr( "<p><font color=\"#ff0000\"><b>The author of this message (with ID %1) is banned.</b>").arg(QString::fromStdString(msg.mMeta.mAuthorId.toStdString())) ; ui->downloadButton->setDisabled(true);
extraTxt += tr( "<UL><li><b><font color=\"#ff0000\">Messages from this author are not forwarded. </font></b></li>") ; if (!mDisplayBannedText) {
extraTxt += tr( "<li><b><font color=\"#ff0000\">Messages from this author are replaced by this text. </font></b></li></ul>") ; QString extraTxt = tr( "<p><font color=\"#ff0000\"><b>The author of this message (with ID %1) is banned.</b>").arg(QString::fromStdString(msg.mMeta.mAuthorId.toStdString())) ;
extraTxt += tr( "<p><b><font color=\"#ff0000\">You can force the visibility and forwarding of messages by setting a different opinion for that Id in People's tab.</font></b></p>") ; extraTxt += tr( "<UL><li><b><font color=\"#ff0000\">Messages from this author are not forwarded. </font></b></li>") ;
extraTxt += tr( "<li><b><font color=\"#ff0000\">Messages from this author are replaced by this text. </font></b></li></ul>") ;
extraTxt += tr( "<p><b><font color=\"#ff0000\">You can force the visibility and forwarding of messages by setting a different opinion for that Id in People's tab.</font></b></p>") ;
ui->postText->setHtml(extraTxt) ; ui->postText->setHtml(extraTxt) ;
} else { return;
uint32_t flags = RSHTML_FORMATTEXT_EMBED_LINKS; }
if(Settings->getForumLoadEmoticons()) else {
flags |= RSHTML_FORMATTEXT_EMBED_SMILEYS ; RsIdentityDetails details;
flags |= RSHTML_OPTIMIZEHTML_MASK; rsIdentity->getIdDetails(msg.mMeta.mAuthorId, details);
QString name = GxsIdDetails::getName(details);
QColor backgroundColor = ui->postText->palette().base().color(); banned_text_info += "<p><font color=\"#e00000\"><b>" + tr( "The author of this message (with ID %1) is banned. And named by name ( %2 )").arg(QString::fromStdString(msg.mMeta.mAuthorId.toStdString()), name) + "</b>";
qreal desiredContrast = Settings->valueFromGroup("Forum", banned_text_info += "<ul><li><b><font color=\"#e00000\">" + tr( "Messages from this author are not forwarded.") + "</font></b></li></ul>";
"MinimumContrast", 4.5).toDouble(); banned_text_info += "<p><b><font color=\"#e00000\">" + tr( "You can force the visibility and forwarding of messages by setting a different opinion for that Id in People's tab.") + "</font></b></p><hr>";
int desiredMinimumFontSize = Settings->valueFromGroup("Forum", }
"MinimumFontSize", 10).toInt();
QString extraTxt = RsHtml().formatText(ui->postText->document(),
QString::fromUtf8(msg.mMsg.c_str()), flags
, backgroundColor, desiredContrast, desiredMinimumFontSize
);
ui->postText->setHtml(extraTxt);
} }
uint32_t flags = RSHTML_FORMATTEXT_EMBED_LINKS;
if(Settings->getForumLoadEmoticons())
flags |= RSHTML_FORMATTEXT_EMBED_SMILEYS ;
flags |= RSHTML_OPTIMIZEHTML_MASK;
QColor backgroundColor = ui->postText->palette().base().color();
qreal desiredContrast = Settings->valueFromGroup("Forum",
"MinimumContrast", 4.5).toDouble();
int desiredMinimumFontSize = Settings->valueFromGroup("Forum",
"MinimumFontSize", 10).toInt();
QString extraTxt = banned_text_info + RsHtml().formatText(ui->postText->document(),
QString::fromUtf8(msg.mMsg.c_str()), flags
, backgroundColor, desiredContrast, desiredMinimumFontSize
);
ui->postText->setHtml(extraTxt);
QStringList urls; QStringList urls;
RsHtml::findAnchors(ui->postText->toHtml(), urls); RsHtml::findAnchors(ui->postText->toHtml(), urls);
ui->downloadButton->setEnabled(urls.count() > 0); ui->downloadButton->setEnabled(urls.count() > 0);
@ -1340,6 +1459,20 @@ void GxsForumThreadWidget::setAllMessagesReadDo(bool read, uint32_t &/*token*/)
markMsgAsReadUnread(read, true, true); markMsgAsReadUnread(read, true, true);
} }
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
void GxsForumThreadWidget::expandSubtree() {
QAction* the_action = qobject_cast<QAction*>(sender());
if (!the_action) {
return;
}
const QModelIndex current_index = ui->threadTreeWidget->currentIndex();
if (!current_index.isValid()) {
return;
}
ui->threadTreeWidget->expandRecursively(current_index);
}
#endif
bool GxsForumThreadWidget::navigate(const RsGxsMessageId &msgId) bool GxsForumThreadWidget::navigate(const RsGxsMessageId &msgId)
{ {
QModelIndex source_index = mThreadModel->getIndexOfMessage(msgId); QModelIndex source_index = mThreadModel->getIndexOfMessage(msgId);
@ -1683,10 +1816,26 @@ void GxsForumThreadWidget::filterItems(const QString& text)
// We do this in order to trigger a new filtering action in the proxy model. // We do this in order to trigger a new filtering action in the proxy model.
mThreadProxyModel->setFilterRegExp(QRegExp(QString(RsGxsForumModel::FilterString))) ; mThreadProxyModel->setFilterRegExp(QRegExp(QString(RsGxsForumModel::FilterString))) ;
if(!lst.empty()) if(!lst.empty())
ui->threadTreeWidget->expandAll(); ui->threadTreeWidget->expandAll();
else else {
// currentIndex() not on the clicked message, so not this way
// if (!mThreadId.isNull()) {
// an_index = mThreadProxyModel->mapToSource(ui->threadTreeWidget->currentIndex());
// }
ui->threadTreeWidget->collapseAll(); ui->threadTreeWidget->collapseAll();
if (!mThreadId.isNull()) {
// ...but this one
QModelIndex an_index = mThreadModel->getIndexOfMessage(mThreadId);
if (an_index.isValid()) {
QModelIndex the_index = mThreadProxyModel->mapFromSource(an_index);
ui->threadTreeWidget->setCurrentIndex(the_index);
ui->threadTreeWidget->scrollTo(the_index);
// don't change focus
// ui->threadTreeWidget->setFocus();
}
}
}
if(count > 0) if(count > 0)
ui->filterLineEdit->setToolTip(tr("No result.")) ; ui->filterLineEdit->setToolTip(tr("No result.")) ;

View File

@ -52,6 +52,10 @@ class GxsForumThreadWidget : public GxsMessageFrameWidget
Q_PROPERTY(QColor textColorUnreadChildren READ textColorUnreadChildren WRITE setTextColorUnreadChildren) Q_PROPERTY(QColor textColorUnreadChildren READ textColorUnreadChildren WRITE setTextColorUnreadChildren)
Q_PROPERTY(QColor textColorNotSubscribed READ textColorNotSubscribed WRITE setTextColorNotSubscribed) Q_PROPERTY(QColor textColorNotSubscribed READ textColorNotSubscribed WRITE setTextColorNotSubscribed)
Q_PROPERTY(QColor textColorMissing READ textColorMissing WRITE setTextColorMissing) Q_PROPERTY(QColor textColorMissing READ textColorMissing WRITE setTextColorMissing)
Q_PROPERTY(QColor textColorPinned READ textColorPinned WRITE setTextColorPinned)
Q_PROPERTY(QColor backgroundColorPinned READ backgroundColorPinned WRITE setBackgroundColorPinned)
Q_PROPERTY(QColor backgroundColorFiltered READ backgroundColorFiltered WRITE setBackgroundColorFiltered)
public: public:
explicit GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent = NULL); explicit GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent = NULL);
@ -62,12 +66,20 @@ public:
QColor textColorUnreadChildren() const { return mTextColorUnreadChildren; } QColor textColorUnreadChildren() const { return mTextColorUnreadChildren; }
QColor textColorNotSubscribed() const { return mTextColorNotSubscribed; } QColor textColorNotSubscribed() const { return mTextColorNotSubscribed; }
QColor textColorMissing() const { return mTextColorMissing; } QColor textColorMissing() const { return mTextColorMissing; }
QColor textColorPinned() const { return mTextColorPinned; }
QColor backgroundColorPinned() const { return mBackgroundColorPinned; }
QColor backgroundColorFiltered() const { return mBackgroundColorFiltered; }
void setTextColorRead (QColor color) ; void setTextColorRead (QColor color) ;
void setTextColorUnread (QColor color) ; void setTextColorUnread (QColor color) ;
void setTextColorUnreadChildren(QColor color) ; void setTextColorUnreadChildren(QColor color) ;
void setTextColorNotSubscribed (QColor color) ; void setTextColorNotSubscribed (QColor color) ;
void setTextColorMissing (QColor color) ; void setTextColorMissing (QColor color) ;
void setTextColorPinned (QColor color) ;
void setBackgroundColorPinned (QColor color);
void setBackgroundColorFiltered (QColor color);
/* GxsMessageFrameWidget */ /* GxsMessageFrameWidget */
virtual void groupIdChanged(); virtual void groupIdChanged();
@ -97,6 +109,7 @@ private slots:
/** Create the context popup menu and it's submenus */ /** Create the context popup menu and it's submenus */
void threadListCustomPopupMenu(QPoint point); void threadListCustomPopupMenu(QPoint point);
void contextMenuTextBrowser(QPoint point); void contextMenuTextBrowser(QPoint point);
void headerContextMenuRequested(const QPoint& pos);
void changedSelection(const QModelIndex &, const QModelIndex &); void changedSelection(const QModelIndex &, const QModelIndex &);
void changedThread(QModelIndex index); void changedThread(QModelIndex index);
@ -144,6 +157,12 @@ private slots:
void filterColumnChanged(int column); void filterColumnChanged(int column);
void filterItems(const QString &text); void filterItems(const QString &text);
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
void expandSubtree();
#endif
void changeHeaderColumnVisibility(bool visibility);
void showBannedText(bool display);
private: private:
void insertMessageData(const RsGxsForumMsg &msg); void insertMessageData(const RsGxsForumMsg &msg);
bool getCurrentPost(ForumModelPostEntry& fmpe) const ; bool getCurrentPost(ForumModelPostEntry& fmpe) const ;
@ -192,6 +211,7 @@ private:
GxsForumsFillThread *mFillThread; GxsForumsFillThread *mFillThread;
unsigned int mUnreadCount; unsigned int mUnreadCount;
unsigned int mNewCount; unsigned int mNewCount;
bool mDisplayBannedText;
/* Color definitions (for standard see qss.default) */ /* Color definitions (for standard see qss.default) */
QColor mTextColorRead; QColor mTextColorRead;
@ -199,6 +219,10 @@ private:
QColor mTextColorUnreadChildren; QColor mTextColorUnreadChildren;
QColor mTextColorNotSubscribed; QColor mTextColorNotSubscribed;
QColor mTextColorMissing; QColor mTextColorMissing;
QColor mTextColorPinned;
QColor mBackgroundColorPinned;
QColor mBackgroundColorFiltered;
RsGxsMessageId mNavigatePendingMsgId; RsGxsMessageId mNavigatePendingMsgId;
QList<RsGxsMessageId> mIgnoredMsgId; QList<RsGxsMessageId> mIgnoredMsgId;

View File

@ -2,8 +2,14 @@
<qresource prefix="/"> <qresource prefix="/">
<file>icons/onion.png</file> <file>icons/onion.png</file>
<file>icons/svg/hidden.svg</file> <file>icons/svg/hidden.svg</file>
<file>icons/svg/ratio-auto.svg</file>
<file>icons/svg/ratio-1-1.svg</file>
<file>icons/svg/ratio-3-4.svg</file>
<file>icons/svg/ratio-16-9.svg</file>
<file>icons/svg/randomness.svg</file> <file>icons/svg/randomness.svg</file>
<file>icons/svg/password.svg</file> <file>icons/svg/password.svg</file>
<file>icons/svg/listlayout.svg</file>
<file>icons/svg/gridlayout.svg</file>
<file>icons/stars/star0.png</file> <file>icons/stars/star0.png</file>
<file>icons/stars/star1.png</file> <file>icons/stars/star1.png</file>
<file>icons/stars/star2.png</file> <file>icons/stars/star2.png</file>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 35 35" style="enable-background:new 0 0 35 35;" xml:space="preserve" class=""><g transform="matrix(0.922745 0 0 0.922745 1.35197 1.35197)"><g>
<g>
<rect width="9.182" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
<rect y="19.5" width="9.182" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
<rect x="12.9" y="19.5" width="9.202" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
<rect x="12.9" width="9.202" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
<rect x="25.82" width="9.18" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
<rect x="25.82" y="19.5" width="9.18" height="15.5" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/>
</g>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" enable-background="new 0 0 512 512" height="512px" viewBox="0 0 512 512" width="512px" class=""><g transform="matrix(0.94148 0 0 0.94148 14.9811 14.9811)"><path d="m464.883 64.267h-417.766c-25.98 0-47.117 21.136-47.117 47.149 0 25.98 21.137 47.117 47.117 47.117h417.766c25.98 0 47.117-21.137 47.117-47.117 0-26.013-21.137-47.149-47.117-47.149z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/><path d="m464.883 208.867h-417.766c-25.98 0-47.117 21.136-47.117 47.149 0 25.98 21.137 47.117 47.117 47.117h417.766c25.98 0 47.117-21.137 47.117-47.117 0-26.013-21.137-47.149-47.117-47.149z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/><path d="m464.883 353.467h-417.766c-25.98 0-47.117 21.137-47.117 47.149 0 25.98 21.137 47.117 47.117 47.117h417.766c25.98 0 47.117-21.137 47.117-47.117 0-26.012-21.137-47.149-47.117-47.149z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#039BD5"/></g> </svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
height="512"
viewBox="0 -64 512 512"
width="512"
version="1.1"
id="svg18"
sodipodi:docname="ratio-1-1.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata24">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs22" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="705"
id="namedview20"
showgrid="true"
inkscape:zoom="0.65186406"
inkscape:cx="256"
inkscape:cy="342.77966"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g16">
<inkscape:grid
type="xygrid"
id="grid834" />
</sodipodi:namedview>
<g
transform="matrix(0.922745 0 0 0.922745 19.7773 14.833)"
id="g16">
<path
d="m 366.53972,252.68845 c -8.53125,0 -17.0664,-6.39844 -17.0664,-17.06641 v -93.86719 c 0,-8.53125 6.40234,-17.0664 17.0664,-17.0664 10.66797,0 14.9336,6.39843 14.9336,17.0664 v 96 c 0,8.53516 -6.39844,14.9336 -14.9336,14.9336 z m 0,0"
data-original="#000000"
class="active-path"
data-old_color="#000000"
style="fill:#039bd5"
id="path4"
inkscape:connector-curvature="0" />
<path
d="m277.332031 149.332031c0 11.785157-9.550781 21.335938-21.332031 21.335938s-21.332031-9.550781-21.332031-21.335938c0-11.78125 9.550781-21.332031 21.332031-21.332031s21.332031 9.550781 21.332031 21.332031zm0 0"
data-original="#000000"
class="active-path"
data-old_color="#000000"
style="fill:#039BD5"
id="path10" />
<path
d="m277.332031 234.667969c0 11.78125-9.550781 21.332031-21.332031 21.332031s-21.332031-9.550781-21.332031-21.332031c0-11.785157 9.550781-21.335938 21.332031-21.335938s21.332031 9.550781 21.332031 21.335938zm0 0"
data-original="#000000"
class="active-path"
data-old_color="#000000"
style="fill:#039BD5"
id="path12" />
<path
d="m454.398438 384h-394.664063c-34.132813 0-59.734375-25.601562-59.734375-57.601562v-266.664063c0-34.132813 25.601562-59.734375 59.734375-59.734375h394.664063c32 0 57.601562 25.601562 57.601562 59.734375v266.664063c0 32-25.601562 57.601562-57.601562 57.601562zm-394.664063-352c-14.933594 0-27.734375 12.800781-27.734375 27.734375v266.664063c0 14.933593 12.800781 27.734374 27.734375 27.734374h394.664063c14.933593 0 27.734374-12.800781 27.734374-27.734374v-266.664063c0-14.933594-12.800781-27.734375-27.734374-27.734375zm0 0"
data-original="#000000"
class="active-path"
data-old_color="#000000"
style="fill:#039BD5"
id="path14" />
<path
d="m 156.29747,252.68845 c -8.53125,0 -17.0664,-6.39844 -17.0664,-17.0664 v -93.8672 c 0,-8.53125 6.40234,-17.0664 17.0664,-17.0664 10.66797,0 14.9336,6.39843 14.9336,17.0664 v 96.00001 c 0,8.53515 -6.39844,14.93359 -14.9336,14.93359 z"
data-original="#000000"
class="active-path"
data-old_color="#000000"
style="fill:#039bd5;stroke-width:1"
id="path4-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="sssssss" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 -64 512 512" width="512" class=""><g transform="matrix(0.932065 0 0 0.932065 17.3913 13.0434)"><path d="m454.398438 384h-394.664063c-34.132813 0-59.734375-25.601562-59.734375-57.601562v-266.664063c0-34.132813 25.601562-59.734375 59.734375-59.734375h394.664063c32 0 57.601562 25.601562 57.601562 59.734375v266.664063c0 32-25.601562 57.601562-57.601562 57.601562zm-394.664063-352c-14.933594 0-27.734375 12.800781-27.734375 27.734375v266.664063c0 14.933593 12.800781 27.734374 27.734375 27.734374h394.664063c14.933593 0 27.734374-12.800781 27.734374-27.734374v-266.664063c0-14.933594-12.800781-27.734375-27.734374-27.734375zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/><path d="m177.066406 256h-10.667968c-21.332032 0-38.398438-17.066406-38.398438-38.398438v-53.335937c0-19.199219 17.066406-36.265625 38.398438-36.265625h32c8.535156 0 14.933593 6.398438 14.933593 17.066406 0 10.667969-6.398437 17.066406-17.066406 17.066406h-29.867187c-2.132813 0-6.398438 2.132813-6.398438 6.402344v10.664063h17.066406c21.332032 0 38.398438 17.066406 38.398438 38.402343v6.398438c-2.132813 14.933594-19.199219 32-38.398438 32zm-17.066406-46.933594v10.667969c0 2.132813 2.132812 6.398437 6.398438 6.398437h10.667968c2.132813 0 6.398438-2.132812 6.398438-6.398437v-6.402344c0-2.132812-2.132813-6.398437-6.398438-6.398437h-17.066406zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/><path d="m390.398438 256h-32c-8.53125 0-17.066407-6.398438-17.066407-17.066406 0-10.667969 6.402344-17.066406 17.066407-17.066406h32c2.136718 0 6.402343-2.132813 6.402343-6.402344v-10.664063h-17.066406c-21.335937 0-38.402344-17.066406-38.402344-38.402343v-6.398438c0-21.332031 17.066407-38.398438 38.402344-38.398438h10.664063c21.335937 0 38.402343 17.066407 38.402343 38.398438v53.332031c-2.132812 25.601563-19.199219 42.667969-38.402343 42.667969zm-10.664063-96c-2.132813 0-6.402344 2.132812-6.402344 6.398438v4.269531c0 2.132812 2.132813 6.398437 6.402344 6.398437h17.066406v-10.667968c0-2.132813-2.132812-6.398438-6.402343-6.398438zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/><path d="m91.734375 256c-8.535156 0-17.066406-6.398438-17.066406-17.066406v-93.867188c0-10.667968 6.398437-17.066406 17.066406-17.066406 10.664063 0 14.933594 6.398438 14.933594 17.066406v96c0 8.535156-6.402344 14.933594-14.933594 14.933594zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/><path d="m298.667969 149.332031c0 11.785157-9.550781 21.335938-21.335938 21.335938-11.78125 0-21.332031-9.550781-21.332031-21.335938 0-11.78125 9.550781-21.332031 21.332031-21.332031 11.785157 0 21.335938 9.550781 21.335938 21.332031zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/><path d="m298.667969 234.667969c0 11.78125-9.550781 21.332031-21.335938 21.332031-11.78125 0-21.332031-9.550781-21.332031-21.332031 0-11.785157 9.550781-21.335938 21.332031-21.335938 11.785157 0 21.335938 9.550781 21.335938 21.335938zm0 0" data-original="#000000" class="active-path" style="fill:#039BD5" data-old_color="#000000"/></g> </svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,2 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" height="512" viewBox="0 -64 512 512" width="512"><g transform="matrix(0.922745 0 0 0.922745 19.7773 14.833)"><path d="m390.398438 209.066406h-32c-21.332032 0-38.398438-17.066406-38.398438-38.398437v-25.601563c0-10.667968 6.398438-17.066406 17.066406-17.066406 10.667969 0 17.066406 6.398438 17.066406 17.066406v25.601563c0 2.132812 2.132813 6.398437 6.402344 6.398437h32c8.53125 0 17.066406 6.398438 17.066406 17.066406 0 10.667969-10.667968 14.933594-19.203124 14.933594zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m390.398438 256c-8.53125 0-17.066407-6.398438-17.066407-17.066406v-93.867188c0-8.53125 6.402344-17.066406 17.066407-17.066406 10.667968 0 14.933593 6.398438 14.933593 17.066406v96c0 8.535156-6.398437 14.933594-14.933593 14.933594zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m177.066406 209.066406h-53.332031c-10.667969 0-17.066406-8.53125-17.066406-17.066406s6.398437-17.066406 17.066406-17.066406h53.332031c8.535156 2.132812 14.933594 8.53125 14.933594 17.066406s-6.398438 17.066406-14.933594 17.066406zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m155.734375 256h-32c-10.667969 0-17.066406-6.398438-17.066406-14.933594 0-8.53125 6.398437-17.066406 17.066406-17.066406h32c2.132813 0 6.398437-2.132812 6.398437-6.398438v-53.335937c0-2.132813-2.132812-6.398437-6.398437-6.398437h-32c-10.667969 2.132812-17.066406-4.265626-17.066406-12.800782 0-8.53125 6.398437-17.066406 17.066406-17.066406h32c19.199219 0 36.265625 17.066406 36.265625 38.398438v53.335937c0 19.199219-17.066406 36.265625-36.265625 36.265625zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m277.332031 149.332031c0 11.785157-9.550781 21.335938-21.332031 21.335938s-21.332031-9.550781-21.332031-21.335938c0-11.78125 9.550781-21.332031 21.332031-21.332031s21.332031 9.550781 21.332031 21.332031zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m277.332031 234.667969c0 11.78125-9.550781 21.332031-21.332031 21.332031s-21.332031-9.550781-21.332031-21.332031c0-11.785157 9.550781-21.335938 21.332031-21.335938s21.332031 9.550781 21.332031 21.335938zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/><path d="m454.398438 384h-394.664063c-34.132813 0-59.734375-25.601562-59.734375-57.601562v-266.664063c0-34.132813 25.601562-59.734375 59.734375-59.734375h394.664063c32 0 57.601562 25.601562 57.601562 59.734375v266.664063c0 32-25.601562 57.601562-57.601562 57.601562zm-394.664063-352c-14.933594 0-27.734375 12.800781-27.734375 27.734375v266.664063c0 14.933593 12.800781 27.734374 27.734375 27.734374h394.664063c14.933593 0 27.734374-12.800781 27.734374-27.734374v-266.664063c0-14.933594-12.800781-27.734375-27.734374-27.734375zm0 0" data-original="#000000" class="active-path" data-old_color="#000000" style="fill:#039BD5"/></g> </svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve" width="512" height="512" class="hovered-paths"><g transform="matrix(0.922745 0 0 0.922745 19.7773 19.7773)"><g>
<g>
<g>
<path d="M0,64v384h512V64H0z M480,416H32V96h448V416z" data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<polygon points="96,160 160,160 160,128 64,128 64,224 96,224 " data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<polygon points="448,288 416,288 416,352 352,352 352,384 448,384 " data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<rect x="192" y="128" width="32" height="32" data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<rect x="64" y="256" width="32" height="32" data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<rect x="416" y="224" width="32" height="32" data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
<rect x="288" y="352" width="32" height="32" data-original="#000000" class="hovered-path active-path" style="fill:#039BD5" data-old_color="#000000"/>
</g>
</g>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -928,3 +928,9 @@ MessagesDialog QWidget#messageTreeWidget::item:hover {
GxsForumThreadWidget QWidget#threadTreeWidget::item { GxsForumThreadWidget QWidget#threadTreeWidget::item {
padding: 2px; padding: 2px;
} }
PulseTopLevel QFrame#frame, PulseViewGroup QFrame#frame, PulseReply QFrame#frame {
border: 2px solid #7ecbfb;
border-radius: 6px;
background: white;
}

View File

@ -138,6 +138,10 @@ ForumsDialog, GxsForumThreadWidget
qproperty-textColorUnreadChildren: darkgray; qproperty-textColorUnreadChildren: darkgray;
qproperty-textColorNotSubscribed: black; qproperty-textColorNotSubscribed: black;
qproperty-textColorMissing: darkRed; qproperty-textColorMissing: darkRed;
qproperty-textColorPinned: darkOrange;
qproperty-backgroundColorPinned: rgb(255, 200, 180);
qproperty-backgroundColorFiltered: rgb(255, 240, 210);
} }
GroupTreeWidget GroupTreeWidget

View File

@ -33,17 +33,10 @@ ChannelPage::ChannelPage(QWidget * parent, Qt::WindowFlags flags)
ui.groupFrameSettingsWidget->setOpenAllInNewTabText(tr("Open each channel in a new tab")); ui.groupFrameSettingsWidget->setOpenAllInNewTabText(tr("Open each channel in a new tab"));
ui.groupFrameSettingsWidget->setType(GroupFrameSettings::Channel) ; ui.groupFrameSettingsWidget->setType(GroupFrameSettings::Channel) ;
connect(ui.loadThreadCheckBox,SIGNAL(toggled(bool)),this,SLOT(updateLoadThread())) ;
connect(ui.emoteicon_checkBox,SIGNAL(toggled(bool)),this,SLOT(updateEmotes())) ; connect(ui.emoteicon_checkBox,SIGNAL(toggled(bool)),this,SLOT(updateEmotes())) ;
} }
void ChannelPage::updateLoadThread()
{
Settings->setChannelLoadThread(ui.loadThreadCheckBox->isChecked());
NotifyQt::getInstance()->notifySettingsChanged();
}
ChannelPage::~ChannelPage() ChannelPage::~ChannelPage()
{ {
} }
@ -51,7 +44,6 @@ ChannelPage::~ChannelPage()
/** Loads the settings for this page */ /** Loads the settings for this page */
void ChannelPage::load() void ChannelPage::load()
{ {
whileBlocking(ui.loadThreadCheckBox)->setChecked(Settings->getChannelLoadThread());
ui.groupFrameSettingsWidget->loadSettings(GroupFrameSettings::Channel); ui.groupFrameSettingsWidget->loadSettings(GroupFrameSettings::Channel);
Settings->beginGroup(QString("ChannelPostsWidget")); Settings->beginGroup(QString("ChannelPostsWidget"));

View File

@ -42,9 +42,6 @@ public:
private slots: private slots:
void updateEmotes(); void updateEmotes();
protected slots:
void updateLoadThread() ;
private: private:
Ui::ChannelPage ui; Ui::ChannelPage ui;
}; };

View File

@ -29,13 +29,6 @@
<string>General</string> <string>General</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="loadThreadCheckBox">
<property name="text">
<string>Load posts in background (Thread)</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="emoteicon_checkBox"> <widget class="QCheckBox" name="emoteicon_checkBox">
<property name="text"> <property name="text">

View File

@ -35,6 +35,8 @@
#include <retroshare/rsstatus.h> #include <retroshare/rsstatus.h>
#include <retroshare/rspeers.h> #include <retroshare/rspeers.h>
#include <retroshare/rsservicecontrol.h> #include <retroshare/rsservicecontrol.h>
#include "rsitems/rsserviceids.h"
#include <QTextDocument>
#define NOT_IMPLEMENTED std::cerr << __PRETTY_FUNCTION__ << ": not yet implemented." << std::endl; #define NOT_IMPLEMENTED std::cerr << __PRETTY_FUNCTION__ << ": not yet implemented." << std::endl;
@ -531,6 +533,45 @@ void RSPermissionMatrixWidget::paintEvent(QPaintEvent *)
_max_height = S*fMATRIX_START_Y + (peer_ids.size()+3) * S*fROW_SIZE ; _max_height = S*fMATRIX_START_Y + (peer_ids.size()+3) * S*fROW_SIZE ;
_max_width = matrix_start_x + (service_ids.size()+3) * S*fCOL_SIZE ; _max_width = matrix_start_x + (service_ids.size()+3) * S*fCOL_SIZE ;
if(n_row_selected == -1)
{
for(uint32_t i=0;i<service_ids.size();++i)
{
if(_current_service_id == service_ids[i])
{
//uint16_t serviceOrig = (uint16_t)((service_ids[i] - (((uint32_t)RS_PKT_VERSION_SERVICE) << 24) ) >> 8);
uint16_t serviceOrig=(service_ids[i]>>8) & 0xffff;
QTextDocument td;
td.setHtml(ServiceDescription(serviceOrig));
QString service_name = "Service: 0x"+ QString::number(serviceOrig,16) + " " + QString::fromStdString(rsServiceControl->getServiceName(service_ids[i]));
QBrush brush ;
brush.setColor(QColor::fromHsvF(0.0f,0.0f,1.0f,0.8f));
brush.setStyle(Qt::SolidPattern) ;
QPen pen ;
pen.setWidth(1) ;
pen.setBrush(FOREGROUND_COLOR) ;
_painter->setPen(pen) ;
QRect position = computeNodePosition(0,i,false) ;
int popup_x = position.x() + (50 * S / 14.0);
int popup_y = position.y() - (10 * S / 14.0) + line_height;
int popup_width = std::max((int)td.size().width(), fm.width(service_name)) + S;
int popup_height = td.size().height() + line_height*2;
while (popup_x + popup_width > _max_width)
popup_x -= S;
if (popup_y + popup_height > _max_height)
popup_y -= popup_height;
QRect info_pos(popup_x, popup_y, popup_width, popup_height) ;
_painter->fillRect(info_pos,brush) ;
_painter->drawRect(info_pos) ;
float x = info_pos.x() + 5*S/14.0 ;
float y = info_pos.y() + line_height + 1*S/14.0 ;
_painter->drawText(QPointF(x,y), service_name) ; y += line_height ;
_painter->translate(QPointF(popup_x, popup_y+2*line_height));
td.drawContents(_painter);
}
}
}
/* Stop the painter */ /* Stop the painter */
_painter->end(); _painter->end();
} }
@ -611,4 +652,33 @@ void RSPermissionMatrixWidget::userPermissionSwitched(uint32_t /* ServiceId */,c
NOT_IMPLEMENTED ; NOT_IMPLEMENTED ;
} }
QString RSPermissionMatrixWidget::ServiceDescription(uint16_t serviceid)
{
switch(serviceid)
{
case RS_SERVICE_TYPE_DISC: return tr("Location info exchange between friends. Helps to find actual address in case of dynamic IPs<br>Without it you will have to rely on DHT only for getting fresh addresses");
case RS_SERVICE_TYPE_CHAT: return tr("Used by direct F2F chat, distant chat and chat lobbies");
case RS_SERVICE_TYPE_MSG: return tr("Mailing service. Also required for direct f2f chat");
case RS_SERVICE_TYPE_TURTLE: return tr("Anonymous routing. Used by file transfers and file search,<br> distant chat, distant mail and distant channels/etc sync");
case RS_SERVICE_TYPE_HEARTBEAT: return tr("Checks if peers alive");
case RS_SERVICE_TYPE_FILE_TRANSFER: return tr("File transfer. If you kill it - you won't be able to dl files from friend shares. Anonymous access unnaffected");
case RS_SERVICE_TYPE_GROUTER: return tr("Used by distant mail for immediate delivery using anonymous tunnels (turtle router)");
case RS_SERVICE_TYPE_FILE_DATABASE: return tr("Exchange shared directories info, aka browsable(visible) files");
case RS_SERVICE_TYPE_SERVICEINFO: return tr("Allows your node to tell to your friends which service are ON on your side, and vice-versa");
case RS_SERVICE_TYPE_BWCTRL: return tr("Speed management");
case RS_SERVICE_TYPE_GXS_TUNNEL: return tr("Used by distant chat, distant mail, and distant channels sync for transfer data using anonymous tunnels");
case RS_SERVICE_TYPE_BANLIST: return tr("IP filter lists exchange");
case RS_SERVICE_TYPE_STATUS: return tr("Share user status like online, away, busy with friends");
case RS_SERVICE_GXS_TYPE_GXSID: return tr("Identity data exchange. Required by all identities-related functions like chats, forums, mail, etc");
case RS_SERVICE_GXS_TYPE_PHOTO: return tr("RS_SERVICE_GXS_TYPE_PHOTO");
case RS_SERVICE_GXS_TYPE_WIKI: return tr("RS_SERVICE_GXS_TYPE_WIKI");
case RS_SERVICE_GXS_TYPE_FORUMS: return tr("Transfer Forums");
case RS_SERVICE_GXS_TYPE_POSTED: return tr("Transfer Posted");
case RS_SERVICE_GXS_TYPE_CHANNELS: return tr("Transfer Channels");
case RS_SERVICE_GXS_TYPE_GXSCIRCLE: return tr("Transfer Circles");
case RS_SERVICE_GXS_TYPE_REPUTATION:return tr("Votes exchange - bans/upvotes for Identities");
case RS_SERVICE_TYPE_GXS_TRANS: return tr("Used by distant mail for deferred delivery - stored at friends when target offline");
case RS_SERVICE_TYPE_RTT: return tr("Measures the Round Trip Time between you and your friends");
default: return tr("unknown");
}
}

View File

@ -59,6 +59,7 @@ class RSPermissionMatrixWidget: public QFrame
public: public:
RSPermissionMatrixWidget(QWidget *parent=NULL); RSPermissionMatrixWidget(QWidget *parent=NULL);
virtual ~RSPermissionMatrixWidget() ; virtual ~RSPermissionMatrixWidget() ;
QString ServiceDescription(uint16_t serviceid);
public slots: public slots:
void setHideOffline(bool hide); void setHideOffline(bool hide);

View File

@ -2143,8 +2143,8 @@ PostedCardView > QFrame#mainFrame[new=true] {
background-color: #005000; background-color: #005000;
} }
WireGroupItem QFrame#frame{ WireGroupItem QFrame#wire_frame{
background: transparent; background: transparent;
} }
GxsChannelDialog GroupTreeWidget QTreeWidget#treeWidget::item{ GxsChannelDialog GroupTreeWidget QTreeWidget#treeWidget::item{
@ -2157,3 +2157,8 @@ RSTextBrowser, MimeTextEdit
/*qproperty-textColorQuote: rgb(125, 125, 255);*/ /*qproperty-textColorQuote: rgb(125, 125, 255);*/
qproperty-textColorQuotes: ColorList(#789922 #039bd5 #800000 #800080 #008080 #b10dc9 #85144b #3d9970); qproperty-textColorQuotes: ColorList(#789922 #039bd5 #800000 #800080 #008080 #b10dc9 #85144b #3d9970);
} }
PulseTopLevel QFrame#frame, PulseViewGroup QFrame#frame, PulseReply QFrame#frame {
border: 2px solid #38444d;
border-radius: 6px;
}

View File

@ -93,6 +93,10 @@ ForumsDialog, GxsForumThreadWidget
qproperty-textColorUnreadChildren: red; qproperty-textColorUnreadChildren: red;
qproperty-textColorNotSubscribed: white; qproperty-textColorNotSubscribed: white;
qproperty-textColorMissing: darkred; qproperty-textColorMissing: darkred;
qproperty-textColorPinned: #D07000;
qproperty-backgroundColorPinned: #202020;
qproperty-backgroundColorFiltered: darkGreen;
} }
QMenuBar QMenuBar
@ -1295,7 +1299,9 @@ PostedCardView > QFrame#mainFrame[new=true] {
background-color: #005000; background-color: #005000;
} }
WireGroupItem QFrame#frame{ WireGroupItem QFrame#wire_frame
{
border: 1px solid #38444d;
background: transparent; background: transparent;
} }
@ -1307,5 +1313,10 @@ RSTextBrowser, MimeTextEdit
ChatWidget QFrame#pluginTitleFrame ChatWidget QFrame#pluginTitleFrame
{ {
background: transparent; background: transparent;
}
PulseTopLevel QFrame#frame, PulseViewGroup QFrame#frame, PulseReply QFrame#frame {
border: 2px solid #38444d;
border-radius: 6px;
} }

View File

@ -1377,6 +1377,8 @@ gxschannels {
gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp \ gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp \
gui/gxschannels/GxsChannelPostFilesModel.cpp \ gui/gxschannels/GxsChannelPostFilesModel.cpp \
gui/gxschannels/GxsChannelFilesStatusWidget.cpp \ gui/gxschannels/GxsChannelFilesStatusWidget.cpp \
gui/gxschannels/GxsChannelFilesWidget.cpp \
gui/gxschannels/GxsChannelPostThumbnail.cpp \
gui/gxschannels/GxsChannelGroupDialog.cpp \ gui/gxschannels/GxsChannelGroupDialog.cpp \
gui/gxschannels/CreateGxsChannelMsg.cpp \ gui/gxschannels/CreateGxsChannelMsg.cpp \
gui/feeds/GxsChannelGroupItem.cpp \ gui/feeds/GxsChannelGroupItem.cpp \

View File

@ -307,7 +307,11 @@ QPixmap misc::getOpenThumbnailedPicture(QWidget *parent, const QString &caption,
if (!getOpenFileName(parent, RshareSettings::LASTDIR_IMAGES, caption, tr("Pictures (*.png *.jpeg *.xpm *.jpg *.tiff *.gif)"), fileName)) if (!getOpenFileName(parent, RshareSettings::LASTDIR_IMAGES, caption, tr("Pictures (*.png *.jpeg *.xpm *.jpg *.tiff *.gif)"), fileName))
return QPixmap(); return QPixmap();
return QPixmap(fileName).scaledToHeight(height, Qt::SmoothTransformation).copy( 0, 0, width, height); if(width > 0 && height > 0)
return QPixmap(fileName).scaledToHeight(height, Qt::SmoothTransformation).copy( 0, 0, width, height);
else
return QPixmap(fileName);
//return QPixmap(fileName).scaledToHeight(width, height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); //return QPixmap(fileName).scaledToHeight(width, height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
} }

View File

@ -146,7 +146,6 @@ void init_item(RsGxsMsgMetaData* metaMsg)
init_random(metaMsg->mGroupId) ; init_random(metaMsg->mGroupId) ;
init_random(metaMsg->mMsgId) ; init_random(metaMsg->mMsgId) ;
metaMsg->refcount = 1;
init_random(metaMsg->mThreadId) ; init_random(metaMsg->mThreadId) ;
init_random(metaMsg->mParentId) ; init_random(metaMsg->mParentId) ;
init_random(metaMsg->mOrigMsgId) ; init_random(metaMsg->mOrigMsgId) ;