2018-11-17 22:36:07 +01:00
# include <QApplication>
# include <QFontMetrics>
# include <QModelIndex>
2018-11-21 23:18:08 +01:00
# include "util/qtthreadsutils.h"
2018-11-22 09:28:07 +01:00
# include "util/DateTime.h"
2018-11-17 22:36:07 +01:00
# include "GxsForumModel.h"
2018-11-21 23:18:08 +01:00
# include "retroshare/rsgxsforums.h"
2018-11-17 22:36:07 +01:00
# define DEBUG_FORUMMODEL
2018-11-21 23:18:08 +01:00
# define COLUMN_THREAD_TITLE 0
# define COLUMN_THREAD_READ 1
# define COLUMN_THREAD_DATE 2
# define COLUMN_THREAD_DISTRIBUTION 3
# define COLUMN_THREAD_AUTHOR 4
# define COLUMN_THREAD_SIGNED 5
# define COLUMN_THREAD_CONTENT 6
# define COLUMN_THREAD_COUNT 7
# define COLUMN_THREAD_MSGID 8
# define COLUMN_THREAD_NB_COLUMNS 9
# define COLUMN_THREAD_DATA 0 // column for storing the userdata like parentid
2018-11-17 22:36:07 +01:00
Q_DECLARE_METATYPE ( RsMsgMetaData ) ;
2018-11-18 21:36:13 +01:00
std : : ostream & operator < < ( std : : ostream & o , const QModelIndex & i ) ; // defined elsewhere
2018-11-17 22:36:07 +01:00
RsGxsForumModel : : RsGxsForumModel ( QObject * parent )
: QAbstractItemModel ( parent )
{
mPosts . resize ( 1 ) ; // adds a sentinel item
2018-11-18 21:36:13 +01:00
2018-11-22 09:28:07 +01:00
mFilterColumn = 0 ;
mUseChildTS = false ;
mFlatView = false ;
2018-11-21 23:18:08 +01:00
// // adds some fake posts to debug
//
// int N=5 ;
// mPosts.resize(N+1);
//
// for(int i=1;i<=N;++i)
// {
// mPosts[0].mChildren.push_back(ForumModelIndex(i));
// mPosts[i].mParent = ForumModelIndex(0);
// mPosts[i].prow = i-1;
//
// RsMsgMetaData meta;
// meta.mMsgName = std::string("message ") + QString::number(i).toStdString() ;
// }
//
// // add one child to last post
// mPosts.resize(N+2);
// mPosts[N].mChildren.push_back(ForumModelIndex(N+1));
// mPosts[N+1].mParent = ForumModelIndex(N);
// mPosts[N+1].prow = 0;
//
// RsMsgMetaData meta;
// meta.mMsgName = std::string("message ") + QString::number(N+1).toStdString() ;
2018-11-17 22:36:07 +01:00
}
int RsGxsForumModel : : rowCount ( const QModelIndex & parent ) const
{
2018-11-19 21:45:42 +01:00
if ( parent . column ( ) > 0 )
return 0 ;
2018-11-17 22:36:07 +01:00
if ( mPosts . empty ( ) ) // security. Should never happen.
return 0 ;
2018-11-19 21:45:42 +01:00
if ( ! parent . isValid ( ) )
return getChildrenCount ( NULL ) ;
else
return getChildrenCount ( parent . internalPointer ( ) ) ;
2018-11-17 22:36:07 +01:00
}
int RsGxsForumModel : : columnCount ( const QModelIndex & parent ) const
{
2018-11-21 23:18:08 +01:00
return COLUMN_THREAD_COUNT ;
2018-11-17 22:36:07 +01:00
}
bool RsGxsForumModel : : hasChildren ( const QModelIndex & parent ) const
{
2018-11-19 21:45:42 +01:00
if ( ! parent . isValid ( ) )
return true ;
void * ref = parent . internalPointer ( ) ;
2018-11-17 22:36:07 +01:00
uint32_t entry = 0 ;
if ( ! convertRefPointerToTabEntry ( ref , entry ) | | entry > = mPosts . size ( ) )
{
# ifdef DEBUG_FORUMMODEL
std : : cerr < < " hasChildren-2( " < < parent < < " ) : " < < false < < std : : endl ;
# endif
return false ;
}
2018-11-19 21:45:42 +01:00
# ifdef DEBUG_FORUMMODEL
2018-11-21 23:18:08 +01:00
std : : cerr < < " hasChildren-3( " < < parent < < " ) : " < < ! mPosts [ entry ] . mChildren . empty ( ) < < std : : endl ;
2018-11-17 22:36:07 +01:00
# endif
2018-11-21 23:18:08 +01:00
return ! mPosts [ entry ] . mChildren . empty ( ) ;
2018-11-17 22:36:07 +01:00
}
bool RsGxsForumModel : : convertTabEntryToRefPointer ( uint32_t entry , void * & ref )
{
// the pointer is formed the following way:
//
// [ 32 bits ]
//
// This means that the whole software has the following build-in limitation:
// * 4 B simultaenous posts. Should be enough !
ref = reinterpret_cast < void * > ( ( intptr_t ) entry ) ;
return true ;
}
bool RsGxsForumModel : : convertRefPointerToTabEntry ( void * ref , uint32_t & entry )
{
intptr_t val = ( intptr_t ) ref ;
if ( val > ( intptr_t ) ( ~ ( uint32_t ( 0 ) ) ) ) // make sure the pointer is an int that fits in 32bits
{
2018-11-19 21:45:42 +01:00
std : : cerr < < " (EE) trying to make a ForumModelIndex out of a number that is larger than 2^32-1 ! " < < std : : endl ;
2018-11-17 22:36:07 +01:00
return false ;
}
entry = uint32_t ( val ) ;
return true ;
}
QModelIndex RsGxsForumModel : : index ( int row , int column , const QModelIndex & parent ) const
{
2018-11-19 21:45:42 +01:00
// if(!hasIndex(row,column,parent))
2018-11-21 23:18:08 +01:00
if ( row < 0 | | column < 0 | | column > = COLUMN_THREAD_COUNT )
2018-11-17 22:36:07 +01:00
return QModelIndex ( ) ;
2018-11-19 21:45:42 +01:00
void * ref = getChildRef ( parent . internalPointer ( ) , row ) ;
2018-11-17 22:36:07 +01:00
# ifdef DEBUG_FORUMMODEL
2018-11-19 21:45:42 +01:00
std : : cerr < < " index-3( " < < row < < " , " < < column < < " parent= " < < parent < < " ) : " < < createIndex ( row , column , ref ) < < std : : endl ;
2018-11-17 22:36:07 +01:00
# endif
2018-11-19 21:45:42 +01:00
return createIndex ( row , column , ref ) ;
}
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
QModelIndex RsGxsForumModel : : parent ( const QModelIndex & index ) const
{
if ( ! index . isValid ( ) )
return QModelIndex ( ) ;
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
void * child_ref = index . internalPointer ( ) ;
int row = 0 ;
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
void * parent_ref = getParentRef ( child_ref , row ) ;
if ( parent_ref = = NULL ) // root
return QModelIndex ( ) ;
return createIndex ( row , 0 , parent_ref ) ;
2018-11-17 22:36:07 +01:00
}
2018-11-19 21:45:42 +01:00
Qt : : ItemFlags RsGxsForumModel : : flags ( const QModelIndex & index ) const
2018-11-17 22:36:07 +01:00
{
2018-11-19 21:45:42 +01:00
if ( ! index . isValid ( ) )
return 0 ;
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
return QAbstractItemModel : : flags ( index ) ;
}
void * RsGxsForumModel : : getChildRef ( void * ref , int row ) const
{
ForumModelIndex entry ;
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
if ( ! convertRefPointerToTabEntry ( ref , entry ) | | entry > = mPosts . size ( ) )
return NULL ;
2018-11-18 21:36:13 +01:00
2018-11-19 21:45:42 +01:00
void * new_ref ;
2018-11-21 23:18:08 +01:00
if ( row > = mPosts [ entry ] . mChildren . size ( ) )
2018-11-19 21:45:42 +01:00
return NULL ;
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
convertTabEntryToRefPointer ( mPosts [ entry ] . mChildren [ row ] , new_ref ) ;
2018-11-19 21:45:42 +01:00
return new_ref ;
}
void * RsGxsForumModel : : getParentRef ( void * ref , int & row ) const
{
ForumModelIndex ref_entry ;
if ( ! convertRefPointerToTabEntry ( ref , ref_entry ) | | ref_entry > = mPosts . size ( ) )
return NULL ;
2018-11-21 23:18:08 +01:00
ForumModelIndex parent_entry = mPosts [ ref_entry ] . mParent ;
2018-11-17 22:36:07 +01:00
2018-11-18 21:36:13 +01:00
if ( parent_entry = = 0 ) // top level index
2018-11-19 21:45:42 +01:00
{
row = 0 ;
return NULL ;
}
2018-11-18 21:36:13 +01:00
else
{
2018-11-19 21:45:42 +01:00
void * parent_ref ;
convertTabEntryToRefPointer ( parent_entry , parent_ref ) ;
row = mPosts [ parent_entry ] . prow ;
2018-11-17 22:36:07 +01:00
2018-11-19 21:45:42 +01:00
return parent_ref ;
2018-11-18 21:36:13 +01:00
}
2018-11-17 22:36:07 +01:00
}
2018-11-19 21:45:42 +01:00
int RsGxsForumModel : : getChildrenCount ( void * ref ) const
{
uint32_t entry = 0 ;
if ( ! convertRefPointerToTabEntry ( ref , entry ) | | entry > = mPosts . size ( ) )
return 0 ;
2018-11-21 23:18:08 +01:00
return mPosts [ entry ] . mChildren . size ( ) ;
2018-11-19 21:45:42 +01:00
}
//bool RsGxsForumModel::hasIndex(int row,int column,const QModelIndex& parent) const
//{
// if(row < 0 || column < 0 || column >= COLUMN_COUNT)
// return false;
//
// if(!parent.isValid())
// return false;
//
// ForumModelIndex entry;
//
// convertRefPointerToTabEntry(parent.internalPointer(),entry);
//
// if(entry >= mPosts.size())
// return false;
//
// if(row >= mPosts[entry].children.size())
// return false;
//
// if(mPosts[entry].children[row] >= mPosts.size())
// return false;
//
// return true;
//}
2018-11-17 22:36:07 +01:00
QVariant RsGxsForumModel : : headerData ( int section , Qt : : Orientation orientation , int role ) const
{
if ( role ! = Qt : : DisplayRole )
return QVariant ( ) ;
switch ( section )
{
default :
2018-11-21 23:18:08 +01:00
case COLUMN_THREAD_TITLE : return tr ( " Title " ) ;
case COLUMN_THREAD_DATE : return tr ( " Date " ) ;
case COLUMN_THREAD_AUTHOR : return tr ( " Author " ) ;
2018-11-17 22:36:07 +01:00
}
}
QVariant RsGxsForumModel : : data ( const QModelIndex & index , int role ) const
{
2018-11-19 21:45:42 +01:00
# ifdef DEBUG_FORUMMODEL
std : : cerr < < " calling data( " < < index < < " ) role= " < < role < < std : : endl ;
# endif
2018-11-17 22:36:07 +01:00
if ( ! index . isValid ( ) )
return QVariant ( ) ;
switch ( role )
{
case Qt : : SizeHintRole : return sizeHintRole ( index . column ( ) ) ;
case Qt : : TextAlignmentRole :
case Qt : : TextColorRole :
case Qt : : WhatsThisRole :
case Qt : : EditRole :
2018-11-19 21:45:42 +01:00
case Qt : : StatusTipRole : return QVariant ( ) ;
default : break ;
2018-11-17 22:36:07 +01:00
}
void * ref = ( index . isValid ( ) ) ? index . internalPointer ( ) : NULL ;
uint32_t entry = 0 ;
# ifdef DEBUG_FORUMMODEL
std : : cerr < < " data( " < < index < < " ) " ;
# endif
if ( ! ref )
{
# ifdef DEBUG_FORUMMODEL
std : : cerr < < " [empty] " < < std : : endl ;
# endif
return QVariant ( ) ;
}
if ( ! convertRefPointerToTabEntry ( ref , entry ) | | entry > = mPosts . size ( ) )
{
# ifdef DEBUG_FORUMMODEL
std : : cerr < < " Bad pointer: " < < ( void * ) ref < < std : : endl ;
# endif
return QVariant ( ) ;
}
2018-11-21 23:18:08 +01:00
const ForumModelPostEntry & fmpe ( mPosts [ entry ] ) ;
2018-11-17 22:36:07 +01:00
2018-11-18 21:36:13 +01:00
# ifdef DEBUG_FORUMMODEL
2018-11-17 22:36:07 +01:00
std : : cerr < < " [ok] " < < std : : endl ;
# endif
switch ( role )
{
2018-11-21 23:18:08 +01:00
case Qt : : DisplayRole : return displayRole ( fmpe , index . column ( ) ) ;
case Qt : : DecorationRole : return decorationRole ( fmpe , index . column ( ) ) ;
case Qt : : ToolTipRole : return toolTipRole ( fmpe , index . column ( ) ) ;
case ThreadPinnedRole : return pinnedRole ( fmpe , index . column ( ) ) ;
case MissingRole : return missingRole ( fmpe , index . column ( ) ) ;
case StatusRole : return statusRole ( fmpe , index . column ( ) ) ;
2018-11-17 22:36:07 +01:00
default :
return QVariant ( ) ;
}
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : statusRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column ! = COLUMN_THREAD_DATA )
return QVariant ( ) ;
2018-11-21 23:18:08 +01:00
return QVariant ( fmpe . mStatus ) ;
2018-11-20 23:28:07 +01:00
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : missingRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column ! = COLUMN_THREAD_DATA )
return QVariant ( ) ;
2018-11-21 23:18:08 +01:00
if ( fmpe . mPostFlags & ForumModelPostEntry : : FLAG_POST_IS_MISSING )
2018-11-20 23:28:07 +01:00
return QVariant ( true ) ;
else
return QVariant ( false ) ;
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : toolTipRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column ! = COLUMN_THREAD_DISTRIBUTION )
return QVariant ( ) ;
2018-11-21 23:18:08 +01:00
switch ( fmpe . mReputationWarningLevel )
2018-11-20 23:28:07 +01:00
{
case 3 : return QVariant ( tr ( " Information for this identity is currently missing. " ) ) ;
case 2 : return QVariant ( tr ( " You have banned this ID. The message will not be \n displayed nor forwarded to your friends. " ) ) ;
case 1 : return QVariant ( tr ( " You have not set an opinion for this person, \n and your friends do not vote positively: Spam regulation \n prevents the message to be forwarded to your friends. " ) ) ;
2018-11-21 23:18:08 +01:00
case 0 : return QVariant ( tr ( " Message will be forwarded to your friends. " ) ) ;
2018-11-20 23:28:07 +01:00
default :
return QVariant ( " [ERROR: missing reputation level information - contact the developers] " ) ;
}
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : pinnedRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column ! = COLUMN_THREAD_DATE )
return QVariant ( ) ;
2018-11-21 23:18:08 +01:00
if ( fmpe . mPostFlags & ForumModelPostEntry : : FLAG_POST_IS_PINNED )
2018-11-20 23:28:07 +01:00
return QVariant ( true ) ;
else
return QVariant ( false ) ;
}
2018-11-17 22:36:07 +01:00
QVariant RsGxsForumModel : : sizeHintRole ( int col ) const
{
float factor = QFontMetricsF ( QApplication : : font ( ) ) . height ( ) / 14.0f ;
switch ( col )
{
default :
2018-11-21 23:18:08 +01:00
case COLUMN_THREAD_TITLE : return QVariant ( QSize ( factor * 170 , factor * 14 ) ) ;
case COLUMN_THREAD_DATE : return QVariant ( QSize ( factor * 75 , factor * 14 ) ) ;
case COLUMN_THREAD_AUTHOR : return QVariant ( QSize ( factor * 75 , factor * 14 ) ) ;
2018-11-17 22:36:07 +01:00
}
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : authorRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column = = COLUMN_THREAD_DATA )
2018-11-22 09:28:07 +01:00
return QVariant ( QString : : fromStdString ( fmpe . mAuthorId . toStdString ( ) ) ) ;
2018-11-20 23:28:07 +01:00
return QVariant ( ) ;
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : sortRole ( const ForumModelPostEntry & fmpe , int column ) const
2018-11-20 23:28:07 +01:00
{
if ( column = = COLUMN_THREAD_DATA )
return QVariant ( QString : : number ( fmpe . mPublishTs ) ) ; // we should probably have leading zeroes here
2018-11-22 09:28:07 +01:00
return QVariant ( ) ;
2018-11-20 23:28:07 +01:00
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : displayRole ( const ForumModelPostEntry & fmpe , int col ) const
2018-11-17 22:36:07 +01:00
{
switch ( col )
{
2018-11-21 23:18:08 +01:00
case COLUMN_THREAD_TITLE : if ( fmpe . mPostFlags & ForumModelPostEntry : : FLAG_POST_IS_REDACTED )
2018-11-20 23:28:07 +01:00
return QVariant ( tr ( " [ ... Redacted message ... ] " ) ) ;
else if ( fmpe . mPostFlags & ForumModelPostEntry : : FLAG_POST_IS_PINNED )
2018-11-21 23:18:08 +01:00
return QVariant ( tr ( " [PINNED] " ) + QString : : fromUtf8 ( fmpe . mTitle . c_str ( ) ) ) ;
2018-11-20 23:28:07 +01:00
else
2018-11-21 23:18:08 +01:00
return QVariant ( QString : : fromUtf8 ( fmpe . mTitle . c_str ( ) ) ) ;
2018-11-20 23:28:07 +01:00
2018-11-21 23:18:08 +01:00
//case COLUMN_THREAD_READ_STATUS:return QVariant(fmpe.mMsgStatus);
case COLUMN_THREAD_DATE : {
2018-11-20 23:28:07 +01:00
QDateTime qtime ;
2018-11-21 23:18:08 +01:00
qtime . setTime_t ( fmpe . mPublishTs ) ;
2018-11-20 23:28:07 +01:00
2018-11-22 09:28:07 +01:00
return QVariant ( DateTime : : formatDateTime ( qtime ) ) ;
2018-11-20 23:28:07 +01:00
}
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
case COLUMN_THREAD_AUTHOR : return QVariant ( QString : : fromStdString ( fmpe . mAuthorId . toStdString ( ) ) ) ;
case COLUMN_THREAD_MSGID : return QVariant ( QString : : fromStdString ( fmpe . mMsgId . toStdString ( ) ) ) ;
2018-11-20 23:28:07 +01:00
# ifdef TODO
if ( filterColumn = = COLUMN_THREAD_CONTENT ) {
// need content for filter
QTextDocument doc ;
doc . setHtml ( QString : : fromUtf8 ( msg . mMsg . c_str ( ) ) ) ;
item - > setText ( COLUMN_THREAD_CONTENT , doc . toPlainText ( ) . replace ( QString ( " \n " ) , QString ( " " ) ) ) ;
}
# endif
2018-11-17 22:36:07 +01:00
default :
return QVariant ( " [ TODO ] " ) ;
}
return QVariant ( " [ERROR] " ) ;
}
2018-11-21 23:18:08 +01:00
QVariant RsGxsForumModel : : decorationRole ( const ForumModelPostEntry & fmpe , int col ) const
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
if ( col = = COLUMN_THREAD_DISTRIBUTION )
return QVariant ( fmpe . mReputationWarningLevel ) ;
else
return QVariant ( ) ;
}
2018-11-22 09:28:07 +01:00
void RsGxsForumModel : : setForum ( const RsGxsGroupId & forum_group_id )
2018-11-21 23:18:08 +01:00
{
2018-11-22 09:28:07 +01:00
if ( mForumGroup . mMeta . mGroupId = = forum_group_id )
2018-11-21 23:18:08 +01:00
return ;
2018-11-22 09:28:07 +01:00
// we do not set mForumGroupId yet. We'll do it when the forum data is updated.
update_posts ( forum_group_id ) ;
}
void RsGxsForumModel : : setPosts ( const RsGxsForumGroup & group , const std : : vector < ForumModelPostEntry > & posts )
{
mForumGroup = group ;
mPosts = posts ;
2018-11-21 23:18:08 +01:00
2018-11-22 09:28:07 +01:00
emit layoutChanged ( ) ;
2018-11-21 23:18:08 +01:00
}
void RsGxsForumModel : : update_posts ( const RsGxsGroupId & group_id )
{
RsThread : : async ( [ this , group_id ] ( )
{
// 1 - get message data from p3GxsForums
std : : list < RsGxsGroupId > forumIds ;
std : : vector < RsGxsForumMsg > messages ;
std : : vector < RsGxsForumGroup > groups ;
forumIds . push_back ( group_id ) ;
if ( ! rsGxsForums - > getForumsInfo ( forumIds , groups ) )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
std : : cerr < < __PRETTY_FUNCTION__ < < " failed to retrieve forum group info for forum " < < group_id < < std : : endl ;
return ;
}
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
if ( ! rsGxsForums - > getForumsContent ( forumIds , messages ) )
{
std : : cerr < < __PRETTY_FUNCTION__ < < " failed to retrieve forum message info for forum " < < group_id < < std : : endl ;
return ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
// 2 - sort the messages into a proper hierarchy
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
std : : vector < ForumModelPostEntry > * vect = new std : : vector < ForumModelPostEntry > ( ) ;
2018-11-22 09:28:07 +01:00
RsGxsForumGroup group = groups [ 0 ] ;
2018-11-17 22:36:07 +01:00
2018-11-22 09:28:07 +01:00
computeMessagesHierarchy ( group , messages , * vect ) ;
2018-11-21 23:18:08 +01:00
// 3 - update the model in the UI thread.
2018-11-22 09:28:07 +01:00
RsQThreadUtils : : postToObject ( [ group , vect , this ] ( )
2018-11-21 23:18:08 +01:00
{
/* Here it goes any code you want to be executed on the Qt Gui
* thread , for example to update the data model with new information
* after a blocking call to RetroShare API complete , note that
* Qt : : QueuedConnection is important !
*/
2018-11-17 22:36:07 +01:00
2018-11-22 09:28:07 +01:00
setPosts ( group , * vect ) ;
2018-11-21 23:18:08 +01:00
delete vect ;
} , this ) ;
} ) ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
ForumModelIndex RsGxsForumModel : : addEntry ( std : : vector < ForumModelPostEntry > & posts , const ForumModelPostEntry & entry , ForumModelIndex parent )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
uint32_t N = posts . size ( ) ;
posts . push_back ( entry ) ;
posts [ N ] . mParent = parent ;
posts [ parent ] . mChildren . push_back ( N ) ;
return ForumModelIndex ( N ) ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
void RsGxsForumModel : : generateMissingItem ( const RsGxsMessageId & msgId , ForumModelPostEntry & entry )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
entry . mPostFlags = ForumModelPostEntry : : FLAG_POST_IS_MISSING ;
entry . mTitle = std : : string ( tr ( " [ ... Missing Message ... ] " ) . toUtf8 ( ) ) ;
entry . mMsgId = msgId ;
entry . mAuthorId . clear ( ) ;
entry . mPublishTs = 0 ;
entry . mReputationWarningLevel = 3 ;
}
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
void RsGxsForumModel : : convertMsgToPostEntry ( const RsGxsForumGroup & mForumGroup , const RsGxsForumMsg & msg , bool useChildTS , uint32_t filterColumn , ForumModelPostEntry & fentry )
{
fentry . mMsgId = msg . mMeta . mMsgId ;
fentry . mPublishTs = msg . mMeta . mPublishTs ;
fentry . mStatus = msg . mMeta . mMsgStatus ;
if ( mForumGroup . mPinnedPosts . ids . find ( msg . mMeta . mMsgId ) ! = mForumGroup . mPinnedPosts . ids . end ( ) )
fentry . mPostFlags | = ForumModelPostEntry : : FLAG_POST_IS_PINNED ;
// Early check for a message that should be hidden because its author
// is flagged with a bad reputation
uint32_t idflags = 0 ;
RsReputations : : ReputationLevel reputation_level = rsReputations - > overallReputationLevel ( msg . mMeta . mAuthorId , & idflags ) ;
bool redacted = false ;
if ( reputation_level = = RsReputations : : REPUTATION_LOCALLY_NEGATIVE )
fentry . mPostFlags | = ForumModelPostEntry : : FLAG_POST_IS_REDACTED ;
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
// We use a specific item model for forums in order to handle the post pinning.
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
if ( reputation_level = = RsReputations : : REPUTATION_UNKNOWN )
fentry . mReputationWarningLevel = 3 ;
else if ( reputation_level = = RsReputations : : REPUTATION_LOCALLY_NEGATIVE )
fentry . mReputationWarningLevel = 2 ;
else if ( reputation_level < rsGxsForums - > minReputationForForwardingMessages ( mForumGroup . mMeta . mSignFlags , idflags ) )
fentry . mReputationWarningLevel = 1 ;
else
fentry . mReputationWarningLevel = 0 ;
# ifdef TODO
// This is an attempt to put pinned posts on the top. We should rather use a QSortFilterProxyModel here.
QString itemSort = QString : : number ( msg . mMeta . mPublishTs ) ; //Don't need to format it as for sort.
if ( useChildTS )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
for ( QTreeWidgetItem * grandParent = parent ; grandParent ! = NULL ; grandParent = grandParent - > parent ( ) )
{
//Update Parent Child TimeStamp
QString oldTSSort = grandParent - > data ( COLUMN_THREAD_DATE , ROLE_THREAD_SORT ) . toString ( ) ;
QString oldCTSSort = oldTSSort . split ( " | " ) . at ( 0 ) ;
QString oldPTSSort = oldTSSort . contains ( " | " ) ? oldTSSort . split ( " | " ) . at ( 1 ) : oldCTSSort ;
# ifdef SHOW_COMBINED_DATES
QString oldTSText = grandParent - > text ( COLUMN_THREAD_DATE ) ;
QString oldCTSText = oldTSText . split ( " | " ) . at ( 0 ) ;
QString oldPTSText = oldTSText . contains ( " | " ) ? oldTSText . split ( " | " ) . at ( 1 ) : oldCTSText ; //If first time parent get only its mPublishTs
# endif
if ( oldCTSSort . toDouble ( ) < itemSort . toDouble ( ) )
{
# ifdef SHOW_COMBINED_DATES
grandParent - > setText ( COLUMN_THREAD_DATE , DateTime : : formatDateTime ( qtime ) + " | " + oldPTSText ) ;
# endif
grandParent - > setData ( COLUMN_THREAD_DATE , ROLE_THREAD_SORT , itemSort + " | " + oldPTSSort ) ;
}
}
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
# endif
}
static bool decreasing_time_comp ( const QPair < time_t , RsGxsMessageId > & e1 , const QPair < time_t , RsGxsMessageId > & e2 ) { return e2 . first < e1 . first ; }
2018-11-22 09:28:07 +01:00
void RsGxsForumModel : : computeMessagesHierarchy ( const RsGxsForumGroup & forum_group ,
const std : : vector < RsGxsForumMsg > & msgs_array ,
std : : vector < ForumModelPostEntry > & posts )
2018-11-21 23:18:08 +01:00
{
std : : cerr < < " updating messages data with " < < msgs_array . size ( ) < < " messages " < < std : : endl ;
//#ifdef DEBUG_FORUMS
std : : cerr < < " Retrieved group data: " < < std : : endl ;
std : : cerr < < " Group ID: " < < forum_group . mMeta . mGroupId < < std : : endl ;
std : : cerr < < " Admin lst: " < < forum_group . mAdminList . ids . size ( ) < < " elements. " < < std : : endl ;
for ( auto it ( forum_group . mAdminList . ids . begin ( ) ) ; it ! = forum_group . mAdminList . ids . end ( ) ; + + it )
std : : cerr < < " " < < * it < < std : : endl ;
std : : cerr < < " Pinned Post: " < < forum_group . mPinnedPosts . ids . size ( ) < < " messages. " < < std : : endl ;
for ( auto it ( forum_group . mPinnedPosts . ids . begin ( ) ) ; it ! = forum_group . mPinnedPosts . ids . end ( ) ; + + it )
std : : cerr < < " " < < * it < < std : : endl ;
//#endif
/* get messages */
std : : map < RsGxsMessageId , RsGxsForumMsg > msgs ;
for ( uint32_t i = 0 ; i < msgs_array . size ( ) ; + + i )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " Adding message " < < msgs_array [ i ] . mMeta . mMsgId < < " with parent " < < msgs_array [ i ] . mMeta . mParentId < < " to message map " < < std : : endl ;
# endif
msgs [ msgs_array [ i ] . mMeta . mMsgId ] = msgs_array [ i ] ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
int count = msgs . size ( ) ;
int pos = 0 ;
// int steps = count / PROGRESSBAR_MAX;
int step = 0 ;
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
posts . clear ( ) ;
QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > mPostVersions ;
// ThreadList contains the list of parent threads. The algorithm below iterates through all messages
// and tries to establish parenthood relationships between them, given that we only know the
// immediate parent of a message and now its children. Some messages have a missing parent and for them
// a fake top level parent is generated.
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
// In order to be efficient, we first create a structure that lists the children of every mesage ID in the list.
// Then the hierarchy of message is build by attaching the kids to every message until all of them have been processed.
// The messages with missing parents will be the last ones remaining in the list.
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
std : : list < std : : pair < RsGxsMessageId , ForumModelIndex > > threadStack ;
std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > kids_array ;
std : : set < RsGxsMessageId > missing_parents ;
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
// First of all, remove all older versions of posts. This is done by first adding all posts into a hierarchy structure
// and then removing all posts which have a new versions available. The older versions are kept appart.
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Collecting post versions " < < std : : endl ;
# endif
mPostVersions . clear ( ) ;
std : : list < RsGxsMessageId > msg_stack ;
for ( std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt = msgs . begin ( ) ; msgIt ! = msgs . end ( ) ; + + msgIt )
{
if ( ! msgIt - > second . mMeta . mOrigMsgId . isNull ( ) & & msgIt - > second . mMeta . mOrigMsgId ! = msgIt - > second . mMeta . mMsgId )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " Post " < < msgIt - > second . mMeta . mMsgId < < " is a new version of " < < msgIt - > second . mMeta . mOrigMsgId < < std : : endl ;
2018-11-17 22:36:07 +01:00
# endif
2018-11-21 23:18:08 +01:00
std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt2 = msgs . find ( msgIt - > second . mMeta . mOrigMsgId ) ;
// Ensuring that the post exists allows to only collect the existing data.
if ( msgIt2 = = msgs . end ( ) )
continue ;
// Make sure that the author is the same than the original message, or is a moderator. This should always happen when messages are constructed using
// the UI but nothing can prevent a nasty user to craft a new version of a message with his own signature.
if ( msgIt2 - > second . mMeta . mAuthorId ! = msgIt - > second . mMeta . mAuthorId )
{
if ( ! IS_FORUM_MSG_MODERATION ( msgIt - > second . mMeta . mMsgFlags ) ) // if authors are different the moderation flag needs to be set on the editing msg
continue ;
if ( forum_group . mAdminList . ids . find ( msgIt - > second . mMeta . mAuthorId ) = = forum_group . mAdminList . ids . end ( ) ) // if author is not a moderator, continue
continue ;
}
// always add the post a self version
if ( mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . empty ( ) )
mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . push_back ( QPair < time_t , RsGxsMessageId > ( msgIt2 - > second . mMeta . mPublishTs , msgIt2 - > second . mMeta . mMsgId ) ) ;
mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . push_back ( QPair < time_t , RsGxsMessageId > ( msgIt - > second . mMeta . mPublishTs , msgIt - > second . mMeta . mMsgId ) ) ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
}
// The following code assembles all new versions of a given post into the same array, indexed by the oldest version of the post.
for ( QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it ( mPostVersions . begin ( ) ) ; it ! = mPostVersions . end ( ) ; + + it )
{
QVector < QPair < time_t , RsGxsMessageId > > & v ( * it ) ;
for ( int32_t i = 0 ; i < v . size ( ) ; + + i )
{
if ( v [ i ] . second ! = it . key ( ) )
{
RsGxsMessageId sub_msg_id = v [ i ] . second ;
QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it2 = mPostVersions . find ( sub_msg_id ) ;
if ( it2 ! = mPostVersions . end ( ) )
{
for ( int32_t j = 0 ; j < ( * it2 ) . size ( ) ; + + j )
if ( ( * it2 ) [ j ] . second ! = sub_msg_id ) // dont copy it, since it is already present at slot i
v . append ( ( * it2 ) [ j ] ) ;
mPostVersions . erase ( it2 ) ; // it2 is never equal to it
}
}
}
}
// Now remove from msg ids, all posts except the most recent one. And make the mPostVersion be indexed by the most recent version of the post,
// which corresponds to the item in the tree widget.
# ifdef DEBUG_FORUMS
std : : cerr < < " Final post versions: " < < std : : endl ;
# endif
QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > mTmp ;
std : : map < RsGxsMessageId , RsGxsMessageId > most_recent_versions ;
for ( QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it ( mPostVersions . begin ( ) ) ; it ! = mPostVersions . end ( ) ; + + it )
{
# ifdef DEBUG_FORUMS
std : : cerr < < " Original post: " < < it . key ( ) < < std : : endl ;
# endif
// Finally, sort the posts from newer to older
qSort ( ( * it ) . begin ( ) , ( * it ) . end ( ) , decreasing_time_comp ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " most recent version " < < ( * it ) [ 0 ] . first < < " " < < ( * it ) [ 0 ] . second < < std : : endl ;
# endif
for ( int32_t i = 1 ; i < ( * it ) . size ( ) ; + + i )
{
msgs . erase ( ( * it ) [ i ] . second ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " older version " < < ( * it ) [ i ] . first < < " " < < ( * it ) [ i ] . second < < std : : endl ;
# endif
}
mTmp [ ( * it ) [ 0 ] . second ] = * it ; // index the versions map by the ID of the most recent post.
// Now make sure that message parents are consistent. Indeed, an old post may have the old version of a post as parent. So we need to change that parent
// to the newest version. So we create a map of which is the most recent version of each message, so that parent messages can be searched in it.
for ( int i = 1 ; i < ( * it ) . size ( ) ; + + i )
most_recent_versions [ ( * it ) [ i ] . second ] = ( * it ) [ 0 ] . second ;
}
mPostVersions = mTmp ;
// The next step is to find the top level thread messages. These are defined as the messages without
// any parent message ID.
// this trick is needed because while we remove messages, the parents a given msg may already have been removed
// and wrongly understand as a missing parent.
std : : map < RsGxsMessageId , RsGxsForumMsg > kept_msgs ;
for ( std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt = msgs . begin ( ) ; msgIt ! = msgs . end ( ) ; + + msgIt )
{
if ( mFlatView | | msgIt - > second . mMeta . mParentId . isNull ( ) )
2018-11-17 22:36:07 +01:00
{
2018-11-21 23:18:08 +01:00
/* add all threads */
const RsGxsForumMsg & msg = msgIt - > second ;
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Adding TopLevel Thread: mId: " < < msg . mMeta . mMsgId < < std : : endl ;
2018-11-17 22:36:07 +01:00
# endif
2018-11-21 23:18:08 +01:00
ForumModelPostEntry entry ;
2018-11-22 09:28:07 +01:00
convertMsgToPostEntry ( forum_group , msg , mUseChildTS , mFilterColumn , entry ) ;
2018-11-21 23:18:08 +01:00
ForumModelIndex entry_index = addEntry ( posts , entry , 0 ) ;
if ( ! mFlatView )
threadStack . push_back ( std : : make_pair ( msg . mMeta . mMsgId , entry_index ) ) ;
//calculateExpand(msg, item);
//mItems.append(entry_index);
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
else
{
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Storing kid " < < msgIt - > first < < " of message " < < msgIt - > second . mMeta . mParentId < < std : : endl ;
# endif
// The same missing parent may appear multiple times, so we first store them into a unique container.
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
RsGxsMessageId parent_msg = msgIt - > second . mMeta . mParentId ;
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
if ( msgs . find ( parent_msg ) = = msgs . end ( ) )
{
// also check that the message is not versionned
2018-11-17 22:36:07 +01:00
2018-11-21 23:18:08 +01:00
std : : map < RsGxsMessageId , RsGxsMessageId > : : const_iterator mrit = most_recent_versions . find ( parent_msg ) ;
if ( mrit ! = most_recent_versions . end ( ) )
parent_msg = mrit - > second ;
else
missing_parents . insert ( parent_msg ) ;
}
kids_array [ parent_msg ] . push_back ( msgIt - > first ) ;
kept_msgs . insert ( * msgIt ) ;
}
}
msgs = kept_msgs ;
// Also create a list of posts by time, when they are new versions of existing posts. Only the last one will have an item created.
// Add a fake toplevel item for the parent IDs that we dont actually have.
for ( std : : set < RsGxsMessageId > : : const_iterator it ( missing_parents . begin ( ) ) ; it ! = missing_parents . end ( ) ; + + it )
{
// add dummy parent item
ForumModelPostEntry e ;
2018-11-22 09:28:07 +01:00
generateMissingItem ( * it , e ) ;
2018-11-21 23:18:08 +01:00
ForumModelIndex e_index = addEntry ( posts , e , 0 ) ; // no parent -> parent is level 0
//mItems.append( e_index );
threadStack . push_back ( std : : make_pair ( * it , e_index ) ) ;
}
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Processing stack: " < < std : : endl ;
# endif
// Now use a stack to go down the hierarchy
while ( ! threadStack . empty ( ) )
{
std : : pair < RsGxsMessageId , uint32_t > threadPair = threadStack . front ( ) ;
threadStack . pop_front ( ) ;
std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > : : iterator it = kids_array . find ( threadPair . first ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Node: " < < threadPair . first < < std : : endl ;
# endif
if ( it = = kids_array . end ( ) )
continue ;
for ( std : : list < RsGxsMessageId > : : const_iterator it2 ( it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; + + it2 )
{
// We iterate through the top level thread items, and look for which message has the current item as parent.
// When found, the item is put in the thread list itself, as a potential new parent.
std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator mit = msgs . find ( * it2 ) ;
if ( mit = = msgs . end ( ) )
{
std : : cerr < < " GxsForumsFillThread::run() Cannot find submessage " < < * it2 < < " !!! " < < std : : endl ;
continue ;
}
const RsGxsForumMsg & msg ( mit - > second ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() adding sub_item " < < msg . mMeta . mMsgId < < std : : endl ;
# endif
ForumModelPostEntry e ;
convertMsgToPostEntry ( forum_group , msg , mUseChildTS , mFilterColumn , e ) ;
ForumModelIndex e_index = addEntry ( posts , e , threadPair . second ) ;
//calculateExpand(msg, item);
/* add item to process list */
threadStack . push_back ( std : : make_pair ( msg . mMeta . mMsgId , e_index ) ) ;
msgs . erase ( mit ) ;
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Erasing entry " < < it - > first < < " from kids tab. " < < std : : endl ;
# endif
kids_array . erase ( it ) ; // This is not strictly needed, but it improves performance by reducing the search space.
2018-11-17 22:36:07 +01:00
}
2018-11-21 23:18:08 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " Kids array now has " < < kids_array . size ( ) < < " elements " < < std : : endl ;
for ( std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > : : const_iterator it ( kids_array . begin ( ) ) ; it ! = kids_array . end ( ) ; + + it )
{
std : : cerr < < " Node " < < it - > first < < std : : endl ;
for ( std : : list < RsGxsMessageId > : : const_iterator it2 ( it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; + + it2 )
std : : cerr < < " " < < * it2 < < std : : endl ;
}
std : : cerr < < " GxsForumsFillThread::run() stopped: " < < ( wasStopped ( ) ? " yes " : " no " ) < < std : : endl ;
2018-11-17 22:36:07 +01:00
# endif
}
2018-11-21 23:18:08 +01:00