1 /* packet_list_model.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
14 #include "packet_list_model.h"
18 #include <wsutil/nstime.h>
19 #include <epan/column.h>
20 #include <epan/expert.h>
21 #include <epan/prefs.h>
23 #include "ui/packet_list_utils.h"
24 #include "ui/recent.h"
26 #include <epan/color_filters.h>
27 #include "frame_tvbuff.h"
29 #include <ui/qt/utils/color_utils.h>
30 #include <ui/qt/utils/qt_ui_utils.h>
31 #include "main_application.h"
32 #include <ui/qt/main_window.h>
33 #include <ui/qt/main_status_bar.h>
34 #include <ui/qt/widgets/wireless_timeline.h>
37 #include <QElapsedTimer>
38 #include <QFontMetrics>
39 #include <QModelIndex>
40 #include <QElapsedTimer>
42 // Print timing information
43 //#define DEBUG_PACKET_LIST_MODEL 1
45 #ifdef DEBUG_PACKET_LIST_MODEL
46 #include <wsutil/time_util.h>
49 class SortAbort
: public std::runtime_error
51 using std::runtime_error::runtime_error
;
54 static PacketListModel
* glbl_plist_model
= Q_NULLPTR
;
55 static const int reserved_packets_
= 100000;
58 packet_list_append(column_info
*, frame_data
*fdata
)
60 if (!glbl_plist_model
)
63 /* fdata should be filled with the stuff we need
64 * strings are built at display time.
66 return glbl_plist_model
->appendPacket(fdata
);
70 packet_list_recreate_visible_rows(void)
73 glbl_plist_model
->recreateVisibleRows();
76 PacketListModel::PacketListModel(QObject
*parent
, capture_file
*cf
) :
77 QAbstractItemModel(parent
),
78 number_to_row_(QVector
<int>()),
81 idle_dissection_row_(0)
83 Q_ASSERT(glbl_plist_model
== Q_NULLPTR
);
84 glbl_plist_model
= this;
87 physical_rows_
.reserve(reserved_packets_
);
88 visible_rows_
.reserve(reserved_packets_
);
89 new_visible_rows_
.reserve(1000);
90 number_to_row_
.reserve(reserved_packets_
);
92 if (qobject_cast
<MainWindow
*>(mainApp
->mainWindow()))
94 MainWindow
*mw
= qobject_cast
<MainWindow
*>(mainApp
->mainWindow());
95 QWidget
* wtWidget
= mw
->findChild
<WirelessTimeline
*>();
96 if (wtWidget
&& qobject_cast
<WirelessTimeline
*>(wtWidget
))
98 WirelessTimeline
* wt
= qobject_cast
<WirelessTimeline
*>(wtWidget
);
99 connect(this, &PacketListModel::bgColorizationProgress
,
100 wt
, &WirelessTimeline::bgColorizationProgress
);
105 connect(this, &PacketListModel::maxLineCountChanged
,
106 this, &PacketListModel::emitItemHeightChanged
,
107 Qt::QueuedConnection
);
108 idle_dissection_timer_
= new QElapsedTimer();
111 PacketListModel::~PacketListModel()
113 delete idle_dissection_timer_
;
116 void PacketListModel::setCaptureFile(capture_file
*cf
)
121 // Packet list records have no children (for now, at least).
122 QModelIndex
PacketListModel::index(int row
, int column
, const QModelIndex
&) const
124 if (row
>= visible_rows_
.count() || row
< 0 || !cap_file_
|| column
>= prefs
.num_cols
)
125 return QModelIndex();
127 PacketListRecord
*record
= visible_rows_
[row
];
129 return createIndex(row
, column
, record
);
132 // Everything is under the root.
133 QModelIndex
PacketListModel::parent(const QModelIndex
&) const
135 return QModelIndex();
138 int PacketListModel::packetNumberToRow(int packet_num
) const
140 // map 1-based values to 0-based row numbers. Invisible rows are stored as
141 // the default value (0) and should map to -1.
142 return number_to_row_
.value(packet_num
) - 1;
145 unsigned PacketListModel::recreateVisibleRows()
148 visible_rows_
.resize(0);
149 number_to_row_
.fill(0);
152 foreach (PacketListRecord
*record
, physical_rows_
) {
153 frame_data
*fdata
= record
->frameData();
155 if (fdata
->passed_dfilter
|| fdata
->ref_time
) {
156 visible_rows_
<< record
;
157 if (static_cast<uint32_t>(number_to_row_
.size()) <= fdata
->num
) {
158 number_to_row_
.resize(fdata
->num
+ 10000);
160 number_to_row_
[fdata
->num
] = static_cast<int>(visible_rows_
.count());
163 if (!visible_rows_
.isEmpty()) {
164 beginInsertRows(QModelIndex(), 0, static_cast<int>(visible_rows_
.count()) - 1);
167 idle_dissection_row_
= 0;
168 return static_cast<unsigned>(visible_rows_
.count());
171 void PacketListModel::clear() {
173 qDeleteAll(physical_rows_
);
174 PacketListRecord::invalidateAllRecords();
175 physical_rows_
.resize(0);
176 visible_rows_
.resize(0);
177 new_visible_rows_
.resize(0);
178 number_to_row_
.resize(0);
182 idle_dissection_timer_
->invalidate();
183 idle_dissection_row_
= 0;
186 void PacketListModel::invalidateAllColumnStrings()
188 // https://bugreports.qt.io/browse/QTBUG-58580
189 // https://bugreports.qt.io/browse/QTBUG-124173
190 // https://codereview.qt-project.org/c/qt/qtbase/+/285280
192 // In Qt 6, QAbstractItemView::dataChanged determines how much of the
193 // viewport rectangle is covered by the changed indices and only updates
194 // that much. Unfortunately, if the number of indices is very large,
195 // computing the union of the intersecting rectangle takes much longer
196 // than unconditionally updating the entire viewport. It increases linearly
197 // with the total number of packets in the list, unlike updating the
198 // viewport, which scales with the size of the viewport but is unaffected
199 // by undisplayed packets.
201 // In particular, if the data for all of the model is invalidated, we
202 // know we want to update the entire viewport and very much do not
203 // want to waste time calculating the affected area. (This can take
204 // 1 s with 1.4 M packets, 9 s with 12 M packets.)
206 // Issuing layoutAboutToBeChanged() and layoutChanged() causes the
207 // QTreeView to clear all the information for each of the view items,
208 // but without clearing the current and selected items (unlike
209 // [begin|end]ResetModel.)
211 // Theoretically this is less efficient because dataChanged() has a list
212 // of what roles changed and the other signals do not; in practice,
213 // neither QTreeView::dataChanged nor QAbstractItemView::dataChanged
214 // actually use the roles parameter, and just reset everything.
215 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
216 emit
layoutAboutToBeChanged();
218 PacketListRecord::invalidateAllRecords();
219 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
220 emit
layoutChanged();
222 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
223 QVector
<int>() << Qt::DisplayRole
);
227 void PacketListModel::resetColumns()
229 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
230 emit
layoutAboutToBeChanged();
233 PacketListRecord::resetColumns(&cap_file_
->cinfo
);
236 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
237 emit
layoutChanged();
239 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
241 emit
headerDataChanged(Qt::Horizontal
, 0, columnCount() - 1);
244 void PacketListModel::resetColorized()
246 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
247 emit
layoutAboutToBeChanged();
249 PacketListRecord::resetColorization();
250 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
251 emit
layoutChanged();
253 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
254 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
);
258 void PacketListModel::toggleFrameMark(const QModelIndexList
&indeces
)
260 if (!cap_file_
|| indeces
.count() <= 0)
263 int sectionMax
= columnCount() - 1;
265 foreach (QModelIndex index
, indeces
) {
266 if (! index
.isValid())
269 PacketListRecord
*record
= static_cast<PacketListRecord
*>(index
.internalPointer());
273 frame_data
*fdata
= record
->frameData();
278 cf_unmark_frame(cap_file_
, fdata
);
280 cf_mark_frame(cap_file_
, fdata
);
282 emit
dataChanged(index
.sibling(index
.row(), 0), index
.sibling(index
.row(), sectionMax
),
283 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
);
287 void PacketListModel::setDisplayedFrameMark(bool set
)
289 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
290 emit
layoutAboutToBeChanged();
292 foreach (PacketListRecord
*record
, visible_rows_
) {
294 cf_mark_frame(cap_file_
, record
->frameData());
296 cf_unmark_frame(cap_file_
, record
->frameData());
299 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
300 emit
layoutChanged();
302 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
303 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
);
307 void PacketListModel::toggleFrameIgnore(const QModelIndexList
&indeces
)
309 if (!cap_file_
|| indeces
.count() <= 0)
312 int sectionMax
= columnCount() - 1;
314 foreach (QModelIndex index
, indeces
) {
315 if (! index
.isValid())
318 PacketListRecord
*record
= static_cast<PacketListRecord
*>(index
.internalPointer());
322 frame_data
*fdata
= record
->frameData();
327 cf_unignore_frame(cap_file_
, fdata
);
329 cf_ignore_frame(cap_file_
, fdata
);
331 emit
dataChanged(index
.sibling(index
.row(), 0), index
.sibling(index
.row(), sectionMax
),
332 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
336 void PacketListModel::setDisplayedFrameIgnore(bool set
)
338 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
339 emit
layoutAboutToBeChanged();
341 foreach (PacketListRecord
*record
, visible_rows_
) {
343 cf_ignore_frame(cap_file_
, record
->frameData());
345 cf_unignore_frame(cap_file_
, record
->frameData());
348 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
349 emit
layoutChanged();
351 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
352 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
356 void PacketListModel::toggleFrameRefTime(const QModelIndex
&rt_index
)
358 if (!cap_file_
|| !rt_index
.isValid()) return;
360 PacketListRecord
*record
= static_cast<PacketListRecord
*>(rt_index
.internalPointer());
363 frame_data
*fdata
= record
->frameData();
366 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
367 emit
layoutAboutToBeChanged();
369 if (fdata
->ref_time
) {
371 cap_file_
->ref_time_count
--;
374 cap_file_
->ref_time_count
++;
376 cf_reftime_packets(cap_file_
);
377 if (!fdata
->ref_time
&& !fdata
->passed_dfilter
) {
378 cap_file_
->displayed_count
--;
380 record
->resetColumns(&cap_file_
->cinfo
);
381 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
384 void PacketListModel::unsetAllFrameRefTime()
386 if (!cap_file_
) return;
388 /* XXX: we might need a progressbar here */
390 foreach (PacketListRecord
*record
, physical_rows_
) {
391 frame_data
*fdata
= record
->frameData();
392 if (fdata
->ref_time
) {
396 cap_file_
->ref_time_count
= 0;
397 cf_reftime_packets(cap_file_
);
398 PacketListRecord::resetColumns(&cap_file_
->cinfo
);
399 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
400 emit
layoutChanged();
402 emit
dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
406 void PacketListModel::addFrameComment(const QModelIndexList
&indices
, const QByteArray
&comment
)
408 int sectionMax
= columnCount() - 1;
410 if (!cap_file_
) return;
412 for (const auto &index
: indices
) {
413 if (!index
.isValid()) continue;
415 PacketListRecord
*record
= static_cast<PacketListRecord
*>(index
.internalPointer());
416 if (!record
) continue;
418 fdata
= record
->frameData();
419 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
420 wtap_block_add_string_option(pkt_block
, OPT_COMMENT
, comment
.data(), comment
.size());
422 if (!cf_set_modified_block(cap_file_
, fdata
, pkt_block
)) {
423 cap_file_
->packet_comment_count
++;
424 expert_update_comment_count(cap_file_
->packet_comment_count
);
427 // In case there are coloring rules or columns related to comments.
430 // XXX: "Does any active coloring rule relate to frame data"
431 // could be an optimization. For columns, note that
432 // "col_based_on_frame_data" only applies to built in columns,
433 // not custom columns based on frame data. (Should we prevent
434 // custom columns based on frame data from being created,
435 // substituting them with the other columns?)
437 // Note that there are not currently any fields that depend on
438 // whether other frames have comments, unlike with time references
439 // and time shifts ("frame.time_relative", "frame.offset_shift", etc.)
440 // If there were, then we'd need to reset data for all frames instead
441 // of just the frames changed.
442 record
->invalidateColorized();
443 record
->invalidateRecord();
444 emit
dataChanged(index
.sibling(index
.row(), 0), index
.sibling(index
.row(), sectionMax
),
445 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
449 void PacketListModel::setFrameComment(const QModelIndex
&index
, const QByteArray
&comment
, unsigned c_number
)
451 int sectionMax
= columnCount() - 1;
453 if (!cap_file_
) return;
455 if (!index
.isValid()) return;
457 PacketListRecord
*record
= static_cast<PacketListRecord
*>(index
.internalPointer());
460 fdata
= record
->frameData();
462 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
463 if (comment
.isEmpty()) {
464 wtap_block_remove_nth_option_instance(pkt_block
, OPT_COMMENT
, c_number
);
465 if (!cf_set_modified_block(cap_file_
, fdata
, pkt_block
)) {
466 cap_file_
->packet_comment_count
--;
467 expert_update_comment_count(cap_file_
->packet_comment_count
);
470 wtap_block_set_nth_string_option_value(pkt_block
, OPT_COMMENT
, c_number
, comment
.data(), comment
.size());
471 cf_set_modified_block(cap_file_
, fdata
, pkt_block
);
474 record
->invalidateColorized();
475 record
->invalidateRecord();
476 emit
dataChanged(index
.sibling(index
.row(), 0), index
.sibling(index
.row(), sectionMax
),
477 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
480 void PacketListModel::deleteFrameComments(const QModelIndexList
&indices
)
482 int sectionMax
= columnCount() - 1;
484 if (!cap_file_
) return;
486 for (const auto &index
: indices
) {
487 if (!index
.isValid()) continue;
489 PacketListRecord
*record
= static_cast<PacketListRecord
*>(index
.internalPointer());
490 if (!record
) continue;
492 fdata
= record
->frameData();
493 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
494 unsigned n_comments
= wtap_block_count_option(pkt_block
, OPT_COMMENT
);
497 for (unsigned i
= 0; i
< n_comments
; i
++) {
498 wtap_block_remove_nth_option_instance(pkt_block
, OPT_COMMENT
, 0);
500 if (!cf_set_modified_block(cap_file_
, fdata
, pkt_block
)) {
501 cap_file_
->packet_comment_count
-= n_comments
;
502 expert_update_comment_count(cap_file_
->packet_comment_count
);
505 record
->invalidateColorized();
506 record
->invalidateRecord();
507 emit
dataChanged(index
.sibling(index
.row(), 0), index
.sibling(index
.row(), sectionMax
),
508 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
513 void PacketListModel::deleteAllFrameComments()
516 int sectionMax
= columnCount() - 1;
517 if (!cap_file_
) return;
519 /* XXX: we might need a progressbar here */
521 foreach (PacketListRecord
*record
, physical_rows_
) {
522 frame_data
*fdata
= record
->frameData();
523 wtap_block_t pkt_block
= cf_get_packet_block(cap_file_
, fdata
);
524 unsigned n_comments
= wtap_block_count_option(pkt_block
, OPT_COMMENT
);
527 for (unsigned i
= 0; i
< n_comments
; i
++) {
528 wtap_block_remove_nth_option_instance(pkt_block
, OPT_COMMENT
, 0);
530 cf_set_modified_block(cap_file_
, fdata
, pkt_block
);
532 record
->invalidateColorized();
533 record
->invalidateRecord();
534 row
= packetNumberToRow(fdata
->num
);
536 emit
dataChanged(index(row
, 0), index(row
, sectionMax
),
537 QVector
<int>() << Qt::BackgroundRole
<< Qt::ForegroundRole
<< Qt::DisplayRole
);
541 cap_file_
->packet_comment_count
= 0;
542 expert_update_comment_count(cap_file_
->packet_comment_count
);
545 void PacketListModel::setMaximumRowHeight(int height
)
547 max_row_height_
= height
;
548 // As the QTreeView uniformRowHeights documentation says,
549 // "The height is obtained from the first item in the view. It is
550 // updated when the data changes on that item."
551 emit
dataChanged(index(0, 0), index(0, columnCount() - 1));
554 int PacketListModel::sort_column_
;
555 int PacketListModel::sort_column_is_numeric_
;
556 int PacketListModel::text_sort_column_
;
557 Qt::SortOrder
PacketListModel::sort_order_
;
558 capture_file
*PacketListModel::sort_cap_file_
;
559 bool PacketListModel::stop_flag_
;
560 ProgressFrame
*PacketListModel::progress_frame_
;
561 double PacketListModel::comps_
;
562 double PacketListModel::exp_comps_
;
564 QElapsedTimer busy_timer_
;
565 const int busy_timeout_
= 65; // ms, approximately 15 fps
566 void PacketListModel::sort(int column
, Qt::SortOrder order
)
568 if (!cap_file_
|| visible_rows_
.count() < 1) return;
569 if (column
< 0) return;
571 if (physical_rows_
.count() < 1)
574 sort_column_
= column
;
575 text_sort_column_
= PacketListRecord::textColumn(column
);
577 sort_cap_file_
= cap_file_
;
579 QString col_title
= get_column_title(column
);
581 if (text_sort_column_
>= 0 && (unsigned)visible_rows_
.count() > prefs
.gui_packet_list_cached_rows_max
) {
582 /* Column not based on frame data but by column text that requires
583 * dissection, so to sort in a reasonable amount of time the column
584 * text needs to be cached.
586 /* If the sort is being triggered because the columns were already
587 * sorted and the filter is being cleared (or changed to something
588 * else with more rows than fit in the cache), then the temporary
589 * message will be immediately overwritten with the standard capture
590 * statistics by the packets_bar_update() call after thawing the rows.
591 * It will still blink yellow, and the user will get the message if
592 * they then click on the header file (wondering why it didn't sort.)
594 if (col_title
.isEmpty()) {
595 col_title
= tr("Column");
597 QString temp_msg
= tr("%1 can only be sorted with %2 or fewer visible rows; increase cache size in Layout preferences").arg(col_title
).arg(prefs
.gui_packet_list_cached_rows_max
);
598 mainApp
->pushStatus(MainApplication::TemporaryStatus
, temp_msg
);
602 /* If we are currently in the middle of reading the capture file, don't
603 * sort. PacketList::captureFileReadFinished invalidates all the cached
604 * column strings and then tries to sort again.
605 * Similarly, claim the read lock because we don't want the file to
606 * change out from under us while sorting, which can segfault. (Previously
607 * we ignored user input, but now in order to cancel sorting we don't.)
609 if (sort_cap_file_
->read_lock
) {
610 ws_info("Refusing to sort because capture file is being read");
611 /* We shouldn't have to tell the user because we're just deferring
612 * the sort until PacketList::captureFileReadFinished
616 sort_cap_file_
->read_lock
= true;
619 if (!col_title
.isEmpty()) {
620 busy_msg
= tr("Sorting \"%1\"…").arg(col_title
);
622 busy_msg
= tr("Sorting …");
626 /* XXX: The expected number of comparisons is O(N log N), but this could
627 * be a pretty significant overestimate of the amount of time it takes,
628 * if there are lots of identical entries. (Especially with string
629 * comparisons, some comparisons are faster than others.) Better to
632 exp_comps_
= log2(visible_rows_
.count()) * visible_rows_
.count();
633 progress_frame_
= nullptr;
634 if (qobject_cast
<MainWindow
*>(mainApp
->mainWindow())) {
635 MainWindow
*mw
= qobject_cast
<MainWindow
*>(mainApp
->mainWindow());
636 progress_frame_
= mw
->findChild
<ProgressFrame
*>();
637 if (progress_frame_
) {
638 progress_frame_
->showProgress(busy_msg
, true, false, &stop_flag_
, 0);
639 connect(progress_frame_
, &ProgressFrame::stopLoading
,
640 this, &PacketListModel::stopSorting
);
645 sort_column_is_numeric_
= isNumericColumn(sort_column_
);
646 QVector
<PacketListRecord
*> sorted_visible_rows_
= visible_rows_
;
648 std::sort(sorted_visible_rows_
.begin(), sorted_visible_rows_
.end(), recordLessThan
);
651 visible_rows_
.resize(0);
652 number_to_row_
.fill(0);
653 foreach (PacketListRecord
*record
, sorted_visible_rows_
) {
654 frame_data
*fdata
= record
->frameData();
656 if (fdata
->passed_dfilter
|| fdata
->ref_time
) {
657 visible_rows_
<< record
;
658 if (number_to_row_
.size() <= (int)fdata
->num
) {
659 number_to_row_
.resize(fdata
->num
+ 10000);
661 number_to_row_
[fdata
->num
] = static_cast<int>(visible_rows_
.count());
665 } catch (const SortAbort
& e
) {
666 mainApp
->pushStatus(MainApplication::TemporaryStatus
, e
.what());
669 if (progress_frame_
!= nullptr) {
670 progress_frame_
->hide();
671 disconnect(progress_frame_
, &ProgressFrame::stopLoading
,
672 this, &PacketListModel::stopSorting
);
674 sort_cap_file_
->read_lock
= false;
676 if (cap_file_
->current_frame
) {
677 emit
goToPacket(cap_file_
->current_frame
->num
);
681 void PacketListModel::stopSorting()
686 bool PacketListModel::isNumericColumn(int column
)
688 /* XXX - Should this and ui/packet_list_utils.c right_justify_column()
689 * be the same list of columns?
694 switch (sort_cap_file_
->cinfo
.columns
[column
].col_fmt
) {
695 case COL_CUMULATIVE_BYTES
: /**< 3) Cumulative number of bytes */
696 case COL_DELTA_TIME
: /**< 5) Delta time */
697 case COL_DELTA_TIME_DIS
: /**< 8) Delta time displayed*/
698 case COL_UNRES_DST_PORT
: /**< 10) Unresolved dest port */
699 case COL_FREQ_CHAN
: /**< 15) IEEE 802.11 (and WiMax?) - Channel */
700 case COL_RSSI
: /**< 22) IEEE 802.11 - received signal strength */
701 case COL_TX_RATE
: /**< 23) IEEE 802.11 - TX rate in Mbps */
702 case COL_NUMBER
: /**< 32) Packet list item number */
703 case COL_NUMBER_DIS
: /**< 33) Packet list item number */
704 case COL_PACKET_LENGTH
: /**< 34) Packet length in bytes */
705 case COL_UNRES_SRC_PORT
: /**< 42) Unresolved source port */
709 * Try to sort port numbers as number, if the numeric comparison fails (due
710 * to name resolution), it will fallback to string comparison.
712 case COL_RES_DST_PORT
: /**< 10) Resolved dest port */
713 case COL_DEF_DST_PORT
: /**< 12) Destination port */
714 case COL_DEF_SRC_PORT
: /**< 38) Source port */
715 case COL_RES_SRC_PORT
: /**< 41) Resolved source port */
719 /* handle custom columns below. */
726 unsigned num_fields
= g_slist_length(sort_cap_file_
->cinfo
.columns
[column
].col_custom_fields_ids
);
727 col_custom_t
*col_custom
;
728 for (unsigned i
= 0; i
< num_fields
; i
++) {
729 col_custom
= (col_custom_t
*) g_slist_nth_data(sort_cap_file_
->cinfo
.columns
[column
].col_custom_fields_ids
, i
);
730 if (col_custom
->field_id
== 0) {
731 /* XXX - We need some way to check the compiled dfilter's expected
732 * return type. Best would be to use the actual field values return
733 * and sort on those (we could skip expensive string conversions
734 * in the numeric case, see below)
738 header_field_info
*hfi
= proto_registrar_get_nth(col_custom
->field_id
);
741 * Reject a field when there is no numeric field type or when:
742 * - there are (value_string) "strings"
743 * (but do accept fields which have a unit suffix).
744 * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string
745 * comparison is faster than conversion to double).
746 * - BASE_CUSTOM (these can be formatted in any way).
749 (hfi
->strings
!= NULL
&& !(hfi
->display
& BASE_UNIT_STRING
)) ||
750 !(((FT_IS_INT(hfi
->type
) || FT_IS_UINT(hfi
->type
)) &&
751 ((FIELD_DISPLAY(hfi
->display
) == BASE_DEC
) ||
752 (FIELD_DISPLAY(hfi
->display
) == BASE_OCT
) ||
753 (FIELD_DISPLAY(hfi
->display
) == BASE_DEC_HEX
))) ||
754 (hfi
->type
== FT_DOUBLE
) || (hfi
->type
== FT_FLOAT
) ||
755 (hfi
->type
== FT_BOOLEAN
) || (hfi
->type
== FT_FRAMENUM
) ||
756 (hfi
->type
== FT_RELATIVE_TIME
))) {
764 bool PacketListModel::recordLessThan(PacketListRecord
*r1
, PacketListRecord
*r2
)
769 // Wherein we try to cram the logic of packet_list_compare_records,
770 // _packet_list_compare_records, and packet_list_compare_custom from
771 // gtk/packet_list_store.c into one function
773 if (busy_timer_
.elapsed() > busy_timeout_
) {
774 if (progress_frame_
) {
775 progress_frame_
->setValue(static_cast<int>(comps_
/exp_comps_
* 100));
777 // What's the least amount of processing that we can do which will draw
778 // the busy indicator?
779 mainApp
->processEvents(QEventLoop::ExcludeSocketNotifiers
, 1);
781 throw SortAbort("Sorting aborted");
783 busy_timer_
.restart();
785 if (sort_column_
< 0) {
787 cmp_val
= frame_data_compare(sort_cap_file_
->epan
, r1
->frameData(), r2
->frameData(), COL_NUMBER
);
788 } else if (text_sort_column_
< 0) {
789 // Column comes directly from frame data
790 cmp_val
= frame_data_compare(sort_cap_file_
->epan
, r1
->frameData(), r2
->frameData(), sort_cap_file_
->cinfo
.columns
[sort_column_
].col_fmt
);
792 QString r1String
= r1
->columnString(sort_cap_file_
, sort_column_
);
793 QString r2String
= r2
->columnString(sort_cap_file_
, sort_column_
);
794 // XXX: The naive string comparison compares Unicode code points.
795 // Proper collation is more expensive
796 cmp_val
= r1String
.compare(r2String
);
797 if (cmp_val
!= 0 && sort_column_is_numeric_
) {
798 // Custom column with numeric data (or something like a port number).
799 // Attempt to convert to numbers.
800 // XXX This is slow. Can we avoid doing this? Perhaps the actual
801 // values used for sorting should be cached too as QVariant[List].
802 // If so, we could consider using QCollatorSortKeys or similar
803 // for strings as well.
805 double num_r1
= parseNumericColumn(r1String
, &ok_r1
);
806 double num_r2
= parseNumericColumn(r2String
, &ok_r2
);
808 if (!ok_r1
&& !ok_r2
) {
810 } else if (!ok_r1
|| (ok_r2
&& num_r1
< num_r2
)) {
811 // either r1 is invalid (and sort it before others) or both
812 // r1 and r2 are valid (sort normally)
814 } else if (!ok_r2
|| (num_r1
> num_r2
)) {
820 // All else being equal, compare column numbers.
821 cmp_val
= frame_data_compare(sort_cap_file_
->epan
, r1
->frameData(), r2
->frameData(), COL_NUMBER
);
825 if (sort_order_
== Qt::AscendingOrder
) {
832 // Parses a field as a double. Handle values with suffixes ("12ms"), negative
833 // values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values
834 // that do not contain any numeric value ("Unknown") as invalid.
835 double PacketListModel::parseNumericColumn(const QString
&val
, bool *ok
)
837 QByteArray ba
= val
.toUtf8();
838 const char *strval
= ba
.constData();
840 double num
= g_ascii_strtod(strval
, &end
);
845 // ::data is const so we have to make changes here.
846 void PacketListModel::emitItemHeightChanged(const QModelIndex
&ih_index
)
848 if (!ih_index
.isValid()) return;
850 PacketListRecord
*record
= static_cast<PacketListRecord
*>(ih_index
.internalPointer());
853 if (record
->lineCount() > max_line_count_
) {
854 max_line_count_
= record
->lineCount();
855 emit
itemHeightChanged(ih_index
);
859 int PacketListModel::rowCount(const QModelIndex
&) const
861 return static_cast<int>(visible_rows_
.count());
864 int PacketListModel::columnCount(const QModelIndex
&) const
866 return prefs
.num_cols
;
869 QVariant
PacketListModel::data(const QModelIndex
&d_index
, int role
) const
871 if (!d_index
.isValid())
874 PacketListRecord
*record
= static_cast<PacketListRecord
*>(d_index
.internalPointer());
877 const frame_data
*fdata
= record
->frameData();
882 case Qt::TextAlignmentRole
:
883 switch(recent_get_column_xalign(d_index
.column())) {
884 case COLUMN_XALIGN_RIGHT
:
885 return Qt::AlignRight
;
886 case COLUMN_XALIGN_CENTER
:
887 return Qt::AlignCenter
;
888 case COLUMN_XALIGN_LEFT
:
889 return Qt::AlignLeft
;
890 case COLUMN_XALIGN_DEFAULT
:
892 if (right_justify_column(d_index
.column(), cap_file_
)) {
893 return Qt::AlignRight
;
897 return Qt::AlignLeft
;
899 case Qt::BackgroundRole
:
900 const color_t
*color
;
901 if (fdata
->ignored
) {
902 color
= &prefs
.gui_ignored_bg
;
903 } else if (fdata
->marked
) {
904 color
= &prefs
.gui_marked_bg
;
905 } else if (fdata
->color_filter
&& recent
.packet_list_colorize
) {
906 const color_filter_t
*color_filter
= (const color_filter_t
*) fdata
->color_filter
;
907 color
= &color_filter
->bg_color
;
911 return ColorUtils::fromColorT(color
);
912 case Qt::ForegroundRole
:
913 if (fdata
->ignored
) {
914 color
= &prefs
.gui_ignored_fg
;
915 } else if (fdata
->marked
) {
916 color
= &prefs
.gui_marked_fg
;
917 } else if (fdata
->color_filter
&& recent
.packet_list_colorize
) {
918 const color_filter_t
*color_filter
= (const color_filter_t
*) fdata
->color_filter
;
919 color
= &color_filter
->fg_color
;
923 return ColorUtils::fromColorT(color
);
924 case Qt::DisplayRole
:
926 int column
= d_index
.column();
927 QString column_string
= record
->columnString(cap_file_
, column
, true);
928 // We don't know an item's sizeHint until we fetch its text here.
929 // Assume each line count is 1. If the line count changes, emit
930 // itemHeightChanged which triggers another redraw (including a
931 // fetch of SizeHintRole and DisplayRole) in the next event loop.
932 if (column
== 0 && record
->lineCountChanged() && record
->lineCount() > max_line_count_
) {
933 emit
maxLineCountChanged(d_index
);
935 return column_string
;
937 case Qt::SizeHintRole
:
939 // If this is the first row and column, return the maximum row height...
940 if (d_index
.row() < 1 && d_index
.column() < 1 && max_row_height_
> 0) {
941 QSize size
= QSize(-1, max_row_height_
);
944 // ...otherwise punt so that the item delegate can correctly calculate the item width.
952 QVariant
PacketListModel::headerData(int section
, Qt::Orientation orientation
,
955 if (!cap_file_
) return QVariant();
957 if (orientation
== Qt::Horizontal
&& section
< prefs
.num_cols
) {
959 case Qt::DisplayRole
:
960 return QVariant::fromValue(QString(get_column_title(section
)));
961 case Qt::ToolTipRole
:
962 return QVariant::fromValue(gchar_free_to_qstring(get_column_tooltip(section
)));
963 case PacketListModel::HEADER_CAN_DISPLAY_STRINGS
:
964 return (bool)display_column_strings(section
, cap_file_
);
965 case PacketListModel::HEADER_CAN_DISPLAY_DETAILS
:
966 return (bool)display_column_details(section
, cap_file_
);
975 void PacketListModel::flushVisibleRows()
977 int pos
= static_cast<int>(visible_rows_
.count());
979 if (new_visible_rows_
.count() > 0) {
980 beginInsertRows(QModelIndex(), pos
, pos
+ static_cast<int>(new_visible_rows_
.count()));
981 foreach (PacketListRecord
*record
, new_visible_rows_
) {
982 frame_data
*fdata
= record
->frameData();
984 visible_rows_
<< record
;
985 if (static_cast<unsigned int>(number_to_row_
.size()) <= fdata
->num
) {
986 number_to_row_
.resize(fdata
->num
+ 10000);
988 number_to_row_
[fdata
->num
] = static_cast<int>(visible_rows_
.count());
991 new_visible_rows_
.resize(0);
995 // Fill our column string and colorization cache while the application is
996 // idle. Try to be as conservative with the CPU and disk as possible.
997 static const int idle_dissection_interval_
= 5; // ms
998 void PacketListModel::dissectIdle(bool reset
)
1001 // qDebug() << "=di reset" << idle_dissection_row_;
1002 idle_dissection_row_
= 0;
1003 } else if (!idle_dissection_timer_
->isValid()) {
1007 idle_dissection_timer_
->restart();
1009 if (!cap_file_
|| cap_file_
->read_lock
) {
1010 // File is in use (at worst, being rescanned). Try again later.
1011 QTimer::singleShot(idle_dissection_interval_
, this, [=]() { dissectIdle(); });
1015 int first
= idle_dissection_row_
;
1016 while (idle_dissection_timer_
->elapsed() < idle_dissection_interval_
1017 && idle_dissection_row_
< physical_rows_
.count()) {
1018 ensureRowColorized(idle_dissection_row_
);
1019 idle_dissection_row_
++;
1020 // if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
1023 if (idle_dissection_row_
< physical_rows_
.count()) {
1024 QTimer::singleShot(0, this, [=]() { dissectIdle(); });
1026 idle_dissection_timer_
->invalidate();
1029 // report colorization progress
1030 emit
bgColorizationProgress(first
+1, idle_dissection_row_
+1);
1033 // XXX Pass in cinfo from packet_list_append so that we can fill in
1035 int PacketListModel::appendPacket(frame_data
*fdata
)
1037 PacketListRecord
*record
= new PacketListRecord(fdata
);
1040 #ifdef DEBUG_PACKET_LIST_MODEL
1041 if (fdata
->num
% 10000 == 1) {
1042 log_resource_usage(fdata
->num
== 1, "%u packets", fdata
->num
);
1046 physical_rows_
<< record
;
1048 if (fdata
->passed_dfilter
|| fdata
->ref_time
) {
1049 new_visible_rows_
<< record
;
1050 if (new_visible_rows_
.count() < 2) {
1051 // This is the first queued packet. Schedule an insertion for
1052 // the next UI update.
1053 QTimer::singleShot(0, this, &PacketListModel::flushVisibleRows
);
1055 pos
= static_cast<int>( visible_rows_
.count() + new_visible_rows_
.count() ) - 1;
1058 emit
packetAppended(cap_file_
, fdata
, physical_rows_
.size() - 1);
1060 return static_cast<int>(pos
);
1063 frame_data
*PacketListModel::getRowFdata(QModelIndex idx
) const
1067 return getRowFdata(idx
.row());
1070 frame_data
*PacketListModel::getRowFdata(int row
) const {
1071 if (row
< 0 || row
>= visible_rows_
.count())
1073 PacketListRecord
*record
= visible_rows_
[row
];
1076 return record
->frameData();
1079 void PacketListModel::ensureRowColorized(int row
)
1081 if (row
< 0 || row
>= visible_rows_
.count())
1083 PacketListRecord
*record
= visible_rows_
[row
];
1086 if (!record
->colorized()) {
1087 record
->ensureColorized(cap_file_
);
1091 int PacketListModel::visibleIndexOf(frame_data
*fdata
) const
1093 if (fdata
== nullptr) {
1096 return packetNumberToRow(fdata
->num
);