Merge pull request #1323 from csoler/v0.6-ForumAdmin

V0.6 forum admin
This commit is contained in:
csoler 2018-09-11 15:17:33 +02:00 committed by GitHub
commit b138263878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 666 additions and 367 deletions

View File

@ -111,7 +111,7 @@ public:
RsTlvKeySignatureSet signSet;
std::string mMsgName;
time_t mPublishTs;
uint32_t mMsgFlags; // Whats this for?
uint32_t mMsgFlags; // used by some services (e.g. by forums to store message moderation flags)
// BELOW HERE IS LOCAL DATA, THAT IS NOT FROM MSG.
// normally READ / UNREAD flags. LOCAL Data.

View File

@ -115,8 +115,7 @@ namespace GXS_SERV {
/** START GXS Grp status flags **/
static const uint32_t GXS_GRP_STATUS_UNPROCESSED = 0x000000100;
static const uint32_t GXS_GRP_STATUS_UNREAD = 0x00000200;
static const uint32_t GXS_GRP_STATUS_UNREAD = 0x000000200;
/** END GXS Grp status flags **/
}

View File

@ -28,8 +28,15 @@
#include "retroshare/rstokenservice.h"
#include "retroshare/rsgxsifacehelper.h"
#include "serialiser/rstlvidset.h"
// Forum Service message flags, to be used in RsMsgMetaData::mMsgFlags
// Gxs imposes to use the first two bytes (lower bytes) of mMsgFlags for private forum flags, the upper bytes being used for internal GXS stuff.
static const uint32_t RS_GXS_FORUM_MSG_FLAGS_MASK = 0x0000000f ;
static const uint32_t RS_GXS_FORUM_MSG_FLAGS_MODERATED = 0x00000001 ;
#define IS_FORUM_MSG_MODERATION(flags) (flags & RS_GXS_FORUM_MSG_FLAGS_MODERATED)
/* The Main Interface Class - for information about your Peers */
class RsGxsForums;
@ -40,6 +47,11 @@ class RsGxsForumGroup
public:
RsGroupMetaData mMeta;
std::string mDescription;
// What's below is optional, and handled by the serialiser
RsTlvGxsIdSet mAdminList;
RsTlvGxsMsgIdSet mPinnedPosts;
};
class RsGxsForumMsg

View File

@ -138,7 +138,7 @@ struct RsMsgMetaData : RsSerializable
std::string mMsgName;
time_t mPublishTs;
/// the first 16 bits for service, last 16 for GXS
/// the lower 16 bits for service, upper 16 bits for GXS
uint32_t mMsgFlags;
// BELOW HERE IS LOCAL DATA, THAT IS NOT FROM MSG.

View File

@ -48,7 +48,16 @@ void RsGxsForumGroupItem::clear()
void RsGxsForumGroupItem::serial_process(RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx)
{
RsTypeSerializer::serial_process(j,ctx,TLV_TYPE_STR_DESCR,mGroup.mDescription,"mGroup.Description");
// This is for backward compatibility: normally all members are serialized, but in the previous version, these members are missing.
if(j == RsGenericSerializer::DESERIALIZE && ctx.mOffset == ctx.mSize)
return ;
RsTypeSerializer::serial_process<RsTlvItem>(j,ctx,mGroup.mAdminList ,"admin_list" ) ;
RsTypeSerializer::serial_process<RsTlvItem>(j,ctx,mGroup.mPinnedPosts,"pinned_posts") ;
}
void RsGxsForumMsgItem::clear()
{
mMsg.mMsg.clear();

View File

@ -200,6 +200,7 @@ const uint16_t TLV_TYPE_RECOGNSET = 0x1024;
const uint16_t TLV_TYPE_GXSIDSET = 0x1025;
const uint16_t TLV_TYPE_GXSCIRCLEIDSET= 0x1026;
const uint16_t TLV_TYPE_NODEGROUPIDSET= 0x1027;
const uint16_t TLV_TYPE_GXSMSGIDSET = 0x1028;
const uint16_t TLV_TYPE_SERVICESET = 0x1030;

View File

@ -114,12 +114,13 @@ template<class ID_CLASS,uint32_t TLV_TYPE> class t_RsTlvIdSet: public RsTlvItem
std::set<ID_CLASS> ids ;
};
typedef t_RsTlvIdSet<RsPeerId, TLV_TYPE_PEERSET> RsTlvPeerIdSet ;
typedef t_RsTlvIdSet<RsPgpId, TLV_TYPE_PGPIDSET> RsTlvPgpIdSet ;
typedef t_RsTlvIdSet<Sha1CheckSum, TLV_TYPE_HASHSET> RsTlvHashSet ;
typedef t_RsTlvIdSet<RsGxsId, TLV_TYPE_GXSIDSET> RsTlvGxsIdSet ;
typedef t_RsTlvIdSet<RsGxsCircleId,TLV_TYPE_GXSCIRCLEIDSET> RsTlvGxsCircleIdSet ;
typedef t_RsTlvIdSet<RsNodeGroupId,TLV_TYPE_NODEGROUPIDSET> RsTlvNodeGroupIdSet ;
typedef t_RsTlvIdSet<RsPeerId, TLV_TYPE_PEERSET> RsTlvPeerIdSet ;
typedef t_RsTlvIdSet<RsPgpId, TLV_TYPE_PGPIDSET> RsTlvPgpIdSet ;
typedef t_RsTlvIdSet<Sha1CheckSum, TLV_TYPE_HASHSET> RsTlvHashSet ;
typedef t_RsTlvIdSet<RsGxsId, TLV_TYPE_GXSIDSET> RsTlvGxsIdSet ;
typedef t_RsTlvIdSet<RsGxsMessageId,TLV_TYPE_GXSMSGIDSET> RsTlvGxsMsgIdSet ;
typedef t_RsTlvIdSet<RsGxsCircleId, TLV_TYPE_GXSCIRCLEIDSET> RsTlvGxsCircleIdSet ;
typedef t_RsTlvIdSet<RsNodeGroupId, TLV_TYPE_NODEGROUPIDSET> RsTlvNodeGroupIdSet ;
class RsTlvServiceIdSet: public RsTlvItem
{

View File

@ -26,8 +26,8 @@
RsPhoto *rsPhoto = NULL;
const uint32_t RsPhoto::FLAG_MSG_TYPE_MASK = 0x000f;
const uint32_t RsPhoto::FLAG_MSG_TYPE_PHOTO_POST = 0x0001;
const uint32_t RsPhoto::FLAG_MSG_TYPE_MASK = 0x000f;
const uint32_t RsPhoto::FLAG_MSG_TYPE_PHOTO_POST = 0x0001;
const uint32_t RsPhoto::FLAG_MSG_TYPE_PHOTO_COMMENT = 0x0002;

View File

@ -1539,7 +1539,7 @@ void SharedFilesDialog::FilterItems()
if(text.length() < 3)
return ;
FileSearchFlags flags = isRemote()?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL;
//FileSearchFlags flags = isRemote()?RS_FILE_HINTS_REMOTE:RS_FILE_HINTS_LOCAL;
QStringList lst = text.split(" ",QString::SkipEmptyParts) ;
std::list<std::string> keywords ;

View File

@ -32,7 +32,7 @@ const uint32_t PostedCreateEnabledFlags = (
GXS_GROUP_FLAGS_DESCRIPTION |
GXS_GROUP_FLAGS_DISTRIBUTION |
// GXS_GROUP_FLAGS_PUBLISHSIGN |
GXS_GROUP_FLAGS_SHAREKEYS |
// GXS_GROUP_FLAGS_SHAREKEYS | // disabled because the UI doesn't handle it yet.
// GXS_GROUP_FLAGS_PERSONALSIGN |
// GXS_GROUP_FLAGS_COMMENTS |
0);
@ -84,7 +84,7 @@ void PostedGroupDialog::initUi()
break;
}
setUiText(UITYPE_KEY_SHARE_CHECKBOX, tr("Add Topic Admins"));
setUiText(UITYPE_ADD_ADMINS_CHECKBOX, tr("Add Topic Admins"));
setUiText(UITYPE_CONTACTS_DOCK, tr("Select Topic Admins"));
}

View File

@ -288,8 +288,14 @@ void FriendSelectionWidget::secured_fillList()
}
std::set<RsGxsId> gxsIdsSelected;
if (mShowTypes & SHOW_GXS)
{
selectedIds<RsGxsId,IDTYPE_GXS>(gxsIdsSelected,true);
if(!ui->friendList->topLevelItemCount()) // if not loaded yet, use the existing list.
gxsIdsSelected = mPreSelectedGxsIds;
}
std::set<RsGxsId> gxsIdsSelected2;
if (mShowTypes & SHOW_CONTACTS)
@ -683,6 +689,11 @@ void FriendSelectionWidget::requestGXSIdList()
mIdQueue->requestGroupInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, IDDIALOG_IDLIST);
}
template<> void FriendSelectionWidget::setSelectedIds<RsGxsId,FriendSelectionWidget::IDTYPE_GXS>(const std::set<RsGxsId>& ids, bool add)
{
mPreSelectedGxsIds = ids ;
requestGXSIdList();
}
void FriendSelectionWidget::groupsChanged(int /*type*/)
{

View File

@ -174,6 +174,8 @@ private:
std::vector<RsGxsGroupId> gxsIds ;
TokenQueue *mIdQueue ;
QList<QAction*> mContextMenuActions;
std::set<RsGxsId> mPreSelectedGxsIds; // because loading of GxsIds is asynchroneous we keep selected Ids from the client in a list here and use it to initialize after loading them.
};
Q_DECLARE_OPERATORS_FOR_FLAGS(FriendSelectionWidget::ShowTypes)

View File

@ -101,6 +101,7 @@ void GxsGroupDialog::init()
connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(submitGroup()));
connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(cancelDialog()));
connect(ui.pubKeyShare_cb, SIGNAL(clicked()), this, SLOT(setShareList()));
connect(ui.addAdmins_cb, SIGNAL(clicked()), this, SLOT(setAdminsList()));
connect(ui.groupLogo, SIGNAL(clicked() ), this , SLOT(addGroupLogo()));
connect(ui.addLogoButton, SIGNAL(clicked() ), this , SLOT(addGroupLogo()));
@ -115,14 +116,25 @@ void GxsGroupDialog::init()
if (!ui.pubKeyShare_cb->isChecked())
{
ui.contactsdockWidget->hide();
this->resize(this->size().width() - ui.contactsdockWidget->size().width(), this->size().height());
ui.shareKeyList->hide();
//this->resize(this->size().width() - ui.contactsdockWidget->size().width(), this->size().height());
}
if (!ui.addAdmins_cb->isChecked())
{
ui.adminsList->hide();
//this->resize(this->size().width() - ui.contactsdockWidget->size().width(), this->size().height());
}
/* initialize key share list */
ui.keyShareList->setHeaderText(tr("Contacts:"));
ui.keyShareList->setModus(FriendSelectionWidget::MODUS_CHECK);
ui.keyShareList->start();
ui.shareKeyList->setHeaderText(tr("Contacts:"));
ui.shareKeyList->setModus(FriendSelectionWidget::MODUS_CHECK);
ui.shareKeyList->start();
/* initialize key share list */
ui.adminsList->setHeaderText(tr("Moderators:"));
ui.adminsList->setModus(FriendSelectionWidget::MODUS_CHECK);
ui.adminsList->setShowType(FriendSelectionWidget::SHOW_GXS);
ui.adminsList->start();
/* Setup Reasonable Defaults */
@ -165,7 +177,8 @@ void GxsGroupDialog::setUiText(UiType uiType, const QString &text)
ui.pubKeyShare_cb->setText(text);
break;
case UITYPE_CONTACTS_DOCK:
ui.contactsdockWidget->setWindowTitle(text);
case UITYPE_ADD_ADMINS_CHECKBOX:
//ui.contactsdockWidget->setWindowTitle(text);
break;
case UITYPE_BUTTONBOX_OK:
ui.buttonBox->button(QDialogButtonBox::Ok)->setText(text);
@ -173,6 +186,23 @@ void GxsGroupDialog::setUiText(UiType uiType, const QString &text)
}
}
void GxsGroupDialog::setUiToolTip(UiType uiType, const QString &text)
{
switch (uiType)
{
case UITYPE_KEY_SHARE_CHECKBOX:
ui.pubKeyShare_cb->setToolTip(text);
break;
case UITYPE_ADD_ADMINS_CHECKBOX:
ui.addAdmins_cb->setToolTip(text);
break;
case UITYPE_BUTTONBOX_OK:
ui.buttonBox->button(QDialogButtonBox::Ok)->setToolTip(text);
default:
break;
}
}
void GxsGroupDialog::initMode()
{
setAllReadonly();
@ -345,6 +375,9 @@ void GxsGroupDialog::setupVisibility()
ui.publishGroupBox->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_PUBLISHSIGN);
ui.pubKeyShare_cb->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_SHAREKEYS);
ui.addAdmins_cb->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_ADDADMINS);
ui.label_8->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_ADDADMINS);
ui.moderatorsLabel->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_ADDADMINS);
ui.personalGroupBox->setVisible(mEnabledFlags & GXS_GROUP_FLAGS_PERSONALSIGN);
@ -828,6 +861,31 @@ QString GxsGroupDialog::getDescription()
return ui.groupDesc->toPlainText();
}
void GxsGroupDialog::getSelectedModerators(std::set<RsGxsId>& ids)
{
ui.adminsList->selectedIds<RsGxsId,FriendSelectionWidget::IDTYPE_GXS>(ids, true);
}
void GxsGroupDialog::setSelectedModerators(const std::set<RsGxsId>& ids)
{
ui.adminsList->setSelectedIds<RsGxsId,FriendSelectionWidget::IDTYPE_GXS>(ids, false);
QString moderatorsListString ;
RsIdentityDetails det;
for(auto it(ids.begin());it!=ids.end();++it)
{
rsIdentity->getIdDetails(*it,det);
if(!moderatorsListString.isNull())
moderatorsListString += ", " ;
moderatorsListString += det.mNickname.empty()?("[Unknown]"):QString::fromStdString(det.mNickname) ;
}
ui.moderatorsLabel->setText(moderatorsListString);
}
/***********************************************************************************
Share Lists.
***********************************************************************************/
@ -837,6 +895,20 @@ void GxsGroupDialog::sendShareList(std::string /*groupId*/)
close();
}
void GxsGroupDialog::setAdminsList()
{
if (ui.addAdmins_cb->isChecked())
{
//this->resize(this->size().width() + ui.contactsdockWidget->size().width(), this->size().height());
ui.adminsList->show();
}
else
{ // hide share widget
ui.adminsList->hide();
//this->resize(this->size().width() - ui.contactsdockWidget->size().width(), this->size().height());
}
}
void GxsGroupDialog::setShareList()
{
if (ui.pubKeyShare_cb->isChecked()) {

View File

@ -55,44 +55,45 @@ public:
/*** Group flags affect what is visually enabled that gets input into the grpMeta ***/
#define GXS_GROUP_FLAGS_NAME 0x00000001
#define GXS_GROUP_FLAGS_ICON 0x00000002
#define GXS_GROUP_FLAGS_DESCRIPTION 0x00000004
#define GXS_GROUP_FLAGS_DISTRIBUTION 0x00000008
#define GXS_GROUP_FLAGS_PUBLISHSIGN 0x00000010
#define GXS_GROUP_FLAGS_SHAREKEYS 0x00000020
#define GXS_GROUP_FLAGS_PERSONALSIGN 0x00000040
#define GXS_GROUP_FLAGS_COMMENTS 0x00000080
#define GXS_GROUP_FLAGS_EXTRA 0x00000100
#define GXS_GROUP_FLAGS_ANTI_SPAM 0x00000200
#define GXS_GROUP_FLAGS_NAME 0x00000001
#define GXS_GROUP_FLAGS_ICON 0x00000002
#define GXS_GROUP_FLAGS_DESCRIPTION 0x00000004
#define GXS_GROUP_FLAGS_DISTRIBUTION 0x00000008
#define GXS_GROUP_FLAGS_PUBLISHSIGN 0x00000010
#define GXS_GROUP_FLAGS_SHAREKEYS 0x00000020
#define GXS_GROUP_FLAGS_PERSONALSIGN 0x00000040
#define GXS_GROUP_FLAGS_COMMENTS 0x00000080
#define GXS_GROUP_FLAGS_EXTRA 0x00000100
#define GXS_GROUP_FLAGS_ANTI_SPAM 0x00000200
#define GXS_GROUP_FLAGS_ADDADMINS 0x00000400
/*** Default flags are used to determine privacy of group, signatures required ***
*** whether publish or id and whether comments are allowed or not ***/
#define GXS_GROUP_DEFAULTS_DISTRIB_MASK 0x0000000f
#define GXS_GROUP_DEFAULTS_PUBLISH_MASK 0x000000f0
#define GXS_GROUP_DEFAULTS_PERSONAL_MASK 0x00000f00
#define GXS_GROUP_DEFAULTS_COMMENTS_MASK 0x0000f000
#define GXS_GROUP_DEFAULTS_DISTRIB_MASK 0x0000000f
#define GXS_GROUP_DEFAULTS_PUBLISH_MASK 0x000000f0
#define GXS_GROUP_DEFAULTS_PERSONAL_MASK 0x00000f00
#define GXS_GROUP_DEFAULTS_COMMENTS_MASK 0x0000f000
#define GXS_GROUP_DEFAULTS_DISTRIB_PUBLIC 0x00000001
#define GXS_GROUP_DEFAULTS_DISTRIB_GROUP 0x00000002
#define GXS_GROUP_DEFAULTS_DISTRIB_LOCAL 0x00000004
#define GXS_GROUP_DEFAULTS_DISTRIB_PUBLIC 0x00000001
#define GXS_GROUP_DEFAULTS_DISTRIB_GROUP 0x00000002
#define GXS_GROUP_DEFAULTS_DISTRIB_LOCAL 0x00000004
#define GXS_GROUP_DEFAULTS_PUBLISH_OPEN 0x00000010
#define GXS_GROUP_DEFAULTS_PUBLISH_THREADS 0x00000020
#define GXS_GROUP_DEFAULTS_PUBLISH_REQUIRED 0x00000040
#define GXS_GROUP_DEFAULTS_PUBLISH_ENCRYPTED 0x00000080
#define GXS_GROUP_DEFAULTS_PUBLISH_OPEN 0x00000010
#define GXS_GROUP_DEFAULTS_PUBLISH_THREADS 0x00000020
#define GXS_GROUP_DEFAULTS_PUBLISH_REQUIRED 0x00000040
#define GXS_GROUP_DEFAULTS_PUBLISH_ENCRYPTED 0x00000080
#define GXS_GROUP_DEFAULTS_PERSONAL_PGP 0x00000100
#define GXS_GROUP_DEFAULTS_PERSONAL_REQUIRED 0x00000200
#define GXS_GROUP_DEFAULTS_PERSONAL_IFNOPUB 0x00000400
#define GXS_GROUP_DEFAULTS_PERSONAL_PGP 0x00000100
#define GXS_GROUP_DEFAULTS_PERSONAL_REQUIRED 0x00000200
#define GXS_GROUP_DEFAULTS_PERSONAL_IFNOPUB 0x00000400
#define GXS_GROUP_DEFAULTS_COMMENTS_YES 0x00001000
#define GXS_GROUP_DEFAULTS_COMMENTS_NO 0x00002000
#define GXS_GROUP_DEFAULTS_COMMENTS_YES 0x00001000
#define GXS_GROUP_DEFAULTS_COMMENTS_NO 0x00002000
#define GXS_GROUP_DEFAULTS_ANTISPAM_FAVOR_PGP 0x00100000
#define GXS_GROUP_DEFAULTS_ANTISPAM_TRACK 0x00200000
#define GXS_GROUP_DEFAULTS_ANTISPAM_FAVOR_PGP_KNOWN 0x00400000
#define GXS_GROUP_DEFAULTS_ANTISPAM_FAVOR_PGP 0x00100000
#define GXS_GROUP_DEFAULTS_ANTISPAM_TRACK 0x00200000
#define GXS_GROUP_DEFAULTS_ANTISPAM_FAVOR_PGP_KNOWN 0x00400000
/*!
* The aim of this dialog is to be convenient to encapsulate group
@ -121,6 +122,7 @@ public:
enum UiType {
UITYPE_SERVICE_HEADER,
UITYPE_KEY_SHARE_CHECKBOX,
UITYPE_ADD_ADMINS_CHECKBOX,
UITYPE_CONTACTS_DOCK,
UITYPE_BUTTONBOX_OK
};
@ -172,7 +174,14 @@ protected:
virtual QPixmap serviceImage() = 0;
virtual QIcon serviceWindowIcon();
void setUiText(UiType uiType, const QString &text);
/*!
* \brief setUiToolTip/setUiText
* Sets the text and tooltip of some parts of the UI
* \param uiType widget to set
* \param text text to set
*/
void setUiToolTip(UiType uiType, const QString &text);
void setUiText (UiType uiType, const QString &text);
/*!
* It is up to the service to do the actual group creation
@ -219,12 +228,20 @@ protected:
*/
QString getDescription();
/*!
* \brief getSelectedModerators
* Returns the set of ids that hve been selected as moderators.
*/
void getSelectedModerators(std::set<RsGxsId>& ids);
void setSelectedModerators(const std::set<RsGxsId>& ids);
private slots:
/* actions to take.... */
void cancelDialog();
// set private forum key share list
void setShareList();
void setAdminsList();
void updateCircleOptions();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1237</width>
<height>1145</height>
<height>1189</height>
</rect>
</property>
<property name="windowTitle">
@ -44,7 +44,7 @@
<enum>QFrame::Raised</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="createmode">
<layout class="QGridLayout" name="gridLayout_8">
@ -172,13 +172,23 @@
<string>Will be used to send feedback</string>
</property>
<property name="text">
<string>Owner:</string>
<string>Contact:</string>
</property>
</widget>
</item>
<item>
<widget class="GxsIdChooser" name="idChooser"/>
</item>
<item>
<widget class="QCheckBox" name="addAdmins_cb">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Add moderators</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pubKeyShare_cb">
<property name="toolTip">
@ -196,102 +206,6 @@
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QDockWidget" name="contactsdockWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>524287</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>check peers you would like to share private publish key with</string>
</property>
<property name="floating">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Share Key With</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QGridLayout" name="_2">
<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>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="FriendSelectionWidget" name="keyShareList" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>4</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>300</width>
<height>16777215</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>220</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="distribGroupBox">
<property name="sizePolicy">
@ -435,7 +349,7 @@
</layout>
</widget>
</item>
<item row="2" column="0">
<item row="4" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Description</string>
@ -447,7 +361,7 @@
</layout>
</widget>
</item>
<item row="3" column="0">
<item row="5" column="0">
<widget class="QGroupBox" name="publishGroupBox">
<property name="title">
<string>Publish Signatures</string>
@ -512,7 +426,7 @@
</layout>
</widget>
</item>
<item row="4" column="0">
<item row="6" column="0">
<widget class="QGroupBox" name="personalGroupBox">
<property name="title">
<string>Personal Signatures</string>
@ -555,7 +469,7 @@
</layout>
</widget>
</item>
<item row="5" column="0">
<item row="7" column="0">
<widget class="QGroupBox" name="commentGroupBox">
<property name="title">
<string>Comments</string>
@ -606,7 +520,7 @@
</layout>
</widget>
</item>
<item row="6" column="0">
<item row="8" column="0">
<widget class="QGroupBox" name="spamProtection_GB">
<property name="title">
<string>Spam-protection</string>
@ -657,7 +571,7 @@
</layout>
</widget>
</item>
<item row="7" column="0" colspan="2">
<item row="9" column="0" colspan="2">
<widget class="QFrame" name="extraFrame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
@ -667,6 +581,12 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="FriendSelectionWidget" name="adminsList" native="true"/>
</item>
<item row="3" column="0">
<widget class="FriendSelectionWidget" name="shareKeyList" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="showmode">
@ -676,150 +596,168 @@
<property name="title">
<string>Info</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="nameline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Popularity</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="popline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Posts</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="postsline">
<property name="text">
<string/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Last Post</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lastpostline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Author</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="GxsIdLabel" name="authorLabel">
<property name="text">
<string>GxsIdLabel</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>ID</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="IDline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0" rowspan="2">
<widget class="QLabel" name="commentsLabel">
<property name="text">
<string>Comments:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="commentsValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="7" column="1" rowspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="distributionValueLabel">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="9" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Moderators:</string>
</property>
</widget>
</item>
<item row="7" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_10">
<item>
<widget class="QLabel" name="distributionValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="GxsCircleChooser" name="distributionCircleComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="postsline">
<property name="text">
<string/>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="popline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Posts</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="GxsIdLabel" name="authorLabel">
<property name="text">
<string>GxsIdLabel</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>ID</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLineEdit" name="lastpostline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Author</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="antiSpamValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="GxsCircleChooser" name="distributionCircleComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="5" column="2">
<widget class="QLineEdit" name="IDline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="commentsLabel">
<property name="text">
<string>Comments:</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="commentsValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="distributionLabel">
<property name="text">
<string>Distribution:</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="antiSpamLabel">
<property name="text">
<string>Anti Spam:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Last Post</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Popularity</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="nameline">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QLabel" name="moderatorsLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="distributionLabel">
<property name="text">
<string>Distribution:</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="antiSpamLabel">
<property name="text">
<string>Anti Spam:</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="antiSpamValueLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -870,7 +870,7 @@ QString GxsIdDetails::getFailedText(const RsGxsId &id)
QString GxsIdDetails::getEmptyIdText()
{
return QApplication::translate("GxsIdDetails", "No Signature");
return QApplication::translate("GxsIdDetails", "[None]");
}
QString GxsIdDetails::getNameForType(GxsIdDetailsType type, const RsIdentityDetails &details)

View File

@ -34,7 +34,7 @@ const uint32_t ChannelCreateEnabledFlags = (
GXS_GROUP_FLAGS_DESCRIPTION |
GXS_GROUP_FLAGS_DISTRIBUTION |
// GXS_GROUP_FLAGS_PUBLISHSIGN |
GXS_GROUP_FLAGS_SHAREKEYS |
// GXS_GROUP_FLAGS_SHAREKEYS | // disabled because the UI doesn't handle it, so no need to show the disabled button. The user can do it in a second step from the channel menu.
// GXS_GROUP_FLAGS_PERSONALSIGN |
GXS_GROUP_FLAGS_COMMENTS |
0);
@ -75,19 +75,19 @@ void GxsChannelGroupDialog::initUi()
{
case MODE_CREATE:
setUiText(UITYPE_SERVICE_HEADER, tr("Create New Channel"));
setUiText(UITYPE_BUTTONBOX_OK, tr("Create"));
setUiText(UITYPE_BUTTONBOX_OK, tr("Create"));
break;
case MODE_SHOW:
setUiText(UITYPE_SERVICE_HEADER, tr("Channel"));
break;
case MODE_EDIT:
setUiText(UITYPE_SERVICE_HEADER, tr("Edit Channel"));
setUiText(UITYPE_BUTTONBOX_OK, tr("Update Channel"));
setUiText(UITYPE_BUTTONBOX_OK, tr("Update Channel"));
break;
}
setUiText(UITYPE_KEY_SHARE_CHECKBOX, tr("Add Channel Admins"));
setUiText(UITYPE_CONTACTS_DOCK, tr("Select Channel Admins"));
setUiText(UITYPE_CONTACTS_DOCK, tr("Select Channel Admins"));
}
QPixmap GxsChannelGroupDialog::serviceImage()

View File

@ -50,8 +50,9 @@
//#define ENABLE_GENERATE
/** Constructor */
CreateGxsForumMsg::CreateGxsForumMsg(const RsGxsGroupId &fId, const RsGxsMessageId &pId,const RsGxsMessageId& mOId,const RsGxsId& posterId)
: QDialog(NULL, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint), mForumId(fId), mParentId(pId), mOrigMsgId(mOId),mPosterId(posterId)
CreateGxsForumMsg::CreateGxsForumMsg(const RsGxsGroupId &fId, const RsGxsMessageId &pId, const RsGxsMessageId& mOId, const RsGxsId& posterId, bool isModerating)
: QDialog(NULL, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint),
mForumId(fId), mParentId(pId), mOrigMsgId(mOId),mPosterId(posterId),mIsModerating(isModerating)
{
/* Invoke the Qt Designer generated object setup routine */
ui.setupUi(this);
@ -355,7 +356,9 @@ void CreateGxsForumMsg::createMsg()
msg.mMeta.mGroupId = mForumId;
msg.mMeta.mParentId = mParentId;
msg.mMeta.mOrigMsgId = mOrigMsgId;
msg.mMeta.mMsgFlags = mIsModerating?RS_GXS_FORUM_MSG_FLAGS_MODERATED : 0;
msg.mMeta.mMsgId.clear() ;
if (mParentMsgLoaded) {
msg.mMeta.mThreadId = mParentMsg.mMeta.mThreadId;
}//if (mParentMsgLoaded)

View File

@ -36,7 +36,7 @@ class CreateGxsForumMsg : public QDialog, public TokenResponse
Q_OBJECT
public:
CreateGxsForumMsg(const RsGxsGroupId &fId, const RsGxsMessageId &pId, const RsGxsMessageId &moId, const RsGxsId &posterId = RsGxsId());
CreateGxsForumMsg(const RsGxsGroupId &fId, const RsGxsMessageId &pId, const RsGxsMessageId &moId, const RsGxsId &posterId = RsGxsId(),bool isModerating=false);
~CreateGxsForumMsg();
void newMsg(); /* cleanup */
@ -75,6 +75,7 @@ private:
bool mOrigMsgLoaded;
bool mForumMetaLoaded;
bool mForumCircleLoaded ;
bool mIsModerating; // means that the msg has a orig author Id that is not the Id of the author
RsGxsForumMsg mParentMsg;
RsGxsForumMsg mOrigMsg;

View File

@ -27,12 +27,13 @@
// To start with we only have open forums - with distribution controls.
const uint32_t ForumCreateEnabledFlags = (
GXS_GROUP_FLAGS_NAME |
// GXS_GROUP_FLAGS_ICON |
GXS_GROUP_FLAGS_NAME |
// GXS_GROUP_FLAGS_ICON |
GXS_GROUP_FLAGS_DESCRIPTION |
GXS_GROUP_FLAGS_DISTRIBUTION |
// GXS_GROUP_FLAGS_PUBLISHSIGN |
GXS_GROUP_FLAGS_SHAREKEYS |
// GXS_GROUP_FLAGS_PUBLISHSIGN|
// GXS_GROUP_FLAGS_SHAREKEYS |
GXS_GROUP_FLAGS_ADDADMINS |
GXS_GROUP_FLAGS_ANTI_SPAM |
// GXS_GROUP_FLAGS_PERSONALSIGN |
// GXS_GROUP_FLAGS_COMMENTS |
@ -42,17 +43,17 @@ const uint32_t ForumCreateDefaultsFlags = ( GXS_GROUP_DEFAULTS_DISTRIB_PUBLIC
//GXS_GROUP_DEFAULTS_DISTRIB_GROUP |
//GXS_GROUP_DEFAULTS_DISTRIB_LOCAL |
GXS_GROUP_DEFAULTS_PUBLISH_OPEN |
GXS_GROUP_DEFAULTS_PUBLISH_OPEN |
//GXS_GROUP_DEFAULTS_PUBLISH_THREADS |
//GXS_GROUP_DEFAULTS_PUBLISH_REQUIRED |
//GXS_GROUP_DEFAULTS_PUBLISH_ENCRYPTED |
//GXS_GROUP_DEFAULTS_PERSONAL_PGP |
GXS_GROUP_DEFAULTS_PERSONAL_REQUIRED |
GXS_GROUP_DEFAULTS_PERSONAL_REQUIRED |
//GXS_GROUP_DEFAULTS_PERSONAL_IFNOPUB |
//GXS_GROUP_DEFAULTS_COMMENTS_YES |
GXS_GROUP_DEFAULTS_COMMENTS_NO |
GXS_GROUP_DEFAULTS_COMMENTS_NO |
0);
const uint32_t ForumEditEnabledFlags = ForumCreateEnabledFlags;
@ -61,11 +62,13 @@ const uint32_t ForumEditDefaultsFlags = ForumCreateDefaultsFlags;
GxsForumGroupDialog::GxsForumGroupDialog(TokenQueue *tokenQueue, QWidget *parent)
: GxsGroupDialog(tokenQueue, ForumCreateEnabledFlags, ForumCreateDefaultsFlags, parent)
{
ui.pubKeyShare_cb->setEnabled(true) ;
}
GxsForumGroupDialog::GxsForumGroupDialog(TokenQueue *tokenExternalQueue, RsTokenService *tokenService, Mode mode, RsGxsGroupId groupId, QWidget *parent)
: GxsGroupDialog(tokenExternalQueue, tokenService, mode, groupId, ForumEditEnabledFlags, ForumEditDefaultsFlags, parent)
{
ui.pubKeyShare_cb->setEnabled(true) ;
}
void GxsForumGroupDialog::initUi()
@ -85,8 +88,10 @@ void GxsForumGroupDialog::initUi()
break;
}
setUiText(UITYPE_KEY_SHARE_CHECKBOX, tr("Add Forum Admins"));
setUiText(UITYPE_CONTACTS_DOCK, tr("Select Forum Admins"));
setUiToolTip(UITYPE_ADD_ADMINS_CHECKBOX,tr("Forum moderators can edit/delete/pinup others posts"));
//setUiText(UITYPE_KEY_SHARE_CHECKBOX, tr("Add Forum Admins"));
//setUiText(UITYPE_CONTACTS_DOCK, tr("Select Forum Admins"));
}
QPixmap GxsForumGroupDialog::serviceImage()
@ -100,6 +105,7 @@ bool GxsForumGroupDialog::service_CreateGroup(uint32_t &token, const RsGroupMeta
RsGxsForumGroup grp;
grp.mMeta = meta;
grp.mDescription = getDescription().toUtf8().constData();
getSelectedModerators(grp.mAdminList.ids);
rsGxsForums->createGroup(token, grp);
return true;
@ -107,10 +113,15 @@ bool GxsForumGroupDialog::service_CreateGroup(uint32_t &token, const RsGroupMeta
bool GxsForumGroupDialog::service_EditGroup(uint32_t &token, RsGroupMetaData &editedMeta)
{
RsGxsForumGroup grp;
RsGxsForumGroup grp(mGroupData); // start again from cached information. That allows to keep the pinned posts for instance.
// now replace data by locally edited/changed information
grp.mMeta = editedMeta;
grp.mDescription = getDescription().toUtf8().constData();
getSelectedModerators(grp.mAdminList.ids);
std::cerr << "GxsForumGroupDialog::service_EditGroup() submitting changes";
std::cerr << std::endl;
@ -124,6 +135,7 @@ bool GxsForumGroupDialog::service_loadGroup(uint32_t token, Mode /*mode*/, RsGro
std::cerr << std::endl;
std::vector<RsGxsForumGroup> groups;
if (!rsGxsForums->getGroupData(token, groups))
{
std::cerr << "GxsForumGroupDialog::service_loadGroup() Error getting GroupData";
@ -141,8 +153,16 @@ bool GxsForumGroupDialog::service_loadGroup(uint32_t token, Mode /*mode*/, RsGro
std::cerr << "GxsForumsGroupDialog::service_loadGroup() Unfinished Loading";
std::cerr << std::endl;
// Information handled by GxsGroupDialog. description should rather be handled here in the service part!
groupMetaData = groups[0].mMeta;
description = QString::fromUtf8(groups[0].mDescription.c_str());
// Local information. Description should be handled here.
setSelectedModerators(groups[0].mAdminList.ids);
mGroupData = groups[0]; // keeps the private information
return true;
}

View File

@ -39,6 +39,9 @@ protected:
virtual bool service_CreateGroup(uint32_t &token, const RsGroupMetaData &meta);
virtual bool service_loadGroup(uint32_t token, Mode mode, RsGroupMetaData& groupMetaData, QString &description);
virtual bool service_EditGroup(uint32_t &token, RsGroupMetaData &editedMeta);
private:
RsGxsForumGroup mGroupData;
};
#endif

View File

@ -88,6 +88,7 @@
#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
@ -99,6 +100,7 @@
#define ROLE_THREAD_READCHILDREN Qt::UserRole + 4
#define ROLE_THREAD_UNREADCHILDREN Qt::UserRole + 5
#define ROLE_THREAD_SORT Qt::UserRole + 6
#define ROLE_THREAD_PINNED Qt::UserRole + 7
#define ROLE_THREAD_COUNT 4
@ -246,8 +248,6 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget
ttheader->resizeSection (COLUMN_THREAD_DISTRIBUTION, 24*f);
ttheader->resizeSection (COLUMN_THREAD_AUTHOR, 150*f);
ui->threadTreeWidget->sortItems(COLUMN_THREAD_DATE, Qt::DescendingOrder);
/* Set text of column "Read" to empty - without this the column has a number as header text */
QTreeWidgetItem *headerItem = ui->threadTreeWidget->headerItem();
headerItem->setText(COLUMN_THREAD_READ, "") ;
@ -297,6 +297,7 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget
forum visible to all other friends.</p><p>Afterwards you can unsubscribe from the context menu of the forum list at left.</p>"));
ui->threadTreeWidget->enableColumnCustomize(true);
ui->threadTreeWidget->sortItems(COLUMN_THREAD_DATE, Qt::DescendingOrder);
}
void GxsForumThreadWidget::blank()
@ -511,10 +512,15 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
}
QMenu contextMnu(this);
QList<QTreeWidgetItem*> selectedItems = ui->threadTreeWidget->selectedItems();
QAction *editAct = new QAction(QIcon(IMAGE_MESSAGEEDIT), tr("Edit"), &contextMnu);
connect(editAct, SIGNAL(triggered()), this, SLOT(editforummessage()));
bool is_pinned = mForumGroup.mPinnedPosts.ids.find(mThreadId) != mForumGroup.mPinnedPosts.ids.end();
QAction *pinUpPostAct = new QAction(QIcon(IMAGE_MESSAGE), (is_pinned?tr("Un-pin this post"):tr("Pin this post up")), &contextMnu);
connect(pinUpPostAct , SIGNAL(triggered()), this, SLOT(togglePinUpPost()));
QAction *replyAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply"), &contextMnu);
connect(replyAct, SIGNAL(triggered()), this, SLOT(replytoforummessage()));
@ -604,16 +610,37 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
replyauthorAct->setDisabled (true);
}
QList<QTreeWidgetItem*> selectedItems = ui->threadTreeWidget->selectedItems();
if(selectedItems.size() == 1)
{
QTreeWidgetItem *item = *selectedItems.begin();
GxsIdRSTreeWidgetItem *gxsIdItem = dynamic_cast<GxsIdRSTreeWidgetItem*>(item);
RsGxsId author_id;
if(gxsIdItem && gxsIdItem->getId(author_id) && rsIdentity->isOwnId(author_id))
contextMnu.addAction(editAct);
bool is_pinned = mForumGroup.mPinnedPosts.ids.find( RsGxsMessageId(item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString()) ) != mForumGroup.mPinnedPosts.ids.end();
if(!is_pinned)
{
RsGxsId author_id;
if(gxsIdItem && gxsIdItem->getId(author_id) && rsIdentity->isOwnId(author_id))
contextMnu.addAction(editAct);
else
{
// Go through the list of own ids and see if one of them is a moderator
// TODO: offer to select which moderator ID to use if multiple IDs fit the conditions of the forum
std::list<RsGxsId> own_ids ;
rsIdentity->getOwnIds(own_ids) ;
for(auto it(own_ids.begin());it!=own_ids.end();++it)
if(mForumGroup.mAdminList.ids.find(*it) != mForumGroup.mAdminList.ids.end())
{
contextMnu.addAction(editAct);
break ;
}
}
}
if(IS_GROUP_ADMIN(mSubscribeFlags) && (*selectedItems.begin())->parent() == NULL)
contextMnu.addAction(pinUpPostAct);
}
contextMnu.addAction(replyAct);
@ -660,6 +687,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
contextMnu.addAction(showinpeopleAct);
contextMnu.addAction(replyauthorAct);
}
}
contextMnu.exec(QCursor::pos());
@ -783,6 +811,7 @@ void GxsForumThreadWidget::calculateIconsAndFonts(QTreeWidgetItem *item, bool &h
bool isNew = IS_MSG_NEW(status);
bool unread = IS_MSG_UNREAD(status);
bool missing = item->data(COLUMN_THREAD_DATA, ROLE_THREAD_MISSING).toBool();
RsGxsMessageId msgId(item->data(COLUMN_THREAD_MSGID,Qt::DisplayRole).toString().toStdString());
// set icon
if (missing) {
@ -811,6 +840,8 @@ void GxsForumThreadWidget::calculateIconsAndFonts(QTreeWidgetItem *item, bool &h
calculateIconsAndFonts(item->child(index), myReadChilddren, myUnreadChilddren);
}
bool is_pinned = mForumGroup.mPinnedPosts.ids.find(msgId) != mForumGroup.mPinnedPosts.ids.end();
// set font
for (int i = 0; i < COLUMN_THREAD_COUNT; ++i) {
QFont qf = item->font(i);
@ -832,6 +863,15 @@ void GxsForumThreadWidget::calculateIconsAndFonts(QTreeWidgetItem *item, bool &h
/* Missing message */
item->setForeground(i, textColorMissing());
}
if(is_pinned)
{
qf.setBold(true);
item->setForeground(i, textColorUnread());
item->setData(i,Qt::BackgroundRole, QBrush(QColor(255,200,180))) ;
}
else
item->setData(i,Qt::BackgroundRole, QBrush());
item->setFont(i, qf);
}
@ -968,6 +1008,10 @@ static QString getDurationString(uint32_t days)
else if(IS_GROUP_PGP_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous posts forwarded if reputation is positive");
tw->mForumDescription = QString("<b>%1: \t</b>%2<br/>").arg(tr("Forum name"), QString::fromUtf8( group.mMeta.mGroupName.c_str()));
tw->mForumDescription += QString("<b>%1: </b>%2<br/>").arg(tr("Description"),
group.mDescription.empty()?
tr("[None]<br/>")
:(QString::fromUtf8(group.mDescription.c_str())+"<br/>"));
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Subscribers")).arg(group.mMeta.mPop);
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Posts (at neighbor nodes)")).arg(group.mMeta.mVisibleMsgCount);
if(group.mMeta.mLastPost==0)
@ -1014,21 +1058,31 @@ static QString getDurationString(uint32_t days)
}
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Distribution"), distrib_string);
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Author"), author);
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Contact"), author);
if(!anti_spam_features1.isNull())
tw->mForumDescription += QString("<b>%1: \t</b>%2<br/>").arg(tr("Anti-spam")).arg(anti_spam_features1);
tw->mForumDescription += QString("<b>%1: </b><br/><br/>%2").arg(tr("Description"), QString::fromUtf8(group.mDescription.c_str()));
tw->ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(tw->mSubscribeFlags));
tw->mStateHelper->setWidgetEnabled(tw->ui->newthreadButton, (IS_GROUP_SUBSCRIBED(tw->mSubscribeFlags)));
if (tw->mThreadId.isNull() && !tw->mStateHelper->isLoading(tw->mTokenTypeMessageData))
if(!group.mAdminList.ids.empty())
{
//ui->threadTitle->setText(tr("Forum Description"));
tw->ui->postText->setText(tw->mForumDescription);
QString admin_list_str ;
for(auto it(group.mAdminList.ids.begin());it!=group.mAdminList.ids.end();++it)
{
RsIdentityDetails det ;
rsIdentity->getIdDetails(*it,det);
admin_list_str += (admin_list_str.isNull()?"":", ") + QString::fromUtf8(det.mNickname.c_str()) ;
}
tw->mForumDescription += QString("<b>%1: </b>%2").arg(tr("Moderators"), admin_list_str);
}
if (tw->mThreadId.isNull() && !tw->mStateHelper->isLoading(tw->mTokenTypeMessageData))
tw->ui->postText->setText(tw->mForumDescription);
}
void GxsForumThreadWidget::fillThreadFinished()
@ -1167,11 +1221,49 @@ void GxsForumThreadWidget::fillThreadStatus(QString text)
ui->progressText->setText(text);
}
//#define DEBUG_PINNED_POST_SORTING 1
class ForumThreadItem: public GxsIdRSTreeWidgetItem
{
public:
ForumThreadItem(QHeaderView *header,const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask,QTreeWidget *parent = NULL)
: GxsIdRSTreeWidgetItem(compareRole,icon_mask,parent), m_header(header) {}
bool operator<(const QTreeWidgetItem& other) const
{
bool left_is_not_pinned = ! data(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED).toBool();
bool right_is_not_pinned = !other.data(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED).toBool();
#ifdef DEBUG_PINNED_POST_SORTING
std::cerr << "Comparing item date \"" << data(COLUMN_THREAD_DATE,Qt::DisplayRole).toString().toStdString() << "\" ("
<< data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toUInt() << ", \"" << data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toString().toStdString() << "\" --> " << left_is_not_pinned << ") to \""
<< other.data(COLUMN_THREAD_DATE,Qt::DisplayRole).toString().toStdString() << "\" ("
<< other.data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toUInt() << ", \"" << other.data(COLUMN_THREAD_DATE,ROLE_THREAD_SORT).toString().toStdString() << "\" --> " << right_is_not_pinned << ") ";
#endif
if(left_is_not_pinned ^ right_is_not_pinned)
{
#ifdef DEBUG_PINNED_POST_SORTING
std::cerr << "Local: " << ((m_header->sortIndicatorOrder()==Qt::AscendingOrder)?right_is_not_pinned:left_is_not_pinned) << std::endl;
#endif
return (m_header->sortIndicatorOrder()==Qt::AscendingOrder)?right_is_not_pinned:left_is_not_pinned ; // always put pinned posts on top
}
#ifdef DEBUG_PINNED_POST_SORTING
std::cerr << "Remote: " << GxsIdRSTreeWidgetItem::operator<(other) << std::endl;
#endif
return GxsIdRSTreeWidgetItem::operator<(other);
}
private:
QHeaderView *m_header ;
};
QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForumMsg &msg, bool useChildTS, uint32_t filterColumn, QTreeWidgetItem *parent)
{
// Early check for a message that should be hidden because its author
// is flagged with a bad reputation
bool is_pinned = mForumGroup.mPinnedPosts.ids.find(msg.mMeta.mMsgId) != mForumGroup.mPinnedPosts.ids.end();
uint32_t idflags =0;
RsReputations::ReputationLevel reputation_level = rsReputations->overallReputationLevel(msg.mMeta.mAuthorId,&idflags) ;
@ -1179,11 +1271,15 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum
redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE);
GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR );
// We use a specific item model for forums in order to handle the post pinning.
GxsIdRSTreeWidgetItem *item = new ForumThreadItem(ui->threadTreeWidget->header(),mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR );
item->moveToThread(ui->threadTreeWidget->thread());
if(redacted)
item->setText(COLUMN_THREAD_TITLE, tr("[ ... Redacted message ... ]"));
else if(is_pinned)
item->setText(COLUMN_THREAD_TITLE, tr("[PINNED] ") + QString::fromUtf8(msg.mMeta.mMsgName.c_str()));
else
item->setText(COLUMN_THREAD_TITLE, QString::fromUtf8(msg.mMeta.mMsgName.c_str()));
@ -1220,30 +1316,42 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum
qtime.setTime_t(msg.mMeta.mPublishTs);
QString itemText = DateTime::formatDateTime(qtime);
// 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.
//#define SHOW_COMBINED_DATES 1
if (useChildTS)
{
for(QTreeWidgetItem *grandParent = parent; grandParent!=NULL; grandParent = grandParent->parent())
{
//Update Parent Child TimeStamp
QString oldTSText = grandParent->text(COLUMN_THREAD_DATE);
QString oldTSSort = grandParent->data(COLUMN_THREAD_DATE, ROLE_THREAD_SORT).toString();
QString oldCTSText = oldTSText.split("|").at(0);
QString oldPTSText = oldTSText.contains("|") ? oldTSText.split(" | ").at(1) : oldCTSText;//If first time parent get only its mPublishTs
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);
}
}
}
item->setText(COLUMN_THREAD_DATE, itemText);
item->setData(COLUMN_THREAD_DATE, ROLE_THREAD_SORT, itemSort);
item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_SORT, itemSort);
if(is_pinned)
item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED, QVariant(true)); // this is used by the sorting model to put all posts on top
else
item->setData(COLUMN_THREAD_DATE,ROLE_THREAD_PINNED, QVariant(false));
// Set later with GxsIdRSTreeWidgetItem::setId
item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_AUTHOR, QString::fromStdString(msg.mMeta.mAuthorId.toStdString()));
@ -2080,6 +2188,37 @@ void GxsForumThreadWidget::createmessage()
/* window will destroy itself! */
}
void GxsForumThreadWidget::togglePinUpPost()
{
if (groupId().isNull() || mThreadId.isNull())
return;
QTreeWidgetItem *item = ui->threadTreeWidget->currentItem();
// normally this method is only called on top level items. We still check it just in case...
if(item->parent() != NULL)
{
std::cerr << "(EE) togglePinUpPost() called on non top level post. This is inconsistent." << std::endl;
return ;
}
QString thread_title = (item != NULL)?item->text(COLUMN_THREAD_TITLE):QString() ;
std::cerr << "Toggling Pin-up state of post " << mThreadId.toStdString() << ": \"" << thread_title.toStdString() << "\"" << std::endl;
if(mForumGroup.mPinnedPosts.ids.find(mThreadId) == mForumGroup.mPinnedPosts.ids.end())
mForumGroup.mPinnedPosts.ids.insert(mThreadId) ;
else
mForumGroup.mPinnedPosts.ids.erase(mThreadId) ;
uint32_t token;
rsGxsForums->updateGroup(token,mForumGroup);
ui->threadTreeWidget->takeTopLevelItem(ui->threadTreeWidget->indexOfTopLevelItem(item)); // forces the re-creation of all posts widgets. A bit extreme. We should rather only delete item above
updateDisplay(true) ;
}
void GxsForumThreadWidget::createthread()
{
if (groupId().isNull ()) {
@ -2223,9 +2362,30 @@ void GxsForumThreadWidget::editForumMessageData(const RsGxsForumMsg& msg)
return;
}
// Go through the list of own ids and see if one of them is a moderator
// TODO: offer to select which moderator ID to use if multiple IDs fit the conditions of the forum
RsGxsId moderator_id ;
std::list<RsGxsId> own_ids ;
rsIdentity->getOwnIds(own_ids) ;
for(auto it(own_ids.begin());it!=own_ids.end();++it)
if(mForumGroup.mAdminList.ids.find(*it) != mForumGroup.mAdminList.ids.end())
{
moderator_id = *it;
break;
}
// Check that author is in own ids, if not use the moderator id that was collected among own ids.
bool is_own = false ;
for(auto it(own_ids.begin());it!=own_ids.end() && !is_own;++it)
if(*it == msg.mMeta.mAuthorId)
is_own = true ;
if (!msg.mMeta.mAuthorId.isNull())
{
CreateGxsForumMsg *cfm = new CreateGxsForumMsg(groupId(), msg.mMeta.mParentId, msg.mMeta.mMsgId, msg.mMeta.mAuthorId);
CreateGxsForumMsg *cfm = new CreateGxsForumMsg(groupId(), msg.mMeta.mParentId, msg.mMeta.mMsgId, is_own?(msg.mMeta.mAuthorId):moderator_id,!is_own);
cfm->insertPastedText(QString::fromUtf8(msg.mMsg.c_str())) ;
cfm->show();

View File

@ -108,6 +108,7 @@ private slots:
void subscribeGroup(bool subscribe);
void createthread();
void togglePinUpPost();
void createmessage();
void previousMessage();

View File

@ -182,7 +182,7 @@
<widget class="QComboBox" name="viewBox">
<item>
<property name="text">
<string>Last Post</string>
<string>Lastest post in thread</string>
</property>
</item>
<item>
@ -577,8 +577,8 @@
</customwidget>
</customwidgets>
<resources>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
<include location="../images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -94,57 +94,101 @@ static bool decreasing_time_comp(const QPair<time_t,RsGxsMessageId>& e1,const QP
void GxsForumsFillThread::run()
{
RsTokenService *service = rsGxsForums->getTokenService();
uint32_t msg_token;
uint32_t grp_token;
emit status(tr("Waiting"));
/* get all messages of the forum */
RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA;
{
/* get all messages of the forum */
RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA;
std::list<RsGxsGroupId> grpIds;
grpIds.push_back(mForumId);
std::list<RsGxsGroupId> grpIds;
grpIds.push_back(mForumId);
#ifdef DEBUG_FORUMS
std::cerr << "GxsForumsFillThread::run() forum id " << mForumId << std::endl;
std::cerr << "GxsForumsFillThread::run() forum id " << mForumId << std::endl;
#endif
uint32_t token;
service->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds);
service->requestMsgInfo(msg_token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds);
/* wait for the answer */
uint32_t requestStatus = RsTokenService::PENDING;
while (!wasStopped()) {
requestStatus = service->requestStatus(token);
if (requestStatus == RsTokenService::FAILED ||
requestStatus == RsTokenService::COMPLETE) {
break;
/* wait for the answer */
uint32_t requestStatus = RsTokenService::PENDING;
while (!wasStopped()) {
requestStatus = service->requestStatus(msg_token);
if (requestStatus == RsTokenService::FAILED ||
requestStatus == RsTokenService::COMPLETE) {
break;
}
msleep(200);
}
msleep(100);
if (requestStatus == RsTokenService::FAILED)
return;
}
if (wasStopped()) {
// also get the forum meta data.
{
RsTokReqOptions opts;
opts.mReqType = GXS_REQUEST_TYPE_GROUP_DATA;
std::list<RsGxsGroupId> grpIds;
grpIds.push_back(mForumId);
#ifdef DEBUG_FORUMS
std::cerr << "GxsForumsFillThread::run() forum id " << mForumId << std::endl;
#endif
service->requestGroupInfo(grp_token, RS_TOKREQ_ANSTYPE_DATA, opts, grpIds);
/* wait for the answer */
uint32_t requestStatus = RsTokenService::PENDING;
while (!wasStopped()) {
requestStatus = service->requestStatus(grp_token);
if (requestStatus == RsTokenService::FAILED ||
requestStatus == RsTokenService::COMPLETE) {
break;
}
msleep(200);
}
if (requestStatus == RsTokenService::FAILED)
return;
}
if (wasStopped())
{
#ifdef DEBUG_FORUMS
std::cerr << "GxsForumsFillThread::run() thread stopped, cancel request" << std::endl;
#endif
/* cancel request */
service->cancelRequest(token);
service->cancelRequest(msg_token);
service->cancelRequest(grp_token);
return;
}
if (requestStatus == RsTokenService::FAILED) {
//#TODO
return;
}
//#TODO
// if (failed) {
// mService->cancelRequest(token);
// return;
// }
emit status(tr("Retrieving"));
std::vector<RsGxsForumGroup> forum_groups;
if (!rsGxsForums->getGroupData(grp_token, forum_groups) || forum_groups.size() != 1)
return;
RsGxsForumGroup forum_group = *forum_groups.begin();
//#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;
@ -152,9 +196,8 @@ void GxsForumsFillThread::run()
std::vector<RsGxsForumMsg> msgs_array;
if (!rsGxsForums->getMsgData(token, msgs_array)) {
if (!rsGxsForums->getMsgData(msg_token, msgs_array))
return;
}
// now put everything into a map in order to make search log(n)
@ -178,11 +221,11 @@ void GxsForumsFillThread::run()
// 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.
// 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.
std::list<std::pair< RsGxsMessageId, QTreeWidgetItem* > > threadStack;
std::map<RsGxsMessageId,std::list<RsGxsMessageId> > kids_array ;
std::set<RsGxsMessageId> missing_parents;
@ -209,11 +252,17 @@ void GxsForumsFillThread::run()
if(msgIt2 == msgs.end())
continue ;
// Make sure that the author is the same than the original message. This should always happen, but nothing can prevent someone to
// craft a new version of a message with his own signature.
// 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)
continue ;
{
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
@ -290,7 +339,7 @@ void GxsForumsFillThread::run()
// 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.
@ -391,7 +440,7 @@ void GxsForumsFillThread::run()
{
// 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())