Qt: Edit capture comments in separate dialog

The capture file properties dialog already lists the section
comments in the Details section, so it wastes some space to have
them a second time in the edit box.

Have an edit dialog for the capture comments. Have the edit dialog
use tabs so that we can edit multiple comments in a section, and
all sections. Allow adding new comments, removing comments, and
reordering comments.

Fix a few small leaks.

Related to #14599, #16133.
This commit is contained in:
John Thacker 2023-10-15 18:17:18 -04:00 committed by AndersBroman
parent 988635fd26
commit 861a3eef63
10 changed files with 430 additions and 107 deletions

46
file.c
View File

@ -4428,6 +4428,52 @@ cf_update_section_comment(capture_file *cf, gchar *comment)
cf->unsaved_changes = TRUE;
}
/*
* Modify the section comments for a given section.
*/
void
cf_update_section_comments(capture_file *cf, unsigned shb_idx, char **comments)
{
wtap_block_t shb_inf;
gchar *shb_comment;
shb_inf = wtap_file_get_shb(cf->provider.wth, shb_idx);
if (shb_inf == NULL) {
/* Shouldn't happen. XXX: Report it if it does? */
return;
}
unsigned n_comments = g_strv_length(comments);
unsigned i;
char* comment;
for (i = 0; i < n_comments; i++) {
comment = comments[i];
if (wtap_block_get_nth_string_option_value(shb_inf, OPT_COMMENT, i, &shb_comment) != WTAP_OPTTYPE_SUCCESS) {
/* There's no comment - add one. */
wtap_block_add_string_option_owned(shb_inf, OPT_COMMENT, comment);
cf->unsaved_changes = TRUE;
} else {
/* See if the comment has changed or not */
if (strcmp(shb_comment, comment) != 0) {
/* The comment has changed, let's update it */
wtap_block_set_nth_string_option_value(shb_inf, OPT_COMMENT, 0, comment, strlen(comment));
cf->unsaved_changes = TRUE;
}
g_free(comment);
}
}
/* We either transferred ownership of the comments or freed them
* above, so free the array of strings but not the strings themselves. */
g_free(comments);
/* If there are extra old comments, remove them. Start at the end. */
for (i = wtap_block_count_option(shb_inf, OPT_COMMENT); i > n_comments; i--) {
wtap_block_remove_nth_option_instance(shb_inf, OPT_COMMENT, i - 1);
cf->unsaved_changes = TRUE;
}
}
/*
* Get the packet block for a packet (record).
* If the block has been edited, it returns the result of the edit,

10
file.h
View File

@ -689,6 +689,16 @@ cf_merge_files_to_tempfile(gpointer pd_window, const char *temp_dir, char **out_
*/
void cf_update_section_comment(capture_file *cf, gchar *comment);
/**
* Update(replace) the comments on a capture from the SHB data block
*
* @param cf the capture file
* @param shb_idx the index of the SHB (0-indexed)
* @param comments a NULL-terminated string array of comments. The function
* takes ownership of the string array and frees it and the contents.
*/
void cf_update_section_comments(capture_file *cf, unsigned shb_idx, char **comments);
/*
* Get the packet block for a packet (record).
* If the block has been edited, it returns the result of the edit,

View File

@ -137,6 +137,7 @@ set(WIRESHARK_QT_HEADERS
../qt/accordion_frame.h
../qt/address_editor_frame.h
../qt/byte_view_tab.h
../qt/capture_comment_dialog.h
../qt/capture_file_dialog.h
../qt/capture_file_properties_dialog.h
../qt/capture_file.h
@ -361,6 +362,7 @@ set(WIRESHARK_QT_SRC
../qt/accordion_frame.cpp
../qt/address_editor_frame.cpp
../qt/byte_view_tab.cpp
../qt/capture_comment_dialog.cpp
../qt/capture_file_dialog.cpp
../qt/capture_file_properties_dialog.cpp
../qt/capture_file.cpp
@ -487,6 +489,7 @@ set (LOGRAY_QT_FILES ${WIRESHARK_QT_FILES})
set(WIRESHARK_QT_UI
../qt/about_dialog.ui
../qt/address_editor_frame.ui
../qt/capture_comment_dialog.ui
../qt/capture_file_properties_dialog.ui
../qt/capture_info_dialog.ui
../qt/capture_options_dialog.ui

View File

@ -141,6 +141,7 @@ set(WIRESHARK_QT_HEADERS
bluetooth_hci_summary_dialog.h
browser_sslkeylog_dialog.h
byte_view_tab.h
capture_comment_dialog.h
capture_file_dialog.h
capture_file_properties_dialog.h
capture_file.h
@ -395,6 +396,7 @@ set(WIRESHARK_QT_SRC
bluetooth_hci_summary_dialog.cpp
browser_sslkeylog_dialog.cpp
byte_view_tab.cpp
capture_comment_dialog.cpp
capture_file_dialog.cpp
capture_file_properties_dialog.cpp
capture_file.cpp
@ -559,6 +561,7 @@ set(WIRESHARK_QT_UI
bluetooth_devices_dialog.ui
bluetooth_hci_summary_dialog.ui
browser_sslkeylog_dialog.ui
capture_comment_dialog.ui
capture_file_properties_dialog.ui
capture_info_dialog.ui
capture_options_dialog.ui

View File

@ -0,0 +1,252 @@
/* packet_comment_dialog.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "capture_comment_dialog.h"
#include <ui_capture_comment_dialog.h>
#include "file.h"
#include "ui/simple_dialog.h"
#include "ui/qt/utils/qt_ui_utils.h"
#include "main_application.h"
#include <QTabBar>
#include <QPushButton>
#include <QPlainTextEdit>
class CaptureCommentTabWidget : public QTabWidget
{
Q_OBJECT
public:
CaptureCommentTabWidget(QWidget *parent = nullptr) : QTabWidget(parent)
{
setTabsClosable(true);
setMovable(true);
connect(this, &QTabWidget::tabCloseRequested, this, &CaptureCommentTabWidget::closeTab);
connect(tabBar(), &QTabBar::tabMoved, this, &CaptureCommentTabWidget::setTabTitles);
}
int addTab(QWidget *page);
void tabRemoved(int index) override;
void closeTab(int index);
void setReadOnly(bool ro);
char** getCommentsText();
private:
void setTabTitles(int from, int to);
};
int CaptureCommentTabWidget::addTab(QWidget *page)
{
return QTabWidget::addTab(page, tr("Comment %1").arg(count() + 1));
}
void CaptureCommentTabWidget::closeTab(int index)
{
QPlainTextEdit *te;
te = qobject_cast<QPlainTextEdit*>(widget(index));
if (te != nullptr) {
removeTab(index);
delete te;
}
}
void CaptureCommentTabWidget::setReadOnly(bool ro)
{
QPlainTextEdit *commentTextEdit;
for (int index = 0; index < count(); index++) {
commentTextEdit = qobject_cast<QPlainTextEdit*>(widget(index));
if (commentTextEdit != nullptr) {
commentTextEdit->setReadOnly(ro);
}
}
}
void CaptureCommentTabWidget::tabRemoved(int index)
{
setTabTitles(index, count() - 1);
}
char** CaptureCommentTabWidget::getCommentsText()
{
/* glib 2.68 and later have g_strv_builder which is slightly
* more convenient.
*/
QPlainTextEdit *te;
GPtrArray *ptr_array = g_ptr_array_new_full(count() + 1, g_free);
for (int index = 0; index < count(); index++) {
te = qobject_cast<QPlainTextEdit*>(widget(index));
if (te != nullptr) {
gchar *str = qstring_strdup(te->toPlainText());
/*
* Make sure this would fit in a pcapng option.
*
* XXX - 65535 is the maximum size for an option in pcapng;
* what if another capture file format supports larger
* comments?
*/
if (strlen(str) > 65535) {
/* It doesn't fit. Give up. */
g_ptr_array_free(ptr_array, TRUE);
return nullptr;
}
g_ptr_array_add(ptr_array, str);
}
}
g_ptr_array_add(ptr_array, nullptr);
return (char**)g_ptr_array_free(ptr_array, FALSE);
}
void CaptureCommentTabWidget::setTabTitles(int from, int to)
{
if (from < to) {
for (; from <= to; from++) {
this->setTabText(from, tr("Comment %1").arg(from + 1));
}
} else {
for (; from >= to; from--) {
this->setTabText(from, tr("Comment %1").arg(from + 1));
}
}
}
CaptureCommentDialog::CaptureCommentDialog(QWidget &parent, CaptureFile &capture_file) :
WiresharkDialog(parent, capture_file),
ui(new Ui::CaptureCommentDialog)
{
ui->setupUi(this);
loadGeometry();
setWindowSubtitle(tr("Edit Capture Comments"));
ui->sectionTabWidget->setTabBarAutoHide(true);
this->actionAddButton = ui->buttonBox->addButton(tr("Add Comment"), QDialogButtonBox::ActionRole);
connect(this->actionAddButton, &QPushButton::clicked, this, &CaptureCommentDialog::addComment);
connect(this, SIGNAL(captureCommentChanged()),
mainApp->mainWindow(), SLOT(updateForUnsavedChanges()));
QTimer::singleShot(0, this, SLOT(updateWidgets()));
}
CaptureCommentDialog::~CaptureCommentDialog()
{
delete ui;
}
void CaptureCommentDialog::addComment()
{
QPlainTextEdit *commentTextEdit;
CaptureCommentTabWidget *currentTW = qobject_cast<CaptureCommentTabWidget*>(ui->sectionTabWidget->currentWidget());
if (currentTW != nullptr) {
commentTextEdit = new QPlainTextEdit(currentTW);
currentTW->addTab(commentTextEdit);
}
}
void CaptureCommentDialog::updateWidgets()
{
QPlainTextEdit *commentTextEdit;
CaptureCommentTabWidget *shbTW;
QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
if (file_closed_ || !cap_file_.isValid()) {
for (int shb = 0; shb < ui->sectionTabWidget->count(); shb++) {
shbTW = qobject_cast<CaptureCommentTabWidget*>(ui->sectionTabWidget->widget(shb));
shbTW->setReadOnly(true);
}
if (save_bt) {
save_bt->setEnabled(false);
}
actionAddButton->setEnabled(false);
WiresharkDialog::updateWidgets();
return;
}
bool enable = wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION);
save_bt->setEnabled(enable);
actionAddButton->setEnabled(enable);
unsigned num_shbs = wtap_file_get_num_shbs(cap_file_.capFile()->provider.wth);
for (unsigned shb_idx = 0; shb_idx < num_shbs; shb_idx++) {
shbTW = qobject_cast<CaptureCommentTabWidget*>(ui->sectionTabWidget->widget(shb_idx));
if (shbTW == nullptr) {
shbTW = new CaptureCommentTabWidget(ui->sectionTabWidget);
ui->sectionTabWidget->addTab(shbTW, tr("Section %1").arg(shb_idx + 1));
}
wtap_block_t shb = wtap_file_get_shb(cap_file_.capFile()->provider.wth, shb_idx);
unsigned num_comments = wtap_block_count_option(shb, OPT_COMMENT);
char *shb_comment;
for (unsigned index = 0; index < num_comments; index++) {
commentTextEdit = qobject_cast<QPlainTextEdit*>(shbTW->widget(index));
if (commentTextEdit == nullptr) {
commentTextEdit = new QPlainTextEdit(shbTW);
shbTW->addTab(commentTextEdit);
}
if (wtap_block_get_nth_string_option_value(shb, OPT_COMMENT, index,
&shb_comment) == WTAP_OPTTYPE_SUCCESS) {
commentTextEdit->setPlainText(shb_comment);
} else {
// XXX: Should we warn about this failure?
commentTextEdit->setPlainText("");
}
commentTextEdit->setReadOnly(!enable);
}
for (unsigned index = shbTW->count(); index > num_comments; index--) {
shbTW->closeTab(index - 1);
}
}
WiresharkDialog::updateWidgets();
}
void CaptureCommentDialog::on_buttonBox_helpRequested()
{
// mainApp->helpTopicAction(HELP_CAPTURE_COMMENT_DIALOG);
}
void CaptureCommentDialog::on_buttonBox_accepted()
{
int ret = QDialog::Rejected;
if (file_closed_ || !cap_file_.capFile()->filename) {
done(ret);
return;
}
if (wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION))
{
CaptureCommentTabWidget *current;
char** comments_text;
for (int shb_idx = 0; shb_idx < ui->sectionTabWidget->count(); shb_idx++) {
current = qobject_cast<CaptureCommentTabWidget*>(ui->sectionTabWidget->widget(shb_idx));
comments_text = current->getCommentsText();
if (comments_text == nullptr) {
/* This is the only error we track currently, so it must be
* this. Tell the user and give up. */
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
"A comment is too large to save in a capture file.");
done(ret);
return;
}
cf_update_section_comments(cap_file_.capFile(), shb_idx, comments_text);
emit captureCommentChanged();
ret = QDialog::Accepted;
}
}
done(ret);
}
void CaptureCommentDialog::on_buttonBox_rejected()
{
reject();
}
#include "capture_comment_dialog.moc"

View File

@ -0,0 +1,44 @@
/** @file
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef CAPTURE_COMMENT_DIALOG_H
#define CAPTURE_COMMENT_DIALOG_H
#include <glib.h>
#include "wireshark_dialog.h"
namespace Ui {
class CaptureCommentDialog;
}
class CaptureCommentDialog : public WiresharkDialog
{
Q_OBJECT
public:
explicit CaptureCommentDialog(QWidget &parent, CaptureFile &capture_file);
~CaptureCommentDialog();
signals:
void captureCommentChanged();
private slots:
void addComment();
void updateWidgets();
void on_buttonBox_helpRequested();
void on_buttonBox_accepted();
void on_buttonBox_rejected();
private:
QPushButton *actionAddButton;
Ui::CaptureCommentDialog *ui;
};
#endif // CAPTURE_COMMENT_DIALOG_H

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CaptureCommentDialog</class>
<widget class="QDialog" name="CaptureCommentDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<!--<property name="modal">
<bool>true</bool>
</property>-->
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="sectionTabWidget"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
</ui>

View File

@ -21,6 +21,7 @@
#include <ui/qt/utils/qt_ui_utils.h>
#include "main_application.h"
#include "capture_comment_dialog.h"
#include <QPushButton>
#include <QScrollBar>
@ -39,10 +40,6 @@ CaptureFilePropertiesDialog::CaptureFilePropertiesDialog(QWidget &parent, Captur
ui->detailsTextEdit->setAcceptRichText(true);
// make the details box larger than the comments
ui->splitter->setStretchFactor(0, 6);
ui->splitter->setStretchFactor(1, 1);
QPushButton *button = ui->buttonBox->button(QDialogButtonBox::Reset);
if (button) {
button->setText(tr("Refresh"));
@ -53,16 +50,14 @@ CaptureFilePropertiesDialog::CaptureFilePropertiesDialog(QWidget &parent, Captur
button->setText(tr("Copy To Clipboard"));
}
button = ui->buttonBox->button(QDialogButtonBox::Save);
if (button) {
button->setText(tr("Save Comments"));
}
button = ui->buttonBox->button(QDialogButtonBox::Close);
if (button) {
button->setDefault(true);
}
ui->buttonBox->addButton(ui->actionEditButton, QDialogButtonBox::ActionRole);
connect(ui->actionEditButton, &QPushButton::clicked, this, &CaptureFilePropertiesDialog::addCaptureComment);
setWindowSubtitle(tr("Capture File Properties"));
QTimer::singleShot(0, this, SLOT(updateWidgets()));
}
@ -81,34 +76,16 @@ CaptureFilePropertiesDialog::~CaptureFilePropertiesDialog()
void CaptureFilePropertiesDialog::updateWidgets()
{
QPushButton *refresh_bt = ui->buttonBox->button(QDialogButtonBox::Reset);
QPushButton *save_bt = ui->buttonBox->button(QDialogButtonBox::Save);
if (file_closed_ || !cap_file_.isValid()) {
if (refresh_bt) {
refresh_bt->setEnabled(false);
}
ui->commentsTextEdit->setReadOnly(true);
if (save_bt) {
save_bt->setEnabled(false);
}
WiresharkDialog::updateWidgets();
return;
}
bool enable = wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION);
save_bt->setEnabled(enable);
ui->commentsTextEdit->setEnabled(enable);
fillDetails();
// XXX - this just handles the first comment in the first section;
// add support for multiple sections with multiple comments.
wtap_block_t shb = wtap_file_get_shb(cap_file_.capFile()->provider.wth, 0);
char *shb_comment;
if (wtap_block_get_nth_string_option_value(shb, OPT_COMMENT, 0,
&shb_comment) == WTAP_OPTTYPE_SUCCESS)
ui->commentsTextEdit->setText(shb_comment);
else
ui->commentsTextEdit->setText(NULL);
WiresharkDialog::updateWidgets();
}
@ -397,6 +374,7 @@ QString CaptureFilePropertiesDialog::summaryToHtml()
g_free(iface.descr);
g_free(iface.name);
g_free(iface.cfilter);
}
g_array_free(summary.ifaces, TRUE);
@ -611,40 +589,21 @@ void CaptureFilePropertiesDialog::changeEvent(QEvent* event)
QDialog::changeEvent(event);
}
void CaptureFilePropertiesDialog::addCaptureComment()
{
CaptureCommentDialog* cc_dialog;
cc_dialog = new CaptureCommentDialog(*this, cap_file_);
connect(cc_dialog, &CaptureCommentDialog::captureCommentChanged, this, &CaptureFilePropertiesDialog::updateWidgets);
//cc_dialog->setWindowModality(Qt::ApplicationModal);
cc_dialog->setAttribute(Qt::WA_DeleteOnClose);
cc_dialog->show();
}
void CaptureFilePropertiesDialog::on_buttonBox_helpRequested()
{
mainApp->helpTopicAction(HELP_STATS_SUMMARY_DIALOG);
}
void CaptureFilePropertiesDialog::on_buttonBox_accepted()
{
if (file_closed_ || !cap_file_.capFile()->filename) {
return;
}
if (wtap_dump_can_write(cap_file_.capFile()->linktypes, WTAP_COMMENT_PER_SECTION))
{
gchar *str = qstring_strdup(ui->commentsTextEdit->toPlainText());
/*
* Make sure this would fit in a pcapng option.
*
* XXX - 65535 is the maximum size for an option in pcapng;
* what if another capture file format supports larger
* comments?
*/
if (strlen(str) > 65535) {
/* It doesn't fit. Tell the user and give up. */
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
"That comment is too large to save in a capture file.");
return;
}
cf_update_section_comment(cap_file_.capFile(), str);
emit captureCommentChanged();
fillDetails();
}
}
void CaptureFilePropertiesDialog::on_buttonBox_clicked(QAbstractButton *button)
{
if (button == ui->buttonBox->button(QDialogButtonBox::Apply)) {

View File

@ -62,8 +62,8 @@ private:
private slots:
void updateWidgets();
void addCaptureComment();
void on_buttonBox_helpRequested();
void on_buttonBox_accepted();
void on_buttonBox_clicked(QAbstractButton *button);
void on_buttonBox_rejected();
};

View File

@ -21,65 +21,38 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="opaqueResize">
<bool>false</bool>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="detailsLabel">
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="detailsTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="commentsLabel">
<property name="text">
<string>Capture file comments</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="commentsTextEdit">
<property name="sizeIncrement">
<size>
<width>0</width>
<height>10</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="detailsLabel">
<property name="text">
<string>Details</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="detailsTextEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Reset|QDialogButtonBox::Save</set>
<set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
<widget class="QPushButton" name="actionEditButton">
<property name="text">
<string>Edit Comments</string>
</property>
</widget>
</widget>
<resources/>
<connections/>