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 "qt_utils/utils.h" |
18 | #include <QApplication> |
19 | #include <QByteArray> |
20 | #include <QDir> |
21 | #include <QFile> |
22 | #include <QFileInfo> |
23 | #include <QProcess> |
24 | #include <QProcessEnvironment> |
25 | #include <QString> |
26 | #include <QTextCodec> |
27 | #include <QtGlobal> |
28 | #include <QDebug> |
29 | #include <QStyleFactory> |
30 | #include <QDateTime> |
31 | #include <QColor> |
32 | #include <QWindow> |
33 | #include <QScreen> |
34 | #include <QDirIterator> |
35 | #ifdef Q_OS_WIN |
36 | #include <QDirIterator> |
37 | #include <QMimeDatabase> |
38 | #include <windows.h> |
39 | #endif |
40 | |
41 | BaseError::BaseError(const QString &reason): |
42 | mReason(reason) |
43 | { |
44 | |
45 | } |
46 | |
47 | QString BaseError::reason() const |
48 | { |
49 | return mReason; |
50 | } |
51 | |
52 | IndexOutOfRange::IndexOutOfRange(int Index): |
53 | BaseError(QObject::tr("Index %1 out of range" ).arg(Index)) |
54 | { |
55 | |
56 | } |
57 | |
58 | FileError::FileError(const QString &reason): BaseError(reason) |
59 | { |
60 | |
61 | } |
62 | |
63 | const QByteArray guessTextEncoding(const QByteArray& text){ |
64 | bool allAscii; |
65 | int ii; |
66 | int size; |
67 | const QByteArray& s=text; |
68 | size = s.length(); |
69 | if ( (size >= 3) && ((unsigned char)s[0]==0xEF) && ((unsigned char)s[1]==0xBB) && ((unsigned char)s[2]==0xBF)) { |
70 | return ENCODING_UTF8_BOM; |
71 | } |
72 | allAscii = true; |
73 | ii = 0; |
74 | while (ii < size) { |
75 | unsigned char ch = s[ii]; |
76 | if (ch < 0x80 ) { |
77 | ii++; // is an ascii char |
78 | } else if (ch < 0xC0) { // value between 0x80 and 0xC0 is an invalid UTF-8 char |
79 | return ENCODING_SYSTEM_DEFAULT; |
80 | } else if (ch < 0xE0) { // should be an 2-byte UTF-8 char |
81 | if (ii>=size-1) { |
82 | return ENCODING_SYSTEM_DEFAULT; |
83 | } |
84 | unsigned char ch2=s[ii+1]; |
85 | if ((ch2 & 0xC0) !=0x80) { |
86 | return ENCODING_SYSTEM_DEFAULT; |
87 | } |
88 | allAscii = false; |
89 | ii+=2; |
90 | } else if (ch < 0xF0) { // should be an 3-byte UTF-8 char |
91 | if (ii>=size-2) { |
92 | return ENCODING_SYSTEM_DEFAULT; |
93 | } |
94 | unsigned char ch2=s[ii+1]; |
95 | unsigned char ch3=s[ii+2]; |
96 | if (((ch2 & 0xC0)!=0x80) || ((ch3 & 0xC0)!=0x80)) { |
97 | return ENCODING_SYSTEM_DEFAULT; |
98 | } |
99 | allAscii = false; |
100 | ii+=3; |
101 | } else { // invalid UTF-8 char |
102 | return ENCODING_SYSTEM_DEFAULT; |
103 | } |
104 | } |
105 | if (allAscii) |
106 | return ENCODING_ASCII; |
107 | return ENCODING_UTF8; |
108 | } |
109 | |
110 | |
111 | |
112 | bool isTextAllAscii(const QByteArray& text) { |
113 | for (char c:text) { |
114 | if (c<0) { |
115 | return false; |
116 | } |
117 | } |
118 | return true; |
119 | } |
120 | |
121 | bool isTextAllAscii(const QString& text) { |
122 | for (QChar c:text) { |
123 | if (c.unicode()>127) { |
124 | return false; |
125 | } |
126 | } |
127 | return true; |
128 | } |
129 | |
130 | bool isNonPrintableAsciiChar(char ch) |
131 | { |
132 | return (ch<=32) && (ch>=0); |
133 | } |
134 | |
135 | QStringList textToLines(const QString &text) |
136 | { |
137 | QTextStream stream(&((QString&)text),QIODevice::ReadOnly); |
138 | return readStreamToLines(&stream); |
139 | } |
140 | |
141 | |
142 | void textToLines(const QString &text, LineProcessFunc lineFunc) |
143 | { |
144 | QTextStream stream(&((QString&)text),QIODevice::ReadOnly); |
145 | readStreamToLines(&stream,lineFunc); |
146 | } |
147 | |
148 | QString linesToText(const QStringList &lines, const QString& lineBreak) |
149 | { |
150 | return lines.join(lineBreak); |
151 | } |
152 | |
153 | QList<QByteArray> splitByteArrayToLines(const QByteArray &content) |
154 | { |
155 | QList<QByteArray> lines; |
156 | const char* p =content.constData(); |
157 | const char* end = p+content.length(); |
158 | const char* lineStart = p; |
159 | QByteArray line; |
160 | while (p<=end) { |
161 | char ch=*p; |
162 | switch(ch) { |
163 | case '\r': |
164 | line = QByteArray(lineStart, p-lineStart); |
165 | lines.append(line); |
166 | p++; |
167 | if (*p=='\n') |
168 | p++; |
169 | lineStart = p; |
170 | break; |
171 | case '\n': |
172 | line = QByteArray(lineStart, p-lineStart); |
173 | lines.append(line); |
174 | p++; |
175 | lineStart = p; |
176 | break; |
177 | default: |
178 | p++; |
179 | } |
180 | } |
181 | if (lineStart>end) { |
182 | lines.append("" ); |
183 | } else { |
184 | line = QByteArray(lineStart, end-lineStart+1); |
185 | lines.append(line); |
186 | } |
187 | return lines; |
188 | } |
189 | |
190 | QString trimRight(const QString &s) |
191 | { |
192 | if (s.isEmpty()) |
193 | return s; |
194 | int i = s.length()-1; |
195 | // while ((i>=0) && ((s[i] == '\r') || (s[i]=='\n') || (s[i] == '\t') || (s[i]==' '))) { |
196 | while ((i>=0) && (s[i]<=32)) { |
197 | i--; |
198 | }; |
199 | if (i>=0) { |
200 | return s.left(i+1); |
201 | } else { |
202 | return QString(); |
203 | } |
204 | } |
205 | |
206 | QString trimLeft(const QString &s) |
207 | { |
208 | if (s.isEmpty()) |
209 | return s; |
210 | int i=0; |
211 | // while ((i<s.length()) && ((s[i] == '\r') || (s[i]=='\n') || (s[i] == '\t') || (s[i]==' '))) { |
212 | // i++; |
213 | // }; |
214 | while ((i<s.length()) && (s[i]<=32)) { |
215 | i++; |
216 | }; |
217 | if (i<s.length()) { |
218 | return s.mid(i); |
219 | } else { |
220 | return QString(); |
221 | } |
222 | } |
223 | |
224 | int countLeadingWhitespaceChars(const QString &line) |
225 | { |
226 | int n=0; |
227 | while (n<line.length()) { |
228 | if (line[n].unicode()>32) |
229 | break; |
230 | n++; |
231 | } |
232 | return n; |
233 | } |
234 | |
235 | bool stringIsBlank(const QString &s) |
236 | { |
237 | for (QChar ch:s) { |
238 | if (ch != ' ' && ch != '\t') |
239 | return false; |
240 | } |
241 | return true; |
242 | } |
243 | |
244 | QByteArray toByteArray(const QString &s) |
245 | { |
246 | //return s.toLocal8Bit(); |
247 | return s.toUtf8(); |
248 | } |
249 | |
250 | QString fromByteArray(const QByteArray &s) |
251 | { |
252 | QTextCodec* codec = QTextCodec::codecForName(ENCODING_UTF8); |
253 | QTextCodec::ConverterState state; |
254 | if (!codec) |
255 | return QString(s); |
256 | QString tmp = codec->toUnicode(s,s.length(),&state); |
257 | if (state.invalidChars>0) |
258 | tmp = QString::fromLocal8Bit(s); |
259 | return tmp; |
260 | } |
261 | |
262 | QStringList readStreamToLines(QTextStream *stream) |
263 | { |
264 | QStringList list; |
265 | QString s; |
266 | while (stream->readLineInto(&s)) { |
267 | list.append(s); |
268 | } |
269 | return list; |
270 | } |
271 | |
272 | void readStreamToLines(QTextStream *stream, |
273 | LineProcessFunc lineFunc) |
274 | { |
275 | QString s; |
276 | while (stream->readLineInto(&s)) { |
277 | lineFunc(s); |
278 | } |
279 | } |
280 | |
281 | QStringList readFileToLines(const QString& fileName, QTextCodec* codec) |
282 | { |
283 | QFile file(fileName); |
284 | if (file.open(QFile::ReadOnly)) { |
285 | QTextStream stream(&file); |
286 | stream.setCodec(codec); |
287 | stream.setAutoDetectUnicode(false); |
288 | return readStreamToLines(&stream); |
289 | } |
290 | return QStringList(); |
291 | } |
292 | |
293 | void readFileToLines(const QString &fileName, QTextCodec *codec, LineProcessFunc lineFunc) |
294 | { |
295 | QFile file(fileName); |
296 | if (file.open(QFile::ReadOnly)) { |
297 | QTextStream stream(&file); |
298 | stream.setCodec(codec); |
299 | stream.setAutoDetectUnicode(false); |
300 | readStreamToLines(&stream, lineFunc); |
301 | } |
302 | } |
303 | |
304 | QStringList readFileToLines(const QString &fileName) |
305 | { |
306 | QFile file(fileName); |
307 | if (file.size()<=0) |
308 | return QStringList(); |
309 | QTextCodec* codec = QTextCodec::codecForLocale(); |
310 | QStringList result; |
311 | QTextCodec::ConverterState state; |
312 | bool ok = true; |
313 | if (file.open(QFile::ReadOnly)) { |
314 | while (!file.atEnd()) { |
315 | QByteArray array = file.readLine(); |
316 | QString s = codec->toUnicode(array,array.length(),&state); |
317 | if (state.invalidChars>0) { |
318 | ok=false; |
319 | break; |
320 | } |
321 | if (s.endsWith("\r\n" )) { |
322 | s.remove(s.length()-2,2); |
323 | } else if (s.endsWith("\r" )) { |
324 | s.remove(s.length()-1,1); |
325 | } else if (s.endsWith("\n" )){ |
326 | s.remove(s.length()-1,1); |
327 | } |
328 | result.append(s); |
329 | } |
330 | if (!ok) { |
331 | file.seek(0); |
332 | result.clear(); |
333 | codec = QTextCodec::codecForName("UTF-8" ); |
334 | while (!file.atEnd()) { |
335 | QByteArray array = file.readLine(); |
336 | QString s = codec->toUnicode(array,array.length(),&state); |
337 | if (state.invalidChars>0) { |
338 | result.clear(); |
339 | break; |
340 | } |
341 | if (s.endsWith("\r\n" )) { |
342 | s.remove(s.length()-2,2); |
343 | } else if (s.endsWith("\r" )) { |
344 | s.remove(s.length()-1,1); |
345 | } else if (s.endsWith("\n" )){ |
346 | s.remove(s.length()-1,1); |
347 | } |
348 | result.append(s); |
349 | } |
350 | } |
351 | } |
352 | return result; |
353 | } |
354 | |
355 | QByteArray readFileToByteArray(const QString &fileName) |
356 | { |
357 | QFile file(fileName); |
358 | if (file.open(QFile::ReadOnly)) { |
359 | return file.readAll(); |
360 | } |
361 | return QByteArray(); |
362 | } |
363 | |
364 | void stringToFile(const QString &str, const QString &fileName) |
365 | { |
366 | QFile file(fileName); |
367 | if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { |
368 | QTextStream stream(&file); |
369 | stream<<str; |
370 | } |
371 | } |
372 | |
373 | |
374 | void stringsToFile(const QStringList &list, const QString &fileName) |
375 | { |
376 | QFile file(fileName); |
377 | if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { |
378 | QTextStream stream(&file); |
379 | for (const QString& s:list) { |
380 | stream<<s |
381 | #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) |
382 | <<Qt::endl; |
383 | #else |
384 | <<endl; |
385 | #endif |
386 | } |
387 | } |
388 | } |
389 | |
390 | bool fileExists(const QString &file) |
391 | { |
392 | if (file.isEmpty()) |
393 | return false; |
394 | return QFile(file).exists(); |
395 | } |
396 | |
397 | bool fileExists(const QString &dir, const QString &fileName) |
398 | { |
399 | if (dir.isEmpty() || fileName.isEmpty()) |
400 | return false; |
401 | QDir dirInfo(dir); |
402 | return dirInfo.exists(fileName); |
403 | } |
404 | |
405 | bool directoryExists(const QString &file) |
406 | { |
407 | if (file.isEmpty()) |
408 | return false; |
409 | QFileInfo dir(file); |
410 | return dir.exists() && dir.isDir(); |
411 | } |
412 | |
413 | bool removeFile(const QString &filename) |
414 | { |
415 | QFile file(filename); |
416 | return file.remove(); |
417 | } |
418 | |
419 | bool copyFile(const QString &fromPath, const QString &toPath, bool overwrite) |
420 | { |
421 | QFile fromFile(fromPath); |
422 | QFile toFile(toPath); |
423 | if (!fromFile.exists()) |
424 | return false; |
425 | if (toFile.exists()) { |
426 | if (!overwrite) |
427 | return false; |
428 | if (!toFile.remove()) |
429 | return false; |
430 | } |
431 | |
432 | if (!fromFile.open(QFile::ReadOnly)) |
433 | return false; |
434 | if (!toFile.open(QFile::WriteOnly | QFile::Truncate)) |
435 | return false; |
436 | |
437 | int bufferSize=64*1024; |
438 | char buffer[bufferSize]; |
439 | |
440 | while (!fromFile.atEnd()) { |
441 | int readed = fromFile.read(buffer,bufferSize); |
442 | toFile.write(buffer,readed); |
443 | } |
444 | toFile.close(); |
445 | fromFile.close(); |
446 | return true; |
447 | } |
448 | |
449 | void copyFolder(const QString &fromDir, const QString &toDir) |
450 | { |
451 | QDirIterator it(fromDir); |
452 | QDir dir(fromDir); |
453 | QDir targetDir(toDir); |
454 | const int absSourcePathLength = dir.absolutePath().length(); |
455 | |
456 | |
457 | if (targetDir.exists()) |
458 | return; |
459 | targetDir.mkpath(targetDir.absolutePath()); |
460 | |
461 | while (it.hasNext()){ |
462 | it.next(); |
463 | const auto fileInfo = it.fileInfo(); |
464 | if(!fileInfo.isHidden() && !fileInfo.fileName().startsWith('.')) { //filters dot and dotdot |
465 | const QString subPathStructure = fileInfo.absoluteFilePath().mid(absSourcePathLength); |
466 | const QString constructedAbsolutePath = targetDir.absolutePath() + subPathStructure; |
467 | if(fileInfo.isDir()){ |
468 | //Create directory in target folder |
469 | dir.mkpath(constructedAbsolutePath); |
470 | copyFolder(fileInfo.absoluteFilePath(), constructedAbsolutePath); |
471 | } else if(fileInfo.isFile()) { |
472 | //Copy File to target directory |
473 | |
474 | //Remove file at target location, if it exists, or QFile::copy will fail |
475 | QFile::remove(constructedAbsolutePath); |
476 | QFile::copy(fileInfo.absoluteFilePath(), constructedAbsolutePath); |
477 | QFile newFile(constructedAbsolutePath); |
478 | QFile::Permissions permissions = newFile.permissions(); |
479 | permissions |= (QFile::Permission::WriteOwner |
480 | | QFile::Permission::WriteUser |
481 | | QFile::Permission::WriteGroup |
482 | | QFile::Permission::WriteOther); |
483 | newFile.setPermissions(permissions); |
484 | } |
485 | } |
486 | } |
487 | } |
488 | |
489 | QString includeTrailingPathDelimiter(const QString &path) |
490 | { |
491 | if (path.endsWith('/') || path.endsWith(QDir::separator())) { |
492 | return path; |
493 | } else { |
494 | return path + "/" ; |
495 | } |
496 | } |
497 | |
498 | QString excludeTrailingPathDelimiter(const QString &path) |
499 | { |
500 | int pos = path.length()-1; |
501 | while (pos>=0 && (path[pos]=='/' || path[pos]==QDir::separator())) |
502 | pos--; |
503 | return path.mid(0,pos+1); |
504 | } |
505 | |
506 | QString changeFileExt(const QString& filename, QString ext) |
507 | { |
508 | QFileInfo fileInfo(filename); |
509 | QString suffix = fileInfo.suffix(); |
510 | QString name = fileInfo.fileName(); |
511 | QString path; |
512 | if (!ext.isEmpty() && !ext.startsWith("." )) { |
513 | ext = "." +ext; |
514 | } |
515 | if (fileInfo.path() != "." ) { |
516 | path = includeTrailingPathDelimiter(fileInfo.path()); |
517 | } |
518 | if (suffix.isEmpty()) { |
519 | return path+filename+ext; |
520 | } else { |
521 | return path+fileInfo.completeBaseName()+ext; |
522 | } |
523 | } |
524 | |
525 | QString (const QString &base, const QString &dest) |
526 | { |
527 | QFileInfo baseInfo(base); |
528 | QDir baseDir; |
529 | if (baseInfo.isDir()) { |
530 | baseDir = QDir(baseInfo.absoluteFilePath()); |
531 | } else { |
532 | baseDir = baseInfo.absoluteDir(); |
533 | } |
534 | return baseDir.relativeFilePath(dest); |
535 | } |
536 | |
537 | QString localizePath(const QString &path) |
538 | { |
539 | QString result = path; |
540 | result.replace("/" ,QDir::separator()); |
541 | return result; |
542 | } |
543 | |
544 | QString (const QString &fileName) |
545 | { |
546 | QFileInfo fileInfo(fileName); |
547 | return fileInfo.fileName(); |
548 | } |
549 | |
550 | QString (const QString &fileName) |
551 | { |
552 | return extractFilePath(fileName); |
553 | } |
554 | |
555 | QString (const QString &filePath) |
556 | { |
557 | QFileInfo info(filePath); |
558 | return info.path(); |
559 | } |
560 | |
561 | QString (const QString &filePath) |
562 | { |
563 | QFileInfo info(filePath); |
564 | return info.absoluteFilePath(); |
565 | } |
566 | |
567 | bool isReadOnly(const QString &filename) |
568 | { |
569 | return QFile(filename).isWritable(); |
570 | } |
571 | |
572 | int compareFileModifiedTime(const QString &filename1, const QString &filename2) |
573 | { |
574 | QFileInfo fileInfo1(filename1); |
575 | QFileInfo fileInfo2(filename2); |
576 | qint64 time1=fileInfo1.lastModified().toMSecsSinceEpoch(); |
577 | qint64 time2=fileInfo2.lastModified().toMSecsSinceEpoch(); |
578 | if (time1 > time2) |
579 | return 1; |
580 | if (time1 < time2) |
581 | return -1; |
582 | return 0; |
583 | } |
584 | |
585 | |
586 | void inflateRect(QRect &rect, int delta) |
587 | { |
588 | inflateRect(rect,delta,delta); |
589 | } |
590 | |
591 | void inflateRect(QRect &rect, int dx, int dy) |
592 | { |
593 | rect.setLeft(rect.left()-dx); |
594 | rect.setRight(rect.right()+dx); |
595 | rect.setTop(rect.top()-dy); |
596 | rect.setBottom(rect.bottom()+dy); |
597 | } |
598 | |
599 | |
600 | static int defaultScreenDPI = -1; |
601 | |
602 | int screenDPI() |
603 | { |
604 | if (defaultScreenDPI<1) { |
605 | defaultScreenDPI = qApp->primaryScreen()->logicalDotsPerInch(); |
606 | } |
607 | return defaultScreenDPI; |
608 | } |
609 | |
610 | |
611 | void setScreenDPI(int dpi) |
612 | { |
613 | defaultScreenDPI = dpi; |
614 | } |
615 | |
616 | float pointToPixel(float point, float dpi) |
617 | { |
618 | return point * dpi / 72; |
619 | } |
620 | |
621 | float pointToPixel(float point) |
622 | { |
623 | return pointToPixel(point,screenDPI()); |
624 | } |
625 | |
626 | float pixelToPoint(float pixel) |
627 | { |
628 | return pixel * 72 / screenDPI(); |
629 | } |
630 | |
631 | |
632 | void decodeKey(const int combinedKey, int &key, Qt::KeyboardModifiers &modifiers) |
633 | { |
634 | modifiers = Qt::NoModifier; |
635 | if (combinedKey & Qt::ShiftModifier) { |
636 | modifiers|=Qt::ShiftModifier; |
637 | } |
638 | if (combinedKey & Qt::ControlModifier) { |
639 | modifiers|=Qt::ControlModifier; |
640 | } |
641 | if (combinedKey & Qt::AltModifier) { |
642 | modifiers|=Qt::AltModifier; |
643 | } |
644 | if (combinedKey & Qt::MetaModifier) { |
645 | modifiers|=Qt::MetaModifier; |
646 | } |
647 | if (combinedKey & Qt::KeypadModifier) { |
648 | modifiers|= Qt::KeypadModifier; |
649 | } |
650 | key = combinedKey & ~(Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier | Qt::KeypadModifier); |
651 | } |
652 | |