1 | /* |
2 | * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) |
3 | * |
4 | * This program is free software: you can redistribute it and/or modify |
5 | * it under the terms of the GNU General Public License as published by |
6 | * the Free Software Foundation, either version 3 of the License, or |
7 | * (at your option) any later version. |
8 | * |
9 | * This program is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | * GNU General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU General Public License |
15 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
16 | */ |
17 | #include "headercompletionpopup.h" |
18 | |
19 | #include <QCoreApplication> |
20 | #include <QFileInfo> |
21 | #include <QVBoxLayout> |
22 | #include <QDebug> |
23 | |
24 | HeaderCompletionPopup::(QWidget* parent):QWidget(parent) |
25 | { |
26 | setWindowFlags(Qt::Popup); |
27 | mListView = new CodeCompletionListView(this); |
28 | mModel=new HeaderCompletionListModel(&mCompletionList); |
29 | mListView->setModel(mModel); |
30 | setLayout(new QVBoxLayout()); |
31 | layout()->addWidget(mListView); |
32 | layout()->setMargin(0); |
33 | |
34 | mSearchLocal = false; |
35 | mCurrentFile = "" ; |
36 | mPhrase = "" ; |
37 | mIgnoreCase = false; |
38 | } |
39 | |
40 | HeaderCompletionPopup::() |
41 | { |
42 | delete mModel; |
43 | } |
44 | |
45 | void HeaderCompletionPopup::(const QString &phrase, const QString &fileName) |
46 | { |
47 | QCursor oldCursor = cursor(); |
48 | setCursor(Qt::WaitCursor); |
49 | mCurrentFile = fileName; |
50 | mPhrase = phrase; |
51 | getCompletionFor(phrase); |
52 | setCursor(oldCursor); |
53 | } |
54 | |
55 | bool HeaderCompletionPopup::(const QString &phrase, bool autoHideOnSingleResult) |
56 | { |
57 | mPhrase = phrase; |
58 | if (mPhrase.isEmpty()) { |
59 | hide(); |
60 | return false; |
61 | } |
62 | if(!isEnabled()) |
63 | return false; |
64 | |
65 | QCursor oldCursor = cursor(); |
66 | setCursor(Qt::WaitCursor); |
67 | |
68 | int i = mPhrase.lastIndexOf('\\'); |
69 | if (i<0) { |
70 | i = mPhrase.lastIndexOf('/'); |
71 | } |
72 | QString symbol = mPhrase; |
73 | if (i>=0) { |
74 | symbol = mPhrase.mid(i+1); |
75 | } |
76 | |
77 | // filter fFullCompletionList to fCompletionList |
78 | filterList(symbol); |
79 | mModel->notifyUpdated(); |
80 | setCursor(oldCursor); |
81 | |
82 | if (!mCompletionList.isEmpty()) { |
83 | mListView->setCurrentIndex(mModel->index(0,0)); |
84 | if (mCompletionList.count() == 1) { |
85 | // if only one suggestion and auto hide , don't show the frame |
86 | if (autoHideOnSingleResult) |
87 | return true; |
88 | // if only one suggestion, and is exactly the symbol to search, hide the frame (the search is over) |
89 | if (symbol == mCompletionList[0]->filename) |
90 | return true; |
91 | } |
92 | } else { |
93 | hide(); |
94 | } |
95 | return false; |
96 | } |
97 | |
98 | void HeaderCompletionPopup::(const KeyPressedCallback &newKeypressedCallback) |
99 | { |
100 | mListView->setKeypressedCallback(newKeypressedCallback); |
101 | } |
102 | |
103 | void HeaderCompletionPopup::(const QColor& localColor, |
104 | const QColor& projectColor, |
105 | const QColor& systemColor, |
106 | const QColor& folderColor) |
107 | { |
108 | mModel->setLocalColor(localColor); |
109 | mModel->setProjectColor(projectColor); |
110 | mModel->setSystemColor(systemColor); |
111 | mModel->setFolderColor(folderColor); |
112 | } |
113 | |
114 | QString HeaderCompletionPopup::(bool updateUsageCount) |
115 | { |
116 | if (!isEnabled()) |
117 | return "" ; |
118 | int index = mListView->currentIndex().row(); |
119 | PHeaderCompletionListItem item; |
120 | if (index>=0 && index<mCompletionList.count()) |
121 | item=mCompletionList[index]; |
122 | else if (mCompletionList.count()>0) { |
123 | item=mCompletionList.front(); |
124 | } |
125 | if (item) { |
126 | if (updateUsageCount) { |
127 | item->usageCount++; |
128 | mHeaderUsageCounts.insert(item->fullpath,item->usageCount); |
129 | } |
130 | if (item->isFolder) |
131 | return item->filename+"/" ; |
132 | return item->filename; |
133 | } |
134 | return "" ; |
135 | } |
136 | |
137 | static bool (const PHeaderCompletionListItem& item1,const PHeaderCompletionListItem& item2){ |
138 | if (item1->usageCount != item2->usageCount) |
139 | return item1->usageCount > item2->usageCount; |
140 | |
141 | if (item1->itemType != item2->itemType) |
142 | return item1->itemType<item2->itemType; |
143 | |
144 | return item1->filename < item2->filename; |
145 | } |
146 | |
147 | |
148 | void HeaderCompletionPopup::(const QString &member) |
149 | { |
150 | Qt::CaseSensitivity caseSensitivity=mIgnoreCase?Qt::CaseInsensitive:Qt::CaseSensitive; |
151 | mCompletionList.clear(); |
152 | if (member.isEmpty()) { |
153 | foreach (const PHeaderCompletionListItem& item,mFullCompletionList.values()) { |
154 | mCompletionList.append(item); |
155 | } |
156 | } else { |
157 | foreach (const PHeaderCompletionListItem& item,mFullCompletionList.values()) { |
158 | if (item->filename.startsWith(member, caseSensitivity)) { |
159 | mCompletionList.append(item); |
160 | } |
161 | } |
162 | } |
163 | std::sort(mCompletionList.begin(),mCompletionList.end(), sortByUsage); |
164 | } |
165 | |
166 | |
167 | void HeaderCompletionPopup::(const QString &phrase) |
168 | { |
169 | int idx = phrase.lastIndexOf('\\'); |
170 | if (idx<0) { |
171 | idx = phrase.lastIndexOf('/'); |
172 | } |
173 | mFullCompletionList.clear(); |
174 | if (idx < 0) { // dont have basedir |
175 | if (mSearchLocal) { |
176 | QFileInfo fileInfo(mCurrentFile); |
177 | addFilesInPath(fileInfo.absolutePath(), HeaderCompletionListItemType::LocalHeader); |
178 | }; |
179 | |
180 | for (const QString& path: mParser->includePaths()) { |
181 | addFilesInPath(path, HeaderCompletionListItemType::ProjectHeader); |
182 | } |
183 | |
184 | for (const QString& path: mParser->projectIncludePaths()) { |
185 | addFilesInPath(path, HeaderCompletionListItemType::SystemHeader); |
186 | } |
187 | } else { |
188 | QString current = phrase.mid(0,idx); |
189 | if (mSearchLocal) { |
190 | QFileInfo fileInfo(mCurrentFile); |
191 | addFilesInSubDir(fileInfo.absolutePath(),current, HeaderCompletionListItemType::LocalHeader); |
192 | } |
193 | for (const QString& path: mParser->includePaths()) { |
194 | addFilesInSubDir(path,current, HeaderCompletionListItemType::ProjectHeader); |
195 | } |
196 | |
197 | for (const QString& path: mParser->projectIncludePaths()) { |
198 | addFilesInSubDir(path,current, HeaderCompletionListItemType::SystemHeader); |
199 | } |
200 | } |
201 | } |
202 | |
203 | void HeaderCompletionPopup::(const QString &path, HeaderCompletionListItemType type) |
204 | { |
205 | QDir dir(path); |
206 | if (!dir.exists()) |
207 | return; |
208 | foreach (const QFileInfo& fileInfo, dir.entryInfoList()) { |
209 | if (fileInfo.fileName().startsWith("." )) |
210 | continue; |
211 | if (fileInfo.isDir()) { |
212 | addFile(dir, fileInfo, type); |
213 | continue; |
214 | } |
215 | QString suffix = fileInfo.suffix().toLower(); |
216 | if (suffix == "h" || suffix == "hpp" || suffix == "" ) { |
217 | addFile(dir, fileInfo, type); |
218 | } |
219 | } |
220 | } |
221 | |
222 | void HeaderCompletionPopup::(const QDir& dir, const QFileInfo& fileInfo, HeaderCompletionListItemType type) |
223 | { |
224 | QString fileName = fileInfo.fileName(); |
225 | if (fileName.isEmpty()) |
226 | return; |
227 | if (fileName.startsWith('.')) |
228 | return; |
229 | PHeaderCompletionListItem item = std::make_shared<HeaderCompletionListItem>(); |
230 | item->filename = fileName; |
231 | item->itemType = type; |
232 | item->fullpath = dir.absoluteFilePath(fileName); |
233 | item->usageCount = mHeaderUsageCounts.value(item->fullpath,0); |
234 | item->isFolder = fileInfo.isDir(); |
235 | mFullCompletionList.insert(fileName,item); |
236 | } |
237 | |
238 | void HeaderCompletionPopup::(const QString &baseDirPath, const QString &subDirName, HeaderCompletionListItemType type) |
239 | { |
240 | QDir baseDir(baseDirPath); |
241 | QString subDirPath = baseDir.filePath(subDirName); |
242 | addFilesInPath(subDirPath, type); |
243 | } |
244 | |
245 | bool HeaderCompletionPopup::() const |
246 | { |
247 | return mSearchLocal; |
248 | } |
249 | |
250 | void HeaderCompletionPopup::(bool newSearchLocal) |
251 | { |
252 | mSearchLocal = newSearchLocal; |
253 | } |
254 | |
255 | bool HeaderCompletionPopup::() const |
256 | { |
257 | return mIgnoreCase; |
258 | } |
259 | |
260 | void HeaderCompletionPopup::(bool newIgnoreCase) |
261 | { |
262 | mIgnoreCase = newIgnoreCase; |
263 | } |
264 | |
265 | const QString &HeaderCompletionPopup::() const |
266 | { |
267 | return mPhrase; |
268 | } |
269 | |
270 | void HeaderCompletionPopup::(const PCppParser &newParser) |
271 | { |
272 | mParser = newParser; |
273 | } |
274 | |
275 | void HeaderCompletionPopup::(QShowEvent *) |
276 | { |
277 | mListView->setFocus(); |
278 | } |
279 | |
280 | void HeaderCompletionPopup::(QHideEvent *) |
281 | { |
282 | mCompletionList.clear(); |
283 | mFullCompletionList.clear(); |
284 | mParser = nullptr; |
285 | } |
286 | |
287 | bool HeaderCompletionPopup::(QEvent *event) |
288 | { |
289 | bool result = QWidget::event(event); |
290 | switch (event->type()) { |
291 | case QEvent::FontChange: |
292 | mListView->setFont(font()); |
293 | break; |
294 | default: |
295 | break; |
296 | } |
297 | return result; |
298 | } |
299 | |
300 | HeaderCompletionListModel::(const QList<PHeaderCompletionListItem> *files, QObject *parent): |
301 | QAbstractListModel(parent), |
302 | mFiles(files) |
303 | { |
304 | |
305 | } |
306 | |
307 | int HeaderCompletionListModel::(const QModelIndex &) const |
308 | { |
309 | return mFiles->count(); |
310 | } |
311 | |
312 | QVariant HeaderCompletionListModel::(const QModelIndex &index, int role) const |
313 | { |
314 | if (!index.isValid()) |
315 | return QVariant(); |
316 | if (index.row()>=mFiles->count()) |
317 | return QVariant(); |
318 | |
319 | switch(role) { |
320 | case Qt::DisplayRole: { |
321 | return mFiles->at(index.row())->filename; |
322 | } |
323 | case Qt::ForegroundRole: { |
324 | PHeaderCompletionListItem item=mFiles->at(index.row()); |
325 | if (item->isFolder) |
326 | return mFolderColor; |
327 | switch(item->itemType) { |
328 | case HeaderCompletionListItemType::LocalHeader: |
329 | return mLocalColor; |
330 | case HeaderCompletionListItemType::ProjectHeader: |
331 | return mProjectColor; |
332 | case HeaderCompletionListItemType::SystemHeader: |
333 | return mSystemColor; |
334 | } |
335 | } |
336 | break; |
337 | } |
338 | return QVariant(); |
339 | } |
340 | |
341 | void HeaderCompletionListModel::() |
342 | { |
343 | beginResetModel(); |
344 | endResetModel(); |
345 | } |
346 | |
347 | void HeaderCompletionListModel::(const QColor &newColor) |
348 | { |
349 | mSystemColor = newColor; |
350 | } |
351 | |
352 | void HeaderCompletionListModel::(const QColor &newColor) |
353 | { |
354 | mProjectColor = newColor; |
355 | } |
356 | |
357 | void HeaderCompletionListModel::(const QColor &newFolderColor) |
358 | { |
359 | mFolderColor = newFolderColor; |
360 | } |
361 | |
362 | void HeaderCompletionListModel::(const QColor &newColor) |
363 | { |
364 | mLocalColor = newColor; |
365 | } |
366 | |