forked from osmocom/wireshark
Add capture filter autocompletion.
Autocomplete on recent and saved capture filters along with the primitives in gencode.l in the libpcap sources. Move common autocomplete code to SyntaxLineEdit. Change-Id: I0931a6775bacf9c917c294befbbdaade51d19b93 Reviewed-on: https://code.wireshark.org/review/8542 Petri-Dish: Gerald Combs <gerald@wireshark.org> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Gerald Combs <gerald@wireshark.org>
This commit is contained in:
parent
fd985194f5
commit
af054591c3
|
@ -30,8 +30,6 @@
|
|||
#include "capture_filter_combo.h"
|
||||
#include "wireshark_application.h"
|
||||
|
||||
#include <QCompleter>
|
||||
|
||||
CaptureFilterCombo::CaptureFilterCombo(QWidget *parent) :
|
||||
QComboBox(parent),
|
||||
cf_edit_(NULL)
|
||||
|
@ -39,6 +37,10 @@ CaptureFilterCombo::CaptureFilterCombo(QWidget *parent) :
|
|||
cf_edit_ = new CaptureFilterEdit(this, true);
|
||||
|
||||
setEditable(true);
|
||||
// Enabling autocompletion here gives us two simultaneous completions:
|
||||
// Inline (highlighted text) for entire filters, handled here and popup
|
||||
// completion for fields handled by CaptureFilterEdit.
|
||||
setAutoCompletion(false);
|
||||
setLineEdit(cf_edit_);
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
setInsertPolicy(QComboBox::NoInsert);
|
||||
|
@ -72,7 +74,6 @@ CaptureFilterCombo::CaptureFilterCombo(QWidget *parent) :
|
|||
" left: 1px;"
|
||||
"}"
|
||||
);
|
||||
completer()->setCompletionMode(QCompleter::PopupCompletion);
|
||||
|
||||
connect(this, SIGNAL(interfacesChanged()), cf_edit_, SLOT(checkFilter()));
|
||||
connect(cf_edit_, SIGNAL(pushFilterSyntaxStatus(const QString&)),
|
||||
|
|
|
@ -26,17 +26,27 @@
|
|||
#include <epan/proto.h>
|
||||
|
||||
#include "capture_opts.h"
|
||||
#include "ui/capture_globals.h"
|
||||
|
||||
#include <ui/capture_globals.h>
|
||||
#include <ui/filters.h>
|
||||
#include <ui/utf8_entities.h>
|
||||
|
||||
#include "capture_filter_edit.h"
|
||||
#include "wireshark_application.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCompleter>
|
||||
#include <QPainter>
|
||||
#include <QStringListModel>
|
||||
#include <QStyleOptionFrame>
|
||||
|
||||
#include "ui/utf8_entities.h"
|
||||
#include "qt_ui_utils.h"
|
||||
|
||||
// To do:
|
||||
// - This duplicates some DisplayFilterEdit code.
|
||||
// - We need simplified (button- and dropdown-free) versions for use in dialogs and field-only checking.
|
||||
|
||||
|
||||
#if defined(Q_OS_MAC) && 0
|
||||
// http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSImage_Class/Reference/Reference.html
|
||||
// http://www.virtualbox.org/svn/vbox/trunk/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.mm
|
||||
|
@ -72,8 +82,26 @@ UIMiniCancelButton::UIMiniCancelButton(QWidget *pParent /* = 0 */)
|
|||
|
||||
#endif
|
||||
|
||||
static const QString libpcap_primitive_chars_ = "-0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
// XXX - We need simplified (button- and dropdown-free) versions for use in dialogs and field-only checking.
|
||||
// grep '^[a-z].*return [A-Z].*;$' scanner.l | awk '{gsub(/\|/, "\n") ; print " << \"" $1 "\""}' | sort
|
||||
static const QStringList libpcap_primitives_ = QStringList()
|
||||
<< "aarp" << "action" << "address1" << "address2" << "address3" << "address4"
|
||||
<< "ah" << "and" << "arp" << "atalk" << "bcc" << "broadcast" << "byte" << "carp"
|
||||
<< "clnp" << "connectmsg" << "csnp" << "decnet" << "direction" << "dpc"
|
||||
<< "dst" << "es-is" << "esis" << "esp" << "fddi" << "fisu" << "gateway"
|
||||
<< "greater" << "hdpc" << "hfisu" << "hlssu" << "hmsu" << "hopc" << "host"
|
||||
<< "hsio" << "hsls" << "icmp" << "icmp6" << "igmp" << "igrp" << "iih" << "ilmic"
|
||||
<< "inbound" << "ip" << "ip6" << "ipx" << "is-is" << "isis" << "iso" << "l1"
|
||||
<< "l2" << "lane" << "lat" << "len" << "less" << "link" << "llc" << "lsp"
|
||||
<< "lssu" << "lsu" << "mask" << "metac" << "metaconnect" << "mopdl" << "moprc"
|
||||
<< "mpls" << "msu" << "multicast" << "net" << "netbeui" << "oam" << "oamf4"
|
||||
<< "oamf4ec" << "oamf4sc" << "on" << "opc" << "or" << "outbound" << "pim"
|
||||
<< "port" << "portrange" << "pppoed" << "pppoes" << "proto" << "psnp" << "ra"
|
||||
<< "radio" << "rarp" << "reason" << "rnr" << "rset" << "sc" << "sca" << "sctp"
|
||||
<< "sio" << "sls" << "snp" << "src" << "srnr" << "stp" << "subtype" << "ta"
|
||||
<< "tcp" << "type" << "udp" << "vci" << "vlan" << "vpi" << "vrrp"
|
||||
;
|
||||
|
||||
CaptureFilterEdit::CaptureFilterEdit(QWidget *parent, bool plain) :
|
||||
SyntaxLineEdit(parent),
|
||||
|
@ -83,6 +111,10 @@ CaptureFilterEdit::CaptureFilterEdit(QWidget *parent, bool plain) :
|
|||
{
|
||||
setAccessibleName(tr("Capture filter entry"));
|
||||
|
||||
completion_model_ = new QStringListModel(this);
|
||||
setCompleter(new QCompleter(completion_model_, this));
|
||||
setCompletionTokenChars(libpcap_primitive_chars_);
|
||||
|
||||
placeholder_text_ = QString(tr("Enter a capture filter %1")).arg(UTF8_HORIZONTAL_ELLIPSIS);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
setPlaceholderText(placeholder_text_);
|
||||
|
@ -343,6 +375,41 @@ void CaptureFilterEdit::bookmarkClicked()
|
|||
emit addBookmark(text());
|
||||
}
|
||||
|
||||
void CaptureFilterEdit::buildCompletionList(const QString &primitive_word)
|
||||
{
|
||||
if (primitive_word.length() < 1) {
|
||||
completion_model_->setStringList(QStringList());
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab matching capture filters from our parent combo and from the
|
||||
// saved capture filters file. Skip ones that look like single fields
|
||||
// and assume they will be added below.
|
||||
QStringList complex_list;
|
||||
QComboBox *cf_combo = qobject_cast<QComboBox *>(parent());
|
||||
if (cf_combo) {
|
||||
for (int i = 0; i < cf_combo->count() ; i++) {
|
||||
QString recent_filter = cf_combo->itemText(i);
|
||||
|
||||
if (isComplexFilter(recent_filter)) {
|
||||
complex_list << recent_filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const GList *cf_item = get_filter_list_first(CFILTER_LIST); cf_item; cf_item = g_list_next(cf_item)) {
|
||||
const filter_def *cf_def = (filter_def *) cf_item->data;
|
||||
if (!cf_def || !cf_def->strval) continue;
|
||||
QString saved_filter = cf_def->strval;
|
||||
|
||||
if (isComplexFilter(saved_filter) && !complex_list.contains(saved_filter)) {
|
||||
complex_list << saved_filter;
|
||||
}
|
||||
}
|
||||
|
||||
completion_model_->setStringList(complex_list + libpcap_primitives_);
|
||||
completer()->setCompletionPrefix(primitive_word);
|
||||
}
|
||||
|
||||
void CaptureFilterEdit::applyCaptureFilter()
|
||||
{
|
||||
if (syntaxState() == Invalid) {
|
||||
|
|
|
@ -38,8 +38,8 @@ protected:
|
|||
void paintEvent(QPaintEvent *evt);
|
||||
#endif
|
||||
void resizeEvent(QResizeEvent *);
|
||||
// void focusInEvent(QFocusEvent *evt);
|
||||
// void focusOutEvent(QFocusEvent *evt);
|
||||
void keyPressEvent(QKeyEvent *event) { completionKeyPressEvent(event); }
|
||||
void focusInEvent(QFocusEvent *event) { completionFocusInEvent(event); }
|
||||
|
||||
public slots:
|
||||
void checkFilter();
|
||||
|
@ -60,6 +60,8 @@ private:
|
|||
QToolButton *apply_button_;
|
||||
CaptureFilterSyntaxWorker *syntax_worker_;
|
||||
|
||||
void buildCompletionList(const QString& primitive_word);
|
||||
|
||||
signals:
|
||||
void pushFilterSyntaxStatus(const QString&);
|
||||
void popFilterSyntaxStatus();
|
||||
|
|
|
@ -36,9 +36,7 @@
|
|||
#include <QComboBox>
|
||||
#include <QCompleter>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStringListModel>
|
||||
#include <QStyleOptionFrame>
|
||||
#include <QToolButton>
|
||||
|
@ -95,22 +93,19 @@ UIMiniCancelButton::UIMiniCancelButton(QWidget *pParent /* = 0 */)
|
|||
#define DEFAULT_MODIFIER "Ctrl-"
|
||||
#endif
|
||||
|
||||
const int max_completion_items_ = 20;
|
||||
|
||||
// proto.c:fld_abbrev_chars
|
||||
static const QString fld_abbrev_chars_ = "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
DisplayFilterEdit::DisplayFilterEdit(QWidget *parent, bool plain) :
|
||||
SyntaxLineEdit(parent),
|
||||
plain_(plain),
|
||||
apply_button_(NULL),
|
||||
completer_(NULL)
|
||||
apply_button_(NULL)
|
||||
{
|
||||
setAccessibleName(tr("Display filter entry"));
|
||||
|
||||
completion_model_ = new QStringListModel(this);
|
||||
QCompleter *completer_ = new QCompleter(completion_model_, this);
|
||||
setCompleter(completer_);
|
||||
setCompleter(new QCompleter(completion_model_, this));
|
||||
setCompletionTokenChars(fld_abbrev_chars_);
|
||||
|
||||
if (plain_) {
|
||||
placeholder_text_ = QString(tr("Enter a display filter %1")).arg(UTF8_HORIZONTAL_ELLIPSIS);
|
||||
|
@ -238,27 +233,6 @@ DisplayFilterEdit::DisplayFilterEdit(QWidget *parent, bool plain) :
|
|||
);
|
||||
}
|
||||
|
||||
// Override setCompleter so that we don't clobber the filter text on activate.
|
||||
void DisplayFilterEdit::setCompleter(QCompleter *c)
|
||||
{
|
||||
if (completer_)
|
||||
QObject::disconnect(completer_, 0, this, 0);
|
||||
|
||||
completer_ = c;
|
||||
|
||||
if (!completer_)
|
||||
return;
|
||||
|
||||
completer_->setWidget(this);
|
||||
completer_->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer_->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
// Completion items are not guaranteed to be sorted (recent filters +
|
||||
// fields), so no setModelSorting.
|
||||
completer_->setMaxVisibleItems(max_completion_items_);
|
||||
QObject::connect(completer_, SIGNAL(activated(QString)),
|
||||
this, SLOT(insertFieldCompletion(QString)));
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
|
||||
void DisplayFilterEdit::paintEvent(QPaintEvent *evt) {
|
||||
SyntaxLineEdit::paintEvent(evt);
|
||||
|
@ -308,64 +282,11 @@ void DisplayFilterEdit::resizeEvent(QResizeEvent *)
|
|||
bookmark_button_->setMaximumHeight(contentsRect().height());
|
||||
}
|
||||
|
||||
void DisplayFilterEdit::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
// Forward to the completer if needed...
|
||||
if (completer_ && completer_->popup()->isVisible()) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab:
|
||||
event->ignore();
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ...otherwise process the key ourselves.
|
||||
QLineEdit::keyPressEvent(event);
|
||||
|
||||
if (!completer_) return;
|
||||
|
||||
// Do nothing on bare shift.
|
||||
if ((event->modifiers() & Qt::ShiftModifier) && event->text().isEmpty()) return;
|
||||
|
||||
if (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QPoint field_coords(getFieldUnderCursor());
|
||||
|
||||
QString field_word = text().mid(field_coords.x(), field_coords.y());
|
||||
buildCompletionList(field_word);
|
||||
|
||||
if (completion_model_->stringList().length() < 1) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QRect cr = cursorRect();
|
||||
cr.setWidth(completer_->popup()->sizeHintForColumn(0)
|
||||
+ completer_->popup()->verticalScrollBar()->sizeHint().width());
|
||||
completer_->complete(cr);
|
||||
}
|
||||
|
||||
void DisplayFilterEdit::focusInEvent(QFocusEvent *evt)
|
||||
{
|
||||
if (completer_)
|
||||
completer_->setWidget(this);
|
||||
SyntaxLineEdit::focusInEvent(evt);
|
||||
}
|
||||
|
||||
void DisplayFilterEdit::focusOutEvent(QFocusEvent *evt)
|
||||
void DisplayFilterEdit::focusOutEvent(QFocusEvent *event)
|
||||
{
|
||||
if (syntaxState() == Valid)
|
||||
emit popFilterSyntaxStatus();
|
||||
SyntaxLineEdit::focusOutEvent(evt);
|
||||
SyntaxLineEdit::focusOutEvent(event);
|
||||
}
|
||||
|
||||
void DisplayFilterEdit::checkFilter(const QString& text)
|
||||
|
@ -404,22 +325,6 @@ void DisplayFilterEdit::checkFilter(const QString& text)
|
|||
}
|
||||
}
|
||||
|
||||
bool DisplayFilterEdit::isComplexFilter(const QString &dfilter)
|
||||
{
|
||||
bool is_complex = false;
|
||||
for (int i = 0; i < dfilter.length(); i++) {
|
||||
if (!fld_abbrev_chars_.contains(dfilter.at(i))) {
|
||||
is_complex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Don't complete the current filter.
|
||||
if (is_complex && dfilter.startsWith(text()) && dfilter.compare(text())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// GTK+ behavior:
|
||||
// - Operates on words (proto.c:fld_abbrev_chars).
|
||||
// - Popup appears when you enter or remove text.
|
||||
|
@ -563,44 +468,6 @@ void DisplayFilterEdit::changeEvent(QEvent* event)
|
|||
SyntaxLineEdit::changeEvent(event);
|
||||
}
|
||||
|
||||
void DisplayFilterEdit::insertFieldCompletion(const QString &completion_text)
|
||||
{
|
||||
QCompleter *completer_ = completer();
|
||||
if (!completer_) return;
|
||||
|
||||
QPoint field_coords(getFieldUnderCursor());
|
||||
|
||||
// Insert only if we have a matching field or if the entry is empty
|
||||
if (field_coords.y() < 1 && !text().isEmpty()) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QString new_text = text().replace(field_coords.x(), field_coords.y(), completion_text);
|
||||
setText(new_text);
|
||||
setCursorPosition(field_coords.x() + completion_text.length());
|
||||
}
|
||||
|
||||
QPoint DisplayFilterEdit::getFieldUnderCursor()
|
||||
{
|
||||
if (selectionStart() >= 0) return (QPoint(0,0));
|
||||
|
||||
int pos = cursorPosition();
|
||||
int start = pos;
|
||||
int len = 0;
|
||||
|
||||
while (start > 0 && fld_abbrev_chars_.contains(text().at(start -1))) {
|
||||
start--;
|
||||
len++;
|
||||
}
|
||||
while (pos < text().length() && fld_abbrev_chars_.contains(text().at(pos))) {
|
||||
pos++;
|
||||
len++;
|
||||
}
|
||||
|
||||
return QPoint(start, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Editor modelines
|
||||
*
|
||||
|
|
|
@ -24,9 +24,7 @@
|
|||
|
||||
#include "syntax_line_edit.h"
|
||||
|
||||
class QCompleter;
|
||||
class QEvent;
|
||||
class QStringListModel;
|
||||
class QToolButton;
|
||||
|
||||
class DisplayFilterEdit : public SyntaxLineEdit
|
||||
|
@ -35,17 +33,14 @@ class DisplayFilterEdit : public SyntaxLineEdit
|
|||
public:
|
||||
explicit DisplayFilterEdit(QWidget *parent = 0, bool plain = true);
|
||||
|
||||
void setCompleter(QCompleter *c);
|
||||
QCompleter *completer() const { return completer_; }
|
||||
|
||||
protected:
|
||||
#if QT_VERSION < QT_VERSION_CHECK(4, 7, 0)
|
||||
void paintEvent(QPaintEvent *evt);
|
||||
#endif
|
||||
void resizeEvent(QResizeEvent *);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void focusInEvent(QFocusEvent *evt);
|
||||
void focusOutEvent(QFocusEvent *evt);
|
||||
void keyPressEvent(QKeyEvent *event) { completionKeyPressEvent(event); }
|
||||
void focusInEvent(QFocusEvent *event) { completionFocusInEvent(event); }
|
||||
void focusOutEvent(QFocusEvent *event);
|
||||
|
||||
public slots:
|
||||
void applyDisplayFilter();
|
||||
|
@ -56,7 +51,6 @@ private slots:
|
|||
void bookmarkClicked();
|
||||
void clearFilter();
|
||||
void changeEvent(QEvent* event);
|
||||
void insertFieldCompletion(const QString & completion_text);
|
||||
|
||||
private:
|
||||
bool plain_;
|
||||
|
@ -64,13 +58,8 @@ private:
|
|||
QToolButton *bookmark_button_;
|
||||
QToolButton *clear_button_;
|
||||
QToolButton *apply_button_;
|
||||
QCompleter *completer_;
|
||||
QStringListModel *completion_model_;
|
||||
|
||||
bool isComplexFilter(const QString &dfilter);
|
||||
void buildCompletionList(const QString& field_word);
|
||||
// x = Start position, y = length
|
||||
QPoint getFieldUnderCursor();
|
||||
|
||||
signals:
|
||||
void pushFilterSyntaxStatus(const QString&);
|
||||
|
|
|
@ -31,12 +31,43 @@
|
|||
|
||||
#include "color_utils.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCompleter>
|
||||
#include <QKeyEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QStringListModel>
|
||||
|
||||
const int max_completion_items_ = 20;
|
||||
|
||||
SyntaxLineEdit::SyntaxLineEdit(QWidget *parent) :
|
||||
QLineEdit(parent)
|
||||
QLineEdit(parent),
|
||||
completer_(NULL),
|
||||
completion_model_(NULL)
|
||||
{
|
||||
setSyntaxState();
|
||||
}
|
||||
|
||||
// Override setCompleter so that we don't clobber the filter text on activate.
|
||||
void SyntaxLineEdit::setCompleter(QCompleter *c)
|
||||
{
|
||||
if (completer_)
|
||||
QObject::disconnect(completer_, 0, this, 0);
|
||||
|
||||
completer_ = c;
|
||||
|
||||
if (!completer_)
|
||||
return;
|
||||
|
||||
completer_->setWidget(this);
|
||||
completer_->setCompletionMode(QCompleter::PopupCompletion);
|
||||
completer_->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
// Completion items are not guaranteed to be sorted (recent filters +
|
||||
// fields), so no setModelSorting.
|
||||
completer_->setMaxVisibleItems(max_completion_items_);
|
||||
QObject::connect(completer_, SIGNAL(activated(QString)),
|
||||
this, SLOT(insertFieldCompletion(QString)));
|
||||
}
|
||||
|
||||
void SyntaxLineEdit::setSyntaxState(SyntaxState state) {
|
||||
syntax_state_ = state;
|
||||
state_style_sheet_ = QString(
|
||||
|
@ -144,3 +175,109 @@ void SyntaxLineEdit::checkInteger(QString number)
|
|||
setSyntaxState(SyntaxLineEdit::Invalid);
|
||||
}
|
||||
}
|
||||
|
||||
bool SyntaxLineEdit::isComplexFilter(const QString &filter)
|
||||
{
|
||||
bool is_complex = false;
|
||||
for (int i = 0; i < filter.length(); i++) {
|
||||
if (!token_chars_.contains(filter.at(i))) {
|
||||
is_complex = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Don't complete the current filter.
|
||||
if (is_complex && filter.startsWith(text()) && filter.compare(text())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SyntaxLineEdit::completionKeyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
// Forward to the completer if needed...
|
||||
if (completer_ && completer_->popup()->isVisible()) {
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Escape:
|
||||
case Qt::Key_Tab:
|
||||
case Qt::Key_Backtab:
|
||||
event->ignore();
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ...otherwise process the key ourselves.
|
||||
SyntaxLineEdit::keyPressEvent(event);
|
||||
|
||||
if (!completer_ || !completion_model_) return;
|
||||
|
||||
// Do nothing on bare shift.
|
||||
if ((event->modifiers() & Qt::ShiftModifier) && event->text().isEmpty()) return;
|
||||
|
||||
if (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QPoint token_coords(getTokenUnderCursor());
|
||||
|
||||
QString token_word = text().mid(token_coords.x(), token_coords.y());
|
||||
buildCompletionList(token_word);
|
||||
|
||||
if (completion_model_->stringList().length() < 1) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QRect cr = cursorRect();
|
||||
cr.setWidth(completer_->popup()->sizeHintForColumn(0)
|
||||
+ completer_->popup()->verticalScrollBar()->sizeHint().width());
|
||||
completer_->complete(cr);
|
||||
}
|
||||
|
||||
void SyntaxLineEdit::completionFocusInEvent(QFocusEvent *event)
|
||||
{
|
||||
if (completer_)
|
||||
completer_->setWidget(this);
|
||||
SyntaxLineEdit::focusInEvent(event);
|
||||
}
|
||||
|
||||
void SyntaxLineEdit::insertFieldCompletion(const QString &completion_text)
|
||||
{
|
||||
if (!completer_) return;
|
||||
|
||||
QPoint field_coords(getTokenUnderCursor());
|
||||
|
||||
// Insert only if we have a matching field or if the entry is empty
|
||||
if (field_coords.y() < 1 && !text().isEmpty()) {
|
||||
completer_->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
QString new_text = text().replace(field_coords.x(), field_coords.y(), completion_text);
|
||||
setText(new_text);
|
||||
setCursorPosition(field_coords.x() + completion_text.length());
|
||||
}
|
||||
|
||||
QPoint SyntaxLineEdit::getTokenUnderCursor()
|
||||
{
|
||||
if (selectionStart() >= 0) return (QPoint(0,0));
|
||||
|
||||
int pos = cursorPosition();
|
||||
int start = pos;
|
||||
int len = 0;
|
||||
|
||||
while (start > 0 && token_chars_.contains(text().at(start -1))) {
|
||||
start--;
|
||||
len++;
|
||||
}
|
||||
while (pos < text().length() && token_chars_.contains(text().at(pos))) {
|
||||
pos++;
|
||||
len++;
|
||||
}
|
||||
|
||||
return QPoint(start, len);
|
||||
}
|
||||
|
|
|
@ -24,6 +24,13 @@
|
|||
|
||||
#include <QLineEdit>
|
||||
|
||||
class QCompleter;
|
||||
class QStringListModel;
|
||||
|
||||
// Autocompletion is partially implemented. Subclasses must:
|
||||
// - Provide buildCompletionList
|
||||
// - Call setCompletionTokenChars
|
||||
|
||||
class SyntaxLineEdit : public QLineEdit
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -39,6 +46,9 @@ public:
|
|||
QString styleSheet() const;
|
||||
QString deprecatedToken();
|
||||
|
||||
void setCompleter(QCompleter *c);
|
||||
QCompleter *completer() const { return completer_; }
|
||||
|
||||
public slots:
|
||||
void setStyleSheet(const QString &style_sheet);
|
||||
|
||||
|
@ -47,12 +57,28 @@ public slots:
|
|||
void checkFieldName(QString field);
|
||||
void checkInteger(QString number);
|
||||
|
||||
protected:
|
||||
QCompleter *completer_;
|
||||
QStringListModel *completion_model_;
|
||||
void setCompletionTokenChars(const QString &token_chars) { token_chars_ = token_chars; }
|
||||
bool isComplexFilter(const QString &filter);
|
||||
virtual void buildCompletionList(const QString&) { }
|
||||
// x = Start position, y = length
|
||||
QPoint getTokenUnderCursor();
|
||||
|
||||
void completionKeyPressEvent(QKeyEvent *event);
|
||||
void completionFocusInEvent(QFocusEvent *event);
|
||||
|
||||
private:
|
||||
SyntaxState syntax_state_;
|
||||
QString style_sheet_;
|
||||
QString state_style_sheet_;
|
||||
QString deprecated_token_;
|
||||
QString syntax_error_message_;
|
||||
QString token_chars_;
|
||||
|
||||
private slots:
|
||||
void insertFieldCompletion(const QString &completion_text);
|
||||
|
||||
signals:
|
||||
|
||||
|
|
Loading…
Reference in New Issue