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