3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
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.
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.
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.
22 #include "main_window.h"
23 #include "ui_main_window.h"
25 #include <epan/addr_resolv.h>
26 #include <epan/epan_dissect.h>
27 #include <wsutil/filesystem.h>
28 #include <epan/prefs.h>
29 #include <epan/stats_tree_priv.h>
30 #include <epan/ext_menubar.h>
33 #include "ui/capture.h"
34 #include <capchild/capture_session.h>
37 #include "ui/alert_box.h"
39 #include "ui/capture_ui_utils.h"
41 #include "ui/capture_globals.h"
42 #include "ui/main_statusbar.h"
43 #include "ui/recent.h"
46 #include "byte_view_tab.h"
47 #include "display_filter_edit.h"
48 #include "export_dissection_dialog.h"
49 #include "import_text_dialog.h"
50 #include "packet_list.h"
51 #include "proto_tree.h"
52 #include "simple_dialog.h"
53 #include "stock_icon.h"
54 #include "wireshark_application.h"
56 #include "qt_ui_utils.h"
59 #include <QActionGroup>
60 #include <QDesktopWidget>
62 #include <QMessageBox>
63 #include <QMetaObject>
66 #include <QToolButton>
67 #include <QTreeWidget>
70 #if defined(QT_MACEXTRAS_LIB) && QT_VERSION < QT_VERSION_CHECK(5, 2, 1)
71 #include <QtMacExtras/QMacNativeToolBar>
75 //menu_recent_file_write_all
77 // If we ever add support for multiple windows this will need to be replaced.
78 static MainWindow *gbl_cur_main_window_ = NULL;
80 void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
82 gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
86 simple_dialog(ESD_TYPE_E type, gint btn_mask, const gchar *msg_format, ...)
90 va_start(ap, msg_format);
91 SimpleDialog sd(gbl_cur_main_window_, type, btn_mask, msg_format, ap);
99 * Alert box, with optional "don't show this message again" variable
100 * and checkbox, and optional secondary text.
103 simple_message_box(ESD_TYPE_E type, gboolean *notagain,
104 const char *secondary_msg, const char *msg_format, ...)
106 if (notagain && *notagain) {
112 va_start(ap, msg_format);
113 SimpleDialog sd(gbl_cur_main_window_, type, ESD_BTN_OK, msg_format, ap);
116 sd.setDetailedText(secondary_msg);
118 #if (QT_VERSION > QT_VERSION_CHECK(5, 2, 0))
119 QCheckBox *cb = new QCheckBox();
121 cb->setChecked(true);
122 cb->setText(QObject::tr("Don't show this message again."));
129 #if (QT_VERSION > QT_VERSION_CHECK(5, 2, 0))
131 *notagain = cb->isChecked();
137 * Error alert box, taking a format and a va_list argument.
140 vsimple_error_message_box(const char *msg_format, va_list ap)
142 SimpleDialog sd(gbl_cur_main_window_, ESD_TYPE_ERROR, ESD_BTN_OK, msg_format, ap);
147 QMenu* MainWindow::findOrAddMenu(QMenu *parent_menu, QString& menu_text) {
148 QList<QAction *> actions = parent_menu->actions();
149 QList<QAction *>::const_iterator i;
150 for (i = actions.constBegin(); i != actions.constEnd(); ++i) {
151 if ((*i)->text()==menu_text) {
155 // If we get here there menu entry was not found, add a sub menu
156 return parent_menu->addMenu(menu_text);
159 MainWindow::MainWindow(QWidget *parent) :
161 main_ui_(new Ui::MainWindow),
162 cur_layout_(QVector<unsigned>()),
163 df_combo_box_(new DisplayFilterCombo()),
164 previous_focus_(NULL),
165 show_hide_actions_(NULL),
166 time_display_actions_(NULL),
167 time_precision_actions_(NULL),
168 capture_stopping_(false),
169 capture_filter_valid_(false),
176 if (!gbl_cur_main_window_) {
177 connect(wsApp, SIGNAL(openStatCommandDialog(QString,const char*,void*)),
178 this, SLOT(openStatCommandDialog(QString,const char*,void*)));
180 gbl_cur_main_window_ = this;
182 capture_session_init(&cap_session_, CaptureFile::globalCapFile());
184 main_ui_->setupUi(this);
185 setWindowIcon(wsApp->normalIcon());
186 setTitlebarForCaptureFile();
187 setMenusForCaptureFile();
188 setForCapturedPackets(false);
189 setMenusForSelectedPacket();
190 setMenusForSelectedTreeRow();
191 setMenusForFileSet(false);
192 interfaceSelectionChanged();
193 loadWindowGeometry();
195 //To prevent users use features before initialization complete
196 //Otherwise unexpected problems may occur
197 setFeaturesEnabled(false);
198 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled()));
199 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(zoomText()));
200 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu()));
201 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addExternalMenus()));
203 connect(wsApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry()));
204 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes()));
205 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutToolbars()));
206 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(updatePreferenceActions()));
207 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(zoomText()));
209 connect(wsApp, SIGNAL(updateRecentItemStatus(const QString &, qint64, bool)), this, SLOT(updateRecentFiles()));
213 connect(&capture_interfaces_dialog_, SIGNAL(startCapture()), this, SLOT(startCapture()));
214 connect(&capture_interfaces_dialog_, SIGNAL(stopCapture()), this, SLOT(stopCapture()));
217 const DisplayFilterEdit *df_edit = dynamic_cast<DisplayFilterEdit *>(df_combo_box_->lineEdit());
218 connect(df_edit, SIGNAL(pushFilterSyntaxStatus(const QString&)),
219 main_ui_->statusBar, SLOT(pushFilterStatus(const QString&)));
220 connect(df_edit, SIGNAL(popFilterSyntaxStatus()), main_ui_->statusBar, SLOT(popFilterStatus()));
221 connect(df_edit, SIGNAL(pushFilterSyntaxWarning(const QString&)),
222 main_ui_->statusBar, SLOT(pushTemporaryStatus(const QString&)));
223 connect(df_edit, SIGNAL(filterPackets(QString&,bool)), this, SLOT(filterPackets(QString&,bool)));
224 connect(df_edit, SIGNAL(addBookmark(QString)), this, SLOT(addDisplayFilterButton(QString)));
225 connect(this, SIGNAL(displayFilterSuccess(bool)), df_edit, SLOT(displayFilterSuccess(bool)));
227 initMainToolbarIcons();
229 // In Qt4 multiple toolbars and "pretty" are mutually exculsive on OS X. If
230 // unifiedTitleAndToolBarOnMac is enabled everything ends up in the same row.
231 // https://bugreports.qt-project.org/browse/QTBUG-22433
232 // This property is obsolete in Qt5 so this issue may be fixed in that version.
233 main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionDisplayFilterExpression, df_combo_box_);
235 main_ui_->goToFrame->hide();
236 // XXX For some reason the cursor is drawn funny with an input mask set
237 // https://bugreports.qt-project.org/browse/QTBUG-7174
239 main_ui_->searchFrame->hide();
240 connect(main_ui_->searchFrame, SIGNAL(pushFilterSyntaxStatus(const QString&)),
241 main_ui_->statusBar, SLOT(pushTemporaryStatus(const QString&)));
243 main_ui_->columnEditorFrame->hide();
246 main_ui_->menuCapture->setEnabled(false);
249 #if defined(Q_OS_MAC)
250 #if defined(QT_MACEXTRAS_LIB) && QT_VERSION < QT_VERSION_CHECK(5, 2, 1)
251 QMacNativeToolBar *ntb = QtMacExtras::setNativeToolBar(main_ui_->mainToolBar);
252 ntb->setIconSize(QSize(24, 24));
253 #endif // QT_MACEXTRAS_LIB
255 main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true);
256 main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true);
257 main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true);
259 main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole);
263 #ifdef HAVE_SOFTWARE_UPDATE
264 QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout);
265 QAction *update_action = new QAction(tr("Check for Updates..."), main_ui_->menuHelp);
266 main_ui_->menuHelp->insertAction(update_sep, update_action);
267 connect(update_action, SIGNAL(triggered()), this, SLOT(checkForUpdates()));
269 master_split_.setObjectName(tr("splitterMaster"));
270 extra_split_.setObjectName(tr("splitterExtra"));
271 main_ui_->mainStack->addWidget(&master_split_);
273 empty_pane_.setObjectName(tr("emptyPane"));
275 packet_list_ = new PacketList(&master_split_);
277 proto_tree_ = new ProtoTree(&master_split_);
278 proto_tree_->installEventFilter(this);
280 byte_view_tab_ = new ByteViewTab(&master_split_);
282 packet_list_->setProtoTree(proto_tree_);
283 packet_list_->setByteViewTab(byte_view_tab_);
284 packet_list_->installEventFilter(this);
286 main_welcome_ = main_ui_->welcomePage;
288 initShowHideMainWidgets();
289 initTimeDisplayFormatMenu();
290 initTimePrecisionFormatMenu();
291 updatePreferenceActions();
292 setForCaptureInProgress(false);
294 connect(&capture_file_, SIGNAL(captureCapturePrepared(capture_session *)),
295 this, SLOT(captureCapturePrepared(capture_session *)));
296 connect(&capture_file_, SIGNAL(captureCaptureUpdateStarted(capture_session *)),
297 this, SLOT(captureCaptureUpdateStarted(capture_session *)));
298 connect(&capture_file_, SIGNAL(captureCaptureUpdateFinished(capture_session *)),
299 this, SLOT(captureCaptureUpdateFinished(capture_session *)));
300 connect(&capture_file_, SIGNAL(captureCaptureFixedStarted(capture_session *)),
301 this, SLOT(captureCaptureFixedStarted(capture_session *)));
302 connect(&capture_file_, SIGNAL(captureCaptureFixedContinue(capture_session *)),
303 main_ui_->statusBar, SLOT(updateCaptureFixedStatistics(capture_session*)));
304 connect(&capture_file_, SIGNAL(captureCaptureFixedFinished(capture_session *)),
305 this, SLOT(captureCaptureFixedFinished(capture_session *)));
306 connect(&capture_file_, SIGNAL(captureCaptureStopping(capture_session *)),
307 this, SLOT(captureCaptureStopping(capture_session *)));
308 connect(&capture_file_, SIGNAL(captureCaptureFailed(capture_session *)),
309 this, SLOT(captureCaptureFailed(capture_session *)));
310 connect(&capture_file_, SIGNAL(captureCaptureUpdateContinue(capture_session*)),
311 main_ui_->statusBar, SLOT(updateCaptureStatistics(capture_session*)));
313 connect(&capture_file_, SIGNAL(captureFileOpened()),
314 this, SLOT(captureFileOpened()));
315 connect(&capture_file_, SIGNAL(captureFileReadStarted()),
316 this, SLOT(captureFileReadStarted()));
317 connect(&capture_file_, SIGNAL(captureFileReadFinished()),
318 this, SLOT(captureFileReadFinished()));
319 connect(&capture_file_, SIGNAL(captureFileReloadStarted()),
320 this, SLOT(captureFileReloadStarted()));
321 connect(&capture_file_, SIGNAL(captureFileReloadFinished()),
322 this, SLOT(captureFileReadFinished()));
323 connect(&capture_file_, SIGNAL(captureFileRescanStarted()),
324 this, SLOT(captureFileRescanStarted()));
325 connect(&capture_file_, SIGNAL(captureFileRescanFinished()),
326 this, SLOT(captureFileReadFinished()));
327 connect(&capture_file_, SIGNAL(captureFileClosing()),
328 this, SLOT(captureFileClosing()));
329 connect(&capture_file_, SIGNAL(captureFileClosed()),
330 this, SLOT(captureFileClosed()));
332 connect(&capture_file_, SIGNAL(captureFileSaveStarted(QString)),
333 this, SLOT(captureFileSaveStarted(QString)));
334 connect(&capture_file_, SIGNAL(captureFileSaveFinished()),
335 main_ui_->statusBar, SLOT(popFileStatus()));
336 connect(&capture_file_, SIGNAL(captureFileSaveFailed()),
337 main_ui_->statusBar, SLOT(popFileStatus()));
338 connect(&capture_file_, SIGNAL(captureFileSaveStopped()),
339 main_ui_->statusBar, SLOT(popFileStatus()));
341 connect(&capture_file_, SIGNAL(setCaptureStopFlag(bool)),
342 this, SLOT(setCaptureStopFlag(bool)));
344 connect(&capture_file_, SIGNAL(captureFileReadStarted()),
345 wsApp, SLOT(captureFileReadStarted()));
346 connect(&capture_file_, SIGNAL(captureFileReadFinished()),
347 wsApp, SLOT(updateTaps()));
349 connect(wsApp, SIGNAL(recentFilesRead()),
350 packet_list_, SLOT(applyRecentColumnWidths()));
351 connect(wsApp, SIGNAL(columnsChanged()),
352 packet_list_, SLOT(redrawVisiblePackets()));
353 connect(wsApp, SIGNAL(recentFilesRead()),
354 this, SLOT(applyRecentPaneGeometry()));
355 connect(wsApp, SIGNAL(packetDissectionChanged()),
356 this, SLOT(redissectPackets()));
357 connect(wsApp, SIGNAL(appInitialized()),
358 this, SLOT(filterExpressionsChanged()));
359 connect(wsApp, SIGNAL(filterExpressionsChanged()),
360 this, SLOT(filterExpressionsChanged()));
361 connect(wsApp, SIGNAL(fieldsChanged()),
362 this, SLOT(fieldsChanged()));
364 connect(main_welcome_, SIGNAL(startCapture()),
365 this, SLOT(startCapture()));
366 connect(main_welcome_, SIGNAL(recentFileActivated(QString&)),
367 this, SLOT(openCaptureFile(QString&)));
368 connect(main_welcome_, SIGNAL(pushFilterSyntaxStatus(const QString&)),
369 main_ui_->statusBar, SLOT(pushFilterStatus(const QString&)));
370 connect(main_welcome_, SIGNAL(popFilterSyntaxStatus()),
371 main_ui_->statusBar, SLOT(popFilterStatus()));
373 connect(this, SIGNAL(setCaptureFile(capture_file*)),
374 main_ui_->searchFrame, SLOT(setCaptureFile(capture_file*)));
375 connect(this, SIGNAL(setCaptureFile(capture_file*)),
376 main_ui_->statusBar, SLOT(setCaptureFile(capture_file*)));
377 connect(this, SIGNAL(setCaptureFile(capture_file*)),
378 packet_list_, SLOT(setCaptureFile(capture_file*)));
379 connect(this, SIGNAL(setCaptureFile(capture_file*)),
380 byte_view_tab_, SLOT(setCaptureFile(capture_file*)));
382 connect(this, SIGNAL(monospaceFontChanged(QFont)),
383 packet_list_, SLOT(setMonospaceFont(QFont)));
384 connect(this, SIGNAL(monospaceFontChanged(QFont)),
385 proto_tree_, SLOT(setMonospaceFont(QFont)));
386 connect(this, SIGNAL(monospaceFontChanged(QFont)),
387 byte_view_tab_, SLOT(setMonospaceFont(QFont)));
389 connect(main_ui_->actionGoNextPacket, SIGNAL(triggered()),
390 packet_list_, SLOT(goNextPacket()));
391 connect(main_ui_->actionGoPreviousPacket, SIGNAL(triggered()),
392 packet_list_, SLOT(goPreviousPacket()));
393 connect(main_ui_->actionGoFirstPacket, SIGNAL(triggered()),
394 packet_list_, SLOT(goFirstPacket()));
395 connect(main_ui_->actionGoLastPacket, SIGNAL(triggered()),
396 packet_list_, SLOT(goLastPacket()));
398 connect(main_ui_->actionViewExpandSubtrees, SIGNAL(triggered()),
399 proto_tree_, SLOT(expandSubtrees()));
400 connect(main_ui_->actionViewExpandAll, SIGNAL(triggered()),
401 proto_tree_, SLOT(expandAll()));
402 connect(main_ui_->actionViewCollapseAll, SIGNAL(triggered()),
403 proto_tree_, SLOT(collapseAll()));
405 connect(packet_list_, SIGNAL(packetSelectionChanged()),
406 this, SLOT(setMenusForSelectedPacket()));
407 connect(packet_list_, SIGNAL(packetDissectionChanged()),
408 this, SLOT(redissectPackets()));
409 connect(packet_list_, SIGNAL(packetSelectionChanged()),
410 this, SLOT(setMenusForFollowStream()));
411 connect(packet_list_, SIGNAL(showPreferences(PreferencesDialog::PreferencesPane)),
412 this, SLOT(showPreferencesDialog(PreferencesDialog::PreferencesPane)));
413 connect(packet_list_, SIGNAL(editColumn(int)), this, SLOT(showColumnEditor(int)));
414 connect(main_ui_->columnEditorFrame, SIGNAL(columnEdited()),
415 packet_list_, SLOT(redrawVisiblePackets()));
416 connect(packet_list_, SIGNAL(doubleClicked(QModelIndex)),
417 this, SLOT(openPacketDialog()));
418 connect(packet_list_, SIGNAL(packetListScrolled(bool)),
419 main_ui_->actionGoAutoScroll, SLOT(setChecked(bool)));
421 connect(proto_tree_, SIGNAL(protoItemSelected(const QString&)),
422 main_ui_->statusBar, SLOT(pushFieldStatus(const QString&)));
423 connect(proto_tree_, SIGNAL(protoItemSelected(field_info *)),
424 this, SLOT(setMenusForSelectedTreeRow(field_info *)));
425 connect(proto_tree_, SIGNAL(openPacketInNewWindow(bool)),
426 this, SLOT(openPacketDialog(bool)));
428 connect(byte_view_tab_, SIGNAL(byteFieldHovered(const QString&)),
429 main_ui_->statusBar, SLOT(pushByteStatus(const QString&)));
431 connect(main_ui_->statusBar, SIGNAL(showExpertInfo()),
432 this, SLOT(on_actionAnalyzeExpertInfo_triggered()));
434 connect(main_ui_->statusBar, SIGNAL(editCaptureComment()),
435 this, SLOT(on_actionStatisticsCaptureFileProperties_triggered()));
437 connect(&file_set_dialog_, SIGNAL(fileSetOpenCaptureFile(QString&)),
438 this, SLOT(openCaptureFile(QString&)));
441 QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree");
443 connect(iface_tree, SIGNAL(itemSelectionChanged()),
444 this, SLOT(interfaceSelectionChanged()));
446 connect(main_ui_->welcomePage, SIGNAL(captureFilterSyntaxChanged(bool)),
447 this, SLOT(captureFilterSyntaxChanged(bool)));
450 connect(this->main_welcome_, SIGNAL(showExtcapOptions(QString&)),
451 this, SLOT(showExtcapOptionsDialog(QString&)));
454 connect(&capture_interfaces_dialog_, SIGNAL(getPoints(int,PointList*)),
455 this->main_welcome_->getInterfaceTree(), SLOT(getPoints(int,PointList*)));
456 connect(&capture_interfaces_dialog_, SIGNAL(setSelectedInterfaces()),
457 this->main_welcome_->getInterfaceTree(), SLOT(setSelectedInterfaces()));
458 connect(&capture_interfaces_dialog_, SIGNAL(interfaceListChanged()),
459 this->main_welcome_->getInterfaceTree(), SLOT(interfaceListChanged()));
462 main_ui_->mainStack->setCurrentWidget(main_welcome_);
465 MainWindow::~MainWindow()
470 QString MainWindow::getFilter()
472 return df_combo_box_->itemText(df_combo_box_->count());
475 void MainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
477 pipe_source_ = source;
478 pipe_child_process_ = child_process;
479 pipe_user_data_ = user_data;
480 pipe_input_cb_ = input_cb;
483 /* Tricky to use pipes in win9x, as no concept of wait. NT can
484 do this but that doesn't cover all win32 platforms. GTK can do
485 this but doesn't seem to work over processes. Attempt to do
486 something similar here, start a timer and check for data on every
488 /*g_log(NULL, G_LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
491 disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
495 pipe_timer_ = new QTimer(this);
496 connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
497 connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
498 pipe_timer_->start(200);
500 if (pipe_notifier_) {
501 disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
502 delete pipe_notifier_;
505 pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
506 // XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
507 connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
508 connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
513 bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
515 // The user typed some text. Start filling in a filter.
516 // We may need to be more choosy here. We just need to catch events for the packet list,
517 // proto tree, and main welcome widgets.
518 if (event->type() == QEvent::KeyPress) {
519 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
520 if (kevt->text().length() > 0 && kevt->text()[0].isPrint()) {
521 df_combo_box_->lineEdit()->insert(kevt->text());
522 df_combo_box_->lineEdit()->setFocus();
527 return QMainWindow::eventFilter(obj, event);
530 void MainWindow::keyPressEvent(QKeyEvent *event) {
532 // Explicitly focus on the display filter combo.
533 if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) {
534 df_combo_box_->setFocus(Qt::ShortcutFocusReason);
538 if (wsApp->focusWidget() == main_ui_->goToLineEdit) {
539 if (event->modifiers() == Qt::NoModifier) {
540 if (event->key() == Qt::Key_Escape) {
541 on_goToCancel_clicked();
542 } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
546 return; // goToLineEdit didn't want it and we don't either.
549 // Move up & down the packet list.
550 if (event->key() == Qt::Key_F7) {
551 packet_list_->goPreviousPacket();
552 } else if (event->key() == Qt::Key_F8) {
553 packet_list_->goNextPacket();
556 // Move along, citizen.
557 QMainWindow::keyPressEvent(event);
560 void MainWindow::closeEvent(QCloseEvent *event) {
561 saveWindowGeometry();
563 /* If we're in the middle of stopping a capture, don't do anything;
564 the user can try deleting the window after the capture stops. */
565 if (capture_stopping_) {
570 if (!testCaptureFileClose(TRUE, *new QString(" before quitting"))) {
576 capture_interfaces_dialog_.close();
578 // Make sure we kill any open dumpcap processes.
579 delete main_welcome_;
581 // One of the many places we assume one main window.
582 if(!wsApp->isInitialized()) {
583 // If we're still initializing, QCoreApplication::quit() won't
584 // exit properly because we are not in the event loop. This
585 // means that the application won't clean up after itself. We
586 // might want to call wsApp->processEvents() during startup
587 // instead so that we can do a normal exit here.
593 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
596 foreach (QUrl drag_url, event->mimeData()->urls()) {
597 if (!drag_url.toLocalFile().isEmpty()) {
602 if (accept) event->acceptProposedAction();
605 void MainWindow::dropEvent(QDropEvent *event)
607 foreach (QUrl drop_url, event->mimeData()->urls()) {
608 QString local_file = drop_url.toLocalFile();
609 if (!local_file.isEmpty()) {
610 openCaptureFile(local_file);
611 event->acceptProposedAction();
617 // Apply recent settings to the main window geometry.
618 // We haven't loaded the preferences at this point so we assume that the
619 // position and size preference are enabled.
620 void MainWindow::loadWindowGeometry()
622 int min_sensible_dimension_ = 200;
625 if (recent.gui_geometry_main_maximized) {
626 setWindowState(Qt::WindowMaximized);
630 // if (prefs.gui_geometry_save_position) {
631 move(recent.gui_geometry_main_x, recent.gui_geometry_main_y);
634 if (// prefs.gui_geometry_save_size &&
635 recent.gui_geometry_main_width > min_sensible_dimension_ &&
636 recent.gui_geometry_main_height > min_sensible_dimension_) {
637 resize(recent.gui_geometry_main_width, recent.gui_geometry_main_height);
642 void MainWindow::saveWindowGeometry()
644 if (prefs.gui_geometry_save_position) {
645 recent.gui_geometry_main_x = pos().x();
646 recent.gui_geometry_main_y = pos().y();
649 if (prefs.gui_geometry_save_size) {
650 recent.gui_geometry_main_width = size().width();
651 recent.gui_geometry_main_height = size().height();
654 if (prefs.gui_geometry_save_maximized) {
655 // On OS X this is false when it shouldn't be
656 recent.gui_geometry_main_maximized = isMaximized();
659 if (master_split_.sizes().length() > 0) {
660 recent.gui_geometry_main_upper_pane = master_split_.sizes()[0];
663 if (master_split_.sizes().length() > 2) {
664 recent.gui_geometry_main_lower_pane = master_split_.sizes()[1];
665 } else if (extra_split_.sizes().length() > 0) {
666 recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0];
670 QWidget* MainWindow::getLayoutWidget(layout_pane_content_e type) {
672 case layout_pane_content_none:
674 case layout_pane_content_plist:
676 case layout_pane_content_pdetails:
678 case layout_pane_content_pbytes:
679 return byte_view_tab_;
681 g_assert_not_reached();
686 void MainWindow::mergeCaptureFile()
688 QString file_name = "";
689 QString display_filter = "";
690 dfilter_t *rfcode = NULL;
693 if (!capture_file_.capFile())
696 if (prefs.gui_ask_unsaved) {
697 if (cf_has_unsaved_data(capture_file_.capFile())) {
698 QMessageBox msg_dialog;
699 gchar *display_basename;
702 msg_dialog.setIcon(QMessageBox::Question);
703 /* This file has unsaved data; ask the user whether to save
705 if (capture_file_.capFile()->is_tempfile) {
706 msg_dialog.setText(tr("Save packets before merging?"));
707 msg_dialog.setInformativeText(tr("A temporary capture file can't be merged."));
710 * Format the message.
712 display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
713 msg_dialog.setText(QString(tr("Save changes in \"%1\" before merging?")).arg(display_basename));
714 g_free(display_basename);
715 msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged."));
718 msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
719 msg_dialog.setDefaultButton(QMessageBox::Save);
721 response = msg_dialog.exec();
725 case QMessageBox::Save:
726 /* Save the file but don't close it */
727 saveCaptureFile(capture_file_.capFile(), FALSE);
730 case QMessageBox::Cancel:
732 /* Don't do the merge. */
739 CaptureFileDialog merge_dlg(this, capture_file_.capFile(), display_filter);
741 cf_status_t merge_status;
742 char *in_filenames[2];
745 switch (prefs.gui_fileopen_style) {
747 case FO_STYLE_LAST_OPENED:
748 /* The user has specified that we should start out in the last directory
749 we looked in. If we've already opened a file, use its containing
750 directory, if we could determine it, as the directory, otherwise
751 use the "last opened" directory saved in the preferences file if
753 /* This is now the default behaviour in file_selection_new() */
756 case FO_STYLE_SPECIFIED:
757 /* The user has specified that we should always start out in a
758 specified directory; if they've specified that directory,
759 start out by showing the files in that dir. */
760 if (prefs.gui_fileopen_dir[0] != '\0')
761 merge_dlg.setDirectory(prefs.gui_fileopen_dir);
765 if (merge_dlg.merge(file_name)) {
768 if (dfilter_compile(display_filter.toUtf8().constData(), &rfcode, &err_msg)) {
769 cf_set_rfcode(capture_file_.capFile(), rfcode);
771 /* Not valid. Tell the user, and go back and run the file
772 selection box again once they dismiss the alert. */
773 //bad_dfilter_alert_box(top_level, display_filter->str);
774 QMessageBox::warning(this, tr("Invalid Display Filter"),
775 QString(tr("The filter expression %1 isn't a valid display filter. (%2).").arg(display_filter, err_msg)),
784 file_type = capture_file_.capFile()->cd_t;
786 /* Try to merge or append the two files */
788 if (merge_dlg.mergeType() == 0) {
789 /* chronological order */
790 in_filenames[0] = capture_file_.capFile()->filename;
791 in_filenames[1] = file_name.toUtf8().data();
792 merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, FALSE);
793 } else if (merge_dlg.mergeType() <= 0) {
795 in_filenames[0] = file_name.toUtf8().data();
796 in_filenames[1] = capture_file_.capFile()->filename;
797 merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, TRUE);
800 in_filenames[0] = capture_file_.capFile()->filename;
801 in_filenames[1] = file_name.toUtf8().data();
802 merge_status = cf_merge_files(&tmpname, 2, in_filenames, file_type, TRUE);
805 if (merge_status != CF_OK) {
807 dfilter_free(rfcode);
812 cf_close(capture_file_.capFile());
814 /* Try to open the merged capture file. */
815 CaptureFile::globalCapFile()->window = this;
816 if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, TRUE /* temporary file */, &err) != CF_OK) {
817 /* We couldn't open it; fail. */
818 CaptureFile::globalCapFile()->window = NULL;
820 dfilter_free(rfcode);
825 /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
826 it closed the previous capture file, and thus destroyed any
827 previous read filter attached to "cf"). */
828 CaptureFile::globalCapFile()->rfcode = rfcode;
830 switch (cf_read(CaptureFile::globalCapFile(), FALSE)) {
834 /* Just because we got an error, that doesn't mean we were unable
835 to read any of the file; we handle what we could get from the
839 case CF_READ_ABORTED:
840 /* The user bailed out of re-reading the capture file; the
841 capture file has been closed - just free the capture file name
842 string and return (without changing the last containing
848 /* Save the name of the containing directory specified in the path name,
849 if any; we can write over cf_merged_name, which is a good thing, given that
850 "get_dirname()" does write over its argument. */
851 wsApp->setLastOpenDir(get_dirname(tmpname));
853 df_combo_box_->setEditText(display_filter);
854 main_ui_->statusBar->showExpert();
860 void MainWindow::importCaptureFile() {
861 ImportTextDialog import_dlg;
863 if (!testCaptureFileClose(FALSE, *new QString(tr(" before importing a new capture"))))
868 if (import_dlg.result() != QDialog::Accepted) {
869 main_ui_->mainStack->setCurrentWidget(main_welcome_);
873 openCaptureFile(import_dlg.capfileName());
876 void MainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) {
878 gboolean discard_comments;
880 if (cf->is_tempfile) {
881 /* This is a temporary capture file, so saving it means saving
882 it to a permanent file. Prompt the user for a location
883 to which to save it. Don't require that the file format
884 support comments - if it's a temporary capture file, it's
885 probably pcap-ng, which supports comments and, if it's
886 not pcap-ng, let the user decide what they want to do
887 if they've added comments. */
888 saveAsCaptureFile(cf, FALSE, dont_reopen);
890 if (cf->unsaved_changes) {
891 cf_write_status_t status;
893 /* This is not a temporary capture file, but it has unsaved
894 changes, so saving it means doing a "safe save" on top
895 of the existing file, in the same format - no UI needed
896 unless the file has comments and the file's format doesn't
899 If the file has comments, does the file's format support them?
900 If not, ask the user whether they want to discard the comments
901 or choose a different format. */
902 switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) {
905 /* The file can be saved in the specified format as is;
906 just drive on and save in the format they selected. */
907 discard_comments = FALSE;
910 case SAVE_WITHOUT_COMMENTS:
911 /* The file can't be saved in the specified format as is,
912 but it can be saved without the comments, and the user
913 said "OK, discard the comments", so save it in the
914 format they specified without the comments. */
915 discard_comments = TRUE;
918 case SAVE_IN_ANOTHER_FORMAT:
919 /* There are file formats in which we can save this that
920 support comments, and the user said not to delete the
921 comments. Do a "Save As" so the user can select
922 one of those formats and choose a file name. */
923 saveAsCaptureFile(cf, TRUE, dont_reopen);
927 /* The user said "forget it". Just return. */
931 /* Squelch warnings that discard_comments is being used
933 g_assert_not_reached();
937 /* XXX - cf->filename might get freed out from under us, because
938 the code path through which cf_save_records() goes currently
939 closes the current file and then opens and reloads the saved file,
940 so make a copy and free it later. */
941 file_name = cf->filename;
942 status = cf_save_records(cf, file_name.toUtf8().constData(), cf->cd_t, cf->iscompressed,
943 discard_comments, dont_reopen);
947 /* The save succeeded; we're done.
948 If we discarded comments, redraw the packet list to reflect
949 any packets that no longer have comments. */
950 if (discard_comments)
951 packet_list_queue_draw();
953 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
954 updateForUnsavedChanges(); // we update the title bar to remove the *
959 XXX - OK, what do we do now? Let them try a
960 "Save As", in case they want to try to save to a
961 different directory r file system? */
964 case CF_WRITE_ABORTED:
965 /* The write was aborted; just drive on. */
969 /* Otherwise just do nothing. */
973 void MainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) {
974 QString file_name = "";
977 cf_write_status_t status;
979 gboolean discard_comments = FALSE;
986 CaptureFileDialog save_as_dlg(this, cf);
988 switch (prefs.gui_fileopen_style) {
990 case FO_STYLE_LAST_OPENED:
991 /* The user has specified that we should start out in the last directory
992 we looked in. If we've already opened a file, use its containing
993 directory, if we could determine it, as the directory, otherwise
994 use the "last opened" directory saved in the preferences file if
996 /* This is now the default behaviour in file_selection_new() */
999 case FO_STYLE_SPECIFIED:
1000 /* The user has specified that we should always start out in a
1001 specified directory; if they've specified that directory,
1002 start out by showing the files in that dir. */
1003 if (prefs.gui_fileopen_dir[0] != '\0')
1004 save_as_dlg.setDirectory(prefs.gui_fileopen_dir);
1008 /* If the file has comments, does the format the user selected
1009 support them? If not, ask the user whether they want to
1010 discard the comments or choose a different format. */
1011 switch(save_as_dlg.saveAs(file_name, must_support_comments)) {
1014 /* The file can be saved in the specified format as is;
1015 just drive on and save in the format they selected. */
1016 discard_comments = FALSE;
1019 case SAVE_WITHOUT_COMMENTS:
1020 /* The file can't be saved in the specified format as is,
1021 but it can be saved without the comments, and the user
1022 said "OK, discard the comments", so save it in the
1023 format they specified without the comments. */
1024 discard_comments = TRUE;
1027 case SAVE_IN_ANOTHER_FORMAT:
1028 /* There are file formats in which we can save this that
1029 support comments, and the user said not to delete the
1030 comments. The combo box of file formats has had the
1031 formats that don't support comments trimmed from it,
1032 so run the dialog again, to let the user decide
1033 whether to save in one of those formats or give up. */
1034 must_support_comments = TRUE;
1038 /* The user said "forget it". Just get rid of the dialog box
1042 file_type = save_as_dlg.selectedFileType();
1043 compressed = save_as_dlg.isCompressed();
1045 fileAddExtension(file_name, file_type, compressed);
1048 // /* If the file exists and it's user-immutable or not writable,
1049 // ask the user whether they want to override that. */
1050 // if (!file_target_unwritable_ui(top_level, file_name.toUtf8().constData())) {
1051 // /* They don't. Let them try another file name or cancel. */
1056 /* Attempt to save the file */
1057 status = cf_save_records(cf, file_name.toUtf8().constData(), file_type, compressed,
1058 discard_comments, dont_reopen);
1062 /* The save succeeded; we're done. */
1063 /* Save the directory name for future file dialogs. */
1064 dirname = get_dirname(qstring_strdup(file_name)); /* Overwrites cf_name */
1065 set_last_open_dir(dirname);
1067 /* If we discarded comments, redraw the packet list to reflect
1068 any packets that no longer have comments. */
1069 if (discard_comments)
1070 packet_list_queue_draw();
1072 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1073 updateForUnsavedChanges(); // we update the title bar to remove the *
1076 case CF_WRITE_ERROR:
1077 /* The save failed; let the user try again. */
1080 case CF_WRITE_ABORTED:
1081 /* The user aborted the save; just return. */
1088 void MainWindow::exportSelectedPackets() {
1089 QString file_name = "";
1091 gboolean compressed;
1092 packet_range_t range;
1093 cf_write_status_t status;
1095 gboolean discard_comments = FALSE;
1097 if (!capture_file_.capFile())
1100 /* Init the packet range */
1101 packet_range_init(&range, capture_file_.capFile());
1102 range.process_filtered = TRUE;
1103 range.include_dependents = TRUE;
1106 CaptureFileDialog esp_dlg(this, capture_file_.capFile());
1108 switch (prefs.gui_fileopen_style) {
1110 case FO_STYLE_LAST_OPENED:
1111 /* The user has specified that we should start out in the last directory
1112 we looked in. If we've already opened a file, use its containing
1113 directory, if we could determine it, as the directory, otherwise
1114 use the "last opened" directory saved in the preferences file if
1116 /* This is now the default behaviour in file_selection_new() */
1119 case FO_STYLE_SPECIFIED:
1120 /* The user has specified that we should always start out in a
1121 specified directory; if they've specified that directory,
1122 start out by showing the files in that dir. */
1123 if (prefs.gui_fileopen_dir[0] != '\0')
1124 esp_dlg.setDirectory(prefs.gui_fileopen_dir);
1128 /* If the file has comments, does the format the user selected
1129 support them? If not, ask the user whether they want to
1130 discard the comments or choose a different format. */
1131 switch(esp_dlg.exportSelectedPackets(file_name, &range)) {
1134 /* The file can be saved in the specified format as is;
1135 just drive on and save in the format they selected. */
1136 discard_comments = FALSE;
1139 case SAVE_WITHOUT_COMMENTS:
1140 /* The file can't be saved in the specified format as is,
1141 but it can be saved without the comments, and the user
1142 said "OK, discard the comments", so save it in the
1143 format they specified without the comments. */
1144 discard_comments = TRUE;
1147 case SAVE_IN_ANOTHER_FORMAT:
1148 /* There are file formats in which we can save this that
1149 support comments, and the user said not to delete the
1150 comments. The combo box of file formats has had the
1151 formats that don't support comments trimmed from it,
1152 so run the dialog again, to let the user decide
1153 whether to save in one of those formats or give up. */
1157 /* The user said "forget it". Just get rid of the dialog box
1163 * Check that we're not going to save on top of the current
1165 * We do it here so we catch all cases ...
1166 * Unfortunately, the file requester gives us an absolute file
1167 * name and the read file name may be relative (if supplied on
1168 * the command line). From Joerg Mayer.
1170 if (files_identical(capture_file_.capFile()->filename, file_name.toUtf8().constData())) {
1171 QMessageBox msg_box;
1172 gchar *display_basename = g_filename_display_basename(file_name.toUtf8().constData());
1174 msg_box.setIcon(QMessageBox::Critical);
1175 msg_box.setText(QString(tr("Unable to export to \"%1\".").arg(display_basename)));
1176 msg_box.setInformativeText(tr("You cannot export packets to the current capture file."));
1177 msg_box.setStandardButtons(QMessageBox::Ok);
1178 msg_box.setDefaultButton(QMessageBox::Ok);
1180 g_free(display_basename);
1184 file_type = esp_dlg.selectedFileType();
1185 compressed = esp_dlg.isCompressed();
1186 fileAddExtension(file_name, file_type, compressed);
1189 // /* If the file exists and it's user-immutable or not writable,
1190 // ask the user whether they want to override that. */
1191 // if (!file_target_unwritable_ui(top_level, file_name.toUtf8().constData())) {
1192 // /* They don't. Let them try another file name or cancel. */
1197 /* Attempt to save the file */
1198 status = cf_export_specified_packets(capture_file_.capFile(), file_name.toUtf8().constData(), &range, file_type, compressed);
1202 /* The save succeeded; we're done. */
1203 /* Save the directory name for future file dialogs. */
1204 dirname = get_dirname(qstring_strdup(file_name)); /* Overwrites cf_name */
1205 set_last_open_dir(dirname);
1207 /* If we discarded comments, redraw the packet list to reflect
1208 any packets that no longer have comments. */
1209 if (discard_comments)
1210 packet_list_queue_draw();
1213 case CF_WRITE_ERROR:
1214 /* The save failed; let the user try again. */
1217 case CF_WRITE_ABORTED:
1218 /* The user aborted the save; just return. */
1225 void MainWindow::exportDissections(export_type_e export_type) {
1226 ExportDissectionDialog ed_dlg(this, capture_file_.capFile(), export_type);
1227 packet_range_t range;
1229 if (!capture_file_.capFile())
1232 /* Init the packet range */
1233 packet_range_init(&range, capture_file_.capFile());
1234 range.process_filtered = TRUE;
1235 range.include_dependents = TRUE;
1240 void MainWindow::fileAddExtension(QString &file_name, int file_type, bool compressed) {
1241 QString file_name_lower;
1242 QString file_suffix;
1243 GSList *extensions_list;
1244 gboolean add_extension;
1247 * Append the default file extension if there's none given by
1248 * the user or if they gave one that's not one of the valid
1249 * extensions for the file type.
1251 file_name_lower = file_name.toLower();
1252 extensions_list = wtap_get_file_extensions_list(file_type, FALSE);
1253 if (extensions_list != NULL) {
1256 /* We have one or more extensions for this file type.
1257 Start out assuming we need to add the default one. */
1258 add_extension = TRUE;
1260 /* OK, see if the file has one of those extensions. */
1261 for (extension = extensions_list; extension != NULL;
1262 extension = g_slist_next(extension)) {
1263 file_suffix += tr(".") + (char *)extension->data;
1264 if (file_name_lower.endsWith(file_suffix)) {
1266 * The file name has one of the extensions for
1269 add_extension = FALSE;
1272 file_suffix += ".gz";
1273 if (file_name_lower.endsWith(file_suffix)) {
1275 * The file name has one of the extensions for
1278 add_extension = FALSE;
1283 /* We have no extensions for this file type. Don't add one. */
1284 add_extension = FALSE;
1286 if (add_extension) {
1287 if (wtap_default_file_extension(file_type) != NULL) {
1288 file_name += tr(".") + wtap_default_file_extension(file_type);
1296 bool MainWindow::testCaptureFileClose(bool from_quit, QString &before_what) {
1297 bool capture_in_progress = FALSE;
1299 if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED)
1300 return true; /* Already closed, nothing to do */
1303 if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1304 /* This is true if we're reading a capture file *or* if we're doing
1305 a live capture. If we're reading a capture file, the main loop
1306 is busy reading packets, and only accepting input from the
1307 progress dialog, so we can't get here, so this means we're
1309 capture_in_progress = TRUE;
1313 if (prefs.gui_ask_unsaved) {
1314 if (cf_has_unsaved_data(capture_file_.capFile()) || capture_in_progress) {
1315 QMessageBox msg_dialog;
1317 QPushButton *saveButton;
1318 QPushButton *discardButton;
1320 msg_dialog.setIcon(QMessageBox::Question);
1321 msg_dialog.setWindowTitle("Unsaved packets...");
1322 /* This file has unsaved data or there's a capture in
1323 progress; ask the user whether to save the data. */
1324 if (capture_file_.capFile()->is_tempfile) {
1326 msg_dialog.setText(tr("You have unsaved packets"));
1327 msg_dialog.setInformativeText(tr("They will be lost if you don't save them."));
1329 if (capture_in_progress) {
1330 question.append(tr("Do you want to stop the capture and save the captured packets"));
1332 question.append(tr("Do you want to save the captured packets"));
1334 question.append(before_what).append(tr("?"));
1335 msg_dialog.setInformativeText(question);
1340 * Format the message.
1342 if (capture_in_progress) {
1343 question.append(tr("Do you want to stop the capture and save the captured packets"));
1344 question.append(before_what).append(tr("?"));
1345 msg_dialog.setText(question);
1346 msg_dialog.setInformativeText(tr("Your captured packets will be lost if you don't save them."));
1348 gchar *display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1349 question.append(QString(tr("Do you want to save the changes you've made to the capture file \"%1\"%2?"))
1350 .arg(display_basename)
1353 g_free(display_basename);
1354 msg_dialog.setText(question);
1355 msg_dialog.setInformativeText(tr("Your changes will be lost if you don't save them."));
1359 // XXX Text comes from ui/gtk/stock_icons.[ch]
1360 // Note that the button roles differ from the GTK+ version.
1361 // Cancel = RejectRole
1362 // Save = AcceptRole
1363 // Don't Save = DestructiveRole
1364 msg_dialog.addButton(QMessageBox::Cancel);
1366 if (capture_in_progress) {
1367 saveButton = msg_dialog.addButton(tr("Stop and Save"), QMessageBox::AcceptRole);
1369 saveButton = msg_dialog.addButton(QMessageBox::Save);
1371 msg_dialog.setDefaultButton(saveButton);
1374 if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1375 discardButton = msg_dialog.addButton(tr("Stop and Quit without Saving"),
1376 QMessageBox::DestructiveRole);
1378 discardButton = msg_dialog.addButton(tr("Quit without Saving"),
1379 QMessageBox::DestructiveRole);
1382 if (capture_in_progress) {
1383 discardButton = msg_dialog.addButton(tr("Stop and Continue without Saving"),
1384 QMessageBox::DestructiveRole);
1386 discardButton = msg_dialog.addButton(tr("Continue &without Saving"), QMessageBox::DestructiveRole);
1391 /* According to the Qt doc:
1392 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
1394 * Therefore we should use clickedButton() to determine which button was clicked. */
1396 if(msg_dialog.clickedButton() == saveButton)
1399 /* If there's a capture in progress, we have to stop the capture
1400 and then do the save. */
1401 if (capture_in_progress)
1404 /* Save the file and close it */
1405 saveCaptureFile(capture_file_.capFile(), TRUE);
1407 else if(msg_dialog.clickedButton() == discardButton)
1411 * If there's a capture in progress; we have to stop the capture
1412 * and then do the close.
1414 if (capture_in_progress)
1417 /* Just close the file, discarding changes */
1418 cf_close(capture_file_.capFile());
1421 else //cancelButton or some other unspecified button
1427 /* Unchanged file, just close it */
1428 cf_close(capture_file_.capFile());
1431 /* User asked not to be bothered by those prompts, just close it.
1432 XXX - should that apply only to saving temporary files? */
1434 /* If there's a capture in progress, we have to stop the capture
1435 and then do the close. */
1436 if (capture_in_progress)
1439 cf_close(capture_file_.capFile());
1442 return true; /* File closed */
1445 void MainWindow::captureStop() {
1448 while(capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1449 WiresharkApplication::processEvents();
1453 void MainWindow::initMainToolbarIcons()
1455 #if defined(Q_OS_WIN)
1456 // Current GTK+ and other Windows app behavior.
1457 main_ui_->mainToolBar->setIconSize(QSize(16, 16));
1459 // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
1460 main_ui_->mainToolBar->setIconSize(QSize(24, 24));
1463 // Toolbar actions. The GNOME HIG says that we should have a menu icon for each
1464 // toolbar item but that clutters up our menu. Set menu icons sparingly.
1466 main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start"));
1467 main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop"));
1468 main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart"));
1469 main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options"));
1471 // Menu icons are disabled in main_window.ui for these items.
1472 main_ui_->actionFileOpen->setIcon(StockIcon("document-open"));
1473 main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save"));
1474 main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close"));
1475 // main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload"));
1477 main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find"));
1478 main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous"));
1479 main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next"));
1480 main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump"));
1481 main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first"));
1482 main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last"));
1483 main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last"));
1485 main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets"));
1486 main_ui_->actionViewColorizePacketList->setChecked(recent.packet_list_colorize);
1487 // main_ui_->actionViewAutoScroll->setIcon(StockIcon("x-stay-last"));
1489 QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts();
1490 zi_seq << QKeySequence(Qt::CTRL + Qt::Key_Equal);
1491 main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in"));
1492 main_ui_->actionViewZoomIn->setShortcuts(zi_seq);
1493 main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
1494 main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
1495 main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
1498 void MainWindow::initShowHideMainWidgets()
1500 if (show_hide_actions_) {
1504 show_hide_actions_ = new QActionGroup(this);
1505 QMap<QAction *, QWidget *> shmw_actions;
1507 show_hide_actions_->setExclusive(false);
1508 shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar;
1509 shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar;
1510 shmw_actions[main_ui_->actionViewWirelessToolbar] = NULL; // Doesn't exist yet.
1511 shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar;
1512 shmw_actions[main_ui_->actionViewPacketList] = packet_list_;
1513 shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_;
1514 shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_;
1516 main_ui_->actionViewMainToolbar->setChecked(recent.main_toolbar_show);
1517 main_ui_->actionViewFilterToolbar->setChecked(recent.filter_toolbar_show);
1518 main_ui_->actionViewWirelessToolbar->setChecked(recent.wireless_toolbar_show);
1519 main_ui_->actionViewStatusBar->setChecked(recent.statusbar_show);
1520 main_ui_->actionViewPacketList->setChecked(recent.packet_list_show);
1521 main_ui_->actionViewPacketDetails->setChecked(recent.tree_view_show);
1522 main_ui_->actionViewPacketBytes->setChecked(recent.byte_view_show);
1524 foreach (QAction *shmwa, shmw_actions.keys()) {
1525 shmwa->setData(qVariantFromValue(shmw_actions[shmwa]));
1526 show_hide_actions_->addAction(shmwa);
1527 showHideMainWidgets(shmwa);
1530 connect(show_hide_actions_, SIGNAL(triggered(QAction*)), this, SLOT(showHideMainWidgets(QAction*)));
1533 Q_DECLARE_METATYPE(ts_type)
1535 void MainWindow::initTimeDisplayFormatMenu()
1537 if (time_display_actions_) {
1541 time_display_actions_ = new QActionGroup(this);
1542 QMap<QAction *, ts_type> td_actions;
1544 td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD;
1545 td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY;
1546 td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE;
1547 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH;
1548 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceBeginningOfCapture] = TS_RELATIVE;
1549 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA;
1550 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS;
1551 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD;
1552 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY;
1553 td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC;
1555 foreach (QAction* tda, td_actions.keys()) {
1556 tda->setData(qVariantFromValue(td_actions[tda]));
1557 time_display_actions_->addAction(tda);
1558 if (recent.gui_time_format == td_actions[tda]) {
1559 tda->setChecked(true);
1563 connect(time_display_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampFormat(QAction*)));
1565 main_ui_->actionViewTimeDisplaySecondsWithHoursAndMinutes->setChecked(recent.gui_seconds_format == TS_SECONDS_HOUR_MIN_SEC);
1568 Q_DECLARE_METATYPE(ts_precision)
1570 void MainWindow::initTimePrecisionFormatMenu()
1572 if (time_precision_actions_) {
1576 time_precision_actions_ = new QActionGroup(this);
1577 QMap<QAction *, ts_precision> tp_actions;
1578 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO;
1579 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC;
1580 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionDeciseconds] = TS_PREC_FIXED_DSEC;
1581 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionCentiseconds] = TS_PREC_FIXED_CSEC;
1582 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC;
1583 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC;
1584 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC;
1586 foreach (QAction* tpa, tp_actions.keys()) {
1587 tpa->setData(qVariantFromValue(tp_actions[tpa]));
1588 time_precision_actions_->addAction(tpa);
1589 if (recent.gui_time_precision == tp_actions[tpa]) {
1590 tpa->setChecked(true);
1594 connect(time_precision_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampPrecision(QAction*)));
1598 void MainWindow::setTitlebarForCaptureFile()
1600 if (capture_file_.capFile() && capture_file_.capFile()->filename) {
1602 // Qt *REALLY* doesn't like windows that sometimes have a
1603 // title set with setWindowTitle() and other times have a
1604 // file path set; apparently, once you've set the title
1605 // with setWindowTitle(), it sticks, and setWindowFilePath()
1606 // has no effect. It appears to can clear the title with
1607 // setWindowTitle(NULL), but that clears the actual title in
1608 // the title bar, and setWindowFilePath() then, I guess, sees
1609 // that there's already a file path, and does nothing, leaving
1610 // the title bar empty. So you then have to clear the file path
1611 // with setWindowFilePath(NULL), and then set it.
1613 // Maybe there's a #include "you're holding it wrong" here.
1614 // However, I really don't want to hear from people who think
1615 // that a window can never be associated with something other
1616 // than a user file at time T1 and with a user file at time T2,
1617 // given that, in Wireshark, a window can be associated with a
1618 // live capture at time T1 and then, after you've saved the live
1619 // capture to a user file, associated with a user file at time T2.
1621 if (capture_file_.capFile()->is_tempfile) {
1623 // For a temporary file, put the source of the data
1624 // in the window title, not whatever random pile
1625 // of characters is the last component of the path
1628 // XXX - on non-Mac platforms, put in the application
1631 // XXX - Use setWindowModified
1633 setWindowFilePath(NULL);
1634 window_name = g_strdup_printf("[*]%s", cf_get_tempfile_source(capture_file_.capFile())); //TODO : Fix Translate
1635 setWindowTitle(window_name);
1636 g_free(window_name);
1639 // For a user file, set the full path; that way,
1640 // for OS X, it'll set the "proxy icon". Qt
1641 // handles extracting the last component.
1643 // Sadly, some UN*Xes don't necessarily use UTF-8
1644 // for their file names, so we have to map the
1645 // file path to UTF-8. If that fails, we're somewhat
1648 char *utf8_filename = g_filename_to_utf8(capture_file_.capFile()->filename,
1653 if (utf8_filename == NULL) {
1654 // So what the heck else can we do here?
1655 setWindowTitle(tr("(File name can't be mapped to UTF-8)"));
1657 setWindowTitle(NULL);
1658 setWindowFilePath(NULL);
1659 setWindowFilePath(utf8_filename);
1660 g_free(utf8_filename);
1663 setWindowModified(cf_has_unsaved_data(capture_file_.capFile()));
1665 /* We have no capture file. */
1666 setWindowFilePath(NULL);
1667 setWindowTitle(tr("The Wireshark Network Analyzer"));
1671 void MainWindow::setTitlebarForSelectedTreeRow()
1673 setWindowTitle(tr("The Wireshark Network Analyzer"));
1677 void MainWindow::setTitlebarForCaptureInProgress()
1681 setWindowFilePath(NULL);
1682 if (capture_file_.capFile()) {
1683 window_name = g_strdup_printf("Capturing from %s", cf_get_tempfile_source(capture_file_.capFile())); //TODO : Fix Translate
1684 setWindowTitle(window_name);
1685 g_free(window_name);
1687 /* We have no capture in progress. */
1688 setWindowTitle(tr("The Wireshark Network Analyzer"));
1694 void MainWindow::setMenusForFollowStream()
1696 gboolean is_tcp = FALSE, is_udp = FALSE;
1698 if (!capture_file_.capFile())
1701 if (!capture_file_.capFile()->edt)
1704 main_ui_->actionAnalyzeFollowTCPStream->setEnabled(false);
1705 main_ui_->actionAnalyzeFollowUDPStream->setEnabled(false);
1706 main_ui_->actionAnalyzeFollowSSLStream->setEnabled(false);
1708 proto_get_frame_protocols(capture_file_.capFile()->edt->pi.layers, NULL, &is_tcp, &is_udp, NULL, NULL);
1712 main_ui_->actionAnalyzeFollowTCPStream->setEnabled(true);
1717 main_ui_->actionAnalyzeFollowUDPStream->setEnabled(true);
1720 if ( epan_dissect_packet_contains_field(capture_file_.capFile()->edt, "ssl") )
1722 main_ui_->actionAnalyzeFollowSSLStream->setEnabled(true);
1726 /* Enable or disable menu items based on whether you have a capture file
1727 you've finished reading and, if you have one, whether it's been saved
1728 and whether it could be saved except by copying the raw packet data. */
1729 void MainWindow::setMenusForCaptureFile(bool force_disable)
1731 if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1732 /* We have no capture file or we're currently reading a file */
1733 main_ui_->actionFileMerge->setEnabled(false);
1734 main_ui_->actionFileClose->setEnabled(false);
1735 main_ui_->actionFileSave->setEnabled(false);
1736 main_ui_->actionFileSaveAs->setEnabled(false);
1737 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
1738 main_ui_->actionFileExportPackets->setEnabled(false);
1739 main_ui_->menuFileExportPacketDissections->setEnabled(false);
1740 main_ui_->actionFileExportPacketBytes->setEnabled(false);
1741 main_ui_->actionFileExportPDU->setEnabled(false);
1742 main_ui_->actionFileExportSSLSessionKeys->setEnabled(false);
1743 main_ui_->menuFileExportObjects->setEnabled(false);
1744 main_ui_->actionViewReload->setEnabled(false);
1746 main_ui_->actionFileMerge->setEnabled(cf_can_write_with_wiretap(capture_file_.capFile()));
1748 main_ui_->actionFileClose->setEnabled(true);
1749 main_ui_->actionFileSave->setEnabled(cf_can_save(capture_file_.capFile()));
1750 main_ui_->actionFileSaveAs->setEnabled(cf_can_save_as(capture_file_.capFile()));
1751 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(true);
1753 * "Export Specified Packets..." should be available only if
1754 * we can write the file out in at least one format.
1756 main_ui_->actionFileExportPackets->setEnabled(cf_can_write_with_wiretap(capture_file_.capFile()));
1757 main_ui_->menuFileExportPacketDissections->setEnabled(true);
1758 main_ui_->actionFileExportPacketBytes->setEnabled(true);
1759 main_ui_->actionFileExportPDU->setEnabled(true);
1760 main_ui_->actionFileExportSSLSessionKeys->setEnabled(true);
1761 main_ui_->menuFileExportObjects->setEnabled(true);
1762 main_ui_->actionViewReload->setEnabled(true);
1766 void MainWindow::setMenusForCaptureInProgress(bool capture_in_progress) {
1767 /* Either a capture was started or stopped; in either case, it's not
1768 in the process of stopping, so allow quitting. */
1770 main_ui_->actionFileOpen->setEnabled(!capture_in_progress);
1771 main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress);
1772 main_ui_->menuFileExportPacketDissections->setEnabled(capture_in_progress);
1773 main_ui_->actionFileExportPacketBytes->setEnabled(capture_in_progress);
1774 main_ui_->actionFileExportPDU->setEnabled(capture_in_progress);
1775 main_ui_->actionFileExportSSLSessionKeys->setEnabled(capture_in_progress);
1776 main_ui_->menuFileExportObjects->setEnabled(capture_in_progress);
1777 main_ui_->menuFileSet->setEnabled(!capture_in_progress);
1778 main_ui_->actionFileQuit->setEnabled(true);
1780 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress);
1782 // XXX Fix packet list heading menu sensitivity
1783 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
1784 // !capture_in_progress);
1785 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
1786 // !capture_in_progress);
1787 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
1788 // !capture_in_progress);
1791 main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress);
1792 main_ui_->actionCaptureStart->setEnabled(!capture_in_progress);
1793 main_ui_->actionCaptureStart->setChecked(capture_in_progress);
1794 main_ui_->actionCaptureStop->setEnabled(capture_in_progress);
1795 main_ui_->actionCaptureRestart->setEnabled(capture_in_progress);
1796 #endif /* HAVE_LIBPCAP */
1800 void MainWindow::setMenusForCaptureStopping() {
1801 main_ui_->actionFileQuit->setEnabled(false);
1802 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
1804 main_ui_->actionCaptureStart->setChecked(false);
1805 main_ui_->actionCaptureStop->setEnabled(false);
1806 main_ui_->actionCaptureRestart->setEnabled(false);
1807 #endif /* HAVE_LIBPCAP */
1810 void MainWindow::setForCapturedPackets(bool have_captured_packets)
1812 main_ui_->actionFilePrint->setEnabled(have_captured_packets);
1814 // set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
1815 // have_captured_packets);
1817 main_ui_->actionEditFindPacket->setEnabled(have_captured_packets);
1818 main_ui_->actionEditFindNext->setEnabled(have_captured_packets);
1819 main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets);
1821 main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets);
1822 main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets);
1823 main_ui_->actionGoNextPacket->setEnabled(have_captured_packets);
1824 main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets);
1825 main_ui_->actionGoLastPacket->setEnabled(have_captured_packets);
1827 main_ui_->actionViewZoomIn->setEnabled(have_captured_packets);
1828 main_ui_->actionViewZoomOut->setEnabled(have_captured_packets);
1829 main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
1830 main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
1832 // set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/GoMenu/PreviousPacketInConversation",
1833 // have_captured_packets);
1834 // set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/GoMenu/NextPacketInConversation",
1835 // have_captured_packets);
1836 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
1837 main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
1838 main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
1841 void MainWindow::setMenusForFileSet(bool enable_list_files) {
1842 bool enable_next = fileset_get_next() != NULL && enable_list_files;
1843 bool enable_prev = fileset_get_previous() != NULL && enable_list_files;
1845 main_ui_->actionFileSetListFiles->setEnabled(enable_list_files);
1846 main_ui_->actionFileSetNextFile->setEnabled(enable_next);
1847 main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev);
1850 void MainWindow::updateForUnsavedChanges() {
1851 setTitlebarForCaptureFile();
1852 setMenusForCaptureFile();
1855 void MainWindow::changeEvent(QEvent* event)
1859 switch (event->type())
1861 case QEvent::LanguageChange:
1862 main_ui_->retranslateUi(this);
1864 case QEvent::LocaleChange:{
1865 QString locale = QLocale::system().name();
1866 locale.truncate(locale.lastIndexOf('_'));
1867 wsApp->loadLanguage(locale);
1874 QMainWindow::changeEvent(event);
1877 /* Update main window items based on whether there's a capture in progress. */
1878 void MainWindow::setForCaptureInProgress(gboolean capture_in_progress)
1880 setMenusForCaptureInProgress(capture_in_progress);
1883 packet_list_->setCaptureInProgress(capture_in_progress);
1884 // set_toolbar_for_capture_in_progress(capture_in_progress);
1886 // set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
1890 void MainWindow::externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, gint depth)
1892 QAction * itemAction = NULL;
1893 ext_menubar_t * item = NULL;
1894 GList * children = NULL;
1896 /* There must exists an xpath parent */
1897 g_assert(subMenu != NULL);
1899 /* If the depth counter exceeds, something must have gone wrong */
1900 g_assert(depth < EXT_MENUBAR_MAX_DEPTH);
1902 children = menu->children;
1903 /* Iterate the child entries */
1904 while ( children != NULL && children->data != NULL )
1906 item = (ext_menubar_t *) children->data;
1908 if ( item->type == EXT_MENUBAR_MENU )
1910 /* Handle Submenu entry */
1911 this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++ );
1913 else if ( item->type == EXT_MENUBAR_SEPARATOR )
1915 subMenu->addSeparator();
1917 else if ( item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL )
1919 itemAction = subMenu->addAction(item->name);
1920 itemAction->setData(QVariant::fromValue((void *)item));
1921 itemAction->setText(item->label);
1922 connect(itemAction, SIGNAL(triggered()),
1923 this, SLOT(externalMenuItem_triggered()));
1927 children = g_list_next(children);
1931 void MainWindow::addExternalMenus()
1933 QMenu * subMenu = NULL;
1934 GList * user_menu = NULL;
1935 ext_menu_t * menu = NULL;
1937 user_menu = ext_menubar_get_entries();
1939 while ( ( user_menu != NULL ) && ( user_menu->data != NULL ) )
1941 menu = (ext_menu_t *) user_menu->data;
1943 /* On this level only menu items should exist. Not doing an assert here,
1944 * as it could be an honest mistake */
1945 if ( menu->type != EXT_MENUBAR_MENU )
1947 user_menu = g_list_next(user_menu);
1951 /* Create main submenu and add it to the menubar */
1952 subMenu = main_ui_->menuBar->addMenu(menu->label);
1954 /* This will generate the action structure for each menu. It is recursive,
1955 * therefore a sub-routine, and we have a depth counter to prevent endless loops. */
1956 this->externalMenuHelper(menu, subMenu, 0);
1959 user_menu = g_list_next (user_menu);
1969 * indent-tabs-mode: nil
1972 * ex: set shiftwidth=4 tabstop=8 expandtab:
1973 * :indentSize=4:tabSize=8:noTabs=true: