Add relative start time, duration, and average data rate (bps) columns
authorgerald <gerald@f5534014-38df-0310-8fa8-9805f1628bb7>
Tue, 20 Nov 2007 18:35:41 +0000 (18:35 +0000)
committergerald <gerald@f5534014-38df-0310-8fa8-9805f1628bb7>
Tue, 20 Nov 2007 18:35:41 +0000 (18:35 +0000)
to the conversation lists. Move the "copy" button down to the button bar
in order to free up some real estate. Add an nstime_cmp() function. Clean
up code in a few places.

git-svn-id: http://anonsvn.wireshark.org/wireshark/trunk@23516 f5534014-38df-0310-8fa8-9805f1628bb7

20 files changed:
epan/libwireshark.def
epan/nstime.c
epan/nstime.h
gtk/conversations_eth.c
gtk/conversations_fc.c
gtk/conversations_fddi.c
gtk/conversations_ip.c
gtk/conversations_ipx.c
gtk/conversations_jxta.c
gtk/conversations_ncp.c
gtk/conversations_rsvp.c
gtk/conversations_sctp.c
gtk/conversations_table.c
gtk/conversations_table.h
gtk/conversations_tcpip.c
gtk/conversations_tr.c
gtk/conversations_udpip.c
gtk/conversations_usb.c
gtk/conversations_wlan.c
gtk/dlg_utils.c

index 74bb1b4ab9a5e33aac253f1bff4f71ab40bb0249..7b55ca45473dd0a6b9ec2fae3d87f21ec6f871b6 100644 (file)
@@ -514,6 +514,7 @@ mtp3_service_indicator_code_short_vals DATA
 new_create_dissector_handle
 new_register_dissector
 npf_sys_is_running
+nstime_cmp
 nstime_delta
 nstime_is_unset
 nstime_is_zero
index e1b27f6759a88a65a5787c57f59e59a18fa178eb..b913947bdf0114de35a6036f11601744f55c6a2b 100644 (file)
@@ -2,7 +2,7 @@
  * Routines for manipulating nstime_t structures
  *
  * Copyright (c) 2005 MX Telecom Ltd. <richardv@mxtelecom.com>
- * 
+ *
  * $Id$
  *
  * Wireshark - Network traffic analyzer
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
@@ -127,7 +127,24 @@ void nstime_sum(nstime_t *sum, const nstime_t *a, const nstime_t *b)
     } else if(sum->nsecs<=-NS_PER_S || (sum->nsecs<0 && sum->secs>0)) {
         sum->nsecs+=NS_PER_S;
         sum->secs--;
-    }    
+    }
+}
+
+/*
+ * function: nstime_cmp
+ *
+ * a > b : > 0
+ * a = b : 0
+ * a < b : < 0
+ */
+
+int nstime_cmp (nstime_t *a, const nstime_t *b )
+{
+    if (a->secs == b->secs) {
+        return a->nsecs - b->nsecs;
+    } else {
+        return (int) (a->secs - b->secs);
+    }
 }
 
 /*
@@ -159,3 +176,17 @@ double wtap_nstime_to_sec(const struct wtap_nstime *time)
 {
     return ((double)time->secs + (double)time->nsecs/1000000000);
 }
+
+/*
+ * 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:
+ */
+
index 853e3adc9c1e966d587d0fc7ed778a637d1aba46..ea97ea5924cb030eb8740d5e5683517d1793e51c 100644 (file)
@@ -72,6 +72,14 @@ extern void nstime_sum(nstime_t *sum, const nstime_t *b, const nstime_t *a );
 /* sum += a */
 #define nstime_add(sum, a) nstime_sum(sum, sum, a)
 
+/* compare two times are return a value similar to memcmp() or strcmp().
+ *
+ * a > b : > 0
+ * a = b : 0
+ * a < b : < 0
+ */
+extern int nstime_cmp(nstime_t *a, const nstime_t *b );
+
 /* converts nstime to double, time base is milli seconds */
 extern double nstime_to_msec(const nstime_t *time);
 
index f8a690d5ab7edb41810109a889d00c69418ae892..d25c5e2994a61481c21d1e5311d55f3cbada7b4b 100644 (file)
@@ -47,7 +47,7 @@ eth_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const eth_hdr *ehdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_ETHER, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_ETHER, PT_NONE);
 
        return 1;
 }
index 66ad8f77ef5a243e8ec9d66a57ed0e8a8edd3609..cf34e91ba98fcae2c3067091d8f9b4b8517baf5b 100644 (file)
@@ -49,7 +49,7 @@ fc_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, c
 {
        const fc_hdr *fchdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &fchdr->s_id, &fchdr->d_id, 0, 0, 1, pinfo->fd->pkt_len, SAT_NONE, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &fchdr->s_id, &fchdr->d_id, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NONE);
 
        return 1;
 }
index ac9784ee65ed766cb0777948719fef888fb11a32..0f95870b7dd9b1c96fa7e1927a3adaa90d6a6ec8 100644 (file)
@@ -47,7 +47,7 @@ fddi_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const fddi_hdr *ehdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_FDDI, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &ehdr->src, &ehdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_FDDI, PT_NONE);
 
        return 1;
 }
index 2ae005d8c1bc9c752c208bf2b47fb62f3f53f066..68454f3c37943e5bcc4b55d9f1a361f258eb80fc 100644 (file)
@@ -47,7 +47,7 @@ ip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, c
 {
        const e_ip *iph=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &iph->ip_src, &iph->ip_dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_NONE, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &iph->ip_src, &iph->ip_dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NONE);
 
        return 1;
 }
index 2b0718ddc53c9537ac26e3d710b18c0b74e54fbd..53f288cb0917242b20e8d54bc89a89fcf300f58d 100644 (file)
@@ -47,7 +47,7 @@ ipx_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const ipxhdr_t *ipxh=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &ipxh->ipx_src, &ipxh->ipx_dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_NONE, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &ipxh->ipx_src, &ipxh->ipx_dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NONE);
 
        return 1;
 }
index 12166fc0324620c27d197afc5a35cf526219478d..a6ac37c818c848630579947c9a480191cbcff1a7 100644 (file)
@@ -1,7 +1,7 @@
 /* conversations_jxta.c
  * conversations_jxta  copyright (c) 2005 Mike Duigou <bondolo@jxta.org>
  * copied from conversations_sctp.c
- * 
+ *
  * $Id$
  *
  * Wireshark - Network traffic analyzer
@@ -48,14 +48,15 @@ jxta_conversation_packet(void *pct, packet_info *pinfo _U_, epan_dissect_t *edt
 {
        const jxta_tap_header *jxtahdr = (const jxta_tap_header *) vip;
 
-       add_conversation_table_data((conversations_table *)pct, 
-               &jxtahdr->src_address, 
-               &jxtahdr->dest_address, 
-               0, 
-               0, 
-               1, 
-               jxtahdr->size, 
-               SAT_JXTA, 
+       add_conversation_table_data((conversations_table *)pct,
+               &jxtahdr->src_address,
+               &jxtahdr->dest_address,
+               0,
+               0,
+               1,
+               jxtahdr->size,
+               NULL,
+               SAT_JXTA,
                PT_NONE);
 
 
index 8c1475c4e118068581795c53fea251e675247fcc..5479f57f13f709cf12220ac8a4a1ef5fb69ec646 100644 (file)
@@ -50,7 +50,7 @@ ncp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 
     connection = (ncph->conn_high * 256)+ncph->conn_low;
     if (connection < 65535) {
-        add_conversation_table_data((conversations_table *)pct, &pinfo->src, &pinfo->dst, connection, connection, 1, pinfo->fd->pkt_len, SAT_NONE, PT_NCP);
+        add_conversation_table_data((conversations_table *)pct, &pinfo->src, &pinfo->dst, connection, connection, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NCP);
     }
 
        return 1;
index c6fd7f08d02533c0477d38a438a43c6ee16a89ba..8e8772c621ce439d69b6f77bf101650128164f24 100644 (file)
@@ -45,9 +45,9 @@ rsvp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const rsvp_conversation_info *rsvph = vip;
 
-       add_conversation_table_data((conversations_table *)pct, 
-                                   &rsvph->source, &rsvph->destination, 0, 0, 1, 
-                                   pinfo->fd->pkt_len, SAT_NONE, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct,
+                                   &rsvph->source, &rsvph->destination, 0, 0, 1,
+                                   pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NONE);
 
        return 1;
 }
@@ -63,7 +63,7 @@ rsvp_conversation_init(const char *optarg, void* userdata _U_)
                filter=NULL;
        }
 
-       init_conversation_table(TRUE, "RSVP", "rsvp", filter, 
+       init_conversation_table(TRUE, "RSVP", "rsvp", filter,
                                rsvp_conversation_packet);
 
 }
@@ -84,6 +84,6 @@ register_tap_listener_rsvp_conversation(void)
        register_stat_menu_item("RSVP", REGISTER_STAT_GROUP_CONVERSATION_LIST,
                               rsvp_endpoints_cb, NULL, NULL, NULL);
 
-       register_conversation_table(TRUE, "RSVP", "rsvp", NULL /*filter*/, 
+       register_conversation_table(TRUE, "RSVP", "rsvp", NULL /*filter*/,
                                    rsvp_conversation_packet);
 }
index 6b0470203860ff7c0adb968bfd143f225398130d..87fc13468fb6412924bd88bf7c992eb54ec03771 100644 (file)
@@ -47,14 +47,15 @@ sctp_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const struct _sctp_info *sctphdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, 
-               &sctphdr->ip_src, 
-               &sctphdr->ip_dst, 
-               sctphdr->sport, 
-               sctphdr->dport, 
-               1, 
-               pinfo->fd->pkt_len, 
-               SAT_NONE, 
+       add_conversation_table_data((conversations_table *)pct,
+               &sctphdr->ip_src,
+               &sctphdr->ip_dst,
+               sctphdr->sport,
+               sctphdr->dport,
+               1,
+               pinfo->fd->pkt_len,
+               &pinfo->fd->rel_ts,
+                SAT_NONE,
                PT_SCTP);
 
 
index 0ce70a3fee71c3a88011233f57dc23a76f938d22..eaddc27d1860998b61b704afe02292bd6cd17faa 100644 (file)
@@ -42,6 +42,7 @@
 #include <epan/address.h>
 #include <epan/addr_resolv.h>
 #include <epan/tap.h>
+#include <epan/nstime.h>
 
 #include "compat_macros.h"
 #include "sat.h"
 
 #define GTK_MENU_FUNC(a) ((GtkItemFactoryCallback)(a))
 
-#define NUM_COLS 10
+#define NUM_COLS 14
+#define COL_STR_LEN 16
 #define CONV_PTR_KEY "conversations-pointer"
+#define NB_PAGES_KEY "notebook-pages"
+#define NO_BPS_STR "N/A"
 
-#define CMP_INT(i1, i2)        \
-       if ((i1) > (i2))        \
+#define CMP_NUM(n1, n2)        \
+       if ((n1) > (n2))        \
                return 1;       \
-       else if ((i1) < (i2))   \
+       else if ((n1) < (n2))   \
                return -1;      \
        else                    \
                return 0;
@@ -343,6 +347,7 @@ ct_sort_column(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2)
        conversations_table *ct = OBJECT_GET_DATA(clist, CONV_PTR_KEY);
        conv_t *conv1 = NULL;
        conv_t *conv2 = NULL;
+        double duration1, duration2;
 
        const GtkCListRow *row1 = ptr1;
        const GtkCListRow *row2 = ptr2;
@@ -356,29 +361,48 @@ ct_sort_column(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2)
        conv1 = &ct->conversations[idx1];
        conv2 = &ct->conversations[idx2];
 
+        duration1 = nstime_to_sec(&conv1->stop_time) - nstime_to_sec(&conv1->start_time);
+        duration2 = nstime_to_sec(&conv2->stop_time) - nstime_to_sec(&conv2->start_time);
+
        switch(clist->sort_column){
        case 0: /* Source address */
                return(CMP_ADDRESS(&conv1->src_address, &conv2->src_address));
        case 2: /* Destination address */
                return(CMP_ADDRESS(&conv1->dst_address, &conv2->dst_address));
        case 1: /* Source port */
-               CMP_INT(conv1->src_port, conv2->src_port);
+               CMP_NUM(conv1->src_port, conv2->src_port);
        case 3: /* Destination port */
-               CMP_INT(conv1->dst_port, conv2->dst_port);
+               CMP_NUM(conv1->dst_port, conv2->dst_port);
        case 4: /* Packets */
-               CMP_INT(conv1->tx_frames+conv1->rx_frames,
+               CMP_NUM(conv1->tx_frames+conv1->rx_frames,
                        conv2->tx_frames+conv2->rx_frames);
         case 5: /* Bytes */
-               CMP_INT(conv1->tx_bytes+conv1->rx_bytes,
+               CMP_NUM(conv1->tx_bytes+conv1->rx_bytes,
                        conv2->tx_bytes+conv2->rx_bytes);
         case 6: /* Packets A->B */
-               CMP_INT(conv1->tx_frames, conv2->tx_frames);
+               CMP_NUM(conv1->tx_frames, conv2->tx_frames);
         case 7: /* Bytes A->B */
-               CMP_INT(conv1->tx_bytes, conv2->tx_bytes);
+               CMP_NUM(conv1->tx_bytes, conv2->tx_bytes);
         case 8: /* Packets A<-B */
-               CMP_INT(conv1->rx_frames, conv2->rx_frames);
+               CMP_NUM(conv1->rx_frames, conv2->rx_frames);
         case 9: /* Bytes A<-B */
-               CMP_INT(conv1->rx_bytes, conv2->rx_bytes);
+               CMP_NUM(conv1->rx_bytes, conv2->rx_bytes);
+        case 10: /* Start time */
+               return nstime_cmp(&conv1->start_time, &conv2->start_time);
+        case 11: /* Duration */
+               CMP_NUM(duration1, duration2);
+        case 12: /* bps A->B */
+            if (duration1 > 0 && conv1->tx_frames > 1 && duration2 > 0 && conv2->tx_frames > 1) {
+                CMP_NUM((gint64) conv1->tx_bytes / duration1, (gint64) conv2->tx_bytes / duration2);
+            } else {
+                CMP_NUM(conv1->tx_bytes, conv2->tx_bytes);
+            }
+        case 13: /* bps A<-B */
+            if (duration1 > 0 && conv1->rx_frames > 1 && duration2 > 0 && conv2->rx_frames > 1) {
+                CMP_NUM((gint64) conv1->rx_bytes / duration1, (gint64) conv2->rx_bytes / duration2);
+            } else {
+                CMP_NUM(conv1->rx_bytes, conv2->rx_bytes);
+            }
        default:
                g_assert_not_reached();
        }
@@ -1101,6 +1125,7 @@ draw_ct_table_data(conversations_table *ct)
     guint32 i;
     int j;
     char title[256];
+    double duration_s;
 
     if (ct->page_lb) {
         if(ct->num_conversations) {
@@ -1113,27 +1138,46 @@ draw_ct_table_data(conversations_table *ct)
     }
 
     for(i=0;i<ct->num_conversations;i++){
-        char str[16];
+        char str[COL_STR_LEN];
 
         j=gtk_clist_find_row_from_data(ct->table, (gpointer)(unsigned long)i);
 
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_frames+ct->conversations[i].rx_frames);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_frames+ct->conversations[i].rx_frames);
         gtk_clist_set_text(ct->table, j, 4, str);
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_bytes+ct->conversations[i].rx_bytes);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_bytes+ct->conversations[i].rx_bytes);
         gtk_clist_set_text(ct->table, j, 5, str);
 
 
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_frames);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_frames);
         gtk_clist_set_text(ct->table, j, 6, str);
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_bytes);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].tx_bytes);
         gtk_clist_set_text(ct->table, j, 7, str);
 
 
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].rx_frames);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].rx_frames);
         gtk_clist_set_text(ct->table, j, 8, str);
-        g_snprintf(str, 16, "%" G_GINT64_MODIFIER "u", ct->conversations[i].rx_bytes);
+        g_snprintf(str, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", ct->conversations[i].rx_bytes);
         gtk_clist_set_text(ct->table, j, 9, str);
 
+        duration_s = nstime_to_sec(&ct->conversations[i].stop_time) - nstime_to_sec(&ct->conversations[i].start_time);
+        g_snprintf(str, COL_STR_LEN, "%s", rel_time_to_secs_str(&ct->conversations[i].start_time));
+        gtk_clist_set_text(ct->table, j, 10, str);
+        g_snprintf(str, COL_STR_LEN, "%.4f", duration_s);
+        gtk_clist_set_text(ct->table, j, 11, str);
+        if (duration_s > 0 && ct->conversations[i].tx_frames > 1) {
+            /* XXX - The gint64 casts below are needed for MSVC++ 6.0 */
+            g_snprintf(str, COL_STR_LEN, "%.2f", (gint64) ct->conversations[i].tx_bytes * 8 / duration_s);
+            gtk_clist_set_text(ct->table, j, 12, str);
+        } else {
+            gtk_clist_set_text(ct->table, j, 12, NO_BPS_STR);
+        }
+        if (duration_s > 0 && ct->conversations[i].rx_frames > 1) {
+            /* XXX - The gint64 casts below are needed for MSVC++ 6.0 */
+            g_snprintf(str, COL_STR_LEN, "%.2f", (gint64) ct->conversations[i].rx_bytes * 8 / duration_s);
+            gtk_clist_set_text(ct->table, j, 13, str);
+        } else {
+            gtk_clist_set_text(ct->table, j, 13, NO_BPS_STR);
+        }
     }
 
     draw_ct_table_addresses(ct);
@@ -1153,14 +1197,16 @@ draw_ct_table_data_cb(void *arg)
 
 #if (GTK_MAJOR_VERSION >= 2)
 static void
-copy_as_csv_cb(GtkWindow *win _U_, gpointer data)
+copy_as_csv_cb(GtkWindow *copy_bt, gpointer data _U_)
 {
    guint32         i,j;
    gchar           *table_entry;
    GtkClipboard    *cb;
    GString         *CSV_str = g_string_new("");
 
-   conversations_table *talkers=(conversations_table *)data;
+   conversations_table *talkers=OBJECT_GET_DATA(copy_bt, CONV_PTR_KEY);
+   if (!talkers)
+     return;
 
    /* Add the column headers to the CSV data */
    for(i=0;i<talkers->num_columns;i++){                  /* all columns         */
@@ -1201,11 +1247,6 @@ init_ct_table_page(conversations_table *conversations, GtkWidget *vbox, gboolean
     GString *error_string;
     GtkWidget *label;
     char title[256];
-#if (GTK_MAJOR_VERSION >= 2)
-    GtkWidget *copy_bt;
-    GtkTooltips *tooltips = gtk_tooltips_new();
-#endif
-
 
     conversations->page_lb=NULL;
     conversations->resolve_names=TRUE;
@@ -1221,6 +1262,10 @@ init_ct_table_page(conversations_table *conversations, GtkWidget *vbox, gboolean
     conversations->default_titles[7]="Bytes A->B";
     conversations->default_titles[8]="Packets A<-B";
     conversations->default_titles[9]="Bytes A<-B";
+    conversations->default_titles[10]="Rel Start";
+    conversations->default_titles[11]="Duration";
+    conversations->default_titles[12]="bps A->B";
+    conversations->default_titles[13]="bps A<-B";
     if (strcmp(table_name, "NCP")==0) {
         conversations->default_titles[1]="Connection A";
         conversations->default_titles[3]="Connection B";
@@ -1264,16 +1309,9 @@ init_ct_table_page(conversations_table *conversations, GtkWidget *vbox, gboolean
     gtk_clist_set_sort_type(conversations->table, GTK_SORT_ASCENDING);
 
 
-    gtk_clist_set_column_auto_resize(conversations->table, 0, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 1, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 2, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 3, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 4, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 5, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 6, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 7, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 8, TRUE);
-    gtk_clist_set_column_auto_resize(conversations->table, 9, TRUE);
+    for (i = 0; i < NUM_COLS; i++) {
+        gtk_clist_set_column_auto_resize(conversations->table, i, TRUE);
+    }
 
     gtk_clist_set_shadow_type(conversations->table, GTK_SHADOW_IN);
     gtk_clist_column_titles_show(conversations->table);
@@ -1293,16 +1331,6 @@ init_ct_table_page(conversations_table *conversations, GtkWidget *vbox, gboolean
     /* create popup menu for this table */
     ct_create_popup_menu(conversations);
 
-#if (GTK_MAJOR_VERSION >= 2)
-    /* XXX - maybe we want to have a "Copy as CSV" stock button here? */
-    /*copy_bt = gtk_button_new_with_label ("Copy content to clipboard as CSV");*/
-    copy_bt = BUTTON_NEW_FROM_STOCK(GTK_STOCK_COPY);
-    gtk_tooltips_set_tip(tooltips, copy_bt,
-        "Copy all statistical values of this page to the clipboard in CSV (Comma Separated Values) format.", NULL);
-    SIGNAL_CONNECT(copy_bt, "clicked", copy_as_csv_cb,(gpointer *) conversations);
-    gtk_box_pack_start(GTK_BOX(vbox), copy_bt, FALSE, FALSE, 0);
-#endif
-
     /* register the tap and rerun the taps on the packet list */
     error_string=register_tap_listener(tap_name, conversations, filter, reset_ct_table_data_cb, packet_func, draw_ct_table_data_cb);
     if(error_string){
@@ -1324,7 +1352,10 @@ init_conversation_table(gboolean hide_ports, const char *table_name, const char
     GtkWidget *bbox;
     GtkWidget *close_bt, *help_bt;
     gboolean ret;
-
+#if (GTK_MAJOR_VERSION >= 2)
+    GtkWidget *copy_bt;
+    GtkTooltips *tooltips = gtk_tooltips_new();
+#endif
 
     conversations=g_malloc(sizeof(conversations_table));
 
@@ -1345,16 +1376,34 @@ init_conversation_table(gboolean hide_ports, const char *table_name, const char
     }
 
     /* Button row. */
+    /* XXX - maybe we want to have a "Copy as CSV" stock button here? */
+    /*copy_bt = gtk_button_new_with_label ("Copy content to clipboard as CSV");*/
+#if (GTK_MAJOR_VERSION >= 2)
+    if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
+        bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_COPY, GTK_STOCK_HELP, NULL);
+    } else {
+        bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_COPY, NULL);
+    }
+#else
     if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
         bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_HELP, NULL);
     } else {
         bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL);
     }
+#endif
     gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
 
     close_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_CLOSE);
     window_set_cancel_button(conversations->win, close_bt, window_cancel_button_cb);
 
+#if (GTK_MAJOR_VERSION >= 2)
+    copy_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_COPY);
+    gtk_tooltips_set_tip(tooltips, copy_bt,
+        "Copy all statistical values of this page to the clipboard in CSV (Comma Separated Values) format.", NULL);
+    OBJECT_SET_DATA(copy_bt, CONV_PTR_KEY, conversations);
+    SIGNAL_CONNECT(copy_bt, "clicked", copy_as_csv_cb, NULL);
+#endif
+
     if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
         help_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_HELP);
         SIGNAL_CONNECT(help_bt, "clicked", topic_cb, HELP_STATS_CONVERSATIONS_DIALOG);
@@ -1379,6 +1428,21 @@ init_conversation_table(gboolean hide_ports, const char *table_name, const char
 
 
 
+#if (GTK_MAJOR_VERSION >= 2)
+static void
+ct_nb_switch_page_cb(GtkNotebook *nb, GtkNotebookPage *pg _U_, guint page, gpointer data)
+{
+    GtkWidget *copy_bt = (GtkWidget *) data;
+    void ** pages = OBJECT_GET_DATA(nb, NB_PAGES_KEY);
+
+    page++;
+
+    if (pages && page > 0 && (int) page <= GPOINTER_TO_INT(pages[0]) && copy_bt) {
+        OBJECT_SET_DATA(copy_bt, CONV_PTR_KEY, pages[page]);
+    }
+}
+#endif
+
 static void
 ct_win_destroy_notebook_cb(GtkWindow *win _U_, gpointer data)
 {
@@ -1389,11 +1453,9 @@ ct_win_destroy_notebook_cb(GtkWindow *win _U_, gpointer data)
     for (page=1; page<=GPOINTER_TO_INT(pages[0]); page++) {
         ct_win_destroy_cb(NULL, pages[page]);
     }
+    g_free(pages);
 }
 
-
-
-
 static conversations_table *
 init_ct_notebook_page_cb(gboolean hide_ports, const char *table_name, const char *tap_name, const char *filter, tap_packet_cb packet_func)
 {
@@ -1464,9 +1526,9 @@ ct_resolve_toggle_dest(GtkWidget *widget, gpointer data)
 
         draw_ct_table_addresses(conversations);
 
-           /* Allow table to redraw */
+        /* Allow table to redraw */
         gtk_clist_thaw(conversations->table);
-           gtk_clist_freeze(conversations->table);
+        gtk_clist_freeze(conversations->table);
     }
 }
 
@@ -1488,7 +1550,9 @@ init_conversation_notebook_cb(GtkWidget *w _U_, gpointer d _U_)
     GSList  *current_table;
     register_ct_t *registered;
     GtkTooltips *tooltips = gtk_tooltips_new();
-
+#if (GTK_MAJOR_VERSION >= 2)
+    GtkWidget *copy_bt;
+#endif
 
     pages = g_malloc(sizeof(void *) * (g_slist_length(registered_ct_tables) + 1));
 
@@ -1502,6 +1566,7 @@ init_conversation_notebook_cb(GtkWidget *w _U_, gpointer d _U_)
 
     nb = gtk_notebook_new();
     gtk_container_add(GTK_CONTAINER(vbox), nb);
+    OBJECT_SET_DATA(nb, NB_PAGES_KEY, pages);
 
     page = 0;
 
@@ -1511,6 +1576,7 @@ init_conversation_notebook_cb(GtkWidget *w _U_, gpointer d _U_)
         page_lb = gtk_label_new("");
         conversations = init_ct_notebook_page_cb(registered->hide_ports, registered->table_name, registered->tap_name,
             registered->filter, registered->packet_func);
+        OBJECT_SET_DATA(conversations->win, CONV_PTR_KEY, conversations);
         gtk_notebook_append_page(GTK_NOTEBOOK(nb), conversations->win, page_lb);
         conversations->win = win;
         conversations->page_lb = page_lb;
@@ -1533,16 +1599,36 @@ init_conversation_notebook_cb(GtkWidget *w _U_, gpointer d _U_)
     SIGNAL_CONNECT(resolv_cb, "toggled", ct_resolve_toggle_dest, pages);
 
     /* Button row. */
+    /* XXX - maybe we want to have a "Copy as CSV" stock button here? */
+    /*copy_bt = gtk_button_new_with_label ("Copy content to clipboard as CSV");*/
+#if (GTK_MAJOR_VERSION >= 2)
+    if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
+        bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_COPY, GTK_STOCK_HELP, NULL);
+    } else {
+        bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_COPY, NULL);
+    }
+#else
     if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
         bbox = dlg_button_row_new(GTK_STOCK_CLOSE, GTK_STOCK_HELP, NULL);
     } else {
         bbox = dlg_button_row_new(GTK_STOCK_CLOSE, NULL);
     }
+#endif
     gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
 
     close_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_CLOSE);
     window_set_cancel_button(win, close_bt, window_cancel_button_cb);
 
+#if (GTK_MAJOR_VERSION >= 2)
+    copy_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_COPY);
+    gtk_tooltips_set_tip(tooltips, copy_bt,
+        "Copy all statistical values of this page to the clipboard in CSV (Comma Separated Values) format.", NULL);
+    SIGNAL_CONNECT(copy_bt, "clicked", copy_as_csv_cb, NULL);
+    OBJECT_SET_DATA(copy_bt, CONV_PTR_KEY, pages[page]);
+
+    SIGNAL_CONNECT(nb, "switch-page", ct_nb_switch_page_cb, copy_bt);
+#endif
+
     if(topic_available(HELP_STATS_CONVERSATIONS_DIALOG)) {
         help_bt = OBJECT_GET_DATA(bbox, GTK_STOCK_HELP);
         SIGNAL_CONNECT(help_bt, "clicked", topic_cb, HELP_STATS_CONVERSATIONS_DIALOG);
@@ -1564,7 +1650,7 @@ init_conversation_notebook_cb(GtkWidget *w _U_, gpointer d _U_)
 
 
 void
-add_conversation_table_data(conversations_table *ct, const address *src, const address *dst, guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, SAT_E sat, int port_type)
+add_conversation_table_data(conversations_table *ct, const address *src, const address *dst, guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, nstime_t *ts, SAT_E sat, int port_type)
 {
     const address *addr1, *addr2;
     guint32 port1, port2;
@@ -1646,6 +1732,13 @@ add_conversation_table_data(conversations_table *ct, const address *src, const a
         conversation->tx_frames=0;
         conversation->rx_bytes=0;
         conversation->tx_bytes=0;
+        if (ts) {
+            memcpy(&conversation->start_time, ts, sizeof(conversation->start_time));
+            memcpy(&conversation->stop_time, ts, sizeof(conversation->stop_time));
+        } else {
+            nstime_set_unset(&conversation->start_time);
+            nstime_set_unset(&conversation->stop_time);
+        }
     }
 
     /* update the conversation struct */
@@ -1657,10 +1750,23 @@ add_conversation_table_data(conversations_table *ct, const address *src, const a
         conversation->rx_bytes+=num_bytes;
     }
 
+    if (ts) {
+        if (nstime_cmp(ts, &conversation->stop_time) > 0) {
+            memcpy(&conversation->stop_time, ts, sizeof(conversation->stop_time));
+        } else if (nstime_cmp(ts, &conversation->start_time) < 0) {
+            memcpy(&conversation->start_time, ts, sizeof(conversation->start_time));
+        }
+    }
+
     /* if this was a new conversation we have to create a clist row for it */
     if(new_conversation){
         char *entries[NUM_COLS];
-        char frames[16],bytes[16],txframes[16],txbytes[16],rxframes[16],rxbytes[16];
+        char frames[COL_STR_LEN], bytes[COL_STR_LEN],
+             txframes[COL_STR_LEN], txbytes[COL_STR_LEN],
+             rxframes[COL_STR_LEN], rxbytes[COL_STR_LEN],
+             start_time[COL_STR_LEN], duration[COL_STR_LEN],
+             txbps[COL_STR_LEN], rxbps[COL_STR_LEN];
+        double duration_s;
 
         /* these values will be filled by call to draw_ct_table_addresses() below */
         entries[0] = "";
@@ -1668,24 +1774,56 @@ add_conversation_table_data(conversations_table *ct, const address *src, const a
         entries[2] = "";
         entries[3] = "";
 
-        g_snprintf(frames, 16, "%" G_GINT64_MODIFIER "u", conversation->tx_frames+conversation->rx_frames);
+        g_snprintf(frames, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->tx_frames+conversation->rx_frames);
         entries[4]=frames;
-        g_snprintf(bytes, 16, "%" G_GINT64_MODIFIER "u", conversation->tx_bytes+conversation->rx_bytes);
+        g_snprintf(bytes, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->tx_bytes+conversation->rx_bytes);
         entries[5]=bytes;
 
-        g_snprintf(txframes, 16, "%" G_GINT64_MODIFIER "u", conversation->tx_frames);
+        g_snprintf(txframes, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->tx_frames);
         entries[6]=txframes;
-        g_snprintf(txbytes, 16, "%" G_GINT64_MODIFIER "u", conversation->tx_bytes);
+        g_snprintf(txbytes, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->tx_bytes);
         entries[7]=txbytes;
 
-        g_snprintf(rxframes, 16, "%" G_GINT64_MODIFIER "u", conversation->rx_frames);
+        g_snprintf(rxframes, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->rx_frames);
         entries[8]=rxframes;
-        g_snprintf(rxbytes, 16, "%" G_GINT64_MODIFIER "u", conversation->rx_bytes);
+        g_snprintf(rxbytes, COL_STR_LEN, "%" G_GINT64_MODIFIER "u", conversation->rx_bytes);
         entries[9]=rxbytes;
 
+        duration_s = nstime_to_sec(&conversation->start_time) - nstime_to_sec(&conversation->stop_time);
+        g_snprintf(start_time, COL_STR_LEN, "%s", rel_time_to_secs_str(&conversation->start_time));
+        g_snprintf(duration, COL_STR_LEN, "%.4f", duration_s);
+        entries[10]=start_time;
+        entries[11]=duration;
+        if (duration_s > 0 && conversation->tx_frames > 1) {
+            g_snprintf(txbps, COL_STR_LEN, "%.2f", (gint64) conversation->tx_bytes * 8 / duration_s);
+            entries[12]=txbps;
+        } else {
+            entries[12] = NO_BPS_STR;
+        }
+        if (duration_s > 0 && conversation->rx_frames > 1) {
+            g_snprintf(rxbps, COL_STR_LEN, "%.2f", (gint64) conversation->rx_bytes * 8 / duration_s);
+            entries[13]=rxbps;
+        } else {
+            entries[13] = NO_BPS_STR;
+        }
+
         gtk_clist_insert(ct->table, conversation_idx, entries);
         gtk_clist_set_row_data(ct->table, conversation_idx, (gpointer)(long) conversation_idx);
 
         draw_ct_table_address(ct, conversation_idx);
     }
 }
+
+/*
+ * 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:
+ */
+
index 5a65246e89b6379fff99774c29eed1bb0e57b1a3..89888c3cd1bf33df8679469e7dcbd9f78852826e 100644 (file)
@@ -7,17 +7,17 @@
  * Wireshark - Network traffic analyzer
  * By Gerald Combs <gerald@wireshark.org>
  * Copyright 1998 Gerald Combs
- * 
+ *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
  * as published by the Free Software Foundation; either version 2
  * of the License, or (at your option) any later version.
- * 
+ *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 /** Conversation information */
 typedef struct _conversation_t {
-       address src_address;    /**< source address */
-       address dst_address;    /**< destination address */
-       SAT_E   sat;            /**< address type */
-       guint32 port_type;      /**< port_type (e.g. PT_TCP) */
-       guint32 src_port;       /**< source port */
-       guint32 dst_port;       /**< destination port */
+       address  src_address;    /**< source address */
+       address  dst_address;    /**< destination address */
+       SAT_E    sat;            /**< address type */
+       guint32  port_type;      /**< port_type (e.g. PT_TCP) */
+       guint32  src_port;       /**< source port */
+       guint32  dst_port;       /**< destination port */
+
+       guint64  rx_frames;      /**< number of received packets */
+       guint64  tx_frames;      /**< number of transmitted packets */
+       guint64  rx_bytes;       /**< number of received bytes */
+       guint64  tx_bytes;       /**< number of transmitted bytes */
 
-       guint64 rx_frames;      /**< number of received packets */
-       guint64 tx_frames;      /**< number of transmitted packets */
-       guint64 rx_bytes;       /**< number of received bytes */
-       guint64 tx_bytes;       /**< number of transmitted bytes */
+       nstime_t start_time;     /**< start time for the conversation */
+       nstime_t stop_time;      /**< stop time for the conversation */
 } conv_t;
 
 /** Conversation widget */
@@ -52,8 +55,8 @@ typedef struct _conversations_table {
        GtkWidget           *page_lb;           /**< label */
        GtkWidget           *scrolled_window;   /**< the scrolled window */
        GtkCList            *table;             /**< the GTK table */
-        guint32             num_columns;         /**< number of columns in the above table */
-        const char          *default_titles[10]; /**< Column headers */
+       guint32             num_columns;         /**< number of columns in the above table */
+       const char          *default_titles[14]; /**< Column headers */
        GtkWidget           *menu;              /**< context menu */
        gboolean            has_ports;          /**< table has ports */
        guint32             num_conversations;  /**< number of conversations */
@@ -97,10 +100,12 @@ extern void init_conversation_notebook_cb(GtkWidget *widget, gpointer data);
  * @param dst_port destination port
  * @param num_frames number of packets
  * @param num_bytes number of bytes
+ * @param ts timestamp
  * @param sat address type
  * @param port_type the port type (e.g. PT_TCP)
  */
-extern void add_conversation_table_data(conversations_table *ct, const address *src, const address *dst, 
-                        guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, SAT_E sat, int port_type);
+extern void add_conversation_table_data(conversations_table *ct, const address *src, const address *dst,
+                       guint32 src_port, guint32 dst_port, int num_frames, int num_bytes, nstime_t *ts,
+                       SAT_E sat, int port_type);
 #endif /* __CONVERSATIONS_TABLE_H__ */
 
index 7d8afd8ac0e4797b2dd83f2fd74e59efae19810d..4c86501d7e76b4dca2b1d72155f1e6cfc490c658 100644 (file)
@@ -48,7 +48,7 @@ tcpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_
 {
        const struct tcpheader *tcphdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &tcphdr->ip_src, &tcphdr->ip_dst, tcphdr->th_sport, tcphdr->th_dport, 1, pinfo->fd->pkt_len, SAT_NONE, PT_TCP);
+       add_conversation_table_data((conversations_table *)pct, &tcphdr->ip_src, &tcphdr->ip_dst, tcphdr->th_sport, tcphdr->th_dport, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_TCP);
 
        return 1;
 }
index d3a0c80ced7c8052f96a89fa3172f5ee073ff526..440f3b095df41b368d1940d014b851f6545116c4 100644 (file)
@@ -47,7 +47,7 @@ tr_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, c
 {
        const tr_hdr *trhdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &trhdr->src, &trhdr->dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_TOKENRING, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &trhdr->src, &trhdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_TOKENRING, PT_NONE);
 
        return 1;
 }
index e18dbcd9381ead20394eec6d4f205f37ad8d23d2..5ef123b67cf1d07c8778d666a29b148dd5501005 100644 (file)
@@ -47,7 +47,7 @@ udpip_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_
 {
        const e_udphdr *udphdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &udphdr->ip_src, &udphdr->ip_dst, udphdr->uh_sport, udphdr->uh_dport, 1, pinfo->fd->pkt_len, SAT_NONE, PT_UDP);
+       add_conversation_table_data((conversations_table *)pct, &udphdr->ip_src, &udphdr->ip_dst, udphdr->uh_sport, udphdr->uh_dport, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_UDP);
 
        return 1;
 }
index e29689b28bb0110e23bee66d8f9746ccf7a2734c..cabeb5ed83a65ca411577c90b6fab14e66f407df 100644 (file)
@@ -44,7 +44,7 @@
 static int
 usb_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_, const void *vip _U_)
 {
-       add_conversation_table_data((conversations_table *)pct, &pinfo->src, &pinfo->dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_NONE, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &pinfo->src, &pinfo->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_NONE, PT_NONE);
 
        return 1;
 }
index e666b9d321a49f022ac70ada3ec1064ffaf4b9e1..b23d2111aa4681abdceb1bf26ba67ec4291800ac 100644 (file)
@@ -47,7 +47,7 @@ wlan_conversation_packet(void *pct, packet_info *pinfo, epan_dissect_t *edt _U_,
 {
        const wlan_hdr *whdr=vip;
 
-       add_conversation_table_data((conversations_table *)pct, &whdr->src, &whdr->dst, 0, 0, 1, pinfo->fd->pkt_len, SAT_WLAN, PT_NONE);
+       add_conversation_table_data((conversations_table *)pct, &whdr->src, &whdr->dst, 0, 0, 1, pinfo->fd->pkt_len, &pinfo->fd->rel_ts, SAT_WLAN, PT_NONE);
 
        return 1;
 }
index bb6d137fdeacdb57a2532c6ec811352865640b7b..d2455c030e2bac5013bb106f90b3696f5653989f 100644 (file)
@@ -106,25 +106,26 @@ dlg_button_row_new(const gchar *stock_id_first, ...)
     GtkWidget   *help_hbox;
     GtkWidget   *button;
 
-    const gchar *ok           = NULL;
     const gchar *apply        = NULL;
-    const gchar *save         = NULL;
-    const gchar *dont_save    = NULL;
     const gchar *cancel       = NULL;
-    const gchar *close        = NULL;
-    const gchar *clear        = NULL;
     const gchar *cap_start    = NULL;
     const gchar *cap_stop     = NULL;
-    const gchar *stop         = NULL;
+    const gchar *clear        = NULL;
+    const gchar *close        = NULL;
+    const gchar *copy         = NULL;
     const gchar *create_stat  = NULL;
-    const gchar *help         = NULL;
-    const gchar *print        = NULL;
+    const gchar *delete       = NULL;
+    const gchar *dont_save    = NULL;
+    const gchar *filter_stream= NULL;
     const gchar *find         = NULL;
+    const gchar *help         = NULL;
     const gchar *jump         = NULL;
-    const gchar *yes          = NULL;
     const gchar *no           = NULL;
-    const gchar *filter_stream= NULL;
-    const gchar *delete       = NULL;
+    const gchar *ok           = NULL;
+    const gchar *print        = NULL;
+    const gchar *save         = NULL;
+    const gchar *stop         = NULL;
+    const gchar *yes          = NULL;
 
 
     va_start(stock_id_list, stock_id_first);
@@ -171,6 +172,8 @@ dlg_button_row_new(const gchar *stock_id_first, ...)
             filter_stream = stock_id;
         } else if (strcmp(stock_id, GTK_STOCK_DELETE) == 0) {
             delete = stock_id;
+        } else if (strcmp(stock_id, GTK_STOCK_COPY) == 0) {
+            copy = stock_id;
         } else {
             /* we don't know that button! */
             g_assert_not_reached();
@@ -336,6 +339,7 @@ dlg_button_row_new(const gchar *stock_id_first, ...)
     if (jump    != NULL) dlg_button_new(hbox, button_hbox, jump);
     if (find    != NULL) dlg_button_new(hbox, button_hbox, find);
     if (print   != NULL) dlg_button_new(hbox, button_hbox, print);
+    if (copy    != NULL) dlg_button_new(hbox, button_hbox, copy);
     if (create_stat != NULL) dlg_button_new(hbox, button_hbox, create_stat);
     if (apply   != NULL) dlg_button_new(hbox, button_hbox, apply);
     if (yes     != NULL) dlg_button_new(hbox, button_hbox, yes);