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
This commit is contained in:
Alexis La Goutte 2013-12-08 11:09:54 +00:00
parent 4ed05f22b8
commit 006f84a565
6 changed files with 154 additions and 210 deletions

View File

@ -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(i<indent)
indentation[i++] = ' ';
}
indentation[i] = '\0';
g_string_append_printf(s,format,
indentation,node->name,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_time<b->burst_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();
}

View File

@ -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);

View File

@ -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);

View File

@ -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 <QMessageBox>
#include <QTreeWidget>
#include <QTreeWidgetItemIterator>
#include <QFileDialog>
// 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 <QDebug>
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 *>();
stat_node *othernode = other.data(item_col_, Qt::UserRole).value<stat_node *>();
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; count<st_->num_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; count<st_->num_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<stat_node *>();
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; count<st->num_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; count<st->num_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" {

View File

@ -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

View File

@ -21,21 +21,6 @@
<string notr="true">Item</string>
</property>
</column>
<column>
<property name="text">
<string>Count</string>
</property>
</column>
<column>
<property name="text">
<string>Rate (ms)</string>
</property>
</column>
<column>
<property name="text">
<string>Percent</string>
</property>
</column>
</widget>
</item>
<item>
@ -73,26 +58,26 @@
</widget>
</item>
</layout>
<action name="actionCopyAsCSV">
<action name="actionCopyToClipboard">
<property name="text">
<string>Copy as CSV</string>
<string>Copy</string>
</property>
<property name="toolTip">
<string>Copy the tree as CSV</string>
<string>Copy a text representation of the tree to the clipboard</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
</action>
<action name="actionCopyAsYAML">
<action name="actionSaveAs">
<property name="text">
<string>Copy as YAML</string>
<string>Save as...</string>
</property>
<property name="toolTip">
<string>Copy the tree as YAML</string>
<string>Save the stats_tree data in various formats</string>
</property>
<property name="shortcut">
<string>Ctrl+Y</string>
<string>Ctrl+S</string>
</property>
</action>
</widget>