qt: add missing initializers (CID 1325722)
[metze/wireshark/wip.git] / ui / qt / packet_list_model.cpp
1 /* packet_list_model.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
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.
11  *
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.
16  *
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.
20  */
21
22 #include <algorithm>
23
24 #include "packet_list_model.h"
25
26 #include "file.h"
27
28 #include <wsutil/nstime.h>
29 #include <epan/column.h>
30 #include <epan/prefs.h>
31
32 #include "ui/packet_list_utils.h"
33 #include "ui/recent.h"
34
35 #include "color.h"
36 #include "color_filters.h"
37 #include "frame_tvbuff.h"
38
39 #include "color_utils.h"
40 #include "wireshark_application.h"
41
42 #include <QColor>
43 #include <QElapsedTimer>
44 #include <QFontMetrics>
45 #include <QModelIndex>
46 #include <QElapsedTimer>
47
48 PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
49     QAbstractItemModel(parent),
50     max_row_height_(0),
51     max_line_count_(1),
52     idle_dissection_row_(0)
53 {
54     setCaptureFile(cf);
55     PacketListRecord::clearStringPool();
56     connect(this, SIGNAL(maxLineCountChanged(QModelIndex)),
57             this, SLOT(emitItemHeightChanged(QModelIndex)),
58             Qt::QueuedConnection);
59     idle_dissection_timer_ = new QElapsedTimer();
60 }
61
62 PacketListModel::~PacketListModel()
63 {
64     delete idle_dissection_timer_;
65 }
66
67 void PacketListModel::setCaptureFile(capture_file *cf)
68 {
69     cap_file_ = cf;
70     resetColumns();
71 }
72
73 // Packet list records have no children (for now, at least).
74 QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
75 {
76     if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
77         return QModelIndex();
78
79     PacketListRecord *record = visible_rows_[row];
80
81     return createIndex(row, column, record);
82 }
83
84 // Everything is under the root.
85 QModelIndex PacketListModel::parent(const QModelIndex &) const
86 {
87     return QModelIndex();
88 }
89
90 int PacketListModel::packetNumberToRow(int packet_num) const
91 {
92     return number_to_row_.value(packet_num, -1);
93 }
94
95 guint PacketListModel::recreateVisibleRows()
96 {
97     int pos = visible_rows_.count();
98
99     beginResetModel();
100     visible_rows_.clear();
101     number_to_row_.clear();
102     endResetModel();
103
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;
109         }
110     }
111     endInsertRows();
112     idle_dissection_row_ = 0;
113     return visible_rows_.count();
114 }
115
116 void PacketListModel::clear() {
117     beginResetModel();
118     qDeleteAll(physical_rows_);
119     physical_rows_.clear();
120     visible_rows_.clear();
121     number_to_row_.clear();
122     PacketListRecord::clearStringPool();
123     endResetModel();
124     max_row_height_ = 0;
125     max_line_count_ = 1;
126     idle_dissection_row_ = 0;
127 }
128
129 void PacketListModel::resetColumns()
130 {
131     if (cap_file_) {
132         PacketListRecord::resetColumns(&cap_file_->cinfo);
133     }
134     dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
135     headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
136 }
137
138 void PacketListModel::resetColorized()
139 {
140     foreach (PacketListRecord *record, physical_rows_) {
141         record->resetColorized();
142     }
143     dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
144 }
145
146 void PacketListModel::toggleFrameMark(const QModelIndex &fm_index)
147 {
148     if (!cap_file_ || !fm_index.isValid()) return;
149
150     PacketListRecord *record = static_cast<PacketListRecord*>(fm_index.internalPointer());
151     if (!record) return;
152
153     frame_data *fdata = record->frameData();
154     if (!fdata) return;
155
156     if (fdata->flags.marked)
157         cf_unmark_frame(cap_file_, fdata);
158     else
159         cf_mark_frame(cap_file_, fdata);
160
161     dataChanged(fm_index, fm_index);
162 }
163
164 void PacketListModel::setDisplayedFrameMark(gboolean set)
165 {
166     foreach (PacketListRecord *record, visible_rows_) {
167         if (set) {
168             cf_mark_frame(cap_file_, record->frameData());
169         } else {
170             cf_unmark_frame(cap_file_, record->frameData());
171         }
172     }
173     dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
174 }
175
176 void PacketListModel::toggleFrameIgnore(const QModelIndex &i_index)
177 {
178     if (!cap_file_ || !i_index.isValid()) return;
179
180     PacketListRecord *record = static_cast<PacketListRecord*>(i_index.internalPointer());
181     if (!record) return;
182
183     frame_data *fdata = record->frameData();
184     if (!fdata) return;
185
186     if (fdata->flags.ignored)
187         cf_unignore_frame(cap_file_, fdata);
188     else
189         cf_ignore_frame(cap_file_, fdata);
190 }
191
192 void PacketListModel::setDisplayedFrameIgnore(gboolean set)
193 {
194     foreach (PacketListRecord *record, visible_rows_) {
195         if (set) {
196             cf_ignore_frame(cap_file_, record->frameData());
197         } else {
198             cf_unignore_frame(cap_file_, record->frameData());
199         }
200     }
201     dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
202 }
203
204 void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
205 {
206     if (!cap_file_ || !rt_index.isValid()) return;
207
208     PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
209     if (!record) return;
210
211     frame_data *fdata = record->frameData();
212     if (!fdata) return;
213
214     if (fdata->flags.ref_time) {
215         fdata->flags.ref_time=0;
216         cap_file_->ref_time_count--;
217     } else {
218         fdata->flags.ref_time=1;
219         cap_file_->ref_time_count++;
220     }
221     cf_reftime_packets(cap_file_);
222     if (!fdata->flags.ref_time && !fdata->flags.passed_dfilter) {
223         cap_file_->displayed_count--;
224     }
225     record->resetColumns(&cap_file_->cinfo);
226     dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
227 }
228
229 void PacketListModel::unsetAllFrameRefTime()
230 {
231     if (!cap_file_) return;
232
233     /* XXX: we might need a progressbar here */
234
235     foreach (PacketListRecord *record, physical_rows_) {
236         frame_data *fdata = record->frameData();
237         if (fdata->flags.ref_time) {
238             fdata->flags.ref_time = 0;
239         }
240     }
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));
245 }
246
247 void PacketListModel::setMaximiumRowHeight(int height)
248 {
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));
254 }
255
256 //void PacketListModel::setMonospaceFont(const QFont &mono_font, int row_height)
257 //{
258 //    QFontMetrics fm(mono_font_);
259 //    mono_font_ = mono_font;
260 //    row_height_ = row_height;
261 //    line_spacing_ = fm.lineSpacing();
262 //}
263
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.
267
268 int PacketListModel::sort_column_;
269 int PacketListModel::text_sort_column_;
270 Qt::SortOrder PacketListModel::sort_order_;
271 capture_file *PacketListModel::sort_cap_file_;
272
273 QElapsedTimer busy_timer_;
274 const int busy_timeout_ = 65; // ms, approximately 15 fps
275 void PacketListModel::sort(int column, Qt::SortOrder order)
276 {
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;
280
281     sort_column_ = column;
282     text_sort_column_ = PacketListRecord::textColumn(column);
283     sort_order_ = order;
284     sort_cap_file_ = cap_file_;
285
286     gboolean stop_flag = FALSE;
287     QString col_title = get_column_title(column);
288
289     busy_timer_.start();
290     emit pushProgressStatus(tr("Dissecting"), true, true, &stop_flag);
291     int row_num = 0;
292     foreach (PacketListRecord *row, physical_rows_) {
293         row->columnString(sort_cap_file_, column);
294         row_num++;
295         if (busy_timer_.elapsed() > busy_timeout_) {
296             if (stop_flag) {
297                 emit popProgressStatus();
298                 return;
299             }
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();
305         }
306     }
307     emit popProgressStatus();
308
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);
314     }
315
316     busy_timer_.restart();
317     std::sort(physical_rows_.begin(), physical_rows_.end(), recordLessThan);
318
319     beginResetModel();
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;
326         }
327     }
328     endResetModel();
329
330     if (!col_title.isEmpty()) {
331         emit popBusyStatus();
332     }
333
334     if (cap_file_->current_frame) {
335         emit goToPacket(cap_file_->current_frame->num);
336     }
337 }
338
339 bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
340 {
341     int cmp_val = 0;
342
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
346
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();
352     }
353     if (sort_column_ < 0) {
354         // No column.
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);
359     } else  {
360         if (r1->columnString(sort_cap_file_, sort_column_).constData() == r2->columnString(sort_cap_file_, sort_column_).constData()) {
361             cmp_val = 0;
362         } else if (sort_cap_file_->cinfo.columns[sort_column_].col_fmt == COL_CUSTOM) {
363             header_field_info *hfi;
364
365             // Column comes from custom data
366             hfi = proto_registrar_get_byname(sort_cap_file_->cinfo.columns[sort_column_].col_custom_field);
367
368             if (hfi == NULL) {
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)))
377             {
378                 // Attempt to convert to numbers.
379                 // XXX This is slow. Can we avoid doing this?
380                 bool ok_r1, ok_r2;
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);
383
384                 if (!ok_r1 && !ok_r2) {
385                     cmp_val = 0;
386                 } else if (!ok_r1 || num_r1 < num_r2) {
387                     cmp_val = -1;
388                 } else if (!ok_r2 || num_r1 > num_r2) {
389                     cmp_val = 1;
390                 }
391             } else {
392                 cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData());
393             }
394         } else {
395             cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData());
396         }
397
398         if (cmp_val == 0) {
399             // All else being equal, compare column numbers.
400             cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
401         }
402     }
403
404     if (sort_order_ == Qt::AscendingOrder) {
405         return cmp_val < 0;
406     } else {
407         return cmp_val > 0;
408     }
409 }
410
411 // ::data is const so we have to make changes here.
412 void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
413 {
414     if (!ih_index.isValid()) return;
415
416     PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
417     if (!record) return;
418
419     if (record->lineCount() > max_line_count_) {
420         max_line_count_ = record->lineCount();
421         emit itemHeightChanged(ih_index);
422     }
423 }
424
425 int PacketListModel::rowCount(const QModelIndex &parent) const
426 {
427     if (parent.column() >= prefs.num_cols)
428         return 0;
429
430     return visible_rows_.count();
431 }
432
433 int PacketListModel::columnCount(const QModelIndex &) const
434 {
435     return prefs.num_cols;
436 }
437
438 QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
439 {
440     if (!d_index.isValid())
441         return QVariant();
442
443     PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
444     if (!record)
445         return QVariant();
446     const frame_data *fdata = record->frameData();
447     if (!fdata)
448         return QVariant();
449
450     switch (role) {
451     case Qt::TextAlignmentRole:
452         switch(recent_get_column_xalign(d_index.column())) {
453         case COLUMN_XALIGN_RIGHT:
454             return Qt::AlignRight;
455             break;
456         case COLUMN_XALIGN_CENTER:
457             return Qt::AlignCenter;
458             break;
459         case COLUMN_XALIGN_LEFT:
460             return Qt::AlignLeft;
461             break;
462         case COLUMN_XALIGN_DEFAULT:
463         default:
464             if (right_justify_column(d_index.column(), cap_file_)) {
465                 return Qt::AlignRight;
466             }
467             break;
468         }
469         return Qt::AlignLeft;
470
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;
480         } else {
481             return QVariant();
482         }
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;
492         } else {
493             return QVariant();
494         }
495         return ColorUtils::fromColorT(color);
496     case Qt::DisplayRole:
497     {
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);
506         }
507         return column_string;
508     }
509     case Qt::SizeHintRole:
510     {
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_);
514             return size;
515         }
516         // ...otherwise punt so that the item delegate can correctly calculate the item width.
517         return QVariant();
518     }
519     default:
520         return QVariant();
521     }
522 }
523
524 QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
525                                int role) const
526 {
527     if (!cap_file_) return QVariant();
528
529     if (orientation == Qt::Horizontal && section < prefs.num_cols) {
530         switch (role) {
531         case Qt::DisplayRole:
532             return get_column_title(section);
533         default:
534             break;
535         }
536     }
537
538     return QVariant();
539 }
540
541 void PacketListModel::flushVisibleRows()
542 {
543     gint pos = visible_rows_.count();
544
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();
549
550             visible_rows_ << record;
551             number_to_row_[fdata->num] = visible_rows_.count() - 1;
552         }
553         endInsertRows();
554         new_visible_rows_.clear();
555     }
556 }
557
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)
562 {
563     if (reset) {
564 //        qDebug() << "=di reset" << idle_dissection_row_;
565         idle_dissection_row_ = 0;
566     } else if (!idle_dissection_timer_->isValid()) {
567         return;
568     }
569
570     idle_dissection_timer_->restart();
571
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_;
577     }
578
579     if (idle_dissection_row_ < physical_rows_.count()) {
580         QTimer::singleShot(idle_dissection_interval_, this, SLOT(dissectIdle()));
581     } else {
582         idle_dissection_timer_->invalidate();
583     }
584 }
585
586 // XXX Pass in cinfo from packet_list_append so that we can fill in
587 // line counts?
588 gint PacketListModel::appendPacket(frame_data *fdata)
589 {
590     PacketListRecord *record = new PacketListRecord(fdata);
591     gint pos = -1;
592
593     physical_rows_ << record;
594
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()));
601         }
602         pos = visible_rows_.count() + new_visible_rows_.count() - 1;
603     }
604
605     return pos;
606 }
607
608 frame_data *PacketListModel::getRowFdata(int row) {
609     if (row < 0 || row >= visible_rows_.count())
610         return NULL;
611     PacketListRecord *record = visible_rows_[row];
612     if (!record)
613         return NULL;
614     return record->frameData();
615 }
616
617 void PacketListModel::ensureRowColorized(int row)
618 {
619     if (row < 0 || row >= visible_rows_.count())
620         return;
621     PacketListRecord *record = visible_rows_[row];
622     if (!record)
623         return;
624     if (!record->colorized()) {
625         record->columnString(cap_file_, 1, true);
626     }
627 }
628
629 int PacketListModel::visibleIndexOf(frame_data *fdata) const
630 {
631     int row = 0;
632     foreach (PacketListRecord *record, visible_rows_) {
633         if (record->frameData() == fdata) {
634             return row;
635         }
636         row++;
637     }
638
639     return -1;
640 }
641
642 /*
643  * Editor modelines
644  *
645  * Local Variables:
646  * c-basic-offset: 4
647  * tab-width: 8
648  * indent-tabs-mode: nil
649  * End:
650  *
651  * ex: set shiftwidth=4 tabstop=8 expandtab:
652  * :indentSize=4:tabSize=8:noTabs=true:
653  */