PageRenderTime 2273ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/ui/qt/widgets/syntax_line_edit.cpp

https://gitlab.com/jvelando/wireshark
C++ | 472 lines | 339 code | 76 blank | 57 comment | 64 complexity | 2882dfc518838c888966fc3a6631a110 MD5 | raw file
  1. /* syntax_line_edit.cpp
  2. *
  3. * Wireshark - Network traffic analyzer
  4. * By Gerald Combs <gerald@wireshark.org>
  5. * Copyright 1998 Gerald Combs
  6. *
  7. * SPDX-License-Identifier: GPL-2.0-or-later
  8. */
  9. #include "config.h"
  10. #include <glib.h>
  11. #include <epan/prefs.h>
  12. #include <epan/proto.h>
  13. #include <epan/dfilter/dfilter.h>
  14. #include <epan/column-info.h>
  15. #include <wsutil/utf8_entities.h>
  16. #include <ui/qt/widgets/syntax_line_edit.h>
  17. #include <ui/qt/utils/qt_ui_utils.h>
  18. #include <ui/qt/utils/color_utils.h>
  19. #include <ui/qt/utils/stock_icon.h>
  20. #include <QAbstractItemView>
  21. #include <QApplication>
  22. #include <QCompleter>
  23. #include <QKeyEvent>
  24. #include <QPainter>
  25. #include <QScrollBar>
  26. #include <QStringListModel>
  27. #include <QStyleOptionFrame>
  28. #include <limits>
  29. const int max_completion_items_ = 20;
  30. SyntaxLineEdit::SyntaxLineEdit(QWidget *parent) :
  31. QLineEdit(parent),
  32. completer_(NULL),
  33. completion_model_(NULL),
  34. completion_enabled_(false)
  35. {
  36. setSyntaxState();
  37. setMaxLength(std::numeric_limits<quint32>::max());
  38. }
  39. // Override setCompleter so that we don't clobber the filter text on activate.
  40. void SyntaxLineEdit::setCompleter(QCompleter *c)
  41. {
  42. if (completer_)
  43. QObject::disconnect(completer_, 0, this, 0);
  44. completer_ = c;
  45. if (!completer_)
  46. return;
  47. completer_->setWidget(this);
  48. completer_->setCompletionMode(QCompleter::PopupCompletion);
  49. completer_->setCaseSensitivity(Qt::CaseInsensitive);
  50. // Completion items are not guaranteed to be sorted (recent filters +
  51. // fields), so no setModelSorting.
  52. completer_->setMaxVisibleItems(max_completion_items_);
  53. QObject::connect(completer_, static_cast<void (QCompleter::*)(const QString &)>(&QCompleter::activated),
  54. this, &SyntaxLineEdit::insertFieldCompletion);
  55. // Auto-completion is turned on.
  56. completion_enabled_ = true;
  57. }
  58. void SyntaxLineEdit::allowCompletion(bool enabled)
  59. {
  60. completion_enabled_ = enabled;
  61. }
  62. void SyntaxLineEdit::setSyntaxState(SyntaxState state) {
  63. syntax_state_ = state;
  64. QColor valid_bg = ColorUtils::fromColorT(&prefs.gui_text_valid);
  65. QColor valid_fg = ColorUtils::contrastingTextColor(valid_bg);
  66. QColor invalid_bg = ColorUtils::fromColorT(&prefs.gui_text_invalid);
  67. QColor invalid_fg = ColorUtils::contrastingTextColor(invalid_bg);
  68. QColor deprecated_bg = ColorUtils::fromColorT(&prefs.gui_text_deprecated);
  69. QColor deprecated_fg = ColorUtils::contrastingTextColor(deprecated_bg);
  70. // Try to matche QLineEdit's placeholder text color (which sets the
  71. // alpha channel to 50%, which doesn't work in style sheets).
  72. // Setting the foreground color lets us avoid yet another background
  73. // color preference and should hopefully make things easier to
  74. // distinguish for color blind folk.
  75. QColor busy_fg = ColorUtils::alphaBlend(QApplication::palette().text(), QApplication::palette().base(), 0.5);
  76. state_style_sheet_ = QString(
  77. "SyntaxLineEdit[syntaxState=\"%1\"] {"
  78. " color: %2;"
  79. " background-color: %3;"
  80. "}"
  81. "SyntaxLineEdit[syntaxState=\"%4\"] {"
  82. " color: %5;"
  83. " background-color: %6;"
  84. "}"
  85. "SyntaxLineEdit[syntaxState=\"%7\"] {"
  86. " color: %8;"
  87. " background-color: %9;"
  88. "}"
  89. "SyntaxLineEdit[syntaxState=\"%10\"] {"
  90. " color: %11;"
  91. " background-color: %12;"
  92. "}"
  93. )
  94. // CSS selector, foreground, background
  95. .arg(Valid)
  96. .arg(valid_fg.name())
  97. .arg(valid_bg.name())
  98. .arg(Invalid)
  99. .arg(invalid_fg.name())
  100. .arg(invalid_bg.name())
  101. .arg(Deprecated)
  102. .arg(deprecated_fg.name())
  103. .arg(deprecated_bg.name())
  104. .arg(Busy)
  105. .arg(busy_fg.name())
  106. .arg(palette().base().color().name())
  107. ;
  108. setStyleSheet(style_sheet_);
  109. }
  110. QString SyntaxLineEdit::syntaxErrorMessage() {
  111. return syntax_error_message_;
  112. }
  113. QString SyntaxLineEdit::styleSheet() const {
  114. return style_sheet_;
  115. }
  116. void SyntaxLineEdit::setStyleSheet(const QString &style_sheet) {
  117. style_sheet_ = style_sheet;
  118. QLineEdit::setStyleSheet(style_sheet_ + state_style_sheet_);
  119. }
  120. void SyntaxLineEdit::insertFilter(const QString &filter)
  121. {
  122. QString padded_filter = filter;
  123. if (hasSelectedText()) {
  124. backspace();
  125. }
  126. int pos = cursorPosition();
  127. if (pos > 0 && !text().at(pos - 1).isSpace()) {
  128. padded_filter.prepend(" ");
  129. }
  130. if (pos < text().length() - 1 && !text().at(pos + 1).isSpace()) {
  131. padded_filter.append(" ");
  132. }
  133. insert(padded_filter);
  134. }
  135. bool SyntaxLineEdit::checkDisplayFilter(QString filter)
  136. {
  137. if (!completion_enabled_) {
  138. return false;
  139. }
  140. if (filter.isEmpty()) {
  141. setSyntaxState(SyntaxLineEdit::Empty);
  142. return true;
  143. }
  144. dfilter_t *dfp = NULL;
  145. gchar *err_msg;
  146. if (dfilter_compile(filter.toUtf8().constData(), &dfp, &err_msg)) {
  147. GPtrArray *depr = NULL;
  148. if (dfp) {
  149. depr = dfilter_deprecated_tokens(dfp);
  150. }
  151. if (depr) {
  152. // You keep using that word. I do not think it means what you think it means.
  153. // Possible alternatives: ::Troubled, or ::Problematic maybe?
  154. setSyntaxState(SyntaxLineEdit::Deprecated);
  155. /*
  156. * We're being lazy and only printing the first "problem" token.
  157. * Would it be better to print all of them?
  158. */
  159. QString token((const char *)g_ptr_array_index(depr, 0));
  160. gchar *token_str = qstring_strdup(token.section('.', 0, 0));
  161. header_field_info *hfi = proto_registrar_get_byalias(token_str);
  162. if (hfi)
  163. syntax_error_message_ = tr("\"%1\" is deprecated in favour of \"%2\". "
  164. "See Help section 6.4.8 for details.").arg(token_str).arg(hfi->abbrev);
  165. else
  166. // The token_str is the message.
  167. syntax_error_message_ = tr("%1").arg(token_str);
  168. g_free(token_str);
  169. } else {
  170. setSyntaxState(SyntaxLineEdit::Valid);
  171. }
  172. } else {
  173. setSyntaxState(SyntaxLineEdit::Invalid);
  174. syntax_error_message_ = QString::fromUtf8(err_msg);
  175. g_free(err_msg);
  176. }
  177. dfilter_free(dfp);
  178. return true;
  179. }
  180. void SyntaxLineEdit::checkFieldName(QString field)
  181. {
  182. if (field.isEmpty()) {
  183. setSyntaxState(SyntaxLineEdit::Empty);
  184. return;
  185. }
  186. char invalid_char = proto_check_field_name(field.toUtf8().constData());
  187. if (invalid_char) {
  188. setSyntaxState(SyntaxLineEdit::Invalid);
  189. } else {
  190. checkDisplayFilter(field);
  191. }
  192. }
  193. void SyntaxLineEdit::checkCustomColumn(QString fields)
  194. {
  195. if (fields.isEmpty()) {
  196. setSyntaxState(SyntaxLineEdit::Empty);
  197. return;
  198. }
  199. gchar **splitted_fields = g_regex_split_simple(COL_CUSTOM_PRIME_REGEX,
  200. fields.toUtf8().constData(), G_REGEX_ANCHORED, G_REGEX_MATCH_ANCHORED);
  201. for (guint i = 0; i < g_strv_length(splitted_fields); i++) {
  202. if (splitted_fields[i] && *splitted_fields[i]) {
  203. if (proto_check_field_name(splitted_fields[i]) != 0) {
  204. setSyntaxState(SyntaxLineEdit::Invalid);
  205. g_strfreev(splitted_fields);
  206. return;
  207. }
  208. }
  209. }
  210. g_strfreev(splitted_fields);
  211. checkDisplayFilter(fields);
  212. }
  213. void SyntaxLineEdit::checkInteger(QString number)
  214. {
  215. if (number.isEmpty()) {
  216. setSyntaxState(SyntaxLineEdit::Empty);
  217. return;
  218. }
  219. bool ok;
  220. text().toInt(&ok);
  221. if (ok) {
  222. setSyntaxState(SyntaxLineEdit::Valid);
  223. } else {
  224. setSyntaxState(SyntaxLineEdit::Invalid);
  225. }
  226. }
  227. bool SyntaxLineEdit::isComplexFilter(const QString &filter)
  228. {
  229. bool is_complex = false;
  230. for (int i = 0; i < filter.length(); i++) {
  231. if (!token_chars_.contains(filter.at(i))) {
  232. is_complex = true;
  233. break;
  234. }
  235. }
  236. // Don't complete the current filter.
  237. if (is_complex && filter.startsWith(text()) && filter.compare(text())) {
  238. return true;
  239. }
  240. return false;
  241. }
  242. bool SyntaxLineEdit::event(QEvent *event)
  243. {
  244. if (event->type() == QEvent::ShortcutOverride) {
  245. // You can't set time display formats while the display filter edit
  246. // has focus.
  247. // Keep shortcuts in the main window from stealing keyPressEvents
  248. // with Ctrl+Alt modifiers from us. This is a problem for many AltGr
  249. // combinations since they are delivered with Ctrl+Alt modifiers
  250. // instead of Qt::Key_AltGr and they tend to match the time display
  251. // format shortcuts.
  252. // Uncommenting the qDebug line below prints the following here:
  253. //
  254. // US Keyboard:
  255. // Ctrl+o: 79 QFlags<Qt::KeyboardModifiers>(ControlModifier) "\u000F"
  256. // Ctrl+Alt+2: 50 QFlags<Qt::KeyboardModifiers>(ControlModifier|AltModifier) "2"
  257. //
  258. // Swedish (Sweden) Keyboard:
  259. // Ctrl+o: 79 QFlags<Qt::KeyboardModifiers>(ControlModifier) "\u000F"
  260. // Ctrl+Alt+2: 64 QFlags<Qt::KeyboardModifiers>(ControlModifier|AltModifier) "@"
  261. // AltGr+{: 123 QFlags<Qt::KeyboardModifiers>(ControlModifier|AltModifier) "{"
  262. QKeyEvent* key_event = static_cast<QKeyEvent*>(event);
  263. // qDebug() << "=so" << key_event->key() << key_event->modifiers() << key_event->text();
  264. if (key_event->modifiers() == Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier)) {
  265. event->accept();
  266. return true;
  267. }
  268. }
  269. return QLineEdit::event(event);
  270. }
  271. void SyntaxLineEdit::completionKeyPressEvent(QKeyEvent *event)
  272. {
  273. // Forward to the completer if needed...
  274. if (completer_ && completer_->popup()->isVisible()) {
  275. switch (event->key()) {
  276. case Qt::Key_Enter:
  277. case Qt::Key_Return:
  278. break;
  279. case Qt::Key_Tab:
  280. focusNextChild();
  281. break;
  282. case Qt::Key_Escape:
  283. case Qt::Key_Backtab:
  284. event->ignore();
  285. return;
  286. default:
  287. break;
  288. }
  289. }
  290. // ...otherwise process the key ourselves.
  291. SyntaxLineEdit::keyPressEvent(event);
  292. if (!completion_enabled_ || !completer_ || !completion_model_ || !prefs.gui_autocomplete_filter) return;
  293. // Do nothing on bare shift.
  294. if ((event->modifiers() & Qt::ShiftModifier) && event->text().isEmpty()) return;
  295. if (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)) {
  296. completer_->popup()->hide();
  297. return;
  298. }
  299. QPoint token_coords(getTokenUnderCursor());
  300. QString token_word = text().mid(token_coords.x(), token_coords.y());
  301. buildCompletionList(token_word);
  302. if (completion_model_->stringList().length() < 1) {
  303. completer_->popup()->hide();
  304. return;
  305. }
  306. QRect cr = cursorRect();
  307. cr.setWidth(completer_->popup()->sizeHintForColumn(0)
  308. + completer_->popup()->verticalScrollBar()->sizeHint().width());
  309. completer_->complete(cr);
  310. }
  311. void SyntaxLineEdit::completionFocusInEvent(QFocusEvent *event)
  312. {
  313. if (completer_)
  314. completer_->setWidget(this);
  315. SyntaxLineEdit::focusInEvent(event);
  316. }
  317. void SyntaxLineEdit::focusOutEvent(QFocusEvent *event)
  318. {
  319. if (completer_ && completer_->popup()->isVisible() && event->reason() == Qt::PopupFocusReason) {
  320. // Pretend we still have focus so that we'll draw our cursor.
  321. // If cursorRect() were more precise we could just draw the cursor
  322. // during a paintEvent.
  323. return;
  324. }
  325. QLineEdit::focusOutEvent(event);
  326. }
  327. // Add indicator icons for syntax states in order to make things more clear for
  328. // color blind people.
  329. void SyntaxLineEdit::paintEvent(QPaintEvent *event)
  330. {
  331. QLineEdit::paintEvent(event);
  332. QString si_name;
  333. switch (syntax_state_) {
  334. case Invalid:
  335. si_name = "x-filter-invalid";
  336. break;
  337. case Deprecated:
  338. si_name = "x-filter-deprecated";
  339. break;
  340. default:
  341. return;
  342. }
  343. QStyleOptionFrame opt;
  344. initStyleOption(&opt);
  345. QRect cr = style()->subElementRect(QStyle::SE_LineEditContents, &opt, this);
  346. QRect sir = QRect(0, 0, 14, 14); // QIcon::paint scales, which is not what we want.
  347. int textWidth = fontMetrics().boundingRect(text()).width();
  348. // Qt always adds a margin of 6px between the border and text, see
  349. // QLineEditPrivate::effectiveLeftTextMargin and
  350. // QLineEditPrivate::sideWidgetParameters.
  351. int margin = 2 * 6 + 1;
  352. if (cr.width() - margin - textWidth < sir.width() || cr.height() < sir.height()) {
  353. // No space to draw
  354. return;
  355. }
  356. QIcon state_icon = StockIcon(si_name);
  357. if (state_icon.isNull()) {
  358. return;
  359. }
  360. int si_off = (cr.height() - sir.height()) / 2;
  361. sir.moveTop(si_off);
  362. sir.moveRight(cr.right() - si_off);
  363. QPainter painter(this);
  364. painter.setOpacity(0.25);
  365. state_icon.paint(&painter, sir);
  366. }
  367. void SyntaxLineEdit::insertFieldCompletion(const QString &completion_text)
  368. {
  369. if (!completer_) return;
  370. QPoint field_coords(getTokenUnderCursor());
  371. // Insert only if we have a matching field or if the entry is empty
  372. if (field_coords.y() < 1 && !text().isEmpty()) {
  373. completer_->popup()->hide();
  374. return;
  375. }
  376. QString new_text = text().replace(field_coords.x(), field_coords.y(), completion_text);
  377. setText(new_text);
  378. setCursorPosition(field_coords.x() + completion_text.length());
  379. emit textEdited(new_text);
  380. }
  381. QPoint SyntaxLineEdit::getTokenUnderCursor()
  382. {
  383. if (selectionStart() >= 0) return (QPoint(0,0));
  384. int pos = cursorPosition();
  385. int start = pos;
  386. int len = 0;
  387. while (start > 0 && token_chars_.contains(text().at(start -1))) {
  388. start--;
  389. len++;
  390. }
  391. while (pos < text().length() && token_chars_.contains(text().at(pos))) {
  392. pos++;
  393. len++;
  394. }
  395. return QPoint(start, len);
  396. }