From 006f84a565f2581e4e1e9aefecceac64658466e8 Mon Sep 17 00:00:00 2001 From: Alexis La Goutte Date: Sun, 8 Dec 2013 11:09:54 +0000 Subject: [PATCH] From Deon van der Westhuysen via https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=9452 Patch: Stats_tree enhancements for sorting, averages and burst rate Add sort, save-as and new columns to Qt ui, remove old functions stats_tree.c / stats_tree_priv.h: Make all columns sortable. Remove unneeded functions stats_tree_get_strs_from_node, stats_tree_branch_to_str and stats_tree_is_sortable_column. stats_tree_stat.c: Set all columns sortable. stats_tree_dialog.cpp / stats_tree_dialog.h: Add new stats_tree columns. Make columns sortable. Remove copy to csv and copy to yaml buttons. Add copy to clipboard as plain text and save as buttons. stats_tree_dialog.ui: Remove copy to csv and copy to yaml buttons. Add copy to clipboard as plain text and save as buttons. Only define one column in ui, rest are added dynmically. From me : fix trailing whitespace svn path=/trunk/; revision=53848 --- epan/stats_tree.c | 98 ++--------------- epan/stats_tree_priv.h | 21 +--- ui/gtk/stats_tree_stat.c | 6 +- ui/qt/stats_tree_dialog.cpp | 205 +++++++++++++++++++++++------------- ui/qt/stats_tree_dialog.h | 5 +- ui/qt/stats_tree_dialog.ui | 29 ++--- 6 files changed, 154 insertions(+), 210 deletions(-) diff --git a/epan/stats_tree.c b/epan/stats_tree.c index 0d4f21382d..48bdfc9812 100644 --- a/epan/stats_tree.c +++ b/epan/stats_tree.c @@ -62,34 +62,6 @@ enum _stat_tree_columns { /* used to contain the registered stat trees */ static GHashTable *registry = NULL; -/* writes into the buffers pointed by value, rate and percent - the string representations of a node*/ -/*** DEPRECIATED ***/ -extern void -stats_tree_get_strs_from_node(const stat_node *node, gchar *value, gchar *rate, gchar *percent) -{ - float f; - - if (value) g_snprintf(value,NUM_BUF_SIZE,"%u",node->counter); - - if (rate) { - *rate = '\0'; - if (node->st->elapsed > 0.0) { - f = ((float)node->counter) / (float)node->st->elapsed; - g_snprintf(rate,NUM_BUF_SIZE,"%f",f); - } - } - - if (percent) { - *percent = '\0'; - if (node->parent->counter > 0) { - f = (float)(((float)node->counter * 100.0) / node->parent->counter); - g_snprintf(percent,NUM_BUF_SIZE,"%.2f%%",f); - } - } -} - - /* a text representation of a node if buffer is NULL returns a newly allocated string */ extern gchar* @@ -132,53 +104,6 @@ stats_tree_branch_max_namelen(const stat_node *node, guint indent) return maxlen; } -static gchar *format; - -/* populates the given GString with a tree representation of a branch given by node, -using indent spaces as initial indentation */ -/*** DEPRECIATED ***/ -extern void -stats_tree_branch_to_str(const stat_node *node, GString *s, guint indent) -{ - stat_node *child; - static gchar indentation[INDENT_MAX+1]; - static gchar value[NUM_BUF_SIZE]; - static gchar rate[NUM_BUF_SIZE]; - static gchar percent[NUM_BUF_SIZE]; - - guint i = 0; - - if (indent == 0) { - format = g_strdup_printf(" %%s%%-%us%%12s %%12s %%12s\n",stats_tree_branch_max_namelen(node,0)); - } - - stats_tree_get_strs_from_node(node, value, rate, percent); - - indent = indent > INDENT_MAX ? INDENT_MAX : indent; - - /* fill indentation with indent spaces */ - if (indent > 0) { - while(iname,value,rate,percent); - - if (node->children) { - for (child = node->children; child; child = child->next ) { - stats_tree_branch_to_str(child,s,indent+1); - } - } - - if (indent == 0) { - g_free(format); - } -} - - /* frees the resources allocated by a stat_tree node */ static void free_stat_node(stat_node *node) @@ -567,7 +492,7 @@ new_stat_node(stats_tree *st, const gchar *name, int parent_id, node->bt = node->bh; node->bcount = 0; node->max_burst = 0; - node->burst_time = -1; + node->burst_time = -1.0; node->name = g_strdup(name); node->children = NULL; @@ -1106,20 +1031,6 @@ stats_tree_get_column_size (gint col_index) return 0; /* invalid column */ } -extern gboolean -stats_tree_is_sortable_column (gint col_index) -{ - switch (col_index) { - case COL_NAME: - case COL_COUNT: - case COL_AVERAGE: - case COL_MIN: - case COL_MAX: - case COL_BURSTRATE: return TRUE; - default: return FALSE; - } -} - extern gchar** stats_tree_get_values_from_node (const stat_node* node) { @@ -1181,6 +1092,8 @@ stats_tree_sort_compare (const stat_node *a, const stat_node *b, gint sort_colum } break; + case COL_RATE: + case COL_PERCENT: case COL_COUNT: result = a->counter - b->counter; break; @@ -1206,8 +1119,11 @@ stats_tree_sort_compare (const stat_node *a, const stat_node *b, gint sort_colum case COL_BURSTRATE: result = a->max_burst - b->max_burst; break; + case COL_BURSTTIME: result = (a->burst_time>b->burst_time)?1:((a->burst_timeburst_time)?-1:0); + break; + default: - /* see stats_tree_is_sortable_column */ + /* no sort comparison found for column - must update this switch statement */ g_assert_not_reached(); } diff --git a/epan/stats_tree_priv.h b/epan/stats_tree_priv.h index 9a89ac9c6d..bc79d4df59 100644 --- a/epan/stats_tree_priv.h +++ b/epan/stats_tree_priv.h @@ -219,29 +219,13 @@ WS_DLL_PUBLIC stats_tree_cfg *stats_tree_get_cfg_by_abbr(const char *abbr); caller should free returned list with g_list_free() */ WS_DLL_PUBLIC GList *stats_tree_get_cfg_list(void); -/** extracts node data as strings from a stat_node into - the buffers given by value, rate and precent - if NULL they are ignored - - DO NOT USE FOR NEW CODE. Use stats_tree_get_values_from_node() instead */ -WS_DLL_PUBLIC void stats_tree_get_strs_from_node(const stat_node *node, - gchar *value, - gchar *rate, - gchar *percent); - -/** populates the given GString with a tree representation of a branch given by node, - using indent spaces as indentation */ -WS_DLL_PUBLIC void stats_tree_branch_to_str(const stat_node *node, - GString *s, - guint indent); - /** used to calcuate the size of the indentation and the longest string */ WS_DLL_PUBLIC guint stats_tree_branch_max_namelen(const stat_node *node, guint indent); /** a text representation of a node, if buffer is NULL returns a newly allocated string */ WS_DLL_PUBLIC gchar *stats_tree_node_to_str(const stat_node *node, - gchar *buffer, guint len); + gchar *buffer, guint len); /** get the display name for the stats_tree (or node name) based on the st_sort_showfullname preference. If not set remove everything before @@ -260,9 +244,6 @@ WS_DLL_PUBLIC const gchar* stats_tree_get_column_name (gint index); /** returns the maximum number of characters in the value of a column */ WS_DLL_PUBLIC gint stats_tree_get_column_size (gint index); -/** returns TRUE is the the column name for a given column index can be sorted*/ -WS_DLL_PUBLIC gboolean stats_tree_is_sortable_column (gint index); - /** returns the formatted column values for the current node as array of gchar*. Caller must free entries and free array */ WS_DLL_PUBLIC gchar** stats_tree_get_values_from_node (const stat_node* node); diff --git a/ui/gtk/stats_tree_stat.c b/ui/gtk/stats_tree_stat.c index bbb8018fc9..1b5d610288 100644 --- a/ui/gtk/stats_tree_stat.c +++ b/ui/gtk/stats_tree_stat.c @@ -489,10 +489,8 @@ init_gtk_tree(const char* opt_arg, void *userdata _U_) renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (stats_tree_get_column_name(count), renderer, "text", count+N_RESERVED_COL, NULL); - if (stats_tree_is_sortable_column(count)) { - gtk_tree_view_column_set_sort_column_id(column, count+N_RESERVED_COL); - gtk_tree_sortable_set_sort_func(sortable,count+N_RESERVED_COL, st_sort_func, sortable, NULL); - } + gtk_tree_view_column_set_sort_column_id(column, count+N_RESERVED_COL); + gtk_tree_sortable_set_sort_func(sortable,count+N_RESERVED_COL, st_sort_func, sortable, NULL); gtk_tree_view_column_set_resizable (column,TRUE); gtk_tree_view_column_set_sizing(column,GTK_TREE_VIEW_COLUMN_AUTOSIZE); gtk_tree_view_append_column (GTK_TREE_VIEW (st->pr->tree), column); diff --git a/ui/qt/stats_tree_dialog.cpp b/ui/qt/stats_tree_dialog.cpp index 52564dea2c..0bb6691952 100644 --- a/ui/qt/stats_tree_dialog.cpp +++ b/ui/qt/stats_tree_dialog.cpp @@ -27,6 +27,8 @@ #include "file.h" #include "epan/stats_tree_priv.h" +#include "wsutil/file_util.h" +#include "ui/last_open_dir.h" #include "wireshark_application.h" @@ -34,6 +36,7 @@ #include #include #include +#include // The GTK+ counterpart uses tap_param_dlg, which we don't use. If we // need tap parameters we should probably create a TapParameterDialog @@ -45,12 +48,29 @@ #include const int item_col_ = 0; -const int count_col_ = 1; -const int rate_col_ = 2; -const int percent_col_ = 3; Q_DECLARE_METATYPE(stat_node *); +class StatsTreeWidgetItem : public QTreeWidgetItem +{ +public: + StatsTreeWidgetItem(int type = Type) : QTreeWidgetItem (type) {} + bool operator< (const QTreeWidgetItem &other) const + { + stat_node *thisnode = data(item_col_, Qt::UserRole).value(); + stat_node *othernode = other.data(item_col_, Qt::UserRole).value(); + Qt::SortOrder order = treeWidget()->header()->sortIndicatorOrder(); + int result; + + result = stats_tree_sort_compare(thisnode, othernode, treeWidget()->sortColumn(), + order==Qt::DescendingOrder); + if (order==Qt::DescendingOrder) { + result= -result; + } + return result < 0; + } +}; + StatsTreeDialog::StatsTreeDialog(QWidget *parent, capture_file *cf, const char *cfg_abbr) : QDialog(parent), ui(new Ui::StatsTreeDialog), @@ -67,16 +87,16 @@ StatsTreeDialog::StatsTreeDialog(QWidget *parent, capture_file *cf, const char * QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection); } - ui->statsTreeWidget->addAction(ui->actionCopyAsCSV); - ui->statsTreeWidget->addAction(ui->actionCopyAsYAML); + ui->statsTreeWidget->addAction(ui->actionCopyToClipboard); + ui->statsTreeWidget->addAction(ui->actionSaveAs); ui->statsTreeWidget->setContextMenuPolicy(Qt::ActionsContextMenu); - QPushButton *copy_as_bt; - copy_as_bt = ui->buttonBox->addButton(tr("Copy as CSV"), QDialogButtonBox::ActionRole); - connect(copy_as_bt, SIGNAL(clicked()), this, SLOT(on_actionCopyAsCSV_triggered())); + QPushButton *button; + button = ui->buttonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole); + connect(button, SIGNAL(clicked()), this, SLOT(on_actionCopyToClipboard_triggered())); - copy_as_bt = ui->buttonBox->addButton(tr("Copy as YAML"), QDialogButtonBox::ActionRole); - connect(copy_as_bt, SIGNAL(clicked()), this, SLOT(on_actionCopyAsYAML_triggered())); + button = ui->buttonBox->addButton(tr("Save as..."), QDialogButtonBox::ActionRole); + connect(button, SIGNAL(clicked()), this, SLOT(on_actionSaveAs_triggered())); fillTree(); } @@ -103,12 +123,16 @@ void StatsTreeDialog::fillTree() GString *error_string; if (!st_cfg_) return; - setWindowTitle(st_cfg_->name + tr(" Stats Tree")); + gchar* display_name_temp = stats_tree_get_displayname(st_cfg_->name); + QString display_name(display_name_temp); + g_free(display_name_temp); + + setWindowTitle(display_name + tr(" Stats Tree")); if (!cap_file_) return; if (st_cfg_->in_use) { - QMessageBox::warning(this, tr("%1 already open").arg(st_cfg_->name), + QMessageBox::warning(this, tr("%1 already open").arg(display_name), tr("Each type of tree can only be generated one at at time.")); reject(); } @@ -117,8 +141,24 @@ void StatsTreeDialog::fillTree() st_cfg_->pr = &cfg_pr_; cfg_pr_.st_dlg = this; + if (st_) { + stats_tree_free(st_); + } st_ = stats_tree_new(st_cfg_, NULL, ui->displayFilterLineEdit->text().toUtf8().constData()); + // Add number of columns for this stats_tree + QStringList headerLabels; + for (int count = 0; countnum_columns; count++) { + headerLabels.push_back(stats_tree_get_column_name(count)); + } + ui->statsTreeWidget->setColumnCount(headerLabels.count()); + ui->statsTreeWidget->setHeaderLabels(headerLabels); + resize(st_->num_columns*80+80, height()); + for (int count = 0; countnum_columns; count++) { + headerLabels.push_back(stats_tree_get_column_name(count)); + } + ui->statsTreeWidget->setSortingEnabled(false); + error_string = register_tap_listener(st_cfg_->tapname, st_, st_->filter, @@ -127,7 +167,7 @@ void StatsTreeDialog::fillTree() stats_tree_packet, drawTreeItems); if (error_string) { - QMessageBox::critical(this, tr("%1 failed to attach to tap").arg(st_cfg_->name), + QMessageBox::critical(this, tr("%1 failed to attach to tap").arg(display_name), error_string->str); g_string_free(error_string, TRUE); reject(); @@ -135,10 +175,10 @@ void StatsTreeDialog::fillTree() cf_retap_packets(cap_file_); drawTreeItems(st_); + + ui->statsTreeWidget->setSortingEnabled(true); remove_tap_listener(st_); - stats_tree_free(st_); - st_ = NULL; st_cfg_->in_use = FALSE; st_cfg_->pr = NULL; } @@ -160,7 +200,7 @@ void StatsTreeDialog::setupNode(stat_node* node) || !node->st->cfg->pr->st_dlg) return; StatsTreeDialog *st_dlg = node->st->cfg->pr->st_dlg; - QTreeWidgetItem *ti = new QTreeWidgetItem(), *parent = NULL; + QTreeWidgetItem *ti = new StatsTreeWidgetItem(), *parent = NULL; ti->setText(item_col_, node->name); ti->setData(item_col_, Qt::UserRole, qVariantFromValue(node)); @@ -185,22 +225,23 @@ void StatsTreeDialog::drawTreeItems(void *st_ptr) QTreeWidgetItemIterator iter(st_dlg->ui->statsTreeWidget); while (*iter) { - gchar value[NUM_BUF_SIZE]; - gchar rate[NUM_BUF_SIZE]; - gchar percent[NUM_BUF_SIZE]; stat_node *node = (*iter)->data(item_col_, Qt::UserRole).value(); if (node) { - stats_tree_get_strs_from_node(node, value, rate, - percent); - (*iter)->setText(count_col_, value); - (*iter)->setText(rate_col_, rate); - (*iter)->setText(percent_col_, percent); + gchar **valstrs = stats_tree_get_values_from_node(node); + for (int count = 0; countnum_columns; count++) { + (*iter)->setText(count,valstrs[count]); + g_free(valstrs[count]); + } + if (node->parent==(&st->root)) { + (*iter)->setExpanded(!(node->st_flags&ST_FLG_DEF_NOEXPAND)); + } + g_free(valstrs); } ++iter; } - st_dlg->ui->statsTreeWidget->resizeColumnToContents(count_col_); - st_dlg->ui->statsTreeWidget->resizeColumnToContents(rate_col_); - st_dlg->ui->statsTreeWidget->resizeColumnToContents(percent_col_); + for (int count = 0; countnum_columns; count++) { + st_dlg->ui->statsTreeWidget->resizeColumnToContents(count); + } } void StatsTreeDialog::on_applyFilterButton_clicked() @@ -208,56 +249,78 @@ void StatsTreeDialog::on_applyFilterButton_clicked() fillTree(); } -void StatsTreeDialog::on_actionCopyAsCSV_triggered() +void StatsTreeDialog::on_actionCopyToClipboard_triggered() { - QTreeWidgetItemIterator iter(ui->statsTreeWidget); - QString clip = QString("%1,%2,%3,%4\n") - .arg(ui->statsTreeWidget->headerItem()->text(item_col_)) - .arg(ui->statsTreeWidget->headerItem()->text(count_col_)) - .arg(ui->statsTreeWidget->headerItem()->text(rate_col_)) - .arg(ui->statsTreeWidget->headerItem()->text(percent_col_)); - - while (*iter) { - clip += QString("\"%1\",\"%2\",\"%3\",\"%4\"\n") - .arg((*iter)->text(item_col_)) - .arg((*iter)->text(count_col_)) - .arg((*iter)->text(rate_col_)) - .arg((*iter)->text(percent_col_)); - ++iter; - } - wsApp->clipboard()->setText(clip); + GString* s= stats_tree_format_as_str(st_ ,ST_FORMAT_PLAIN, ui->statsTreeWidget->sortColumn(), + ui->statsTreeWidget->header()->sortIndicatorOrder()==Qt::DescendingOrder); + wsApp->clipboard()->setText(s->str); + g_string_free(s,TRUE); } -void StatsTreeDialog::on_actionCopyAsYAML_triggered() +void StatsTreeDialog::on_actionSaveAs_triggered() { - QTreeWidgetItemIterator iter(ui->statsTreeWidget); - QString clip; + QString selectedFilter; + st_format_type file_type; + const char *file_ext; + FILE *f; + GString *str_tree; + bool success= false; + int last_errno; - while (*iter) { - QString indent; - if ((*iter)->parent()) { - QTreeWidgetItem *parent = (*iter)->parent(); - while (parent) { - indent += " "; - parent = parent->parent(); - } - clip += indent + "- description: \"" + (*iter)->text(item_col_) + "\"\n"; - indent += " "; - clip += indent + "count: " + (*iter)->text(count_col_) + "\n"; - clip += indent + "rate_ms: " + (*iter)->text(rate_col_) + "\n"; - clip += indent + "percent: " + (*iter)->text(percent_col_) + "\n"; - } else { - // Top level - clip += "description: \"" + (*iter)->text(item_col_) + "\"\n"; - clip += "count: " + (*iter)->text(count_col_) + "\n"; - clip += "rate_ms: " + (*iter)->text(rate_col_) + "\n"; - } - if ((*iter)->childCount() > 0) { - clip += indent + "items:\n"; - } - ++iter; + QFileDialog SaveAsDialog(this, tr("Wireshark: Save stats tree as ..."), get_last_open_dir()); + SaveAsDialog.setNameFilter(tr("Plain text file (*.txt);;" + "Comma separated values (*.csv);;" + "XML document (*.xml);;" + "YAML document (*.yaml)")); + SaveAsDialog.selectNameFilter(tr("Plain text file (*.txt)")); + SaveAsDialog.setAcceptMode(QFileDialog::AcceptSave); + if (!SaveAsDialog.exec()) { + return; } - wsApp->clipboard()->setText(clip); + selectedFilter= SaveAsDialog.selectedNameFilter(); + if (selectedFilter.contains("*.yaml", Qt::CaseInsensitive)) { + file_type= ST_FORMAT_YAML; + file_ext = ".yaml"; + } + else if (selectedFilter.contains("*.xml", Qt::CaseInsensitive)) { + file_type= ST_FORMAT_XML; + file_ext = ".xml"; + } + else if (selectedFilter.contains("*.csv", Qt::CaseInsensitive)) { + file_type= ST_FORMAT_CSV; + file_ext = ".csv"; + } + else { + file_type= ST_FORMAT_PLAIN; + file_ext = ".txt"; + } + + // Get selected filename and add extension of necessary + QString file_name = SaveAsDialog.selectedFiles()[0]; + if (!file_name.endsWith(file_ext, Qt::CaseInsensitive)) { + file_name.append(file_ext); + } + + // produce output in selected format using current sort information + str_tree=stats_tree_format_as_str(st_ ,file_type, ui->statsTreeWidget->sortColumn(), + ui->statsTreeWidget->header()->sortIndicatorOrder()==Qt::DescendingOrder); + + // actually save the file + f= ws_fopen (file_name.toUtf8().constData(),"w"); + last_errno= errno; + if (f) { + if (fputs(str_tree->str, f)!=EOF) { + success= true; + } + last_errno= errno; + fclose(f); + } + if (!success) { + QMessageBox::warning(this, tr("Error saving file %1").arg(file_name), + g_strerror (last_errno)); + } + + g_string_free(str_tree, TRUE); } extern "C" { diff --git a/ui/qt/stats_tree_dialog.h b/ui/qt/stats_tree_dialog.h index 1bf8df6d5e..533d8de33f 100644 --- a/ui/qt/stats_tree_dialog.h +++ b/ui/qt/stats_tree_dialog.h @@ -36,6 +36,7 @@ namespace Ui { class StatsTreeDialog; +class StatsTreeWidgetItem; } struct _tree_cfg_pres { @@ -68,8 +69,8 @@ private: private slots: void on_applyFilterButton_clicked(); - void on_actionCopyAsCSV_triggered(); - void on_actionCopyAsYAML_triggered(); + void on_actionCopyToClipboard_triggered(); + void on_actionSaveAs_triggered(); }; #endif // STATS_TREE_DIALOG_H diff --git a/ui/qt/stats_tree_dialog.ui b/ui/qt/stats_tree_dialog.ui index 7ae32e7cc4..11be8132be 100644 --- a/ui/qt/stats_tree_dialog.ui +++ b/ui/qt/stats_tree_dialog.ui @@ -21,21 +21,6 @@ Item - - - Count - - - - - Rate (ms) - - - - - Percent - - @@ -73,26 +58,26 @@ - + - Copy as CSV + Copy - Copy the tree as CSV + Copy a text representation of the tree to the clipboard Ctrl+C - + - Copy as YAML + Save as... - Copy the tree as YAML + Save the stats_tree data in various formats - Ctrl+Y + Ctrl+S