2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "trackerlistwidget.h"
32 #include <QApplication>
37 #include <QHeaderView>
39 #include <QMessageBox>
41 #include <QStringList>
43 #include <QTreeWidgetItem>
47 #include "base/bittorrent/peerinfo.h"
48 #include "base/bittorrent/session.h"
49 #include "base/bittorrent/torrenthandle.h"
50 #include "base/bittorrent/trackerentry.h"
51 #include "base/global.h"
52 #include "base/preferences.h"
53 #include "gui/autoexpandabledialog.h"
54 #include "gui/uithememanager.h"
55 #include "propertieswidget.h"
56 #include "trackersadditiondialog.h"
58 #define NB_STICKY_ITEM 3
60 TrackerListWidget::TrackerListWidget(PropertiesWidget
*properties
)
62 , m_properties(properties
)
65 // Must be set before calling loadSettings() otherwise the header is reset on restart
66 setHeaderLabels(headerLabels());
70 setRootIsDecorated(false);
71 setAllColumnsShowFocus(true);
72 setItemsExpandable(false);
73 setSelectionMode(QAbstractItemView::ExtendedSelection
);
74 header()->setStretchLastSection(false); // Must be set after loadSettings() in order to work
75 // Ensure that at least one column is visible at all times
76 if (visibleColumnsCount() == 0)
77 setColumnHidden(COL_URL
, false);
78 // To also mitigate the above issue, we have to resize each column when
79 // its size is 0, because explicitly 'showing' the column isn't enough
80 // in the above scenario.
81 for (int i
= 0; i
< COL_COUNT
; ++i
)
82 if ((columnWidth(i
) <= 0) && !isColumnHidden(i
))
83 resizeColumnToContents(i
);
85 setContextMenuPolicy(Qt::CustomContextMenu
);
86 connect(this, &QWidget::customContextMenuRequested
, this, &TrackerListWidget::showTrackerListMenu
);
88 header()->setContextMenuPolicy(Qt::CustomContextMenu
);
89 connect(header(), &QWidget::customContextMenuRequested
, this, &TrackerListWidget::displayToggleColumnsMenu
);
90 connect(header(), &QHeaderView::sectionMoved
, this, &TrackerListWidget::saveSettings
);
91 connect(header(), &QHeaderView::sectionResized
, this, &TrackerListWidget::saveSettings
);
92 connect(header(), &QHeaderView::sortIndicatorChanged
, this, &TrackerListWidget::saveSettings
);
94 // Set DHT, PeX, LSD items
95 m_DHTItem
= new QTreeWidgetItem({ "", "** [DHT] **", "", "0", "", "", "0" });
96 insertTopLevelItem(0, m_DHTItem
);
97 setRowColor(0, QColor("grey"));
98 m_PEXItem
= new QTreeWidgetItem({ "", "** [PeX] **", "", "0", "", "", "0" });
99 insertTopLevelItem(1, m_PEXItem
);
100 setRowColor(1, QColor("grey"));
101 m_LSDItem
= new QTreeWidgetItem({ "", "** [LSD] **", "", "0", "", "", "0" });
102 insertTopLevelItem(2, m_LSDItem
);
103 setRowColor(2, QColor("grey"));
105 // Set static items alignment
106 const Qt::Alignment alignment
= (Qt::AlignRight
| Qt::AlignVCenter
);
107 m_DHTItem
->setTextAlignment(COL_PEERS
, alignment
);
108 m_PEXItem
->setTextAlignment(COL_PEERS
, alignment
);
109 m_LSDItem
->setTextAlignment(COL_PEERS
, alignment
);
110 m_DHTItem
->setTextAlignment(COL_SEEDS
, alignment
);
111 m_PEXItem
->setTextAlignment(COL_SEEDS
, alignment
);
112 m_LSDItem
->setTextAlignment(COL_SEEDS
, alignment
);
113 m_DHTItem
->setTextAlignment(COL_LEECHES
, alignment
);
114 m_PEXItem
->setTextAlignment(COL_LEECHES
, alignment
);
115 m_LSDItem
->setTextAlignment(COL_LEECHES
, alignment
);
116 m_DHTItem
->setTextAlignment(COL_DOWNLOADED
, alignment
);
117 m_PEXItem
->setTextAlignment(COL_DOWNLOADED
, alignment
);
118 m_LSDItem
->setTextAlignment(COL_DOWNLOADED
, alignment
);
120 // Set header alignment
121 headerItem()->setTextAlignment(COL_TIER
, alignment
);
122 headerItem()->setTextAlignment(COL_PEERS
, alignment
);
123 headerItem()->setTextAlignment(COL_SEEDS
, alignment
);
124 headerItem()->setTextAlignment(COL_LEECHES
, alignment
);
125 headerItem()->setTextAlignment(COL_DOWNLOADED
, alignment
);
128 const auto *editHotkey
= new QShortcut(Qt::Key_F2
, this, nullptr, nullptr, Qt::WidgetShortcut
);
129 connect(editHotkey
, &QShortcut::activated
, this, &TrackerListWidget::editSelectedTracker
);
130 const auto *deleteHotkey
= new QShortcut(QKeySequence::Delete
, this, nullptr, nullptr, Qt::WidgetShortcut
);
131 connect(deleteHotkey
, &QShortcut::activated
, this, &TrackerListWidget::deleteSelectedTrackers
);
132 const auto *copyHotkey
= new QShortcut(QKeySequence::Copy
, this, nullptr, nullptr, Qt::WidgetShortcut
);
133 connect(copyHotkey
, &QShortcut::activated
, this, &TrackerListWidget::copyTrackerUrl
);
135 connect(this, &QAbstractItemView::doubleClicked
, this, &TrackerListWidget::editSelectedTracker
);
137 // This hack fixes reordering of first column with Qt5.
138 // https://github.com/qtproject/qtbase/commit/e0fc088c0c8bc61dbcaf5928b24986cd61a22777
140 unused
.setVerticalHeader(header());
141 header()->setParent(this);
142 unused
.setVerticalHeader(new QHeaderView(Qt::Horizontal
));
145 TrackerListWidget::~TrackerListWidget()
150 QVector
<QTreeWidgetItem
*> TrackerListWidget::getSelectedTrackerItems() const
152 const QList
<QTreeWidgetItem
*> selectedTrackerItems
= selectedItems();
153 QVector
<QTreeWidgetItem
*> selectedTrackers
;
154 selectedTrackers
.reserve(selectedTrackerItems
.size());
156 for (QTreeWidgetItem
*item
: selectedTrackerItems
)
158 if (indexOfTopLevelItem(item
) >= NB_STICKY_ITEM
) // Ignore STICKY ITEMS
159 selectedTrackers
<< item
;
162 return selectedTrackers
;
165 void TrackerListWidget::setRowColor(const int row
, const QColor
&color
)
167 const int nbColumns
= columnCount();
168 QTreeWidgetItem
*item
= topLevelItem(row
);
169 for (int i
= 0; i
< nbColumns
; ++i
)
170 item
->setData(i
, Qt::ForegroundRole
, color
);
173 void TrackerListWidget::moveSelectionUp()
175 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
181 const QVector
<QTreeWidgetItem
*> selectedTrackerItems
= getSelectedTrackerItems();
182 if (selectedTrackerItems
.isEmpty()) return;
185 for (QTreeWidgetItem
*item
: selectedTrackerItems
)
187 int index
= indexOfTopLevelItem(item
);
188 if (index
> NB_STICKY_ITEM
)
190 insertTopLevelItem(index
- 1, takeTopLevelItem(index
));
197 QItemSelectionModel
*selection
= selectionModel();
198 for (QTreeWidgetItem
*item
: selectedTrackerItems
)
199 selection
->select(indexFromItem(item
), (QItemSelectionModel::Rows
| QItemSelectionModel::Select
));
201 setSelectionModel(selection
);
202 // Update torrent trackers
203 QVector
<BitTorrent::TrackerEntry
> trackers
;
204 trackers
.reserve(topLevelItemCount());
205 for (int i
= NB_STICKY_ITEM
; i
< topLevelItemCount(); ++i
)
207 const QString trackerURL
= topLevelItem(i
)->data(COL_URL
, Qt::DisplayRole
).toString();
208 BitTorrent::TrackerEntry
e(trackerURL
);
209 e
.setTier(i
- NB_STICKY_ITEM
);
213 torrent
->replaceTrackers(trackers
);
215 if (!torrent
->isPaused())
216 torrent
->forceReannounce();
219 void TrackerListWidget::moveSelectionDown()
221 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
227 const QVector
<QTreeWidgetItem
*> selectedTrackerItems
= getSelectedTrackerItems();
228 if (selectedTrackerItems
.isEmpty()) return;
231 for (int i
= selectedItems().size() - 1; i
>= 0; --i
)
233 int index
= indexOfTopLevelItem(selectedTrackerItems
.at(i
));
234 if (index
< (topLevelItemCount() - 1))
236 insertTopLevelItem(index
+ 1, takeTopLevelItem(index
));
243 QItemSelectionModel
*selection
= selectionModel();
244 for (QTreeWidgetItem
*item
: selectedTrackerItems
)
245 selection
->select(indexFromItem(item
), (QItemSelectionModel::Rows
| QItemSelectionModel::Select
));
247 setSelectionModel(selection
);
248 // Update torrent trackers
249 QVector
<BitTorrent::TrackerEntry
> trackers
;
250 trackers
.reserve(topLevelItemCount());
251 for (int i
= NB_STICKY_ITEM
; i
< topLevelItemCount(); ++i
)
253 const QString trackerURL
= topLevelItem(i
)->data(COL_URL
, Qt::DisplayRole
).toString();
254 BitTorrent::TrackerEntry
e(trackerURL
);
255 e
.setTier(i
- NB_STICKY_ITEM
);
259 torrent
->replaceTrackers(trackers
);
261 if (!torrent
->isPaused())
262 torrent
->forceReannounce();
265 void TrackerListWidget::clear()
267 qDeleteAll(m_trackerItems
);
268 m_trackerItems
.clear();
270 m_DHTItem
->setText(COL_STATUS
, "");
271 m_DHTItem
->setText(COL_SEEDS
, "");
272 m_DHTItem
->setText(COL_LEECHES
, "");
273 m_DHTItem
->setText(COL_MSG
, "");
274 m_PEXItem
->setText(COL_STATUS
, "");
275 m_PEXItem
->setText(COL_SEEDS
, "");
276 m_PEXItem
->setText(COL_LEECHES
, "");
277 m_PEXItem
->setText(COL_MSG
, "");
278 m_LSDItem
->setText(COL_STATUS
, "");
279 m_LSDItem
->setText(COL_SEEDS
, "");
280 m_LSDItem
->setText(COL_LEECHES
, "");
281 m_LSDItem
->setText(COL_MSG
, "");
284 void TrackerListWidget::loadStickyItems(const BitTorrent::TorrentHandle
*torrent
)
286 const QString working
{tr("Working")};
287 const QString disabled
{tr("Disabled")};
288 const QString torrentDisabled
{tr("Disabled for this torrent")};
289 const auto *session
= BitTorrent::Session::instance();
291 // load DHT information
292 if (torrent
->isPrivate() || torrent
->isDHTDisabled())
293 m_DHTItem
->setText(COL_STATUS
, torrentDisabled
);
294 else if (!session
->isDHTEnabled())
295 m_DHTItem
->setText(COL_STATUS
, disabled
);
297 m_DHTItem
->setText(COL_STATUS
, working
);
299 // Load PeX Information
300 if (torrent
->isPrivate() || torrent
->isPEXDisabled())
301 m_PEXItem
->setText(COL_STATUS
, torrentDisabled
);
302 else if (!session
->isPeXEnabled())
303 m_PEXItem
->setText(COL_STATUS
, disabled
);
305 m_PEXItem
->setText(COL_STATUS
, working
);
307 // Load LSD Information
308 if (torrent
->isPrivate() || torrent
->isLSDDisabled())
309 m_LSDItem
->setText(COL_STATUS
, torrentDisabled
);
310 else if (!session
->isLSDEnabled())
311 m_LSDItem
->setText(COL_STATUS
, disabled
);
313 m_LSDItem
->setText(COL_STATUS
, working
);
315 if (torrent
->isPrivate())
317 QString privateMsg
= tr("This torrent is private");
318 m_DHTItem
->setText(COL_MSG
, privateMsg
);
319 m_PEXItem
->setText(COL_MSG
, privateMsg
);
320 m_LSDItem
->setText(COL_MSG
, privateMsg
);
323 // XXX: libtorrent should provide this info...
324 // Count peers from DHT, PeX, LSD
325 uint seedsDHT
= 0, seedsPeX
= 0, seedsLSD
= 0, peersDHT
= 0, peersPeX
= 0, peersLSD
= 0;
326 for (const BitTorrent::PeerInfo
&peer
: asConst(torrent
->peers()))
328 if (peer
.isConnecting()) continue;
353 m_DHTItem
->setText(COL_SEEDS
, QString::number(seedsDHT
));
354 m_DHTItem
->setText(COL_LEECHES
, QString::number(peersDHT
));
355 m_PEXItem
->setText(COL_SEEDS
, QString::number(seedsPeX
));
356 m_PEXItem
->setText(COL_LEECHES
, QString::number(peersPeX
));
357 m_LSDItem
->setText(COL_SEEDS
, QString::number(seedsLSD
));
358 m_LSDItem
->setText(COL_LEECHES
, QString::number(peersLSD
));
361 void TrackerListWidget::loadTrackers()
363 // Load trackers from torrent handle
364 const BitTorrent::TorrentHandle
*torrent
= m_properties
->getCurrentTorrent();
365 if (!torrent
) return;
367 loadStickyItems(torrent
);
369 // Load actual trackers information
370 const QHash
<QString
, BitTorrent::TrackerInfo
> trackerData
= torrent
->trackerInfos();
371 QStringList oldTrackerURLs
= m_trackerItems
.keys();
373 for (const BitTorrent::TrackerEntry
&entry
: asConst(torrent
->trackers()))
375 const QString trackerURL
= entry
.url();
377 QTreeWidgetItem
*item
= m_trackerItems
.value(trackerURL
, nullptr);
380 item
= new QTreeWidgetItem();
381 item
->setText(COL_URL
, trackerURL
);
382 addTopLevelItem(item
);
383 m_trackerItems
[trackerURL
] = item
;
387 oldTrackerURLs
.removeOne(trackerURL
);
390 item
->setText(COL_TIER
, QString::number(entry
.tier()));
392 const BitTorrent::TrackerInfo data
= trackerData
.value(trackerURL
);
394 switch (entry
.status())
396 case BitTorrent::TrackerEntry::Working
:
397 item
->setText(COL_STATUS
, tr("Working"));
398 item
->setText(COL_MSG
, "");
400 case BitTorrent::TrackerEntry::Updating
:
401 item
->setText(COL_STATUS
, tr("Updating..."));
402 item
->setText(COL_MSG
, "");
404 case BitTorrent::TrackerEntry::NotWorking
:
405 item
->setText(COL_STATUS
, tr("Not working"));
406 item
->setText(COL_MSG
, data
.lastMessage
.trimmed());
408 case BitTorrent::TrackerEntry::NotContacted
:
409 item
->setText(COL_STATUS
, tr("Not contacted yet"));
410 item
->setText(COL_MSG
, "");
414 item
->setText(COL_PEERS
, QString::number(data
.numPeers
));
415 item
->setText(COL_SEEDS
, ((entry
.numSeeds() > -1)
416 ? QString::number(entry
.numSeeds())
418 item
->setText(COL_LEECHES
, ((entry
.numLeeches() > -1)
419 ? QString::number(entry
.numLeeches())
421 item
->setText(COL_DOWNLOADED
, ((entry
.numDownloaded() > -1)
422 ? QString::number(entry
.numDownloaded())
425 const Qt::Alignment alignment
= (Qt::AlignRight
| Qt::AlignVCenter
);
426 item
->setTextAlignment(COL_TIER
, alignment
);
427 item
->setTextAlignment(COL_PEERS
, alignment
);
428 item
->setTextAlignment(COL_SEEDS
, alignment
);
429 item
->setTextAlignment(COL_LEECHES
, alignment
);
430 item
->setTextAlignment(COL_DOWNLOADED
, alignment
);
433 // Remove old trackers
434 for (const QString
&tracker
: asConst(oldTrackerURLs
))
435 delete m_trackerItems
.take(tracker
);
438 // Ask the user for new trackers and add them to the torrent
439 void TrackerListWidget::askForTrackers()
441 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
442 if (!torrent
) return;
444 QVector
<BitTorrent::TrackerEntry
> trackers
;
445 for (const QString
&tracker
: asConst(TrackersAdditionDialog::askForTrackers(this, torrent
)))
448 torrent
->addTrackers(trackers
);
451 void TrackerListWidget::copyTrackerUrl()
453 const QVector
<QTreeWidgetItem
*> selectedTrackerItems
= getSelectedTrackerItems();
454 if (selectedTrackerItems
.isEmpty()) return;
456 QStringList urlsToCopy
;
457 for (const QTreeWidgetItem
*item
: selectedTrackerItems
)
459 QString trackerURL
= item
->data(COL_URL
, Qt::DisplayRole
).toString();
460 qDebug() << QString("Copy: ") + trackerURL
;
461 urlsToCopy
<< trackerURL
;
463 QApplication::clipboard()->setText(urlsToCopy
.join('\n'));
467 void TrackerListWidget::deleteSelectedTrackers()
469 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
476 const QVector
<QTreeWidgetItem
*> selectedTrackerItems
= getSelectedTrackerItems();
477 if (selectedTrackerItems
.isEmpty()) return;
479 QStringList urlsToRemove
;
480 for (const QTreeWidgetItem
*item
: selectedTrackerItems
)
482 QString trackerURL
= item
->data(COL_URL
, Qt::DisplayRole
).toString();
483 urlsToRemove
<< trackerURL
;
484 m_trackerItems
.remove(trackerURL
);
488 // Iterate over the trackers and remove the selected ones
489 const QVector
<BitTorrent::TrackerEntry
> trackers
= torrent
->trackers();
490 QVector
<BitTorrent::TrackerEntry
> remainingTrackers
;
491 remainingTrackers
.reserve(trackers
.size());
493 for (const BitTorrent::TrackerEntry
&entry
: trackers
)
495 if (!urlsToRemove
.contains(entry
.url()))
496 remainingTrackers
.push_back(entry
);
499 torrent
->replaceTrackers(remainingTrackers
);
501 if (!torrent
->isPaused())
502 torrent
->forceReannounce();
505 void TrackerListWidget::editSelectedTracker()
507 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
508 if (!torrent
) return;
510 const QVector
<QTreeWidgetItem
*> selectedTrackerItems
= getSelectedTrackerItems();
511 if (selectedTrackerItems
.isEmpty()) return;
513 // During multi-select only process item selected last
514 const QUrl trackerURL
= selectedTrackerItems
.last()->text(COL_URL
);
517 const QUrl newTrackerURL
= AutoExpandableDialog::getText(this, tr("Tracker editing"), tr("Tracker URL:"),
518 QLineEdit::Normal
, trackerURL
.toString(), &ok
).trimmed();
521 if (!newTrackerURL
.isValid())
523 QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL entered is invalid."));
526 if (newTrackerURL
== trackerURL
) return;
528 QVector
<BitTorrent::TrackerEntry
> trackers
= torrent
->trackers();
530 for (BitTorrent::TrackerEntry
&entry
: trackers
)
532 if (newTrackerURL
== QUrl(entry
.url()))
534 QMessageBox::warning(this, tr("Tracker editing failed"), tr("The tracker URL already exists."));
538 if (!match
&& (trackerURL
== QUrl(entry
.url())))
541 BitTorrent::TrackerEntry
newEntry(newTrackerURL
.toString());
542 newEntry
.setTier(entry
.tier());
547 torrent
->replaceTrackers(trackers
);
549 if (!torrent
->isPaused())
550 torrent
->forceReannounce();
553 void TrackerListWidget::reannounceSelected()
555 const QList
<QTreeWidgetItem
*> selItems
= selectedItems();
556 if (selItems
.isEmpty()) return;
558 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
559 if (!torrent
) return;
561 const QVector
<BitTorrent::TrackerEntry
> trackers
= torrent
->trackers();
563 for (const QTreeWidgetItem
*item
: selItems
)
566 if (item
== m_DHTItem
)
568 torrent
->forceDHTAnnounce();
573 for (int i
= 0; i
< trackers
.size(); ++i
)
575 if (item
->text(COL_URL
) == trackers
[i
].url())
577 torrent
->forceReannounce(i
);
586 void TrackerListWidget::showTrackerListMenu(const QPoint
&)
588 BitTorrent::TorrentHandle
*const torrent
= m_properties
->getCurrentTorrent();
589 if (!torrent
) return;
591 QMenu
*menu
= new QMenu(this);
592 menu
->setAttribute(Qt::WA_DeleteOnClose
);
595 const QAction
*addAct
= menu
->addAction(UIThemeManager::instance()->getIcon("list-add"), tr("Add a new tracker..."));
596 connect(addAct
, &QAction::triggered
, this, &TrackerListWidget::askForTrackers
);
598 if (!getSelectedTrackerItems().isEmpty())
600 const QAction
*editAct
= menu
->addAction(UIThemeManager::instance()->getIcon("edit-rename"),tr("Edit tracker URL..."));
601 connect(editAct
, &QAction::triggered
, this, &TrackerListWidget::editSelectedTracker
);
603 const QAction
*delAct
= menu
->addAction(UIThemeManager::instance()->getIcon("list-remove"), tr("Remove tracker"));
604 connect(delAct
, &QAction::triggered
, this, &TrackerListWidget::deleteSelectedTrackers
);
606 const QAction
*copyAct
= menu
->addAction(UIThemeManager::instance()->getIcon("edit-copy"), tr("Copy tracker URL"));
607 connect(copyAct
, &QAction::triggered
, this, &TrackerListWidget::copyTrackerUrl
);
610 if (!torrent
->isPaused())
612 const QAction
*reannounceSelAct
= menu
->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to selected trackers"));
613 connect(reannounceSelAct
, &QAction::triggered
, this, &TrackerListWidget::reannounceSelected
);
615 menu
->addSeparator();
617 const QAction
*reannounceAllAct
= menu
->addAction(UIThemeManager::instance()->getIcon("view-refresh"), tr("Force reannounce to all trackers"));
618 connect(reannounceAllAct
, &QAction::triggered
, this, [this]()
620 BitTorrent::TorrentHandle
*h
= m_properties
->getCurrentTorrent();
621 h
->forceReannounce();
622 h
->forceDHTAnnounce();
626 menu
->popup(QCursor::pos());
629 void TrackerListWidget::loadSettings()
631 header()->restoreState(Preferences::instance()->getPropTrackerListState());
634 void TrackerListWidget::saveSettings() const
636 Preferences::instance()->setPropTrackerListState(header()->saveState());
639 QStringList
TrackerListWidget::headerLabels()
654 int TrackerListWidget::visibleColumnsCount() const
657 for (int i
= 0; i
< COL_COUNT
; ++i
)
659 if (!isColumnHidden(i
))
666 void TrackerListWidget::displayToggleColumnsMenu(const QPoint
&)
668 QMenu
*menu
= new QMenu(this);
669 menu
->setAttribute(Qt::WA_DeleteOnClose
);
670 menu
->setTitle(tr("Column visibility"));
672 for (int i
= 0; i
< COL_COUNT
; ++i
)
674 QAction
*myAct
= menu
->addAction(headerLabels().at(i
));
675 myAct
->setCheckable(true);
676 myAct
->setChecked(!isColumnHidden(i
));
680 connect(menu
, &QMenu::triggered
, this, [this](const QAction
*action
)
682 const int col
= action
->data().toInt();
683 Q_ASSERT(visibleColumnsCount() > 0);
685 if (!isColumnHidden(col
) && (visibleColumnsCount() == 1))
688 setColumnHidden(col
, !isColumnHidden(col
));
690 if (!isColumnHidden(col
) && (columnWidth(col
) <= 5))
691 resizeColumnToContents(col
);
696 menu
->popup(QCursor::pos());