wireshark/ui/qt/conversation_dialog.cpp

839 lines
30 KiB
C++

/* conversation_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 "conversation_dialog.h"
#include <epan/prefs.h>
#include <epan/dissectors/packet-tcp.h>
#include "ui/recent.h"
#include "ui/tap-tcp-stream.h"
#include "ui/traffic_table_ui.h"
#include "wsutil/str_util.h"
#include <ui/qt/utils/qt_ui_utils.h>
#include <ui/qt/models/timeline_delegate.h>
#include "wireshark_application.h"
#include <QCheckBox>
#include <QDateTime>
#include <QDialogButtonBox>
#include <QPushButton>
// To do:
// - https://gitlab.com/wireshark/wireshark/-/issues/6727
// - Wide last column?
// + No arrows on unsorted columns
// - Add follow stream to context menu
// + Change "A <- B" to "B -> A"
// - Improper wildcard handling https://gitlab.com/wireshark/wireshark/-/issues/8010
// - TShark consolidation https://gitlab.com/wireshark/wireshark/-/issues/6310
// - Display filter entry?
// - Add follow, copy & graph actions to context menu.
// Bugs:
// - Slow for large numbers of items.
// - Name resolution doesn't do anything if its preference is disabled.
// - Columns don't resize correctly.
// - Closing the capture file clears conversation data.
// Fixed bugs:
// - Friendly unit displays https://gitlab.com/wireshark/wireshark/-/issues/9231
// - Misleading bps calculation https://gitlab.com/wireshark/wireshark/-/issues/8703
// - Show Absolute time in conversation tables https://gitlab.com/wireshark/wireshark/-/issues/11618
// - The value of 'Rel start' and 'Duration' in "Conversations" no need too precise https://gitlab.com/wireshark/wireshark/-/issues/12803
static const QString table_name_ = QObject::tr("Conversation");
ConversationDialog::ConversationDialog(QWidget &parent, CaptureFile &cf, int cli_proto_id, const char *filter) :
TrafficTableDialog(parent, cf, filter, table_name_),
tcp_graph_requested_(false)
{
follow_bt_ = buttonBox()->addButton(tr("Follow Stream…"), QDialogButtonBox::ActionRole);
follow_bt_->setToolTip(tr("Follow a TCP or UDP stream."));
connect(follow_bt_, SIGNAL(clicked()), this, SLOT(followStream()));
graph_bt_ = buttonBox()->addButton(tr("Graph…"), QDialogButtonBox::ActionRole);
graph_bt_->setToolTip(tr("Graph a TCP conversation."));
connect(graph_bt_, SIGNAL(clicked()), this, SLOT(graphTcp()));
connect(wsApp->mainWindow(), SIGNAL(displayFilterSuccess(bool)),
this, SLOT(displayFilterSuccess(bool)));
absoluteTimeCheckBox()->show();
addProgressFrame(&parent);
QList<int> conv_protos;
for (GList *conv_tab = recent.conversation_tabs; conv_tab; conv_tab = conv_tab->next) {
int proto_id = proto_get_id_by_short_name((const char *)conv_tab->data);
if (proto_id > -1 && !conv_protos.contains(proto_id)) {
conv_protos.append(proto_id);
}
}
if (conv_protos.isEmpty()) {
conv_protos = defaultProtos();
}
// Bring the command-line specified type to the front.
if ((cli_proto_id > 0) && (get_conversation_by_proto_id(cli_proto_id))) {
conv_protos.removeAll(cli_proto_id);
conv_protos.prepend(cli_proto_id);
}
// QTabWidget selects the first item by default.
foreach (int conv_proto, conv_protos) {
addTrafficTable(get_conversation_by_proto_id(conv_proto));
}
fillTypeMenu(conv_protos);
QPushButton *close_bt = buttonBox()->button(QDialogButtonBox::Close);
if (close_bt) {
close_bt->setDefault(true);
}
updateWidgets();
// currentTabChanged();
cap_file_.delayedRetapPackets();
}
ConversationDialog::~ConversationDialog()
{
prefs_clear_string_list(recent.conversation_tabs);
recent.conversation_tabs = NULL;
ConversationTreeWidget *cur_tree = qobject_cast<ConversationTreeWidget *>(trafficTableTabWidget()->currentWidget());
foreach (QAction *ca, traffic_type_menu_.actions()) {
int proto_id = ca->data().value<int>();
if (proto_id_to_tree_.contains(proto_id) && ca->isChecked()) {
char *title = g_strdup(proto_get_protocol_short_name(find_protocol_by_id(proto_id)));
if (proto_id_to_tree_[proto_id] == cur_tree) {
recent.conversation_tabs = g_list_prepend(recent.conversation_tabs, title);
} else {
recent.conversation_tabs = g_list_append(recent.conversation_tabs, title);
}
}
}
}
void ConversationDialog::captureFileClosing()
{
// Keep the dialog around but disable any controls that depend
// on a live capture file.
for (int i = 0; i < trafficTableTabWidget()->count(); i++) {
ConversationTreeWidget *cur_tree = qobject_cast<ConversationTreeWidget *>(trafficTableTabWidget()->widget(i));
disconnect(cur_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
}
displayFilterCheckBox()->setEnabled(false);
enabledTypesPushButton()->setEnabled(false);
follow_bt_->setEnabled(false);
graph_bt_->setEnabled(false);
TrafficTableDialog::captureFileClosing();
}
bool ConversationDialog::addTrafficTable(register_ct_t* table)
{
int proto_id = get_conversation_proto_id(table);
if (!table || proto_id_to_tree_.contains(proto_id)) {
return false;
}
ConversationTreeWidget *conv_tree = new ConversationTreeWidget(this, table);
proto_id_to_tree_[proto_id] = conv_tree;
const char* table_name = proto_get_protocol_short_name(find_protocol_by_id(proto_id));
trafficTableTabWidget()->addTab(conv_tree, table_name);
conv_tree->setItemDelegateForColumn(CONV_COLUMN_START, new TimelineDelegate(conv_tree));
conv_tree->setItemDelegateForColumn(CONV_COLUMN_DURATION, new TimelineDelegate(conv_tree));
connect(conv_tree, SIGNAL(itemSelectionChanged()),
this, SLOT(conversationSelectionChanged()));
connect(conv_tree, SIGNAL(titleChanged(QWidget*,QString)),
this, SLOT(setTabText(QWidget*,QString)));
connect(conv_tree, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)),
this, SIGNAL(filterAction(QString,FilterAction::Action,FilterAction::ActionType)));
connect(nameResolutionCheckBox(), SIGNAL(toggled(bool)),
conv_tree, SLOT(setNameResolutionEnabled(bool)));
connect(absoluteTimeCheckBox(), SIGNAL(toggled(bool)),
conv_tree, SLOT(updateStartTime(bool)));
// XXX Move to ConversationTreeWidget ctor?
QByteArray filter_utf8;
const char *filter = NULL;
if (displayFilterCheckBox()->isChecked()) {
filter = cap_file_.capFile()->dfilter;
} else if (!filter_.isEmpty()) {
filter_utf8 = filter_.toUtf8();
filter = filter_utf8.constData();
}
conv_tree->trafficTreeHash()->user_data = conv_tree;
registerTapListener(proto_get_protocol_filter_name(proto_id), conv_tree->trafficTreeHash(), filter, 0,
ConversationTreeWidget::tapReset,
get_conversation_packet_func(table),
ConversationTreeWidget::tapDraw);
return true;
}
void ConversationDialog::followStream()
{
if (file_closed_) {
return;
}
conv_item_t *conv_item = currentConversation();
if (!conv_item) {
return;
}
follow_type_t ftype;
switch (conv_item->etype) {
case ENDPOINT_TCP:
ftype = FOLLOW_TCP;
break;
case ENDPOINT_UDP:
ftype = FOLLOW_UDP;
break;
default:
return;
}
// Will set the display filter too.
// TCP and UDP do not have a "sub-stream", so set a dummy value.
emit openFollowStreamDialog(ftype, conv_item->conv_id, 0);
}
void ConversationDialog::graphTcp()
{
if (file_closed_) {
return;
}
conv_item_t *conv_item = currentConversation();
if (!conv_item) {
return;
}
// XXX The GTK+ code opens the TCP Stream dialog. We might want
// to open the I/O Graphs dialog instead.
QString filter;
if (conv_item->etype == ENDPOINT_TCP) {
filter = QString("tcp.stream eq %1").arg(conv_item->conv_id);
} else {
return;
}
tcp_graph_requested_ = true;
// Apply the filter for this conversation. When the filter is active, we
// can draw the TCP graph.
emit filterAction(filter, FilterAction::ActionApply, FilterAction::ActionTypePlain);
}
void ConversationDialog::currentTabChanged()
{
bool copy_enable = trafficTableTabWidget()->currentWidget() ? true : false;
copy_bt_->setEnabled(copy_enable);
conversationSelectionChanged();
TrafficTableDialog::currentTabChanged();
}
void ConversationDialog::conversationSelectionChanged()
{
bool follow_enable = false, graph_enable = false;
conv_item_t *conv_item = currentConversation();
if (!file_closed_ && conv_item) {
switch (conv_item->etype) {
case ENDPOINT_TCP:
graph_enable = true;
// Fall through
case ENDPOINT_UDP:
follow_enable = true;
break;
default:
break;
}
}
follow_bt_->setEnabled(follow_enable);
graph_bt_->setEnabled(graph_enable);
}
void ConversationDialog::on_displayFilterCheckBox_toggled(bool checked)
{
if (file_closed_) {
return;
}
QByteArray filter_utf8;
const char *filter = NULL;
if (checked) {
filter = cap_file_.capFile()->dfilter;
} else if (!filter_.isEmpty()) {
filter_utf8 = filter_.toUtf8();
filter = filter_utf8.constData();
}
for (int i = 0; i < trafficTableTabWidget()->count(); i++) {
set_tap_dfilter(trafficTableTabWidget()->widget(i), filter);
}
cap_file_.retapPackets();
}
void ConversationDialog::on_buttonBox_helpRequested()
{
wsApp->helpTopicAction(HELP_STATS_CONVERSATIONS_DIALOG);
}
void ConversationDialog::displayFilterSuccess(bool success)
{
if (tcp_graph_requested_) {
if (success) {
// The display filter was applied successfully, i.e. the current
// packet is now part of our selected tcp conversation.
openTcpStreamGraph(GRAPH_TSEQ_TCPTRACE);
}
tcp_graph_requested_ = false;
}
}
void init_conversation_table(struct register_ct* ct, const char *filter)
{
wsApp->emitStatCommandSignal("Conversations", filter, GINT_TO_POINTER(get_conversation_proto_id(ct)));
}
// ConversationTreeWidgetItem
// TrafficTableTreeWidgetItem / QTreeWidgetItem subclass that allows sorting
// Minimum bandwidth calculation duration
// https://gitlab.com/wireshark/wireshark/-/issues/8703
static const double min_bw_calc_duration_ = 5 / 1000.0; // seconds
static const char *bps_na_ = UTF8_EM_DASH;
class ConversationTreeWidgetItem : public TrafficTableTreeWidgetItem
{
public:
ConversationTreeWidgetItem(GArray *conv_array, guint conv_idx, bool *resolve_names_ptr) :
TrafficTableTreeWidgetItem(NULL),
conv_array_(conv_array),
conv_idx_(conv_idx),
resolve_names_ptr_(resolve_names_ptr)
{
QString timeline_tt = QObject::tr("Bars show the relative timeline for each conversation.");
setToolTip(CONV_COLUMN_START, timeline_tt);
setToolTip(CONV_COLUMN_DURATION, timeline_tt);
}
conv_item_t *convItem() {
return &g_array_index(conv_array_, conv_item_t, conv_idx_);
}
virtual QVariant data(int column, int role) const {
switch (role) {
case Qt::DisplayRole:
{
// Column text cooked representation.
conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_);
bool resolve_names = false;
if (resolve_names_ptr_ && *resolve_names_ptr_) resolve_names = true;
double duration = nstime_to_sec(&conv_item->stop_time) - nstime_to_sec(&conv_item->start_time);
QString bps_ab = bps_na_, bps_ba = bps_na_;
switch (column) {
case CONV_COLUMN_PACKETS:
return QString("%L1").arg(conv_item->tx_frames + conv_item->rx_frames);
case CONV_COLUMN_BYTES:
return gchar_free_to_qstring(format_size(conv_item->tx_bytes + conv_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
case CONV_COLUMN_PKT_AB:
return QString("%L1").arg(conv_item->tx_frames);
case CONV_COLUMN_BYTES_AB:
return gchar_free_to_qstring(format_size(conv_item->tx_bytes, format_size_unit_none|format_size_prefix_si));
case CONV_COLUMN_PKT_BA:
return QString("%L1").arg(conv_item->rx_frames);
case CONV_COLUMN_BYTES_BA:
return gchar_free_to_qstring(format_size(conv_item->rx_bytes, format_size_unit_none|format_size_prefix_si));
case CONV_COLUMN_START:
{
bool use_ns = treeWidget()->window()->property("nanosecond_precision").toBool();
int width = use_ns ? 9 : 6;
if (treeWidget()->window()->property("absolute_start_time").toBool()) {
nstime_t *abs_time = &conv_item->start_abs_time;
QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_time));
return QString("%1.%2")
// Mimic column-utils:set_abs_time as best we can
.arg(abs_dt.toString("hh:mm:ss"))
.arg(use_ns ? abs_time->nsecs : abs_time->nsecs / 1000, width, 10, QChar('0'));
}
return QString::number(nstime_to_sec(&conv_item->start_time), 'f', width);
}
case CONV_COLUMN_DURATION:
{
// The GTK+ UI uses 9 digit precision for the start time and 4 for the duration.
// Do the same here and above for non-nanosecond precision and add a couple
// of digits for nanosecond precision.
bool use_ns = treeWidget()->window()->property("nanosecond_precision").toBool();
int width = use_ns ? 6 : 4;
return QString::number(duration, 'f', width);
}
case CONV_COLUMN_BPS_AB:
if (duration > min_bw_calc_duration_) {
bps_ab = gchar_free_to_qstring(format_size((gint64) conv_item->tx_bytes * 8 / duration, format_size_unit_none|format_size_prefix_si));
}
return bps_ab;
case CONV_COLUMN_BPS_BA:
if (duration > min_bw_calc_duration_) {
bps_ba = gchar_free_to_qstring(format_size((gint64) conv_item->rx_bytes * 8 / duration, format_size_unit_none|format_size_prefix_si));
}
return bps_ba;
default:
return colData(column, resolve_names).toString();
}
break;
}
case Qt::UserRole:
{
if (column != CONV_COLUMN_START && column != CONV_COLUMN_DURATION) break;
ConversationTreeWidget *ctw = qobject_cast<ConversationTreeWidget *>(treeWidget());
if (!ctw) break;
conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_);
double start_time = nstime_to_sec(&conv_item->start_time);
double stop_time = nstime_to_sec(&conv_item->stop_time);
double span_s = ctw->maxRelStopTime() - ctw->minRelStartTime();
if (span_s <= 0) break;
int start_px = ctw->columnWidth(CONV_COLUMN_START);
int column_px = start_px + ctw->columnWidth(CONV_COLUMN_DURATION);
struct timeline_span span_px;
span_px.start = ((start_time - ctw->minRelStartTime()) * column_px) / span_s;
span_px.width = ((stop_time - start_time) * column_px) / span_s;
if (column == CONV_COLUMN_DURATION) {
span_px.start -= start_px;
}
return QVariant::fromValue(span_px);
break;
}
default:
break;
}
return QTreeWidgetItem::data(column, role);
}
// Column text raw representation.
// Return a QString, qulonglong, double, or invalid QVariant representing the raw column data.
QVariant colData(int col, bool resolve_names) const {
conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_);
double duration = nstime_to_sec(&conv_item->stop_time) - nstime_to_sec(&conv_item->start_time);
double bps_ab = 0, bps_ba = 0;
if (duration > min_bw_calc_duration_) {
bps_ab = conv_item->tx_bytes * 8 / duration;
bps_ba = conv_item->rx_bytes * 8 / duration;
}
switch (col) {
case CONV_COLUMN_SRC_ADDR:
{
char* addr_str = get_conversation_address(NULL, &conv_item->src_address, resolve_names);
QString q_addr_str(addr_str);
wmem_free(NULL, addr_str);
return q_addr_str;
}
case CONV_COLUMN_SRC_PORT:
if (resolve_names) {
char* port_str = get_conversation_port(NULL, conv_item->src_port, conv_item->etype, resolve_names);
QString q_port_str(port_str);
wmem_free(NULL, port_str);
return q_port_str;
} else {
return quint32(conv_item->src_port);
}
case CONV_COLUMN_DST_ADDR:
{
char* addr_str = get_conversation_address(NULL, &conv_item->dst_address, resolve_names);
QString q_addr_str(addr_str);
wmem_free(NULL, addr_str);
return q_addr_str;
}
case CONV_COLUMN_DST_PORT:
if (resolve_names) {
char* port_str = get_conversation_port(NULL, conv_item->dst_port, conv_item->etype, resolve_names);
QString q_port_str(port_str);
wmem_free(NULL, port_str);
return q_port_str;
} else {
return quint32(conv_item->dst_port);
}
case CONV_COLUMN_PACKETS:
return quint64(conv_item->tx_frames + conv_item->rx_frames);
case CONV_COLUMN_BYTES:
return quint64(conv_item->tx_bytes + conv_item->rx_bytes);
case CONV_COLUMN_PKT_AB:
return quint64(conv_item->tx_frames);
case CONV_COLUMN_BYTES_AB:
return quint64(conv_item->tx_bytes);
case CONV_COLUMN_PKT_BA:
return quint64(conv_item->rx_frames);
case CONV_COLUMN_BYTES_BA:
return quint64(conv_item->rx_bytes);
case CONV_COLUMN_START:
return nstime_to_sec(&conv_item->start_time);
case CONV_COLUMN_DURATION:
return duration;
case CONV_COLUMN_BPS_AB:
return bps_ab;
case CONV_COLUMN_BPS_BA:
return bps_ba;
default:
return QVariant();
}
}
bool operator< (const QTreeWidgetItem &other) const
{
const ConversationTreeWidgetItem *other_row = static_cast<const ConversationTreeWidgetItem *>(&other);
conv_item_t *conv_item = &g_array_index(conv_array_, conv_item_t, conv_idx_);
conv_item_t *other_item = &g_array_index(other_row->conv_array_, conv_item_t, other_row->conv_idx_);
int sort_col = treeWidget()->sortColumn();
double conv_duration = nstime_to_sec(&conv_item->stop_time) - nstime_to_sec(&conv_item->start_time);
double other_duration = nstime_to_sec(&other_item->stop_time) - nstime_to_sec(&other_item->start_time);
switch(sort_col) {
case CONV_COLUMN_SRC_ADDR:
return cmp_address(&conv_item->src_address, &other_item->src_address) < 0 ? true : false;
case CONV_COLUMN_SRC_PORT:
return conv_item->src_port < other_item->src_port;
case CONV_COLUMN_DST_ADDR:
return cmp_address(&conv_item->dst_address, &other_item->dst_address) < 0 ? true : false;
case CONV_COLUMN_DST_PORT:
return conv_item->dst_port < other_item->dst_port;
case CONV_COLUMN_PACKETS:
return (conv_item->tx_frames + conv_item->rx_frames) < (other_item->tx_frames + other_item->rx_frames);
case CONV_COLUMN_BYTES:
return (conv_item->tx_bytes + conv_item->rx_bytes) < (other_item->tx_bytes + other_item->rx_bytes);
case CONV_COLUMN_PKT_AB:
return conv_item->tx_frames < other_item->tx_frames;
case CONV_COLUMN_BYTES_AB:
return conv_item->tx_bytes < other_item->tx_bytes;
case CONV_COLUMN_PKT_BA:
return conv_item->rx_frames < other_item->rx_frames;
case CONV_COLUMN_BYTES_BA:
return conv_item->rx_bytes < other_item->rx_bytes;
case CONV_COLUMN_START:
return nstime_to_sec(&conv_item->start_time) < nstime_to_sec(&other_item->start_time);
case CONV_COLUMN_DURATION:
return conv_duration < other_duration;
case CONV_COLUMN_BPS_AB:
return conv_item->tx_bytes / conv_duration < other_item->tx_bytes / other_duration;
case CONV_COLUMN_BPS_BA:
return conv_item->rx_bytes / conv_duration < other_item->rx_bytes / other_duration;
default:
return false;
}
}
private:
GArray *conv_array_;
guint conv_idx_;
bool *resolve_names_ptr_;
};
conv_item_t *ConversationDialog::currentConversation()
{
ConversationTreeWidget *cur_tree = qobject_cast<ConversationTreeWidget *>(trafficTableTabWidget()->currentWidget());
if (!cur_tree || cur_tree->selectedItems().count() < 1) {
return NULL;
}
ConversationTreeWidgetItem *ctwi = dynamic_cast<ConversationTreeWidgetItem *>(cur_tree->selectedItems()[0]);
return (ctwi ? ctwi->convItem() : NULL);
}
// ConversationTreeWidget
// TrafficTableTreeWidget / QTreeWidget subclass that allows tapping
ConversationTreeWidget::ConversationTreeWidget(QWidget *parent, register_ct_t* table) :
TrafficTableTreeWidget(parent, table),
min_rel_start_time_(0),
max_rel_stop_time_(0)
{
setColumnCount(CONV_NUM_COLUMNS);
setUniformRowHeights(true);
for (int i = 0; i < CONV_NUM_COLUMNS; i++) {
headerItem()->setText(i, conv_column_titles[i]);
}
if (get_conversation_hide_ports(table_)) {
hideColumn(CONV_COLUMN_SRC_PORT);
hideColumn(CONV_COLUMN_DST_PORT);
} else if (!strcmp(proto_get_protocol_filter_name(get_conversation_proto_id(table_)), "ncp")) {
headerItem()->setText(CONV_COLUMN_SRC_PORT, conv_conn_a_title);
headerItem()->setText(CONV_COLUMN_DST_PORT, conv_conn_b_title);
}
int one_en = fontMetrics().height() / 2;
for (int i = 0; i < CONV_NUM_COLUMNS; i++) {
switch (i) {
case CONV_COLUMN_SRC_ADDR:
case CONV_COLUMN_DST_ADDR:
setColumnWidth(i, one_en * (int) strlen("000.000.000.000"));
break;
case CONV_COLUMN_SRC_PORT:
case CONV_COLUMN_DST_PORT:
setColumnWidth(i, one_en * (int) strlen("000000"));
break;
case CONV_COLUMN_PACKETS:
case CONV_COLUMN_PKT_AB:
case CONV_COLUMN_PKT_BA:
setColumnWidth(i, one_en * (int) strlen("00,000"));
break;
case CONV_COLUMN_BYTES:
case CONV_COLUMN_BYTES_AB:
case CONV_COLUMN_BYTES_BA:
setColumnWidth(i, one_en * (int) strlen("000,000"));
break;
case CONV_COLUMN_START:
setColumnWidth(i, one_en * (int) strlen("00.000"));
break;
case CONV_COLUMN_DURATION:
setColumnWidth(i, one_en * (int) strlen("00.000000"));
break;
case CONV_COLUMN_BPS_AB:
case CONV_COLUMN_BPS_BA:
setColumnWidth(i, one_en * (int) strlen("000 k"));
break;
default:
setColumnWidth(i, one_en * 5);
}
}
QMenu *submenu;
initDirectionMap();
FilterAction::Action cur_action = FilterAction::ActionApply;
submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
QMenu *subsubmenu = submenu->addMenu(FilterAction::actionTypeName(at));
foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) {
FilterAction *fa = new FilterAction(subsubmenu, cur_action, at, ad);
subsubmenu->addAction(fa);
connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
}
}
cur_action = FilterAction::ActionPrepare;
submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
foreach (FilterAction::ActionType at, FilterAction::actionTypes()) {
QMenu *subsubmenu = submenu->addMenu(FilterAction::actionTypeName(at));
foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) {
FilterAction *fa = new FilterAction(subsubmenu, cur_action, at, ad);
subsubmenu->addAction(fa);
connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
}
}
cur_action = FilterAction::ActionFind;
submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) {
FilterAction *fa = new FilterAction(submenu, cur_action, FilterAction::ActionTypePlain, ad);
submenu->addAction(fa);
connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
}
cur_action = FilterAction::ActionColorize;
submenu = ctx_menu_.addMenu(FilterAction::actionName(cur_action));
foreach (FilterAction::ActionDirection ad, FilterAction::actionDirections()) {
FilterAction *fa = new FilterAction(submenu, cur_action, FilterAction::ActionTypePlain, ad);
submenu->addAction(fa);
connect(fa, SIGNAL(triggered()), this, SLOT(filterActionTriggered()));
}
updateItems();
}
ConversationTreeWidget::~ConversationTreeWidget() {
reset_conversation_table_data(&hash_);
}
// Callbacks for register_tap_listener
void ConversationTreeWidget::tapReset(void *conv_hash_ptr)
{
conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
ConversationTreeWidget *conv_tree = qobject_cast<ConversationTreeWidget *>((ConversationTreeWidget *)hash->user_data);
if (!conv_tree) return;
conv_tree->clear();
reset_conversation_table_data(&conv_tree->hash_);
conv_tree->min_rel_start_time_ = 0;
conv_tree->max_rel_stop_time_ = 0;
}
void ConversationTreeWidget::tapDraw(void *conv_hash_ptr)
{
conv_hash_t *hash = (conv_hash_t*)conv_hash_ptr;
ConversationTreeWidget *conv_tree = qobject_cast<ConversationTreeWidget *>((ConversationTreeWidget *)hash->user_data);
if (!conv_tree) return;
conv_tree->updateItems();
}
void ConversationTreeWidget::updateStartTime(bool absolute)
{
headerItem()->setText(CONV_COLUMN_START, absolute
? conv_abs_start_title
: conv_column_titles[CONV_COLUMN_START]);
dataChanged(QModelIndex(), QModelIndex());
if (topLevelItemCount() > 0) {
resizeColumnToContents(CONV_COLUMN_START);
}
}
QMap<FilterAction::ActionDirection, conv_direction_e> fad_to_cd_;
void ConversationTreeWidget::initDirectionMap()
{
if (fad_to_cd_.size() > 0) {
return;
}
fad_to_cd_[FilterAction::ActionDirectionAToFromB] = CONV_DIR_A_TO_FROM_B;
fad_to_cd_[FilterAction::ActionDirectionAToB] = CONV_DIR_A_TO_B;
fad_to_cd_[FilterAction::ActionDirectionAFromB] = CONV_DIR_A_FROM_B;
fad_to_cd_[FilterAction::ActionDirectionAToFromAny] = CONV_DIR_A_TO_FROM_ANY;
fad_to_cd_[FilterAction::ActionDirectionAToAny] = CONV_DIR_A_TO_ANY;
fad_to_cd_[FilterAction::ActionDirectionAFromAny] = CONV_DIR_A_FROM_ANY;
fad_to_cd_[FilterAction::ActionDirectionAnyToFromB] = CONV_DIR_ANY_TO_FROM_B;
fad_to_cd_[FilterAction::ActionDirectionAnyToB] = CONV_DIR_ANY_TO_B;
fad_to_cd_[FilterAction::ActionDirectionAnyFromB] = CONV_DIR_ANY_FROM_B;
}
void ConversationTreeWidget::updateItems() {
bool resize = topLevelItemCount() < resizeThreshold();
title_ = proto_get_protocol_short_name(find_protocol_by_id(get_conversation_proto_id(table_)));
if (hash_.conv_array && hash_.conv_array->len > 0) {
title_.append(QString(" %1 %2").arg(UTF8_MIDDLE_DOT).arg(hash_.conv_array->len));
}
emit titleChanged(this, title_);
if (!hash_.conv_array) {
return;
}
setSortingEnabled(false);
QList<QTreeWidgetItem *>new_items;
for (int i = topLevelItemCount(); i < (int) hash_.conv_array->len; i++) {
ConversationTreeWidgetItem *ctwi = new ConversationTreeWidgetItem(hash_.conv_array, i, &resolve_names_);
new_items << ctwi;
if (i == 0) {
min_rel_start_time_ = nstime_to_sec(&(ctwi->convItem()->start_time));
max_rel_stop_time_ = nstime_to_sec(&(ctwi->convItem()->stop_time));
}
for (int col = 0; col < columnCount(); col++) {
switch (col) {
case CONV_COLUMN_SRC_ADDR:
case CONV_COLUMN_DST_ADDR:
break;
default:
ctwi->setTextAlignment(col, Qt::AlignRight);
break;
}
}
}
addTopLevelItems(new_items);
for (int i = 0; i < topLevelItemCount(); i++) {
ConversationTreeWidgetItem *ctwi = dynamic_cast<ConversationTreeWidgetItem *>(topLevelItem(i));
if (ctwi) {
double item_rel_start = nstime_to_sec(&(ctwi->convItem()->start_time));
if (item_rel_start < min_rel_start_time_) {
min_rel_start_time_ = item_rel_start;
}
double item_rel_stop = nstime_to_sec(&(ctwi->convItem()->stop_time));
if (item_rel_stop > max_rel_stop_time_) {
max_rel_stop_time_ = item_rel_stop;
}
}
}
setSortingEnabled(true);
if (resize) {
for (int col = 0; col < columnCount(); col++) {
resizeColumnToContents(col);
}
}
}
void ConversationTreeWidget::filterActionTriggered()
{
ConversationTreeWidgetItem *ctwi = static_cast<ConversationTreeWidgetItem *>(currentItem());
FilterAction *fa = qobject_cast<FilterAction *>(QObject::sender());
if (!fa || !ctwi) {
return;
}
conv_item_t *conv_item = ctwi->convItem();
if (!conv_item) {
return;
}
char* tmp_str = get_conversation_filter(conv_item, fad_to_cd_[fa->actionDirection()]);
QString filter(tmp_str);
g_free(tmp_str);
emit filterAction(filter, fa->action(), fa->actionType());
}
/*
* Editor modelines
*
* Local Variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* ex: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/