1 /*
2 * Copyright (C) Pedram Pourang (aka Tsu Jan) 2014-2019 <tsujan2000@gmail.com>
3 *
4 * FeatherPad is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * FeatherPad is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 * See the GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * @license GPL-3.0+ <https://spdx.org/licenses/GPL-3.0+.html>
18 */
19
20 #include "fpwin.h"
21 #include "ui_fp.h"
22 #include <QTextDocumentFragment>
23
24 namespace FeatherPad {
25
26 /* This order is preserved everywhere for selections:
27 current line -> replacement -> found matches -> selection highlights -> bracket matches */
28
find(bool forward)29 void FPwin::find (bool forward)
30 {
31 if (!isReady()) return;
32
33 TabPage *tabPage = qobject_cast< TabPage *>(ui->tabWidget->currentWidget());
34 if (tabPage == nullptr) return;
35
36 TextEdit *textEdit = tabPage->textEdit();
37 QString txt = tabPage->searchEntry();
38 bool newSrch = false;
39 if (textEdit->getSearchedText() != txt)
40 {
41 textEdit->setSearchedText (txt);
42 newSrch = true;
43 }
44
45 disconnect (textEdit, &TextEdit::resized, this, &FPwin::hlight);
46 disconnect (textEdit, &TextEdit::updateRect, this, &FPwin::hlight);
47 disconnect (textEdit, &QPlainTextEdit::textChanged, this, &FPwin::hlight);
48
49 if (txt.isEmpty())
50 {
51 /* remove all yellow and green highlights */
52 QList<QTextEdit::ExtraSelection> es;
53 textEdit->setGreenSel (es); // not needed
54 if (ui->actionLineNumbers->isChecked() || ui->spinBox->isVisible())
55 es.prepend (textEdit->currentLineSelection());
56 es.append (textEdit->getBlueSel());
57 es.append (textEdit->getRedSel());
58 textEdit->setExtraSelections (es);
59 return;
60 }
61
62 QTextDocument::FindFlags searchFlags = getSearchFlags();
63 QTextDocument::FindFlags newFlags = searchFlags;
64 if (!forward)
65 newFlags = searchFlags | QTextDocument::FindBackward;
66 QTextCursor start = textEdit->textCursor();
67 QTextCursor found = textEdit->finding (txt, start, newFlags, tabPage->matchRegex());
68
69 if (found.isNull())
70 {
71 if (!forward)
72 start.movePosition (QTextCursor::End, QTextCursor::MoveAnchor);
73 else
74 start.movePosition (QTextCursor::Start, QTextCursor::MoveAnchor);
75 found = textEdit->finding (txt, start, newFlags, tabPage->matchRegex());
76 }
77
78 if (!found.isNull())
79 {
80 start.setPosition (found.anchor());
81 /* this is needed for selectionChanged() to be emitted */
82 if (newSrch) textEdit->setTextCursor (start);
83 start.setPosition (found.position(), QTextCursor::KeepAnchor);
84 textEdit->skipSelectionHighlighting();
85 textEdit->setTextCursor (start);
86 }
87 /* matches highlights should come here, after the text area is
88 scrolled and even when no match is found (it may be added later) */
89 hlight();
90 connect (textEdit, &QPlainTextEdit::textChanged, this, &FPwin::hlight);
91 connect (textEdit, &TextEdit::updateRect, this, &FPwin::hlight);
92 connect (textEdit, &TextEdit::resized, this, &FPwin::hlight);
93 }
94 /*************************/
95 // Highlight found matches in the visible part of the text.
hlight() const96 void FPwin::hlight() const
97 {
98 /* When FeatherPad's window is being closed, it's possible that, in a moment,
99 the current index is positive but the current widget is null. So, the latter
100 should be checked, not the former. */
101 TabPage *tabPage = qobject_cast< TabPage *>(ui->tabWidget->currentWidget());
102 if (tabPage == nullptr) return;
103
104 TextEdit *textEdit = tabPage->textEdit();
105
106 const QString txt = textEdit->getSearchedText();
107 if (txt.isEmpty()) return;
108
109 QTextDocument::FindFlags searchFlags = getSearchFlags();
110
111 /* prepend green highlights */
112 QList<QTextEdit::ExtraSelection> es = textEdit->getGreenSel();
113 QColor color = QColor (textEdit->hasDarkScheme() ? QColor (255, 255, 0,
114 /* a quadratic equation for darkValue -> opacity: 0 -> 90, 27 -> 75, 50 -> 65 */
115 static_cast<int>(static_cast<qreal>(textEdit->getDarkValue() * (textEdit->getDarkValue() - 257)) / static_cast<qreal>(414)) + 90)
116 : Qt::yellow);
117 QTextCursor found;
118 /* first put a start cursor at the top left edge... */
119 QPoint Point (0, 0);
120 QTextCursor start = textEdit->cursorForPosition (Point);
121 /* ... then move it backward by the search text length */
122 int startPos = start.position() - (!tabPage->matchRegex() ? txt.length() : 0);
123 if (startPos >= 0)
124 start.setPosition (startPos);
125 else
126 start.setPosition (0);
127 /* get the visible text to check if the search string is inside it */
128 Point = QPoint (textEdit->geometry().width(), textEdit->geometry().height());
129 QTextCursor end = textEdit->cursorForPosition (Point);
130 int endLimit = end.anchor();
131 int endPos = end.position() + (!tabPage->matchRegex() ? txt.length() : 0);
132 end.movePosition (QTextCursor::End);
133 if (endPos <= end.position())
134 end.setPosition (endPos);
135 QTextCursor visCur = start;
136 visCur.setPosition (end.position(), QTextCursor::KeepAnchor);
137 const QString str = visCur.selection().toPlainText(); // '\n' is included in this way
138 Qt::CaseSensitivity cs = tabPage->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive;
139 if (tabPage->matchRegex() || str.contains (txt, cs)) // don't waste time if the searched text isn't visible
140 {
141 while (!(found = textEdit->finding (txt, start, searchFlags, tabPage->matchRegex(), endLimit)).isNull())
142 {
143 QTextEdit::ExtraSelection extra;
144 extra.format.setBackground (color);
145 extra.cursor = found;
146 es.append (extra);
147 start.setPosition (found.position());
148 }
149 }
150
151 /* also prepend the current line highlight,
152 so that it always comes first when it exists */
153 if (ui->actionLineNumbers->isChecked() || ui->spinBox->isVisible())
154 es.prepend (textEdit->currentLineSelection());
155 /* append blue and red highlights */
156 es.append (textEdit->getBlueSel());
157 es.append (textEdit->getRedSel());
158 textEdit->setExtraSelections (es);
159 }
160 /*************************/
searchFlagChanged()161 void FPwin::searchFlagChanged()
162 {
163 if (!isReady()) return;
164
165 TabPage *tabPage = qobject_cast< TabPage *>(ui->tabWidget->currentWidget());
166 if (tabPage == nullptr) return;
167
168 /* deselect text for consistency */
169 TextEdit *textEdit = tabPage->textEdit();
170 QTextCursor start = textEdit->textCursor();
171 if (start.hasSelection())
172 {
173 start.setPosition (start.anchor());
174 textEdit->setTextCursor (start);
175 }
176
177 hlight();
178 }
179 /*************************/
getSearchFlags() const180 QTextDocument::FindFlags FPwin::getSearchFlags() const
181 {
182 TabPage *tabPage = qobject_cast< TabPage *>(ui->tabWidget->currentWidget());
183 QTextDocument::FindFlags searchFlags = QTextDocument::FindFlags();
184 if (tabPage != nullptr)
185 {
186 if (tabPage->matchWhole())
187 searchFlags = QTextDocument::FindWholeWords;
188 if (tabPage->matchCase())
189 searchFlags |= QTextDocument::FindCaseSensitively;
190 }
191 return searchFlags;
192 }
193
194 }
195