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