/* filter_expression_toolbar.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *dfe_property_ = "display filter expression"; //TODO : Fix Translate static const char *dfe_property_label_ = "display_filter_expression_label"; static const char *dfe_property_expression_ = "display_filter_expression_expr"; static const char *dfe_property_comment_ = "display_filter_expression_comment"; static const char *dfe_menu_ = "filter_menu"; #define PARENT_SEPARATOR "//" struct filter_expression_data { FilterExpressionToolBar* toolbar; bool actions_added; }; FilterExpressionToolBar::FilterExpressionToolBar(QWidget * parent) : DragDropToolBar(parent) { updateStyleSheet(); setContextMenuPolicy(Qt::CustomContextMenu); /* Give minimum space to the bar, so that drops on an empty bar will work */ setMinimumWidth(10); connect (this, &QWidget::customContextMenuRequested, this, &FilterExpressionToolBar::onCustomMenuHandler); connect(this, &DragDropToolBar::actionMoved, this, &FilterExpressionToolBar::onActionMoved); connect(this, &DragDropToolBar::newFilterDropped, this, &FilterExpressionToolBar::onFilterDropped); connect(mainApp, &MainApplication::appInitialized, this, &FilterExpressionToolBar::filterExpressionsChanged); connect(mainApp, &MainApplication::filterExpressionsChanged, this, &FilterExpressionToolBar::filterExpressionsChanged); } bool FilterExpressionToolBar::event(QEvent *event) { switch (event->type()) { case QEvent::ApplicationPaletteChange: updateStyleSheet(); break; default: break; } return DragDropToolBar::event(event); } void FilterExpressionToolBar::onCustomMenuHandler(const QPoint& pos) { QAction * filterAction = actionAt(pos); if (! filterAction) return; customMenu(this, filterAction, pos); } void FilterExpressionToolBar::customMenu(FilterExpressionToolBar * target, QAction * filterAction, const QPoint& pos) { QMenu * filterMenu = new QMenu(target); filterMenu->setAttribute(Qt::WA_DeleteOnClose); /* Only display context menu for actual filter actions */ QString filterText = filterAction->property(dfe_property_expression_).toString().trimmed(); if (!filterText.isEmpty()) { filterMenu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionApply, filterText, true, target)); filterMenu->addMenu(FilterAction::createFilterMenu(FilterAction::ActionPrepare, filterText, true, target)); filterMenu->addSeparator(); filterMenu->addAction(FilterAction::copyFilterAction(filterText, target)); filterMenu->addSeparator(); QAction * actEdit = filterMenu->addAction(tr("Edit")); connect(actEdit, &QAction::triggered, target, &FilterExpressionToolBar::editFilter); actEdit->setProperty(dfe_property_label_, filterAction->property(dfe_property_label_)); actEdit->setProperty(dfe_property_expression_, filterAction->property(dfe_property_expression_)); actEdit->setData(filterAction->data()); QAction * actDisable = filterMenu->addAction(tr("Disable")); connect(actDisable, &QAction::triggered, target, &FilterExpressionToolBar::disableFilter); actDisable->setProperty(dfe_property_label_, filterAction->property(dfe_property_label_)); actDisable->setProperty(dfe_property_expression_, filterAction->property(dfe_property_expression_)); actDisable->setData(filterAction->data()); QAction * actRemove = filterMenu->addAction(tr("Remove")); connect(actRemove, &QAction::triggered, target, &FilterExpressionToolBar::removeFilter); actRemove->setProperty(dfe_property_label_, filterAction->property(dfe_property_label_)); actRemove->setProperty(dfe_property_expression_, filterAction->property(dfe_property_expression_)); actRemove->setData(filterAction->data()); filterMenu->addSeparator(); } QAction *actFilter = filterMenu->addAction(tr("Filter Button Preferences...")); connect(actFilter, &QAction::triggered, target, &FilterExpressionToolBar::toolBarShowPreferences); /* Forcing the menus to get closed, no matter which action has been triggered */ connect(filterMenu, &QMenu::triggered, this, &FilterExpressionToolBar::closeMenu); filterMenu->popup(mapToGlobal(pos)); } void FilterExpressionToolBar::filterExpressionsChanged() { struct filter_expression_data data; data.toolbar = this; data.actions_added = false; // Hiding and showing seems to be the only way to get the layout to // work correctly in some cases. See bug 14121 for details. clear(); setUpdatesEnabled(false); hide(); // XXX Add a context menu for removing and changing buttons. filter_expression_iterate_expressions(filter_expression_add_action, &data); show(); setUpdatesEnabled(true); } void FilterExpressionToolBar::removeFilter() { UatModel * uatModel = new UatModel(this, "Display expressions"); QString label = ((QAction *)sender())->property(dfe_property_label_).toString(); QString expr = ((QAction *)sender())->property(dfe_property_expression_).toString(); int idx = uatRowIndexForFilter(label, expr); QModelIndex rowIndex = uatModel->index(idx, 0); if (rowIndex.isValid()) { uatModel->removeRow(rowIndex.row()); save_migrated_uat("Display expressions", &prefs.filter_expressions_old); filterExpressionsChanged(); } } WiresharkMimeData * FilterExpressionToolBar::createMimeData(QString name, int position) { ToolbarEntryMimeData * element = new ToolbarEntryMimeData(name, position); UatModel * uatModel = new UatModel(this, "Display expressions"); QModelIndex rowIndex; for (int cnt = 0; cnt < uatModel->rowCount() && ! rowIndex.isValid(); cnt++) { if (uatModel->data(uatModel->index(cnt, 1), Qt::DisplayRole).toString().compare(name) == 0) { rowIndex = uatModel->index(cnt, 2); element->setFilter(rowIndex.data().toString()); } } return element; } void FilterExpressionToolBar::onActionMoved(QAction* action, int oldPos, int newPos) { gchar* err = NULL; if (oldPos == newPos) return; QString label = action->property(dfe_property_label_).toString(); QString expr = action->property(dfe_property_expression_).toString(); int idx = uatRowIndexForFilter(label, expr); if (idx > -1 && oldPos > -1 && newPos > -1) { uat_t * table = uat_get_table_by_name("Display expressions"); uat_move_index(table, oldPos, newPos); uat_save(table, &err); g_free(err); } } void FilterExpressionToolBar::disableFilter() { QString label = ((QAction *)sender())->property(dfe_property_label_).toString(); QString expr = ((QAction *)sender())->property(dfe_property_expression_).toString(); int idx = uatRowIndexForFilter(label, expr); UatModel * uatModel = new UatModel(this, "Display expressions"); QModelIndex rowIndex = uatModel->index(idx, 0); if (rowIndex.isValid()) { uatModel->setData(rowIndex, QVariant::fromValue(false)); save_migrated_uat("Display expressions", &prefs.filter_expressions_old); filterExpressionsChanged(); } } void FilterExpressionToolBar::editFilter() { if (! sender()) return; QString label = ((QAction *)sender())->property(dfe_property_label_).toString(); QString expr = ((QAction *)sender())->property(dfe_property_expression_).toString(); int idx = uatRowIndexForFilter(label, expr); if (idx > -1) emit filterEdit(idx); } void FilterExpressionToolBar::onFilterDropped(QString description, QString filter) { if (filter.length() == 0) return; filter_expression_new(qUtf8Printable(description), qUtf8Printable(filter), qUtf8Printable(description), TRUE); save_migrated_uat("Display expressions", &prefs.filter_expressions_old); filterExpressionsChanged(); } void FilterExpressionToolBar::toolBarShowPreferences() { emit filterPreferences(); } void FilterExpressionToolBar::updateStyleSheet() { // Try to draw 1-pixel-wide separator lines from the button label // ascent to its baseline. setStyleSheet(QString( "QToolBar { background: none; border: none; spacing: 1px; }" "QFrame { background: none; min-width: 1px; max-width: 1px; }" )); } int FilterExpressionToolBar::uatRowIndexForFilter(QString label, QString expression) { int result = -1; if (expression.length() == 0) return result; UatModel * uatModel = new UatModel(this, "Display expressions"); QModelIndex rowIndex; if (label.length() > 0) { for (int cnt = 0; cnt < uatModel->rowCount() && ! rowIndex.isValid(); cnt++) { if (uatModel->data(uatModel->index(cnt, 1), Qt::DisplayRole).toString().compare(label) == 0 && uatModel->data(uatModel->index(cnt, 2), Qt::DisplayRole).toString().compare(expression) == 0) { rowIndex = uatModel->index(cnt, 2); } } } else { rowIndex = uatModel->findRowForColumnContent(((QAction *)sender())->data(), 2); } if (rowIndex.isValid()) result = rowIndex.row(); delete uatModel; return result; } bool FilterExpressionToolBar::eventFilter(QObject *obj, QEvent *event) { QMenu * qm = qobject_cast(obj); if (qm && qm->property(dfe_menu_).toBool()) { if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *ctx = static_cast(event); QAction * filterAction = qm->actionAt(ctx->pos()); if (filterAction) { QPoint tb_pos = this->mapFromGlobal(ctx->globalPos()); customMenu(this, filterAction, tb_pos); } return true; } else if (event->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(event); QAction * filterAction = qm->actionAt(helpEvent->pos()); if (filterAction) { QToolTip::showText(helpEvent->globalPos(), filterAction->property(dfe_property_comment_).toString().trimmed()); } else { QToolTip::hideText(); event->ignore(); } return true; } } return DragDropToolBar::eventFilter(obj, event); } void FilterExpressionToolBar::closeMenu(QAction * /*sender*/) { foreach(QAction * entry, actions()) { QWidget * widget = widgetForAction(entry); QToolButton * tb = qobject_cast(widget); if (tb && tb->menu()) tb->menu()->close(); } } QMenu * FilterExpressionToolBar::findParentMenu(const QStringList tree, void *fed_data, QMenu *parent ) { if (!fed_data) return Q_NULLPTR; struct filter_expression_data* data = (filter_expression_data*)fed_data; if (!data->toolbar) return Q_NULLPTR; if (! tree.isEmpty()) { if (!parent) { /* Searching existing main menus */ foreach(QAction * entry, data->toolbar->actions()) { QWidget * widget = data->toolbar->widgetForAction(entry); QToolButton * tb = qobject_cast(widget); if (tb && tb->menu() && tb->text().compare(tree.at(0).trimmed()) == 0) return findParentMenu(tree.mid(1), fed_data, tb->menu()); } } else if (parent) { QString menuName = tree.at(0).trimmed(); /* Iterate to see, if we next have to jump into another submenu */ foreach(QAction *entry, parent->actions()) { if (entry->menu() && entry->text().compare(menuName) == 0) return findParentMenu(tree.mid(1), fed_data, entry->menu()); } /* Submenu not found, creating */ QMenu * subMenu = new QMenu(menuName); subMenu->installEventFilter(data->toolbar); subMenu->setProperty(dfe_menu_, QVariant::fromValue(true)); parent->addMenu(subMenu); return findParentMenu(tree.mid(1), fed_data, subMenu); } /* No menu has been found, create one */ QString parentName = tree.at(0).trimmed(); QToolButton * menuButton = new QToolButton(); menuButton->setText(parentName); menuButton->setPopupMode(QToolButton::MenuButtonPopup); QMenu * parentMenu = new QMenu(menuButton); parentMenu->installEventFilter(data->toolbar); parentMenu->setProperty(dfe_menu_, QVariant::fromValue(true)); menuButton->setMenu(parentMenu); // Required for QToolButton::MenuButtonPopup. connect(menuButton, &QToolButton::pressed, menuButton, &QToolButton::showMenu); data->toolbar->addWidget(menuButton); return findParentMenu(tree.mid(1), fed_data, parentMenu); } else if (parent) return parent; return Q_NULLPTR; } bool FilterExpressionToolBar::filter_expression_add_action(const void *key _U_, void *value, void *user_data) { filter_expression_t* fe = (filter_expression_t*)value; struct filter_expression_data* data = (filter_expression_data*)user_data; if (!fe->enabled) return FALSE; QString label = QString(fe->label); /* Search for parent menu and create if not found */ QStringList tree = label.split(PARENT_SEPARATOR); if (!tree.isEmpty()) tree.removeLast(); QMenu * parentMenu = findParentMenu(tree, data); if (parentMenu) label = label.mid(label.lastIndexOf(PARENT_SEPARATOR) + QString(PARENT_SEPARATOR).length()).trimmed(); QAction *dfb_action = new QAction(label, data->toolbar); if (strlen(fe->comment) > 0) { QString tooltip = QString("%1\n%2").arg(fe->comment).arg(fe->expression); dfb_action->setToolTip(tooltip); dfb_action->setProperty(dfe_property_comment_, tooltip); } else { dfb_action->setToolTip(fe->expression); dfb_action->setProperty(dfe_property_comment_, QString(fe->expression)); } dfb_action->setData(QString::fromUtf8(fe->expression)); dfb_action->setProperty(dfe_property_, true); dfb_action->setProperty(dfe_property_label_, QString(fe->label)); dfb_action->setProperty(dfe_property_expression_, QString(fe->expression)); if (data->actions_added) { QFrame *sep = new QFrame(); sep->setEnabled(false); data->toolbar->addWidget(sep); } if (parentMenu) parentMenu->addAction(dfb_action); else data->toolbar->addAction(dfb_action); connect(dfb_action, &QAction::triggered, data->toolbar, &FilterExpressionToolBar::filterClicked); data->actions_added = true; return FALSE; } void FilterExpressionToolBar::filterClicked() { bool prepare = false; QAction *dfb_action = qobject_cast(sender()); if (!dfb_action) return; QString filterText = dfb_action->data().toString(); prepare = (QApplication::keyboardModifiers() & Qt::ShiftModifier); emit filterSelected(filterText, prepare); }