Qt: Add dynamic menu support.

Generalize the dynamic menu code and make it possible to connect
multiple types of actions to their corresponding slots.

Change-Id: Ib915ad5a666310e2a6e366fada006336820d1653
Reviewed-on: https://code.wireshark.org/review/9568
Reviewed-by: Gerald Combs <gerald@wireshark.org>
This commit is contained in:
Gerald Combs 2015-07-08 15:44:04 -07:00
parent 131e0ab7ea
commit a8faa04234
10 changed files with 121 additions and 98 deletions

View File

@ -56,7 +56,7 @@ extern "C" {
/** The group this stat should be registered in. */
typedef enum {
REGISTER_ANALYZE_GROUP_UNSORTED, /* unsorted analyze stuff */
REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER, /* conversation filters */
REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER, /* conversation filters. Unused? */
REGISTER_STAT_GROUP_UNSORTED, /* unsorted statistic function */
REGISTER_STAT_GROUP_GENERIC, /* generic statistic function, not specific to a protocol */
REGISTER_STAT_GROUP_CONVERSATION_LIST, /* member of the conversation list */

View File

@ -74,6 +74,7 @@ public:
retap_(retap)
{
setText(title);
setObjectName(FunnelStatistics::actionName());
}
void triggerCallback() {
@ -94,6 +95,7 @@ private:
};
static QList<FunnelAction *> funnel_actions_;
const QString FunnelStatistics::action_name_ = "FunnelStatisticsAction";
FunnelStatistics::FunnelStatistics(QObject *parent, CaptureFile &cf) :
QObject(parent),
@ -276,7 +278,7 @@ static void register_menu_cb(const char *name,
gpointer callback_data,
gboolean retap) {
FunnelAction *funnel_action = new FunnelAction(name, callback, callback_data, retap);
wsApp->addFunnelGroupItem(group, funnel_action);
wsApp->addDynamicMenuGroupItem(group, funnel_action);
funnel_actions_ << funnel_action;
}

View File

@ -44,6 +44,7 @@ public:
void reloadPackets();
void emitApplyDisplayFilter();
void emitOpenCaptureFile(QString &cf_path, QString &filter);
static const QString &actionName() { return action_name_; }
signals:
void setDisplayFilter(const QString &filter);
@ -55,6 +56,7 @@ public slots:
void displayFilterTextChanged(const QString &filter);
private:
static const QString action_name_;
struct _funnel_ops_t *funnel_ops_;
CaptureFile &capture_file_;
QByteArray display_filter_;

View File

@ -53,6 +53,7 @@
#include "proto_tree.h"
#include "simple_dialog.h"
#include "stock_icon.h"
#include "tap_parameter_dialog.h"
#include "wireless_frame.h"
#include "wireshark_application.h"
@ -239,9 +240,8 @@ MainWindow::MainWindow(QWidget *parent) :
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(zoomText()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatisticsMenus()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addDynamicMenus()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addExternalMenus()));
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addFunnelMenus()));
connect(wsApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry()));
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes()));
@ -1977,33 +1977,93 @@ void MainWindow::setForCaptureInProgress(gboolean capture_in_progress)
#endif
}
void MainWindow::addStatisticsMenus()
void MainWindow::addDynamicMenus()
{
// actionStatistics_REGISTER_STAT_GROUP_UNSORTED should exist and be.
// invisible.
QList<QAction *>unsorted_actions = wsApp->statisticsGroupItems(REGISTER_STAT_GROUP_UNSORTED);
QList<register_stat_group_t> menu_groups = QList<register_stat_group_t>()
<< REGISTER_ANALYZE_GROUP_UNSORTED
<< REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER
<< REGISTER_STAT_GROUP_UNSORTED
<< REGISTER_STAT_GROUP_GENERIC
<< REGISTER_STAT_GROUP_CONVERSATION_LIST
<< REGISTER_STAT_GROUP_ENDPOINT_LIST
<< REGISTER_STAT_GROUP_RESPONSE_TIME
<< REGISTER_STAT_GROUP_TELEPHONY
<< REGISTER_STAT_GROUP_TELEPHONY_ANSI
<< REGISTER_STAT_GROUP_TELEPHONY_GSM
<< REGISTER_STAT_GROUP_TELEPHONY_LTE
<< REGISTER_STAT_GROUP_TELEPHONY_SCTP
<< REGISTER_TOOLS_GROUP_UNSORTED;
foreach (QAction *unsorted_action, unsorted_actions) {
main_ui_->menuStatistics->insertAction(
main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
unsorted_action);
connect(unsorted_action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
foreach (register_stat_group_t menu_group, menu_groups) {
QList<QAction *>actions = wsApp->dynamicMenuGroupItems(menu_group);
foreach (QAction *action, actions) {
switch (menu_group) {
case REGISTER_ANALYZE_GROUP_UNSORTED:
case REGISTER_STAT_GROUP_UNSORTED:
main_ui_->menuStatistics->insertAction(
main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
action);
break;
case REGISTER_STAT_GROUP_RESPONSE_TIME:
main_ui_->menuServiceResponseTime->addAction(action);
break;
case REGISTER_STAT_GROUP_TELEPHONY:
main_ui_->menuTelephony->addAction(action);
break;
case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
main_ui_->menuANSI->addAction(action);
break;
case REGISTER_TOOLS_GROUP_UNSORTED:
{
// Allow the creation of submenus. Mimics the behavor of
// ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
// and GtkUIManager.
//
// For now we limit the insanity to the "Tools" menu.
QStringList menu_path = action->text().split('/');
QMenu *cur_menu = main_ui_->menuTools;
while (menu_path.length() > 1) {
QString menu_title = menu_path.takeFirst();
#if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
#else
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower());
if (submenu && submenu->parent() != cur_menu) submenu = NULL;
#endif
if (!submenu) {
submenu = cur_menu->addMenu(menu_title);
submenu->setObjectName(menu_title.toLower());
}
cur_menu = submenu;
}
action->setText(menu_path.last());
cur_menu->addAction(action);
break;
}
default:
// qDebug() << "FIX: Add" << action->text() << "to the menu";
break;
}
// Connect each action type to its corresponding slot. We to
// distinguish various types of actions. Setting their objectName
// seems to work OK.
if (action->objectName() == TapParameterDialog::actionName()) {
connect(action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
} else if (action->objectName() == FunnelStatistics::actionName()) {
connect(action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered()));
}
}
}
// Response time
QList<QAction *>sg_actions = wsApp->statisticsGroupItems(REGISTER_STAT_GROUP_RESPONSE_TIME);
foreach (QAction *sg_action, sg_actions) {
main_ui_->menuServiceResponseTime->addAction(sg_action);
connect(sg_action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
// Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
// We've added a placeholder in order to make sure the "Tools" menu is
// visible. Hide it as needed.
if (wsApp->dynamicMenuGroupItems(REGISTER_TOOLS_GROUP_UNSORTED).length() > 0) {
main_ui_->actionToolsPlaceholder->setVisible(false);
}
// Telephony
sg_actions = wsApp->statisticsGroupItems(REGISTER_STAT_GROUP_TELEPHONY);
foreach (QAction *sg_action, sg_actions) {
main_ui_->menuTelephony->addAction(sg_action);
connect(sg_action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_ANSI).length() > 0) {
main_ui_->actionTelephonyANSIPlaceholder->setVisible(false);
}
}
@ -2080,43 +2140,6 @@ void MainWindow::addExternalMenus()
}
}
void MainWindow::addFunnelMenus()
{
// XXX Add support for MENU_STAT_UNSORTED, MENU_STAT_GENERIC, etc. We
// should probably add a common routine that we can use in
// addStatisticsMenus as well.
QList<QAction *>funnel_actions = wsApp->funnelGroupItems(REGISTER_TOOLS_GROUP_UNSORTED);
// Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
// We've added a placeholder in order to make sure the "Tools" menu is
// visible. Hide it as needed.
if (funnel_actions.length() > 0) {
main_ui_->actionToolsPlaceholder->setVisible(false);
}
foreach (QAction *tools_action, funnel_actions) {
QStringList menu_path = tools_action->text().split('/');
QMenu *cur_menu = main_ui_->menuTools;
while (menu_path.length() > 1) {
QString menu_title = menu_path.takeFirst();
#if (QT_VERSION > QT_VERSION_CHECK(5, 0, 0))
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
#else
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower());
if (submenu && submenu->parent() != cur_menu) submenu = NULL;
#endif
if (!submenu) {
submenu = cur_menu->addMenu(menu_title);
submenu->setObjectName(menu_title.toLower());
}
cur_menu = submenu;
}
tools_action->setText(menu_path.last());
cur_menu->addAction(tools_action);
connect(tools_action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered()));
}
}
/*
* Editor modelines
*

View File

@ -262,9 +262,8 @@ private slots:
void showColumnEditor(int column);
void showPreferenceEditor(); // module_t *, pref *
void addStatsPluginsToMenu();
void addStatisticsMenus();
void addDynamicMenus();
void addExternalMenus();
void addFunnelMenus();
void startInterfaceCapture(bool valid);

View File

@ -481,7 +481,14 @@
</property>
<addaction name="actionTelephonyRTPStreams"/>
</widget>
<widget class="QMenu" name="menuANSI">
<property name="title">
<string>ANSI</string>
</property>
<addaction name="actionTelephonyANSIPlaceholder"/>
</widget>
<addaction name="actionTelephonyVoipCalls"/>
<addaction name="menuANSI"/>
<addaction name="actionTelephonyISUPMessages"/>
<addaction name="menuRTP"/>
<addaction name="menuRTSP"/>
@ -2390,6 +2397,14 @@
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="actionTelephonyANSIPlaceholder">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>No ANSI statistics registered</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -70,6 +70,7 @@
const int expand_all_threshold_ = 100; // Arbitrary
static QHash<const QString, tpdCreator> cfg_str_to_creator_;
const QString TapParameterDialog::action_name_ = "TapParameterAction";
TapParameterDialog::TapParameterDialog(QWidget &parent, CaptureFile &cf, int help_topic) :
WiresharkDialog(parent, cf),
@ -117,8 +118,9 @@ void TapParameterDialog::registerDialog(const QString title, const char *cfg_abb
cfg_str_to_creator_[cfg_str] = creator;
QAction *tpd_action = new QAction(title, NULL);
tpd_action->setObjectName(action_name_);
tpd_action->setData(cfg_str);
wsApp->addStatisticsGroupItem(group, tpd_action);
wsApp->addDynamicMenuGroupItem(group, tpd_action);
}
TapParameterDialog *TapParameterDialog::showTapParameterStatistics(QWidget &parent, CaptureFile &cf, const QString cfg_str, const QString arg, void *)

View File

@ -52,6 +52,7 @@ public:
explicit TapParameterDialog(QWidget &parent, CaptureFile &cf, int help_topic = 0);
~TapParameterDialog();
static const QString &actionName() { return action_name_; }
static void registerDialog(const QString title, const char *cfg_abbr, register_stat_group_t group, stat_tap_init_cb tap_init_cb, tpdCreator creator);
static TapParameterDialog *showTapParameterStatistics(QWidget &parent, CaptureFile &cf, const QString cfg_str, const QString arg, void *);
@ -81,6 +82,7 @@ protected slots:
private:
Ui::TapParameterDialog *ui;
int help_topic_;
static const QString action_name_;
// Called by the constructor. The subclass should tap packets here.
virtual void fillTree() = 0;

View File

@ -89,7 +89,7 @@ WiresharkApplication *wsApp = NULL;
static char *last_open_dir = NULL;
static bool updated_last_open_dir = FALSE;
static QList<recent_item_status *> recent_items_;
static QHash<int, QList<QAction *> > statistics_groups_;
static QHash<int, QList<QAction *> > dynamic_menu_groups_;
static QHash<int, QList<QAction *> > funnel_groups_;
QString WiresharkApplication::window_title_separator_ = QString::fromUtf8(" " UTF8_MIDDLE_DOT " ");
@ -573,44 +573,25 @@ void WiresharkApplication::emitTapParameterSignal(const QString cfg_abbr, const
}
// XXX Combine statistics and funnel routines into addGroupItem + groupItems?
void WiresharkApplication::addStatisticsGroupItem(int group, QAction *sg_action)
void WiresharkApplication::addDynamicMenuGroupItem(int group, QAction *sg_action)
{
if (!statistics_groups_.contains(group)) {
statistics_groups_[group] = QList<QAction *>();
if (!dynamic_menu_groups_.contains(group)) {
dynamic_menu_groups_[group] = QList<QAction *>();
}
statistics_groups_[group] << sg_action;
dynamic_menu_groups_[group] << sg_action;
}
QList<QAction *> WiresharkApplication::statisticsGroupItems(int group)
QList<QAction *> WiresharkApplication::dynamicMenuGroupItems(int group)
{
if (!statistics_groups_.contains(group)) {
if (!dynamic_menu_groups_.contains(group)) {
return QList<QAction *>();
}
QList<QAction *> sgi_list = statistics_groups_[group];
QList<QAction *> sgi_list = dynamic_menu_groups_[group];
std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan);
return sgi_list;
}
void WiresharkApplication::addFunnelGroupItem(int group, QAction *fg_action)
{
if (!funnel_groups_.contains(group)) {
funnel_groups_[group] = QList<QAction *>();
}
funnel_groups_[group] << fg_action;
}
QList<QAction *> WiresharkApplication::funnelGroupItems(int group)
{
if (!funnel_groups_.contains(group)) {
return QList<QAction *>();
}
QList<QAction *> fgi_list = funnel_groups_[group];
std::sort(fgi_list.begin(), fgi_list.end(), qActionLessThan);
return fgi_list;
}
#ifdef HAVE_LIBPCAP
static void

View File

@ -74,11 +74,8 @@ public:
void emitAppSignal(AppSignal signal);
void emitStatCommandSignal(const QString &menu_path, const char *arg, void *userdata);
void emitTapParameterSignal(const QString cfg_abbr, const QString arg, void *userdata);
// Map a register_stat_group_t to a list of stat_tap_ui.title
void addStatisticsGroupItem(int group, QAction *sg_action);
QList<QAction *>statisticsGroupItems(int group);
void addFunnelGroupItem(int group, QAction *fg_action);
QList<QAction *> funnelGroupItems(int group);
void addDynamicMenuGroupItem(int group, QAction *sg_action);
QList<QAction *> dynamicMenuGroupItems(int group);
void allSystemsGo();
void refreshLocalInterfaces();