/* main_status_bar.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include "file.h" #include #include #include #include #include "ui/main_statusbar.h" #include "ui/profile.h" #include #include "capture_file.h" #include "main_status_bar.h" #include "profile_dialog.h" #include #include #include #include #include #include // XXX - The GTK+ code assigns priorities to these and pushes/pops accordingly. enum StatusContext { STATUS_CTX_MAIN, STATUS_CTX_FILE, STATUS_CTX_FIELD, STATUS_CTX_BYTE, STATUS_CTX_FILTER, STATUS_CTX_PROGRESS, STATUS_CTX_TEMPORARY }; Q_DECLARE_METATYPE(ProfileDialog::ProfileAction) // If we ever add support for multiple windows this will need to be replaced. // See also: main_window.cpp static MainStatusBar *cur_main_status_bar_ = NULL; /* * Push a formatted temporary message onto the statusbar. */ void statusbar_push_temporary_msg(const gchar *msg_format, ...) { va_list ap; QString push_msg; if (!cur_main_status_bar_) return; va_start(ap, msg_format); push_msg.vsprintf(msg_format, ap); va_end(ap); cur_main_status_bar_->pushTemporaryStatus(push_msg); } /* * Update the packets statusbar to the current values */ void packets_bar_update(void) { if (!cur_main_status_bar_) return; cur_main_status_bar_->updateCaptureStatistics(NULL); } static const int icon_size = 14; // px MainStatusBar::MainStatusBar(QWidget *parent) : QStatusBar(parent), cap_file_(NULL), edit_action_(NULL), delete_action_(NULL), #ifdef HAVE_LIBPCAP ready_msg_(tr("Ready to load or capture")), #else ready_msg_(tr("Ready to load file")), #endif cs_fixed_(false), cs_count_(0) { QSplitter *splitter = new QSplitter(this); QWidget *info_progress = new QWidget(this); QHBoxLayout *info_progress_hb = new QHBoxLayout(info_progress); QAction *action; #if defined(Q_OS_WIN) // Handles are the same color as widgets, at least on Windows 7. splitter->setHandleWidth(3); splitter->setStyleSheet(QString( "QSplitter::handle {" " border-left: 1px solid palette(mid);" " border-right: 1px solid palette(mid);" "}" )); #endif QString button_ss = "QToolButton {" " border: none;" " background: transparent;" // Disables platform style on Windows. " padding: 0px;" " margin: 0px;" "}"; expert_button_ = new QToolButton(this); expert_button_->setIconSize(QSize(icon_size, icon_size)); expert_button_->setStyleSheet(button_ss); expert_button_->hide(); // We just want a clickable image. Using a QPushButton or QToolButton would require // a lot of adjustment. StockIcon comment_icon("x-capture-comment-update"); comment_button_ = new QToolButton(this); comment_button_->setIcon(comment_icon); comment_button_->setIconSize(QSize(icon_size, icon_size)); comment_button_->setStyleSheet(button_ss); comment_button_->setToolTip(tr("Open the Capture File Properties dialog")); comment_button_->setEnabled(false); connect(expert_button_, SIGNAL(clicked(bool)), this, SIGNAL(showExpertInfo())); connect(comment_button_, SIGNAL(clicked(bool)), this, SIGNAL(editCaptureComment())); info_progress_hb->setContentsMargins(icon_size / 2, 0, 0, 0); info_status_.setTemporaryContext(STATUS_CTX_TEMPORARY); info_status_.setShrinkable(true); info_progress_hb->addWidget(expert_button_); info_progress_hb->addWidget(comment_button_); info_progress_hb->addWidget(&info_status_); info_progress_hb->addWidget(&progress_frame_); info_progress_hb->addStretch(10); splitter->addWidget(info_progress); splitter->addWidget(&packet_status_); splitter->addWidget(&profile_status_); splitter->setStretchFactor(0, 3); splitter->setStretchFactor(1, 3); splitter->setStretchFactor(2, 1); addWidget(splitter, 1); cur_main_status_bar_ = this; splitter->hide(); info_status_.pushText(ready_msg_, STATUS_CTX_MAIN); packets_bar_update(); action = ctx_menu_.addAction(tr("Manage Profiles" UTF8_HORIZONTAL_ELLIPSIS)); action->setData(ProfileDialog::ShowProfiles); connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); ctx_menu_.addSeparator(); action = ctx_menu_.addAction(tr("New" UTF8_HORIZONTAL_ELLIPSIS)); action->setData(ProfileDialog::NewProfile); connect(action, SIGNAL(triggered()), this, SLOT(manageProfile())); edit_action_ = ctx_menu_.addAction(tr("Edit" UTF8_HORIZONTAL_ELLIPSIS)); edit_action_->setData(ProfileDialog::EditCurrentProfile); connect(edit_action_, SIGNAL(triggered()), this, SLOT(manageProfile())); delete_action_ = ctx_menu_.addAction(tr("Delete")); delete_action_->setData(ProfileDialog::DeleteCurrentProfile); connect(delete_action_, SIGNAL(triggered()), this, SLOT(manageProfile())); ctx_menu_.addSeparator(); profile_menu_.setTitle(tr("Switch to")); ctx_menu_.addMenu(&profile_menu_); #ifdef QWINTASKBARPROGRESS_H progress_frame_.enableTaskbarUpdates(true); #endif connect(wsApp, SIGNAL(appInitialized()), splitter, SLOT(show())); connect(wsApp, SIGNAL(appInitialized()), this, SLOT(pushProfileName())); connect(&info_status_, SIGNAL(toggleTemporaryFlash(bool)), this, SLOT(toggleBackground(bool))); connect(wsApp, SIGNAL(profileNameChanged(const gchar *)), this, SLOT(pushProfileName())); connect(&profile_status_, SIGNAL(mousePressedAt(QPoint,Qt::MouseButton)), this, SLOT(showProfileMenu(QPoint,Qt::MouseButton))); connect(&progress_frame_, SIGNAL(stopLoading()), this, SIGNAL(stopLoading())); } void MainStatusBar::showExpert() { expertUpdate(); } void MainStatusBar::captureFileClosing() { expert_button_->hide(); progress_frame_.captureFileClosing(); } void MainStatusBar::expertUpdate() { // won't load @2x versions in Qt versions earlier than 5.4. // https://bugreports.qt.io/browse/QTBUG-36383 // We might have to switch to a QPushButton. QString stock_name = "x-expert-"; QString tt_text = tr(" is the highest expert information level"); switch(expert_get_highest_severity()) { case(PI_ERROR): stock_name.append("error"); tt_text.prepend(tr("ERROR")); break; case(PI_WARN): stock_name.append("warn"); tt_text.prepend(tr("WARNING")); break; case(PI_NOTE): stock_name.append("note"); tt_text.prepend(tr("NOTE")); break; case(PI_CHAT): stock_name.append("chat"); tt_text.prepend(tr("CHAT")); break; // case(PI_COMMENT): // m_expertStatus.setText(""); // break; default: stock_name.append("none"); tt_text = tr("No expert information"); break; } StockIcon expert_icon(stock_name); expert_button_->setIcon(expert_icon); expert_button_->setToolTip(tt_text); expert_button_->show(); } // ui/gtk/main_statusbar.c void MainStatusBar::setFileName(CaptureFile &cf) { if (cf.isValid()) { popFileStatus(); QString msgtip = QString("%1 (%2)") .arg(cf.capFile()->filename) .arg(file_size_to_qstring(cf.capFile()->f_datalen)); pushFileStatus(cf.fileName(), msgtip); } } void MainStatusBar::changeEvent(QEvent *event) { if (event->type() == QEvent::LanguageChange) { info_status_.popText(STATUS_CTX_MAIN); info_status_.pushText(ready_msg_, STATUS_CTX_MAIN); showCaptureStatistics(); pushProfileName(); } QStatusBar::changeEvent(event); } void MainStatusBar::setCaptureFile(capture_file *cf) { cap_file_ = cf; comment_button_->setEnabled(cap_file_ != NULL); } void MainStatusBar::selectedFieldChanged(FieldInformation * finfo) { QString item_info; if ( ! finfo ) return; FieldInformation::HeaderInfo hInfo = finfo->headerInfo(); if ( hInfo.isValid ) { if ( hInfo.description.length() > 0 ) { item_info.append(hInfo.description); } else { item_info.append(hInfo.name); } } if (!item_info.isEmpty()) { int finfo_length; if ( hInfo.isValid ) item_info.append(" (" + hInfo.abbreviation + ")"); finfo_length = finfo->position().length + finfo->appendix().length; if (finfo_length == 1) { item_info.append(tr(", 1 byte")); } else if (finfo_length > 1) { item_info.append(QString(tr(", %1 bytes")).arg(finfo_length)); } } pushFieldStatus(item_info); } void MainStatusBar::pushTemporaryStatus(const QString &message) { info_status_.pushText(message, STATUS_CTX_TEMPORARY); } void MainStatusBar::popTemporaryStatus() { info_status_.popText(STATUS_CTX_TEMPORARY); } void MainStatusBar::pushFileStatus(const QString &message, const QString &messagetip) { info_status_.pushText(message, STATUS_CTX_FILE); info_status_.setToolTip(messagetip); expertUpdate(); } void MainStatusBar::popFileStatus() { info_status_.popText(STATUS_CTX_FILE); info_status_.setToolTip(QString()); } void MainStatusBar::pushFieldStatus(const QString &message) { if (message.isEmpty()) { popFieldStatus(); } else { info_status_.pushText(message, STATUS_CTX_FIELD); } } void MainStatusBar::popFieldStatus() { info_status_.popText(STATUS_CTX_FIELD); } void MainStatusBar::highlightedFieldChanged(FieldInformation * finfo) { QString hint; if ( finfo ) { FieldInformation::Position pos = finfo->position(); QString field_str; if (pos.length < 2) { hint = QString(tr("Byte %1")).arg(pos.start); } else { hint = QString(tr("Bytes %1-%2")).arg(pos.start).arg(pos.start + pos.length - 1); } hint += QString(": %1 (%2)") .arg(finfo->headerInfo().name) .arg(finfo->headerInfo().abbreviation); } pushByteStatus(hint); } void MainStatusBar::pushByteStatus(const QString &message) { if (message.isEmpty()) { popByteStatus(); } else { info_status_.pushText(message, STATUS_CTX_BYTE); } } void MainStatusBar::popByteStatus() { info_status_.popText(STATUS_CTX_BYTE); } void MainStatusBar::pushFilterStatus(const QString &message) { if (message.isEmpty()) { popFilterStatus(); } else { info_status_.pushText(message, STATUS_CTX_FILTER); } expertUpdate(); } void MainStatusBar::popFilterStatus() { info_status_.popText(STATUS_CTX_FILTER); } void MainStatusBar::pushPacketStatus(const QString &message) { if (message.isEmpty()) { popPacketStatus(); } else { packet_status_.pushText(message, STATUS_CTX_MAIN); } } void MainStatusBar::popPacketStatus() { packet_status_.popText(STATUS_CTX_MAIN); } void MainStatusBar::pushProfileStatus(const QString &message) { profile_status_.pushText(message, STATUS_CTX_MAIN); } void MainStatusBar::pushProfileName() { const gchar *cur_profile = get_profile_name(); QString status = tr("Profile: ") + cur_profile; popProfileStatus(); pushProfileStatus(status); if (profile_exists(cur_profile, FALSE) && strcmp (cur_profile, DEFAULT_PROFILE) != 0) { edit_action_->setEnabled(true); delete_action_->setEnabled(true); } else { edit_action_->setEnabled(false); delete_action_->setEnabled(false); } } void MainStatusBar::pushBusyStatus(const QString &message, const QString &messagetip) { info_status_.pushText(message, STATUS_CTX_PROGRESS); info_status_.setToolTip(messagetip); progress_frame_.showBusy(true, false, NULL); } void MainStatusBar::popBusyStatus() { info_status_.popText(STATUS_CTX_PROGRESS); info_status_.setToolTip(QString()); progress_frame_.hide(); } void MainStatusBar::popProfileStatus() { profile_status_.popText(STATUS_CTX_MAIN); } void MainStatusBar::pushProgressStatus(const QString &message, bool animate, bool terminate_is_stop, gboolean *stop_flag) { info_status_.pushText(message, STATUS_CTX_PROGRESS); progress_frame_.showProgress(animate, terminate_is_stop, stop_flag); } void MainStatusBar::updateProgressStatus(int value) { progress_frame_.setValue(value); } void MainStatusBar::popProgressStatus() { info_status_.popText(STATUS_CTX_PROGRESS); progress_frame_.hide(); } void MainStatusBar::selectedFrameChanged(int frameNum) { Q_UNUSED(frameNum); showCaptureStatistics(); } void MainStatusBar::showCaptureStatistics() { QString packets_str; #ifdef HAVE_LIBPCAP /* Do we have any packets? */ if (cs_fixed_ && cs_count_ > 0) { if (prefs.gui_qt_show_selected_packet && cap_file_->current_frame) { packets_str.append(QString(tr("Selected Packet: %1 %2 ")) .arg(cap_file_->current_frame->num) .arg(UTF8_MIDDLE_DOT)); } packets_str.append(QString(tr("Packets: %1")) .arg(cs_count_)); } else if (cap_file_ && cs_count_ > 0) { if (prefs.gui_qt_show_selected_packet && cap_file_->current_frame) { packets_str.append(QString(tr("Selected Packet: %1 %2 ")) .arg(cap_file_->current_frame->num) .arg(UTF8_MIDDLE_DOT)); } packets_str.append(QString(tr("Packets: %1 %4 Displayed: %2 (%3%)")) .arg(cap_file_->count) .arg(cap_file_->displayed_count) .arg((100.0*cap_file_->displayed_count)/cap_file_->count, 0, 'f', 1) .arg(UTF8_MIDDLE_DOT)); if(cap_file_->marked_count > 0) { packets_str.append(QString(tr(" %1 Marked: %2 (%3%)")) .arg(UTF8_MIDDLE_DOT) .arg(cap_file_->marked_count) .arg((100.0*cap_file_->marked_count)/cap_file_->count, 0, 'f', 1)); } if(cap_file_->drops_known) { packets_str.append(QString(tr(" %1 Dropped: %2 (%3%)")) .arg(UTF8_MIDDLE_DOT) .arg(cap_file_->drops) .arg((100.0*cap_file_->drops)/cap_file_->count, 0, 'f', 1)); } if(cap_file_->ignored_count > 0) { packets_str.append(QString(tr(" %1 Ignored: %2 (%3%)")) .arg(UTF8_MIDDLE_DOT) .arg(cap_file_->ignored_count) .arg((100.0*cap_file_->ignored_count)/cap_file_->count, 0, 'f', 1)); } if(prefs.gui_qt_show_file_load_time && !cap_file_->is_tempfile) { /* Loading an existing file */ gulong computed_elapsed = cf_get_computed_elapsed(cap_file_); packets_str.append(QString(tr(" %1 Load time: %2:%3.%4")) .arg(UTF8_MIDDLE_DOT) .arg(computed_elapsed/60000) .arg(computed_elapsed%60000/1000) .arg(computed_elapsed%1000)); } } else #endif // HAVE_LIBPCAP { packets_str = tr("No Packets"); } popPacketStatus(); pushPacketStatus(packets_str); } void MainStatusBar::updateCaptureStatistics(capture_session *cap_session) { cs_fixed_ = false; #ifndef HAVE_LIBPCAP Q_UNUSED(cap_session) #else if ((!cap_session || cap_session->cf == cap_file_) && cap_file_ && cap_file_->count) { cs_count_ = cap_file_->count; } else { cs_count_ = 0; } #endif // HAVE_LIBPCAP showCaptureStatistics(); } void MainStatusBar::updateCaptureFixedStatistics(capture_session *cap_session) { cs_fixed_ = true; #ifndef HAVE_LIBPCAP Q_UNUSED(cap_session) #else if (cap_session && cap_session->count) { cs_count_ = cap_session->count; } else { cs_count_ = 0; } #endif // HAVE_LIBPCAP showCaptureStatistics(); } void MainStatusBar::showProfileMenu(const QPoint &global_pos, Qt::MouseButton button) { const gchar *profile_name = get_profile_name(); bool separator_added = false; GList *fl_entry; profile_def *profile; QAction *pa; init_profile_list(); fl_entry = current_profile_list(); profile_menu_.clear(); while (fl_entry && fl_entry->data) { profile = (profile_def *) fl_entry->data; if (!profile->is_global || !profile_exists(profile->name, false)) { if (profile->is_global && !separator_added) { profile_menu_.addSeparator(); separator_added = true; } pa = profile_menu_.addAction(profile->name); if (strcmp(profile->name, profile_name) == 0) { /* Current profile */ pa->setCheckable(true); pa->setChecked(true); } connect(pa, SIGNAL(triggered()), this, SLOT(switchToProfile())); } fl_entry = g_list_next(fl_entry); } if (button == Qt::LeftButton) { profile_menu_.exec(global_pos); } else { ctx_menu_.exec(global_pos); } } void MainStatusBar::toggleBackground(bool enabled) { if (enabled) { setStyleSheet(QString( "QStatusBar {" " color: #%1;" " background-color: #%2;" "}" ) .arg(ws_css_warn_text, 6, 16, QChar('0')) .arg(ws_css_warn_background, 6, 16, QChar('0'))); } else { setStyleSheet(QString()); } } void MainStatusBar::switchToProfile() { QAction *pa = qobject_cast(sender()); if (pa) { wsApp->setConfigurationProfile(pa->text().toUtf8().constData()); } } void MainStatusBar::manageProfile() { QAction *pa = qobject_cast(sender()); if (pa) { ProfileDialog cp_dialog; cp_dialog.execAction(static_cast(pa->data().toInt())); } } /* * Editor modelines * * Local Variables: * c-basic-offset: 4 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=4 tabstop=8 expandtab: * :indentSize=4:tabSize=8:noTabs=true: */