/******************************************************************************* * gui/common/NewFriendList.cpp * * * * Copyright (C) 2011, 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 #include #include #include #include #include #include "rsserver/rsaccounts.h" #include "retroshare/rspeers.h" #include "GroupDefs.h" #include "gui/chat/ChatDialog.h" #include "gui/common/AvatarDefs.h" #include "gui/common/FilesDefs.h" #include "gui/common/RSElidedItemDelegate.h" #include "gui/connect/ConfCertDialog.h" #include "gui/connect/PGPKeyDialog.h" #include "gui/connect/ConnectFriendWizard.h" #include "gui/groups/CreateGroup.h" #include "gui/msgs/MessageComposer.h" #include "gui/RetroShareLink.h" #include "retroshare-gui/RsAutoUpdatePage.h" #ifdef UNFINISHED_FD #include "gui/unfinished/profile/ProfileView.h" #endif #include "RSTreeWidgetItem.h" #include "StatusDefs.h" #include "util/misc.h" #include "util/qtthreadsutils.h" #include "vmessagebox.h" #include "util/QtVersion.h" #include "gui/chat/ChatUserNotify.h" #include "gui/connect/ConnectProgressDialog.h" #include "gui/common/ElidedLabel.h" #include "NewFriendList.h" #include "ui_NewFriendList.h" /* Images for context menu icons */ #define IMAGE_DENYFRIEND ":/images/denied16.png" #define IMAGE_REMOVEFRIEND ":/images/remove_user24.png" #define IMAGE_EXPORTFRIEND ":/images/user/friend_suggestion16.png" #define IMAGE_ADDFRIEND ":/images/user/add_user16.png" #define IMAGE_FRIENDINFO ":/images/info16.png" #define IMAGE_CHAT ":/icons/png/chats.png" #define IMAGE_MSG ":/icons/mail/write-mail.png" #define IMAGE_CONNECT ":/images/connect_friend.png" #define IMAGE_COPYLINK ":/images/copyrslink.png" #define IMAGE_GROUP16 ":/images/user/group16.png" #define IMAGE_EDIT ":/icons/png/pencil-edit-button.png" #define IMAGE_REMOVE ":/images/delete.png" #define IMAGE_EXPAND ":/images/edit_add24.png" #define IMAGE_COLLAPSE ":/images/edit_remove24.png" /* Images for Status icons */ #define IMAGE_AVAILABLE ":/images/user/identityavaiblecyan24.png" #define IMAGE_PASTELINK ":/images/pasterslink.png" #define IMAGE_GROUP24 ":/images/user/group24.png" #define COLUMN_DATA 0 // column for storing the userdata id // #define ROLE_ID Qt::UserRole // #define ROLE_STANDARD Qt::UserRole + 1 // #define ROLE_SORT_GROUP Qt::UserRole + 2 // #define ROLE_SORT_STANDARD_GROUP Qt::UserRole + 3 // #define ROLE_SORT_NAME Qt::UserRole + 4 // #define ROLE_SORT_STATE Qt::UserRole + 5 // #define ROLE_FILTER Qt::UserRole + 6 // states for sorting (equal values are possible) // used in BuildSortString - state + name #define PEER_STATE_ONLINE 1 #define PEER_STATE_BUSY 2 #define PEER_STATE_AWAY 3 #define PEER_STATE_AVAILABLE 4 #define PEER_STATE_INACTIVE 5 #define PEER_STATE_OFFLINE 6 /****** * #define FRIENDS_DEBUG 1 * #define DEBUG_NEW_FRIEND_LIST 1 *****/ Q_DECLARE_METATYPE(ElidedLabel*) class FriendListSortFilterProxyModel: public QSortFilterProxyModel { public: explicit FriendListSortFilterProxyModel(const QHeaderView *header,QObject *parent = NULL) : QSortFilterProxyModel(parent) , m_header(header) , m_sortingEnabled(false), m_sortByState(false) , m_showOfflineNodes(true) { setDynamicSortFilter(false); // causes crashes when true. } bool lessThan(const QModelIndex& left, const QModelIndex& right) const override { bool is_group_1 = left.data(RsFriendListModel::TypeRole).toUInt() == (uint)RsFriendListModel::ENTRY_TYPE_GROUP; bool is_group_2 = right.data(RsFriendListModel::TypeRole).toUInt() == (uint)RsFriendListModel::ENTRY_TYPE_GROUP; if(is_group_1 ^ is_group_2) // if the two are different, put the group first. return is_group_1 ; bool online1 = (left .data(RsFriendListModel::OnlineRole).toInt() != RS_STATUS_OFFLINE); bool online2 = (right.data(RsFriendListModel::OnlineRole).toInt() != RS_STATUS_OFFLINE); if(online1 != online2 && m_sortByState) return (m_header->sortIndicatorOrder()==Qt::AscendingOrder)?online1:online2 ; // always put online nodes first return left.data(RsFriendListModel::SortRole) < right.data(RsFriendListModel::SortRole) ; } bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override { // do not show empty groups QModelIndex index = sourceModel()->index(source_row,0,source_parent); if(index.data(RsFriendListModel::TypeRole) == RsFriendListModel::ENTRY_TYPE_GROUP) // always show groups, so we can delete them even when empty return true; // Filter offline friends if(!m_showOfflineNodes && (index.data(RsFriendListModel::OnlineRole).toInt() == RS_STATUS_OFFLINE)) return false; return index.data(RsFriendListModel::FilterRole).toString() == RsFriendListModel::FilterString ; } void sort( int column, Qt::SortOrder order = Qt::AscendingOrder ) override { if(m_sortingEnabled) return QSortFilterProxyModel::sort(column,order) ; } void setSortingEnabled(bool b) { m_sortingEnabled = b ; } void setSortByState(bool b) { m_sortByState = b ; } void setShowOfflineNodes(bool b) { m_showOfflineNodes = b ; } bool showOfflineNodes() const { return m_showOfflineNodes ; } bool sortByState() const { return m_sortByState ; } private: const QHeaderView *m_header ; bool m_sortingEnabled; bool m_sortByState; bool m_showOfflineNodes; }; NewFriendList::NewFriendList(QWidget */*parent*/) : /* RsAutoUpdatePage(5000,parent),*/ ui(new Ui::NewFriendList()) { ui->setupUi(this); ui->filterLineEdit->setPlaceholderText(tr("Search")) ; ui->filterLineEdit->showFilterIcon(); mEventHandlerId_peer=0; // forces initialization mEventHandlerId_gssp=0; // forces initialization mEventHandlerId_pssc=0; // forces initialization rsEvents->registerEventsHandler( [this](std::shared_ptr e) { handleEvent(e); }, mEventHandlerId_pssc, RsEventType::PEER_STATE_CHANGED ); rsEvents->registerEventsHandler( [this](std::shared_ptr e) { handleEvent(e); }, mEventHandlerId_peer, RsEventType::PEER_CONNECTION ); rsEvents->registerEventsHandler( [this](std::shared_ptr e) { handleEvent(e); }, mEventHandlerId_gssp, RsEventType::GOSSIP_DISCOVERY ); mModel = new RsFriendListModel(); mProxyModel = new FriendListSortFilterProxyModel(ui->peerTreeWidget->header(),this); mProxyModel->setSourceModel(mModel); mProxyModel->setSortRole(RsFriendListModel::SortRole); mProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); mProxyModel->setFilterRole(RsFriendListModel::FilterRole); mProxyModel->setFilterRegExp(QRegExp(RsFriendListModel::FilterString)); ui->peerTreeWidget->setModel(mProxyModel); ui->peerTreeWidget->setItemDelegate(new RSElidedItemDelegate()); ui->peerTreeWidget->setWordWrap(false); /* Add filter actions */ QString headerText = mModel->headerData(RsFriendListModel::COLUMN_THREAD_NAME,Qt::Horizontal,Qt::DisplayRole).toString(); ui->filterLineEdit->addFilter(QIcon(), headerText, RsFriendListModel::COLUMN_THREAD_NAME, QString("%1 %2").arg(tr("Search"), headerText)); ui->filterLineEdit->addFilter(QIcon(), tr("ID"), RsFriendListModel::COLUMN_THREAD_ID, tr("Search ID")); mActionSortByState = new QAction(tr("Online friends on top"), this); mActionSortByState->setCheckable(true); //setting default filter by column as subject ui->filterLineEdit->setCurrentFilter(RsFriendListModel::COLUMN_THREAD_NAME); ui->peerTreeWidget->setSortingEnabled(true); /* Set sort */ sortColumn(RsFriendListModel::COLUMN_THREAD_NAME, Qt::AscendingOrder); toggleSortByState(false); // workaround for Qt bug, should be solved in next Qt release 4.7.0 // http://bugreports.qt.nokia.com/browse/QTBUG-8270 QShortcut *Shortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->peerTreeWidget, 0, 0, Qt::WidgetShortcut); connect(Shortcut, SIGNAL(activated()), this, SLOT(removeItem()),Qt::QueuedConnection); QFontMetricsF fontMetrics(ui->peerTreeWidget->font()); /* Set initial column width */ int fontWidth = fontMetrics.width("W"); ui->peerTreeWidget->setColumnWidth(RsFriendListModel::COLUMN_THREAD_NAME , 22 * fontWidth); ui->peerTreeWidget->setColumnWidth(RsFriendListModel::COLUMN_THREAD_IP , 15 * fontWidth); ui->peerTreeWidget->setColumnWidth(RsFriendListModel::COLUMN_THREAD_ID , 32 * fontWidth); ui->peerTreeWidget->setColumnWidth(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT, 12 * fontWidth); int avatarHeight = fontMetrics.height() * 3; ui->peerTreeWidget->setIconSize(QSize(avatarHeight, avatarHeight)); mModel->checkInternalData(true); QHeaderView *h = ui->peerTreeWidget->header(); h->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->peerTreeWidget->header(),SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(sortColumn(int,Qt::SortOrder))); connect(ui->peerTreeWidget, SIGNAL(expanded(const QModelIndex&)), this, SLOT(itemExpanded(const QModelIndex&))); connect(ui->peerTreeWidget, SIGNAL(collapsed(const QModelIndex&)), this, SLOT(itemCollapsed(const QModelIndex&))); connect(mActionSortByState, SIGNAL(toggled(bool)), this, SLOT(toggleSortByState(bool))); connect(ui->peerTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(peerTreeWidgetCustomPopupMenu())); connect(ui->actionShowOfflineFriends, SIGNAL(triggered(bool)), this, SLOT(setShowUnconnected(bool))); connect(ui->actionShowState, SIGNAL(triggered(bool)), this, SLOT(setShowState(bool)) ); connect(ui->actionShowGroups, SIGNAL(triggered(bool)), this, SLOT(setShowGroups(bool)) ); connect(ui->actionExportFriendlist, SIGNAL(triggered()) , this, SLOT(exportFriendlistClicked())); connect(ui->actionImportFriendlist, SIGNAL(triggered()) , this, SLOT(importFriendlistClicked())); connect(ui->filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterItems(QString)),Qt::QueuedConnection); connect(h, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(headerContextMenuRequested(QPoint))); // #ifdef RS_DIRECT_CHAT // connect(ui->peerTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(chatNode())); // #endif } void NewFriendList::handleEvent(std::shared_ptr /*e*/) { // /!\ The function we're in is called from a different thread. It's very important // to use this trick in order to avoid data races. RsQThreadUtils::postToObject( [=]() { forceUpdateDisplay() ; }, this ) ; } NewFriendList::~NewFriendList() { rsEvents->unregisterEventsHandler(mEventHandlerId_peer); rsEvents->unregisterEventsHandler(mEventHandlerId_gssp); rsEvents->unregisterEventsHandler(mEventHandlerId_pssc); delete mModel; delete mProxyModel; delete ui; } void NewFriendList::itemExpanded(const QModelIndex& index) { mModel->expandItem(mProxyModel->mapToSource(index)); } void NewFriendList::itemCollapsed(const QModelIndex& index) { mModel->collapseItem(mProxyModel->mapToSource(index)); } void NewFriendList::headerContextMenuRequested(QPoint /*p*/) { QMenu displayMenu(tr("Show Items"), this); QWidget *widget = new QWidget(&displayMenu); widget->setStyleSheet( ".QWidget{background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 #FEFEFE, stop:1 #E8E8E8); border: 1px solid #CCCCCC;}"); // create menu header QHBoxLayout *hbox = new QHBoxLayout(widget); hbox->setMargin(0); hbox->setSpacing(6); QLabel *iconLabel = new QLabel(widget); QPixmap pix = FilesDefs::getPixmapFromQtResourcePath(":/images/user/friends24.png").scaledToHeight(QFontMetricsF(iconLabel->font()).height()*1.5); iconLabel->setPixmap(pix); iconLabel->setMaximumSize(iconLabel->frameSize().height() + pix.height(), pix.width()); hbox->addWidget(iconLabel); QLabel *textLabel = new QLabel("Show/hide...", widget); hbox->addWidget(textLabel); QSpacerItem *spacerItem = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hbox->addItem(spacerItem); widget->setLayout(hbox); QWidgetAction *widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(widget); displayMenu.addAction(widgetAction); displayMenu.addAction(mActionSortByState); mActionSortByState->setChecked(mProxyModel->sortByState()); displayMenu.addAction(ui->actionShowOfflineFriends); displayMenu.addAction(ui->actionShowState); displayMenu.addAction(ui->actionShowGroups); ui->actionShowOfflineFriends->setChecked(mProxyModel->showOfflineNodes()); ui->actionShowState->setChecked(mModel->getDisplayStatusString()); ui->actionShowGroups->setChecked(mModel->getDisplayGroups()); QHeaderView *header = ui->peerTreeWidget->header(); { QAction *action = displayMenu.addAction(QIcon(), tr("Last contact"), this, SLOT(toggleColumnVisible())); action->setCheckable(true); action->setData(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT); action->setChecked(!header->isSectionHidden(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT)); } { QAction *action = displayMenu.addAction(QIcon(), tr("IP"), this, SLOT(toggleColumnVisible())); action->setCheckable(true); action->setData(RsFriendListModel::COLUMN_THREAD_IP); action->setChecked(!header->isSectionHidden(RsFriendListModel::COLUMN_THREAD_IP)); } { QAction *action = displayMenu.addAction(QIcon(), tr("ID"), this, SLOT(toggleColumnVisible())); action->setCheckable(true); action->setData(RsFriendListModel::COLUMN_THREAD_ID); action->setChecked(!header->isSectionHidden(RsFriendListModel::COLUMN_THREAD_ID)); } displayMenu.exec(QCursor::pos()); } void NewFriendList::addToolButton(QToolButton *toolButton) { if (!toolButton) return; /* Initialize button */ toolButton->setAutoRaise(true); float S = QFontMetricsF(ui->filterLineEdit->font()).height() ; toolButton->setIconSize(QSize(S*1.5,S*1.5)); toolButton->setFocusPolicy(Qt::NoFocus); ui->titleBarFrame->layout()->addWidget(toolButton); } void NewFriendList::saveExpandedPathsAndSelection(std::set& expanded_indexes, QString& sel) { QModelIndexList selectedIndexes = ui->peerTreeWidget->selectionModel()->selectedIndexes(); QModelIndex current_index = selectedIndexes.empty()?QModelIndex():(*selectedIndexes.begin()); #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << "Saving expended paths and selection..." << std::endl; #endif for(int row = 0; row < mProxyModel->rowCount(); ++row) recursSaveExpandedItems(mProxyModel->index(row,0),current_index,QString(),expanded_indexes,sel); #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " selected index: \"" << sel.toStdString() << "\"" << std::endl; #endif } void NewFriendList::restoreExpandedPathsAndSelection(const std::set& expanded_indexes,const QString& index_to_select,QModelIndex& selected_index) { #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << "Restoring expended paths and selection..." << std::endl; std::cerr << " index to select: \"" << index_to_select.toStdString() << "\"" << std::endl; #endif ui->peerTreeWidget->blockSignals(true) ; for(int row = 0; row < mProxyModel->rowCount(); ++row) recursRestoreExpandedItems(mProxyModel->index(row,0),QString(),expanded_indexes,index_to_select,selected_index); ui->peerTreeWidget->blockSignals(false) ; } void NewFriendList::recursSaveExpandedItems(const QModelIndex& index,const QModelIndex& current_index,const QString& parent_path,std::set& exp, QString& sel) { QString local_path = parent_path + index.sibling(index.row(),RsFriendListModel::COLUMN_THREAD_ID).data(Qt::DisplayRole).toString() + " "; #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " At index " << index.row() << ". data[1]=" << local_path.toStdString() ; #endif if(ui->peerTreeWidget->isExpanded(index)) { #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " Index is expanded." ; #endif if(index.isValid()) exp.insert(local_path) ; for(int row=0;rowrowCount(index);++row) recursSaveExpandedItems(index.child(row,0),current_index,local_path,exp,sel) ; } #ifdef DEBUG_NEW_FRIEND_LIST else std::cerr << " not expanded." ; #endif if(index == current_index) { #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " adding to selection!" << std::endl; #endif sel = local_path ; } #ifdef DEBUG_NEW_FRIEND_LIST else std::cerr << std::endl; #endif } void NewFriendList::recursRestoreExpandedItems(const QModelIndex& index, const QString& parent_path, const std::set& exp, const QString& sel,QModelIndex& selected_index) { QString local_path = parent_path + index.sibling(index.row(),RsFriendListModel::COLUMN_THREAD_ID).data(Qt::DisplayRole).toString() + " "; #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " At index " << index.row() << ". data[1]=" << local_path.toStdString() ; #endif if(exp.find(local_path) != exp.end()) { #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " re expanding " << std::endl; #endif ui->peerTreeWidget->setExpanded(index,true) ; for(int row=0;rowrowCount(index);++row) recursRestoreExpandedItems(index.child(row,0),local_path,exp,sel,selected_index) ; } if(sel == local_path) { #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << " selecting index \"" << local_path.toStdString() << "\"" << std::endl; #endif ui->peerTreeWidget->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); selected_index = index; } #ifdef DEBUG_NEW_FRIEND_LIST else std::cerr << std::endl; #endif } void NewFriendList::processSettings(bool load) { // state of peer tree if (load) // load settings { std::cerr <<"Re-loading settings..." << std::endl; // states setShowUnconnected(!Settings->value("hideUnconnected", !mProxyModel->showOfflineNodes()).toBool()); setShowState(Settings->value("showState", mModel->getDisplayStatusString()).toBool()); setShowGroups(Settings->value("showGroups", mModel->getDisplayGroups()).toBool()); setColumnVisible(RsFriendListModel::COLUMN_THREAD_IP,Settings->value("showIP", isColumnVisible(RsFriendListModel::COLUMN_THREAD_IP)).toBool()); setColumnVisible(RsFriendListModel::COLUMN_THREAD_ID,Settings->value("showID", isColumnVisible(RsFriendListModel::COLUMN_THREAD_ID)).toBool()); setColumnVisible(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT,Settings->value("showLastContact", isColumnVisible(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT)).toBool()); ui->peerTreeWidget->header()->restoreState(Settings->value("headers").toByteArray()); // sort toggleSortByState(Settings->value("sortByState", mProxyModel->sortByState()).toBool()); // open groups int arrayIndex = Settings->beginReadArray("Groups"); for (int index = 0; index < arrayIndex; ++index) { Settings->setArrayIndex(index); std::string gids = Settings->value("open").toString().toStdString(); } Settings->endArray(); } else { // save settings // states Settings->setValue("hideUnconnected", !mProxyModel->showOfflineNodes()); Settings->setValue("showState", mModel->getDisplayStatusString()); Settings->setValue("showGroups", mModel->getDisplayGroups()); Settings->setValue("showIP",isColumnVisible(RsFriendListModel::COLUMN_THREAD_IP)); Settings->setValue("showID",isColumnVisible(RsFriendListModel::COLUMN_THREAD_ID)); Settings->setValue("showLastContact",isColumnVisible(RsFriendListModel::COLUMN_THREAD_LAST_CONTACT)); Settings->setValue("headers",ui->peerTreeWidget->header()->saveState()); // sort Settings->setValue("sortByState", mProxyModel->sortByState()); Settings->endArray(); } } void NewFriendList::toggleSortByState(bool sort) { mProxyModel->setSortByState(sort); mProxyModel->setFilterRegExp(QRegExp(QString(RsFriendListModel::FilterString))) ;// triggers a re-display. } void NewFriendList::changeEvent(QEvent *e) { QWidget::changeEvent(e); switch (e->type()) { case QEvent::StyleChange: break; default: // remove compiler warnings break; } } /** * Creates the context popup menu and its submenus, * then shows it at the current cursor position. */ void NewFriendList::peerTreeWidgetCustomPopupMenu() { QModelIndex index = getCurrentSourceIndex(); RsFriendListModel::EntryType type = mModel->getType(index); QMenu contextMenu(this); QWidget *widget = new QWidget(&contextMenu); widget->setStyleSheet( ".QWidget{background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1,stop:0 #FEFEFE, stop:1 #E8E8E8); border: 1px solid #CCCCCC;}"); // create menu header QHBoxLayout *hbox = new QHBoxLayout(widget); hbox->setMargin(0); hbox->setSpacing(6); QLabel *iconLabel = new QLabel(widget); QPixmap pix = FilesDefs::getPixmapFromQtResourcePath(":/images/user/friends24.png").scaledToHeight(QFontMetricsF(iconLabel->font()).height()*1.5); iconLabel->setPixmap(pix); iconLabel->setMaximumSize(iconLabel->frameSize().height() + pix.height(), pix.width()); hbox->addWidget(iconLabel); QLabel *textLabel = new QLabel("Friend list", widget); hbox->addWidget(textLabel); QSpacerItem *spacerItem = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hbox->addItem(spacerItem); widget->setLayout(hbox); QWidgetAction *widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(widget); contextMenu.addAction(widgetAction); // create menu entries if(index.isValid()) { // define header switch (type) { case RsFriendListModel::ENTRY_TYPE_GROUP: //this is a GPG key textLabel->setText("" + tr("Group") + ""); break; case RsFriendListModel::ENTRY_TYPE_PROFILE: //this is a GPG key textLabel->setText("" + tr("Friend") + ""); break; case RsFriendListModel::ENTRY_TYPE_NODE: //this is a SSL key textLabel->setText("" + tr("Node") + ""); break; default: textLabel->setText("" + tr("UNKNOWN TYPE") + ""); } switch (type) { case RsFriendListModel::ENTRY_TYPE_GROUP: { RsGroupInfo group_info ; mModel->getGroupData(index,group_info); bool standard = group_info.flag & RS_GROUP_FLAG_STANDARD; contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_EDIT), tr("Edit Group"), this, SLOT(editGroup())); QAction *action = contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_REMOVE), tr("Remove Group"), this, SLOT(removeGroup())); action->setDisabled(standard); } break; case RsFriendListModel::ENTRY_TYPE_PROFILE: { contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_FRIENDINFO), tr("Profile details"), this, SLOT(configureProfile())); contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_DENYFRIEND), tr("Deny connections"), this, SLOT(removeProfile())); RsFriendListModel::RsProfileDetails details; mModel->getProfileData(index,details); if(mModel->getDisplayGroups()) { QMenu* addToGroupMenu = NULL; QMenu* moveToGroupMenu = NULL; std::list groupInfoList; rsPeers->getGroupInfoList(groupInfoList); GroupDefs::sortByName(groupInfoList); RsPgpId gpgId ( details.gpg_id ); QModelIndex parent = mModel->parent(index); bool foundGroup = false; // add action for all groups, except the own group for (std::list::iterator groupIt = groupInfoList.begin(); groupIt != groupInfoList.end(); ++groupIt) { if (std::find(groupIt->peerIds.begin(), groupIt->peerIds.end(), gpgId) == groupIt->peerIds.end()) { if (parent.isValid()) { if (addToGroupMenu == NULL) addToGroupMenu = new QMenu(tr("Add to group"), &contextMenu); QAction* addToGroupAction = new QAction(GroupDefs::name(*groupIt), addToGroupMenu); addToGroupAction->setData(QString::fromStdString(groupIt->id.toStdString())); connect(addToGroupAction, SIGNAL(triggered()), this, SLOT(addToGroup())); addToGroupMenu->addAction(addToGroupAction); } if (moveToGroupMenu == NULL) { moveToGroupMenu = new QMenu(tr("Move to group"), &contextMenu); } QAction* moveToGroupAction = new QAction(GroupDefs::name(*groupIt), moveToGroupMenu); moveToGroupAction->setData(QString::fromStdString(groupIt->id.toStdString())); connect(moveToGroupAction, SIGNAL(triggered()), this, SLOT(moveToGroup())); moveToGroupMenu->addAction(moveToGroupAction); } else { foundGroup = true; } } QMenu *groupsMenu = contextMenu.addMenu(FilesDefs::getIconFromQtResourcePath(IMAGE_GROUP16), tr("Groups")); groupsMenu->addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_EXPAND), tr("Create new group"), this, SLOT(createNewGroup())); if (addToGroupMenu || moveToGroupMenu || foundGroup) { if (addToGroupMenu) { groupsMenu->addMenu(addToGroupMenu); } if (moveToGroupMenu) { groupsMenu->addMenu(moveToGroupMenu); } if (foundGroup) { // add remove from group if (parent.isValid() && mModel->getType(parent) == RsFriendListModel::ENTRY_TYPE_GROUP) { RsGroupInfo info ; mModel->getGroupData(parent,info); QAction *removeFromGroup = groupsMenu->addAction(tr("Remove from group ")+QString::fromUtf8(info.name.c_str())); removeFromGroup->setData(parent.sibling(parent.row(),RsFriendListModel::COLUMN_THREAD_ID).data(Qt::DisplayRole)); connect(removeFromGroup, SIGNAL(triggered()), this, SLOT(removeFromGroup())); } QAction *removeFromAllGroups = groupsMenu->addAction(tr("Remove from all groups")); removeFromAllGroups->setData(""); connect(removeFromAllGroups, SIGNAL(triggered()), this, SLOT(removeFromGroup())); } } } } break ; case RsFriendListModel::ENTRY_TYPE_NODE: { #ifdef RS_DIRECT_CHAT contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_CHAT), tr("Chat"), this, SLOT(chatNode())); contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MSG), tr("Send message to this node"), this, SLOT(msgNode())); contextMenu.addSeparator(); #endif // RS_DIRECT_CHAT contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_FRIENDINFO), tr("Node details"), this, SLOT(configureNode())); if (type == RsFriendListModel::ENTRY_TYPE_PROFILE || type == RsFriendListModel::ENTRY_TYPE_NODE) contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_EXPORTFRIEND), tr("Recommend this node to..."), this, SLOT(recommendNode())); RsFriendListModel::RsNodeDetails details; mModel->getNodeData(index,details); if(!rsPeers->isHiddenNode(rsPeers->getOwnId()) || rsPeers->isHiddenNode( details.id )) contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_CONNECT), tr("Attempt to connect"), this, SLOT(connectNode())); contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy certificate link"), this, SLOT(copyFullCertificate())); //this is a SSL key contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_REMOVEFRIEND), tr("Remove Friend Node"), this, SLOT(removeNode())); } break; default: { contextMenu.addSection("Report it to Devs!"); } } } contextMenu.addSeparator(); QAction *action = contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_PASTELINK), tr("Paste certificate link"), this, SLOT(pastePerson())); if (RSLinkClipboard::empty(RetroShareLink::TYPE_CERTIFICATE)) action->setDisabled(true); contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_EXPAND), tr("Expand all"), ui->peerTreeWidget, SLOT(expandAll())); contextMenu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COLLAPSE), tr("Collapse all"), ui->peerTreeWidget, SLOT(collapseAll())); contextMenu.addSeparator(); contextMenu.addAction(ui->actionExportFriendlist); contextMenu.addAction(ui->actionImportFriendlist); contextMenu.exec(QCursor::pos()); } void NewFriendList::createNewGroup() { CreateGroup createGrpDialog (RsNodeGroupId(), this); createGrpDialog.exec(); } #ifdef NOT_USED static QIcon createAvatar(const QPixmap &avatar, const QPixmap &overlay) { int avatarWidth = avatar.width(); int avatarHeight = avatar.height(); QPixmap pixmap(avatar); int overlaySize = (avatarWidth > avatarHeight) ? (avatarWidth/2.5) : (avatarHeight/2.5); int overlayX = avatarWidth - overlaySize; int overlayY = avatarHeight - overlaySize; QPainter painter(&pixmap); painter.drawPixmap(overlayX, overlayY, overlaySize, overlaySize, overlay); QIcon icon; icon.addPixmap(pixmap); return icon; } #endif void NewFriendList::addFriend() { std::string groupId = getSelectedGroupId(); ConnectFriendWizard connwiz (this); if (groupId.empty() == false) { connwiz.setGroup(groupId); } connwiz.exec (); } void NewFriendList::msgProfile() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; MessageComposer::msgFriend(det.id); } void NewFriendList::msgGroup() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; MessageComposer::msgFriend(det.id); } void NewFriendList::msgNode() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; MessageComposer::msgFriend(det.id); } void NewFriendList::chatNode() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; ChatDialog::chatFriend(ChatId(det.id)); } void NewFriendList::recommendNode() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; MessageComposer::recommendFriend(std::set({ det.id })); } void NewFriendList::pastePerson() { //RSLinkClipboard::process(RetroShareLink::TYPE_PERSON); RSLinkClipboard::process(RetroShareLink::TYPE_CERTIFICATE); } void NewFriendList::copyFullCertificate() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; QList urls; RetroShareLink link = RetroShareLink::createCertificate(det.id); urls.push_back(link); std::cerr << "link: " << std::endl; std::cerr<< link.toString().toStdString() << std::endl; RSLinkClipboard::copyLinks(urls); } /** * Find out which group is selected * * @return If a group item is selected, its groupId otherwise an empty string */ std::string NewFriendList::getSelectedGroupId() const { RsGroupInfo ginfo; if(!getCurrentGroup(ginfo)) return std::string(); return ginfo.id.toStdString(); } QModelIndex NewFriendList::getCurrentSourceIndex() const { QModelIndexList selectedIndexes = ui->peerTreeWidget->selectionModel()->selectedIndexes(); if(selectedIndexes.size() != RsFriendListModel::COLUMN_THREAD_NB_COLUMNS) // check that a single row is selected return QModelIndex(); return mProxyModel->mapToSource(*selectedIndexes.begin()); } bool NewFriendList::getCurrentGroup(RsGroupInfo& info) const { /* get the current, and extract the Id */ QModelIndex index = getCurrentSourceIndex(); if(!index.isValid()) return false; return mModel->getGroupData(index,info); } bool NewFriendList::getCurrentNode(RsFriendListModel::RsNodeDetails& prof) const { /* get the current, and extract the Id */ QModelIndex index = getCurrentSourceIndex(); if(!index.isValid()) return false; return mModel->getNodeData(index,prof); } bool NewFriendList::getCurrentProfile(RsFriendListModel::RsProfileDetails& prof) const { /* get the current, and extract the Id */ QModelIndex index = getCurrentSourceIndex(); if(!index.isValid()) return false; return mModel->getProfileData(index,prof); } #ifdef UNFINISHED_FD /* GUI stuff -> don't do anything directly with Control */ void FriendsDialog::viewprofile() { /* display Dialog */ QTreeWidgetItem *c = getCurrentPeer(); // static ProfileView *profileview = new ProfileView(); if (!c) return; /* set the Id */ std::string id = getRsId(c); profileview -> setPeerId(id); profileview -> show(); } #endif /* So from the Peers Dialog we can call the following control Functions: * (1) Remove Current. FriendRemove(id) * (2) Allow/DisAllow. FriendStatus(id, accept) * (2) Connect. FriendConnectAttempt(id, accept) * (3) Set Address. FriendSetAddress(id, str, port) * (4) Set Trust. FriendTrustSignature(id, bool) * (5) Configure (GUI Only) -> 3/4 * * All of these rely on the finding of the current Id. */ void NewFriendList::removeItem() { QModelIndex index = getCurrentSourceIndex(); RsFriendListModel::EntryType type = mModel->getType(index); if(index.isValid()) { switch (type) { case RsFriendListModel::ENTRY_TYPE_GROUP: removeGroup(); break; case RsFriendListModel::ENTRY_TYPE_PROFILE: removeProfile(); break; case RsFriendListModel::ENTRY_TYPE_NODE: removeNode(); break; case RsFriendListModel::ENTRY_TYPE_UNKNOWN: RsErr()<<__PRETTY_FUNCTION__<<" Get Item of type unknow."<removeFriendLocation(det.id); checkInternalData(true); } void NewFriendList::removeProfile() { RsFriendListModel::RsProfileDetails det; if(!getCurrentProfile(det) || !rsPeers) return; if ((QMessageBox::question(this, "RetroShare", tr("Do you want to remove this Friend?"), QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes)) == QMessageBox::Yes) rsPeers->removeFriend(det.gpg_id); checkInternalData(true); } void NewFriendList::connectNode() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det) || !rsPeers) return; rsPeers->connectAttempt(det.id); ConnectProgressDialog::showProgress(det.id); } /* GUI stuff -> don't do anything directly with Control */ void NewFriendList::configureNode() { RsFriendListModel::RsNodeDetails det; if(!getCurrentNode(det)) return; ConfCertDialog::showIt(det.id, ConfCertDialog::PageDetails); } void NewFriendList::configureProfile() { RsFriendListModel::RsProfileDetails det; if(!getCurrentProfile(det)) return; PGPKeyDialog::showIt(det.gpg_id, PGPKeyDialog::PageDetails); } void NewFriendList::addToGroup() { RsFriendListModel::RsProfileDetails det; if(!getCurrentProfile(det) || !rsPeers) return; RsNodeGroupId groupId ( qobject_cast(sender())->data().toString().toStdString()); RsPgpId gpgId (det.gpg_id); if (gpgId.isNull() || groupId.isNull()) return; // automatically expand the group, the peer is added to expandGroup(groupId); // add to group rsPeers->assignPeerToGroup(groupId, gpgId, true); checkInternalData(true); } void NewFriendList::forceUpdateDisplay() { checkInternalData(true); } void NewFriendList::moveToGroup() { RsFriendListModel::RsProfileDetails pinfo; if(!getCurrentProfile(pinfo)) return; RsNodeGroupId groupId ( qobject_cast(sender())->data().toString().toStdString()); RsPgpId gpgId ( pinfo.gpg_id ); if (gpgId.isNull() || groupId.isNull()) return; // remove from all groups rsPeers->assignPeerToGroup(RsNodeGroupId(), gpgId, false); // automatically expand the group, the peer is added to expandGroup(groupId); // add to group rsPeers->assignPeerToGroup(groupId, gpgId, true); checkInternalData(true); } void NewFriendList::removeFromGroup() { RsFriendListModel::RsProfileDetails pinfo; if(!getCurrentProfile(pinfo)) return; RsNodeGroupId groupId ( qobject_cast(sender())->data().toString().toStdString()); RsPgpId gpgId ( pinfo.gpg_id ); if (gpgId.isNull()) return; // remove from (all) group(s) rsPeers->assignPeerToGroup(groupId, gpgId, false); checkInternalData(true); } void NewFriendList::editGroup() { RsGroupInfo pinfo; if(!getCurrentGroup(pinfo)) return; RsNodeGroupId groupId ( pinfo.id ); if (!groupId.isNull()) { CreateGroup editGrpDialog(groupId, this); editGrpDialog.exec(); } checkInternalData(true); } void NewFriendList::removeGroup() { RsGroupInfo pinfo; if(!getCurrentGroup(pinfo)) return; rsPeers->removeGroup(pinfo.id); checkInternalData(true); } void NewFriendList::applyWhileKeepingTree(std::function predicate) { std::set expanded_indexes; QString selected; saveExpandedPathsAndSelection(expanded_indexes, selected); #ifdef DEBUG_NEW_FRIEND_LIST std::cerr << "After collecting selection, selected paths is: \"" << selected.toStdString() << "\"" << std::endl; #endif whileBlocking(ui->peerTreeWidget)->clearSelection(); // This is a hack to avoid crashes on windows while calling endInsertRows(). I'm not sure wether these crashes are // due to a Qt bug, or a misuse of the proxy model on my side. Anyway, this solves them for good. // As a side effect we need to save/restore hidden columns because setSourceModel() resets this setting. // save hidden columns and sizes std::vector col_visible(RsFriendListModel::COLUMN_THREAD_NB_COLUMNS); std::vector col_sizes(RsFriendListModel::COLUMN_THREAD_NB_COLUMNS); for(int i=0;ipeerTreeWidget->isColumnHidden(i); col_sizes[i] = ui->peerTreeWidget->columnWidth(i); } mProxyModel->setSourceModel(nullptr); predicate(); QModelIndex selected_index; mProxyModel->setSourceModel(mModel); restoreExpandedPathsAndSelection(expanded_indexes,selected,selected_index); // restore hidden columns for(uint32_t i=0;ipeerTreeWidget->setColumnHidden(i,!col_visible[i]); ui->peerTreeWidget->setColumnWidth(i,col_sizes[i]); } // restore sorting // sortColumn(mLastSortColumn,mLastSortOrder); mProxyModel->sort(mLastSortColumn,mLastSortOrder); if(selected_index.isValid()) ui->peerTreeWidget->scrollTo(selected_index); } void NewFriendList::sortColumn(int col,Qt::SortOrder so) { std::set expanded_indexes; QString selected; QModelIndex selected_index; saveExpandedPathsAndSelection(expanded_indexes, selected); whileBlocking(ui->peerTreeWidget)->clearSelection(); mProxyModel->setSortingEnabled(true); mProxyModel->sort(col,so); mProxyModel->setSortingEnabled(false); restoreExpandedPathsAndSelection(expanded_indexes,selected,selected_index); if(selected_index.isValid()) ui->peerTreeWidget->scrollTo(selected_index); mLastSortColumn = col; mLastSortOrder = so; } void NewFriendList::checkInternalData(bool force) { applyWhileKeepingTree([force,this]() { mModel->checkInternalData(force) ; }); } void NewFriendList::exportFriendlistClicked() { QString fileName; if(!importExportFriendlistFileDialog(fileName, false)) // error was already shown - just return return; if(!exportFriendlist(fileName)) // error was already shown - just return return; QMessageBox mbox; mbox.setIcon(QMessageBox::Information); mbox.setText(tr("Done!")); mbox.setInformativeText(tr("Your friendlist is stored at:\n") + fileName + tr("\n(keep in mind that the file is unencrypted!)")); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); } void NewFriendList::importFriendlistClicked() { QString fileName; if(!importExportFriendlistFileDialog(fileName, true)) // error was already shown - just return return; bool errorPeers, errorGroups; if(importFriendlist(fileName, errorPeers, errorGroups)) { QMessageBox mbox; mbox.setIcon(QMessageBox::Information); mbox.setText(tr("Done!")); mbox.setInformativeText(tr("Your friendlist was imported from:\n") + fileName); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); } else { QMessageBox mbox; mbox.setIcon(QMessageBox::Warning); mbox.setText(tr("Done - but errors happened!")); mbox.setInformativeText(tr("Your friendlist was imported from:\n") + fileName + (errorPeers ? tr("\nat least one peer was not added") : "") + (errorGroups ? tr("\nat least one peer was not added to a group") : "") ); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); } } /** * @brief opens a file dialog to select a file containing a friendlist * @param fileName file containing a friendlist * @param import show dialog for importing (true) or exporting (false) friendlist * @return success or failure * * This function also shows an error message when no valid file was selected */ bool NewFriendList::importExportFriendlistFileDialog(QString &fileName, bool import) { bool res = true; if (import) { res = misc::getOpenFileName(this, RshareSettings::LASTDIR_CERT , tr("Select file for importing your friendlist from") , tr("XML File (*.xml);;All Files (*)") , fileName , QFileDialog::DontConfirmOverwrite ); } else { res = misc::getSaveFileName(this, RshareSettings::LASTDIR_CERT , tr("Select a file for exporting your friendlist to") , tr("XML File (*.xml);;All Files (*)") , fileName, NULL , QFileDialog::Options() ); } if ( res && !fileName.endsWith(".xml",Qt::CaseInsensitive) ) fileName = fileName.append(".xml"); return res; } /** * @brief exports friendlist to a given file * @param fileName file for storing friendlist * @return success or failure * * This function also shows an error message when the selected file is invalid/not writable */ bool NewFriendList::exportFriendlist(QString &fileName) { QDomDocument doc("FriendListWithGroups"); QDomElement root = doc.createElement("root"); doc.appendChild(root); QFile file(fileName); if(!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { // show error to user QMessageBox mbox; mbox.setIcon(QMessageBox::Warning); mbox.setText(tr("Error")); mbox.setInformativeText(tr("File is not writeable!\n") + fileName); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); return false; } std::list gpg_ids; rsPeers->getGPGAcceptedList(gpg_ids); std::list group_info_list; rsPeers->getGroupInfoList(group_info_list); QDomElement pgpIDs = doc.createElement("pgpIDs"); RsPeerDetails detailPGP; for(std::list::iterator list_iter = gpg_ids.begin(); list_iter != gpg_ids.end(); ++list_iter) { rsPeers->getGPGDetails(*list_iter, detailPGP); QDomElement pgpID = doc.createElement("pgpID"); // these values aren't used and just stored for better human readability pgpID.setAttribute("id", QString::fromStdString(detailPGP.gpg_id.toStdString())); pgpID.setAttribute("name", QString::fromUtf8(detailPGP.name.c_str())); std::list ssl_ids; rsPeers->getAssociatedSSLIds(*list_iter, ssl_ids); for(std::list::iterator list_iter2 = ssl_ids.begin(); list_iter2 != ssl_ids.end(); ++list_iter2) { RsPeerDetails detailSSL; if (!rsPeers->getPeerDetails(*list_iter2, detailSSL)) continue; std::string certificate = rsPeers->GetRetroshareInvite(detailSSL.id, RetroshareInviteFlags::CURRENT_IP | RetroshareInviteFlags::DNS | RetroshareInviteFlags::RADIX_FORMAT); // remove \n from certificate certificate.erase(std::remove(certificate.begin(), certificate.end(), '\n'), certificate.end()); QDomElement sslID = doc.createElement("sslID"); // these values aren't used and just stored for better human readability sslID.setAttribute("sslID", QString::fromStdString(detailSSL.id.toStdString())); if(!detailSSL.location.empty()) sslID.setAttribute("location", QString::fromUtf8(detailSSL.location.c_str())); // required values sslID.setAttribute("certificate", QString::fromStdString(certificate)); sslID.setAttribute("service_perm_flags", detailSSL.service_perm_flags.toUInt32()); pgpID.appendChild(sslID); } pgpIDs.appendChild(pgpID); } root.appendChild(pgpIDs); QDomElement groups = doc.createElement("groups"); for(std::list::iterator list_iter = group_info_list.begin(); list_iter != group_info_list.end(); ++list_iter) { RsGroupInfo group_info = *list_iter; //skip groups without peers if(group_info.peerIds.empty()) continue; QDomElement group = doc.createElement("group"); // id is not needed since it may differ between locatiosn / pgp ids (groups are identified by name) group.setAttribute("name", QString::fromUtf8(group_info.name.c_str())); group.setAttribute("flag", group_info.flag); for(std::set::iterator i = group_info.peerIds.begin(); i != group_info.peerIds.end(); ++i) { QDomElement pgpID = doc.createElement("pgpID"); std::string pid = i->toStdString(); pgpID.setAttribute("id", QString::fromStdString(pid)); group.appendChild(pgpID); } groups.appendChild(group); } root.appendChild(groups); QTextStream ts(&file); ts.setCodec("UTF-8"); ts << doc.toString(); file.close(); return true; } /** * @brief helper function to show a message box */ static void showXMLParsingError() { // show error to user QMessageBox mbox; mbox.setIcon(QMessageBox::Warning); mbox.setText(QObject::tr("Error")); mbox.setInformativeText(QObject::tr("unable to parse XML file!")); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); } /** * @brief Imports friends from a given file * @param fileName file to load friends from * @param errorPeers an error occured while adding a peer * @param errorGroups an error occured while adding a peer to a group * @return success or failure (an error can also happen when adding a peer and/or adding a peer to a group fails at least once) */ bool NewFriendList::importFriendlist(QString &fileName, bool &errorPeers, bool &errorGroups) { errorPeers = false; errorGroups = false; QDomDocument doc; // load from file { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { // show error to user QMessageBox mbox; mbox.setIcon(QMessageBox::Warning); mbox.setText(tr("Error")); mbox.setInformativeText(tr("File is not readable!\n") + fileName); mbox.setStandardButtons(QMessageBox::Ok); mbox.exec(); return false; } bool ok = doc.setContent(&file); file.close(); if(!ok) { showXMLParsingError(); return false; } } QDomElement root = doc.documentElement(); if(root.tagName() != "root") { showXMLParsingError(); return false; } RsPeerDetails rsPeerDetails; RsPeerId rsPeerID; RsPgpId rsPgpID; // lock all events for faster processing RsAutoUpdatePage::lockAllEvents(); // pgp and ssl IDs QDomElement pgpIDs; { QDomNodeList nodes = root.elementsByTagName("pgpIDs"); if(nodes.isEmpty() || nodes.size() != 1){ showXMLParsingError(); return false; } pgpIDs = nodes.item(0).toElement(); if(pgpIDs.isNull()){ showXMLParsingError(); return false; } } QDomNode pgpIDElem = pgpIDs.firstChildElement("pgpID"); while (!pgpIDElem.isNull()) { QDomElement sslIDElem = pgpIDElem.firstChildElement("sslID"); while (!sslIDElem.isNull()) { rsPeerID.clear(); rsPgpID.clear(); // load everything needed from the pubkey string std::string pubkey = sslIDElem.attribute("certificate").toStdString(); ServicePermissionFlags service_perm_flags(sslIDElem.attribute("service_perm_flags").toInt()); if (!rsPeers->acceptInvite(pubkey, service_perm_flags)) { errorPeers = true; std::cerr << "FriendList::importFriendlist(): failed to get peer detaisl from public key (SSL id: " << sslIDElem.attribute("sslID", "invalid").toStdString() << ")" << std::endl; } sslIDElem = sslIDElem.nextSiblingElement("sslID"); } pgpIDElem = pgpIDElem.nextSiblingElement("pgpID"); } // groups QDomElement groups; { QDomNodeList nodes = root.elementsByTagName("groups"); if(nodes.isEmpty() || nodes.size() != 1){ showXMLParsingError(); return false; } groups = nodes.item(0).toElement(); if(groups.isNull()){ showXMLParsingError(); return false; } } QDomElement group = groups.firstChildElement("group"); while (!group.isNull()) { // get name and flags and try to get the group ID std::string groupName = group.attribute("name").toStdString(); uint32_t flag = group.attribute("flag").toInt(); RsNodeGroupId groupId; if(getOrCreateGroup(groupName, flag, groupId)) { // group id found! QDomElement pgpID = group.firstChildElement("pgpID"); while (!pgpID.isNull()) { // add pgp id to group RsPgpId rsPgpId(pgpID.attribute("id").toStdString()); if(rsPgpID.isNull() || !rsPeers->assignPeerToGroup(groupId, rsPgpId, true)) { errorGroups = true; std::cerr << "FriendList::importFriendlist(): failed to add '" << rsPeers->getGPGName(rsPgpId) << "'' to group '" << groupName << "'" << std::endl; } pgpID = pgpID.nextSiblingElement("pgpID"); } pgpID = pgpID.nextSiblingElement("pgpID"); } else { errorGroups = true; std::cerr << "FriendList::importFriendlist(): failed to find/create group '" << groupName << "'" << std::endl; } group = group.nextSiblingElement("group"); } // unlock events RsAutoUpdatePage::unlockAllEvents(); return !(errorPeers || errorGroups); } /** * @brief Gets the groups ID for a given group name * @param name group name to search for * @param id groupd id for the given name * @return success or fail */ bool NewFriendList::getGroupIdByName(const std::string &name, RsNodeGroupId &id) { std::list grpList; if(!rsPeers->getGroupInfoList(grpList)) return false; foreach (const RsGroupInfo &grp, grpList) { if(grp.name == name) { id = grp.id; return true; } } return false; } /** * @brief Gets the groups ID for a given group name. If no groupd was it will create one * @param name group name to search for * @param flag flag to use when creating the group * @param id groupd id * @return success or failure */ bool NewFriendList::getOrCreateGroup(const std::string& name, uint flag, RsNodeGroupId &id) { if(getGroupIdByName(name, id)) return true; // -> create one RsGroupInfo grp; grp.id.clear(); // RS will generate an ID grp.name = name; grp.flag = flag; if(!rsPeers->addGroup(grp)) return false; // try again return getGroupIdByName(name, id); } void NewFriendList::setShowUnconnected(bool show) { mProxyModel->setShowOfflineNodes(show); mProxyModel->setFilterRegExp(QRegExp(QString(RsFriendListModel::FilterString))) ;// triggers a re-display. } bool NewFriendList::isColumnVisible(int col) const { return !ui->peerTreeWidget->isColumnHidden(col); } void NewFriendList::setColumnVisible(int col,bool visible) { std::cerr << "Setting column " << col << " to be visible: " << visible << std::endl; ui->peerTreeWidget->setColumnHidden(col, !visible); } void NewFriendList::toggleColumnVisible() { QAction *action = dynamic_cast(sender()); if (!action) return; int column = action->data().toInt(); bool visible = action->isChecked(); ui->peerTreeWidget->setColumnHidden(column, !visible); } void NewFriendList::setShowState(bool show) { applyWhileKeepingTree([show,this]() { mModel->setDisplayStatusString(show) ; }); } void NewFriendList::setShowGroups(bool show) { applyWhileKeepingTree([show,this]() { mModel->setDisplayGroups(show) ; }); } /** * Hides all items that don't contain text in the name column. */ void NewFriendList::filterItems(const QString &text) { QStringList lst = text.split(' ',QString::SkipEmptyParts); int filterColumn = ui->filterLineEdit->currentFilter(); if(filterColumn == 0) mModel->setFilter(RsFriendListModel::FILTER_TYPE_NAME,lst); else mModel->setFilter(RsFriendListModel::FILTER_TYPE_ID,lst); // We do this in order to trigger a new filtering action in the proxy model. mProxyModel->setFilterRegExp(QRegExp(QString(RsFriendListModel::FilterString))) ; if(!lst.empty()) ui->peerTreeWidget->expandAll(); else ui->peerTreeWidget->collapseAll(); } void NewFriendList::expandGroup(const RsNodeGroupId& gid) { QModelIndex index = mProxyModel->mapFromSource(mModel->getIndexOfGroup(gid)); ui->peerTreeWidget->setExpanded(index,true) ; }