diff options
-rw-r--r-- | ui/packet_range.c | 88 | ||||
-rw-r--r-- | ui/packet_range.h | 10 | ||||
-rw-r--r-- | ui/qt/capture_file_dialog.cpp | 15 | ||||
-rw-r--r-- | ui/qt/capture_file_dialog.h | 4 | ||||
-rw-r--r-- | ui/qt/export_dissection_dialog.cpp | 4 | ||||
-rw-r--r-- | ui/qt/export_dissection_dialog.h | 2 | ||||
-rw-r--r-- | ui/qt/main_status_bar.cpp | 20 | ||||
-rw-r--r-- | ui/qt/main_window.cpp | 52 | ||||
-rw-r--r-- | ui/qt/main_window.h | 14 | ||||
-rw-r--r-- | ui/qt/main_window.ui | 30 | ||||
-rw-r--r-- | ui/qt/main_window_slots.cpp | 119 | ||||
-rw-r--r-- | ui/qt/models/packet_list_model.cpp | 66 | ||||
-rw-r--r-- | ui/qt/models/packet_list_model.h | 4 | ||||
-rw-r--r-- | ui/qt/models/related_packet_delegate.cpp | 26 | ||||
-rw-r--r-- | ui/qt/packet_list.cpp | 237 | ||||
-rw-r--r-- | ui/qt/packet_list.h | 16 | ||||
-rw-r--r-- | ui/qt/packet_range_group_box.cpp | 22 | ||||
-rw-r--r-- | ui/qt/packet_range_group_box.h | 2 | ||||
-rw-r--r-- | ui/qt/print_dialog.cpp | 6 | ||||
-rw-r--r-- | ui/qt/print_dialog.h | 9 | ||||
-rw-r--r-- | ui/qt/proto_tree.cpp | 19 | ||||
-rw-r--r-- | ui/qt/proto_tree.h | 2 | ||||
-rw-r--r-- | ui/qt/wireshark_en.ts | 20 | ||||
-rw-r--r-- | ui/win32/file_dlg_win32.c | 22 |
24 files changed, 639 insertions, 170 deletions
diff --git a/ui/packet_range.c b/ui/packet_range.c index 802fdea735..3ad148651a 100644 --- a/ui/packet_range.c +++ b/ui/packet_range.c @@ -31,12 +31,12 @@ static void packet_range_calc(packet_range_t *range) { frame_data *packet; - range->selected_packet = 0; mark_low = 0; mark_high = 0; range->mark_range_cnt = 0; range->ignored_cnt = 0; + range->ignored_selection_range_cnt = 0; range->ignored_marked_cnt = 0; range->ignored_mark_range_cnt = 0; range->ignored_user_range_cnt = 0; @@ -49,6 +49,7 @@ static void packet_range_calc(packet_range_t *range) { range->displayed_mark_range_cnt = 0; range->displayed_plus_dependents_cnt = 0; range->displayed_ignored_cnt = 0; + range->displayed_ignored_selection_range_cnt = 0; range->displayed_ignored_marked_cnt = 0; range->displayed_ignored_mark_range_cnt = 0; range->displayed_ignored_user_range_cnt = 0; @@ -79,8 +80,8 @@ static void packet_range_calc(packet_range_t *range) { for(framenum = 1; framenum <= range->cf->count; framenum++) { packet = frame_data_sequence_find(range->cf->provider.frames, framenum); - if (range->cf->current_frame == packet) { - range->selected_packet = framenum; + if (range->cf->current_frame == packet && range->selection_range == NULL ) { + range_add_value(NULL, &(range->selection_range), framenum); } if (packet->passed_dfilter) { range->displayed_cnt++; @@ -145,17 +146,6 @@ static void packet_range_calc(packet_range_t *range) { } } -#if 0 - /* in case we marked just one packet, we add 1. */ - if (range->cf->marked_count != 0) { - range->mark_range = mark_high - mark_low + 1; - } - - /* in case we marked just one packet, we add 1. */ - if (range->displayed_marked_cnt != 0) { - range->displayed_mark_range = displayed_mark_high - displayed_mark_low + 1; - } -#endif } } @@ -211,6 +201,37 @@ static void packet_range_calc_user(packet_range_t *range) { } } +static void packet_range_calc_selection(packet_range_t *range) { + guint32 framenum; + frame_data *packet; + + range->selection_range_cnt = 0; + range->ignored_selection_range_cnt = 0; + range->displayed_selection_range_cnt = 0; + range->displayed_ignored_selection_range_cnt = 0; + + g_assert(range->cf != NULL); + + if (range->cf->provider.frames != NULL) { + for (framenum = 1; framenum <= range->cf->count; framenum++) { + packet = frame_data_sequence_find(range->cf->provider.frames, framenum); + + if (value_is_in_range(range->selection_range, framenum)) { + range->selection_range_cnt++; + if (packet->ignored) { + range->ignored_selection_range_cnt++; + } + if (packet->passed_dfilter) { + range->displayed_selection_range_cnt++; + if (packet->ignored) { + range->displayed_ignored_selection_range_cnt++; + } + } + } + } + } +} + /* init the range struct */ void packet_range_init(packet_range_t *range, capture_file *cf) { @@ -218,15 +239,18 @@ void packet_range_init(packet_range_t *range, capture_file *cf) { memset(range, 0, sizeof(packet_range_t)); range->process = range_process_all; range->user_range = NULL; + range->selection_range = NULL; range->cf = cf; /* calculate all packet range counters */ packet_range_calc(range); packet_range_calc_user(range); + packet_range_calc_selection(range); } void packet_range_cleanup(packet_range_t *range) { wmem_free(NULL, range->user_range); + wmem_free(NULL, range->selection_range); } /* check whether the packet range is OK */ @@ -235,6 +259,10 @@ convert_ret_t packet_range_check(packet_range_t *range) { /* Not valid - return the error. */ return range->user_range_status; } + if (range->process == range_process_selected && range->selection_range == NULL) { + return range->selection_range_status; + } + return CVT_NO_ERROR; } @@ -270,13 +298,9 @@ range_process_e packet_range_process_packet(packet_range_t *range, frame_data *f case(range_process_all): break; case(range_process_selected): - if (range->selected_done) { - return range_processing_finished; - } - if (fdata->num != range->cf->current_frame->num) { + if (value_is_in_range(range->selection_range, fdata->num) == FALSE) { return range_process_next; } - range->selected_done = TRUE; break; case(range_process_marked): if (fdata->marked == FALSE) { @@ -356,6 +380,32 @@ void packet_range_convert_str(packet_range_t *range, const gchar *es) packet_range_calc_user(range); } /* packet_range_convert_str */ +void packet_range_convert_selection_str(packet_range_t *range, const char *es) +{ + range_t *new_range; + convert_ret_t ret; + + if (range->selection_range != NULL) + wmem_free(NULL, range->selection_range); + + g_assert(range->cf != NULL); + + ret = range_convert_str(NULL, &new_range, es, range->cf->count); + if (ret != CVT_NO_ERROR) { + /* range isn't valid */ + range->selection_range = NULL; + range->selection_range_status = ret; + range->selection_range_cnt = 0; + range->ignored_selection_range_cnt = 0; + range->displayed_selection_range_cnt = 0; + range->displayed_ignored_selection_range_cnt = 0; + return; + } + range->selection_range = new_range; + + /* calculate new user specified packet range counts */ + packet_range_calc_selection(range); +} /* * Editor modelines - https://www.wireshark.org/tools/modelines.html diff --git a/ui/packet_range.h b/ui/packet_range.h index 1622648199..6e7d34d118 100644 --- a/ui/packet_range.h +++ b/ui/packet_range.h @@ -47,16 +47,19 @@ typedef struct packet_range_tag { convert_ret_t user_range_status; /* calculated values */ - guint32 selected_packet; /* the currently selected packet */ + range_t *selection_range; /* the currently selected packets */ + convert_ret_t selection_range_status; /* current packet counts (captured) */ capture_file *cf; /* Associated capture file. */ guint32 mark_range_cnt; /* packets in marked range */ guint32 user_range_cnt; /* packets in user specified range */ + guint32 selection_range_cnt; /* packets in the selected range */ guint32 ignored_cnt; /* packets ignored */ guint32 ignored_marked_cnt; /* packets ignored and marked */ guint32 ignored_mark_range_cnt; /* packets ignored in marked range */ guint32 ignored_user_range_cnt; /* packets ignored in user specified range */ + guint32 ignored_selection_range_cnt; /* packets ignored in the selected range */ /* current packet counts (displayed) */ guint32 displayed_cnt; @@ -64,10 +67,12 @@ typedef struct packet_range_tag { guint32 displayed_marked_cnt; guint32 displayed_mark_range_cnt; guint32 displayed_user_range_cnt; + guint32 displayed_selection_range_cnt; guint32 displayed_ignored_cnt; guint32 displayed_ignored_marked_cnt; guint32 displayed_ignored_mark_range_cnt; guint32 displayed_ignored_user_range_cnt; + guint32 displayed_ignored_selection_range_cnt; /* "enumeration" values */ gboolean marked_range_active; /* marked range is currently processed */ @@ -102,6 +107,9 @@ extern range_process_e packet_range_process_packet(packet_range_t *range, frame_ /* convert user given string to the internal user specified range representation */ extern void packet_range_convert_str(packet_range_t *range, const gchar *es); +/* convert user given string to the internal selection specified range representation */ +extern void packet_range_convert_selection_str(packet_range_t *range, const gchar *es); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/ui/qt/capture_file_dialog.cpp b/ui/qt/capture_file_dialog.cpp index 745f499094..95bb78e335 100644 --- a/ui/qt/capture_file_dialog.cpp +++ b/ui/qt/capture_file_dialog.cpp @@ -292,10 +292,15 @@ check_savability_t CaptureFileDialog::saveAs(QString &file_name, bool must_suppo return CANCELLED; } -check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range) { +check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range, QString selRange) { GString *fname = g_string_new(file_name.toUtf8().constData()); gboolean wespf_status; + if ( selRange.length() > 0 ) + { + packet_range_convert_selection_str(range, selRange.toUtf8().constData()); + } + wespf_status = win32_export_specified_packets_file((HWND)parentWidget()->effectiveWinId(), cap_file_, fname, &file_type_, &compression_type_, range); file_name = fname->str; @@ -643,8 +648,8 @@ void CaptureFileDialog::addGzipControls(QVBoxLayout &v_box) { } -void CaptureFileDialog::addRangeControls(QVBoxLayout &v_box, packet_range_t *range) { - packet_range_group_box_.initRange(range); +void CaptureFileDialog::addRangeControls(QVBoxLayout &v_box, packet_range_t *range, QString selRange) { + packet_range_group_box_.initRange(range, selRange); v_box.addWidget(&packet_range_group_box_, 0, Qt::AlignTop); } @@ -720,7 +725,7 @@ check_savability_t CaptureFileDialog::saveAs(QString &file_name, bool must_suppo return CANCELLED; } -check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range) { +check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, packet_range_t *range, QString selRange) { QDialogButtonBox *button_box; setWindowTitle(wsApp->windowTitleString(tr("Export Specified Packets"))); @@ -729,7 +734,7 @@ check_savability_t CaptureFileDialog::exportSelectedPackets(QString &file_name, setAcceptMode(QFileDialog::AcceptSave); setLabelText(FileType, tr("Export as:")); - addRangeControls(left_v_box_, range); + addRangeControls(left_v_box_, range, selRange); addGzipControls(right_v_box_); button_box = addHelpButton(HELP_EXPORT_FILE_DIALOG); diff --git a/ui/qt/capture_file_dialog.h b/ui/qt/capture_file_dialog.h index ac8e890347..92cb3f47cf 100644 --- a/ui/qt/capture_file_dialog.h +++ b/ui/qt/capture_file_dialog.h @@ -103,7 +103,7 @@ private: QHash<QString, QStringList> type_suffixes_; void addGzipControls(QVBoxLayout &v_box); - void addRangeControls(QVBoxLayout &v_box, packet_range_t *range); + void addRangeControls(QVBoxLayout &v_box, packet_range_t *range, QString selRange = QString()); QDialogButtonBox *addHelpButton(topic_action_e help_topic); QStringList buildFileSaveAsTypeList(bool must_support_comments); @@ -132,7 +132,7 @@ public slots: int exec() Q_DECL_OVERRIDE; int open(QString &file_name, unsigned int &type); check_savability_t saveAs(QString &file_name, bool must_support_comments); - check_savability_t exportSelectedPackets(QString &file_name, packet_range_t *range); + check_savability_t exportSelectedPackets(QString &file_name, packet_range_t *range, QString selRange = QString()); int merge(QString &file_name); private slots: diff --git a/ui/qt/export_dissection_dialog.cpp b/ui/qt/export_dissection_dialog.cpp index e0afdfbe94..507d4c75e5 100644 --- a/ui/qt/export_dissection_dialog.cpp +++ b/ui/qt/export_dissection_dialog.cpp @@ -46,7 +46,7 @@ static const QStringList export_extensions = QStringList() #endif -ExportDissectionDialog::ExportDissectionDialog(QWidget *parent, capture_file *cap_file, export_type_e export_type): +ExportDissectionDialog::ExportDissectionDialog(QWidget *parent, capture_file *cap_file, export_type_e export_type, QString selRange): QFileDialog(parent), export_type_(export_type), cap_file_(cap_file) @@ -119,7 +119,7 @@ ExportDissectionDialog::ExportDissectionDialog(QWidget *parent, capture_file *ca /* Default to displayed packets */ print_args_.range.process_filtered = TRUE; - packet_range_group_box_.initRange(&print_args_.range); + packet_range_group_box_.initRange(&print_args_.range, selRange); h_box->addWidget(&packet_range_group_box_); h_box->addWidget(&packet_format_group_box_, 0, Qt::AlignTop); diff --git a/ui/qt/export_dissection_dialog.h b/ui/qt/export_dissection_dialog.h index 652d32952a..17f5bea990 100644 --- a/ui/qt/export_dissection_dialog.h +++ b/ui/qt/export_dissection_dialog.h @@ -32,7 +32,7 @@ class ExportDissectionDialog : public QFileDialog Q_OBJECT public: - explicit ExportDissectionDialog(QWidget *parent, capture_file *cap_file, export_type_e export_type); + explicit ExportDissectionDialog(QWidget *parent, capture_file *cap_file, export_type_e export_type, QString selRange = QString()); ~ExportDissectionDialog(); public slots: diff --git a/ui/qt/main_status_bar.cpp b/ui/qt/main_status_bar.cpp index 996acee3c1..5ca66d8cf1 100644 --- a/ui/qt/main_status_bar.cpp +++ b/ui/qt/main_status_bar.cpp @@ -21,6 +21,7 @@ #include "ui/main_statusbar.h" #include <ui/qt/utils/qt_ui_utils.h> +#include <ui/qt/main_window.h> #include "capture_file.h" #include "main_status_bar.h" @@ -373,21 +374,26 @@ void MainStatusBar::showCaptureStatistics() { QString packets_str; + QList<int> rows; + MainWindow * mw = qobject_cast<MainWindow *>(wsApp->mainWindow()); + if ( mw ) + rows = mw->selectedRows(true); + #ifdef HAVE_LIBPCAP if (cap_file_) { /* Do we have any packets? */ if (cs_fixed_ && cs_count_ > 0) { - if (prefs.gui_qt_show_selected_packet && cap_file_->current_frame) { + if (prefs.gui_qt_show_selected_packet && rows.count() == 1) { packets_str.append(QString(tr("Selected Packet: %1 %2 ")) - .arg(cap_file_->current_frame->num) + .arg(rows.at(0)) .arg(UTF8_MIDDLE_DOT)); } packets_str.append(QString(tr("Packets: %1")) .arg(cs_count_)); } else if (cs_count_ > 0) { - if (prefs.gui_qt_show_selected_packet && cap_file_->current_frame) { + if (prefs.gui_qt_show_selected_packet && rows.count() == 1) { packets_str.append(QString(tr("Selected Packet: %1 %2 ")) - .arg(cap_file_->current_frame->num) + .arg(rows.at(0)) .arg(UTF8_MIDDLE_DOT)); } packets_str.append(QString(tr("Packets: %1 %4 Displayed: %2 (%3%)")) @@ -395,6 +401,12 @@ void MainStatusBar::showCaptureStatistics() .arg(cap_file_->displayed_count) .arg((100.0*cap_file_->displayed_count)/cap_file_->count, 0, 'f', 1) .arg(UTF8_MIDDLE_DOT)); + if(rows.count() > 1) { + packets_str.append(QString(tr(" %1 Selected: %2 (%3%)")) + .arg(UTF8_MIDDLE_DOT) + .arg(rows.count()) + .arg((100.0*rows.count())/cap_file_->count, 0, 'f', 1)); + } if(cap_file_->marked_count > 0) { packets_str.append(QString(tr(" %1 Marked: %2 (%3%)")) .arg(UTF8_MIDDLE_DOT) diff --git a/ui/qt/main_window.cpp b/ui/qt/main_window.cpp index 1376bdc35f..972e986707 100644 --- a/ui/qt/main_window.cpp +++ b/ui/qt/main_window.cpp @@ -206,9 +206,14 @@ static void plugin_if_mainwindow_get_ws_info(GHashTable * data_set) if (cf) { ws_info->cf_count = cf->count; - if (cf->state == FILE_READ_DONE && cf->current_frame) { - ws_info->cf_framenr = cf->current_frame->num; - ws_info->frame_passed_dfilter = (cf->current_frame->passed_dfilter == 1); + QList<int> rows = gbl_cur_main_window_->selectedRows(); + frame_data * fdata = NULL; + if ( rows.count() > 0 ) + fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0)); + + if (cf->state == FILE_READ_DONE && fdata) { + ws_info->cf_framenr = fdata->num; + ws_info->frame_passed_dfilter = (fdata->passed_dfilter == 1); } else { ws_info->cf_framenr = 0; @@ -286,7 +291,6 @@ MainWindow::MainWindow(QWidget *parent) : , capture_interfaces_dialog_(NULL) , info_data_() #endif - , pdlg_(NULL) , display_filter_dlg_(NULL) , capture_filter_dlg_(NULL) #ifdef _WIN32 @@ -462,6 +466,8 @@ MainWindow::MainWindow(QWidget *parent) : main_ui_->wirelessTimelineWidget->setPacketList(packet_list_); connect(packet_list_, SIGNAL(frameSelected(int)), this, SIGNAL(frameSelected(int))); + connect(packet_list_, SIGNAL(framesSelected(QList<int>)), + this, SLOT(framesSelected(QList<int>))); connect(this, SIGNAL(frameSelected(int)), this, SLOT(setMenusForSelectedPacket())); @@ -475,8 +481,6 @@ MainWindow::MainWindow(QWidget *parent) : connect(proto_tree_, SIGNAL(fieldSelected(FieldInformation *)), this, SIGNAL(fieldSelected(FieldInformation *))); - connect(this, SIGNAL(fieldSelected(FieldInformation *)), - proto_tree_, SLOT(selectedFieldChanged(FieldInformation *))); connect(packet_list_, SIGNAL(fieldSelected(FieldInformation *)), this, SIGNAL(fieldSelected(FieldInformation *))); connect(this, SIGNAL(fieldSelected(FieldInformation *)), @@ -906,7 +910,6 @@ void MainWindow::closeEvent(QCloseEvent *event) { #ifdef HAVE_LIBPCAP if (capture_interfaces_dialog_) capture_interfaces_dialog_->close(); #endif - if (pdlg_) pdlg_->close(); // Make sure we kill any open dumpcap processes. delete welcome_page_; @@ -1489,7 +1492,7 @@ void MainWindow::exportSelectedPackets() { packet_range_t range; cf_write_status_t status; gchar *dirname; - gboolean discard_comments = FALSE; + bool discard_comments = false; if (!capture_file_.capFile()) return; @@ -1499,13 +1502,20 @@ void MainWindow::exportSelectedPackets() { range.process_filtered = TRUE; range.include_dependents = TRUE; + QList<int> rows = packet_list_->selectedRows(true); + + QStringList entries; + foreach ( int row, rows ) + entries << QString::number(row); + QString selRange = entries.join(","); + for (;;) { CaptureFileDialog esp_dlg(this, capture_file_.capFile()); /* If the file has comments, does the format the user selected support them? If not, ask the user whether they want to discard the comments or choose a different format. */ - switch (esp_dlg.exportSelectedPackets(file_name, &range)) { + switch (esp_dlg.exportSelectedPackets(file_name, &range, selRange)) { case SAVE: /* The file can be saved in the specified format as is; @@ -1610,7 +1620,14 @@ void MainWindow::exportDissections(export_type_e export_type) { capture_file *cf = capture_file_.capFile(); g_return_if_fail(cf); - ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type); + QList<int> rows = packet_list_->selectedRows(true); + + QStringList entries; + foreach ( int row, rows ) + entries << QString::number(row); + QString selRange = entries.join(","); + + ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type, selRange); ed_dlg->setWindowModality(Qt::ApplicationModal); ed_dlg->setAttribute(Qt::WA_DeleteOnClose); ed_dlg->show(); @@ -2872,6 +2889,21 @@ void MainWindow::setMwFileName(QString fileName) return; } +QList<int> MainWindow::selectedRows(bool useFrameNum) +{ + if ( packet_list_ ) + return packet_list_->selectedRows(useFrameNum); + return QList<int>(); +} + +frame_data * MainWindow::frameDataForRow(int row) const +{ + if ( packet_list_ ) + return packet_list_->getFDataForRow(row); + + return Q_NULLPTR; +} + /* * Editor modelines * diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h index bbe9d90d32..d92a001b3e 100644 --- a/ui/qt/main_window.h +++ b/ui/qt/main_window.h @@ -133,6 +133,9 @@ public: void insertColumn(QString name, QString abbrev, gint pos = -1); + QList<int> selectedRows(bool useFrameNum = false); + frame_data * frameDataForRow(int row) const; + protected: virtual bool eventFilter(QObject *obj, QEvent *event); virtual bool event(QEvent *event); @@ -158,7 +161,10 @@ private: CopyAllVisibleSelectedTreeItems, CopySelectedDescription, CopySelectedFieldName, - CopySelectedValue + CopySelectedValue, + CopyListAsText, + CopyListAsCSV, + CopyListAsYAML }; enum FileCloseContext { @@ -209,7 +215,6 @@ private: CaptureInterfacesDialog *capture_interfaces_dialog_; info_data_t info_data_; #endif - PrintDialog *pdlg_; FilterDialog *display_filter_dlg_; FilterDialog *capture_filter_dlg_; @@ -320,6 +325,8 @@ public slots: void showWelcome(); void showCapture(); + void framesSelected(QList<int>); + void setTitlebarForCaptureFile(); void setWSWindowTitle(QString title = QString()); @@ -459,6 +466,9 @@ private slots: void actionEditCopyTriggered(MainWindow::CopySelected selection_type); void on_actionCopyAllVisibleItems_triggered(); void on_actionCopyAllVisibleSelectedTreeItems_triggered(); + void on_actionCopyListAsText_triggered(); + void on_actionCopyListAsCSV_triggered(); + void on_actionCopyListAsYAML_triggered(); void on_actionEditCopyDescription_triggered(); void on_actionEditCopyFieldName_triggered(); void on_actionEditCopyValue_triggered(); diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui index 90c11b85fb..6e3a0ffbbb 100644 --- a/ui/qt/main_window.ui +++ b/ui/qt/main_window.ui @@ -619,6 +619,10 @@ <property name="title"> <string>Copy</string> </property> + <addaction name="actionCopyListAsText"/> + <addaction name="actionCopyListAsCSV"/> + <addaction name="actionCopyListAsYAML"/> + <addaction name="separator"/> <addaction name="actionCopyAllVisibleItems"/> <addaction name="actionCopyAllVisibleSelectedTreeItems"/> <addaction name="actionEditCopyDescription"/> @@ -1305,6 +1309,24 @@ <string notr="true">Ctrl+Alt+Shift+D</string> </property> </action> + <action name="actionCopyListAsText"> + <property name="text"> + <string>As Plain &Text…</string> + </property> + <property name="shortcut"> + <string notr="true">Ctrl+C</string> + </property> + </action> + <action name="actionCopyListAsCSV"> + <property name="text"> + <string>As &CSV…</string> + </property> + </action> + <action name="actionCopyListAsYAML"> + <property name="text"> + <string>As &YAML…</string> + </property> + </action> <action name="actionCopyAllVisibleItems"> <property name="text"> <string>All Visible Items</string> @@ -1407,10 +1429,10 @@ </action> <action name="actionEditMarkPacket"> <property name="text"> - <string>&Mark/Unmark Packet</string> + <string>&Mark/Unmark Packet(s)</string> </property> <property name="toolTip"> - <string>Mark or unmark this packet</string> + <string>Mark or unmark each selected packet</string> </property> <property name="shortcut"> <string notr="true">Ctrl+M</string> @@ -1462,10 +1484,10 @@ </action> <action name="actionEditIgnorePacket"> <property name="text"> - <string>&Ignore/Unignore Packet</string> + <string>&Ignore/Unignore Packet(s)</string> </property> <property name="toolTip"> - <string>Ignore or unignore this packet</string> + <string>Ignore or unignore each selected packet</string> </property> <property name="shortcut"> <string notr="true">Ctrl+D</string> diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp index a4c6784956..d843f84037 100644 --- a/ui/qt/main_window_slots.cpp +++ b/ui/qt/main_window_slots.cpp @@ -1130,6 +1130,7 @@ void MainWindow::setMenusForSelectedPacket() bool have_frames = false; /* A frame is selected */ bool frame_selected = false; + bool multi_selection = false; /* A visible packet comes after this one in the selection history */ bool next_selection_history = false; /* A visible packet comes before this one in the selection history */ @@ -1159,20 +1160,31 @@ void MainWindow::setMenusForSelectedPacket() << main_ui_->actionViewColorizeConversation9 << main_ui_->actionViewColorizeConversation10; if (capture_file_.capFile()) { - frame_selected = capture_file_.capFile()->current_frame != NULL; + + QList<int> rows = selectedRows(); + frame_data * current_frame = 0; + if ( rows.count() > 0 ) + current_frame = frameDataForRow(rows.at(0)); + + frame_selected = rows.count() == 1; + if ( packet_list_->multiSelectActive() ) + { + frame_selected = false; + multi_selection = true; + } next_selection_history = packet_list_->haveNextHistory(); previous_selection_history = packet_list_->havePreviousHistory(); have_frames = capture_file_.capFile()->count > 0; have_marked = capture_file_.capFile()->marked_count > 0; - another_is_marked = have_marked && - !(capture_file_.capFile()->marked_count == 1 && frame_selected && capture_file_.capFile()->current_frame->marked); + another_is_marked = have_marked && rows.count() <= 1 && + !(capture_file_.capFile()->marked_count == 1 && frame_selected && current_frame->marked); have_filtered = capture_file_.capFile()->displayed_count > 0 && capture_file_.capFile()->displayed_count != capture_file_.capFile()->count; have_ignored = capture_file_.capFile()->ignored_count > 0; have_time_ref = capture_file_.capFile()->ref_time_count > 0; - another_is_time_ref = have_time_ref && - !(capture_file_.capFile()->ref_time_count == 1 && frame_selected && capture_file_.capFile()->current_frame->ref_time); + another_is_time_ref = have_time_ref && rows.count() <= 1 && + !(capture_file_.capFile()->ref_time_count == 1 && frame_selected && current_frame->ref_time); - if (capture_file_.capFile()->edt) + if (capture_file_.capFile()->edt && ! multi_selection) { proto_get_frame_protocols(capture_file_.capFile()->edt->pi.layers, &is_ip, &is_tcp, &is_udp, &is_sctp, @@ -1183,7 +1195,14 @@ void MainWindow::setMenusForSelectedPacket() } } - main_ui_->actionEditMarkPacket->setEnabled(frame_selected); + main_ui_->actionEditMarkPacket->setText(tr("&Mark/Unmark Packet(s)", "", selectedRows().count())); + main_ui_->actionEditIgnorePacket->setText(tr("&Ignore/Unignore Packet(s)", "", selectedRows().count())); + + main_ui_->actionCopyListAsText->setEnabled(selectedRows().count() > 0); + main_ui_->actionCopyListAsCSV->setEnabled(selectedRows().count() > 0); + main_ui_->actionCopyListAsYAML->setEnabled(selectedRows().count() > 0); + + main_ui_->actionEditMarkPacket->setEnabled(frame_selected || multi_selection); main_ui_->actionEditMarkAllDisplayed->setEnabled(have_frames); /* Unlike un-ignore, do not allow unmark of all frames when no frames are displayed */ main_ui_->actionEditUnmarkAllDisplayed->setEnabled(have_marked); @@ -1193,7 +1212,7 @@ void MainWindow::setMenusForSelectedPacket() main_ui_->actionEditPacketComment->setEnabled(frame_selected && wtap_dump_can_write(capture_file_.capFile()->linktypes, WTAP_COMMENT_PER_PACKET)); main_ui_->actionDeleteAllPacketComments->setEnabled((capture_file_.capFile() != NULL) && wtap_dump_can_write(capture_file_.capFile()->linktypes, WTAP_COMMENT_PER_PACKET)); - main_ui_->actionEditIgnorePacket->setEnabled(frame_selected); + main_ui_->actionEditIgnorePacket->setEnabled(frame_selected || multi_selection); main_ui_->actionEditIgnoreAllDisplayed->setEnabled(have_filtered); /* Allow un-ignore of all frames even with no frames currently displayed */ main_ui_->actionEditUnignoreAllDisplayed->setEnabled(have_ignored); @@ -1313,7 +1332,7 @@ void MainWindow::setMenusForSelectedTreeRow(FieldInformation *finfo) { // Always enable / disable the following items. main_ui_->actionFileExportPacketBytes->setEnabled(have_field_info); - main_ui_->actionCopyAllVisibleItems->setEnabled(capture_file_.capFile() != NULL); + main_ui_->actionCopyAllVisibleItems->setEnabled(capture_file_.capFile() != NULL && ! packet_list_->multiSelectActive()); main_ui_->actionCopyAllVisibleSelectedTreeItems->setEnabled(can_match_selected); main_ui_->actionEditCopyDescription->setEnabled(can_match_selected); main_ui_->actionEditCopyFieldName->setEnabled(can_match_selected); @@ -1874,15 +1893,14 @@ void MainWindow::on_actionFilePrint_triggered() capture_file *cf = capture_file_.capFile(); g_return_if_fail(cf); - if (!pdlg_) - { - pdlg_ = new PrintDialog(this, cf); - } - else - { - pdlg_->cap_file_ = cf; - } + QList<int> rows = packet_list_->selectedRows(true); + + QStringList entries; + foreach ( int row, rows ) + entries << QString::number(row); + QString selRange = entries.join(","); + PrintDialog * pdlg_ = new PrintDialog(this, cf, selRange); pdlg_->setWindowModality(Qt::ApplicationModal); pdlg_->show(); } @@ -1926,6 +1944,32 @@ void MainWindow::actionEditCopyTriggered(MainWindow::CopySelected selection_type clip = proto_tree_->toString(proto_tree_->selectionModel()->selectedIndexes().first()); } break; + case CopyListAsText: + case CopyListAsCSV: + case CopyListAsYAML: + if ( packet_list_->selectedRows().count() > 0 ) + { + QList<int> rows = packet_list_->selectedRows(); + QStringList content; + foreach ( int row, rows ) + { + QModelIndex idx = packet_list_->model()->index(row, 0); + if ( ! idx.isValid() ) + continue; + + PacketList::SummaryCopyType copyType = PacketList::CopyAsText; + if ( selection_type == CopyListAsCSV ) + copyType = PacketList::CopyAsCSV; + else if ( selection_type == CopyListAsYAML ) + copyType = PacketList::CopyAsYAML; + QString entry = packet_list_->createSummaryText(idx, copyType); + content << entry; + } + + if ( content.count() > 0 ) + clip = content.join("\n"); + } + break; } if (clip.length() == 0) { @@ -1947,6 +1991,21 @@ void MainWindow::on_actionCopyAllVisibleItems_triggered() actionEditCopyTriggered(CopyAllVisibleItems); } +void MainWindow::on_actionCopyListAsText_triggered() +{ + actionEditCopyTriggered(CopyListAsText); +} + +void MainWindow::on_actionCopyListAsCSV_triggered() +{ + actionEditCopyTriggered(CopyListAsCSV); +} + +void MainWindow::on_actionCopyListAsYAML_triggered() +{ + actionEditCopyTriggered(CopyListAsYAML); +} + void MainWindow::on_actionCopyAllVisibleSelectedTreeItems_triggered() { actionEditCopyTriggered(CopyAllVisibleSelectedTreeItems); @@ -2104,8 +2163,16 @@ void MainWindow::editTimeShiftFinished(int) void MainWindow::on_actionEditPacketComment_triggered() { + QList<int> rows = selectedRows(); + if ( rows.count() != 1 ) + return; + + frame_data * fdata = frameDataForRow(rows.at(0)); + if ( ! fdata ) + return; + PacketCommentDialog* pc_dialog; - pc_dialog = new PacketCommentDialog(capture_file_.capFile()->current_frame->num, this, packet_list_->packetComment()); + pc_dialog = new PacketCommentDialog(fdata->num, this, packet_list_->packetComment()); connect(pc_dialog, &QDialog::finished, std::bind(&MainWindow::editPacketCommentFinished, this, pc_dialog, std::placeholders::_1)); pc_dialog->setWindowModality(Qt::ApplicationModal); pc_dialog->setAttribute(Qt::WA_DeleteOnClose); @@ -2391,7 +2458,7 @@ void MainWindow::colorizeConversation(bool create_rule) QAction *colorize_action = qobject_cast<QAction *>(sender()); if (!colorize_action) return; - if (capture_file_.capFile() && capture_file_.capFile()->current_frame) { + if (capture_file_.capFile() && selectedRows().count() > 0) { packet_info *pi = capture_file_.packetInfo(); guint8 cc_num = colorize_action->data().toUInt(); gchar *filter = conversation_filter_from_packet(pi); @@ -2499,7 +2566,7 @@ void MainWindow::on_actionViewResizeColumns_triggered() void MainWindow::openPacketDialog(bool from_reference) { - frame_data * fdata; + frame_data * fdata = Q_NULLPTR; /* Find the frame for which we're popping up a dialog */ if (from_reference) { @@ -2508,9 +2575,10 @@ void MainWindow::openPacketDialog(bool from_reference) return; fdata = frame_data_sequence_find(capture_file_.capFile()->provider.frames, framenum); - } else { - fdata = capture_file_.capFile()->current_frame; - } + } else if ( selectedRows().count() == 1 ) { + fdata = frameDataForRow(selectedRows().at(0)); + } else if ( selectedRows().count() > 1 ) + return; /* If we have a frame, pop up the dialog */ if (fdata) { @@ -3859,6 +3927,11 @@ void MainWindow::activatePluginIFToolbar(bool) } } +void MainWindow::framesSelected(QList<int> /* frames */) +{ + setMenusForSelectedPacket(); +} + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/ui/qt/models/packet_list_model.cpp b/ui/qt/models/packet_list_model.cpp index 1b9e29d0de..7eff718d59 100644 --- a/ui/qt/models/packet_list_model.cpp +++ b/ui/qt/models/packet_list_model.cpp @@ -214,22 +214,33 @@ void PacketListModel::resetColorized() emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } -void PacketListModel::toggleFrameMark(const QModelIndex &fm_index) +void PacketListModel::toggleFrameMark(const QModelIndexList &indeces) { - if (!cap_file_ || !fm_index.isValid()) return; + if (!cap_file_ || indeces.count() <= 0) + return; - PacketListRecord *record = static_cast<PacketListRecord*>(fm_index.internalPointer()); - if (!record) return; + int sectionMax = columnCount() - 1; - frame_data *fdata = record->frameData(); - if (!fdata) return; + foreach (QModelIndex index, indeces) { + if (! index.isValid()) + continue; - if (fdata->marked) - cf_unmark_frame(cap_file_, fdata); - else - cf_mark_frame(cap_file_, fdata); + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) + continue; - emit dataChanged(fm_index, fm_index); + frame_data *fdata = record->frameData(); + if (!fdata) + continue; + + if (fdata->marked) + cf_unmark_frame(cap_file_, fdata); + else + cf_mark_frame(cap_file_, fdata); + + dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole); + } } void PacketListModel::setDisplayedFrameMark(gboolean set) @@ -244,20 +255,33 @@ void PacketListModel::setDisplayedFrameMark(gboolean set) emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } -void PacketListModel::toggleFrameIgnore(const QModelIndex &i_index) +void PacketListModel::toggleFrameIgnore(const QModelIndexList &indeces) { - if (!cap_file_ || !i_index.isValid()) return; + if (!cap_file_ || indeces.count() <= 0) + return; - PacketListRecord *record = static_cast<PacketListRecord*>(i_index.internalPointer()); - if (!record) return; + int sectionMax = columnCount() - 1; - frame_data *fdata = record->frameData(); - if (!fdata) return; + foreach (QModelIndex index, indeces) { + if (! index.isValid()) + continue; + + PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer()); + if (!record) + continue; - if (fdata->ignored) - cf_unignore_frame(cap_file_, fdata); - else - cf_ignore_frame(cap_file_, fdata); + frame_data *fdata = record->frameData(); + if (!fdata) + continue; + + if (fdata->ignored) + cf_unignore_frame(cap_file_, fdata); + else + cf_ignore_frame(cap_file_, fdata); + + dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax), + QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole); + } } void PacketListModel::setDisplayedFrameIgnore(gboolean set) diff --git a/ui/qt/models/packet_list_model.h b/ui/qt/models/packet_list_model.h index b12e17cbbc..53f3b908c5 100644 --- a/ui/qt/models/packet_list_model.h +++ b/ui/qt/models/packet_list_model.h @@ -60,9 +60,9 @@ public: */ void resetColumns(); void resetColorized(); - void toggleFrameMark(const QModelIndex &fm_index); + void toggleFrameMark(const QModelIndexList &indeces); void setDisplayedFrameMark(gboolean set); - void toggleFrameIgnore(const QModelIndex &i_index); + void toggleFrameIgnore(const QModelIndexList &indeces); void setDisplayedFrameIgnore(gboolean set); void toggleFrameRefTime(const QModelIndex &rt_index); void unsetAllFrameRefTime(); diff --git a/ui/qt/models/related_packet_delegate.cpp b/ui/qt/models/related_packet_delegate.cpp index 89bae07941..72784ea9be 100644 --- a/ui/qt/models/related_packet_delegate.cpp +++ b/ui/qt/models/related_packet_delegate.cpp @@ -12,6 +12,9 @@ #include <ui/qt/utils/color_utils.h> +#include <ui/qt/main_window.h> +#include <ui/qt/wireshark_application.h> + #include <QApplication> #include <QPainter> @@ -37,6 +40,18 @@ RelatedPacketDelegate::RelatedPacketDelegate(QWidget *parent) : void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + + /* This prevents the drawing of related objects, if multiple lines are being selected */ + if ( wsApp && wsApp->mainWindow() ) + { + MainWindow * mw = qobject_cast<MainWindow *>(wsApp->mainWindow()); + if ( mw && mw->selectedRows().count() > 1 ) + { + QStyledItemDelegate::paint(painter, option, index); + return; + } + } + QStyleOptionViewItem option_vi = option; QStyledItemDelegate::initStyleOption(&option_vi, index); int em_w = option_vi.fontMetrics.height(); @@ -202,7 +217,16 @@ void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem } QSize RelatedPacketDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const { + const QModelIndex &index) const +{ + /* This prevents the sizeHint for the delegate, if multiple lines are being selected */ + if ( wsApp && wsApp->mainWindow() ) + { + MainWindow * mw = qobject_cast<MainWindow *>(wsApp->mainWindow()); + if ( mw && mw->selectedRows().count() > 1 ) + return QStyledItemDelegate::sizeHint(option, index); + } + return QSize(option.fontMetrics.height() + QStyledItemDelegate::sizeHint(option, index).width(), QStyledItemDelegate::sizeHint(option, index).height()); } diff --git a/ui/qt/packet_list.cpp b/ui/qt/packet_list.cpp index ad7c44d984..64c0266f7d 100644 --- a/ui/qt/packet_list.cpp +++ b/ui/qt/packet_list.cpp @@ -205,12 +205,6 @@ packet_list_recent_write_all(FILE *rf) { #define MIN_COL_WIDTH_STR "MMMMMM" -enum copy_summary_type { - copy_summary_text_, - copy_summary_csv_, - copy_summary_yaml_ -}; - PacketList::PacketList(QWidget *parent) : QTreeView(parent), proto_tree_(NULL), @@ -247,6 +241,8 @@ PacketList::PacketList(QWidget *parent) : header()->setFirstSectionMovable(true); #endif + setSelectionMode(QAbstractItemView::ExtendedSelection); + // Shrink down to a small but nonzero size in the main splitter. int one_em = fontMetrics().height(); setMinimumSize(one_em, one_em); @@ -390,6 +386,48 @@ void PacketList::setProtoTree (ProtoTree *proto_tree) { &related_packet_delegate_, SLOT(addRelatedFrame(int,ft_framenum_type_t))); } +bool PacketList::multiSelectActive() +{ + return selectedRows().count() > 1 ? true : false; +} + +QList<int> PacketList::selectedRows(bool useFrameNum) +{ + QList<int> rows; + if ( selectionModel() && selectionModel()->selectedIndexes().count() > 0 ) + { + foreach ( QModelIndex idx, selectionModel()->selectedIndexes() ) + { + if ( idx.isValid() ) + { + if ( ! useFrameNum && ! rows.contains(idx.row()) ) + rows << idx.row(); + else if ( useFrameNum ) + { + frame_data * frame = getFDataForRow(idx.row()); + if ( frame && ! rows.contains(frame->num) ) + rows << frame->num; + } + } + } + + std::sort(rows.begin(), rows.end(), std::less<int>()); + } + else if ( currentIndex().isValid() ) + { + if ( ! useFrameNum ) + rows << currentIndex().row(); + else + { + frame_data *frame = getFDataForRow(currentIndex().row()); + if ( frame ) + rows << frame->num; + } + } + + return rows; +} + void PacketList::selectionChanged (const QItemSelection & selected, const QItemSelection & deselected) { QTreeView::selectionChanged(selected, deselected); @@ -397,13 +435,46 @@ void PacketList::selectionChanged (const QItemSelection & selected, const QItemS if (!cap_file_) return; int row = -1; + static bool multiSelect = false; + + if ( selectionModel() ) + { + if ( selectionModel()->selectedRows(0).count() > 1 ) + { + QList<int> rows; + foreach ( QModelIndex idx, selectionModel()->selectedRows(0)) + { + if ( idx.isValid() && ! rows.contains(idx.row()) ) + rows << idx.row(); + } + + emit frameSelected(-1); + emit framesSelected(rows); + emit fieldSelected(0); + cf_unselect_packet(cap_file_); + + /* We have to repaint the content while changing state, as some delegates react to multi-select */ + if ( ! multiSelect ) + { + related_packet_delegate_.clear(); + viewport()->update(); + } + + multiSelect = true; + + return; + } + else if ( selectionModel()->selectedIndexes().count() > 0 && selectionModel()->selectedIndexes().at(0).isValid() ) + { + multiSelect = false; + row = selectionModel()->selectedIndexes().at(0).row(); + } + } - if (selected.isEmpty()) { + if ( row < 0 ) cf_unselect_packet(cap_file_); - } else { - row = selected.first().top(); + else cf_select_packet(cap_file_, row); - } if (!in_history_ && cap_file_->current_frame) { cur_history_++; @@ -416,6 +487,7 @@ void PacketList::selectionChanged (const QItemSelection & selected, const QItemS if (proto_tree_) proto_tree_->clear(); emit frameSelected(row); + emit framesSelected(QList<int>() << row); if (!cap_file_->edt) { viewport()->update(); @@ -489,6 +561,10 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event) proto_prefs_menu_.setModule(module_name); QModelIndex ctxIndex = indexAt(event->pos()); + + if ( selectionModel() && selectionModel()->selectedRows(0).count() > 1 ) + selectionModel()->select(ctxIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + // frameData will be owned by one of the submenus, see below. FrameInformation * frameData = new FrameInformation(new CaptureFile(this, cap_file_), packet_list_model_->getRowFdata(ctxIndex.row())); @@ -555,13 +631,13 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event) ctx_menu->addMenu(submenu); QAction * action = submenu->addAction(tr("Summary as Text")); - action->setData(copy_summary_text_); + action->setData(CopyAsText); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as CSV")); - action->setData(copy_summary_csv_); + action->setData(CopyAsCSV); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); action = submenu->addAction(tr(UTF8_HORIZONTAL_ELLIPSIS "as YAML")); - action->setData(copy_summary_yaml_); + action->setData(CopyAsYAML); connect(action, SIGNAL(triggered()), this, SLOT(copySummary())); submenu->addSeparator(); @@ -584,9 +660,9 @@ void PacketList::contextMenuEvent(QContextMenuEvent *event) // Set menu sensitivity for the current column and set action data. if ( frameData ) - emit frameSelected(frameData->frameNum()); + emit framesSelected(QList<int>() << frameData->frameNum()); else - emit frameSelected(-1); + emit framesSelected(QList<int>()); ctx_menu->exec(event->globalPos()); } @@ -653,7 +729,7 @@ void PacketList::mousePressEvent (QMouseEvent *event) bool midButton = ( event->buttons() & Qt::MidButton ) == Qt::MidButton; if (midButton && cap_file_ && packet_list_model_) { - packet_list_model_->toggleFrameMark(curIndex); + packet_list_model_->toggleFrameMark(QModelIndexList() << curIndex); create_far_overlay_ = true; packets_bar_update(); } @@ -675,7 +751,24 @@ void PacketList::mouseMoveEvent (QMouseEvent *event) QWidget * content = nullptr; QString filter = getFilterFromRowAndColumn(curIndex); - if ( ! filter.isEmpty() ) + QList<int> rows = selectedRows(); + if ( rows.count() > 1 ) + { + QStringList content; + foreach ( int row, rows ) + { + QModelIndex idx = model()->index(row, 0); + if ( ! idx.isValid() ) + continue; + + QString entry = createSummaryText(idx, CopyAsText); + content << entry; + } + + if ( content.count() > 0 ) + mimeData->setText(content.join("\n")); + } + else if ( ! filter.isEmpty() ) { QString abbrev; QString name = model()->headerData(curIndex.column(), header()->orientation()).toString(); @@ -696,7 +789,6 @@ void PacketList::mouseMoveEvent (QMouseEvent *event) filterData["filter"] = filter; filterData["name"] = abbrev; filterData["description"] = name; - QMimeData * mimeData = new QMimeData(); mimeData->setData(WiresharkMimeData::DisplayFilterMimeType, QJsonDocument(filterData).toJson()); content = new DragLabel(QString("%1\n%2").arg(name, abbrev), this); @@ -708,7 +800,7 @@ void PacketList::mouseMoveEvent (QMouseEvent *event) mimeData->setText(text); } - if ( mimeData ) + if ( mimeData->hasText() || mimeData->hasFormat(WiresharkMimeData::DisplayFilterMimeType) ) { QDrag * drag = new QDrag(this); drag->setMimeData(mimeData); @@ -726,6 +818,34 @@ void PacketList::mouseMoveEvent (QMouseEvent *event) } } +void PacketList::keyPressEvent(QKeyEvent *event) +{ + QTreeView::keyPressEvent(event); + if ( event->matches(QKeySequence::Copy) ) + { + QStringList content; + if ( model() && selectionModel() && selectionModel()->selectedRows(0).count() > 0 ) + { + QList<int> rows; + foreach(QModelIndex row, selectionModel()->selectedRows(0)) + rows.append(row.row()); + + foreach(int row, rows) + { + QModelIndex idx = model()->index(row, 0); + if ( ! idx.isValid() ) + continue; + + QString entry = createSummaryText(idx, CopyAsText); + content << entry; + } + } + + if ( content.count() > 0 ) + wsApp->clipboard()->setText(content.join('\n'), QClipboard::Clipboard); + } +} + void PacketList::resizeEvent(QResizeEvent *event) { create_near_overlay_ = true; @@ -1376,7 +1496,24 @@ void PacketList::markFrame() { if (!cap_file_ || !packet_list_model_) return; - packet_list_model_->toggleFrameMark(currentIndex()); + QModelIndexList frames; + + if ( selectionModel() && selectionModel()->selectedRows(0).count() > 1 ) + { + QList<int> rows; + foreach ( QModelIndex idx, selectionModel()->selectedRows(0) ) + { + if ( idx.isValid() && ! rows.contains(idx.row()) ) + { + frames << idx; + rows << idx.row(); + } + } + } + else + frames << currentIndex(); + + packet_list_model_->toggleFrameMark(frames); create_far_overlay_ = true; packets_bar_update(); } @@ -1394,7 +1531,25 @@ void PacketList::ignoreFrame() { if (!cap_file_ || !packet_list_model_) return; - packet_list_model_->toggleFrameIgnore(currentIndex()); + QModelIndexList frames; + + if ( selectionModel() && selectionModel()->selectedRows(0).count() > 1 ) + { + QList<int> rows; + foreach ( QModelIndex idx, selectionModel()->selectedRows(0) ) + { + if ( idx.isValid() && ! rows.contains(idx.row()) ) + { + frames << idx; + rows << idx.row(); + } + } + } + else + frames << currentIndex(); + + + packet_list_model_->toggleFrameIgnore(frames); create_far_overlay_ = true; int sb_val = verticalScrollBar()->value(); // Surely there's a better way to keep our position? setUpdatesEnabled(false); @@ -1560,19 +1715,13 @@ void PacketList::updateRowHeights(const QModelIndex &ih_index) } } -void PacketList::copySummary() +QString PacketList::createSummaryText(QModelIndex idx, SummaryCopyType type) { - if (!currentIndex().isValid()) return; - - QAction *ca = qobject_cast<QAction*>(sender()); - if (!ca) return; - - bool ok = false; - int copy_type = ca->data().toInt(&ok); - if (!ok) return; + if ( ! idx.isValid() ) + return ""; QStringList col_parts; - int row = currentIndex().row(); + int row = idx.row(); for (int col = 0; col < packet_list_model_->columnCount(); col++) { if (get_column_visible(col)) { col_parts << packet_list_model_->data(packet_list_model_->index(row, col), Qt::DisplayRole).toString(); @@ -1580,23 +1729,41 @@ void PacketList::copySummary() } QString copy_text; - switch (copy_type) { - case copy_summary_csv_: + switch (type) { + case CopyAsCSV: copy_text = "\""; copy_text += col_parts.join("\",\""); copy_text += "\""; break; - case copy_summary_yaml_: + case CopyAsYAML: copy_text = "----\n"; copy_text += QString("# Packet %1 from %2\n").arg(row).arg(cap_file_->filename); copy_text += "- "; copy_text += col_parts.join("\n- "); copy_text += "\n"; break; - case copy_summary_text_: + case CopyAsText: default: copy_text = col_parts.join("\t"); } + + return copy_text; +} + +void PacketList::copySummary() +{ + if (!currentIndex().isValid()) return; + + QAction *ca = qobject_cast<QAction*>(sender()); + if (!ca) return; + + QVariant type = ca->data(); + if ( ! type.canConvert<SummaryCopyType>() ) + return; + SummaryCopyType copy_type = type.value<SummaryCopyType>(); + + QString copy_text = createSummaryText(currentIndex(), copy_type); + wsApp->clipboard()->setText(copy_text); } diff --git a/ui/qt/packet_list.h b/ui/qt/packet_list.h index 52066eb73e..eaaabbab18 100644 --- a/ui/qt/packet_list.h +++ b/ui/qt/packet_list.h @@ -33,6 +33,14 @@ class PacketList : public QTreeView Q_OBJECT public: explicit PacketList(QWidget *parent = 0); + + enum SummaryCopyType { + CopyAsText, + CopyAsCSV, + CopyAsYAML + }; + Q_ENUM(SummaryCopyType) + QMenu *conversationMenu() { return &conv_menu_; } QMenu *colorizeMenu() { return &colorize_menu_; } void setProtoTree(ProtoTree *proto_tree); @@ -69,7 +77,13 @@ public: frame_data * getFDataForRow(int row) const; + bool multiSelectActive(); + QList<int> selectedRows(bool useFrameNum = false); + + QString createSummaryText(QModelIndex idx, SummaryCopyType type); + protected: + void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected) override; virtual void contextMenuEvent(QContextMenuEvent *event) override; void timerEvent(QTimerEvent *event) override; @@ -78,6 +92,7 @@ protected: virtual void mouseReleaseEvent (QMouseEvent *event) override; virtual void mouseMoveEvent (QMouseEvent *event) override; virtual void resizeEvent(QResizeEvent *event) override; + virtual void keyPressEvent(QKeyEvent *event) override; protected slots: void rowsInserted(const QModelIndex &parent, int start, int end) override; @@ -134,6 +149,7 @@ signals: void editProtocolPreference(struct preference *pref, struct pref_module *module); void frameSelected(int frameNum); + void framesSelected(QList<int>); void fieldSelected(FieldInformation *); public slots: diff --git a/ui/qt/packet_range_group_box.cpp b/ui/qt/packet_range_group_box.cpp index fff31a6969..8f808a3971 100644 --- a/ui/qt/packet_range_group_box.cpp +++ b/ui/qt/packet_range_group_box.cpp @@ -28,7 +28,7 @@ PacketRangeGroupBox::~PacketRangeGroupBox() delete pr_ui_; } -void PacketRangeGroupBox::initRange(packet_range_t *range) { +void PacketRangeGroupBox::initRange(packet_range_t *range, QString selRange) { if (!range) return; range_ = range; @@ -39,6 +39,9 @@ void PacketRangeGroupBox::initRange(packet_range_t *range) { pr_ui_->capturedButton->setChecked(true); } + if ( selRange.length() > 0 ) + packet_range_convert_selection_str(range_, selRange.toUtf8().constData()); + if (range_->user_range) { char* tmp_str = range_convert_range(NULL, range_->user_range); pr_ui_->rangeLineEdit->setText(tmp_str); @@ -57,7 +60,6 @@ bool PacketRangeGroupBox::isValid() { void PacketRangeGroupBox::updateCounts() { SyntaxLineEdit::SyntaxState orig_ss = syntax_state_; bool displayed_checked = pr_ui_->displayedButton->isChecked(); - int selected_num; bool can_select; bool selected_packets; int ignored_cnt = 0, displayed_ignored_cnt = 0; @@ -94,12 +96,14 @@ void PacketRangeGroupBox::updateCounts() { pr_ui_->allDisplayedLabel->setText(QString("%1").arg(label_count)); // Selected / Captured + Displayed - selected_num = (range_->cf->current_frame) ? range_->cf->current_frame->num : 0; - can_select = (selected_num != 0); + can_select = (range_->selection_range_cnt > 0 || range_->displayed_selection_range_cnt > 0); if (can_select) { pr_ui_->selectedButton->setEnabled(true); pr_ui_->selectedCapturedLabel->setEnabled(!displayed_checked); pr_ui_->selectedDisplayedLabel->setEnabled(displayed_checked); + + pr_ui_->selectedCapturedLabel->setText(QString::number(range_->selection_range_cnt)); + pr_ui_->selectedDisplayedLabel->setText(QString::number(range_->displayed_selection_range_cnt)); } else { if (range_->process == range_process_selected) { pr_ui_->allButton->setChecked(true); @@ -107,13 +111,9 @@ void PacketRangeGroupBox::updateCounts() { pr_ui_->selectedButton->setEnabled(false); pr_ui_->selectedCapturedLabel->setEnabled(false); pr_ui_->selectedDisplayedLabel->setEnabled(false); - } - if ((range_->remove_ignored && can_select && range_->cf->current_frame->ignored) || selected_num < 1) { + pr_ui_->selectedCapturedLabel->setText("0"); pr_ui_->selectedDisplayedLabel->setText("0"); - } else { - pr_ui_->selectedCapturedLabel->setText("1"); - pr_ui_->selectedDisplayedLabel->setText("1"); } // Marked / Captured + Displayed @@ -222,8 +222,8 @@ void PacketRangeGroupBox::updateCounts() { displayed_ignored_cnt = range_->displayed_ignored_cnt; break; case(range_process_selected): - ignored_cnt = (can_select && range_->cf->current_frame->ignored) ? 1 : 0; - displayed_ignored_cnt = ignored_cnt; + ignored_cnt = range_->ignored_selection_range_cnt; + displayed_ignored_cnt = range_->displayed_ignored_selection_range_cnt; break; case(range_process_marked): ignored_cnt = range_->ignored_marked_cnt; diff --git a/ui/qt/packet_range_group_box.h b/ui/qt/packet_range_group_box.h index 215f3ee190..12ad025cfa 100644 --- a/ui/qt/packet_range_group_box.h +++ b/ui/qt/packet_range_group_box.h @@ -34,7 +34,7 @@ class PacketRangeGroupBox : public QGroupBox public: explicit PacketRangeGroupBox(QWidget *parent = 0); ~PacketRangeGroupBox(); - void initRange(packet_range_t *range); + void initRange(packet_range_t *range, QString selRange = QString()); bool isValid(); signals: diff --git a/ui/qt/print_dialog.cpp b/ui/qt/print_dialog.cpp index 32bf9e8d27..d443407a93 100644 --- a/ui/qt/print_dialog.cpp +++ b/ui/qt/print_dialog.cpp @@ -63,7 +63,7 @@ new_page_pd(print_stream_t *self) } // extern "C" -PrintDialog::PrintDialog(QWidget *parent, capture_file *cf) : +PrintDialog::PrintDialog(QWidget *parent, capture_file *cf, QString selRange) : QDialog(parent), pd_ui_(new Ui::PrintDialog), cur_printer_(NULL), @@ -110,7 +110,7 @@ PrintDialog::PrintDialog(QWidget *parent, capture_file *cf) : printer_.setDocName(display_basename); g_free(display_basename); - pd_ui_->rangeGroupBox->initRange(&print_args_.range); + pd_ui_->rangeGroupBox->initRange(&print_args_.range, selRange); pd_ui_->buttonBox->addButton(print_bt_, QDialogButtonBox::ActionRole); pd_ui_->buttonBox->addButton(tr("Page &Setup" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ResetRole); @@ -133,8 +133,6 @@ PrintDialog::~PrintDialog() delete pd_ui_; } -// Public - gboolean PrintDialog::printHeader() { if (!cap_file_ || !cap_file_->filename || !cur_printer_ || !cur_painter_) return FALSE; diff --git a/ui/qt/print_dialog.h b/ui/qt/print_dialog.h index 79af98f457..8f821a7541 100644 --- a/ui/qt/print_dialog.h +++ b/ui/qt/print_dialog.h @@ -30,17 +30,16 @@ class PrintDialog : public QDialog Q_OBJECT public: - explicit PrintDialog(QWidget *parent = 0, capture_file *cf = NULL); + explicit PrintDialog(QWidget *parent = 0, capture_file *cf = NULL, QString selRange = QString()); ~PrintDialog(); + gboolean printHeader(); gboolean printLine(int indent, const char *line); protected: - void keyPressEvent(QKeyEvent *event); + virtual void keyPressEvent(QKeyEvent *event) override; private: - void printPackets(QPrinter *printer = NULL, bool in_preview = false); - Ui::PrintDialog *pd_ui_; QPrinter printer_; @@ -59,6 +58,8 @@ private: int page_pos_; bool in_preview_; + void printPackets(QPrinter *printer = NULL, bool in_preview = false); + private slots: void paintPreview(QPrinter *printer); void checkValidity(); diff --git a/ui/qt/proto_tree.cpp b/ui/qt/proto_tree.cpp index 360bed5811..a8fcf2136f 100644 --- a/ui/qt/proto_tree.cpp +++ b/ui/qt/proto_tree.cpp @@ -95,6 +95,8 @@ ProtoTree::ProtoTree(QWidget *parent, epan_dissect_t *edt_fixed) : connect(verticalScrollBar(), SIGNAL(sliderReleased()), this, SLOT(updateContentWidth())); + connect(wsApp, SIGNAL(appInitialized()), this, SLOT(connectToMainWindow())); + viewport()->installEventFilter(this); } @@ -103,6 +105,17 @@ void ProtoTree::clear() { updateContentWidth(); } +void ProtoTree::connectToMainWindow() +{ + if ( wsApp->mainWindow() ) + { + connect(wsApp->mainWindow(), SIGNAL(fieldSelected(FieldInformation *)), + this, SLOT(selectedFieldChanged(FieldInformation *))); + connect(wsApp->mainWindow(), SIGNAL(frameSelected(int)), + this, SLOT(selectedFrameChanged(int))); + } +} + void ProtoTree::ctxCopyVisibleItems() { bool selected_tree = false; @@ -590,6 +603,12 @@ void ProtoTree::itemDoubleClicked(const QModelIndex &index) { } } +void ProtoTree::selectedFrameChanged(int frameNum) +{ + if ( frameNum < 0 ) + proto_tree_model_->setRootNode(Q_NULLPTR); +} + // Select a field and bring it into view. Intended to be called by external // components (such as the byte view). void ProtoTree::selectedFieldChanged(FieldInformation *finfo) diff --git a/ui/qt/proto_tree.h b/ui/qt/proto_tree.h index 476f72b727..608ddcb7cb 100644 --- a/ui/qt/proto_tree.h +++ b/ui/qt/proto_tree.h @@ -95,6 +95,7 @@ public slots: void collapseAll(); void itemDoubleClicked(const QModelIndex & index); void selectedFieldChanged(FieldInformation *); + void selectedFrameChanged(int); protected slots: void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); @@ -109,6 +110,7 @@ protected slots: private slots: void updateContentWidth(); + void connectToMainWindow(); }; #endif // PROTO_TREE_H diff --git a/ui/qt/wireshark_en.ts b/ui/qt/wireshark_en.ts index 595e8f8f0f..fe040c29fe 100644 --- a/ui/qt/wireshark_en.ts +++ b/ui/qt/wireshark_en.ts @@ -6481,9 +6481,13 @@ For example, use 1 hour to have a new file created every hour on the hour.</sour <source>Find the previous packet</source> <translation type="unfinished"></translation> </message> - <message> - <source>&Mark/Unmark Packet</source> - <translation type="unfinished"></translation> + <message numerus="yes"> + <source>&Mark/Unmark Packet(s)</source> + <oldsource>&Mark/Unmark Packet</oldsource> + <translation type="unfinished"> + <numerusform>&Mark/Unmark Packet</numerusform> + <numerusform>&Mark/Unmark Packets</numerusform> + </translation> </message> <message> <source>Mark or unmark this packet</source> @@ -6517,9 +6521,13 @@ For example, use 1 hour to have a new file created every hour on the hour.</sour <source>Go to the previous marked packet</source> <translation type="unfinished"></translation> </message> - <message> - <source>&Ignore/Unignore Packet</source> - <translation type="unfinished"></translation> + <message numerus="yes"> + <source>&Ignore/Unignore Packet(s)</source> + <oldsource>&Ignore/Unignore Packet</oldsource> + <translation type="unfinished"> + <numerusform>&Ignore/Unignore Packet</numerusform> + <numerusform>&Ignore/Unignore Packets</numerusform> + </translation> </message> <message> <source>Ignore or unignore this packet</source> diff --git a/ui/win32/file_dlg_win32.c b/ui/win32/file_dlg_win32.c index 6a8a9201e2..a9b579ac6d 100644 --- a/ui/win32/file_dlg_win32.c +++ b/ui/win32/file_dlg_win32.c @@ -1606,7 +1606,6 @@ range_update_dynamics(HWND dlg_hwnd, packet_range_t *range) { HWND cur_ctrl; gboolean filtered_active = FALSE; TCHAR static_val[STATIC_LABEL_CHARS]; - gint selected_num; guint32 ignored_cnt = 0, displayed_ignored_cnt = 0; guint32 displayed_cnt; gboolean range_valid = TRUE; @@ -1639,22 +1638,21 @@ range_update_dynamics(HWND dlg_hwnd, packet_range_t *range) { SetWindowText(cur_ctrl, static_val); /* RANGE_SELECT_CURR */ - selected_num = (g_cf->current_frame) ? g_cf->current_frame->num : 0; cur_ctrl = GetDlgItem(dlg_hwnd, EWFD_SEL_PKT_CAP); - EnableWindow(cur_ctrl, selected_num && !filtered_active); - if (range->remove_ignored && g_cf->current_frame && g_cf->current_frame->ignored) { - StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("0")); + EnableWindow(cur_ctrl, range->selection_range_cnt > 0 && !filtered_active); + if (range->remove_ignored) { + StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), range->selection_range_cnt - range->ignored_selection_range_cnt); } else { - StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), selected_num ? 1 : 0); + StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), range->selection_range_cnt); } SetWindowText(cur_ctrl, static_val); cur_ctrl = GetDlgItem(dlg_hwnd, EWFD_SEL_PKT_DISP); - EnableWindow(cur_ctrl, selected_num && filtered_active); - if (range->remove_ignored && g_cf->current_frame && g_cf->current_frame->ignored) { - StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("0")); + EnableWindow(cur_ctrl, range->displayed_selection_range_cnt > 0 && filtered_active); + if (range->remove_ignored) { + StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), range->displayed_selection_range_cnt - range->displayed_ignored_selection_range_cnt); } else { - StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), selected_num ? 1 : 0); + StringCchPrintf(static_val, STATIC_LABEL_CHARS, _T("%d"), range->displayed_selection_range_cnt); } SetWindowText(cur_ctrl, static_val); @@ -1756,8 +1754,8 @@ range_update_dynamics(HWND dlg_hwnd, packet_range_t *range) { displayed_ignored_cnt = range->displayed_ignored_cnt; break; case(range_process_selected): - ignored_cnt = (g_cf->current_frame && g_cf->current_frame->ignored) ? 1 : 0; - displayed_ignored_cnt = ignored_cnt; + ignored_cnt = range->ignored_selection_range_cnt; + displayed_ignored_cnt = range->displayed_ignored_selection_range_cnt; break; case(range_process_marked): ignored_cnt = range->ignored_marked_cnt; |