/******************************************************************************* * gui/common/GroupTreeWidget.cpp * * * * Copyright (C) 2010, Retroshare Team * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * * * * You should have received a copy of the GNU Affero General Public License * * along with this program. If not, see . * * * *******************************************************************************/ #include "GroupTreeWidget.h" #include "ui_GroupTreeWidget.h" #include "gui/common/FilesDefs.h" #include "retroshare/rsgxsflags.h" #include "PopularityDefs.h" #include "RSElidedItemDelegate.h" #include "RSTreeWidgetItem.h" #include "gui/settings/rsharesettings.h" #include "util/QtVersion.h" #include "util/DateTime.h" #include #include #include #include Q_GUI_EXPORT int qt_defaultDpi(); #define ROLE_ID Qt::UserRole #define ROLE_NAME Qt::UserRole + 1 #define ROLE_DESCRIPTION Qt::UserRole + 2 #define ROLE_SUBSCRIBE_FLAGS Qt::UserRole + 3 #define ROLE_COLOR Qt::UserRole + 4 #define ROLE_REQUEST_ID Qt::UserRole + 5 #define ROLE_SORT Qt::UserRole + 6 #define FILTER_NAME_INDEX 0 #define FILTER_DESC_INDEX 1 GroupTreeWidget::GroupTreeWidget(QWidget *parent) : QWidget(parent), ui(new Ui::GroupTreeWidget) { ui->setupUi(this); /* Connect signals */ connect(ui->filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged())); connect(ui->filterLineEdit, SIGNAL(filterChanged(int)), this, SLOT(filterChanged())); connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(customContextMenuRequested(QPoint))); connect(ui->treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), this, SLOT(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*))); connect(ui->treeWidget, SIGNAL(itemActivated(QTreeWidgetItem*,int)), this, SLOT(itemActivated(QTreeWidgetItem*,int))); if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, NULL, this)) { // need signal itemClicked too connect(ui->treeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(itemActivated(QTreeWidgetItem*,int))); } int H = QFontMetricsF(ui->treeWidget->font()).height() ; #if QT_VERSION < QT_VERSION_CHECK(5,11,0) int W = QFontMetricsF(ui->treeWidget->font()).width("_") ; int D = QFontMetricsF(ui->treeWidget->font()).width("9999-99-99[]") ; #else int W = QFontMetricsF(ui->treeWidget->font()).horizontalAdvance("_") ; int D = QFontMetricsF(ui->treeWidget->font()).horizontalAdvance("9999-99-99[]") ; #endif /* Add own item delegate */ RSElidedItemDelegate *itemDelegate = new RSElidedItemDelegate(this); itemDelegate->setSpacing(QSize(W/2, H/4)); ui->treeWidget->setItemDelegate(itemDelegate); /* Set compare role for each column */ compareRole = new RSTreeWidgetItemCompareRole(QMap>({ {GTW_COLUMN_UNREAD, {ROLE_SORT}} , {GTW_COLUMN_POSTS, {ROLE_SORT}} , {GTW_COLUMN_POPULARITY, {ROLE_SORT}} , {GTW_COLUMN_LAST_POST, {ROLE_SORT}} , {GTW_COLUMN_SEARCH_SCORE,{ROLE_SORT}} })); /* Initialize tree widget */ ui->treeWidget->setColumnCount(GTW_COLUMN_COUNT); ui->treeWidget->setSortingEnabled(true); ui->treeWidget->header()->setSortIndicator(GTW_COLUMN_NAME,Qt::AscendingOrder); ui->treeWidget->enableColumnCustomize(true); ui->treeWidget->setColumnCustomizable(GTW_COLUMN_NAME, false); QTreeWidgetItem *headerItem = ui->treeWidget->headerItem(); headerItem->setText(GTW_COLUMN_NAME, tr("Name")); headerItem->setText(GTW_COLUMN_UNREAD, ""); headerItem->setText(GTW_COLUMN_POSTS, ""); headerItem->setText(GTW_COLUMN_POPULARITY, ""); headerItem->setText(GTW_COLUMN_LAST_POST, ""); headerItem->setText(GTW_COLUMN_SEARCH_SCORE, ""); headerItem->setText(GTW_COLUMN_DESCRIPTION, tr("Description")); headerItem->setToolTip(GTW_COLUMN_NAME, tr("Name")); headerItem->setToolTip(GTW_COLUMN_UNREAD, tr("Number of Unread message")); headerItem->setToolTip(GTW_COLUMN_POSTS, tr("Friend's Posts")); headerItem->setToolTip(GTW_COLUMN_POPULARITY, tr("Popularity")); headerItem->setToolTip(GTW_COLUMN_LAST_POST, tr("Last Post")); headerItem->setToolTip(GTW_COLUMN_SEARCH_SCORE, tr("Search Score")); headerItem->setToolTip(GTW_COLUMN_DESCRIPTION, tr("Description")); headerItem->setIcon(GTW_COLUMN_UNREAD, FilesDefs::getIconFromQtResourcePath(":/images/message-state-header.png")); headerItem->setIcon(GTW_COLUMN_POSTS, FilesDefs::getIconFromQtResourcePath(":/images/filetype-association.png")); headerItem->setIcon(GTW_COLUMN_POPULARITY, FilesDefs::getIconFromQtResourcePath(":/images/hot_5.png")); headerItem->setIcon(GTW_COLUMN_LAST_POST, FilesDefs::getIconFromQtResourcePath(":/images/kalarm.png")); headerItem->setIcon(GTW_COLUMN_SEARCH_SCORE, FilesDefs::getIconFromQtResourcePath(":/images/find.png")); /* Set header resize modes and initial section sizes */ QHeaderView *header = ui->treeWidget->header (); header->setStretchLastSection(true); QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_NAME, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_NAME, 40*W) ; QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_UNREAD, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_UNREAD, 3*W+4) ; QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_POSTS, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_POSTS, 3*W+4) ; header->setSectionHidden(GTW_COLUMN_POSTS, true); QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_POPULARITY, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_POPULARITY, 3*W) ; QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_LAST_POST, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_LAST_POST, D+4) ; header->setSectionHidden(GTW_COLUMN_LAST_POST, true); QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_SEARCH_SCORE, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_SEARCH_SCORE, 3*W+4) ; header->setSectionHidden(GTW_COLUMN_SEARCH_SCORE, true); QHeaderView_setSectionResizeModeColumn(header, GTW_COLUMN_DESCRIPTION, QHeaderView::Interactive); header->resizeSection(GTW_COLUMN_DESCRIPTION, 40*W+4) ; header->setSectionHidden(GTW_COLUMN_DESCRIPTION, true); /* add filter actions */ ui->filterLineEdit->addFilter(QIcon(), tr("Title"), FILTER_NAME_INDEX , tr("Search Title")); ui->filterLineEdit->addFilter(QIcon(), tr("Description"), FILTER_DESC_INDEX , tr("Search Description")); ui->filterLineEdit->setCurrentFilter(FILTER_NAME_INDEX); ui->distantSearchLineEdit->setPlaceholderText(tr("Search entire network...")) ; connect(ui->distantSearchLineEdit,SIGNAL(returnPressed()),this,SLOT(distantSearch())) ; mFontSizeHandler.registerFontSize(ui->treeWidget, 1.8f, [this] (QAbstractItemView*, int fontSize) { // Set new font size on all items QTreeWidgetItemIterator it(ui->treeWidget); while (*it) { QTreeWidgetItem *item = *it; QFont font = item->font(GTW_COLUMN_NAME); font.setPointSize(fontSize); item->setFont(GTW_COLUMN_NAME, font); ++it; } }); } GroupTreeWidget::~GroupTreeWidget() { delete ui; } void GroupTreeWidget::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::LanguageChange: ui->retranslateUi(this); break; case QEvent::StyleChange: updateColors(); break; default: // remove compiler warnings break; } } void GroupTreeWidget::addToolButton(QToolButton *toolButton) { if (!toolButton) { return; } /* Initialize button */ int i = qt_defaultDpi(); auto desktopWidget = QApplication::desktop(); auto y = desktopWidget->logicalDpiY(); toolButton->setAutoRaise(true); toolButton->setIconSize(QSize(24*y/i,24*y/i)); toolButton->setFocusPolicy(Qt::NoFocus); ui->toolBarFrame->layout()->addWidget(toolButton); } // Load and save settings (group must be started from the caller) void GroupTreeWidget::processSettings(bool load) { if (Settings == NULL) { return; } ui->treeWidget->setSettingsVersion(2);//Change it when modifing column properties ui->treeWidget->processSettings(load); } void GroupTreeWidget::updateColors() { QTreeWidgetItemIterator itemIterator(ui->treeWidget); QTreeWidgetItem *item; while ((item = *itemIterator) != NULL) { ++itemIterator; int color = item->data(GTW_COLUMN_DATA, ROLE_COLOR).toInt(); if (color >= 0) { item->setData(GTW_COLUMN_NAME, Qt::ForegroundRole, mTextColor[color]); } else { item->setData(GTW_COLUMN_NAME, Qt::ForegroundRole, QVariant()); } } } void GroupTreeWidget::customContextMenuRequested(const QPoint &pos) { emit treeCustomContextMenuRequested(pos); } void GroupTreeWidget::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) { Q_UNUSED(previous); QString id; if (current) { id = current->data(GTW_COLUMN_DATA, ROLE_ID).toString(); } emit treeCurrentItemChanged(id); } void GroupTreeWidget::itemActivated(QTreeWidgetItem *item, int column) { Q_UNUSED(column); QString id; if (item) { id = item->data(GTW_COLUMN_DATA, ROLE_ID).toString(); } emit treeItemActivated(id); } QTreeWidgetItem *GroupTreeWidget::addCategoryItem(const QString &name, const QIcon &icon, bool expand, int sortOrder /*= -1*/) { RSTreeWidgetItem *item = new RSTreeWidgetItem(); ui->treeWidget->addTopLevelItem(item); // To get StyleSheet for Items // ui->treeWidget->style()->unpolish(ui->treeWidget); // ui->treeWidget->style()->polish(ui->treeWidget); item->setText(GTW_COLUMN_NAME, name); item->setData(GTW_COLUMN_DATA, ROLE_NAME, name); QFont itFont = item->font(GTW_COLUMN_NAME); itFont.setBold(true); itFont.setPointSize(ui->treeWidget->font().pointSize()); //use treeWidget font size defined in ui. item->setFont(GTW_COLUMN_NAME, itFont); item->setIcon(GTW_COLUMN_NAME, icon); //int S = QFontMetricsF(itFont).height(); //item->setSizeHint(GTW_COLUMN_NAME, QSize(S*1.9, S*1.9)); //size hint is calculated by item delegate. Use itemDelegate->setSpacing() in constructor. item->setData(GTW_COLUMN_NAME, Qt::ForegroundRole, textColorCategory()); item->setData(GTW_COLUMN_DATA, ROLE_COLOR, GROUPTREEWIDGET_COLOR_CATEGORY); item->setExpanded(expand); item->setNoOrder(true, ROLE_SORT); if (sortOrder>=0) item->setData(0, ROLE_SORT, sortOrder); else item->setData(0, ROLE_SORT, ui->treeWidget->topLevelItemCount()); return item; } void GroupTreeWidget::removeSearchItem(QTreeWidgetItem *item) { ui->treeWidget->takeTopLevelItem(ui->treeWidget->indexOfTopLevelItem(item)) ; } QTreeWidgetItem *GroupTreeWidget::addSearchItem(const QString& search_string, uint32_t id, const QIcon& icon) { QTreeWidgetItem *item = addCategoryItem(search_string,icon,true); item->setData(GTW_COLUMN_DATA,ROLE_REQUEST_ID ,id) ; return item; } void GroupTreeWidget::setDistSearchVisible(bool visible) { ui->distantSearchLineEdit->setVisible(visible); } bool GroupTreeWidget::isSearchRequestResult(QPoint &point,QString& group_id,uint32_t& search_req_id) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item == NULL) return false; QTreeWidgetItem *parent = item->parent(); if(parent == NULL) return false ; search_req_id = parent->data(GTW_COLUMN_DATA, ROLE_REQUEST_ID).toUInt(); group_id = itemId(item) ; return search_req_id > 0; } bool GroupTreeWidget::isSearchRequestResultItem(QTreeWidgetItem *item,QString& group_id,uint32_t& search_req_id) { QTreeWidgetItem *parent = item->parent(); if(parent == NULL) return false ; search_req_id = parent->data(GTW_COLUMN_DATA, ROLE_REQUEST_ID).toUInt(); group_id = itemId(item) ; return search_req_id > 0; } bool GroupTreeWidget::isSearchRequestItem(QPoint &point,uint32_t& search_req_id) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item == NULL) return false; search_req_id = item->data(GTW_COLUMN_DATA, ROLE_REQUEST_ID).toUInt(); return search_req_id > 0; } QString GroupTreeWidget::itemId(QTreeWidgetItem *item) { if (item == NULL) { return ""; } return item->data(GTW_COLUMN_DATA, ROLE_ID).toString(); } QString GroupTreeWidget::itemIdAt(QPoint &point) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); if (item == NULL) { return ""; } return item->data(GTW_COLUMN_DATA, ROLE_ID).toString(); } void GroupTreeWidget::fillGroupItems(QTreeWidgetItem *categoryItem, const QList &itemList) { if (categoryItem == NULL) { return; } QString filterText = ui->filterLineEdit->text(); /* Iterate all items */ QList::const_iterator it; for (it = itemList.begin(); it != itemList.end(); ++it) { const GroupItemInfo &itemInfo = *it; QTreeWidgetItem *item = NULL; /* Search exisiting item */ int childCount = categoryItem->childCount(); for (int child = 0; child < childCount; ++child) { QTreeWidgetItem *childItem = categoryItem->child(child); if (childItem->data(GTW_COLUMN_DATA, ROLE_ID).toString() == itemInfo.id) { /* Found child */ item = childItem; break; } } if (item == NULL) { item = new RSTreeWidgetItem(compareRole); item->setData(GTW_COLUMN_DATA, ROLE_ID, itemInfo.id); item->setFont(GTW_COLUMN_NAME, ui->treeWidget->font()); //static_cast(item)->setNoDataAsLast(true); //Uncomment this to sort data with QVariant() always at end. categoryItem->addChild(item); } item->setText(GTW_COLUMN_NAME, itemInfo.name); item->setData(GTW_COLUMN_DATA, ROLE_NAME, itemInfo.name); item->setText(GTW_COLUMN_DESCRIPTION, itemInfo.description); item->setData(GTW_COLUMN_DATA, ROLE_DESCRIPTION, itemInfo.description); // Add children for context strings. This happens in the search. while(nullptr != item->takeChild(0)); for(auto& str:itemInfo.context_strings) if(!str.empty()) { QTreeWidgetItem *wit = new QTreeWidgetItem(QStringList(QString::fromUtf8(str.c_str()))); wit->setData(GTW_COLUMN_DATA,ROLE_ID,itemInfo.id); item->addChild(wit); } /* Set last post */ if(itemInfo.lastpost == DateTime::DateTimeFromTime_t(0)) { item->setText(GTW_COLUMN_LAST_POST, tr("Never")); item->setData(GTW_COLUMN_LAST_POST, ROLE_SORT, QVariant());// To allow them not be sorted with ->setNoDataAsLast(true) } else { item->setText(GTW_COLUMN_LAST_POST, itemInfo.lastpost.toString(Qt::ISODate).replace("T"," ")); item->setData(GTW_COLUMN_LAST_POST, ROLE_SORT, itemInfo.lastpost.toTime_t()); } /* Set visible posts */ item->setText(GTW_COLUMN_POSTS, QString::number(itemInfo.max_visible_posts)); item->setData(GTW_COLUMN_POSTS, ROLE_SORT, itemInfo.max_visible_posts); /* Set icon */ item->setIcon(GTW_COLUMN_NAME, itemInfo.icon); /* Set popularity */ QString tooltip = PopularityDefs::tooltip(itemInfo.popularity); item->setIcon(GTW_COLUMN_POPULARITY, PopularityDefs::icon(itemInfo.popularity)); item->setData(GTW_COLUMN_POPULARITY, ROLE_SORT, itemInfo.popularity); /* Set tooltip */ if (itemInfo.adminKey) tooltip += "\n" + tr("You are admin (modify names and description using Edit menu)"); else if (itemInfo.publishKey) tooltip += "\n" + tr("You have been granted as publisher (you can post here!)"); if(!IS_GROUP_SUBSCRIBED(itemInfo.subscribeFlags)) tooltip += "\n" + QString::number(itemInfo.max_visible_posts) + " messages available" ; // if(itemInfo.max_visible_posts) // wtf? this=0 when there are some posts definitely exist - lastpost is recent if(itemInfo.lastpost == DateTime::DateTimeFromTime_t(0)) tooltip += "\n" + tr("Last Post") + ": " + tr("Never") ; else tooltip += "\n" + tr("Last Post") + ": " + DateTime::formatLongDateTime(itemInfo.lastpost) ; if(!IS_GROUP_SUBSCRIBED(itemInfo.subscribeFlags)) tooltip += "\n" + tr("Subscribe to download and read messages") ; QString desc = itemInfo.description.left(30); desc.replace("\n"," "); desc.replace("\t"," "); if(itemInfo.description.length() > 30) desc += "…"; tooltip += "\n" + tr("Description") + ": " + desc; tooltip += "\n" + tr("Id") + ": " + itemInfo.id; item->setToolTip(GTW_COLUMN_NAME, tooltip); item->setToolTip(GTW_COLUMN_UNREAD, tooltip); item->setToolTip(GTW_COLUMN_POSTS, tooltip); item->setToolTip(GTW_COLUMN_POPULARITY, tooltip); item->setData(GTW_COLUMN_DATA, ROLE_SUBSCRIBE_FLAGS, itemInfo.subscribeFlags); /* Set color */ if (itemInfo.publishKey) { item->setData(GTW_COLUMN_DATA, ROLE_COLOR, GROUPTREEWIDGET_COLOR_PRIVATEKEY); item->setData(GTW_COLUMN_NAME, Qt::ForegroundRole, QBrush(textColorPrivateKey())); } else { // Let StyleSheet color item->setData(GTW_COLUMN_DATA, ROLE_COLOR, GROUPTREEWIDGET_COLOR_STANDARD); item->setData(GTW_COLUMN_NAME, Qt::BackgroundRole, QVariant()); } /* Calculate score */ calculateScore(item, filterText); } /* Remove all items not in list */ int child = 0; int childCount = categoryItem->childCount(); while (child < childCount) { QString id = categoryItem->child(child)->data(GTW_COLUMN_DATA, ROLE_ID).toString(); for (it = itemList.begin(); it != itemList.end(); ++it) { if (it->id == id) { break; } } if (it == itemList.end()) { delete(categoryItem->takeChild(child)); childCount = categoryItem->childCount(); } else { ++child; } } } void GroupTreeWidget::setUnreadCount(QTreeWidgetItem *item, int unreadCount) { if (item == NULL) { return; } QFont itFont = item->font(GTW_COLUMN_NAME); if (unreadCount) { item->setText(GTW_COLUMN_UNREAD, QString::number(unreadCount)); itFont.setBold(true); } else { item->setText(GTW_COLUMN_UNREAD, ""); itFont.setBold(false); } item->setData(GTW_COLUMN_UNREAD, ROLE_SORT, unreadCount); item->setFont(GTW_COLUMN_NAME, itFont); } QTreeWidgetItem *GroupTreeWidget::getItemFromId(const QString &id) { if (id.isEmpty()) { return NULL; } /* Search exisiting item */ QTreeWidgetItemIterator itemIterator(ui->treeWidget); QTreeWidgetItem *item; while ((item = *itemIterator) != NULL) { ++itemIterator; if (item->parent() == NULL) { continue; } if (item->data(GTW_COLUMN_DATA, ROLE_ID).toString() == id) { return item; } } return NULL ; } QTreeWidgetItem *GroupTreeWidget::activateId(const QString &id, bool focus) { QTreeWidgetItem *item = getItemFromId(id); if (item == NULL) { return NULL; } ui->treeWidget->setCurrentItem(item); if (focus) { ui->treeWidget->setFocus(); } return item; } bool GroupTreeWidget::setWaiting(const QString &id, bool wait) { QTreeWidgetItem *item = getItemFromId(id); if (!item) { return false; } item->setData(GTW_COLUMN_NAME, Qt::StatusTipRole, wait ? "waiting" : ""); return true; } RSTreeWidget *GroupTreeWidget::treeWidget() { return ui->treeWidget; } bool GroupTreeWidget::getGroupName(const QString& id, QString& name) { QTreeWidgetItem *item = getItemFromId(id); if (item == NULL) { return false; } name = item->data(GTW_COLUMN_DATA, ROLE_NAME).toString(); return true; } int GroupTreeWidget::subscribeFlags(const QString &id) { QTreeWidgetItem *item = getItemFromId(id); if (item == NULL) { return 0; } return item->data(GTW_COLUMN_DATA, ROLE_SUBSCRIBE_FLAGS).toInt(); } void GroupTreeWidget::calculateScore(QTreeWidgetItem *item, const QString &filterText) { if (item) { /* Calculate one item */ int score; if (filterText.isEmpty()) { score = 0; item->setHidden(false); } else { QString scoreString; switch (ui->filterLineEdit->currentFilter()) { case FILTER_NAME_INDEX: scoreString = item->data(GTW_COLUMN_DATA, ROLE_NAME).toString(); break; case FILTER_DESC_INDEX: scoreString = item->data(GTW_COLUMN_DATA, ROLE_DESCRIPTION).toString(); break; } score = scoreString.count(filterText, Qt::CaseInsensitive); if (score) { item->setHidden(false); } else { item->setHidden(true); } } item->setText(GTW_COLUMN_SEARCH_SCORE, QString::number(score)); item->setData(GTW_COLUMN_SEARCH_SCORE, ROLE_SORT, score); return; } /* Find out which has given word in it */ QTreeWidgetItemIterator itemIterator(ui->treeWidget); QTreeWidgetItem *tmpItem; while ((tmpItem = *itemIterator) != NULL) { ++itemIterator; if (tmpItem->data(GTW_COLUMN_DATA, ROLE_ID).toString().isEmpty()) { continue; } calculateScore(tmpItem, filterText); } } void GroupTreeWidget::filterChanged() { /* Recalculate score */ calculateScore(NULL, ui->filterLineEdit->text()); } void GroupTreeWidget::distantSearch() { emit distantSearchRequested(ui->distantSearchLineEdit->text()); ui->distantSearchLineEdit->clear(); } void GroupTreeWidget::sort() { ui->treeWidget->resort(); }