vutils.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  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::generateExportHtmlTemplate(const QString &p_renderBg)
  579. {
  580. return VNote::generateExportHtmlTemplate(g_config->getRenderBackgroundColor(p_renderBg));
  581. }
  582. QString VUtils::getFileNameWithSequence(const QString &p_directory,
  583. const QString &p_baseFileName,
  584. bool p_completeBaseName)
  585. {
  586. QDir dir(p_directory);
  587. if (!dir.exists() || !dir.exists(p_baseFileName)) {
  588. return p_baseFileName;
  589. }
  590. // Append a sequence.
  591. QFileInfo fi(p_baseFileName);
  592. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  593. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  594. int seq = 1;
  595. QString fileName;
  596. do {
  597. fileName = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
  598. if (!suffix.isEmpty()) {
  599. fileName = fileName + "." + suffix;
  600. }
  601. } while (fileExists(dir, fileName, true));
  602. return fileName;
  603. }
  604. QString VUtils::getDirNameWithSequence(const QString &p_directory,
  605. const QString &p_baseDirName)
  606. {
  607. QDir dir(p_directory);
  608. if (!dir.exists() || !dir.exists(p_baseDirName)) {
  609. return p_baseDirName;
  610. }
  611. // Append a sequence.
  612. int seq = 1;
  613. QString fileName;
  614. do {
  615. fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
  616. } while (fileExists(dir, fileName, true));
  617. return fileName;
  618. }
  619. QString VUtils::getRandomFileName(const QString &p_directory)
  620. {
  621. Q_ASSERT(!p_directory.isEmpty());
  622. QString name;
  623. QDir dir(p_directory);
  624. do {
  625. name = QString::number(QDateTime::currentDateTimeUtc().toTime_t());
  626. name = name + '_' + QString::number(qrand());
  627. } while (fileExists(dir, name, true));
  628. return name;
  629. }
  630. bool VUtils::checkPathLegal(const QString &p_path)
  631. {
  632. // Ensure every part of the p_path is a valid file name until we come to
  633. // an existing parent directory.
  634. if (p_path.isEmpty()) {
  635. return false;
  636. }
  637. if (QFileInfo::exists(p_path)) {
  638. #if defined(Q_OS_WIN)
  639. // On Windows, "/" and ":" will also make exists() return true.
  640. if (p_path.startsWith('/') || p_path == ":") {
  641. return false;
  642. }
  643. #endif
  644. return true;
  645. }
  646. bool ret = false;
  647. int pos;
  648. QString basePath = basePathFromPath(p_path);
  649. QString fileName = fileNameFromPath(p_path);
  650. QValidator *validator = new QRegExpValidator(QRegExp(c_fileNameRegExp));
  651. while (!fileName.isEmpty()) {
  652. QValidator::State validFile = validator->validate(fileName, pos);
  653. if (validFile != QValidator::Acceptable) {
  654. break;
  655. }
  656. if (QFileInfo::exists(basePath)) {
  657. ret = true;
  658. #if defined(Q_OS_WIN)
  659. // On Windows, "/" and ":" will also make exists() return true.
  660. if (basePath.startsWith('/') || basePath == ":") {
  661. ret = false;
  662. }
  663. #endif
  664. break;
  665. }
  666. fileName = fileNameFromPath(basePath);
  667. basePath = basePathFromPath(basePath);
  668. }
  669. delete validator;
  670. return ret;
  671. }
  672. bool VUtils::checkFileNameLegal(const QString &p_name)
  673. {
  674. if (p_name.isEmpty()) {
  675. return false;
  676. }
  677. QRegExp exp(c_fileNameRegExp);
  678. return exp.exactMatch(p_name);
  679. }
  680. bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
  681. {
  682. QString a = QDir::cleanPath(p_patha);
  683. QString b = QDir::cleanPath(p_pathb);
  684. #if defined(Q_OS_WIN)
  685. a = a.toLower();
  686. b = b.toLower();
  687. #endif
  688. return a == b;
  689. }
  690. bool VUtils::splitPathInBasePath(const QString &p_base,
  691. const QString &p_path,
  692. QStringList &p_parts)
  693. {
  694. p_parts.clear();
  695. QString a = QDir::cleanPath(p_base);
  696. QString b = QDir::cleanPath(p_path);
  697. #if defined(Q_OS_WIN)
  698. if (!b.toLower().startsWith(a.toLower())) {
  699. return false;
  700. }
  701. #else
  702. if (!b.startsWith(a)) {
  703. return false;
  704. }
  705. #endif
  706. if (a.size() == b.size()) {
  707. return true;
  708. }
  709. Q_ASSERT(a.size() < b.size());
  710. if (b.at(a.size()) != '/') {
  711. return false;
  712. }
  713. p_parts = b.right(b.size() - a.size() - 1).split("/", QString::SkipEmptyParts);
  714. qDebug() << QString("split path %1 based on %2 to %3 parts").arg(p_path).arg(p_base).arg(p_parts.size());
  715. return true;
  716. }
  717. void VUtils::decodeUrl(QString &p_url)
  718. {
  719. QHash<QString, QString> maps;
  720. maps.insert("%20", " ");
  721. for (auto it = maps.begin(); it != maps.end(); ++it) {
  722. p_url.replace(it.key(), it.value());
  723. }
  724. }
  725. QString VUtils::getShortcutText(const QString &p_keySeq)
  726. {
  727. return QKeySequence(p_keySeq).toString(QKeySequence::NativeText);
  728. }
  729. bool VUtils::deleteDirectory(const VNotebook *p_notebook,
  730. const QString &p_path,
  731. bool p_skipRecycleBin)
  732. {
  733. if (p_skipRecycleBin) {
  734. QDir dir(p_path);
  735. return dir.removeRecursively();
  736. } else {
  737. // Move it to the recycle bin folder.
  738. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  739. }
  740. }
  741. bool VUtils::deleteDirectory(const QString &p_path)
  742. {
  743. if (p_path.isEmpty()) {
  744. return true;
  745. }
  746. QDir dir(p_path);
  747. return dir.removeRecursively();
  748. }
  749. bool VUtils::emptyDirectory(const VNotebook *p_notebook,
  750. const QString &p_path,
  751. bool p_skipRecycleBin)
  752. {
  753. QDir dir(p_path);
  754. if (!dir.exists()) {
  755. return true;
  756. }
  757. QFileInfoList nodes = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  758. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  759. for (int i = 0; i < nodes.size(); ++i) {
  760. const QFileInfo &fileInfo = nodes.at(i);
  761. if (fileInfo.isDir()) {
  762. if (!deleteDirectory(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  763. return false;
  764. }
  765. } else {
  766. Q_ASSERT(fileInfo.isFile());
  767. if (!deleteFile(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  768. return false;
  769. }
  770. }
  771. }
  772. return true;
  773. }
  774. bool VUtils::deleteFile(const VNotebook *p_notebook,
  775. const QString &p_path,
  776. bool p_skipRecycleBin)
  777. {
  778. if (p_skipRecycleBin) {
  779. QFile file(p_path);
  780. return file.remove();
  781. } else {
  782. // Move it to the recycle bin folder.
  783. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  784. }
  785. }
  786. bool VUtils::deleteFile(const QString &p_path)
  787. {
  788. QFile file(p_path);
  789. bool ret = file.remove();
  790. if (ret) {
  791. qDebug() << "deleted file" << p_path;
  792. } else {
  793. qWarning() << "fail to delete file" << p_path;
  794. }
  795. return ret;
  796. }
  797. bool VUtils::deleteFile(const VOrphanFile *p_file,
  798. const QString &p_path,
  799. bool p_skipRecycleBin)
  800. {
  801. if (p_skipRecycleBin) {
  802. QFile file(p_path);
  803. return file.remove();
  804. } else {
  805. // Move it to the recycle bin folder.
  806. return deleteFile(p_file->fetchRecycleBinFolderPath(), p_path);
  807. }
  808. }
  809. static QString getRecycleBinSubFolderToUse(const QString &p_folderPath)
  810. {
  811. QDir dir(p_folderPath);
  812. return QDir::cleanPath(dir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd")));
  813. }
  814. bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
  815. const QString &p_path)
  816. {
  817. QString binPath = getRecycleBinSubFolderToUse(p_recycleBinFolderPath);
  818. QDir binDir(binPath);
  819. if (!binDir.exists()) {
  820. binDir.mkpath(binPath);
  821. if (!binDir.exists()) {
  822. return false;
  823. }
  824. }
  825. QString destName = getFileNameWithSequence(binPath,
  826. fileNameFromPath(p_path),
  827. true);
  828. qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
  829. if (!binDir.rename(p_path, binDir.filePath(destName))) {
  830. qWarning() << "fail to move" << p_path << "to" << binDir.filePath(destName);
  831. return false;
  832. }
  833. return true;
  834. }
  835. QVector<VElementRegion> VUtils::fetchImageRegionsUsingParser(const QString &p_content)
  836. {
  837. Q_ASSERT(!p_content.isEmpty());
  838. QVector<VElementRegion> regs;
  839. QByteArray ba = p_content.toUtf8();
  840. const char *data = (const char *)ba.data();
  841. int len = ba.size();
  842. pmh_element **result = NULL;
  843. char *content = new char[len + 1];
  844. memcpy(content, data, len);
  845. content[len] = '\0';
  846. pmh_markdown_to_elements(content, pmh_EXT_NONE, &result);
  847. if (!result) {
  848. return regs;
  849. }
  850. pmh_element *elem = result[pmh_IMAGE];
  851. while (elem != NULL) {
  852. if (elem->end <= elem->pos) {
  853. elem = elem->next;
  854. continue;
  855. }
  856. regs.push_back(VElementRegion(elem->pos, elem->end));
  857. elem = elem->next;
  858. }
  859. pmh_free_elements(result);
  860. return regs;
  861. }
  862. QString VUtils::displayDateTime(const QDateTime &p_dateTime)
  863. {
  864. QString res = p_dateTime.date().toString(Qt::DefaultLocaleLongDate);
  865. res += " " + p_dateTime.time().toString();
  866. return res;
  867. }
  868. bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive)
  869. {
  870. if (!p_forceCaseInsensitive) {
  871. return p_dir.exists(p_name);
  872. }
  873. QString name = p_name.toLower();
  874. QStringList names = p_dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden
  875. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  876. foreach (const QString &str, names) {
  877. if (str.toLower() == name) {
  878. return true;
  879. }
  880. }
  881. return false;
  882. }
  883. void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
  884. {
  885. if (!p_msg) {
  886. return;
  887. }
  888. if (p_msg->isEmpty()) {
  889. *p_msg = p_str;
  890. } else {
  891. *p_msg = *p_msg + '\n' + p_str;
  892. }
  893. }
  894. QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
  895. {
  896. QStringList paths;
  897. for (int i = 0; i < p_files.size(); ++i) {
  898. if (p_files[i].startsWith('-')) {
  899. continue;
  900. }
  901. QString path = validFilePathToOpen(p_files[i]);
  902. if (!path.isEmpty()) {
  903. paths.append(path);
  904. }
  905. }
  906. return paths;
  907. }
  908. QString VUtils::validFilePathToOpen(const QString &p_file)
  909. {
  910. if (QFileInfo::exists(p_file)) {
  911. QFileInfo fi(p_file);
  912. if (fi.isFile()) {
  913. // Need to use absolute path here since VNote may be launched
  914. // in different working directory.
  915. return QDir::cleanPath(fi.absoluteFilePath());
  916. }
  917. }
  918. return QString();
  919. }
  920. bool VUtils::isControlModifierForVim(int p_modifiers)
  921. {
  922. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  923. return p_modifiers == Qt::MetaModifier;
  924. #else
  925. return p_modifiers == Qt::ControlModifier;
  926. #endif
  927. }
  928. void VUtils::touchFile(const QString &p_file)
  929. {
  930. QFile file(p_file);
  931. if (!file.open(QIODevice::WriteOnly)) {
  932. qWarning() << "fail to touch file" << p_file;
  933. return;
  934. }
  935. file.close();
  936. }
  937. bool VUtils::isMetaKey(int p_key)
  938. {
  939. return p_key == Qt::Key_Control
  940. || p_key == Qt::Key_Shift
  941. || p_key == Qt::Key_Meta
  942. || p_key == Qt::Key_Alt;
  943. }
  944. QComboBox *VUtils::getComboBox(QWidget *p_parent)
  945. {
  946. QComboBox *box = new QComboBox(p_parent);
  947. QStyledItemDelegate *itemDelegate = new QStyledItemDelegate(box);
  948. box->setItemDelegate(itemDelegate);
  949. return box;
  950. }
  951. void VUtils::setDynamicProperty(QWidget *p_widget, const char *p_prop, bool p_val)
  952. {
  953. p_widget->setProperty(p_prop, p_val);
  954. p_widget->style()->unpolish(p_widget);
  955. p_widget->style()->polish(p_widget);
  956. }
  957. QWebEngineView *VUtils::getWebEngineView(QWidget *p_parent)
  958. {
  959. QWebEngineView *viewer = new QWebEngineView(p_parent);
  960. VPreviewPage *page = new VPreviewPage(viewer);
  961. page->setBackgroundColor(Qt::transparent);
  962. viewer->setPage(page);
  963. viewer->setZoomFactor(g_config->getWebZoomFactor());
  964. return viewer;
  965. }
  966. QString VUtils::getFileNameWithLocale(const QString &p_name, const QString &p_locale)
  967. {
  968. QString locale = p_locale.isEmpty() ? getLocale() : p_locale;
  969. locale = locale.split('_')[0];
  970. QFileInfo fi(p_name);
  971. QString baseName = fi.completeBaseName();
  972. QString suffix = fi.suffix();
  973. if (suffix.isEmpty()) {
  974. return QString("%1_%2").arg(baseName).arg(locale);
  975. } else {
  976. return QString("%1_%2.%3").arg(baseName).arg(locale).arg(suffix);
  977. }
  978. }
  979. QString VUtils::getDocFile(const QString &p_name)
  980. {
  981. QDir dir(VNote::c_docFileFolder);
  982. QString name(getFileNameWithLocale(p_name));
  983. if (!dir.exists(name)) {
  984. name = getFileNameWithLocale(p_name, "en_US");
  985. }
  986. return dir.filePath(name);
  987. }
  988. QString VUtils::getCaptainShortcutSequenceText(const QString &p_operation)
  989. {
  990. QString capKey = g_config->getShortcutKeySequence("CaptainMode");
  991. QString sec = g_config->getCaptainShortcutKeySequence(p_operation);
  992. QKeySequence seq(capKey + "," + sec);
  993. if (!seq.isEmpty()) {
  994. return seq.toString(QKeySequence::NativeText);
  995. }
  996. return QString();
  997. }
  998. QString VUtils::getAvailableFontFamily(const QStringList &p_families)
  999. {
  1000. QStringList availFamilies = QFontDatabase().families();
  1001. for (int i = 0; i < p_families.size(); ++i) {
  1002. QString family = p_families[i].trimmed();
  1003. if (family.isEmpty()) {
  1004. continue;
  1005. }
  1006. for (int j = 0; j < availFamilies.size(); ++j) {
  1007. QString availFamily = availFamilies[j];
  1008. availFamily.remove(QRegExp("\\[.*\\]"));
  1009. availFamily = availFamily.trimmed();
  1010. if (family == availFamily
  1011. || family.toLower() == availFamily.toLower()) {
  1012. qDebug() << "matched font family" << availFamilies[j];
  1013. return availFamilies[j];
  1014. }
  1015. }
  1016. }
  1017. return QString();
  1018. }