vutils.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  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 <QComboBox>
  25. #include <QStyledItemDelegate>
  26. #include <QWebEngineView>
  27. #include "vorphanfile.h"
  28. #include "vnote.h"
  29. #include "vnotebook.h"
  30. #include "hgmarkdownhighlighter.h"
  31. #include "vpreviewpage.h"
  32. extern VConfigManager *g_config;
  33. QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
  34. const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(([^\\)\"]+)\\s*(\"(\\\\.|[^\"\\)])*\")?\\s*\\)");
  35. const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
  36. const QString VUtils::c_fileNameRegExp = QString("[^\\\\/:\\*\\?\"<>\\|]*");
  37. const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
  38. const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
  39. const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)");
  40. const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s*(\\S.*)?)$");
  41. const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s*)($|(\\S.*)?$)");
  42. void VUtils::initAvailableLanguage()
  43. {
  44. if (!s_availableLanguages.isEmpty()) {
  45. return;
  46. }
  47. s_availableLanguages.append(QPair<QString, QString>("en_US", "English (US)"));
  48. s_availableLanguages.append(QPair<QString, QString>("zh_CN", "Chinese"));
  49. }
  50. QString VUtils::readFileFromDisk(const QString &filePath)
  51. {
  52. QFile file(filePath);
  53. if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
  54. qWarning() << "fail to open file" << filePath << "to read";
  55. return QString();
  56. }
  57. QString fileText(file.readAll());
  58. file.close();
  59. qDebug() << "read file content:" << filePath;
  60. return fileText;
  61. }
  62. bool VUtils::writeFileToDisk(const QString &filePath, const QString &text)
  63. {
  64. QFile file(filePath);
  65. if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
  66. qWarning() << "fail to open file" << filePath << "to write";
  67. return false;
  68. }
  69. QTextStream stream(&file);
  70. stream << text;
  71. file.close();
  72. qDebug() << "write file content:" << filePath;
  73. return true;
  74. }
  75. bool VUtils::writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json)
  76. {
  77. QFile file(p_filePath);
  78. // We use Unix LF for config file.
  79. if (!file.open(QIODevice::WriteOnly)) {
  80. qWarning() << "fail to open file" << p_filePath << "to write";
  81. return false;
  82. }
  83. QJsonDocument doc(p_json);
  84. if (-1 == file.write(doc.toJson())) {
  85. return false;
  86. }
  87. return true;
  88. }
  89. QJsonObject VUtils::readJsonFromDisk(const QString &p_filePath)
  90. {
  91. QFile file(p_filePath);
  92. if (!file.open(QIODevice::ReadOnly)) {
  93. qWarning() << "fail to open file" << p_filePath << "to read";
  94. return QJsonObject();
  95. }
  96. return QJsonDocument::fromJson(file.readAll()).object();
  97. }
  98. QString VUtils::generateImageFileName(const QString &path,
  99. const QString &title,
  100. const QString &format)
  101. {
  102. QRegExp regExp("\\W");
  103. QString baseName(title.toLower());
  104. // Remove non-character chars.
  105. baseName.remove(regExp);
  106. // Constrain the length of the name.
  107. baseName.truncate(10);
  108. if (!baseName.isEmpty()) {
  109. baseName.prepend('_');
  110. }
  111. // Add current time and random number to make the name be most likely unique
  112. baseName = baseName + '_' + QString::number(QDateTime::currentDateTime().toTime_t());
  113. baseName = baseName + '_' + QString::number(qrand());
  114. QDir dir(path);
  115. QString imageName = baseName + "." + format.toLower();
  116. int index = 1;
  117. while (fileExists(dir, imageName, true)) {
  118. imageName = QString("%1_%2.%3").arg(baseName).arg(index++)
  119. .arg(format.toLower());
  120. }
  121. return imageName;
  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. // Used to de-duplicate the links. Url as the key.
  154. QSet<QString> fetchedLinks;
  155. QVector<VElementRegion> regions = fetchImageRegionsUsingParser(text);
  156. QRegExp regExp(c_imageLinkRegExp);
  157. QString basePath = p_file->fetchBasePath();
  158. for (int i = 0; i < regions.size(); ++i) {
  159. const VElementRegion &reg = regions[i];
  160. QString linkText = text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos);
  161. bool matched = regExp.exactMatch(linkText);
  162. if (!matched) {
  163. // Image links with reference format will not match.
  164. continue;
  165. }
  166. QString imageUrl = regExp.capturedTexts()[2].trimmed();
  167. ImageLink link;
  168. link.m_url = imageUrl;
  169. QFileInfo info(basePath, imageUrl);
  170. if (info.exists()) {
  171. if (info.isNativePath()) {
  172. // Local file.
  173. link.m_path = QDir::cleanPath(info.absoluteFilePath());
  174. if (QDir::isRelativePath(imageUrl)) {
  175. link.m_type = p_file->isInternalImageFolder(VUtils::basePathFromPath(link.m_path)) ?
  176. ImageLink::LocalRelativeInternal : ImageLink::LocalRelativeExternal;
  177. } else {
  178. link.m_type = ImageLink::LocalAbsolute;
  179. }
  180. } else {
  181. link.m_type = ImageLink::Resource;
  182. link.m_path = imageUrl;
  183. }
  184. } else {
  185. QUrl url(imageUrl);
  186. link.m_path = url.toString();
  187. link.m_type = ImageLink::Remote;
  188. }
  189. if (link.m_type & p_type) {
  190. if (!fetchedLinks.contains(link.m_url)) {
  191. fetchedLinks.insert(link.m_url);
  192. images.push_back(link);
  193. qDebug() << "fetch one image:" << link.m_type << link.m_path << link.m_url;
  194. }
  195. }
  196. }
  197. if (!isOpened) {
  198. p_file->close();
  199. }
  200. return images;
  201. }
  202. QString VUtils::imageLinkUrlToPath(const QString &p_basePath, const QString &p_url)
  203. {
  204. QString path;
  205. QFileInfo info(p_basePath, p_url);
  206. if (info.exists()) {
  207. if (info.isNativePath()) {
  208. // Local file.
  209. path = QDir::cleanPath(info.absoluteFilePath());
  210. } else {
  211. path = p_url;
  212. }
  213. } else {
  214. path = QUrl(p_url).toString();
  215. }
  216. return path;
  217. }
  218. bool VUtils::makePath(const QString &p_path)
  219. {
  220. if (p_path.isEmpty()) {
  221. return true;
  222. }
  223. bool ret = true;
  224. QDir dir;
  225. if (dir.mkpath(p_path)) {
  226. qDebug() << "make path" << p_path;
  227. } else {
  228. qWarning() << "fail to make path" << p_path;
  229. ret = false;
  230. }
  231. return ret;
  232. }
  233. QJsonObject VUtils::clipboardToJson()
  234. {
  235. QClipboard *clipboard = QApplication::clipboard();
  236. const QMimeData *mimeData = clipboard->mimeData();
  237. QJsonObject obj;
  238. if (mimeData->hasText()) {
  239. QString text = mimeData->text();
  240. obj = QJsonDocument::fromJson(text.toUtf8()).object();
  241. qDebug() << "Json object in clipboard" << obj;
  242. }
  243. return obj;
  244. }
  245. ClipboardOpType VUtils::operationInClipboard()
  246. {
  247. QJsonObject obj = clipboardToJson();
  248. if (obj.contains(ClipboardConfig::c_type)) {
  249. return (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
  250. }
  251. return ClipboardOpType::Invalid;
  252. }
  253. bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut)
  254. {
  255. QString srcPath = QDir::cleanPath(p_srcFilePath);
  256. QString destPath = QDir::cleanPath(p_destFilePath);
  257. if (srcPath == destPath) {
  258. return true;
  259. }
  260. QDir dir;
  261. if (!dir.mkpath(basePathFromPath(p_destFilePath))) {
  262. qWarning() << "fail to create directory" << basePathFromPath(p_destFilePath);
  263. return false;
  264. }
  265. if (p_isCut) {
  266. QFile file(srcPath);
  267. if (!file.rename(destPath)) {
  268. qWarning() << "fail to copy file" << srcPath << destPath;
  269. return false;
  270. }
  271. } else {
  272. if (!QFile::copy(srcPath, destPath)) {
  273. qWarning() << "fail to copy file" << srcPath << destPath;
  274. return false;
  275. }
  276. }
  277. return true;
  278. }
  279. bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
  280. {
  281. QString srcPath = QDir::cleanPath(p_srcDirPath);
  282. QString destPath = QDir::cleanPath(p_destDirPath);
  283. if (srcPath == destPath) {
  284. return true;
  285. }
  286. if (QFileInfo::exists(destPath)) {
  287. qWarning() << QString("target directory %1 already exists").arg(destPath);
  288. return false;
  289. }
  290. // QDir.rename() could not move directory across drives.
  291. // Make sure target directory exists.
  292. QDir destDir(destPath);
  293. if (!destDir.exists()) {
  294. if (!destDir.mkpath(destPath)) {
  295. qWarning() << QString("fail to create target directory %1").arg(destPath);
  296. return false;
  297. }
  298. }
  299. // Handle directory recursively.
  300. QDir srcDir(srcPath);
  301. Q_ASSERT(srcDir.exists() && destDir.exists());
  302. QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  303. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  304. for (int i = 0; i < nodes.size(); ++i) {
  305. const QFileInfo &fileInfo = nodes.at(i);
  306. QString name = fileInfo.fileName();
  307. if (fileInfo.isDir()) {
  308. if (!copyDirectory(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  309. return false;
  310. }
  311. } else {
  312. Q_ASSERT(fileInfo.isFile());
  313. if (!copyFile(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  314. return false;
  315. }
  316. }
  317. }
  318. if (p_isCut) {
  319. if (!destDir.rmdir(srcPath)) {
  320. qWarning() << QString("fail to delete source directory %1 after cut").arg(srcPath);
  321. return false;
  322. }
  323. }
  324. return true;
  325. }
  326. int VUtils::showMessage(QMessageBox::Icon p_icon,
  327. const QString &p_title,
  328. const QString &p_text,
  329. const QString &p_infoText,
  330. QMessageBox::StandardButtons p_buttons,
  331. QMessageBox::StandardButton p_defaultBtn,
  332. QWidget *p_parent,
  333. MessageBoxType p_type)
  334. {
  335. QMessageBox msgBox(p_icon, p_title, p_text, p_buttons, p_parent);
  336. msgBox.setInformativeText(p_infoText);
  337. msgBox.setDefaultButton(p_defaultBtn);
  338. if (p_type == MessageBoxType::Danger) {
  339. QPushButton *okBtn = dynamic_cast<QPushButton *>(msgBox.button(QMessageBox::Ok));
  340. if (okBtn) {
  341. setDynamicProperty(okBtn, "DangerBtn");
  342. }
  343. }
  344. QPushButton *defaultBtn = dynamic_cast<QPushButton *>(msgBox.button(p_defaultBtn));
  345. if (defaultBtn) {
  346. setDynamicProperty(defaultBtn, "SpecialBtn");
  347. }
  348. return msgBox.exec();
  349. }
  350. QString VUtils::generateCopiedFileName(const QString &p_dirPath,
  351. const QString &p_fileName,
  352. bool p_completeBaseName)
  353. {
  354. QDir dir(p_dirPath);
  355. if (!dir.exists() || !dir.exists(p_fileName)) {
  356. return p_fileName;
  357. }
  358. QFileInfo fi(p_fileName);
  359. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  360. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  361. int index = 0;
  362. QString fileName;
  363. do {
  364. QString seq;
  365. if (index > 0) {
  366. seq = QString("%1").arg(QString::number(index), 3, '0');
  367. }
  368. index++;
  369. fileName = QString("%1_copy%2").arg(baseName).arg(seq);
  370. if (!suffix.isEmpty()) {
  371. fileName = fileName + "." + suffix;
  372. }
  373. } while (fileExists(dir, fileName, true));
  374. return fileName;
  375. }
  376. QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
  377. {
  378. QDir dir(p_parentDirPath);
  379. QString name = p_dirName;
  380. QString dirPath = dir.filePath(name);
  381. int index = 0;
  382. while (QDir(dirPath).exists()) {
  383. QString seq;
  384. if (index > 0) {
  385. seq = QString::number(index);
  386. }
  387. index++;
  388. name = QString("%1_copy%2").arg(p_dirName).arg(seq);
  389. dirPath = dir.filePath(name);
  390. }
  391. return name;
  392. }
  393. const QVector<QPair<QString, QString>>& VUtils::getAvailableLanguages()
  394. {
  395. if (s_availableLanguages.isEmpty()) {
  396. initAvailableLanguage();
  397. }
  398. return s_availableLanguages;
  399. }
  400. bool VUtils::isValidLanguage(const QString &p_lang)
  401. {
  402. for (auto const &lang : getAvailableLanguages()) {
  403. if (lang.first == p_lang) {
  404. return true;
  405. }
  406. }
  407. return false;
  408. }
  409. bool VUtils::isImageURL(const QUrl &p_url)
  410. {
  411. QString urlStr;
  412. if (p_url.isLocalFile()) {
  413. urlStr = p_url.toLocalFile();
  414. } else {
  415. urlStr = p_url.toString();
  416. }
  417. return isImageURLText(urlStr);
  418. }
  419. bool VUtils::isImageURLText(const QString &p_url)
  420. {
  421. QFileInfo info(p_url);
  422. return QImageReader::supportedImageFormats().contains(info.suffix().toLower().toLatin1());
  423. }
  424. qreal VUtils::calculateScaleFactor()
  425. {
  426. // const qreal refHeight = 1152;
  427. // const qreal refWidth = 2048;
  428. const qreal refDpi = 96;
  429. qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
  430. qreal factor = dpi / refDpi;
  431. return factor < 1 ? 1 : factor;
  432. }
  433. bool VUtils::realEqual(qreal p_a, qreal p_b)
  434. {
  435. return std::abs(p_a - p_b) < 1e-8;
  436. }
  437. QChar VUtils::keyToChar(int p_key)
  438. {
  439. if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
  440. return QChar('a' + p_key - Qt::Key_A);
  441. }
  442. return QChar();
  443. }
  444. QString VUtils::getLocale()
  445. {
  446. QString locale = g_config->getLanguage();
  447. if (locale == "System" || !isValidLanguage(locale)) {
  448. locale = QLocale::system().name();
  449. }
  450. return locale;
  451. }
  452. void VUtils::sleepWait(int p_milliseconds)
  453. {
  454. if (p_milliseconds <= 0) {
  455. return;
  456. }
  457. QElapsedTimer t;
  458. t.start();
  459. while (t.elapsed() < p_milliseconds) {
  460. QCoreApplication::processEvents();
  461. }
  462. }
  463. DocType VUtils::docTypeFromName(const QString &p_name)
  464. {
  465. if (p_name.isEmpty()) {
  466. return DocType::Unknown;
  467. }
  468. const QHash<int, QList<QString>> &suffixes = g_config->getDocSuffixes();
  469. QString suf = QFileInfo(p_name).suffix().toLower();
  470. for (auto it = suffixes.begin(); it != suffixes.end(); ++it) {
  471. if (it.value().contains(suf)) {
  472. return DocType(it.key());
  473. }
  474. }
  475. return DocType::Unknown;
  476. }
  477. QString VUtils::generateSimpleHtmlTemplate(const QString &p_body)
  478. {
  479. QString html(VNote::s_simpleHtmlTemplate);
  480. return html.replace(HtmlHolder::c_bodyHolder, p_body);
  481. }
  482. QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType)
  483. {
  484. return generateHtmlTemplate(VNote::s_markdownTemplate, p_conType);
  485. }
  486. QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType,
  487. const QString &p_renderBg,
  488. const QString &p_renderStyle,
  489. const QString &p_renderCodeBlockStyle,
  490. bool p_isPDF)
  491. {
  492. QString templ = VNote::generateHtmlTemplate(g_config->getRenderBackgroundColor(p_renderBg),
  493. g_config->getCssStyleUrl(p_renderStyle),
  494. g_config->getCodeBlockCssStyleUrl(p_renderCodeBlockStyle),
  495. p_isPDF);
  496. return generateHtmlTemplate(templ, p_conType);
  497. }
  498. QString VUtils::generateHtmlTemplate(const QString &p_template,
  499. MarkdownConverterType p_conType)
  500. {
  501. QString jsFile, extraFile;
  502. switch (p_conType) {
  503. case MarkdownConverterType::Marked:
  504. jsFile = "qrc" + VNote::c_markedJsFile;
  505. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  506. break;
  507. case MarkdownConverterType::Hoedown:
  508. jsFile = "qrc" + VNote::c_hoedownJsFile;
  509. // Use Marked to highlight code blocks.
  510. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  511. break;
  512. case MarkdownConverterType::MarkdownIt:
  513. {
  514. jsFile = "qrc" + VNote::c_markdownitJsFile;
  515. extraFile = "<script src=\"qrc" + VNote::c_markdownitExtraFile + "\"></script>\n" +
  516. "<script src=\"qrc" + VNote::c_markdownitAnchorExtraFile + "\"></script>\n" +
  517. "<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n" +
  518. /*
  519. "<script src=\"qrc" + VNote::c_markdownitSubExtraFile + "\"></script>\n" +
  520. "<script src=\"qrc" + VNote::c_markdownitSupExtraFile + "\"></script>\n" +
  521. */
  522. "<script src=\"qrc" + VNote::c_markdownitFootnoteExtraFile + "\"></script>\n";
  523. MarkdownitOption opt = g_config->getMarkdownitOption();
  524. QString optJs = QString("<script>var VMarkdownitOption = {"
  525. "html: %1, breaks: %2, linkify: %3};"
  526. "</script>\n")
  527. .arg(opt.m_html ? "true" : "false")
  528. .arg(opt.m_breaks ? "true" : "false")
  529. .arg(opt.m_linkify ? "true" : "false");
  530. extraFile += optJs;
  531. break;
  532. }
  533. case MarkdownConverterType::Showdown:
  534. jsFile = "qrc" + VNote::c_showdownJsFile;
  535. extraFile = "<script src=\"qrc" + VNote::c_showdownExtraFile + "\"></script>\n" +
  536. "<script src=\"qrc" + VNote::c_showdownAnchorExtraFile + "\"></script>\n";
  537. break;
  538. default:
  539. Q_ASSERT(false);
  540. }
  541. if (g_config->getEnableMermaid()) {
  542. extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"qrc" + VNote::c_mermaidCssFile + "\"/>\n" +
  543. "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n" +
  544. "<script>var VEnableMermaid = true;</script>\n";
  545. }
  546. if (g_config->getEnableFlowchart()) {
  547. extraFile += "<script src=\"qrc" + VNote::c_raphaelJsFile + "\"></script>\n" +
  548. "<script src=\"qrc" + VNote::c_flowchartJsFile + "\"></script>\n" +
  549. "<script>var VEnableFlowchart = true;</script>\n";
  550. }
  551. if (g_config->getEnableMathjax()) {
  552. extraFile += "<script type=\"text/x-mathjax-config\">"
  553. "MathJax.Hub.Config({\n"
  554. " tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n"
  555. "processEscapes: true,\n"
  556. "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
  557. " showProcessingMessages: false,\n"
  558. " messageStyle: \"none\"});\n"
  559. "</script>\n"
  560. "<script type=\"text/javascript\" async src=\"" + g_config->getMathjaxJavascript() + "\"></script>\n" +
  561. "<script>var VEnableMathjax = true;</script>\n";
  562. }
  563. if (g_config->getEnableImageCaption()) {
  564. extraFile += "<script>var VEnableImageCaption = true;</script>\n";
  565. }
  566. if (g_config->getEnableCodeBlockLineNumber()) {
  567. extraFile += "<script src=\"qrc" + VNote::c_highlightjsLineNumberExtraFile + "\"></script>\n" +
  568. "<script>var VEnableHighlightLineNumber = true;</script>\n";
  569. }
  570. extraFile += "<script>var VStylesToInline = '" + g_config->getStylesToInlineWhenCopied() + "';</script>\n";
  571. QString htmlTemplate(p_template);
  572. htmlTemplate.replace(HtmlHolder::c_JSHolder, jsFile);
  573. if (!extraFile.isEmpty()) {
  574. htmlTemplate.replace(HtmlHolder::c_extraHolder, extraFile);
  575. }
  576. return htmlTemplate;
  577. }
  578. QString VUtils::getFileNameWithSequence(const QString &p_directory,
  579. const QString &p_baseFileName,
  580. bool p_completeBaseName)
  581. {
  582. QDir dir(p_directory);
  583. if (!dir.exists() || !dir.exists(p_baseFileName)) {
  584. return p_baseFileName;
  585. }
  586. // Append a sequence.
  587. QFileInfo fi(p_baseFileName);
  588. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  589. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  590. int seq = 1;
  591. QString fileName;
  592. do {
  593. fileName = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
  594. if (!suffix.isEmpty()) {
  595. fileName = fileName + "." + suffix;
  596. }
  597. } while (fileExists(dir, fileName, true));
  598. return fileName;
  599. }
  600. QString VUtils::getDirNameWithSequence(const QString &p_directory,
  601. const QString &p_baseDirName)
  602. {
  603. QDir dir(p_directory);
  604. if (!dir.exists() || !dir.exists(p_baseDirName)) {
  605. return p_baseDirName;
  606. }
  607. // Append a sequence.
  608. int seq = 1;
  609. QString fileName;
  610. do {
  611. fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
  612. } while (fileExists(dir, fileName, true));
  613. return fileName;
  614. }
  615. QString VUtils::getRandomFileName(const QString &p_directory)
  616. {
  617. Q_ASSERT(!p_directory.isEmpty());
  618. QString name;
  619. QDir dir(p_directory);
  620. do {
  621. name = QString::number(QDateTime::currentDateTimeUtc().toTime_t());
  622. name = name + '_' + QString::number(qrand());
  623. } while (fileExists(dir, name, true));
  624. return name;
  625. }
  626. bool VUtils::checkPathLegal(const QString &p_path)
  627. {
  628. // Ensure every part of the p_path is a valid file name until we come to
  629. // an existing parent directory.
  630. if (p_path.isEmpty()) {
  631. return false;
  632. }
  633. if (QFileInfo::exists(p_path)) {
  634. #if defined(Q_OS_WIN)
  635. // On Windows, "/" and ":" will also make exists() return true.
  636. if (p_path.startsWith('/') || p_path == ":") {
  637. return false;
  638. }
  639. #endif
  640. return true;
  641. }
  642. bool ret = false;
  643. int pos;
  644. QString basePath = basePathFromPath(p_path);
  645. QString fileName = fileNameFromPath(p_path);
  646. QValidator *validator = new QRegExpValidator(QRegExp(c_fileNameRegExp));
  647. while (!fileName.isEmpty()) {
  648. QValidator::State validFile = validator->validate(fileName, pos);
  649. if (validFile != QValidator::Acceptable) {
  650. break;
  651. }
  652. if (QFileInfo::exists(basePath)) {
  653. ret = true;
  654. #if defined(Q_OS_WIN)
  655. // On Windows, "/" and ":" will also make exists() return true.
  656. if (basePath.startsWith('/') || basePath == ":") {
  657. ret = false;
  658. }
  659. #endif
  660. break;
  661. }
  662. fileName = fileNameFromPath(basePath);
  663. basePath = basePathFromPath(basePath);
  664. }
  665. delete validator;
  666. return ret;
  667. }
  668. bool VUtils::checkFileNameLegal(const QString &p_name)
  669. {
  670. if (p_name.isEmpty()) {
  671. return false;
  672. }
  673. QRegExp exp(c_fileNameRegExp);
  674. return exp.exactMatch(p_name);
  675. }
  676. bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
  677. {
  678. QString a = QDir::cleanPath(p_patha);
  679. QString b = QDir::cleanPath(p_pathb);
  680. #if defined(Q_OS_WIN)
  681. a = a.toLower();
  682. b = b.toLower();
  683. #endif
  684. return a == b;
  685. }
  686. bool VUtils::splitPathInBasePath(const QString &p_base,
  687. const QString &p_path,
  688. QStringList &p_parts)
  689. {
  690. p_parts.clear();
  691. QString a = QDir::cleanPath(p_base);
  692. QString b = QDir::cleanPath(p_path);
  693. #if defined(Q_OS_WIN)
  694. if (!b.toLower().startsWith(a.toLower())) {
  695. return false;
  696. }
  697. #else
  698. if (!b.startsWith(a)) {
  699. return false;
  700. }
  701. #endif
  702. if (a.size() == b.size()) {
  703. return true;
  704. }
  705. Q_ASSERT(a.size() < b.size());
  706. if (b.at(a.size()) != '/') {
  707. return false;
  708. }
  709. p_parts = b.right(b.size() - a.size() - 1).split("/", QString::SkipEmptyParts);
  710. qDebug() << QString("split path %1 based on %2 to %3 parts").arg(p_path).arg(p_base).arg(p_parts.size());
  711. return true;
  712. }
  713. void VUtils::decodeUrl(QString &p_url)
  714. {
  715. QHash<QString, QString> maps;
  716. maps.insert("%20", " ");
  717. for (auto it = maps.begin(); it != maps.end(); ++it) {
  718. p_url.replace(it.key(), it.value());
  719. }
  720. }
  721. QString VUtils::getShortcutText(const QString &p_keySeq)
  722. {
  723. return QKeySequence(p_keySeq).toString(QKeySequence::NativeText);
  724. }
  725. bool VUtils::deleteDirectory(const VNotebook *p_notebook,
  726. const QString &p_path,
  727. bool p_skipRecycleBin)
  728. {
  729. if (p_skipRecycleBin) {
  730. QDir dir(p_path);
  731. return dir.removeRecursively();
  732. } else {
  733. // Move it to the recycle bin folder.
  734. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  735. }
  736. }
  737. bool VUtils::deleteDirectory(const QString &p_path)
  738. {
  739. if (p_path.isEmpty()) {
  740. return true;
  741. }
  742. QDir dir(p_path);
  743. return dir.removeRecursively();
  744. }
  745. bool VUtils::emptyDirectory(const VNotebook *p_notebook,
  746. const QString &p_path,
  747. bool p_skipRecycleBin)
  748. {
  749. QDir dir(p_path);
  750. if (!dir.exists()) {
  751. return true;
  752. }
  753. QFileInfoList nodes = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  754. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  755. for (int i = 0; i < nodes.size(); ++i) {
  756. const QFileInfo &fileInfo = nodes.at(i);
  757. if (fileInfo.isDir()) {
  758. if (!deleteDirectory(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  759. return false;
  760. }
  761. } else {
  762. Q_ASSERT(fileInfo.isFile());
  763. if (!deleteFile(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  764. return false;
  765. }
  766. }
  767. }
  768. return true;
  769. }
  770. bool VUtils::deleteFile(const VNotebook *p_notebook,
  771. const QString &p_path,
  772. bool p_skipRecycleBin)
  773. {
  774. if (p_skipRecycleBin) {
  775. QFile file(p_path);
  776. return file.remove();
  777. } else {
  778. // Move it to the recycle bin folder.
  779. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  780. }
  781. }
  782. bool VUtils::deleteFile(const QString &p_path)
  783. {
  784. QFile file(p_path);
  785. bool ret = file.remove();
  786. if (ret) {
  787. qDebug() << "deleted file" << p_path;
  788. } else {
  789. qWarning() << "fail to delete file" << p_path;
  790. }
  791. return ret;
  792. }
  793. bool VUtils::deleteFile(const VOrphanFile *p_file,
  794. const QString &p_path,
  795. bool p_skipRecycleBin)
  796. {
  797. if (p_skipRecycleBin) {
  798. QFile file(p_path);
  799. return file.remove();
  800. } else {
  801. // Move it to the recycle bin folder.
  802. return deleteFile(p_file->fetchRecycleBinFolderPath(), p_path);
  803. }
  804. }
  805. static QString getRecycleBinSubFolderToUse(const QString &p_folderPath)
  806. {
  807. QDir dir(p_folderPath);
  808. return QDir::cleanPath(dir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd")));
  809. }
  810. bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
  811. const QString &p_path)
  812. {
  813. QString binPath = getRecycleBinSubFolderToUse(p_recycleBinFolderPath);
  814. QDir binDir(binPath);
  815. if (!binDir.exists()) {
  816. binDir.mkpath(binPath);
  817. if (!binDir.exists()) {
  818. return false;
  819. }
  820. }
  821. QString destName = getFileNameWithSequence(binPath,
  822. fileNameFromPath(p_path),
  823. true);
  824. qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
  825. if (!binDir.rename(p_path, binDir.filePath(destName))) {
  826. qWarning() << "fail to move" << p_path << "to" << binDir.filePath(destName);
  827. return false;
  828. }
  829. return true;
  830. }
  831. QVector<VElementRegion> VUtils::fetchImageRegionsUsingParser(const QString &p_content)
  832. {
  833. Q_ASSERT(!p_content.isEmpty());
  834. QVector<VElementRegion> regs;
  835. QByteArray ba = p_content.toUtf8();
  836. const char *data = (const char *)ba.data();
  837. int len = ba.size();
  838. pmh_element **result = NULL;
  839. char *content = new char[len + 1];
  840. memcpy(content, data, len);
  841. content[len] = '\0';
  842. pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
  843. if (!result) {
  844. return regs;
  845. }
  846. pmh_element *elem = result[pmh_IMAGE];
  847. while (elem != NULL) {
  848. if (elem->end <= elem->pos) {
  849. elem = elem->next;
  850. continue;
  851. }
  852. regs.push_back(VElementRegion(elem->pos, elem->end));
  853. elem = elem->next;
  854. }
  855. pmh_free_elements(result);
  856. return regs;
  857. }
  858. QString VUtils::displayDateTime(const QDateTime &p_dateTime)
  859. {
  860. QString res = p_dateTime.date().toString(Qt::DefaultLocaleLongDate);
  861. res += " " + p_dateTime.time().toString();
  862. return res;
  863. }
  864. bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive)
  865. {
  866. if (!p_forceCaseInsensitive) {
  867. return p_dir.exists(p_name);
  868. }
  869. QString name = p_name.toLower();
  870. QStringList names = p_dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden
  871. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  872. foreach (const QString &str, names) {
  873. if (str.toLower() == name) {
  874. return true;
  875. }
  876. }
  877. return false;
  878. }
  879. void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
  880. {
  881. if (!p_msg) {
  882. return;
  883. }
  884. if (p_msg->isEmpty()) {
  885. *p_msg = p_str;
  886. } else {
  887. *p_msg = *p_msg + '\n' + p_str;
  888. }
  889. }
  890. QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
  891. {
  892. QStringList paths;
  893. for (int i = 0; i < p_files.size(); ++i) {
  894. if (p_files[i].startsWith('-')) {
  895. continue;
  896. }
  897. QString path = validFilePathToOpen(p_files[i]);
  898. if (!path.isEmpty()) {
  899. paths.append(path);
  900. }
  901. }
  902. return paths;
  903. }
  904. QString VUtils::validFilePathToOpen(const QString &p_file)
  905. {
  906. if (QFileInfo::exists(p_file)) {
  907. QFileInfo fi(p_file);
  908. if (fi.isFile()) {
  909. // Need to use absolute path here since VNote may be launched
  910. // in different working directory.
  911. return QDir::cleanPath(fi.absoluteFilePath());
  912. }
  913. }
  914. return QString();
  915. }
  916. bool VUtils::isControlModifierForVim(int p_modifiers)
  917. {
  918. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  919. return p_modifiers == Qt::MetaModifier;
  920. #else
  921. return p_modifiers == Qt::ControlModifier;
  922. #endif
  923. }
  924. void VUtils::touchFile(const QString &p_file)
  925. {
  926. QFile file(p_file);
  927. if (!file.open(QIODevice::WriteOnly)) {
  928. qWarning() << "fail to touch file" << p_file;
  929. return;
  930. }
  931. file.close();
  932. }
  933. bool VUtils::isMetaKey(int p_key)
  934. {
  935. return p_key == Qt::Key_Control
  936. || p_key == Qt::Key_Shift
  937. || p_key == Qt::Key_Meta
  938. || p_key == Qt::Key_Alt;
  939. }
  940. QComboBox *VUtils::getComboBox(QWidget *p_parent)
  941. {
  942. QComboBox *box = new QComboBox(p_parent);
  943. QStyledItemDelegate *itemDelegate = new QStyledItemDelegate(box);
  944. box->setItemDelegate(itemDelegate);
  945. return box;
  946. }
  947. void VUtils::setDynamicProperty(QWidget *p_widget, const char *p_prop, bool p_val)
  948. {
  949. p_widget->setProperty(p_prop, p_val);
  950. p_widget->style()->unpolish(p_widget);
  951. p_widget->style()->polish(p_widget);
  952. }
  953. QWebEngineView *VUtils::getWebEngineView(QWidget *p_parent)
  954. {
  955. QWebEngineView *viewer = new QWebEngineView(p_parent);
  956. VPreviewPage *page = new VPreviewPage(viewer);
  957. page->setBackgroundColor(Qt::transparent);
  958. viewer->setPage(page);
  959. viewer->setZoomFactor(g_config->getWebZoomFactor());
  960. return viewer;
  961. }
  962. QString VUtils::getFileNameWithLocale(const QString &p_name, const QString &p_locale)
  963. {
  964. QString locale = p_locale.isEmpty() ? getLocale() : p_locale;
  965. locale = locale.split('_')[0];
  966. QFileInfo fi(p_name);
  967. QString baseName = fi.completeBaseName();
  968. QString suffix = fi.suffix();
  969. if (suffix.isEmpty()) {
  970. return QString("%1_%2").arg(baseName).arg(locale);
  971. } else {
  972. return QString("%1_%2.%3").arg(baseName).arg(locale).arg(suffix);
  973. }
  974. }
  975. QString VUtils::getDocFile(const QString &p_name)
  976. {
  977. QDir dir(VNote::c_docFileFolder);
  978. QString name(getFileNameWithLocale(p_name));
  979. if (!dir.exists(name)) {
  980. name = getFileNameWithLocale(p_name, "en_US");
  981. }
  982. return dir.filePath(name);
  983. }
  984. QString VUtils::getCaptainShortcutSequenceText(const QString &p_operation)
  985. {
  986. QString capKey = g_config->getShortcutKeySequence("CaptainMode");
  987. QString sec = g_config->getCaptainShortcutKeySequence(p_operation);
  988. QKeySequence seq(capKey + "," + sec);
  989. if (!seq.isEmpty()) {
  990. return seq.toString(QKeySequence::NativeText);
  991. }
  992. return QString();
  993. }
  994. QString VUtils::getAvailableFontFamily(const QStringList &p_families)
  995. {
  996. QStringList availFamilies = QFontDatabase().families();
  997. for (int i = 0; i < p_families.size(); ++i) {
  998. QString family = p_families[i].trimmed();
  999. if (family.isEmpty()) {
  1000. continue;
  1001. }
  1002. for (int j = 0; j < availFamilies.size(); ++j) {
  1003. QString availFamily = availFamilies[j];
  1004. availFamily.remove(QRegExp("\\[.*\\]"));
  1005. availFamily = availFamily.trimmed();
  1006. if (family == availFamily
  1007. || family.toLower() == availFamily.toLower()) {
  1008. qDebug() << "matched font family" << availFamilies[j];
  1009. return availFamilies[j];
  1010. }
  1011. }
  1012. }
  1013. return QString();
  1014. }