vutils.cpp 36 KB

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