Qt: Update window title when preferences changed.
[metze/wireshark/wip.git] / ui / qt / main_window.cpp
1 /* main_window.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main_window.h"
23 #include <ui_main_window.h>
24
25 #include <epan/addr_resolv.h>
26 #include <epan/epan_dissect.h>
27 #include <wsutil/filesystem.h>
28 #include <epan/prefs.h>
29 #include <epan/stats_tree_priv.h>
30 #include <epan/plugin_if.h>
31
32 #ifdef HAVE_LIBPCAP
33 #include "ui/capture.h"
34 #include <capchild/capture_session.h>
35 #endif
36
37 #include "ui/alert_box.h"
38 #ifdef HAVE_LIBPCAP
39 #include "ui/capture_ui_utils.h"
40 #endif
41 #include "ui/capture_globals.h"
42 #include "ui/main_statusbar.h"
43 #include "ui/recent.h"
44 #include "ui/util.h"
45 #include "ui/preference_utils.h"
46
47 #include "byte_view_tab.h"
48 #include "display_filter_edit.h"
49 #include "export_dissection_dialog.h"
50 #include "funnel_statistics.h"
51 #include "import_text_dialog.h"
52 #include "packet_list.h"
53 #include "proto_tree.h"
54 #include "simple_dialog.h"
55 #include "stock_icon.h"
56 #include "tap_parameter_dialog.h"
57 #include "wireless_frame.h"
58 #include "wireshark_application.h"
59
60 #include "qt_ui_utils.h"
61
62 #include <QAction>
63 #include <QActionGroup>
64 #include <QDesktopWidget>
65 #include <QKeyEvent>
66 #include <QMessageBox>
67 #include <QMetaObject>
68 #include <QMimeData>
69 #include <QTabWidget>
70 #include <QToolButton>
71 #include <QTreeWidget>
72 #include <QUrl>
73
74 #if defined(QT_MACEXTRAS_LIB) && QT_VERSION < QT_VERSION_CHECK(5, 2, 1)
75 #include <QtMacExtras/QMacNativeToolBar>
76 #endif
77
78
79 //menu_recent_file_write_all
80
81 // If we ever add support for multiple windows this will need to be replaced.
82 static MainWindow *gbl_cur_main_window_ = NULL;
83
84 void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
85 {
86     gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
87 }
88
89 static void plugin_if_mainwindow_apply_filter(gconstpointer user_data)
90 {
91     if ( gbl_cur_main_window_ != NULL && user_data != NULL )
92     {
93         GHashTable * dataSet = (GHashTable *) user_data;
94
95         if ( g_hash_table_lookup_extended(dataSet, "filter_string", NULL, NULL ) )
96         {
97             QString filter((const char *)g_hash_table_lookup(dataSet, "filter_string"));
98             gbl_cur_main_window_->filterPackets(filter);
99         }
100     }
101 }
102
103 static void plugin_if_mainwindow_preference(gconstpointer user_data)
104 {
105     if ( gbl_cur_main_window_ != NULL && user_data != NULL )
106     {
107         GHashTable * dataSet = (GHashTable *) user_data;
108         const char * module_name;
109         const char * pref_name;
110         const char * pref_value;
111         if ( g_hash_table_lookup_extended(dataSet, "pref_module", NULL, (void**)&module_name ) &&
112                 g_hash_table_lookup_extended(dataSet, "pref_key", NULL, (void**)&pref_name ) &&
113                 g_hash_table_lookup_extended(dataSet, "pref_value", NULL, (void**)&pref_value ) )
114         {
115             if ( prefs_store_ext(module_name, pref_name, pref_value) )
116             {
117                 wsApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
118                 wsApp->emitAppSignal(WiresharkApplication::PreferencesChanged);
119             }
120         }
121     }
122 }
123
124 static void plugin_if_mainwindow_gotoframe(gconstpointer user_data)
125 {
126     if ( gbl_cur_main_window_ != NULL && user_data != NULL )
127     {
128         GHashTable * dataSet = (GHashTable *) user_data;
129         gpointer framenr;
130
131         if ( g_hash_table_lookup_extended(dataSet, "frame_nr", NULL, &framenr ) )
132         {
133             if ( GPOINTER_TO_UINT(framenr) != 0 )
134                 gbl_cur_main_window_->gotoFrame(GPOINTER_TO_UINT(framenr));
135         }
136     }
137 }
138
139 #ifdef HAVE_LIBPCAP
140
141 static void plugin_if_mainwindow_get_ws_info(gconstpointer user_data)
142 {
143     if (gbl_cur_main_window_ != NULL && user_data != NULL)
144     {
145         GHashTable * dataSet = (GHashTable *)user_data;
146         ws_info_t *ws_info = NULL;
147
148         if (g_hash_table_lookup_extended(dataSet, "ws_info", NULL, (void**)&ws_info))
149         {
150             CaptureFile *cfWrap = gbl_cur_main_window_->captureFile();
151             capture_file *cf = cfWrap->capFile();
152
153             ws_info->ws_info_supported = true;
154
155             if (cf != NULL)
156             {
157                 ws_info->cf_state = cf->state;
158                 ws_info->cf_count = cf->count;
159
160                 if (ws_info->cf_filename != NULL)
161                     g_free(ws_info->cf_filename);
162                 ws_info->cf_filename = g_strdup(cf->filename);
163
164                 if (cf->state == FILE_READ_DONE)
165                 {
166                     ws_info->cf_framenr = (cf->current_frame)->num;
167                     ws_info->frame_passed_dfilter = ((cf->current_frame)->flags.passed_dfilter) == 0 ? FALSE : TRUE;
168                 }
169                 else
170                 {
171                     ws_info->cf_framenr = 0;
172                     ws_info->frame_passed_dfilter = FALSE;
173                 }
174             }
175             else if (ws_info->cf_state != FILE_CLOSED)
176             {
177                 /* Initialise the ws_info structure */
178                 ws_info->cf_count = 0;
179
180                 if (ws_info->cf_filename != NULL)
181                     g_free(ws_info->cf_filename);
182                 ws_info->cf_filename = NULL;
183
184                 ws_info->cf_framenr = 0;
185                 ws_info->frame_passed_dfilter = false;
186                 ws_info->cf_state = FILE_CLOSED;
187             }
188         }
189     }
190 }
191
192 #endif /* HAVE_LIBPCAP */
193
194 gpointer
195 simple_dialog(ESD_TYPE_E type, gint btn_mask, const gchar *msg_format, ...)
196 {
197     va_list ap;
198
199     va_start(ap, msg_format);
200     SimpleDialog sd(gbl_cur_main_window_, type, btn_mask, msg_format, ap);
201     va_end(ap);
202
203     sd.exec();
204     return NULL;
205 }
206
207 /*
208  * Alert box, with optional "don't show this message again" variable
209  * and checkbox, and optional secondary text.
210  */
211 void
212 simple_message_box(ESD_TYPE_E type, gboolean *notagain,
213                    const char *secondary_msg, const char *msg_format, ...)
214 {
215     if (notagain && *notagain) {
216         return;
217     }
218
219     va_list ap;
220
221     va_start(ap, msg_format);
222     SimpleDialog sd(gbl_cur_main_window_, type, ESD_BTN_OK, msg_format, ap);
223     va_end(ap);
224
225     sd.setDetailedText(secondary_msg);
226
227 #if (QT_VERSION > QT_VERSION_CHECK(5, 2, 0))
228     QCheckBox *cb = NULL;
229     if (notagain) {
230         cb = new QCheckBox();
231         cb->setChecked(true);
232         cb->setText(QObject::tr("Don't show this message again."));
233         sd.setCheckBox(cb);
234     }
235 #endif
236
237     sd.exec();
238
239 #if (QT_VERSION > QT_VERSION_CHECK(5, 2, 0))
240     if (notagain && cb) {
241         *notagain = cb->isChecked();
242     }
243 #endif
244 }
245
246 /*
247  * Error alert box, taking a format and a va_list argument.
248  */
249 void
250 vsimple_error_message_box(const char *msg_format, va_list ap)
251 {
252     SimpleDialog sd(gbl_cur_main_window_, ESD_TYPE_ERROR, ESD_BTN_OK, msg_format, ap);
253     sd.exec();
254 }
255
256
257 QMenu* MainWindow::findOrAddMenu(QMenu *parent_menu, QString& menu_text) {
258     QList<QAction *> actions = parent_menu->actions();
259     QList<QAction *>::const_iterator i;
260     for (i = actions.constBegin(); i != actions.constEnd(); ++i) {
261         if ((*i)->text()==menu_text) {
262             return (*i)->menu();
263         }
264     }
265     // If we get here there menu entry was not found, add a sub menu
266     return parent_menu->addMenu(menu_text);
267 }
268
269 MainWindow::MainWindow(QWidget *parent) :
270     QMainWindow(parent),
271     main_ui_(new Ui::MainWindow),
272     cur_layout_(QVector<unsigned>()),
273     df_combo_box_(new DisplayFilterCombo()),
274     packet_list_(NULL),
275     proto_tree_(NULL),
276     previous_focus_(NULL),
277     show_hide_actions_(NULL),
278     time_display_actions_(NULL),
279     time_precision_actions_(NULL),
280     funnel_statistics_(new FunnelStatistics(this, capture_file_)),
281     freeze_focus_(NULL),
282     capture_stopping_(false),
283     capture_filter_valid_(false),
284 #ifdef HAVE_LIBPCAP
285     info_data_(),
286 #endif
287 #ifdef _WIN32
288     pipe_timer_(NULL)
289 #else
290     pipe_notifier_(NULL)
291 #endif
292 {
293     if (!gbl_cur_main_window_) {
294         connect(wsApp, SIGNAL(openStatCommandDialog(QString,const char*,void*)),
295                 this, SLOT(openStatCommandDialog(QString,const char*,void*)));
296         connect(wsApp, SIGNAL(openTapParameterDialog(QString,const QString,void*)),
297                 this, SLOT(openTapParameterDialog(QString,const QString,void*)));
298     }
299     gbl_cur_main_window_ = this;
300 #ifdef HAVE_LIBPCAP
301     capture_session_init(&cap_session_, CaptureFile::globalCapFile());
302 #endif
303     main_ui_->setupUi(this);
304     setWindowIcon(wsApp->normalIcon());
305     setTitlebarForCaptureFile();
306     setMenusForCaptureFile();
307     setForCapturedPackets(false);
308     setMenusForFileSet(false);
309     interfaceSelectionChanged();
310     loadWindowGeometry();
311
312 #ifndef HAVE_LUA
313     main_ui_->actionAnalyzeReloadLuaPlugins->setVisible(false);
314 #endif
315
316     //To prevent users use features before initialization complete
317     //Otherwise unexpected problems may occur
318     setFeaturesEnabled(false);
319     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled()));
320     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(zoomText()));
321     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initViewColorizeMenu()));
322     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu()));
323     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addDynamicMenus()));
324     connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addExternalMenus()));
325
326     connect(wsApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry()));
327     connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes()));
328     connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutToolbars()));
329     connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(updatePreferenceActions()));
330     connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(zoomText()));
331     connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(setTitlebarForCaptureFile()));
332
333     connect(wsApp, SIGNAL(updateRecentItemStatus(const QString &, qint64, bool)), this, SLOT(updateRecentFiles()));
334     updateRecentFiles();
335
336 #ifdef HAVE_LIBPCAP
337     connect(&capture_interfaces_dialog_, SIGNAL(startCapture()), this, SLOT(startCapture()));
338     connect(&capture_interfaces_dialog_, SIGNAL(stopCapture()), this, SLOT(stopCapture()));
339 #endif
340
341     const DisplayFilterEdit *df_edit = dynamic_cast<DisplayFilterEdit *>(df_combo_box_->lineEdit());
342     connect(df_edit, SIGNAL(pushFilterSyntaxStatus(const QString&)),
343             main_ui_->statusBar, SLOT(pushFilterStatus(const QString&)));
344     connect(df_edit, SIGNAL(popFilterSyntaxStatus()), main_ui_->statusBar, SLOT(popFilterStatus()));
345     connect(df_edit, SIGNAL(pushFilterSyntaxWarning(const QString&)),
346             main_ui_->statusBar, SLOT(pushTemporaryStatus(const QString&)));
347     connect(df_edit, SIGNAL(filterPackets(QString,bool)), this, SLOT(filterPackets(QString,bool)));
348     connect(df_edit, SIGNAL(showPreferencesDialog(PreferencesDialog::PreferencesPane)),
349             this, SLOT(showPreferencesDialog(PreferencesDialog::PreferencesPane)));
350     connect(wsApp, SIGNAL(preferencesChanged()), df_edit, SLOT(checkFilter()));
351
352     connect(df_edit, SIGNAL(textChanged(QString)), funnel_statistics_, SLOT(displayFilterTextChanged(QString)));
353     connect(funnel_statistics_, SIGNAL(setDisplayFilter(QString)), df_edit, SLOT(setText(QString)));
354     connect(funnel_statistics_, SIGNAL(applyDisplayFilter()), df_combo_box_, SLOT(applyDisplayFilter()));
355     connect(funnel_statistics_, SIGNAL(openCaptureFile(QString,QString)),
356             this, SLOT(openCaptureFile(QString,QString)));
357     connect(this, SIGNAL(displayFilterSuccess(bool)), df_edit, SLOT(displayFilterSuccess(bool)));
358
359     initMainToolbarIcons();
360
361     // In Qt4 multiple toolbars and "pretty" are mutually exculsive on OS X. If
362     // unifiedTitleAndToolBarOnMac is enabled everything ends up in the same row.
363     // https://bugreports.qt-project.org/browse/QTBUG-22433
364     // This property is obsolete in Qt5 so this issue may be fixed in that version.
365     main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionDisplayFilterExpression, df_combo_box_);
366
367     wireless_frame_ = new WirelessFrame(this);
368     main_ui_->wirelessToolBar->addWidget(wireless_frame_);
369     connect(wireless_frame_, SIGNAL(pushAdapterStatus(const QString&)),
370             main_ui_->statusBar, SLOT(pushTemporaryStatus(const QString&)));
371     connect (wireless_frame_, SIGNAL(showWirelessPreferences(QString)),
372              this, SLOT(showPreferencesDialog(QString)));
373
374     main_ui_->goToFrame->hide();
375     connect(main_ui_->goToFrame, SIGNAL(visibilityChanged(bool)),
376             main_ui_->actionGoGoToPacket, SLOT(setChecked(bool)));
377
378     // XXX For some reason the cursor is drawn funny with an input mask set
379     // https://bugreports.qt-project.org/browse/QTBUG-7174
380
381     main_ui_->searchFrame->hide();
382     connect(main_ui_->searchFrame, SIGNAL(pushFilterSyntaxStatus(const QString&)),
383             main_ui_->statusBar, SLOT(pushTemporaryStatus(const QString&)));
384     connect(main_ui_->searchFrame, SIGNAL(visibilityChanged(bool)),
385             main_ui_->actionEditFindPacket, SLOT(setChecked(bool)));
386
387     main_ui_->addressEditorFrame->hide();
388     main_ui_->columnEditorFrame->hide();
389     main_ui_->preferenceEditorFrame->hide();
390     main_ui_->filterExpressionFrame->hide();
391
392 #ifndef HAVE_LIBPCAP
393     main_ui_->menuCapture->setEnabled(false);
394 #endif
395
396 #if defined(Q_OS_MAC)
397 #if defined(QT_MACEXTRAS_LIB) && QT_VERSION < QT_VERSION_CHECK(5, 2, 1)
398     QMacNativeToolBar *ntb = QtMacExtras::setNativeToolBar(main_ui_->mainToolBar);
399     ntb->setIconSize(QSize(24, 24));
400 #endif // QT_MACEXTRAS_LIB
401
402     main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true);
403     main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true);
404     main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true);
405
406     main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole);
407
408 #endif // Q_OS_MAC
409
410 #ifdef HAVE_SOFTWARE_UPDATE
411     QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout);
412     QAction *update_action = new QAction(tr("Check for Updates" UTF8_HORIZONTAL_ELLIPSIS), main_ui_->menuHelp);
413     main_ui_->menuHelp->insertAction(update_sep, update_action);
414     connect(update_action, SIGNAL(triggered()), this, SLOT(checkForUpdates()));
415 #endif
416     master_split_.setObjectName("splitterMaster");
417     extra_split_.setObjectName("splitterExtra");
418     main_ui_->mainStack->addWidget(&master_split_);
419
420     empty_pane_.setObjectName("emptyPane");
421
422     packet_list_ = new PacketList(&master_split_);
423
424     proto_tree_ = new ProtoTree(&master_split_);
425     proto_tree_->installEventFilter(this);
426
427     byte_view_tab_ = new ByteViewTab(&master_split_);
428
429     packet_list_->setProtoTree(proto_tree_);
430     packet_list_->setByteViewTab(byte_view_tab_);
431     packet_list_->installEventFilter(this);
432
433     main_welcome_ = main_ui_->welcomePage;
434
435     // Packet list and proto tree must exist before these are called.
436     setMenusForSelectedPacket();
437     setMenusForSelectedTreeRow();
438
439     initShowHideMainWidgets();
440     initTimeDisplayFormatMenu();
441     initTimePrecisionFormatMenu();
442     initFreezeActions();
443     updatePreferenceActions();
444     updateRecentActions();
445     setForCaptureInProgress(false);
446
447     setTabOrder(df_combo_box_, packet_list_);
448
449     connect(&capture_file_, SIGNAL(captureCapturePrepared(capture_session *)),
450             this, SLOT(captureCapturePrepared(capture_session *)));
451     connect(&capture_file_, SIGNAL(captureCaptureUpdateStarted(capture_session *)),
452             this, SLOT(captureCaptureUpdateStarted(capture_session *)));
453     connect(&capture_file_, SIGNAL(captureCaptureUpdateFinished(capture_session *)),
454             this, SLOT(captureCaptureUpdateFinished(capture_session *)));
455     connect(&capture_file_, SIGNAL(captureCaptureFixedStarted(capture_session *)),
456             this, SLOT(captureCaptureFixedStarted(capture_session *)));
457     connect(&capture_file_, SIGNAL(captureCaptureFixedContinue(capture_session *)),
458             main_ui_->statusBar, SLOT(updateCaptureFixedStatistics(capture_session*)));
459     connect(&capture_file_, SIGNAL(captureCaptureFixedFinished(capture_session *)),
460             this, SLOT(captureCaptureFixedFinished(capture_session *)));
461     connect(&capture_file_, SIGNAL(captureCaptureStopping(capture_session *)),
462             this, SLOT(captureCaptureStopping(capture_session *)));
463     connect(&capture_file_, SIGNAL(captureCaptureFailed(capture_session *)),
464             this, SLOT(captureCaptureFailed(capture_session *)));
465     connect(&capture_file_, SIGNAL(captureCaptureUpdateContinue(capture_session*)),
466             main_ui_->statusBar, SLOT(updateCaptureStatistics(capture_session*)));
467
468     connect(&capture_file_, SIGNAL(captureCaptureUpdateStarted(capture_session *)),
469             wsApp, SLOT(captureStarted()));
470     connect(&capture_file_, SIGNAL(captureCaptureUpdateFinished(capture_session *)),
471             wsApp, SLOT(captureFinished()));
472     connect(&capture_file_, SIGNAL(captureCaptureFixedStarted(capture_session *)),
473             wsApp, SLOT(captureStarted()));
474     connect(&capture_file_, SIGNAL(captureCaptureFixedFinished(capture_session *)),
475             wsApp, SLOT(captureFinished()));
476
477     connect(&capture_file_, SIGNAL(captureFileOpened()),
478             this, SLOT(captureFileOpened()));
479     connect(&capture_file_, SIGNAL(captureFileReadStarted()),
480             this, SLOT(captureFileReadStarted()));
481     connect(&capture_file_, SIGNAL(captureFileReadFinished()),
482             this, SLOT(captureFileReadFinished()));
483     connect(&capture_file_, SIGNAL(captureFileReloadStarted()),
484             this, SLOT(captureFileReloadStarted()));
485     connect(&capture_file_, SIGNAL(captureFileReloadFinished()),
486             this, SLOT(captureFileReadFinished()));
487     connect(&capture_file_, SIGNAL(captureFileRescanStarted()),
488             this, SLOT(captureFileRescanStarted()));
489     connect(&capture_file_, SIGNAL(captureFileRescanFinished()),
490             this, SLOT(captureFileReadFinished()));
491     connect(&capture_file_, SIGNAL(captureFileRetapStarted()),
492             this, SLOT(captureFileRetapStarted()));
493     connect(&capture_file_, SIGNAL(captureFileRetapFinished()),
494             this, SLOT(captureFileRetapFinished()));
495     connect(&capture_file_, SIGNAL(captureFileFlushTapsData()),
496             this, SLOT(captureFileFlushTapsData()));
497     connect(&capture_file_, SIGNAL(captureFileClosing()),
498             this, SLOT(captureFileClosing()));
499     connect(&capture_file_, SIGNAL(captureFileClosed()),
500             this, SLOT(captureFileClosed()));
501
502     connect(&capture_file_, SIGNAL(captureFileSaveStarted(QString)),
503             this, SLOT(captureFileSaveStarted(QString)));
504     connect(&capture_file_, SIGNAL(captureFileSaveFinished()),
505             main_ui_->statusBar, SLOT(popFileStatus()));
506     connect(&capture_file_, SIGNAL(captureFileSaveFailed()),
507             main_ui_->statusBar, SLOT(popFileStatus()));
508     connect(&capture_file_, SIGNAL(captureFileSaveStopped()),
509             main_ui_->statusBar, SLOT(popFileStatus()));
510
511     connect(&capture_file_, SIGNAL(captureFileReadStarted()),
512             wsApp, SLOT(captureFileReadStarted()));
513     connect(&capture_file_, SIGNAL(captureFileReadFinished()),
514             wsApp, SLOT(updateTaps()));
515
516     connect(wsApp, SIGNAL(columnsChanged()),
517             packet_list_, SLOT(columnsChanged()));
518     connect(wsApp, SIGNAL(preferencesChanged()),
519             packet_list_, SLOT(preferencesChanged()));
520     connect(wsApp, SIGNAL(recentFilesRead()),
521             this, SLOT(applyRecentPaneGeometry()));
522     connect(wsApp, SIGNAL(recentFilesRead()),
523             this, SLOT(updateRecentActions()));
524     connect(wsApp, SIGNAL(packetDissectionChanged()),
525             this, SLOT(redissectPackets()), Qt::QueuedConnection);
526     connect(wsApp, SIGNAL(appInitialized()),
527             this, SLOT(filterExpressionsChanged()));
528     connect(wsApp, SIGNAL(filterExpressionsChanged()),
529             this, SLOT(filterExpressionsChanged()));
530     connect(wsApp, SIGNAL(checkDisplayFilter()),
531             this, SLOT(checkDisplayFilter()));
532     connect(wsApp, SIGNAL(fieldsChanged()),
533             this, SLOT(fieldsChanged()));
534     connect(wsApp, SIGNAL(reloadLuaPlugins()),
535             this, SLOT(reloadLuaPlugins()));
536
537     connect(main_ui_->mainStack, SIGNAL(currentChanged(int)),
538             this, SLOT(mainStackChanged(int)));
539
540     connect(main_welcome_, SIGNAL(startCapture()),
541             this, SLOT(startCapture()));
542     connect(main_welcome_, SIGNAL(recentFileActivated(QString)),
543             this, SLOT(openCaptureFile(QString)));
544     connect(main_welcome_, SIGNAL(pushFilterSyntaxStatus(const QString&)),
545             main_ui_->statusBar, SLOT(pushFilterStatus(const QString&)));
546     connect(main_welcome_, SIGNAL(popFilterSyntaxStatus()),
547             main_ui_->statusBar, SLOT(popFilterStatus()));
548
549     connect(main_ui_->addressEditorFrame, SIGNAL(editAddressStatus(QString)),
550             main_ui_->statusBar, SLOT(pushTemporaryStatus(QString)));
551     connect(main_ui_->addressEditorFrame, SIGNAL(redissectPackets()),
552             this, SLOT(redissectPackets()));
553     connect(main_ui_->addressEditorFrame, SIGNAL(showNameResolutionPreferences(QString)),
554             this, SLOT(showPreferencesDialog(QString)));
555     connect(main_ui_->preferenceEditorFrame, SIGNAL(showProtocolPreferences(QString)),
556             this, SLOT(showPreferencesDialog(QString)));
557     connect(main_ui_->filterExpressionFrame, SIGNAL(showPreferencesDialog(PreferencesDialog::PreferencesPane)),
558             this, SLOT(showPreferencesDialog(PreferencesDialog::PreferencesPane)));
559     connect(main_ui_->filterExpressionFrame, SIGNAL(filterExpressionsChanged()),
560             this, SLOT(filterExpressionsChanged()));
561
562     connect(this, SIGNAL(setCaptureFile(capture_file*)),
563             main_ui_->searchFrame, SLOT(setCaptureFile(capture_file*)));
564     connect(this, SIGNAL(setCaptureFile(capture_file*)),
565             main_ui_->statusBar, SLOT(setCaptureFile(capture_file*)));
566     connect(this, SIGNAL(setCaptureFile(capture_file*)),
567             packet_list_, SLOT(setCaptureFile(capture_file*)));
568     connect(this, SIGNAL(setCaptureFile(capture_file*)),
569             byte_view_tab_, SLOT(setCaptureFile(capture_file*)));
570
571     connect(this, SIGNAL(monospaceFontChanged(QFont)),
572             packet_list_, SLOT(setMonospaceFont(QFont)));
573     connect(this, SIGNAL(monospaceFontChanged(QFont)),
574             proto_tree_, SLOT(setMonospaceFont(QFont)));
575     connect(this, SIGNAL(monospaceFontChanged(QFont)),
576             byte_view_tab_, SLOT(setMonospaceFont(QFont)));
577
578     connect(main_ui_->actionGoNextPacket, SIGNAL(triggered()),
579             packet_list_, SLOT(goNextPacket()));
580     connect(main_ui_->actionGoPreviousPacket, SIGNAL(triggered()),
581             packet_list_, SLOT(goPreviousPacket()));
582     connect(main_ui_->actionGoFirstPacket, SIGNAL(triggered()),
583             packet_list_, SLOT(goFirstPacket()));
584     connect(main_ui_->actionGoLastPacket, SIGNAL(triggered()),
585             packet_list_, SLOT(goLastPacket()));
586
587     connect(main_ui_->actionViewExpandSubtrees, SIGNAL(triggered()),
588             proto_tree_, SLOT(expandSubtrees()));
589     connect(main_ui_->actionViewExpandAll, SIGNAL(triggered()),
590             proto_tree_, SLOT(expandAll()));
591     connect(main_ui_->actionViewCollapseAll, SIGNAL(triggered()),
592             proto_tree_, SLOT(collapseAll()));
593
594     connect(packet_list_, SIGNAL(packetSelectionChanged()),
595             this, SLOT(setMenusForSelectedPacket()));
596     connect(packet_list_, SIGNAL(packetDissectionChanged()),
597             this, SLOT(redissectPackets()));
598     connect(packet_list_, SIGNAL(showColumnPreferences(PreferencesDialog::PreferencesPane)),
599             this, SLOT(showPreferencesDialog(PreferencesDialog::PreferencesPane)));
600     connect(packet_list_, SIGNAL(showProtocolPreferences(QString)),
601             this, SLOT(showPreferencesDialog(QString)));
602     connect(packet_list_, SIGNAL(editProtocolPreference(preference*,pref_module*)),
603             main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*,pref_module*)));
604     connect(packet_list_, SIGNAL(editColumn(int)), this, SLOT(showColumnEditor(int)));
605     connect(main_ui_->columnEditorFrame, SIGNAL(columnEdited()),
606             packet_list_, SLOT(columnsChanged()));
607     connect(packet_list_, SIGNAL(doubleClicked(QModelIndex)),
608             this, SLOT(openPacketDialog()));
609     connect(packet_list_, SIGNAL(packetListScrolled(bool)),
610             main_ui_->actionGoAutoScroll, SLOT(setChecked(bool)));
611     connect(packet_list_->packetListModel(), SIGNAL(pushBusyStatus(QString)),
612             main_ui_->statusBar, SLOT(pushBusyStatus(QString)));
613     connect(packet_list_->packetListModel(), SIGNAL(popBusyStatus()),
614             main_ui_->statusBar, SLOT(popBusyStatus()));
615     connect(packet_list_->packetListModel(), SIGNAL(pushProgressStatus(QString,bool,bool,gboolean*)),
616             main_ui_->statusBar, SLOT(pushProgressStatus(QString,bool,bool,gboolean*)));
617     connect(packet_list_->packetListModel(), SIGNAL(updateProgressStatus(int)),
618             main_ui_->statusBar, SLOT(updateProgressStatus(int)));
619     connect(packet_list_->packetListModel(), SIGNAL(popProgressStatus()),
620             main_ui_->statusBar, SLOT(popProgressStatus()));
621
622     connect(proto_tree_, SIGNAL(protoItemSelected(const QString&)),
623             main_ui_->statusBar, SLOT(pushFieldStatus(const QString&)));
624     connect(proto_tree_, SIGNAL(protoItemSelected(field_info *)),
625             this, SLOT(setMenusForSelectedTreeRow(field_info *)));
626     connect(proto_tree_, SIGNAL(openPacketInNewWindow(bool)),
627             this, SLOT(openPacketDialog(bool)));
628     connect(proto_tree_, SIGNAL(showProtocolPreferences(QString)),
629             this, SLOT(showPreferencesDialog(QString)));
630     connect(proto_tree_, SIGNAL(editProtocolPreference(preference*,pref_module*)),
631             main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*,pref_module*)));
632
633     connect(byte_view_tab_, SIGNAL(byteFieldHovered(const QString&)),
634             main_ui_->statusBar, SLOT(pushByteStatus(const QString&)));
635
636     connect(main_ui_->statusBar, SIGNAL(showExpertInfo()),
637             this, SLOT(on_actionAnalyzeExpertInfo_triggered()));
638
639     connect(main_ui_->statusBar, SIGNAL(stopLoading()),
640             &capture_file_, SLOT(stopLoading()));
641
642     connect(main_ui_->statusBar, SIGNAL(editCaptureComment()),
643             this, SLOT(on_actionStatisticsCaptureFileProperties_triggered()));
644
645     connect(&file_set_dialog_, SIGNAL(fileSetOpenCaptureFile(QString)),
646             this, SLOT(openCaptureFile(QString)));
647
648 #ifdef HAVE_LIBPCAP
649     QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree");
650     if (iface_tree) {
651         connect(iface_tree, SIGNAL(itemSelectionChanged()),
652                 this, SLOT(interfaceSelectionChanged()));
653     }
654     connect(main_ui_->welcomePage, SIGNAL(captureFilterSyntaxChanged(bool)),
655             this, SLOT(captureFilterSyntaxChanged(bool)));
656
657 #if HAVE_EXTCAP
658         connect(this->main_welcome_, SIGNAL(showExtcapOptions(QString&)),
659                 this, SLOT(showExtcapOptionsDialog(QString&)));
660 #endif
661
662     connect(&capture_interfaces_dialog_, SIGNAL(getPoints(int,PointList*)),
663             this->main_welcome_->getInterfaceTree(), SLOT(getPoints(int,PointList*)));
664     connect(&capture_interfaces_dialog_, SIGNAL(setSelectedInterfaces()),
665             this->main_welcome_->getInterfaceTree(), SLOT(setSelectedInterfaces()));
666     connect(&capture_interfaces_dialog_, SIGNAL(interfaceListChanged()),
667             this->main_welcome_->getInterfaceTree(), SLOT(interfaceListChanged()));
668 #endif
669
670     /* Create plugin_if hooks */
671     plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY, plugin_if_mainwindow_apply_filter );
672     plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE, plugin_if_mainwindow_apply_filter );
673     plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE, plugin_if_mainwindow_preference);
674     plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME, plugin_if_mainwindow_gotoframe);
675 #ifdef HAVE_LIBPCAP
676     plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO, plugin_if_mainwindow_get_ws_info);
677 #endif
678
679     main_ui_->mainStack->setCurrentWidget(main_welcome_);
680 }
681
682 MainWindow::~MainWindow()
683 {
684     delete main_ui_;
685 }
686
687 QString MainWindow::getFilter()
688 {
689     return df_combo_box_->currentText();
690 }
691
692 QMenu *MainWindow::createPopupMenu()
693 {
694     QMenu *menu = new QMenu();
695     menu->addAction(main_ui_->actionViewMainToolbar);
696     menu->addAction(main_ui_->actionViewFilterToolbar);
697     menu->addAction(main_ui_->actionViewWirelessToolbar);
698     menu->addAction(main_ui_->actionViewStatusBar);
699     menu->addSeparator();
700     menu->addAction(main_ui_->actionViewPacketList);
701     menu->addAction(main_ui_->actionViewPacketDetails);
702     menu->addAction(main_ui_->actionViewPacketBytes);
703     return menu;
704 }
705
706 void MainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
707 {
708     pipe_source_        = source;
709     pipe_child_process_ = child_process;
710     pipe_user_data_     = user_data;
711     pipe_input_cb_      = input_cb;
712
713 #ifdef _WIN32
714     /* Tricky to use pipes in win9x, as no concept of wait.  NT can
715        do this but that doesn't cover all win32 platforms.  GTK can do
716        this but doesn't seem to work over processes.  Attempt to do
717        something similar here, start a timer and check for data on every
718        timeout. */
719        /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
720
721     if (pipe_timer_) {
722         disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
723         delete pipe_timer_;
724     }
725
726     pipe_timer_ = new QTimer(this);
727     connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
728     connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
729     pipe_timer_->start(200);
730 #else
731     if (pipe_notifier_) {
732         disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
733         delete pipe_notifier_;
734     }
735
736     pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
737     // XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
738     connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
739     connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
740 #endif
741 }
742
743 bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
744
745     // The user typed some text. Start filling in a filter.
746     // We may need to be more choosy here. We just need to catch events for the packet list,
747     // proto tree, and main welcome widgets.
748     if (event->type() == QEvent::KeyPress) {
749         QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
750         if (kevt->text().length() > 0 && kevt->text()[0].isPrint()) {
751             df_combo_box_->lineEdit()->insert(kevt->text());
752             df_combo_box_->lineEdit()->setFocus();
753             return true;
754         }
755     }
756
757     return QMainWindow::eventFilter(obj, event);
758 }
759
760 void MainWindow::keyPressEvent(QKeyEvent *event) {
761
762     // Explicitly focus on the display filter combo.
763     if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) {
764         df_combo_box_->setFocus(Qt::ShortcutFocusReason);
765         return;
766     }
767
768     if (wsApp->focusWidget() == main_ui_->goToLineEdit) {
769         if (event->modifiers() == Qt::NoModifier) {
770             if (event->key() == Qt::Key_Escape) {
771                 on_goToCancel_clicked();
772             } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
773                 on_goToGo_clicked();
774             }
775         }
776         return; // goToLineEdit didn't want it and we don't either.
777     }
778
779     // Move up & down the packet list.
780     if (event->key() == Qt::Key_F7) {
781         packet_list_->goPreviousPacket();
782     } else if (event->key() == Qt::Key_F8) {
783         packet_list_->goNextPacket();
784     }
785
786     // Move along, citizen.
787     QMainWindow::keyPressEvent(event);
788 }
789
790 void MainWindow::closeEvent(QCloseEvent *event) {
791     saveWindowGeometry();
792
793     /* If we're in the middle of stopping a capture, don't do anything;
794        the user can try deleting the window after the capture stops. */
795     if (capture_stopping_) {
796         event->ignore();
797         return;
798     }
799
800     QString before_what(tr(" before quitting"));
801     if (!testCaptureFileClose(before_what, QuitButtons)) {
802         event->ignore();
803         return;
804     }
805
806 #ifdef HAVE_LIBPCAP
807     capture_interfaces_dialog_.close();
808 #endif
809     // Make sure we kill any open dumpcap processes.
810     delete main_welcome_;
811
812     // One of the many places we assume one main window.
813     if(!wsApp->isInitialized()) {
814         // If we're still initializing, QCoreApplication::quit() won't
815         // exit properly because we are not in the event loop. This
816         // means that the application won't clean up after itself. We
817         // might want to call wsApp->processEvents() during startup
818         // instead so that we can do a normal exit here.
819         exit(0);
820     }
821     wsApp->quit();
822 }
823
824 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
825 {
826     bool accept = false;
827     foreach (QUrl drag_url, event->mimeData()->urls()) {
828         if (!drag_url.toLocalFile().isEmpty()) {
829             accept = true;
830             break;
831         }
832     }
833     if (accept) event->acceptProposedAction();
834 }
835
836 void MainWindow::dropEvent(QDropEvent *event)
837 {
838     foreach (QUrl drop_url, event->mimeData()->urls()) {
839         QString local_file = drop_url.toLocalFile();
840         if (!local_file.isEmpty()) {
841             event->acceptProposedAction();
842             openCaptureFile(local_file);
843             break;
844         }
845     }
846 }
847
848 // Apply recent settings to the main window geometry.
849 // We haven't loaded the preferences at this point so we assume that the
850 // position and size preference are enabled.
851 void MainWindow::loadWindowGeometry()
852 {
853     int min_sensible_dimension = 200;
854
855 #ifndef Q_OS_MAC
856     if (recent.gui_geometry_main_maximized) {
857         setWindowState(Qt::WindowMaximized);
858     } else
859 #endif
860     {
861         QRect recent_geom(recent.gui_geometry_main_x, recent.gui_geometry_main_y,
862                           recent.gui_geometry_main_width, recent.gui_geometry_main_height);
863         if (!rect_on_screen(recent_geom)) {
864             // We're not visible on any screens. Give up and use the default geometry.
865             return;
866         }
867
868 //        if (prefs.gui_geometry_save_position) {
869             move(recent_geom.topLeft());
870 //        }
871
872         if (// prefs.gui_geometry_save_size &&
873                 recent_geom.width() > min_sensible_dimension &&
874                 recent_geom.height() > min_sensible_dimension) {
875             resize(recent_geom.size());
876         }
877     }
878 }
879
880 void MainWindow::saveWindowGeometry()
881 {
882     if (prefs.gui_geometry_save_position) {
883         recent.gui_geometry_main_x = pos().x();
884         recent.gui_geometry_main_y = pos().y();
885     }
886
887     if (prefs.gui_geometry_save_size) {
888         recent.gui_geometry_main_width = size().width();
889         recent.gui_geometry_main_height = size().height();
890     }
891
892     if (prefs.gui_geometry_save_maximized) {
893         // On OS X this is false when it shouldn't be
894         recent.gui_geometry_main_maximized = isMaximized();
895     }
896
897     if (master_split_.sizes().length() > 0) {
898         recent.gui_geometry_main_upper_pane = master_split_.sizes()[0];
899     }
900
901     if (master_split_.sizes().length() > 2) {
902         recent.gui_geometry_main_lower_pane = master_split_.sizes()[1];
903     } else if (extra_split_.sizes().length() > 0) {
904         recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0];
905     }
906 }
907
908 QWidget* MainWindow::getLayoutWidget(layout_pane_content_e type) {
909     switch (type) {
910         case layout_pane_content_none:
911             return &empty_pane_;
912         case layout_pane_content_plist:
913             return packet_list_;
914         case layout_pane_content_pdetails:
915             return proto_tree_;
916         case layout_pane_content_pbytes:
917             return byte_view_tab_;
918         default:
919             g_assert_not_reached();
920             return NULL;
921     }
922 }
923
924 // Our event loop becomes nested whenever we call update_progress_dlg, which
925 // includes several places in file.c. The GTK+ UI stays out of trouble by
926 // showing a modal progress dialog. We attempt to do the equivalent below by
927 // disabling parts of the main window. At a minumum the ProgressFrame in the
928 // main status bar must remain accessible.
929 //
930 // We might want to do this any time the main status bar progress frame is
931 // shown and hidden.
932 void MainWindow::freeze()
933 {
934     freeze_focus_ = wsApp->focusWidget();
935
936     // XXX Alternatively we could just disable and enable the main menu.
937     for (int i = 0; i < freeze_actions_.size(); i++) {
938         QAction *action = freeze_actions_[i].first;
939         freeze_actions_[i].second = action->isEnabled();
940         action->setEnabled(false);
941     }
942     main_ui_->centralWidget->setEnabled(false);
943 }
944
945 void MainWindow::thaw()
946 {
947     main_ui_->centralWidget->setEnabled(true);
948     for (int i = 0; i < freeze_actions_.size(); i++) {
949         freeze_actions_[i].first->setEnabled(freeze_actions_[i].second);
950     }
951
952     if (freeze_focus_) freeze_focus_->setFocus();
953     freeze_focus_ = NULL;
954 }
955
956 void MainWindow::mergeCaptureFile()
957 {
958     QString file_name = "";
959     QString read_filter = "";
960     dfilter_t *rfcode = NULL;
961     int err;
962
963     if (!capture_file_.capFile())
964         return;
965
966     if (prefs.gui_ask_unsaved) {
967         if (cf_has_unsaved_data(capture_file_.capFile())) {
968             QMessageBox msg_dialog;
969             gchar *display_basename;
970             int response;
971
972             msg_dialog.setIcon(QMessageBox::Question);
973             /* This file has unsaved data; ask the user whether to save
974                the capture. */
975             if (capture_file_.capFile()->is_tempfile) {
976                 msg_dialog.setText(tr("Save packets before merging?"));
977                 msg_dialog.setInformativeText(tr("A temporary capture file can't be merged."));
978             } else {
979                 /*
980                  * Format the message.
981                  */
982                 display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
983                 msg_dialog.setText(QString(tr("Save changes in \"%1\" before merging?")).arg(display_basename));
984                 g_free(display_basename);
985                 msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged."));
986             }
987
988             msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
989             msg_dialog.setDefaultButton(QMessageBox::Save);
990
991             response = msg_dialog.exec();
992
993             switch (response) {
994
995             case QMessageBox::Save:
996                 /* Save the file but don't close it */
997                 saveCaptureFile(capture_file_.capFile(), FALSE);
998                 break;
999
1000             case QMessageBox::Cancel:
1001             default:
1002                 /* Don't do the merge. */
1003                 return;
1004             }
1005         }
1006     }
1007
1008     for (;;) {
1009         CaptureFileDialog merge_dlg(this, capture_file_.capFile(), read_filter);
1010         int file_type;
1011         cf_status_t  merge_status;
1012         char        *in_filenames[2];
1013         char        *tmpname;
1014
1015         switch (prefs.gui_fileopen_style) {
1016
1017         case FO_STYLE_LAST_OPENED:
1018             /* The user has specified that we should start out in the last directory
1019            we looked in.  If we've already opened a file, use its containing
1020            directory, if we could determine it, as the directory, otherwise
1021            use the "last opened" directory saved in the preferences file if
1022            there was one. */
1023             /* This is now the default behaviour in file_selection_new() */
1024             break;
1025
1026         case FO_STYLE_SPECIFIED:
1027             /* The user has specified that we should always start out in a
1028            specified directory; if they've specified that directory,
1029            start out by showing the files in that dir. */
1030             if (prefs.gui_fileopen_dir[0] != '\0')
1031                 merge_dlg.setDirectory(prefs.gui_fileopen_dir);
1032             break;
1033         }
1034
1035         if (merge_dlg.merge(file_name)) {
1036             gchar *err_msg;
1037
1038             if (!dfilter_compile(read_filter.toUtf8().constData(), &rfcode, &err_msg)) {
1039                 /* Not valid.  Tell the user, and go back and run the file
1040                    selection box again once they dismiss the alert. */
1041                 //bad_dfilter_alert_box(top_level, read_filter->str);
1042                 QMessageBox::warning(this, tr("Invalid Read Filter"),
1043                                      QString(tr("The filter expression %1 isn't a valid read filter. (%2).").arg(read_filter, err_msg)),
1044                                      QMessageBox::Ok);
1045                 g_free(err_msg);
1046                 continue;
1047             }
1048         } else {
1049             return;
1050         }
1051
1052         file_type = capture_file_.capFile()->cd_t;
1053
1054         /* Try to merge or append the two files */
1055         tmpname = NULL;
1056         if (merge_dlg.mergeType() == 0) {
1057             /* chronological order */
1058             in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1059             in_filenames[1] = qstring_strdup(file_name);
1060             merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, FALSE);
1061         } else if (merge_dlg.mergeType() <= 0) {
1062             /* prepend file */
1063             in_filenames[0] = qstring_strdup(file_name);
1064             in_filenames[1] = g_strdup(capture_file_.capFile()->filename);
1065             merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, TRUE);
1066         } else {
1067             /* append file */
1068             in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1069             in_filenames[1] = qstring_strdup(file_name);
1070             merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, TRUE);
1071         }
1072
1073         g_free(in_filenames[0]);
1074         g_free(in_filenames[1]);
1075
1076         if (merge_status != CF_OK) {
1077             if (rfcode != NULL)
1078                 dfilter_free(rfcode);
1079             g_free(tmpname);
1080             continue;
1081         }
1082
1083         cf_close(capture_file_.capFile());
1084
1085         /* Try to open the merged capture file. */
1086         CaptureFile::globalCapFile()->window = this;
1087         if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, TRUE /* temporary file */, &err) != CF_OK) {
1088             /* We couldn't open it; fail. */
1089             CaptureFile::globalCapFile()->window = NULL;
1090             if (rfcode != NULL)
1091                 dfilter_free(rfcode);
1092             g_free(tmpname);
1093             return;
1094         }
1095
1096         /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
1097            it closed the previous capture file, and thus destroyed any
1098            previous read filter attached to "cf"). */
1099         cf_set_rfcode(CaptureFile::globalCapFile(), rfcode);
1100
1101         switch (cf_read(CaptureFile::globalCapFile(), FALSE)) {
1102
1103         case CF_READ_OK:
1104         case CF_READ_ERROR:
1105             /* Just because we got an error, that doesn't mean we were unable
1106              to read any of the file; we handle what we could get from the
1107              file. */
1108             break;
1109
1110         case CF_READ_ABORTED:
1111             /* The user bailed out of re-reading the capture file; the
1112              capture file has been closed - just free the capture file name
1113              string and return (without changing the last containing
1114              directory). */
1115             g_free(tmpname);
1116             return;
1117         }
1118
1119         /* Save the name of the containing directory specified in the path name,
1120            if any; we can write over cf_merged_name, which is a good thing, given that
1121            "get_dirname()" does write over its argument. */
1122         wsApp->setLastOpenDir(get_dirname(tmpname));
1123         g_free(tmpname);
1124         main_ui_->statusBar->showExpert();
1125         return;
1126     }
1127
1128 }
1129
1130 void MainWindow::importCaptureFile() {
1131     ImportTextDialog import_dlg;
1132
1133     QString before_what(tr(" before importing a new capture"));
1134     if (!testCaptureFileClose(before_what))
1135         return;
1136
1137     import_dlg.exec();
1138
1139     if (import_dlg.result() != QDialog::Accepted) {
1140         main_ui_->mainStack->setCurrentWidget(main_welcome_);
1141         return;
1142     }
1143
1144     openCaptureFile(import_dlg.capfileName());
1145 }
1146
1147 void MainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) {
1148     QString file_name;
1149     gboolean discard_comments;
1150
1151     if (cf->is_tempfile) {
1152         /* This is a temporary capture file, so saving it means saving
1153            it to a permanent file.  Prompt the user for a location
1154            to which to save it.  Don't require that the file format
1155            support comments - if it's a temporary capture file, it's
1156            probably pcap-ng, which supports comments and, if it's
1157            not pcap-ng, let the user decide what they want to do
1158            if they've added comments. */
1159         saveAsCaptureFile(cf, FALSE, dont_reopen);
1160     } else {
1161         if (cf->unsaved_changes) {
1162             cf_write_status_t status;
1163
1164             /* This is not a temporary capture file, but it has unsaved
1165                changes, so saving it means doing a "safe save" on top
1166                of the existing file, in the same format - no UI needed
1167                unless the file has comments and the file's format doesn't
1168                support them.
1169
1170                If the file has comments, does the file's format support them?
1171                If not, ask the user whether they want to discard the comments
1172                or choose a different format. */
1173             switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) {
1174
1175             case SAVE:
1176                 /* The file can be saved in the specified format as is;
1177                    just drive on and save in the format they selected. */
1178                 discard_comments = FALSE;
1179                 break;
1180
1181             case SAVE_WITHOUT_COMMENTS:
1182                 /* The file can't be saved in the specified format as is,
1183                    but it can be saved without the comments, and the user
1184                    said "OK, discard the comments", so save it in the
1185                    format they specified without the comments. */
1186                 discard_comments = TRUE;
1187                 break;
1188
1189             case SAVE_IN_ANOTHER_FORMAT:
1190                 /* There are file formats in which we can save this that
1191                    support comments, and the user said not to delete the
1192                    comments.  Do a "Save As" so the user can select
1193                    one of those formats and choose a file name. */
1194                 saveAsCaptureFile(cf, TRUE, dont_reopen);
1195                 return;
1196
1197             case CANCELLED:
1198                 /* The user said "forget it".  Just return. */
1199                 return;
1200
1201             default:
1202                 /* Squelch warnings that discard_comments is being used
1203                    uninitialized. */
1204                 g_assert_not_reached();
1205                 return;
1206             }
1207
1208             /* XXX - cf->filename might get freed out from under us, because
1209                the code path through which cf_save_records() goes currently
1210                closes the current file and then opens and reloads the saved file,
1211                so make a copy and free it later. */
1212             file_name = cf->filename;
1213             status = cf_save_records(cf, file_name.toUtf8().constData(), cf->cd_t, cf->iscompressed,
1214                                      discard_comments, dont_reopen);
1215             switch (status) {
1216
1217             case CF_WRITE_OK:
1218                 /* The save succeeded; we're done.
1219                    If we discarded comments, redraw the packet list to reflect
1220                    any packets that no longer have comments. */
1221                 if (discard_comments)
1222                     packet_list_queue_draw();
1223
1224                 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1225                 updateForUnsavedChanges(); // we update the title bar to remove the *
1226                 break;
1227
1228             case CF_WRITE_ERROR:
1229                 /* The write failed.
1230                    XXX - OK, what do we do now?  Let them try a
1231                    "Save As", in case they want to try to save to a
1232                    different directory r file system? */
1233                 break;
1234
1235             case CF_WRITE_ABORTED:
1236                 /* The write was aborted; just drive on. */
1237                 break;
1238             }
1239         }
1240         /* Otherwise just do nothing. */
1241     }
1242 }
1243
1244 void MainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) {
1245     QString file_name = "";
1246     int file_type;
1247     gboolean compressed;
1248     cf_write_status_t status;
1249     gchar   *dirname;
1250     gboolean discard_comments = FALSE;
1251
1252     if (!cf) {
1253         return;
1254     }
1255
1256     for (;;) {
1257         CaptureFileDialog save_as_dlg(this, cf);
1258
1259         switch (prefs.gui_fileopen_style) {
1260
1261         case FO_STYLE_LAST_OPENED:
1262             /* The user has specified that we should start out in the last directory
1263                we looked in.  If we've already opened a file, use its containing
1264                directory, if we could determine it, as the directory, otherwise
1265                use the "last opened" directory saved in the preferences file if
1266                there was one. */
1267             /* This is now the default behaviour in file_selection_new() */
1268             break;
1269
1270         case FO_STYLE_SPECIFIED:
1271             /* The user has specified that we should always start out in a
1272                specified directory; if they've specified that directory,
1273                start out by showing the files in that dir. */
1274             if (prefs.gui_fileopen_dir[0] != '\0')
1275                 save_as_dlg.setDirectory(prefs.gui_fileopen_dir);
1276             break;
1277         }
1278
1279         /* If the file has comments, does the format the user selected
1280            support them?  If not, ask the user whether they want to
1281            discard the comments or choose a different format. */
1282         switch(save_as_dlg.saveAs(file_name, must_support_comments)) {
1283
1284         case SAVE:
1285             /* The file can be saved in the specified format as is;
1286                just drive on and save in the format they selected. */
1287             discard_comments = FALSE;
1288             break;
1289
1290         case SAVE_WITHOUT_COMMENTS:
1291             /* The file can't be saved in the specified format as is,
1292                but it can be saved without the comments, and the user
1293                said "OK, discard the comments", so save it in the
1294                format they specified without the comments. */
1295             discard_comments = TRUE;
1296             break;
1297
1298         case SAVE_IN_ANOTHER_FORMAT:
1299             /* There are file formats in which we can save this that
1300                support comments, and the user said not to delete the
1301                comments.  The combo box of file formats has had the
1302                formats that don't support comments trimmed from it,
1303                so run the dialog again, to let the user decide
1304                whether to save in one of those formats or give up. */
1305             must_support_comments = TRUE;
1306             continue;
1307
1308         case CANCELLED:
1309             /* The user said "forget it".  Just get rid of the dialog box
1310                and return. */
1311             return;
1312         }
1313         file_type = save_as_dlg.selectedFileType();
1314         compressed = save_as_dlg.isCompressed();
1315
1316         fileAddExtension(file_name, file_type, compressed);
1317
1318 //#ifndef _WIN32
1319 //        /* If the file exists and it's user-immutable or not writable,
1320 //                       ask the user whether they want to override that. */
1321 //        if (!file_target_unwritable_ui(top_level, file_name.toUtf8().constData())) {
1322 //            /* They don't.  Let them try another file name or cancel. */
1323 //            continue;
1324 //        }
1325 //#endif
1326
1327         /* Attempt to save the file */
1328         status = cf_save_records(cf, file_name.toUtf8().constData(), file_type, compressed,
1329                                  discard_comments, dont_reopen);
1330         switch (status) {
1331
1332         case CF_WRITE_OK:
1333             /* The save succeeded; we're done. */
1334             /* Save the directory name for future file dialogs. */
1335             dirname = get_dirname(qstring_strdup(file_name));  /* Overwrites cf_name */
1336             set_last_open_dir(dirname);
1337             g_free(dirname);
1338             /* If we discarded comments, redraw the packet list to reflect
1339                any packets that no longer have comments. */
1340             if (discard_comments)
1341                 packet_list_queue_draw();
1342
1343             cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1344             updateForUnsavedChanges(); // we update the title bar to remove the *
1345             return;
1346
1347         case CF_WRITE_ERROR:
1348             /* The save failed; let the user try again. */
1349             continue;
1350
1351         case CF_WRITE_ABORTED:
1352             /* The user aborted the save; just return. */
1353             return;
1354         }
1355     }
1356     return;
1357 }
1358
1359 void MainWindow::exportSelectedPackets() {
1360     QString file_name = "";
1361     int file_type;
1362     gboolean compressed;
1363     packet_range_t range;
1364     cf_write_status_t status;
1365     gchar   *dirname;
1366     gboolean discard_comments = FALSE;
1367
1368     if (!capture_file_.capFile())
1369         return;
1370
1371     /* Init the packet range */
1372     packet_range_init(&range, capture_file_.capFile());
1373     range.process_filtered = TRUE;
1374     range.include_dependents = TRUE;
1375
1376     for (;;) {
1377         CaptureFileDialog esp_dlg(this, capture_file_.capFile());
1378
1379         switch (prefs.gui_fileopen_style) {
1380
1381         case FO_STYLE_LAST_OPENED:
1382             /* The user has specified that we should start out in the last directory
1383                we looked in.  If we've already opened a file, use its containing
1384                directory, if we could determine it, as the directory, otherwise
1385                use the "last opened" directory saved in the preferences file if
1386                there was one. */
1387             /* This is now the default behaviour in file_selection_new() */
1388             break;
1389
1390         case FO_STYLE_SPECIFIED:
1391             /* The user has specified that we should always start out in a
1392                specified directory; if they've specified that directory,
1393                start out by showing the files in that dir. */
1394             if (prefs.gui_fileopen_dir[0] != '\0')
1395                 esp_dlg.setDirectory(prefs.gui_fileopen_dir);
1396             break;
1397         }
1398
1399         /* If the file has comments, does the format the user selected
1400            support them?  If not, ask the user whether they want to
1401            discard the comments or choose a different format. */
1402         switch(esp_dlg.exportSelectedPackets(file_name, &range)) {
1403
1404         case SAVE:
1405             /* The file can be saved in the specified format as is;
1406                just drive on and save in the format they selected. */
1407             discard_comments = FALSE;
1408             break;
1409
1410         case SAVE_WITHOUT_COMMENTS:
1411             /* The file can't be saved in the specified format as is,
1412                but it can be saved without the comments, and the user
1413                said "OK, discard the comments", so save it in the
1414                format they specified without the comments. */
1415             discard_comments = TRUE;
1416             break;
1417
1418         case SAVE_IN_ANOTHER_FORMAT:
1419             /* There are file formats in which we can save this that
1420                support comments, and the user said not to delete the
1421                comments.  The combo box of file formats has had the
1422                formats that don't support comments trimmed from it,
1423                so run the dialog again, to let the user decide
1424                whether to save in one of those formats or give up. */
1425             continue;
1426
1427         case CANCELLED:
1428             /* The user said "forget it".  Just get rid of the dialog box
1429                and return. */
1430             return;
1431         }
1432
1433         /*
1434          * Check that we're not going to save on top of the current
1435          * capture file.
1436          * We do it here so we catch all cases ...
1437          * Unfortunately, the file requester gives us an absolute file
1438          * name and the read file name may be relative (if supplied on
1439          * the command line). From Joerg Mayer.
1440          */
1441         if (files_identical(capture_file_.capFile()->filename, file_name.toUtf8().constData())) {
1442             QMessageBox msg_box;
1443             gchar *display_basename = g_filename_display_basename(file_name.toUtf8().constData());
1444
1445             msg_box.setIcon(QMessageBox::Critical);
1446             msg_box.setText(QString(tr("Unable to export to \"%1\".").arg(display_basename)));
1447             msg_box.setInformativeText(tr("You cannot export packets to the current capture file."));
1448             msg_box.setStandardButtons(QMessageBox::Ok);
1449             msg_box.setDefaultButton(QMessageBox::Ok);
1450             msg_box.exec();
1451             g_free(display_basename);
1452             continue;
1453         }
1454
1455         file_type = esp_dlg.selectedFileType();
1456         compressed = esp_dlg.isCompressed();
1457         fileAddExtension(file_name, file_type, compressed);
1458
1459 //#ifndef _WIN32
1460 //        /* If the file exists and it's user-immutable or not writable,
1461 //                       ask the user whether they want to override that. */
1462 //        if (!file_target_unwritable_ui(top_level, file_name.toUtf8().constData())) {
1463 //            /* They don't.  Let them try another file name or cancel. */
1464 //            continue;
1465 //        }
1466 //#endif
1467
1468         /* Attempt to save the file */
1469         status = cf_export_specified_packets(capture_file_.capFile(), file_name.toUtf8().constData(), &range, file_type, compressed);
1470         switch (status) {
1471
1472         case CF_WRITE_OK:
1473             /* The save succeeded; we're done. */
1474             /* Save the directory name for future file dialogs. */
1475             dirname = get_dirname(qstring_strdup(file_name));  /* Overwrites cf_name */
1476             set_last_open_dir(dirname);
1477             g_free(dirname);
1478             /* If we discarded comments, redraw the packet list to reflect
1479                any packets that no longer have comments. */
1480             if (discard_comments)
1481                 packet_list_queue_draw();
1482             return;
1483
1484         case CF_WRITE_ERROR:
1485             /* The save failed; let the user try again. */
1486             continue;
1487
1488         case CF_WRITE_ABORTED:
1489             /* The user aborted the save; just return. */
1490             return;
1491         }
1492     }
1493     return;
1494 }
1495
1496 void MainWindow::exportDissections(export_type_e export_type) {
1497     ExportDissectionDialog ed_dlg(this, capture_file_.capFile(), export_type);
1498     packet_range_t range;
1499
1500     if (!capture_file_.capFile())
1501         return;
1502
1503     /* Init the packet range */
1504     packet_range_init(&range, capture_file_.capFile());
1505     range.process_filtered = TRUE;
1506     range.include_dependents = TRUE;
1507
1508     ed_dlg.exec();
1509 }
1510
1511 void MainWindow::fileAddExtension(QString &file_name, int file_type, bool compressed) {
1512     QString file_name_lower;
1513     QString file_suffix;
1514     GSList  *extensions_list;
1515     gboolean add_extension;
1516
1517     /*
1518      * Append the default file extension if there's none given by
1519      * the user or if they gave one that's not one of the valid
1520      * extensions for the file type.
1521      */
1522     file_name_lower = file_name.toLower();
1523     extensions_list = wtap_get_file_extensions_list(file_type, FALSE);
1524     if (extensions_list != NULL) {
1525         GSList *extension;
1526
1527         /* We have one or more extensions for this file type.
1528            Start out assuming we need to add the default one. */
1529         add_extension = TRUE;
1530
1531         /* OK, see if the file has one of those extensions. */
1532         for (extension = extensions_list; extension != NULL;
1533              extension = g_slist_next(extension)) {
1534             file_suffix += tr(".") + (char *)extension->data;
1535             if (file_name_lower.endsWith(file_suffix)) {
1536                 /*
1537                  * The file name has one of the extensions for
1538                  * this file type.
1539                  */
1540                 add_extension = FALSE;
1541                 break;
1542             }
1543             file_suffix += ".gz";
1544             if (file_name_lower.endsWith(file_suffix)) {
1545                 /*
1546                  * The file name has one of the extensions for
1547                  * this file type.
1548                  */
1549                 add_extension = FALSE;
1550                 break;
1551             }
1552         }
1553     } else {
1554         /* We have no extensions for this file type.  Don't add one. */
1555         add_extension = FALSE;
1556     }
1557     if (add_extension) {
1558         if (wtap_default_file_extension(file_type) != NULL) {
1559             file_name += tr(".") + wtap_default_file_extension(file_type);
1560             if (compressed) {
1561                 file_name += ".gz";
1562             }
1563         }
1564     }
1565 }
1566
1567 bool MainWindow::testCaptureFileClose(QString before_what, FileCloseButtons buttons) {
1568     bool capture_in_progress = false;
1569     bool do_close_file = false;
1570
1571     if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED)
1572         return true; /* Already closed, nothing to do */
1573
1574 #ifdef HAVE_LIBPCAP
1575     if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1576         /* This is true if we're reading a capture file *or* if we're doing
1577          a live capture.  If we're reading a capture file, the main loop
1578          is busy reading packets, and only accepting input from the
1579          progress dialog, so we can't get here, so this means we're
1580          doing a capture. */
1581         capture_in_progress = true;
1582     }
1583 #endif
1584
1585     if (prefs.gui_ask_unsaved) {
1586         if (cf_has_unsaved_data(capture_file_.capFile()) ||
1587             (capture_in_progress && capture_file_.capFile()->count > 0))
1588         {
1589             QMessageBox msg_dialog;
1590             QString question;
1591             QPushButton *saveButton;
1592             QPushButton *discardButton;
1593
1594             msg_dialog.setIcon(QMessageBox::Question);
1595             msg_dialog.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS);
1596             /* This file has unsaved data or there's a capture in
1597                progress; ask the user whether to save the data. */
1598             if (capture_file_.capFile()->is_tempfile) {
1599
1600                 msg_dialog.setText(tr("You have unsaved packets"));
1601                 msg_dialog.setInformativeText(tr("They will be lost if you don't save them."));
1602
1603                 if (capture_in_progress) {
1604                     question.append(tr("Do you want to stop the capture and save the captured packets"));
1605                 } else {
1606                     question.append(tr("Do you want to save the captured packets"));
1607                 }
1608                 question.append(before_what).append(tr("?"));
1609                 msg_dialog.setInformativeText(question);
1610
1611
1612             } else {
1613                 /*
1614                  * Format the message.
1615                  */
1616                 if (capture_in_progress) {
1617                     question.append(tr("Do you want to stop the capture and save the captured packets"));
1618                     question.append(before_what).append(tr("?"));
1619                     msg_dialog.setText(question);
1620                     msg_dialog.setInformativeText(tr("Your captured packets will be lost if you don't save them."));
1621                 } else {
1622                     gchar *display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1623                     question.append(QString(tr("Do you want to save the changes you've made to the capture file \"%1\"%2?"))
1624                                     .arg(display_basename)
1625                                     .arg(before_what)
1626                                     );
1627                     g_free(display_basename);
1628                     msg_dialog.setText(question);
1629                     msg_dialog.setInformativeText(tr("Your changes will be lost if you don't save them."));
1630                 }
1631             }
1632
1633             // XXX Text comes from ui/gtk/stock_icons.[ch]
1634             // Note that the button roles differ from the GTK+ version.
1635             // Cancel = RejectRole
1636             // Save = AcceptRole
1637             // Don't Save = DestructiveRole
1638             msg_dialog.addButton(QMessageBox::Cancel);
1639
1640             if (capture_in_progress) {
1641                 QString saveButtonText;
1642                 if (buttons == RestartButtons) {
1643                     saveButtonText = tr("Save before Continue");
1644                 } else {
1645                     saveButtonText = tr("Stop and Save");
1646                 }
1647                 saveButton = msg_dialog.addButton(saveButtonText, QMessageBox::AcceptRole);
1648             } else {
1649                 saveButton = msg_dialog.addButton(QMessageBox::Save);
1650             }
1651             msg_dialog.setDefaultButton(saveButton);
1652
1653             QString discardButtonText;
1654             if (capture_in_progress) {
1655                 switch (buttons) {
1656                 case QuitButtons:
1657                     discardButtonText = tr("Stop and Quit without Saving");
1658                     break;
1659                 case RestartButtons:
1660                     discardButtonText = tr("Continue without Saving");
1661                     break;
1662                 default:
1663                     discardButtonText = tr("Stop and Continue without Saving");
1664                     break;
1665                 }
1666             } else {
1667                 switch (buttons) {
1668                 case QuitButtons:
1669                     discardButtonText = tr("Quit without Saving");
1670                     break;
1671                 case RestartButtons:
1672                 default:
1673                     discardButtonText = tr("Continue without Saving");
1674                     break;
1675                 }
1676             }
1677             discardButton = msg_dialog.addButton(discardButtonText, QMessageBox::DestructiveRole);
1678
1679             msg_dialog.exec();
1680             /* According to the Qt doc:
1681              * when using QMessageBox with custom buttons, exec() function returns an opaque value.
1682              *
1683              * Therefore we should use clickedButton() to determine which button was clicked. */
1684
1685             if (msg_dialog.clickedButton() == saveButton) {
1686 #ifdef HAVE_LIBPCAP
1687                 /* If there's a capture in progress, we have to stop the capture
1688                    and then do the save. */
1689                 if (capture_in_progress)
1690                     captureStop();
1691 #endif
1692                 /* Save the file and close it */
1693                 saveCaptureFile(capture_file_.capFile(), true);
1694             } else if(msg_dialog.clickedButton() == discardButton) {
1695                 /* Just close the file, discarding changes */
1696                 do_close_file = true;
1697             } else {
1698                 // cancelButton or some other unspecified button
1699                 return false;
1700             }
1701         } else {
1702             /* Unchanged file or capturing with no packets */
1703             do_close_file = true;
1704         }
1705     } else {
1706         /* User asked not to be bothered by those prompts, just close it.
1707          XXX - should that apply only to saving temporary files? */
1708         do_close_file = true;
1709     }
1710
1711     if (do_close_file) {
1712 #ifdef HAVE_LIBPCAP
1713         /* If there's a capture in progress, we have to stop the capture
1714            and then do the close. */
1715         if (capture_in_progress)
1716             captureStop();
1717 #endif
1718         /* captureStop() will close the file if not having any packets */
1719         if (capture_file_.capFile())
1720             cf_close(capture_file_.capFile());
1721     }
1722
1723     return true; /* File closed */
1724 }
1725
1726 void MainWindow::captureStop() {
1727     stopCapture();
1728
1729     while(capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1730         WiresharkApplication::processEvents();
1731     }
1732 }
1733
1734 void MainWindow::initMainToolbarIcons()
1735 {
1736     // Normally 16 px. Reflects current GTK+ behavior and other Windows apps.
1737     int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize);
1738 #if !defined(Q_OS_WIN)
1739     // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
1740     // The OS X HIG specifies 32-pixel icons but they're a little too
1741     // large IMHO.
1742     icon_size = icon_size * 3 / 2;
1743 #endif
1744     main_ui_->mainToolBar->setIconSize(QSize(icon_size, icon_size));
1745
1746     // Toolbar actions. The GNOME HIG says that we should have a menu icon for each
1747     // toolbar item but that clutters up our menu. Set menu icons sparingly.
1748
1749     main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start"));
1750     main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop"));
1751     main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart"));
1752     main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options"));
1753
1754     // Menu icons are disabled in main_window.ui for these items.
1755     main_ui_->actionFileOpen->setIcon(StockIcon("document-open"));
1756     main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save"));
1757     main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close"));
1758     main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload"));
1759
1760     main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find"));
1761     main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous"));
1762     main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next"));
1763     main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump"));
1764     main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first"));
1765     main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last"));
1766     main_ui_->actionGoPreviousConversationPacket->setIcon(StockIcon("go-previous"));
1767     main_ui_->actionGoNextConversationPacket->setIcon(StockIcon("go-next"));
1768 #if defined(Q_OS_MAC)
1769     main_ui_->actionGoPreviousConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Comma));
1770     main_ui_->actionGoNextConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Period));
1771 #endif
1772     main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last"));
1773
1774     main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets"));
1775 //    main_ui_->actionViewAutoScroll->setIcon(StockIcon("x-stay-last"));
1776
1777     QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts();
1778     zi_seq << QKeySequence(Qt::CTRL + Qt::Key_Equal);
1779     main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in"));
1780     main_ui_->actionViewZoomIn->setShortcuts(zi_seq);
1781     main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
1782     main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
1783     main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
1784 }
1785
1786 void MainWindow::initShowHideMainWidgets()
1787 {
1788     if (show_hide_actions_) {
1789         return;
1790     }
1791
1792     show_hide_actions_ = new QActionGroup(this);
1793     QMap<QAction *, QWidget *> shmw_actions;
1794
1795     show_hide_actions_->setExclusive(false);
1796     shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar;
1797     shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar;
1798     shmw_actions[main_ui_->actionViewWirelessToolbar] = main_ui_->wirelessToolBar;
1799     shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar;
1800     shmw_actions[main_ui_->actionViewPacketList] = packet_list_;
1801     shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_;
1802     shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_;
1803
1804     foreach (QAction *shmwa, shmw_actions.keys()) {
1805         shmwa->setData(qVariantFromValue(shmw_actions[shmwa]));
1806         show_hide_actions_->addAction(shmwa);
1807         showHideMainWidgets(shmwa);
1808     }
1809
1810     connect(show_hide_actions_, SIGNAL(triggered(QAction*)), this, SLOT(showHideMainWidgets(QAction*)));
1811 }
1812
1813 Q_DECLARE_METATYPE(ts_type)
1814
1815 void MainWindow::initTimeDisplayFormatMenu()
1816 {
1817     if (time_display_actions_) {
1818         return;
1819     }
1820
1821     time_display_actions_ = new QActionGroup(this);
1822
1823     td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD;
1824     td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY;
1825     td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE;
1826     td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH;
1827     td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceBeginningOfCapture] = TS_RELATIVE;
1828     td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA;
1829     td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS;
1830     td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD;
1831     td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY;
1832     td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC;
1833
1834     foreach (QAction* tda, td_actions.keys()) {
1835         tda->setData(qVariantFromValue(td_actions[tda]));
1836         time_display_actions_->addAction(tda);
1837     }
1838
1839     connect(time_display_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampFormat(QAction*)));
1840 }
1841
1842 Q_DECLARE_METATYPE(ts_precision)
1843
1844 void MainWindow::initTimePrecisionFormatMenu()
1845 {
1846     if (time_precision_actions_) {
1847         return;
1848     }
1849
1850     time_precision_actions_ = new QActionGroup(this);
1851
1852     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO;
1853     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC;
1854     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionDeciseconds] = TS_PREC_FIXED_DSEC;
1855     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionCentiseconds] = TS_PREC_FIXED_CSEC;
1856     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC;
1857     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC;
1858     tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC;
1859
1860     foreach (QAction* tpa, tp_actions.keys()) {
1861         tpa->setData(qVariantFromValue(tp_actions[tpa]));
1862         time_precision_actions_->addAction(tpa);
1863     }
1864
1865     connect(time_precision_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampPrecision(QAction*)));
1866 }
1867
1868 // Menu items which will be disabled when we freeze() and whose state will
1869 // be restored when we thaw(). Add to the list as needed.
1870 void MainWindow::initFreezeActions()
1871 {
1872     QList<QAction *> freeze_actions = QList<QAction *>()
1873             << main_ui_->actionFileClose
1874             << main_ui_->actionViewReload
1875             << main_ui_->actionEditMarkPacket
1876             << main_ui_->actionEditMarkAllDisplayed
1877             << main_ui_->actionEditUnmarkAllDisplayed
1878             << main_ui_->actionEditIgnorePacket
1879             << main_ui_->actionEditIgnoreAllDisplayed
1880             << main_ui_->actionEditUnignoreAllDisplayed
1881             << main_ui_->actionEditSetTimeReference
1882             << main_ui_->actionEditUnsetAllTimeReferences;
1883
1884     foreach (QAction *action, freeze_actions) {
1885         freeze_actions_ << QPair<QAction *, bool>(action, false);
1886     }
1887 }
1888
1889 // Titlebar
1890 void MainWindow::setTitlebarForCaptureFile()
1891 {
1892     if (capture_file_.capFile() && capture_file_.capFile()->filename) {
1893         if (capture_file_.capFile()->is_tempfile) {
1894             //
1895             // For a temporary file, put the source of the data
1896             // in the window title, not whatever random pile
1897             // of characters is the last component of the path
1898             // name.
1899             //
1900             // XXX - on non-Mac platforms, put in the application
1901             // name?
1902             //
1903             setWSWindowTitle(QString("[*]%1").arg(cf_get_tempfile_source(capture_file_.capFile())));
1904         } else {
1905             //
1906             // For a user file, set the full path; that way,
1907             // for OS X, it'll set the "proxy icon".  Qt
1908             // handles extracting the last component.
1909             //
1910             // Sadly, some UN*Xes don't necessarily use UTF-8
1911             // for their file names, so we have to map the
1912             // file path to UTF-8.  If that fails, we're somewhat
1913             // stuck.
1914             //
1915             char *utf8_filename = g_filename_to_utf8(capture_file_.capFile()->filename,
1916                                                      -1,
1917                                                      NULL,
1918                                                      NULL,
1919                                                      NULL);
1920             if (utf8_filename) {
1921                 QFileInfo fi(utf8_filename);
1922                 setWSWindowTitle(fi.fileName());
1923                 setWindowFilePath(utf8_filename);
1924                 g_free(utf8_filename);
1925             } else {
1926                 // So what the heck else can we do here?
1927                 setWSWindowTitle(tr("(File name can't be mapped to UTF-8)"));
1928             }
1929         }
1930         setWindowModified(cf_has_unsaved_data(capture_file_.capFile()));
1931     } else {
1932         /* We have no capture file. */
1933         setWSWindowTitle();
1934     }
1935 }
1936
1937 void MainWindow::setWSWindowTitle(QString title)
1938 {
1939     if (title.isEmpty()) {
1940         title = tr("The Wireshark Network Analyzer");
1941     }
1942
1943     if (prefs.gui_prepend_window_title && prefs.gui_prepend_window_title[0]) {
1944         title.prepend(QString("[%1] ").arg(prefs.gui_prepend_window_title));
1945     }
1946
1947     if (prefs.gui_window_title && prefs.gui_window_title[0]) {
1948 #ifdef __APPLE__
1949         // On OS X we separate the titles with a unicode em dash
1950         title.append(QString(" %1 %2").arg(UTF8_EM_DASH).arg(prefs.gui_window_title));
1951 #else
1952         title.append(QString(" [%1]").arg(prefs.gui_window_title));
1953 #endif
1954     }
1955
1956     setWindowTitle(title);
1957     setWindowFilePath(NULL);
1958 }
1959
1960 void MainWindow::setTitlebarForCaptureInProgress()
1961 {
1962     if (capture_file_.capFile()) {
1963         setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_.capFile())));
1964     } else {
1965         /* We have no capture in progress. */
1966         setWSWindowTitle();
1967     }
1968 }
1969
1970 // Menu state
1971
1972 /* Enable or disable menu items based on whether you have a capture file
1973    you've finished reading and, if you have one, whether it's been saved
1974    and whether it could be saved except by copying the raw packet data. */
1975 void MainWindow::setMenusForCaptureFile(bool force_disable)
1976 {
1977     if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1978         /* We have no capture file or we're currently reading a file */
1979         main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(false);
1980         main_ui_->actionFileMerge->setEnabled(false);
1981         main_ui_->actionFileClose->setEnabled(false);
1982         main_ui_->actionFileSave->setEnabled(false);
1983         main_ui_->actionFileSaveAs->setEnabled(false);
1984         main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
1985         main_ui_->actionFileExportPackets->setEnabled(false);
1986         main_ui_->menuFileExportPacketDissections->setEnabled(false);
1987         main_ui_->actionFileExportPacketBytes->setEnabled(false);
1988         main_ui_->actionFileExportPDU->setEnabled(false);
1989         main_ui_->actionFileExportSSLSessionKeys->setEnabled(false);
1990         main_ui_->menuFileExportObjects->setEnabled(false);
1991         main_ui_->actionViewReload->setEnabled(false);
1992     } else {
1993         main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(true);
1994         main_ui_->actionFileMerge->setEnabled(cf_can_write_with_wiretap(capture_file_.capFile()));
1995
1996         main_ui_->actionFileClose->setEnabled(true);
1997         main_ui_->actionFileSave->setEnabled(cf_can_save(capture_file_.capFile()));
1998         main_ui_->actionFileSaveAs->setEnabled(cf_can_save_as(capture_file_.capFile()));
1999         main_ui_->actionStatisticsCaptureFileProperties->setEnabled(true);
2000         /*
2001          * "Export Specified Packets..." should be available only if
2002          * we can write the file out in at least one format.
2003          */
2004         main_ui_->actionFileExportPackets->setEnabled(cf_can_write_with_wiretap(capture_file_.capFile()));
2005         main_ui_->menuFileExportPacketDissections->setEnabled(true);
2006         main_ui_->actionFileExportPacketBytes->setEnabled(true);
2007         main_ui_->actionFileExportPDU->setEnabled(true);
2008         main_ui_->actionFileExportSSLSessionKeys->setEnabled(true);
2009         main_ui_->menuFileExportObjects->setEnabled(true);
2010         main_ui_->actionViewReload->setEnabled(true);
2011     }
2012 }
2013
2014 void MainWindow::setMenusForCaptureInProgress(bool capture_in_progress) {
2015     /* Either a capture was started or stopped; in either case, it's not
2016        in the process of stopping, so allow quitting. */
2017
2018     main_ui_->actionFileOpen->setEnabled(!capture_in_progress);
2019     main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress);
2020     main_ui_->menuFileExportPacketDissections->setEnabled(capture_in_progress);
2021     main_ui_->actionFileExportPacketBytes->setEnabled(capture_in_progress);
2022     main_ui_->actionFileExportPDU->setEnabled(capture_in_progress);
2023     main_ui_->actionFileExportSSLSessionKeys->setEnabled(capture_in_progress);
2024     main_ui_->menuFileExportObjects->setEnabled(capture_in_progress);
2025     main_ui_->menuFileSet->setEnabled(!capture_in_progress);
2026     main_ui_->actionFileQuit->setEnabled(true);
2027
2028     main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress);
2029
2030     // XXX Fix packet list heading menu sensitivity
2031     //    set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
2032     //                         !capture_in_progress);
2033     //    set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
2034     //                         !capture_in_progress);
2035     //    set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
2036     //                         !capture_in_progress);
2037
2038 #ifdef HAVE_LIBPCAP
2039     main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress);
2040     main_ui_->actionCaptureStart->setEnabled(!capture_in_progress);
2041     main_ui_->actionCaptureStart->setChecked(capture_in_progress);
2042     main_ui_->actionCaptureStop->setEnabled(capture_in_progress);
2043     main_ui_->actionCaptureRestart->setEnabled(capture_in_progress);
2044     main_ui_->actionCaptureRefreshInterfaces->setEnabled(!capture_in_progress);
2045 #endif /* HAVE_LIBPCAP */
2046
2047 }
2048
2049 void MainWindow::setMenusForCaptureStopping() {
2050     main_ui_->actionFileQuit->setEnabled(false);
2051     main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
2052 #ifdef HAVE_LIBPCAP
2053     main_ui_->actionCaptureStart->setChecked(false);
2054     main_ui_->actionCaptureStop->setEnabled(false);
2055     main_ui_->actionCaptureRestart->setEnabled(false);
2056 #endif /* HAVE_LIBPCAP */
2057 }
2058
2059 void MainWindow::setForCapturedPackets(bool have_captured_packets)
2060 {
2061     main_ui_->actionFilePrint->setEnabled(have_captured_packets);
2062
2063 //    set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
2064 //                         have_captured_packets);
2065
2066     main_ui_->actionEditFindPacket->setEnabled(have_captured_packets);
2067     main_ui_->actionEditFindNext->setEnabled(have_captured_packets);
2068     main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets);
2069
2070     main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets);
2071     main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets);
2072     main_ui_->actionGoNextPacket->setEnabled(have_captured_packets);
2073     main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets);
2074     main_ui_->actionGoLastPacket->setEnabled(have_captured_packets);
2075     main_ui_->actionGoNextConversationPacket->setEnabled(have_captured_packets);
2076     main_ui_->actionGoPreviousConversationPacket->setEnabled(have_captured_packets);
2077
2078     main_ui_->actionViewZoomIn->setEnabled(have_captured_packets);
2079     main_ui_->actionViewZoomOut->setEnabled(have_captured_packets);
2080     main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
2081     main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
2082
2083     main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
2084     main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
2085     main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
2086 }
2087
2088 void MainWindow::setMenusForFileSet(bool enable_list_files) {
2089     bool enable_next = fileset_get_next() != NULL && enable_list_files;
2090     bool enable_prev = fileset_get_previous() != NULL && enable_list_files;
2091
2092     main_ui_->actionFileSetListFiles->setEnabled(enable_list_files);
2093     main_ui_->actionFileSetNextFile->setEnabled(enable_next);
2094     main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev);
2095 }
2096
2097 void MainWindow::setWindowIcon(const QIcon &icon) {
2098     wsApp->setWindowIcon(icon);
2099     QMainWindow::setWindowIcon(icon);
2100 }
2101
2102 void MainWindow::updateForUnsavedChanges() {
2103     setTitlebarForCaptureFile();
2104     setMenusForCaptureFile();
2105 }
2106
2107 void MainWindow::changeEvent(QEvent* event)
2108 {
2109     if (0 != event)
2110     {
2111         switch (event->type())
2112         {
2113         case QEvent::LanguageChange:
2114             main_ui_->retranslateUi(this);
2115             // make sure that the "Clear Menu" item is retranslated
2116             updateRecentFiles();
2117             break;
2118         case QEvent::LocaleChange:{
2119             QString locale = QLocale::system().name();
2120             locale.truncate(locale.lastIndexOf('_'));
2121             wsApp->loadLanguage(locale);
2122             }
2123             break;
2124         default:
2125             break;
2126         }
2127     }
2128     QMainWindow::changeEvent(event);
2129 }
2130
2131 /* Update main window items based on whether there's a capture in progress. */
2132 void MainWindow::setForCaptureInProgress(gboolean capture_in_progress)
2133 {
2134     setMenusForCaptureInProgress(capture_in_progress);
2135
2136     wireless_frame_->setCaptureInProgress(capture_in_progress);
2137
2138 #ifdef HAVE_LIBPCAP
2139     packet_list_->setCaptureInProgress(capture_in_progress);
2140 //    set_toolbar_for_capture_in_progress(capture_in_progress);
2141
2142 //    set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
2143 #endif
2144 }
2145
2146 static QList<register_stat_group_t> menu_groups = QList<register_stat_group_t>()
2147             << REGISTER_ANALYZE_GROUP_UNSORTED
2148             << REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER
2149             << REGISTER_STAT_GROUP_UNSORTED
2150             << REGISTER_STAT_GROUP_GENERIC
2151             << REGISTER_STAT_GROUP_CONVERSATION_LIST
2152             << REGISTER_STAT_GROUP_ENDPOINT_LIST
2153             << REGISTER_STAT_GROUP_RESPONSE_TIME
2154             << REGISTER_STAT_GROUP_TELEPHONY
2155             << REGISTER_STAT_GROUP_TELEPHONY_ANSI
2156             << REGISTER_STAT_GROUP_TELEPHONY_GSM
2157             << REGISTER_STAT_GROUP_TELEPHONY_LTE
2158             << REGISTER_STAT_GROUP_TELEPHONY_MTP3
2159             << REGISTER_STAT_GROUP_TELEPHONY_SCTP
2160             << REGISTER_TOOLS_GROUP_UNSORTED;
2161
2162 void MainWindow::addMenuActions(QList<QAction *> &actions, int menu_group)
2163 {
2164     foreach (QAction *action, actions) {
2165         switch (menu_group) {
2166         case REGISTER_ANALYZE_GROUP_UNSORTED:
2167         case REGISTER_STAT_GROUP_UNSORTED:
2168             main_ui_->menuStatistics->insertAction(
2169                             main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
2170                             action);
2171             break;
2172         case REGISTER_STAT_GROUP_RESPONSE_TIME:
2173             main_ui_->menuServiceResponseTime->addAction(action);
2174             break;
2175         case REGISTER_STAT_GROUP_TELEPHONY:
2176             main_ui_->menuTelephony->addAction(action);
2177             break;
2178         case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
2179             main_ui_->menuANSI->addAction(action);
2180             break;
2181         case REGISTER_STAT_GROUP_TELEPHONY_GSM:
2182             main_ui_->menuGSM->addAction(action);
2183             break;
2184         case REGISTER_STAT_GROUP_TELEPHONY_LTE:
2185             main_ui_->menuLTE->addAction(action);
2186             break;
2187         case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
2188             main_ui_->menuMTP3->addAction(action);
2189             break;
2190         case REGISTER_TOOLS_GROUP_UNSORTED:
2191         {
2192             // Allow the creation of submenus. Mimics the behavor of
2193             // ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
2194             // and GtkUIManager.
2195             //
2196             // For now we limit the insanity to the "Tools" menu.
2197             QStringList menu_path = action->text().split('/');
2198             QMenu *cur_menu = main_ui_->menuTools;
2199             while (menu_path.length() > 1) {
2200                 QString menu_title = menu_path.takeFirst();
2201 #if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
2202                 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2203 #else
2204                 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower());
2205                 if (submenu && submenu->parent() != cur_menu) submenu = NULL;
2206 #endif
2207                 if (!submenu) {
2208                     submenu = cur_menu->addMenu(menu_title);
2209                     submenu->setObjectName(menu_title.toLower());
2210                 }
2211                 cur_menu = submenu;
2212             }
2213             action->setText(menu_path.last());
2214             cur_menu->addAction(action);
2215             break;
2216         }
2217         default:
2218 //            qDebug() << "FIX: Add" << action->text() << "to the menu";
2219             break;
2220         }
2221
2222         // Connect each action type to its corresponding slot. We to
2223         // distinguish various types of actions. Setting their objectName
2224         // seems to work OK.
2225         if (action->objectName() == TapParameterDialog::actionName()) {
2226             connect(action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
2227         } else if (action->objectName() == FunnelStatistics::actionName()) {
2228             connect(action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered()));
2229         }
2230     }
2231 }
2232 void MainWindow::removeMenuActions(QList<QAction *> &actions, int menu_group)
2233 {
2234     foreach (QAction *action, actions) {
2235         switch (menu_group) {
2236         case REGISTER_ANALYZE_GROUP_UNSORTED:
2237         case REGISTER_STAT_GROUP_UNSORTED:
2238             main_ui_->menuStatistics->removeAction(action);
2239             break;
2240         case REGISTER_STAT_GROUP_RESPONSE_TIME:
2241             main_ui_->menuServiceResponseTime->removeAction(action);
2242             break;
2243         case REGISTER_STAT_GROUP_TELEPHONY:
2244             main_ui_->menuTelephony->removeAction(action);
2245             break;
2246         case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
2247             main_ui_->menuANSI->removeAction(action);
2248             break;
2249         case REGISTER_STAT_GROUP_TELEPHONY_GSM:
2250             main_ui_->menuGSM->removeAction(action);
2251             break;
2252         case REGISTER_STAT_GROUP_TELEPHONY_LTE:
2253             main_ui_->menuLTE->removeAction(action);
2254             break;
2255         case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
2256             main_ui_->menuMTP3->removeAction(action);
2257             break;
2258         case REGISTER_TOOLS_GROUP_UNSORTED:
2259         {
2260             // Allow removal of submenus.
2261             // For now we limit the insanity to the "Tools" menu.
2262             QStringList menu_path = action->text().split('/');
2263             QMenu *cur_menu = main_ui_->menuTools;
2264             while (menu_path.length() > 1) {
2265                 QString menu_title = menu_path.takeFirst();
2266 #if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
2267                 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2268 #else
2269                 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower());
2270                 if (submenu && submenu->parent() != cur_menu) submenu = NULL;
2271 #endif
2272                 cur_menu = submenu;
2273             }
2274             cur_menu->removeAction(action);
2275             break;
2276         }
2277         default:
2278 //            qDebug() << "FIX: Remove" << action->text() << "from the menu";
2279             break;
2280         }
2281     }
2282 }
2283
2284 void MainWindow::addDynamicMenus()
2285 {
2286     // Manual additions
2287     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_GSM, main_ui_->actionTelephonyGsmMapSummary);
2288     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteMacStatistics);
2289     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcStatistics);
2290     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcGraph);
2291     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_MTP3, main_ui_->actionTelephonyMtp3Summary);
2292     wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY, main_ui_->actionTelephonySipFlows);
2293
2294     // Fill in each menu
2295     foreach (register_stat_group_t menu_group, menu_groups) {
2296         QList<QAction *>actions = wsApp->dynamicMenuGroupItems(menu_group);
2297         addMenuActions(actions, menu_group);
2298     }
2299
2300     // Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
2301     // We've added a placeholder in order to make sure the "Tools" menu is
2302     // visible. Hide it as needed.
2303     if (wsApp->dynamicMenuGroupItems(REGISTER_TOOLS_GROUP_UNSORTED).length() > 0) {
2304         main_ui_->actionToolsPlaceholder->setVisible(false);
2305     }
2306     if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_ANSI).length() > 0) {
2307         main_ui_->actionTelephonyANSIPlaceholder->setVisible(false);
2308     }
2309     if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_GSM).length() > 0) {
2310         main_ui_->actionTelephonyGSMPlaceholder->setVisible(false);
2311     }
2312     if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_LTE).length() > 0) {
2313         main_ui_->actionTelephonyLTEPlaceholder->setVisible(false);
2314     }
2315     if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_MTP3).length() > 0) {
2316         main_ui_->actionTelephonyMTP3Placeholder->setVisible(false);
2317     }
2318 }
2319
2320 void MainWindow::reloadDynamicMenus()
2321 {
2322     foreach (register_stat_group_t menu_group, menu_groups) {
2323         QList<QAction *>actions = wsApp->removedMenuGroupItems(menu_group);
2324         removeMenuActions(actions, menu_group);
2325
2326         actions = wsApp->addedMenuGroupItems(menu_group);
2327         addMenuActions(actions, menu_group);
2328     }
2329
2330     wsApp->clearAddedMenuGroupItems();
2331     wsApp->clearRemovedMenuGroupItems();
2332 }
2333
2334 void MainWindow::externalMenuHelper(ext_menu_t * menu, QMenu  * subMenu, gint depth)
2335 {
2336     QAction * itemAction = NULL;
2337     ext_menubar_t * item = NULL;
2338     GList * children = NULL;
2339
2340     /* There must exists an xpath parent */
2341     g_assert(subMenu != NULL);
2342
2343     /* If the depth counter exceeds, something must have gone wrong */
2344     g_assert(depth < EXT_MENUBAR_MAX_DEPTH);
2345
2346     children = menu->children;
2347     /* Iterate the child entries */
2348     while ( children != NULL && children->data != NULL )
2349     {
2350         item = (ext_menubar_t *) children->data;
2351
2352         if ( item->type == EXT_MENUBAR_MENU )
2353         {
2354             /* Handle Submenu entry */
2355             this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++ );
2356         }
2357         else if ( item->type == EXT_MENUBAR_SEPARATOR )
2358         {
2359             subMenu->addSeparator();
2360         }
2361         else if ( item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL )
2362         {
2363             itemAction = subMenu->addAction(item->name);
2364             itemAction->setData(QVariant::fromValue((void *)item));
2365             itemAction->setText(item->label);
2366             connect(itemAction, SIGNAL(triggered()),
2367                     this, SLOT(externalMenuItem_triggered()));
2368         }
2369
2370         /* Iterate Loop */
2371         children = g_list_next(children);
2372     }
2373 }
2374
2375 QMenu * MainWindow::searchSubMenu(QString objectName)
2376 {
2377     QList<QMenu*> lst;
2378
2379     if ( objectName.length() > 0 )
2380     {
2381         QString searchName = QString("menu") + objectName;
2382
2383         lst = main_ui_->menuBar->findChildren<QMenu*>();
2384         foreach (QMenu* m, lst)
2385         {
2386             if ( QString::compare( m->objectName(), searchName ) == 0 )
2387                 return m;
2388         }
2389     }
2390
2391     return 0;
2392 }
2393
2394 void MainWindow::addExternalMenus()
2395 {
2396     QMenu * subMenu = NULL;
2397     GList * user_menu = NULL;
2398     ext_menu_t * menu = NULL;
2399
2400     user_menu = ext_menubar_get_entries();
2401
2402     while ( ( user_menu != NULL ) && ( user_menu->data != NULL ) )
2403     {
2404         menu = (ext_menu_t *) user_menu->data;
2405
2406         /* On this level only menu items should exist. Not doing an assert here,
2407          * as it could be an honest mistake */
2408         if ( menu->type != EXT_MENUBAR_MENU )
2409         {
2410             user_menu = g_list_next(user_menu);
2411             continue;
2412         }
2413
2414         /* Create main submenu and add it to the menubar */
2415         if ( menu->parent_menu != NULL )
2416         {
2417             QMenu * sortUnderneath = searchSubMenu(QString(menu->parent_menu));
2418             if ( sortUnderneath != NULL)
2419                 subMenu = sortUnderneath->addMenu(menu->label);
2420         }
2421
2422         if ( subMenu == NULL )
2423             subMenu = main_ui_->menuBar->addMenu(menu->label);
2424
2425         /* This will generate the action structure for each menu. It is recursive,
2426          * therefore a sub-routine, and we have a depth counter to prevent endless loops. */
2427         this->externalMenuHelper(menu, subMenu, 0);
2428
2429         /* Iterate Loop */
2430         user_menu = g_list_next (user_menu);
2431     }
2432 }
2433
2434 /*
2435  * Editor modelines
2436  *
2437  * Local Variables:
2438  * c-basic-offset: 4
2439  * tab-width: 8
2440  * indent-tabs-mode: nil
2441  * End:
2442  *
2443  * ex: set shiftwidth=4 tabstop=8 expandtab:
2444  * :indentSize=4:tabSize=8:noTabs=true:
2445  */