vutils.cpp 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. #include "vutils.h"
  2. #include <QFile>
  3. #include <QDir>
  4. #include <QDebug>
  5. #include <QRegExp>
  6. #include <QClipboard>
  7. #include <QApplication>
  8. #include <QMimeData>
  9. #include <QJsonObject>
  10. #include <QJsonDocument>
  11. #include <QDateTime>
  12. #include <QFileInfo>
  13. #include <QImageReader>
  14. #include <QKeyEvent>
  15. #include <QScreen>
  16. #include <cmath>
  17. #include <QLocale>
  18. #include <QPushButton>
  19. #include <QElapsedTimer>
  20. #include <QValidator>
  21. #include <QRegExpValidator>
  22. #include <QRegExp>
  23. #include <QKeySequence>
  24. #include "vorphanfile.h"
  25. #include "vnote.h"
  26. #include "vnotebook.h"
  27. #include "hgmarkdownhighlighter.h"
  28. extern VConfigManager *g_config;
  29. QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
  30. const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
  31. const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
  32. const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
  33. const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
  34. const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
  35. const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)");
  36. const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s?\\S.*)\\s*$");
  37. const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s?)($|\\S.*\\s*$)");
  38. void VUtils::initAvailableLanguage()
  39. {
  40. if (!s_availableLanguages.isEmpty()) {
  41. return;
  42. }
  43. s_availableLanguages.append(QPair<QString, QString>("en_US", "English (US)"));
  44. s_availableLanguages.append(QPair<QString, QString>("zh_CN", "Chinese"));
  45. }
  46. QString VUtils::readFileFromDisk(const QString &filePath)
  47. {
  48. QFile file(filePath);
  49. if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
  50. qWarning() << "fail to read file" << filePath;
  51. return QString();
  52. }
  53. QString fileText(file.readAll());
  54. file.close();
  55. qDebug() << "read file content:" << filePath;
  56. return fileText;
  57. }
  58. bool VUtils::writeFileToDisk(const QString &filePath, const QString &text)
  59. {
  60. QFile file(filePath);
  61. if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
  62. qWarning() << "fail to open file" << filePath << "to write";
  63. return false;
  64. }
  65. QTextStream stream(&file);
  66. stream << text;
  67. file.close();
  68. qDebug() << "write file content:" << filePath;
  69. return true;
  70. }
  71. QRgb VUtils::QRgbFromString(const QString &str)
  72. {
  73. Q_ASSERT(str.length() == 6);
  74. QString rStr = str.left(2);
  75. QString gStr = str.mid(2, 2);
  76. QString bStr = str.right(2);
  77. bool ok, ret = true;
  78. int red = rStr.toInt(&ok, 16);
  79. ret = ret && ok;
  80. int green = gStr.toInt(&ok, 16);
  81. ret = ret && ok;
  82. int blue = bStr.toInt(&ok, 16);
  83. ret = ret && ok;
  84. if (ret) {
  85. return qRgb(red, green, blue);
  86. }
  87. qWarning() << "fail to construct QRgb from string" << str;
  88. return QRgb();
  89. }
  90. QString VUtils::generateImageFileName(const QString &path,
  91. const QString &title,
  92. const QString &format)
  93. {
  94. QRegExp regExp("\\W");
  95. QString baseName(title.toLower());
  96. // Remove non-character chars.
  97. baseName.remove(regExp);
  98. // Constrain the length of the name.
  99. baseName.truncate(10);
  100. if (!baseName.isEmpty()) {
  101. baseName.prepend('_');
  102. }
  103. // Add current time and random number to make the name be most likely unique
  104. baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t());
  105. baseName = baseName + '_' + QString::number(qrand());
  106. QDir dir(path);
  107. QString imageName = baseName + "." + format.toLower();
  108. int index = 1;
  109. while (fileExists(dir, imageName, true)) {
  110. imageName = QString("%1_%2.%3").arg(baseName).arg(index++)
  111. .arg(format.toLower());
  112. }
  113. return imageName;
  114. }
  115. void VUtils::processStyle(QString &style, const QVector<QPair<QString, QString> > &varMap)
  116. {
  117. // Process style
  118. for (int i = 0; i < varMap.size(); ++i) {
  119. const QPair<QString, QString> &map = varMap[i];
  120. style.replace("@" + map.first, map.second);
  121. }
  122. }
  123. QString VUtils::fileNameFromPath(const QString &p_path)
  124. {
  125. if (p_path.isEmpty()) {
  126. return p_path;
  127. }
  128. return QFileInfo(QDir::cleanPath(p_path)).fileName();
  129. }
  130. QString VUtils::basePathFromPath(const QString &p_path)
  131. {
  132. if (p_path.isEmpty()) {
  133. return p_path;
  134. }
  135. return QFileInfo(QDir::cleanPath(p_path)).path();
  136. }
  137. QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
  138. ImageLink::ImageLinkType p_type)
  139. {
  140. Q_ASSERT(p_file->getDocType() == DocType::Markdown);
  141. QVector<ImageLink> images;
  142. bool isOpened = p_file->isOpened();
  143. if (!isOpened && !p_file->open()) {
  144. return images;
  145. }
  146. const QString &text = p_file->getContent();
  147. if (text.isEmpty()) {
  148. if (!isOpened) {
  149. p_file->close();
  150. }
  151. return images;
  152. }
  153. QVector<VElementRegion> regions = fetchImageRegionsUsingParser(text);
  154. QRegExp regExp(c_imageLinkRegExp);
  155. QString basePath = p_file->fetchBasePath();
  156. for (int i = 0; i < regions.size(); ++i) {
  157. const VElementRegion &reg = regions[i];
  158. QString linkText = text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos);
  159. bool matched = regExp.exactMatch(linkText);
  160. if (!matched) {
  161. // Image links with reference format will not match.
  162. continue;
  163. }
  164. QString imageUrl = regExp.capturedTexts()[2].trimmed();
  165. ImageLink link;
  166. QFileInfo info(basePath, imageUrl);
  167. if (info.exists()) {
  168. if (info.isNativePath()) {
  169. // Local file.
  170. link.m_path = QDir::cleanPath(info.absoluteFilePath());
  171. if (QDir::isRelativePath(imageUrl)) {
  172. link.m_type = p_file->isInternalImageFolder(VUtils::basePathFromPath(link.m_path)) ?
  173. ImageLink::LocalRelativeInternal : ImageLink::LocalRelativeExternal;
  174. } else {
  175. link.m_type = ImageLink::LocalAbsolute;
  176. }
  177. } else {
  178. link.m_type = ImageLink::Resource;
  179. link.m_path = imageUrl;
  180. }
  181. } else {
  182. QUrl url(imageUrl);
  183. link.m_path = url.toString();
  184. link.m_type = ImageLink::Remote;
  185. }
  186. if (link.m_type & p_type) {
  187. images.push_back(link);
  188. qDebug() << "fetch one image:" << link.m_type << link.m_path;
  189. }
  190. }
  191. if (!isOpened) {
  192. p_file->close();
  193. }
  194. return images;
  195. }
  196. bool VUtils::makePath(const QString &p_path)
  197. {
  198. if (p_path.isEmpty()) {
  199. return true;
  200. }
  201. bool ret = true;
  202. QDir dir;
  203. if (dir.mkpath(p_path)) {
  204. qDebug() << "make path" << p_path;
  205. } else {
  206. qWarning() << "fail to make path" << p_path;
  207. ret = false;
  208. }
  209. return ret;
  210. }
  211. QJsonObject VUtils::clipboardToJson()
  212. {
  213. QClipboard *clipboard = QApplication::clipboard();
  214. const QMimeData *mimeData = clipboard->mimeData();
  215. QJsonObject obj;
  216. if (mimeData->hasText()) {
  217. QString text = mimeData->text();
  218. obj = QJsonDocument::fromJson(text.toUtf8()).object();
  219. qDebug() << "Json object in clipboard" << obj;
  220. }
  221. return obj;
  222. }
  223. ClipboardOpType VUtils::operationInClipboard()
  224. {
  225. QJsonObject obj = clipboardToJson();
  226. if (obj.contains(ClipboardConfig::c_type)) {
  227. return (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
  228. }
  229. return ClipboardOpType::Invalid;
  230. }
  231. bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut)
  232. {
  233. QString srcPath = QDir::cleanPath(p_srcFilePath);
  234. QString destPath = QDir::cleanPath(p_destFilePath);
  235. if (srcPath == destPath) {
  236. return true;
  237. }
  238. QDir dir;
  239. if (!dir.mkpath(basePathFromPath(p_destFilePath))) {
  240. qWarning() << "fail to create directory" << basePathFromPath(p_destFilePath);
  241. return false;
  242. }
  243. if (p_isCut) {
  244. QFile file(srcPath);
  245. if (!file.rename(destPath)) {
  246. qWarning() << "fail to copy file" << srcPath << destPath;
  247. return false;
  248. }
  249. } else {
  250. if (!QFile::copy(srcPath, destPath)) {
  251. qWarning() << "fail to copy file" << srcPath << destPath;
  252. return false;
  253. }
  254. }
  255. return true;
  256. }
  257. bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
  258. {
  259. QString srcPath = QDir::cleanPath(p_srcDirPath);
  260. QString destPath = QDir::cleanPath(p_destDirPath);
  261. if (srcPath == destPath) {
  262. return true;
  263. }
  264. if (QFileInfo::exists(destPath)) {
  265. qWarning() << QString("target directory %1 already exists").arg(destPath);
  266. return false;
  267. }
  268. // QDir.rename() could not move directory across drives.
  269. // Make sure target directory exists.
  270. QDir destDir(destPath);
  271. if (!destDir.exists()) {
  272. if (!destDir.mkpath(destPath)) {
  273. qWarning() << QString("fail to create target directory %1").arg(destPath);
  274. return false;
  275. }
  276. }
  277. // Handle directory recursively.
  278. QDir srcDir(srcPath);
  279. Q_ASSERT(srcDir.exists() && destDir.exists());
  280. QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  281. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  282. for (int i = 0; i < nodes.size(); ++i) {
  283. const QFileInfo &fileInfo = nodes.at(i);
  284. QString name = fileInfo.fileName();
  285. if (fileInfo.isDir()) {
  286. if (!copyDirectory(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  287. return false;
  288. }
  289. } else {
  290. Q_ASSERT(fileInfo.isFile());
  291. if (!copyFile(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  292. return false;
  293. }
  294. }
  295. }
  296. if (p_isCut) {
  297. if (!destDir.rmdir(srcPath)) {
  298. qWarning() << QString("fail to delete source directory %1 after cut").arg(srcPath);
  299. return false;
  300. }
  301. }
  302. return true;
  303. }
  304. int VUtils::showMessage(QMessageBox::Icon p_icon, const QString &p_title, const QString &p_text, const QString &p_infoText,
  305. QMessageBox::StandardButtons p_buttons, QMessageBox::StandardButton p_defaultBtn, QWidget *p_parent,
  306. MessageBoxType p_type)
  307. {
  308. QMessageBox msgBox(p_icon, p_title, p_text, p_buttons, p_parent);
  309. msgBox.setInformativeText(p_infoText);
  310. msgBox.setDefaultButton(p_defaultBtn);
  311. if (p_type == MessageBoxType::Danger) {
  312. QPushButton *okBtn = dynamic_cast<QPushButton *>(msgBox.button(QMessageBox::Ok));
  313. if (okBtn) {
  314. okBtn->setStyleSheet(g_config->c_dangerBtnStyle);
  315. }
  316. }
  317. return msgBox.exec();
  318. }
  319. QString VUtils::generateCopiedFileName(const QString &p_dirPath,
  320. const QString &p_fileName,
  321. bool p_completeBaseName)
  322. {
  323. QDir dir(p_dirPath);
  324. if (!dir.exists() || !dir.exists(p_fileName)) {
  325. return p_fileName;
  326. }
  327. QFileInfo fi(p_fileName);
  328. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  329. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  330. int index = 0;
  331. QString fileName;
  332. do {
  333. QString seq;
  334. if (index > 0) {
  335. seq = QString("%1").arg(QString::number(index), 3, '0');
  336. }
  337. index++;
  338. fileName = QString("%1_copy%2").arg(baseName).arg(seq);
  339. if (!suffix.isEmpty()) {
  340. fileName = fileName + "." + suffix;
  341. }
  342. } while (fileExists(dir, fileName, true));
  343. return fileName;
  344. }
  345. QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
  346. {
  347. QDir dir(p_parentDirPath);
  348. QString name = p_dirName;
  349. QString dirPath = dir.filePath(name);
  350. int index = 0;
  351. while (QDir(dirPath).exists()) {
  352. QString seq;
  353. if (index > 0) {
  354. seq = QString::number(index);
  355. }
  356. index++;
  357. name = QString("%1_copy%2").arg(p_dirName).arg(seq);
  358. dirPath = dir.filePath(name);
  359. }
  360. return name;
  361. }
  362. const QVector<QPair<QString, QString>>& VUtils::getAvailableLanguages()
  363. {
  364. if (s_availableLanguages.isEmpty()) {
  365. initAvailableLanguage();
  366. }
  367. return s_availableLanguages;
  368. }
  369. bool VUtils::isValidLanguage(const QString &p_lang)
  370. {
  371. for (auto const &lang : getAvailableLanguages()) {
  372. if (lang.first == p_lang) {
  373. return true;
  374. }
  375. }
  376. return false;
  377. }
  378. bool VUtils::isImageURL(const QUrl &p_url)
  379. {
  380. QString urlStr;
  381. if (p_url.isLocalFile()) {
  382. urlStr = p_url.toLocalFile();
  383. } else {
  384. urlStr = p_url.toString();
  385. }
  386. return isImageURLText(urlStr);
  387. }
  388. bool VUtils::isImageURLText(const QString &p_url)
  389. {
  390. QFileInfo info(p_url);
  391. return QImageReader::supportedImageFormats().contains(info.suffix().toLower().toLatin1());
  392. }
  393. qreal VUtils::calculateScaleFactor()
  394. {
  395. // const qreal refHeight = 1152;
  396. // const qreal refWidth = 2048;
  397. const qreal refDpi = 96;
  398. qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
  399. qreal factor = dpi / refDpi;
  400. return factor < 1 ? 1 : factor;
  401. }
  402. bool VUtils::realEqual(qreal p_a, qreal p_b)
  403. {
  404. return std::abs(p_a - p_b) < 1e-8;
  405. }
  406. QChar VUtils::keyToChar(int p_key)
  407. {
  408. if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
  409. return QChar('a' + p_key - Qt::Key_A);
  410. }
  411. return QChar();
  412. }
  413. QString VUtils::getLocale()
  414. {
  415. QString locale = g_config->getLanguage();
  416. if (locale == "System" || !isValidLanguage(locale)) {
  417. locale = QLocale::system().name();
  418. }
  419. return locale;
  420. }
  421. void VUtils::sleepWait(int p_milliseconds)
  422. {
  423. if (p_milliseconds <= 0) {
  424. return;
  425. }
  426. QElapsedTimer t;
  427. t.start();
  428. while (t.elapsed() < p_milliseconds) {
  429. QCoreApplication::processEvents();
  430. }
  431. }
  432. DocType VUtils::docTypeFromName(const QString &p_name)
  433. {
  434. if (p_name.isEmpty()) {
  435. return DocType::Unknown;
  436. }
  437. const QHash<int, QList<QString>> &suffixes = g_config->getDocSuffixes();
  438. QString suf = QFileInfo(p_name).suffix().toLower();
  439. for (auto it = suffixes.begin(); it != suffixes.end(); ++it) {
  440. if (it.value().contains(suf)) {
  441. return DocType(it.key());
  442. }
  443. }
  444. return DocType::Unknown;
  445. }
  446. QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType, bool p_exportPdf)
  447. {
  448. QString jsFile, extraFile;
  449. switch (p_conType) {
  450. case MarkdownConverterType::Marked:
  451. jsFile = "qrc" + VNote::c_markedJsFile;
  452. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  453. break;
  454. case MarkdownConverterType::Hoedown:
  455. jsFile = "qrc" + VNote::c_hoedownJsFile;
  456. // Use Marked to highlight code blocks.
  457. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  458. break;
  459. case MarkdownConverterType::MarkdownIt:
  460. {
  461. jsFile = "qrc" + VNote::c_markdownitJsFile;
  462. extraFile = "<script src=\"qrc" + VNote::c_markdownitExtraFile + "\"></script>\n" +
  463. "<script src=\"qrc" + VNote::c_markdownitAnchorExtraFile + "\"></script>\n" +
  464. "<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n" +
  465. "<script src=\"qrc" + VNote::c_markdownitSubExtraFile + "\"></script>\n" +
  466. "<script src=\"qrc" + VNote::c_markdownitSupExtraFile + "\"></script>\n" +
  467. "<script src=\"qrc" + VNote::c_markdownitFootnoteExtraFile + "\"></script>\n";
  468. MarkdownitOption opt = g_config->getMarkdownitOption();
  469. QString optJs = QString("<script>var VMarkdownitOption = {"
  470. "html: %1, breaks: %2, linkify: %3};"
  471. "</script>\n")
  472. .arg(opt.m_html ? "true" : "false")
  473. .arg(opt.m_breaks ? "true" : "false")
  474. .arg(opt.m_linkify ? "true" : "false");
  475. extraFile += optJs;
  476. break;
  477. }
  478. case MarkdownConverterType::Showdown:
  479. jsFile = "qrc" + VNote::c_showdownJsFile;
  480. extraFile = "<script src=\"qrc" + VNote::c_showdownExtraFile + "\"></script>\n" +
  481. "<script src=\"qrc" + VNote::c_showdownAnchorExtraFile + "\"></script>\n";
  482. break;
  483. default:
  484. Q_ASSERT(false);
  485. }
  486. if (g_config->getEnableMermaid()) {
  487. extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"qrc" + VNote::c_mermaidCssFile + "\"/>\n" +
  488. "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n" +
  489. "<script>var VEnableMermaid = true;</script>\n";
  490. }
  491. if (g_config->getEnableFlowchart()) {
  492. extraFile += "<script src=\"qrc" + VNote::c_raphaelJsFile + "\"></script>\n" +
  493. "<script src=\"qrc" + VNote::c_flowchartJsFile + "\"></script>\n" +
  494. "<script>var VEnableFlowchart = true;</script>\n";
  495. }
  496. if (g_config->getEnableMathjax()) {
  497. extraFile += "<script type=\"text/x-mathjax-config\">"
  498. "MathJax.Hub.Config({\n"
  499. " tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']]},\n"
  500. " showProcessingMessages: false,\n"
  501. " messageStyle: \"none\"});\n"
  502. "</script>\n"
  503. "<script type=\"text/javascript\" async src=\"" + g_config->getMathjaxJavascript() + "\"></script>\n" +
  504. "<script>var VEnableMathjax = true;</script>\n";
  505. }
  506. if (g_config->getEnableImageCaption()) {
  507. extraFile += "<script>var VEnableImageCaption = true;</script>\n";
  508. }
  509. if (g_config->getEnableCodeBlockLineNumber()) {
  510. extraFile += "<script src=\"qrc" + VNote::c_highlightjsLineNumberExtraFile + "\"></script>\n" +
  511. "<script>var VEnableHighlightLineNumber = true;</script>\n";
  512. }
  513. QString htmlTemplate;
  514. if (p_exportPdf) {
  515. htmlTemplate = VNote::s_markdownTemplatePDF;
  516. } else {
  517. htmlTemplate = VNote::s_markdownTemplate;
  518. }
  519. htmlTemplate.replace(c_htmlJSHolder, jsFile);
  520. if (!extraFile.isEmpty()) {
  521. htmlTemplate.replace(c_htmlExtraHolder, extraFile);
  522. }
  523. return htmlTemplate;
  524. }
  525. QString VUtils::getFileNameWithSequence(const QString &p_directory,
  526. const QString &p_baseFileName,
  527. bool p_completeBaseName)
  528. {
  529. QDir dir(p_directory);
  530. if (!dir.exists() || !dir.exists(p_baseFileName)) {
  531. return p_baseFileName;
  532. }
  533. // Append a sequence.
  534. QFileInfo fi(p_baseFileName);
  535. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  536. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  537. int seq = 1;
  538. QString fileName;
  539. do {
  540. fileName = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
  541. if (!suffix.isEmpty()) {
  542. fileName = fileName + "." + suffix;
  543. }
  544. } while (fileExists(dir, fileName, true));
  545. return fileName;
  546. }
  547. QString VUtils::getDirNameWithSequence(const QString &p_directory,
  548. const QString &p_baseDirName)
  549. {
  550. QDir dir(p_directory);
  551. if (!dir.exists() || !dir.exists(p_baseDirName)) {
  552. return p_baseDirName;
  553. }
  554. // Append a sequence.
  555. int seq = 1;
  556. QString fileName;
  557. do {
  558. fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
  559. } while (fileExists(dir, fileName, true));
  560. return fileName;
  561. }
  562. QString VUtils::getRandomFileName(const QString &p_directory)
  563. {
  564. Q_ASSERT(!p_directory.isEmpty());
  565. QString name;
  566. QDir dir(p_directory);
  567. do {
  568. name = QString::number(QDateTime::currentDateTimeUtc().toTime_t());
  569. name = name + '_' + QString::number(qrand());
  570. } while (fileExists(dir, name, true));
  571. return name;
  572. }
  573. bool VUtils::checkPathLegal(const QString &p_path)
  574. {
  575. // Ensure every part of the p_path is a valid file name until we come to
  576. // an existing parent directory.
  577. if (p_path.isEmpty()) {
  578. return false;
  579. }
  580. if (QFileInfo::exists(p_path)) {
  581. #if defined(Q_OS_WIN)
  582. // On Windows, "/" and ":" will also make exists() return true.
  583. if (p_path.startsWith('/') || p_path == ":") {
  584. return false;
  585. }
  586. #endif
  587. return true;
  588. }
  589. bool ret = false;
  590. int pos;
  591. QString basePath = basePathFromPath(p_path);
  592. QString fileName = fileNameFromPath(p_path);
  593. QValidator *validator = new QRegExpValidator(QRegExp(c_fileNameRegExp));
  594. while (!fileName.isEmpty()) {
  595. QValidator::State validFile = validator->validate(fileName, pos);
  596. if (validFile != QValidator::Acceptable) {
  597. break;
  598. }
  599. if (QFileInfo::exists(basePath)) {
  600. ret = true;
  601. #if defined(Q_OS_WIN)
  602. // On Windows, "/" and ":" will also make exists() return true.
  603. if (basePath.startsWith('/') || basePath == ":") {
  604. ret = false;
  605. }
  606. #endif
  607. break;
  608. }
  609. fileName = fileNameFromPath(basePath);
  610. basePath = basePathFromPath(basePath);
  611. }
  612. delete validator;
  613. return ret;
  614. }
  615. bool VUtils::checkFileNameLegal(const QString &p_name)
  616. {
  617. if (p_name.isEmpty()) {
  618. return false;
  619. }
  620. QRegExp exp(c_fileNameRegExp);
  621. return exp.exactMatch(p_name);
  622. }
  623. bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
  624. {
  625. QString a = QDir::cleanPath(p_patha);
  626. QString b = QDir::cleanPath(p_pathb);
  627. #if defined(Q_OS_WIN)
  628. a = a.toLower();
  629. b = b.toLower();
  630. #endif
  631. return a == b;
  632. }
  633. bool VUtils::splitPathInBasePath(const QString &p_base,
  634. const QString &p_path,
  635. QStringList &p_parts)
  636. {
  637. p_parts.clear();
  638. QString a = QDir::cleanPath(p_base);
  639. QString b = QDir::cleanPath(p_path);
  640. #if defined(Q_OS_WIN)
  641. if (!b.toLower().startsWith(a.toLower())) {
  642. return false;
  643. }
  644. #else
  645. if (!b.startsWith(a)) {
  646. return false;
  647. }
  648. #endif
  649. if (a.size() == b.size()) {
  650. return true;
  651. }
  652. Q_ASSERT(a.size() < b.size());
  653. if (b.at(a.size()) != '/') {
  654. return false;
  655. }
  656. p_parts = b.right(b.size() - a.size() - 1).split("/", QString::SkipEmptyParts);
  657. qDebug() << QString("split path %1 based on %2 to %3 parts").arg(p_path).arg(p_base).arg(p_parts.size());
  658. return true;
  659. }
  660. void VUtils::decodeUrl(QString &p_url)
  661. {
  662. QHash<QString, QString> maps;
  663. maps.insert("%20", " ");
  664. for (auto it = maps.begin(); it != maps.end(); ++it) {
  665. p_url.replace(it.key(), it.value());
  666. }
  667. }
  668. QString VUtils::getShortcutText(const QString &p_keySeq)
  669. {
  670. return QKeySequence(p_keySeq).toString(QKeySequence::NativeText);
  671. }
  672. bool VUtils::deleteDirectory(const VNotebook *p_notebook,
  673. const QString &p_path,
  674. bool p_skipRecycleBin)
  675. {
  676. if (p_skipRecycleBin) {
  677. QDir dir(p_path);
  678. return dir.removeRecursively();
  679. } else {
  680. // Move it to the recycle bin folder.
  681. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  682. }
  683. }
  684. bool VUtils::emptyDirectory(const VNotebook *p_notebook,
  685. const QString &p_path,
  686. bool p_skipRecycleBin)
  687. {
  688. QDir dir(p_path);
  689. if (!dir.exists()) {
  690. return true;
  691. }
  692. QFileInfoList nodes = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  693. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  694. for (int i = 0; i < nodes.size(); ++i) {
  695. const QFileInfo &fileInfo = nodes.at(i);
  696. if (fileInfo.isDir()) {
  697. if (!deleteDirectory(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  698. return false;
  699. }
  700. } else {
  701. Q_ASSERT(fileInfo.isFile());
  702. if (!deleteFile(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  703. return false;
  704. }
  705. }
  706. }
  707. return true;
  708. }
  709. bool VUtils::deleteFile(const VNotebook *p_notebook,
  710. const QString &p_path,
  711. bool p_skipRecycleBin)
  712. {
  713. if (p_skipRecycleBin) {
  714. QFile file(p_path);
  715. return file.remove();
  716. } else {
  717. // Move it to the recycle bin folder.
  718. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  719. }
  720. }
  721. bool VUtils::deleteFile(const VOrphanFile *p_file,
  722. const QString &p_path,
  723. bool p_skipRecycleBin)
  724. {
  725. if (p_skipRecycleBin) {
  726. QFile file(p_path);
  727. return file.remove();
  728. } else {
  729. // Move it to the recycle bin folder.
  730. return deleteFile(p_file->fetchRecycleBinFolderPath(), p_path);
  731. }
  732. }
  733. static QString getRecycleBinSubFolderToUse(const QString &p_folderPath)
  734. {
  735. QDir dir(p_folderPath);
  736. return QDir::cleanPath(dir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd")));
  737. }
  738. bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
  739. const QString &p_path)
  740. {
  741. QString binPath = getRecycleBinSubFolderToUse(p_recycleBinFolderPath);
  742. QDir binDir(binPath);
  743. if (!binDir.exists()) {
  744. binDir.mkpath(binPath);
  745. if (!binDir.exists()) {
  746. return false;
  747. }
  748. }
  749. QString destName = getFileNameWithSequence(binPath,
  750. fileNameFromPath(p_path),
  751. true);
  752. qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
  753. if (!binDir.rename(p_path, binDir.filePath(destName))) {
  754. qWarning() << "fail to move" << p_path << "to" << binDir.filePath(destName);
  755. return false;
  756. }
  757. return true;
  758. }
  759. QVector<VElementRegion> VUtils::fetchImageRegionsUsingParser(const QString &p_content)
  760. {
  761. Q_ASSERT(!p_content.isEmpty());
  762. QVector<VElementRegion> regs;
  763. QByteArray ba = p_content.toUtf8();
  764. const char *data = (const char *)ba.data();
  765. int len = ba.size();
  766. pmh_element **result = NULL;
  767. char *content = new char[len + 1];
  768. memcpy(content, data, len);
  769. content[len] = '\0';
  770. pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
  771. if (!result) {
  772. return regs;
  773. }
  774. pmh_element *elem = result[pmh_IMAGE];
  775. while (elem != NULL) {
  776. if (elem->end <= elem->pos) {
  777. elem = elem->next;
  778. continue;
  779. }
  780. regs.push_back(VElementRegion(elem->pos, elem->end));
  781. elem = elem->next;
  782. }
  783. pmh_free_elements(result);
  784. return regs;
  785. }
  786. QString VUtils::displayDateTime(const QDateTime &p_dateTime)
  787. {
  788. QString res = p_dateTime.date().toString(Qt::DefaultLocaleLongDate);
  789. res += " " + p_dateTime.time().toString();
  790. return res;
  791. }
  792. bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive)
  793. {
  794. if (!p_forceCaseInsensitive) {
  795. return p_dir.exists(p_name);
  796. }
  797. QString name = p_name.toLower();
  798. QStringList names = p_dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden
  799. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  800. foreach (const QString &str, names) {
  801. if (str.toLower() == name) {
  802. return true;
  803. }
  804. }
  805. return false;
  806. }
  807. void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
  808. {
  809. if (!p_msg) {
  810. return;
  811. }
  812. if (p_msg->isEmpty()) {
  813. *p_msg = p_str;
  814. } else {
  815. *p_msg = *p_msg + '\n' + p_str;
  816. }
  817. }
  818. QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
  819. {
  820. QStringList paths;
  821. for (int i = 0; i < p_files.size(); ++i) {
  822. QString path = validFilePathToOpen(p_files[i]);
  823. if (!path.isEmpty()) {
  824. paths.append(path);
  825. }
  826. }
  827. return paths;
  828. }
  829. QString VUtils::validFilePathToOpen(const QString &p_file)
  830. {
  831. if (QFileInfo::exists(p_file)) {
  832. QFileInfo fi(p_file);
  833. if (fi.isFile()) {
  834. // Need to use absolute path here since VNote may be launched
  835. // in different working directory.
  836. return QDir::cleanPath(fi.absoluteFilePath());
  837. }
  838. }
  839. return QString();
  840. }