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
41BaseError::BaseError(const QString &reason):
42mReason(reason)
43{
44
45}
46
47QString BaseError::reason() const
48{
49 return mReason;
50}
51
52IndexOutOfRange::IndexOutOfRange(int Index):
53BaseError(QObject::tr("Index %1 out of range").arg(Index))
54{
55
56}
57
58FileError::FileError(const QString &reason): BaseError(reason)
59{
60
61}
62
63const 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
112bool isTextAllAscii(const QByteArray& text) {
113 for (char c:text) {
114 if (c<0) {
115 return false;
116 }
117 }
118 return true;
119}
120
121bool isTextAllAscii(const QString& text) {
122 for (QChar c:text) {
123 if (c.unicode()>127) {
124 return false;
125 }
126 }
127 return true;
128}
129
130bool isNonPrintableAsciiChar(char ch)
131{
132 return (ch<=32) && (ch>=0);
133}
134
135QStringList textToLines(const QString &text)
136{
137 QTextStream stream(&((QString&)text),QIODevice::ReadOnly);
138 return readStreamToLines(&stream);
139}
140
141
142void textToLines(const QString &text, LineProcessFunc lineFunc)
143{
144 QTextStream stream(&((QString&)text),QIODevice::ReadOnly);
145 readStreamToLines(&stream,lineFunc);
146}
147
148QString linesToText(const QStringList &lines, const QString& lineBreak)
149{
150 return lines.join(lineBreak);
151}
152
153QList<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
190QString 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
206QString 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
224int 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
235bool stringIsBlank(const QString &s)
236{
237 for (QChar ch:s) {
238 if (ch != ' ' && ch != '\t')
239 return false;
240 }
241 return true;
242}
243
244QByteArray toByteArray(const QString &s)
245{
246 //return s.toLocal8Bit();
247 return s.toUtf8();
248}
249
250QString 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
262QStringList 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
272void readStreamToLines(QTextStream *stream,
273 LineProcessFunc lineFunc)
274{
275 QString s;
276 while (stream->readLineInto(&s)) {
277 lineFunc(s);
278 }
279}
280
281QStringList 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
293void 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
304QStringList 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
355QByteArray 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
364void 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
374void 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
390bool fileExists(const QString &file)
391{
392 if (file.isEmpty())
393 return false;
394 return QFile(file).exists();
395}
396
397bool 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
405bool directoryExists(const QString &file)
406{
407 if (file.isEmpty())
408 return false;
409 QFileInfo dir(file);
410 return dir.exists() && dir.isDir();
411}
412
413bool removeFile(const QString &filename)
414{
415 QFile file(filename);
416 return file.remove();
417}
418
419bool 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
449void 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
489QString includeTrailingPathDelimiter(const QString &path)
490{
491 if (path.endsWith('/') || path.endsWith(QDir::separator())) {
492 return path;
493 } else {
494 return path + "/";
495 }
496}
497
498QString 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
506QString 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
525QString extractRelativePath(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
537QString localizePath(const QString &path)
538{
539 QString result = path;
540 result.replace("/",QDir::separator());
541 return result;
542}
543
544QString extractFileName(const QString &fileName)
545{
546 QFileInfo fileInfo(fileName);
547 return fileInfo.fileName();
548}
549
550QString extractFileDir(const QString &fileName)
551{
552 return extractFilePath(fileName);
553}
554
555QString extractFilePath(const QString &filePath)
556{
557 QFileInfo info(filePath);
558 return info.path();
559}
560
561QString extractAbsoluteFilePath(const QString &filePath)
562{
563 QFileInfo info(filePath);
564 return info.absoluteFilePath();
565}
566
567bool isReadOnly(const QString &filename)
568{
569 return QFile(filename).isWritable();
570}
571
572int 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
586void inflateRect(QRect &rect, int delta)
587{
588 inflateRect(rect,delta,delta);
589}
590
591void 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
600static int defaultScreenDPI = -1;
601
602int screenDPI()
603{
604 if (defaultScreenDPI<1) {
605 defaultScreenDPI = qApp->primaryScreen()->logicalDotsPerInch();
606 }
607 return defaultScreenDPI;
608}
609
610
611void setScreenDPI(int dpi)
612{
613 defaultScreenDPI = dpi;
614}
615
616float pointToPixel(float point, float dpi)
617{
618 return point * dpi / 72;
619}
620
621float pointToPixel(float point)
622{
623 return pointToPixel(point,screenDPI());
624}
625
626float pixelToPoint(float pixel)
627{
628 return pixel * 72 / screenDPI();
629}
630
631
632void 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