1 /* packet_list_model.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "packet_list_model.h"
28 #include <wsutil/nstime.h>
29 #include <epan/column.h>
30 #include <epan/prefs.h>
32 #include "ui/packet_list_utils.h"
33 #include "ui/recent.h"
36 #include "color_filters.h"
37 #include "frame_tvbuff.h"
39 #include "color_utils.h"
40 #include "wireshark_application.h"
43 #include <QElapsedTimer>
44 #include <QFontMetrics>
45 #include <QModelIndex>
46 #include <QElapsedTimer>
48 PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
49 QAbstractItemModel(parent),
52 idle_dissection_row_(0)
55 PacketListRecord::clearStringPool();
56 connect(this, SIGNAL(maxLineCountChanged(QModelIndex)),
57 this, SLOT(emitItemHeightChanged(QModelIndex)),
58 Qt::QueuedConnection);
59 idle_dissection_timer_ = new QElapsedTimer();
62 PacketListModel::~PacketListModel()
64 delete idle_dissection_timer_;
67 void PacketListModel::setCaptureFile(capture_file *cf)
73 // Packet list records have no children (for now, at least).
74 QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
76 if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
79 PacketListRecord *record = visible_rows_[row];
81 return createIndex(row, column, record);
84 // Everything is under the root.
85 QModelIndex PacketListModel::parent(const QModelIndex &) const
90 int PacketListModel::packetNumberToRow(int packet_num) const
92 return number_to_row_.value(packet_num, -1);
95 guint PacketListModel::recreateVisibleRows()
97 int pos = visible_rows_.count();
100 visible_rows_.clear();
101 number_to_row_.clear();
104 beginInsertRows(QModelIndex(), pos, pos);
105 foreach (PacketListRecord *record, physical_rows_) {
106 if (record->frameData()->flags.passed_dfilter || record->frameData()->flags.ref_time) {
107 visible_rows_ << record;
108 number_to_row_[record->frameData()->num] = visible_rows_.count() - 1;
112 idle_dissection_row_ = 0;
113 return visible_rows_.count();
116 void PacketListModel::clear() {
118 qDeleteAll(physical_rows_);
119 physical_rows_.clear();
120 visible_rows_.clear();
121 number_to_row_.clear();
122 PacketListRecord::clearStringPool();
126 idle_dissection_row_ = 0;
129 void PacketListModel::resetColumns()
132 PacketListRecord::resetColumns(&cap_file_->cinfo);
134 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
135 headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
138 void PacketListModel::resetColorized()
140 foreach (PacketListRecord *record, physical_rows_) {
141 record->resetColorized();
143 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
146 void PacketListModel::toggleFrameMark(const QModelIndex &fm_index)
148 if (!cap_file_ || !fm_index.isValid()) return;
150 PacketListRecord *record = static_cast<PacketListRecord*>(fm_index.internalPointer());
153 frame_data *fdata = record->frameData();
156 if (fdata->flags.marked)
157 cf_unmark_frame(cap_file_, fdata);
159 cf_mark_frame(cap_file_, fdata);
161 dataChanged(fm_index, fm_index);
164 void PacketListModel::setDisplayedFrameMark(gboolean set)
166 foreach (PacketListRecord *record, visible_rows_) {
168 cf_mark_frame(cap_file_, record->frameData());
170 cf_unmark_frame(cap_file_, record->frameData());
173 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
176 void PacketListModel::toggleFrameIgnore(const QModelIndex &i_index)
178 if (!cap_file_ || !i_index.isValid()) return;
180 PacketListRecord *record = static_cast<PacketListRecord*>(i_index.internalPointer());
183 frame_data *fdata = record->frameData();
186 if (fdata->flags.ignored)
187 cf_unignore_frame(cap_file_, fdata);
189 cf_ignore_frame(cap_file_, fdata);
192 void PacketListModel::setDisplayedFrameIgnore(gboolean set)
194 foreach (PacketListRecord *record, visible_rows_) {
196 cf_ignore_frame(cap_file_, record->frameData());
198 cf_unignore_frame(cap_file_, record->frameData());
201 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
204 void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
206 if (!cap_file_ || !rt_index.isValid()) return;
208 PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
211 frame_data *fdata = record->frameData();
214 if (fdata->flags.ref_time) {
215 fdata->flags.ref_time=0;
216 cap_file_->ref_time_count--;
218 fdata->flags.ref_time=1;
219 cap_file_->ref_time_count++;
221 cf_reftime_packets(cap_file_);
222 if (!fdata->flags.ref_time && !fdata->flags.passed_dfilter) {
223 cap_file_->displayed_count--;
225 record->resetColumns(&cap_file_->cinfo);
226 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
229 void PacketListModel::unsetAllFrameRefTime()
231 if (!cap_file_) return;
233 /* XXX: we might need a progressbar here */
235 foreach (PacketListRecord *record, physical_rows_) {
236 frame_data *fdata = record->frameData();
237 if (fdata->flags.ref_time) {
238 fdata->flags.ref_time = 0;
241 cap_file_->ref_time_count = 0;
242 cf_reftime_packets(cap_file_);
243 PacketListRecord::resetColumns(&cap_file_->cinfo);
244 dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
247 void PacketListModel::setMaximiumRowHeight(int height)
249 max_row_height_ = height;
250 // As the QTreeView uniformRowHeights documentation says,
251 // "The height is obtained from the first item in the view. It is
252 // updated when the data changes on that item."
253 dataChanged(index(0, 0), index(0, columnCount() - 1));
256 //void PacketListModel::setMonospaceFont(const QFont &mono_font, int row_height)
258 // QFontMetrics fm(mono_font_);
259 // mono_font_ = mono_font;
260 // row_height_ = row_height;
261 // line_spacing_ = fm.lineSpacing();
264 // The Qt MVC documentation suggests using QSortFilterProxyModel for sorting
265 // and filtering. That seems like overkill but it might be something we want
266 // to do in the future.
268 int PacketListModel::sort_column_;
269 int PacketListModel::text_sort_column_;
270 Qt::SortOrder PacketListModel::sort_order_;
271 capture_file *PacketListModel::sort_cap_file_;
273 QElapsedTimer busy_timer_;
274 const int busy_timeout_ = 65; // ms, approximately 15 fps
275 void PacketListModel::sort(int column, Qt::SortOrder order)
277 // packet_list_store.c:packet_list_dissect_and_cache_all
278 if (!cap_file_ || visible_rows_.count() < 1) return;
279 if (column < 0) return;
281 sort_column_ = column;
282 text_sort_column_ = PacketListRecord::textColumn(column);
284 sort_cap_file_ = cap_file_;
286 gboolean stop_flag = FALSE;
287 QString col_title = get_column_title(column);
290 emit pushProgressStatus(tr("Dissecting"), true, true, &stop_flag);
292 foreach (PacketListRecord *row, physical_rows_) {
293 row->columnString(sort_cap_file_, column);
295 if (busy_timer_.elapsed() > busy_timeout_) {
297 emit popProgressStatus();
300 emit updateProgressStatus(row_num * 100 / physical_rows_.count());
301 // What's the least amount of processing that we can do which will draw
302 // the progress indicator?
303 wsApp->processEvents(QEventLoop::AllEvents, 1);
304 busy_timer_.restart();
307 emit popProgressStatus();
309 // XXX Use updateProgress instead. We'd have to switch from std::sort to
310 // something we can interrupt.
311 if (!col_title.isEmpty()) {
312 QString busy_msg = tr("Sorting \"%1\"").arg(col_title);
313 emit pushBusyStatus(busy_msg);
316 busy_timer_.restart();
317 std::sort(physical_rows_.begin(), physical_rows_.end(), recordLessThan);
320 visible_rows_.clear();
321 number_to_row_.clear();
322 foreach (PacketListRecord *record, physical_rows_) {
323 if (record->frameData()->flags.passed_dfilter || record->frameData()->flags.ref_time) {
324 visible_rows_ << record;
325 number_to_row_[record->frameData()->num] = visible_rows_.count() - 1;
330 if (!col_title.isEmpty()) {
331 emit popBusyStatus();
334 if (cap_file_->current_frame) {
335 emit goToPacket(cap_file_->current_frame->num);
339 bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
343 // Wherein we try to cram the logic of packet_list_compare_records,
344 // _packet_list_compare_records, and packet_list_compare_custom from
345 // gtk/packet_list_store.c into one function
347 if (busy_timer_.elapsed() > busy_timeout_) {
348 // What's the least amount of processing that we can do which will draw
349 // the busy indicator?
350 wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1);
351 busy_timer_.restart();
353 if (sort_column_ < 0) {
355 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
356 } else if (text_sort_column_ < 0) {
357 // Column comes directly from frame data
358 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt);
360 if (r1->columnString(sort_cap_file_, sort_column_).constData() == r2->columnString(sort_cap_file_, sort_column_).constData()) {
362 } else if (sort_cap_file_->cinfo.columns[sort_column_].col_fmt == COL_CUSTOM) {
363 header_field_info *hfi;
365 // Column comes from custom data
366 hfi = proto_registrar_get_byname(sort_cap_file_->cinfo.columns[sort_column_].col_custom_field);
369 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
370 } else if ((hfi->strings == NULL) &&
371 (((IS_FT_INT(hfi->type) || IS_FT_UINT(hfi->type)) &&
372 ((hfi->display == BASE_DEC) || (hfi->display == BASE_DEC_HEX) ||
373 (hfi->display == BASE_OCT))) ||
374 (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) ||
375 (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) ||
376 (hfi->type == FT_RELATIVE_TIME)))
378 // Attempt to convert to numbers.
379 // XXX This is slow. Can we avoid doing this?
381 double num_r1 = r1->columnString(sort_cap_file_, sort_column_).toDouble(&ok_r1);
382 double num_r2 = r2->columnString(sort_cap_file_, sort_column_).toDouble(&ok_r2);
384 if (!ok_r1 && !ok_r2) {
386 } else if (!ok_r1 || num_r1 < num_r2) {
388 } else if (!ok_r2 || num_r1 > num_r2) {
392 cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData());
395 cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData());
399 // All else being equal, compare column numbers.
400 cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
404 if (sort_order_ == Qt::AscendingOrder) {
411 // ::data is const so we have to make changes here.
412 void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
414 if (!ih_index.isValid()) return;
416 PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
419 if (record->lineCount() > max_line_count_) {
420 max_line_count_ = record->lineCount();
421 emit itemHeightChanged(ih_index);
425 int PacketListModel::rowCount(const QModelIndex &parent) const
427 if (parent.column() >= prefs.num_cols)
430 return visible_rows_.count();
433 int PacketListModel::columnCount(const QModelIndex &) const
435 return prefs.num_cols;
438 QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
440 if (!d_index.isValid())
443 PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
446 const frame_data *fdata = record->frameData();
451 case Qt::TextAlignmentRole:
452 switch(recent_get_column_xalign(d_index.column())) {
453 case COLUMN_XALIGN_RIGHT:
454 return Qt::AlignRight;
456 case COLUMN_XALIGN_CENTER:
457 return Qt::AlignCenter;
459 case COLUMN_XALIGN_LEFT:
460 return Qt::AlignLeft;
462 case COLUMN_XALIGN_DEFAULT:
464 if (right_justify_column(d_index.column(), cap_file_)) {
465 return Qt::AlignRight;
469 return Qt::AlignLeft;
471 case Qt::BackgroundRole:
472 const color_t *color;
473 if (fdata->flags.ignored) {
474 color = &prefs.gui_ignored_bg;
475 } else if (fdata->flags.marked) {
476 color = &prefs.gui_marked_bg;
477 } else if (fdata->color_filter && recent.packet_list_colorize) {
478 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
479 color = &color_filter->bg_color;
483 return ColorUtils::fromColorT(color);
484 case Qt::ForegroundRole:
485 if (fdata->flags.ignored) {
486 color = &prefs.gui_ignored_fg;
487 } else if (fdata->flags.marked) {
488 color = &prefs.gui_marked_fg;
489 } else if (fdata->color_filter && recent.packet_list_colorize) {
490 const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
491 color = &color_filter->fg_color;
495 return ColorUtils::fromColorT(color);
496 case Qt::DisplayRole:
498 int column = d_index.column();
499 QByteArray column_string = record->columnString(cap_file_, column, true);
500 // We don't know an item's sizeHint until we fetch its text here.
501 // Assume each line count is 1. If the line count changes, emit
502 // itemHeightChanged which triggers another redraw (including a
503 // fetch of SizeHintRole and DisplayRole) in the next event loop.
504 if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) {
505 emit maxLineCountChanged(d_index);
507 return column_string;
509 case Qt::SizeHintRole:
511 // If this is the first row and column, return the maximum row height...
512 if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) {
513 QSize size = QSize(-1, max_row_height_);
516 // ...otherwise punt so that the item delegate can correctly calculate the item width.
524 QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
527 if (!cap_file_) return QVariant();
529 if (orientation == Qt::Horizontal && section < prefs.num_cols) {
531 case Qt::DisplayRole:
532 return get_column_title(section);
541 void PacketListModel::flushVisibleRows()
543 gint pos = visible_rows_.count();
545 if (new_visible_rows_.count() > 0) {
546 beginInsertRows(QModelIndex(), pos, pos + new_visible_rows_.count());
547 foreach (PacketListRecord *record, new_visible_rows_) {
548 frame_data *fdata = record->frameData();
550 visible_rows_ << record;
551 number_to_row_[fdata->num] = visible_rows_.count() - 1;
554 new_visible_rows_.clear();
558 // Fill our column string and colorization cache while the application is
559 // idle. Try to be as conservative with the CPU and disk as possible.
560 static const int idle_dissection_interval_ = 5; // ms
561 void PacketListModel::dissectIdle(bool reset)
564 // qDebug() << "=di reset" << idle_dissection_row_;
565 idle_dissection_row_ = 0;
566 } else if (!idle_dissection_timer_->isValid()) {
570 idle_dissection_timer_->restart();
572 while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
573 && idle_dissection_row_ < physical_rows_.count()) {
574 ensureRowColorized(idle_dissection_row_);
575 idle_dissection_row_++;
576 // if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
579 if (idle_dissection_row_ < physical_rows_.count()) {
580 QTimer::singleShot(idle_dissection_interval_, this, SLOT(dissectIdle()));
582 idle_dissection_timer_->invalidate();
586 // XXX Pass in cinfo from packet_list_append so that we can fill in
588 gint PacketListModel::appendPacket(frame_data *fdata)
590 PacketListRecord *record = new PacketListRecord(fdata);
593 physical_rows_ << record;
595 if (fdata->flags.passed_dfilter || fdata->flags.ref_time) {
596 new_visible_rows_ << record;
597 if (new_visible_rows_.count() < 2) {
598 // This is the first queued packet. Schedule an insertion for
599 // the next UI update.
600 QTimer::singleShot(0, this, SLOT(flushVisibleRows()));
602 pos = visible_rows_.count() + new_visible_rows_.count() - 1;
608 frame_data *PacketListModel::getRowFdata(int row) {
609 if (row < 0 || row >= visible_rows_.count())
611 PacketListRecord *record = visible_rows_[row];
614 return record->frameData();
617 void PacketListModel::ensureRowColorized(int row)
619 if (row < 0 || row >= visible_rows_.count())
621 PacketListRecord *record = visible_rows_[row];
624 if (!record->colorized()) {
625 record->columnString(cap_file_, 1, true);
629 int PacketListModel::visibleIndexOf(frame_data *fdata) const
632 foreach (PacketListRecord *record, visible_rows_) {
633 if (record->frameData() == fdata) {
648 * indent-tabs-mode: nil
651 * ex: set shiftwidth=4 tabstop=8 expandtab:
652 * :indentSize=4:tabSize=8:noTabs=true: