vutils.cpp 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772
  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 <QTreeWidgetItem>
  29. #include <QFormLayout>
  30. #include <QInputDialog>
  31. #include <QFontDatabase>
  32. #include <QSvgRenderer>
  33. #include <QPainter>
  34. #include "vorphanfile.h"
  35. #include "vnote.h"
  36. #include "vnotebook.h"
  37. #include "vpreviewpage.h"
  38. #include "pegparser.h"
  39. extern VConfigManager *g_config;
  40. QVector<QPair<QString, QString>> VUtils::s_availableLanguages;
  41. const QString VUtils::c_imageLinkRegExp = QString("\\!\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*"
  42. "((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*"
  43. "(=(\\d*)x(\\d*))?\\s*"
  44. "\\)");
  45. const QString VUtils::c_imageTitleRegExp = QString("[\\w\\(\\)@#%\\*\\-\\+=\\?<>\\,\\.\\s]*");
  46. const QString VUtils::c_linkRegExp = QString("\\[([^\\]]*)\\]\\(\\s*([^\\)\"'\\s]+)\\s*"
  47. "((\"[^\"\\)\\n]*\")|('[^'\\)\\n]*'))?\\s*"
  48. "\\)");
  49. const QString VUtils::c_fileNameRegExp = QString("(?:[^\\\\/:\\*\\?\"<>\\|\\s]| )*");
  50. const QString VUtils::c_fencedCodeBlockStartRegExp = QString("^(\\s*)```([^`\\s]*)\\s*[^`]*$");
  51. const QString VUtils::c_fencedCodeBlockEndRegExp = QString("^(\\s*)```$");
  52. const QString VUtils::c_previewImageBlockRegExp = QString("[\\n|^][ |\\t]*\\xfffc[ |\\t]*(?=\\n)");
  53. const QString VUtils::c_headerRegExp = QString("^(#{1,6})\\s+(((\\d+\\.)+(?=\\s))?\\s*(\\S.*)?)$");
  54. const QString VUtils::c_headerPrefixRegExp = QString("^(#{1,6}\\s+((\\d+\\.)+(?=\\s))?\\s*)($|(\\S.*)?$)");
  55. const QString VUtils::c_listRegExp = QString("^\\s*(-|\\*|\\d+\\.)\\s");
  56. const QString VUtils::c_blockQuoteRegExp = QString("^\\s*(\\>\\s?)");
  57. void VUtils::initAvailableLanguage()
  58. {
  59. if (!s_availableLanguages.isEmpty()) {
  60. return;
  61. }
  62. s_availableLanguages.append(QPair<QString, QString>("en_US", "English (US)"));
  63. s_availableLanguages.append(QPair<QString, QString>("zh_CN", "Chinese"));
  64. }
  65. QString VUtils::readFileFromDisk(const QString &filePath)
  66. {
  67. QFile file(filePath);
  68. if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
  69. qWarning() << "fail to open file" << filePath << "to read";
  70. return QString();
  71. }
  72. QString fileText(file.readAll());
  73. file.close();
  74. qDebug() << "read file content:" << filePath;
  75. return fileText;
  76. }
  77. bool VUtils::writeFileToDisk(const QString &p_filePath, const QString &p_text)
  78. {
  79. QFile file(p_filePath);
  80. if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
  81. qWarning() << "fail to open file" << p_filePath << "to write";
  82. return false;
  83. }
  84. QTextStream stream(&file);
  85. stream << p_text;
  86. file.close();
  87. qDebug() << "write file content:" << p_filePath;
  88. return true;
  89. }
  90. bool VUtils::writeFileToDisk(const QString &p_filePath, const QByteArray &p_data)
  91. {
  92. QFile file(p_filePath);
  93. if (!file.open(QIODevice::WriteOnly)) {
  94. qWarning() << "fail to open file" << p_filePath << "to write";
  95. return false;
  96. }
  97. file.write(p_data);
  98. file.close();
  99. qDebug() << "write file content:" << p_filePath;
  100. return true;
  101. }
  102. bool VUtils::writeJsonToDisk(const QString &p_filePath, const QJsonObject &p_json)
  103. {
  104. QFile file(p_filePath);
  105. // We use Unix LF for config file.
  106. if (!file.open(QIODevice::WriteOnly)) {
  107. qWarning() << "fail to open file" << p_filePath << "to write";
  108. return false;
  109. }
  110. QJsonDocument doc(p_json);
  111. if (-1 == file.write(doc.toJson())) {
  112. return false;
  113. }
  114. return true;
  115. }
  116. QJsonObject VUtils::readJsonFromDisk(const QString &p_filePath)
  117. {
  118. QFile file(p_filePath);
  119. if (!file.open(QIODevice::ReadOnly)) {
  120. qWarning() << "fail to open file" << p_filePath << "to read";
  121. return QJsonObject();
  122. }
  123. return QJsonDocument::fromJson(file.readAll()).object();
  124. }
  125. QString VUtils::generateImageFileName(const QString &path,
  126. const QString &title,
  127. const QString &format)
  128. {
  129. QRegExp regExp("\\W");
  130. QString baseName(title.toLower());
  131. // Remove non-character chars.
  132. baseName.remove(regExp);
  133. // Constrain the length of the name.
  134. baseName.truncate(10);
  135. baseName.prepend(g_config->getImageNamePrefix());
  136. if (!baseName.isEmpty()) {
  137. baseName.append('_');
  138. }
  139. // Add current time and random number to make the name be most likely unique
  140. baseName += QString::number(QDateTime::currentDateTime().toTime_t())
  141. + '_'
  142. + QString::number(qrand());
  143. QDir dir(path);
  144. QString imageName = baseName + "." + format.toLower();
  145. int index = 1;
  146. while (fileExists(dir, imageName, true)) {
  147. imageName = QString("%1_%2.%3").arg(baseName).arg(index++)
  148. .arg(format.toLower());
  149. }
  150. return imageName;
  151. }
  152. QString VUtils::fileNameFromPath(const QString &p_path)
  153. {
  154. if (p_path.isEmpty()) {
  155. return p_path;
  156. }
  157. return QFileInfo(QDir::cleanPath(p_path)).fileName();
  158. }
  159. QString VUtils::basePathFromPath(const QString &p_path)
  160. {
  161. if (p_path.isEmpty()) {
  162. return p_path;
  163. }
  164. return QFileInfo(QDir::cleanPath(p_path)).path();
  165. }
  166. QVector<ImageLink> VUtils::fetchImagesFromMarkdownFile(VFile *p_file,
  167. ImageLink::ImageLinkType p_type)
  168. {
  169. Q_ASSERT(p_file->getDocType() == DocType::Markdown);
  170. QVector<ImageLink> images;
  171. bool isOpened = p_file->isOpened();
  172. if (!isOpened && !p_file->open()) {
  173. return images;
  174. }
  175. const QString &text = p_file->getContent();
  176. if (text.isEmpty()) {
  177. if (!isOpened) {
  178. p_file->close();
  179. }
  180. return images;
  181. }
  182. // Used to de-duplicate the links. Url as the key.
  183. QSet<QString> fetchedLinks;
  184. QVector<VElementRegion> regions = fetchImageRegionsUsingParser(text);
  185. QRegExp regExp(c_imageLinkRegExp);
  186. QString basePath = p_file->fetchBasePath();
  187. for (int i = 0; i < regions.size(); ++i) {
  188. const VElementRegion &reg = regions[i];
  189. QString linkText = text.mid(reg.m_startPos, reg.m_endPos - reg.m_startPos);
  190. bool matched = regExp.exactMatch(linkText);
  191. if (!matched) {
  192. // Image links with reference format will not match.
  193. continue;
  194. }
  195. QString imageUrl = regExp.capturedTexts()[2].trimmed();
  196. ImageLink link;
  197. link.m_url = imageUrl;
  198. QFileInfo info(basePath, imageUrl);
  199. if (info.exists()) {
  200. if (info.isNativePath()) {
  201. // Local file.
  202. link.m_path = QDir::cleanPath(info.absoluteFilePath());
  203. if (QDir::isRelativePath(imageUrl)) {
  204. link.m_type = p_file->isInternalImageFolder(VUtils::basePathFromPath(link.m_path)) ?
  205. ImageLink::LocalRelativeInternal : ImageLink::LocalRelativeExternal;
  206. } else {
  207. link.m_type = ImageLink::LocalAbsolute;
  208. }
  209. } else {
  210. link.m_type = ImageLink::Resource;
  211. link.m_path = imageUrl;
  212. }
  213. } else {
  214. QUrl url(imageUrl);
  215. link.m_path = url.toString();
  216. link.m_type = ImageLink::Remote;
  217. }
  218. if (link.m_type & p_type) {
  219. if (!fetchedLinks.contains(link.m_url)) {
  220. fetchedLinks.insert(link.m_url);
  221. images.push_back(link);
  222. qDebug() << "fetch one image:" << link.m_type << link.m_path << link.m_url;
  223. }
  224. }
  225. }
  226. if (!isOpened) {
  227. p_file->close();
  228. }
  229. return images;
  230. }
  231. QString VUtils::linkUrlToPath(const QString &p_basePath, const QString &p_url)
  232. {
  233. QString fullPath;
  234. QFileInfo info(p_basePath, p_url);
  235. if (info.exists()) {
  236. if (info.isNativePath()) {
  237. // Local file.
  238. fullPath = QDir::cleanPath(info.absoluteFilePath());
  239. } else {
  240. fullPath = p_url;
  241. }
  242. } else {
  243. QString decodedUrl(p_url);
  244. VUtils::decodeUrl(decodedUrl);
  245. QFileInfo dinfo(p_basePath, decodedUrl);
  246. if (dinfo.exists()) {
  247. if (dinfo.isNativePath()) {
  248. // Local file.
  249. fullPath = QDir::cleanPath(dinfo.absoluteFilePath());
  250. } else {
  251. fullPath = p_url;
  252. }
  253. } else {
  254. QUrl url(p_url);
  255. if (url.isLocalFile()) {
  256. fullPath = url.toLocalFile();
  257. } else {
  258. fullPath = url.toString();
  259. }
  260. }
  261. }
  262. return fullPath;
  263. }
  264. bool VUtils::makePath(const QString &p_path)
  265. {
  266. if (p_path.isEmpty()) {
  267. return true;
  268. }
  269. bool ret = true;
  270. QDir dir;
  271. if (dir.mkpath(p_path)) {
  272. qDebug() << "make path" << p_path;
  273. } else {
  274. qWarning() << "fail to make path" << p_path;
  275. ret = false;
  276. }
  277. return ret;
  278. }
  279. QJsonObject VUtils::clipboardToJson()
  280. {
  281. QClipboard *clipboard = QApplication::clipboard();
  282. const QMimeData *mimeData = clipboard->mimeData();
  283. QJsonObject obj;
  284. if (mimeData->hasText()) {
  285. QString text = mimeData->text();
  286. obj = QJsonDocument::fromJson(text.toUtf8()).object();
  287. qDebug() << "Json object in clipboard" << obj;
  288. }
  289. return obj;
  290. }
  291. ClipboardOpType VUtils::operationInClipboard()
  292. {
  293. QJsonObject obj = clipboardToJson();
  294. if (obj.contains(ClipboardConfig::c_type)) {
  295. return (ClipboardOpType)obj[ClipboardConfig::c_type].toInt();
  296. }
  297. return ClipboardOpType::Invalid;
  298. }
  299. bool VUtils::copyFile(const QString &p_srcFilePath, const QString &p_destFilePath, bool p_isCut)
  300. {
  301. QString srcPath = QDir::cleanPath(p_srcFilePath);
  302. QString destPath = QDir::cleanPath(p_destFilePath);
  303. if (srcPath == destPath) {
  304. return true;
  305. }
  306. QDir dir;
  307. if (!dir.mkpath(basePathFromPath(p_destFilePath))) {
  308. qWarning() << "fail to create directory" << basePathFromPath(p_destFilePath);
  309. return false;
  310. }
  311. if (p_isCut) {
  312. QFile file(srcPath);
  313. if (!file.rename(destPath)) {
  314. qWarning() << "fail to copy file" << srcPath << destPath;
  315. return false;
  316. }
  317. } else {
  318. if (!QFile::copy(srcPath, destPath)) {
  319. qWarning() << "fail to copy file" << srcPath << destPath;
  320. return false;
  321. }
  322. }
  323. return true;
  324. }
  325. bool VUtils::copyDirectory(const QString &p_srcDirPath, const QString &p_destDirPath, bool p_isCut)
  326. {
  327. QString srcPath = QDir::cleanPath(p_srcDirPath);
  328. QString destPath = QDir::cleanPath(p_destDirPath);
  329. if (srcPath == destPath) {
  330. return true;
  331. }
  332. if (QFileInfo::exists(destPath)) {
  333. qWarning() << QString("target directory %1 already exists").arg(destPath);
  334. return false;
  335. }
  336. // QDir.rename() could not move directory across drives.
  337. // Make sure target directory exists.
  338. QDir destDir(destPath);
  339. if (!destDir.exists()) {
  340. if (!destDir.mkpath(destPath)) {
  341. qWarning() << QString("fail to create target directory %1").arg(destPath);
  342. return false;
  343. }
  344. }
  345. // Handle directory recursively.
  346. QDir srcDir(srcPath);
  347. Q_ASSERT(srcDir.exists() && destDir.exists());
  348. QFileInfoList nodes = srcDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  349. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  350. for (int i = 0; i < nodes.size(); ++i) {
  351. const QFileInfo &fileInfo = nodes.at(i);
  352. QString name = fileInfo.fileName();
  353. if (fileInfo.isDir()) {
  354. if (!copyDirectory(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  355. return false;
  356. }
  357. } else {
  358. Q_ASSERT(fileInfo.isFile());
  359. if (!copyFile(srcDir.filePath(name), destDir.filePath(name), p_isCut)) {
  360. return false;
  361. }
  362. }
  363. }
  364. if (p_isCut) {
  365. if (!destDir.rmdir(srcPath)) {
  366. qWarning() << QString("fail to delete source directory %1 after cut").arg(srcPath);
  367. return false;
  368. }
  369. }
  370. return true;
  371. }
  372. int VUtils::showMessage(QMessageBox::Icon p_icon,
  373. const QString &p_title,
  374. const QString &p_text,
  375. const QString &p_infoText,
  376. QMessageBox::StandardButtons p_buttons,
  377. QMessageBox::StandardButton p_defaultBtn,
  378. QWidget *p_parent,
  379. MessageBoxType p_type)
  380. {
  381. QMessageBox msgBox(p_icon, p_title, p_text, p_buttons, p_parent);
  382. msgBox.setInformativeText(p_infoText);
  383. msgBox.setDefaultButton(p_defaultBtn);
  384. if (p_type == MessageBoxType::Danger) {
  385. QPushButton *okBtn = dynamic_cast<QPushButton *>(msgBox.button(QMessageBox::Ok));
  386. if (okBtn) {
  387. setDynamicProperty(okBtn, "DangerBtn");
  388. }
  389. }
  390. QPushButton *defaultBtn = dynamic_cast<QPushButton *>(msgBox.button(p_defaultBtn));
  391. if (defaultBtn) {
  392. setDynamicProperty(defaultBtn, "SpecialBtn");
  393. }
  394. return msgBox.exec();
  395. }
  396. void VUtils::promptForReopen(QWidget *p_parent)
  397. {
  398. VUtils::showMessage(QMessageBox::Information,
  399. QObject::tr("Information"),
  400. QObject::tr("Please re-open current opened tabs to make it work."),
  401. "",
  402. QMessageBox::Ok,
  403. QMessageBox::Ok,
  404. p_parent);
  405. }
  406. QString VUtils::generateCopiedFileName(const QString &p_dirPath,
  407. const QString &p_fileName,
  408. bool p_completeBaseName)
  409. {
  410. QDir dir(p_dirPath);
  411. if (!dir.exists() || !dir.exists(p_fileName)) {
  412. return p_fileName;
  413. }
  414. QFileInfo fi(p_fileName);
  415. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  416. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  417. int index = 0;
  418. QString fileName;
  419. do {
  420. QString seq;
  421. if (index > 0) {
  422. seq = QString("%1").arg(QString::number(index), 3, '0');
  423. }
  424. index++;
  425. fileName = QString("%1_copy%2").arg(baseName).arg(seq);
  426. if (!suffix.isEmpty()) {
  427. fileName = fileName + "." + suffix;
  428. }
  429. } while (fileExists(dir, fileName, true));
  430. return fileName;
  431. }
  432. QString VUtils::generateCopiedDirName(const QString &p_parentDirPath, const QString &p_dirName)
  433. {
  434. QDir dir(p_parentDirPath);
  435. QString name = p_dirName;
  436. QString dirPath = dir.filePath(name);
  437. int index = 0;
  438. while (QDir(dirPath).exists()) {
  439. QString seq;
  440. if (index > 0) {
  441. seq = QString::number(index);
  442. }
  443. index++;
  444. name = QString("%1_copy%2").arg(p_dirName).arg(seq);
  445. dirPath = dir.filePath(name);
  446. }
  447. return name;
  448. }
  449. const QVector<QPair<QString, QString>>& VUtils::getAvailableLanguages()
  450. {
  451. if (s_availableLanguages.isEmpty()) {
  452. initAvailableLanguage();
  453. }
  454. return s_availableLanguages;
  455. }
  456. bool VUtils::isValidLanguage(const QString &p_lang)
  457. {
  458. for (auto const &lang : getAvailableLanguages()) {
  459. if (lang.first == p_lang) {
  460. return true;
  461. }
  462. }
  463. return false;
  464. }
  465. bool VUtils::isImageURL(const QUrl &p_url)
  466. {
  467. QString urlStr;
  468. if (p_url.isLocalFile()) {
  469. urlStr = p_url.toLocalFile();
  470. } else {
  471. urlStr = p_url.toString();
  472. }
  473. return isImageURLText(urlStr);
  474. }
  475. bool VUtils::isImageURLText(const QString &p_url)
  476. {
  477. QFileInfo info(p_url);
  478. return QImageReader::supportedImageFormats().contains(info.suffix().toLower().toLatin1());
  479. }
  480. qreal VUtils::calculateScaleFactor()
  481. {
  482. static qreal factor = -1;
  483. if (factor < 0) {
  484. const qreal refDpi = 96;
  485. qreal dpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
  486. factor = dpi / refDpi;
  487. if (factor < 1) {
  488. factor = 1;
  489. } else {
  490. // Keep only two digits after the dot.
  491. factor = (int)(factor * 100) / 100.0;
  492. }
  493. }
  494. return factor;
  495. }
  496. bool VUtils::realEqual(qreal p_a, qreal p_b)
  497. {
  498. return std::abs(p_a - p_b) < 1e-8;
  499. }
  500. QChar VUtils::keyToChar(int p_key)
  501. {
  502. if (p_key >= Qt::Key_A && p_key <= Qt::Key_Z) {
  503. return QChar('a' + p_key - Qt::Key_A);
  504. }
  505. return QChar();
  506. }
  507. QString VUtils::getLocale()
  508. {
  509. QString locale = g_config->getLanguage();
  510. if (locale == "System" || !isValidLanguage(locale)) {
  511. locale = QLocale::system().name();
  512. }
  513. return locale;
  514. }
  515. void VUtils::sleepWait(int p_milliseconds)
  516. {
  517. if (p_milliseconds <= 0) {
  518. return;
  519. }
  520. QElapsedTimer t;
  521. t.start();
  522. while (t.elapsed() < p_milliseconds) {
  523. QCoreApplication::processEvents();
  524. }
  525. }
  526. DocType VUtils::docTypeFromName(const QString &p_name)
  527. {
  528. if (p_name.isEmpty()) {
  529. return DocType::Unknown;
  530. }
  531. const QHash<int, QList<QString>> &suffixes = g_config->getDocSuffixes();
  532. QString suf = QFileInfo(p_name).suffix().toLower();
  533. for (auto it = suffixes.begin(); it != suffixes.end(); ++it) {
  534. if (it.value().contains(suf)) {
  535. return DocType(it.key());
  536. }
  537. }
  538. return DocType::Unknown;
  539. }
  540. QString VUtils::generateSimpleHtmlTemplate(const QString &p_body)
  541. {
  542. QString html(VNote::s_simpleHtmlTemplate);
  543. return html.replace(HtmlHolder::c_bodyHolder, p_body);
  544. }
  545. QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType)
  546. {
  547. return generateHtmlTemplate(VNote::s_markdownTemplate, p_conType);
  548. }
  549. QString VUtils::generateHtmlTemplate(MarkdownConverterType p_conType,
  550. const QString &p_renderBg,
  551. const QString &p_renderStyle,
  552. const QString &p_renderCodeBlockStyle,
  553. bool p_isPDF,
  554. bool p_wkhtmltopdf,
  555. bool p_addToc)
  556. {
  557. Q_ASSERT((p_isPDF && p_wkhtmltopdf) || !p_wkhtmltopdf);
  558. QString templ = VNote::generateHtmlTemplate(g_config->getRenderBackgroundColor(p_renderBg),
  559. g_config->getCssStyleUrl(p_renderStyle),
  560. g_config->getCodeBlockCssStyleUrl(p_renderCodeBlockStyle),
  561. p_isPDF);
  562. return generateHtmlTemplate(templ, p_conType, p_isPDF, p_wkhtmltopdf, p_addToc);
  563. }
  564. QString VUtils::generateHtmlTemplate(const QString &p_template,
  565. MarkdownConverterType p_conType,
  566. bool p_isPDF,
  567. bool p_wkhtmltopdf,
  568. bool p_addToc)
  569. {
  570. bool mathjaxTypeSetOnLoad = true;
  571. QString jsFile, extraFile;
  572. switch (p_conType) {
  573. case MarkdownConverterType::Marked:
  574. jsFile = "qrc" + VNote::c_markedJsFile;
  575. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  576. break;
  577. case MarkdownConverterType::Hoedown:
  578. jsFile = "qrc" + VNote::c_hoedownJsFile;
  579. // Use Marked to highlight code blocks.
  580. extraFile = "<script src=\"qrc" + VNote::c_markedExtraFile + "\"></script>\n";
  581. break;
  582. case MarkdownConverterType::MarkdownIt:
  583. {
  584. jsFile = "qrc" + VNote::c_markdownitJsFile;
  585. extraFile = "<script src=\"qrc" + VNote::c_markdownitExtraFile + "\"></script>\n" +
  586. "<script src=\"qrc" + VNote::c_markdownitAnchorExtraFile + "\"></script>\n" +
  587. "<script src=\"qrc" + VNote::c_markdownitTaskListExtraFile + "\"></script>\n" +
  588. "<script src=\"qrc" + VNote::c_markdownitImsizeExtraFile + "\"></script>\n" +
  589. "<script src=\"qrc" + VNote::c_markdownitFootnoteExtraFile + "\"></script>\n";
  590. if (g_config->getEnableMathjax()) {
  591. extraFile += "<script src=\"qrc" + VNote::c_markdownitTexMathExtraFile + "\"></script>\n";
  592. }
  593. const MarkdownitOption &opt = g_config->getMarkdownitOption();
  594. if (opt.m_sup) {
  595. extraFile += "<script src=\"qrc" + VNote::c_markdownitSupExtraFile + "\"></script>\n";
  596. }
  597. if (opt.m_sub) {
  598. extraFile += "<script src=\"qrc" + VNote::c_markdownitSubExtraFile + "\"></script>\n";
  599. }
  600. if (opt.m_metadata) {
  601. extraFile += "<script src=\"qrc" + VNote::c_markdownitFrontMatterExtraFile + "\"></script>\n";
  602. }
  603. if (opt.m_emoji) {
  604. extraFile += "<script src=\"qrc" + VNote::c_markdownitEmojiExtraFile + "\"></script>\n";
  605. }
  606. QString optJs = QString("<script>var VMarkdownitOption = {"
  607. "html: %1,\n"
  608. "breaks: %2,\n"
  609. "linkify: %3,\n"
  610. "sub: %4,\n"
  611. "sup: %5,\n"
  612. "metadata: %6,\n"
  613. "emoji: %7 };\n"
  614. "</script>\n")
  615. .arg(opt.m_html ? QStringLiteral("true") : QStringLiteral("false"))
  616. .arg(opt.m_breaks ? QStringLiteral("true") : QStringLiteral("false"))
  617. .arg(opt.m_linkify ? QStringLiteral("true") : QStringLiteral("false"))
  618. .arg(opt.m_sub ? QStringLiteral("true") : QStringLiteral("false"))
  619. .arg(opt.m_sup ? QStringLiteral("true") : QStringLiteral("false"))
  620. .arg(opt.m_metadata ? QStringLiteral("true") : QStringLiteral("false"))
  621. .arg(opt.m_emoji ? QStringLiteral("true") : QStringLiteral("false"));
  622. extraFile += optJs;
  623. mathjaxTypeSetOnLoad = false;
  624. break;
  625. }
  626. case MarkdownConverterType::Showdown:
  627. jsFile = "qrc" + VNote::c_showdownJsFile;
  628. extraFile = "<script src=\"qrc" + VNote::c_showdownExtraFile + "\"></script>\n" +
  629. "<script src=\"qrc" + VNote::c_showdownAnchorExtraFile + "\"></script>\n";
  630. break;
  631. default:
  632. Q_ASSERT(false);
  633. }
  634. extraFile += "<script src=\"qrc" + VNote::c_turndownJsFile + "\"></script>\n";
  635. extraFile += "<script src=\"qrc" + VNote::c_turndownGfmExtraFile + "\"></script>\n";
  636. if (g_config->getEnableMermaid()) {
  637. extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + g_config->getMermaidCssStyleUrl() + "\"/>\n" +
  638. "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n" +
  639. "<script>var VEnableMermaid = true;</script>\n";
  640. }
  641. if (g_config->getEnableFlowchart()) {
  642. extraFile += "<script src=\"qrc" + VNote::c_raphaelJsFile + "\"></script>\n" +
  643. "<script src=\"qrc" + VNote::c_flowchartJsFile + "\"></script>\n" +
  644. "<script>var VEnableFlowchart = true;</script>\n";
  645. }
  646. if (g_config->getEnableMathjax()) {
  647. QString mj = g_config->getMathjaxJavascript();
  648. if (p_wkhtmltopdf) {
  649. // Chante MathJax to be rendered as SVG.
  650. // If rendered as HTML, it will make the font of <code> messy.
  651. QRegExp reg("(Mathjax\\.js\\?config=)\\S+", Qt::CaseInsensitive);
  652. mj.replace(reg, QString("\\1%1").arg("TeX-MML-AM_SVG"));
  653. }
  654. extraFile += "<script type=\"text/x-mathjax-config\">"
  655. "MathJax.Hub.Config({\n"
  656. " tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n"
  657. "processEscapes: true,\n"
  658. "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
  659. " showProcessingMessages: false,\n"
  660. " skipStartupTypeset: " + QString("%1,\n").arg(mathjaxTypeSetOnLoad ? "false" : "true") +
  661. " messageStyle: \"none\"});\n"
  662. "MathJax.Hub.Register.StartupHook(\"End\", function() { handleMathjaxReady(); });\n"
  663. "</script>\n"
  664. "<script type=\"text/javascript\" async src=\"" + mj + "\"></script>\n" +
  665. "<script>var VEnableMathjax = true;</script>\n";
  666. if (p_wkhtmltopdf) {
  667. extraFile += "<script>var VRemoveMathjaxScript = true;</script>\n";
  668. }
  669. }
  670. int plantUMLMode = g_config->getPlantUMLMode();
  671. if (plantUMLMode != PlantUMLMode::DisablePlantUML) {
  672. if (plantUMLMode == PlantUMLMode::OnlinePlantUML) {
  673. extraFile += "<script type=\"text/javascript\" src=\"" + VNote::c_plantUMLJsFile + "\"></script>\n" +
  674. "<script type=\"text/javascript\" src=\"" + VNote::c_plantUMLZopfliJsFile + "\"></script>\n" +
  675. "<script>var VPlantUMLServer = '" + g_config->getPlantUMLServer() + "';</script>\n";
  676. }
  677. extraFile += QString("<script>var VPlantUMLMode = %1;</script>\n").arg(plantUMLMode);
  678. QString format = p_isPDF ? "png" : "svg";
  679. extraFile += QString("<script>var VPlantUMLFormat = '%1';</script>\n").arg(format);
  680. }
  681. if (g_config->getEnableGraphviz()) {
  682. extraFile += "<script>var VEnableGraphviz = true;</script>\n";
  683. // If we use png, we need to specify proper font in the dot command to render
  684. // non-ASCII chars properly.
  685. // Hence we use svg format in both cases.
  686. QString format = p_isPDF ? "svg" : "svg";
  687. extraFile += QString("<script>var VGraphvizFormat = '%1';</script>\n").arg(format);
  688. }
  689. if (g_config->getEnableImageCaption()) {
  690. extraFile += "<script>var VEnableImageCaption = true;</script>\n";
  691. }
  692. if (g_config->getEnableCodeBlockLineNumber()) {
  693. extraFile += "<script src=\"qrc" + VNote::c_highlightjsLineNumberExtraFile + "\"></script>\n" +
  694. "<script>var VEnableHighlightLineNumber = true;</script>\n";
  695. }
  696. if (g_config->getEnableFlashAnchor()) {
  697. extraFile += "<script>var VEnableFlashAnchor = true;</script>\n";
  698. }
  699. if (p_addToc) {
  700. extraFile += "<script>var VAddTOC = true;</script>\n";
  701. extraFile += "<style type=\"text/css\">\n"
  702. " @media print {\n"
  703. " .vnote-toc {\n"
  704. " page-break-after: always;\n"
  705. " }\n"
  706. " }\n"
  707. "</style>";
  708. }
  709. extraFile += "<script>var VStylesToInline = '" + g_config->getStylesToInlineWhenCopied() + "';</script>\n";
  710. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  711. extraFile += "<script>var VOS = 'mac';</script>\n";
  712. #elif defined(Q_OS_WIN)
  713. extraFile += "<script>var VOS = 'win';</script>\n";
  714. #else
  715. extraFile += "<script>var VOS = 'linux';</script>\n";
  716. #endif
  717. QString htmlTemplate(p_template);
  718. htmlTemplate.replace(HtmlHolder::c_JSHolder, jsFile);
  719. if (!extraFile.isEmpty()) {
  720. htmlTemplate.replace(HtmlHolder::c_extraHolder, extraFile);
  721. }
  722. return htmlTemplate;
  723. }
  724. QString VUtils::generateExportHtmlTemplate(const QString &p_renderBg, bool p_includeMathJax)
  725. {
  726. QString templ = VNote::generateExportHtmlTemplate(g_config->getRenderBackgroundColor(p_renderBg));
  727. QString extra;
  728. if (p_includeMathJax) {
  729. extra += "<script type=\"text/x-mathjax-config\">\n"
  730. "MathJax.Hub.Config({\n"
  731. "showProcessingMessages: false,\n"
  732. "messageStyle: \"none\",\n"
  733. "SVG: {\n"
  734. "minScaleAdjust: 100,\n"
  735. "styles: {\n"
  736. /*
  737. FIXME: Using wkhtmltopdf, without 2em, the math formula will be very small. However,
  738. with 2em, if there are Chinese characters in it, the font will be a mess.
  739. */
  740. #if defined(Q_OS_WIN)
  741. "\".MathJax_SVG\": {\n"
  742. "\"font-size\": \"2em !important\"\n"
  743. "}\n"
  744. #endif
  745. "}\n"
  746. "}\n"
  747. "});\n"
  748. "</script>\n";
  749. QString mj = g_config->getMathjaxJavascript();
  750. // Chante MathJax to be rendered as SVG.
  751. QRegExp reg("(Mathjax\\.js\\?config=)\\S+", Qt::CaseInsensitive);
  752. mj.replace(reg, QString("\\1%1").arg("TeX-MML-AM_SVG"));
  753. extra += "<script type=\"text/javascript\" async src=\"" + mj + "\"></script>\n";
  754. }
  755. if (!extra.isEmpty()) {
  756. templ.replace(HtmlHolder::c_extraHolder, extra);
  757. }
  758. return templ;
  759. }
  760. QString VUtils::generateMathJaxPreviewTemplate()
  761. {
  762. QString templ = VNote::generateMathJaxPreviewTemplate();
  763. templ.replace(HtmlHolder::c_JSHolder, g_config->getMathjaxJavascript());
  764. QString extraFile;
  765. QString mathjaxScale = QString::number((int)(100 * VUtils::calculateScaleFactor()));
  766. /*
  767. // Mermaid.
  768. extraFile += "<link rel=\"stylesheet\" type=\"text/css\" href=\"" + g_config->getMermaidCssStyleUrl() + "\"/>\n" +
  769. "<script src=\"qrc" + VNote::c_mermaidApiJsFile + "\"></script>\n";
  770. */
  771. // Flowchart.
  772. extraFile += "<script src=\"qrc" + VNote::c_raphaelJsFile + "\"></script>\n" +
  773. "<script src=\"qrc" + VNote::c_flowchartJsFile + "\"></script>\n";
  774. // MathJax.
  775. extraFile += "<script type=\"text/x-mathjax-config\">"
  776. "MathJax.Hub.Config({\n"
  777. " tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']],\n"
  778. "processEscapes: true,\n"
  779. "processClass: \"tex2jax_process|language-mathjax|lang-mathjax\"},\n"
  780. " \"HTML-CSS\": {\n"
  781. " scale: " + mathjaxScale + "\n"
  782. " },\n"
  783. " showProcessingMessages: false,\n"
  784. " messageStyle: \"none\"});\n"
  785. "</script>\n";
  786. templ.replace(HtmlHolder::c_extraHolder, extraFile);
  787. return templ;
  788. }
  789. QString VUtils::getFileNameWithSequence(const QString &p_directory,
  790. const QString &p_baseFileName,
  791. bool p_completeBaseName)
  792. {
  793. QDir dir(p_directory);
  794. if (!dir.exists() || !dir.exists(p_baseFileName)) {
  795. return p_baseFileName;
  796. }
  797. // Append a sequence.
  798. QFileInfo fi(p_baseFileName);
  799. QString baseName = p_completeBaseName ? fi.completeBaseName() : fi.baseName();
  800. QString suffix = p_completeBaseName ? fi.suffix() : fi.completeSuffix();
  801. int seq = 1;
  802. QString fileName;
  803. do {
  804. fileName = QString("%1_%2").arg(baseName).arg(QString::number(seq++), 3, '0');
  805. if (!suffix.isEmpty()) {
  806. fileName = fileName + "." + suffix;
  807. }
  808. } while (fileExists(dir, fileName, true));
  809. return fileName;
  810. }
  811. QString VUtils::getDirNameWithSequence(const QString &p_directory,
  812. const QString &p_baseDirName)
  813. {
  814. QDir dir(p_directory);
  815. if (!dir.exists() || !dir.exists(p_baseDirName)) {
  816. return p_baseDirName;
  817. }
  818. // Append a sequence.
  819. int seq = 1;
  820. QString fileName;
  821. do {
  822. fileName = QString("%1_%2").arg(p_baseDirName).arg(QString::number(seq++), 3, '0');
  823. } while (fileExists(dir, fileName, true));
  824. return fileName;
  825. }
  826. QString VUtils::getRandomFileName(const QString &p_directory)
  827. {
  828. Q_ASSERT(!p_directory.isEmpty());
  829. QString name;
  830. QDir dir(p_directory);
  831. do {
  832. name = QString::number(QDateTime::currentDateTimeUtc().toTime_t());
  833. name = name + '_' + QString::number(qrand());
  834. } while (fileExists(dir, name, true));
  835. return name;
  836. }
  837. bool VUtils::checkPathLegal(const QString &p_path)
  838. {
  839. // Ensure every part of the p_path is a valid file name until we come to
  840. // an existing parent directory.
  841. if (p_path.isEmpty()) {
  842. return false;
  843. }
  844. if (QFileInfo::exists(p_path)) {
  845. #if defined(Q_OS_WIN)
  846. // On Windows, "/" and ":" will also make exists() return true.
  847. if (p_path.startsWith('/') || p_path == ":") {
  848. return false;
  849. }
  850. #endif
  851. return true;
  852. }
  853. bool ret = false;
  854. int pos;
  855. QString basePath = basePathFromPath(p_path);
  856. QString fileName = fileNameFromPath(p_path);
  857. QValidator *validator = new QRegExpValidator(QRegExp(c_fileNameRegExp));
  858. while (!fileName.isEmpty()) {
  859. QValidator::State validFile = validator->validate(fileName, pos);
  860. if (validFile != QValidator::Acceptable) {
  861. break;
  862. }
  863. if (QFileInfo::exists(basePath)) {
  864. ret = true;
  865. #if defined(Q_OS_WIN)
  866. // On Windows, "/" and ":" will also make exists() return true.
  867. if (basePath.startsWith('/') || basePath == ":") {
  868. ret = false;
  869. }
  870. #endif
  871. break;
  872. }
  873. fileName = fileNameFromPath(basePath);
  874. basePath = basePathFromPath(basePath);
  875. }
  876. delete validator;
  877. return ret;
  878. }
  879. bool VUtils::checkFileNameLegal(const QString &p_name)
  880. {
  881. if (p_name.isEmpty()) {
  882. return false;
  883. }
  884. QRegExp exp(c_fileNameRegExp);
  885. return exp.exactMatch(p_name);
  886. }
  887. bool VUtils::equalPath(const QString &p_patha, const QString &p_pathb)
  888. {
  889. QString a = QDir::cleanPath(p_patha);
  890. QString b = QDir::cleanPath(p_pathb);
  891. #if defined(Q_OS_WIN)
  892. a = a.toLower();
  893. b = b.toLower();
  894. #endif
  895. return a == b;
  896. }
  897. bool VUtils::splitPathInBasePath(const QString &p_base,
  898. const QString &p_path,
  899. QStringList &p_parts)
  900. {
  901. p_parts.clear();
  902. QString a = QDir::cleanPath(p_base);
  903. QString b = QDir::cleanPath(p_path);
  904. #if defined(Q_OS_WIN)
  905. if (!b.toLower().startsWith(a.toLower())) {
  906. return false;
  907. }
  908. #else
  909. if (!b.startsWith(a)) {
  910. return false;
  911. }
  912. #endif
  913. if (a.size() == b.size()) {
  914. return true;
  915. }
  916. Q_ASSERT(a.size() < b.size());
  917. if (b.at(a.size()) != '/') {
  918. return false;
  919. }
  920. p_parts = b.right(b.size() - a.size() - 1).split("/", QString::SkipEmptyParts);
  921. return true;
  922. }
  923. void VUtils::decodeUrl(QString &p_url)
  924. {
  925. QHash<QString, QString> maps;
  926. maps.insert("%20", " ");
  927. for (auto it = maps.begin(); it != maps.end(); ++it) {
  928. p_url.replace(it.key(), it.value());
  929. }
  930. }
  931. QString VUtils::getShortcutText(const QString &p_keySeq)
  932. {
  933. return QKeySequence(p_keySeq).toString(QKeySequence::NativeText);
  934. }
  935. bool VUtils::deleteDirectory(const VNotebook *p_notebook,
  936. const QString &p_path,
  937. bool p_skipRecycleBin)
  938. {
  939. if (p_skipRecycleBin) {
  940. QDir dir(p_path);
  941. return dir.removeRecursively();
  942. } else {
  943. // Move it to the recycle bin folder.
  944. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  945. }
  946. }
  947. bool VUtils::deleteDirectory(const QString &p_path)
  948. {
  949. if (p_path.isEmpty()) {
  950. return true;
  951. }
  952. QDir dir(p_path);
  953. return dir.removeRecursively();
  954. }
  955. bool VUtils::emptyDirectory(const VNotebook *p_notebook,
  956. const QString &p_path,
  957. bool p_skipRecycleBin)
  958. {
  959. QDir dir(p_path);
  960. if (!dir.exists()) {
  961. return true;
  962. }
  963. QFileInfoList nodes = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::Hidden
  964. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  965. for (int i = 0; i < nodes.size(); ++i) {
  966. const QFileInfo &fileInfo = nodes.at(i);
  967. if (fileInfo.isDir()) {
  968. if (!deleteDirectory(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  969. return false;
  970. }
  971. } else {
  972. Q_ASSERT(fileInfo.isFile());
  973. if (!deleteFile(p_notebook, fileInfo.absoluteFilePath(), p_skipRecycleBin)) {
  974. return false;
  975. }
  976. }
  977. }
  978. return true;
  979. }
  980. bool VUtils::deleteFile(const VNotebook *p_notebook,
  981. const QString &p_path,
  982. bool p_skipRecycleBin)
  983. {
  984. if (p_skipRecycleBin) {
  985. QFile file(p_path);
  986. return file.remove();
  987. } else {
  988. // Move it to the recycle bin folder.
  989. return deleteFile(p_notebook->getRecycleBinFolderPath(), p_path);
  990. }
  991. }
  992. bool VUtils::deleteFile(const QString &p_path)
  993. {
  994. QFile file(p_path);
  995. bool ret = file.remove();
  996. if (ret) {
  997. qDebug() << "deleted file" << p_path;
  998. } else {
  999. qWarning() << "fail to delete file" << p_path;
  1000. }
  1001. return ret;
  1002. }
  1003. bool VUtils::deleteFile(const VOrphanFile *p_file,
  1004. const QString &p_path,
  1005. bool p_skipRecycleBin)
  1006. {
  1007. if (p_skipRecycleBin) {
  1008. QFile file(p_path);
  1009. return file.remove();
  1010. } else {
  1011. // Move it to the recycle bin folder.
  1012. return deleteFile(p_file->fetchRecycleBinFolderPath(), p_path);
  1013. }
  1014. }
  1015. static QString getRecycleBinSubFolderToUse(const QString &p_folderPath)
  1016. {
  1017. QDir dir(p_folderPath);
  1018. return QDir::cleanPath(dir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd")));
  1019. }
  1020. bool VUtils::deleteFile(const QString &p_recycleBinFolderPath,
  1021. const QString &p_path)
  1022. {
  1023. QString binPath = getRecycleBinSubFolderToUse(p_recycleBinFolderPath);
  1024. QDir binDir(binPath);
  1025. if (!binDir.exists()) {
  1026. binDir.mkpath(binPath);
  1027. if (!binDir.exists()) {
  1028. return false;
  1029. }
  1030. }
  1031. QString destName = getFileNameWithSequence(binPath,
  1032. fileNameFromPath(p_path),
  1033. true);
  1034. qDebug() << "try to move" << p_path << "to" << binPath << "as" << destName;
  1035. if (!binDir.rename(p_path, binDir.filePath(destName))) {
  1036. qWarning() << "fail to move" << p_path << "to" << binDir.filePath(destName);
  1037. return false;
  1038. }
  1039. return true;
  1040. }
  1041. QVector<VElementRegion> VUtils::fetchImageRegionsUsingParser(const QString &p_content)
  1042. {
  1043. Q_ASSERT(!p_content.isEmpty());
  1044. const QSharedPointer<PegParseConfig> parserConfig(new PegParseConfig());
  1045. parserConfig->m_data = p_content.toUtf8();
  1046. return PegParser::parseImageRegions(parserConfig);
  1047. }
  1048. QString VUtils::displayDateTime(const QDateTime &p_dateTime,
  1049. bool p_uniformNum)
  1050. {
  1051. QString res = p_dateTime.date().toString(p_uniformNum ? Qt::ISODate
  1052. : Qt::DefaultLocaleLongDate);
  1053. res += " " + p_dateTime.time().toString(p_uniformNum ? Qt::ISODate
  1054. : Qt::TextDate);
  1055. return res;
  1056. }
  1057. bool VUtils::fileExists(const QDir &p_dir, const QString &p_name, bool p_forceCaseInsensitive)
  1058. {
  1059. if (!p_forceCaseInsensitive) {
  1060. return p_dir.exists(p_name);
  1061. }
  1062. QString name = p_name.toLower();
  1063. QStringList names = p_dir.entryList(QDir::Dirs | QDir::Files | QDir::Hidden
  1064. | QDir::NoSymLinks | QDir::NoDotAndDotDot);
  1065. foreach (const QString &str, names) {
  1066. if (str.toLower() == name) {
  1067. return true;
  1068. }
  1069. }
  1070. return false;
  1071. }
  1072. void VUtils::addErrMsg(QString *p_msg, const QString &p_str)
  1073. {
  1074. if (!p_msg) {
  1075. return;
  1076. }
  1077. if (p_msg->isEmpty()) {
  1078. *p_msg = p_str;
  1079. } else {
  1080. *p_msg = *p_msg + '\n' + p_str;
  1081. }
  1082. }
  1083. QStringList VUtils::filterFilePathsToOpen(const QStringList &p_files)
  1084. {
  1085. QStringList paths;
  1086. for (int i = 0; i < p_files.size(); ++i) {
  1087. if (p_files[i].startsWith('-')) {
  1088. continue;
  1089. }
  1090. QString path = validFilePathToOpen(p_files[i]);
  1091. if (!path.isEmpty()) {
  1092. paths.append(path);
  1093. }
  1094. }
  1095. return paths;
  1096. }
  1097. QString VUtils::validFilePathToOpen(const QString &p_file)
  1098. {
  1099. if (QFileInfo::exists(p_file)) {
  1100. QFileInfo fi(p_file);
  1101. if (fi.isFile()) {
  1102. // Need to use absolute path here since VNote may be launched
  1103. // in different working directory.
  1104. return QDir::cleanPath(fi.absoluteFilePath());
  1105. }
  1106. }
  1107. return QString();
  1108. }
  1109. bool VUtils::isControlModifierForVim(int p_modifiers)
  1110. {
  1111. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  1112. return p_modifiers == Qt::MetaModifier;
  1113. #else
  1114. return p_modifiers == Qt::ControlModifier;
  1115. #endif
  1116. }
  1117. void VUtils::touchFile(const QString &p_file)
  1118. {
  1119. QFile file(p_file);
  1120. if (!file.open(QIODevice::WriteOnly)) {
  1121. qWarning() << "fail to touch file" << p_file;
  1122. return;
  1123. }
  1124. file.close();
  1125. }
  1126. bool VUtils::isMetaKey(int p_key)
  1127. {
  1128. return p_key == Qt::Key_Control
  1129. || p_key == Qt::Key_Shift
  1130. || p_key == Qt::Key_Meta
  1131. || p_key == Qt::Key_Alt;
  1132. }
  1133. QComboBox *VUtils::getComboBox(QWidget *p_parent)
  1134. {
  1135. QComboBox *box = new QComboBox(p_parent);
  1136. QStyledItemDelegate *itemDelegate = new QStyledItemDelegate(box);
  1137. box->setItemDelegate(itemDelegate);
  1138. return box;
  1139. }
  1140. void VUtils::setDynamicProperty(QWidget *p_widget, const char *p_prop, bool p_val)
  1141. {
  1142. p_widget->setProperty(p_prop, p_val);
  1143. p_widget->style()->unpolish(p_widget);
  1144. p_widget->style()->polish(p_widget);
  1145. }
  1146. QWebEngineView *VUtils::getWebEngineView(QWidget *p_parent)
  1147. {
  1148. QWebEngineView *viewer = new QWebEngineView(p_parent);
  1149. VPreviewPage *page = new VPreviewPage(viewer);
  1150. page->setBackgroundColor(Qt::transparent);
  1151. viewer->setPage(page);
  1152. viewer->setZoomFactor(g_config->getWebZoomFactor());
  1153. return viewer;
  1154. }
  1155. QString VUtils::getFileNameWithLocale(const QString &p_name, const QString &p_locale)
  1156. {
  1157. QString locale = p_locale.isEmpty() ? getLocale() : p_locale;
  1158. locale = locale.split('_')[0];
  1159. QFileInfo fi(p_name);
  1160. QString baseName = fi.completeBaseName();
  1161. QString suffix = fi.suffix();
  1162. if (suffix.isEmpty()) {
  1163. return QString("%1_%2").arg(baseName).arg(locale);
  1164. } else {
  1165. return QString("%1_%2.%3").arg(baseName).arg(locale).arg(suffix);
  1166. }
  1167. }
  1168. QString VUtils::getDocFile(const QString &p_name)
  1169. {
  1170. QDir dir(VNote::c_docFileFolder);
  1171. QString name(getFileNameWithLocale(p_name));
  1172. if (!dir.exists(name)) {
  1173. name = getFileNameWithLocale(p_name, "en_US");
  1174. }
  1175. return dir.filePath(name);
  1176. }
  1177. QString VUtils::getCaptainShortcutSequenceText(const QString &p_operation)
  1178. {
  1179. QString capKey = g_config->getShortcutKeySequence("CaptainMode");
  1180. QString sec = g_config->getCaptainShortcutKeySequence(p_operation);
  1181. QKeySequence seq(capKey + "," + sec);
  1182. if (!seq.isEmpty()) {
  1183. return seq.toString(QKeySequence::NativeText);
  1184. }
  1185. return QString();
  1186. }
  1187. QString VUtils::getAvailableFontFamily(const QStringList &p_families)
  1188. {
  1189. QStringList availFamilies = QFontDatabase().families();
  1190. for (int i = 0; i < p_families.size(); ++i) {
  1191. QString family = p_families[i].trimmed();
  1192. if (family.isEmpty()) {
  1193. continue;
  1194. }
  1195. for (int j = 0; j < availFamilies.size(); ++j) {
  1196. QString availFamily = availFamilies[j];
  1197. availFamily.remove(QRegExp("\\[.*\\]"));
  1198. availFamily = availFamily.trimmed();
  1199. if (family == availFamily
  1200. || family.toLower() == availFamily.toLower()) {
  1201. qDebug() << "matched font family" << availFamilies[j];
  1202. return availFamilies[j];
  1203. }
  1204. }
  1205. }
  1206. return QString();
  1207. }
  1208. bool VUtils::fixTextWithShortcut(QAction *p_act, const QString &p_shortcut)
  1209. {
  1210. QString keySeq = g_config->getShortcutKeySequence(p_shortcut);
  1211. if (!keySeq.isEmpty()) {
  1212. p_act->setText(QString("%1\t%2").arg(p_act->text()).arg(VUtils::getShortcutText(keySeq)));
  1213. return true;
  1214. }
  1215. return false;
  1216. }
  1217. bool VUtils::fixTextWithCaptainShortcut(QAction *p_act, const QString &p_shortcut)
  1218. {
  1219. QString keyText = VUtils::getCaptainShortcutSequenceText(p_shortcut);
  1220. if (!keyText.isEmpty()) {
  1221. p_act->setText(QString("%1\t%2").arg(p_act->text()).arg(keyText));
  1222. return true;
  1223. }
  1224. return false;
  1225. }
  1226. QStringList VUtils::parseCombinedArgString(const QString &p_program)
  1227. {
  1228. QStringList args;
  1229. QString tmp;
  1230. int quoteCount = 0;
  1231. bool inQuote = false;
  1232. // handle quoting. tokens can be surrounded by double quotes
  1233. // "hello world". three consecutive double quotes represent
  1234. // the quote character itself.
  1235. for (int i = 0; i < p_program.size(); ++i) {
  1236. if (p_program.at(i) == QLatin1Char('"')) {
  1237. ++quoteCount;
  1238. if (quoteCount == 3) {
  1239. // third consecutive quote
  1240. quoteCount = 0;
  1241. tmp += p_program.at(i);
  1242. }
  1243. continue;
  1244. }
  1245. if (quoteCount) {
  1246. if (quoteCount == 1) {
  1247. inQuote = !inQuote;
  1248. }
  1249. quoteCount = 0;
  1250. }
  1251. if (!inQuote && p_program.at(i).isSpace()) {
  1252. if (!tmp.isEmpty()) {
  1253. args += tmp;
  1254. tmp.clear();
  1255. }
  1256. } else {
  1257. tmp += p_program.at(i);
  1258. }
  1259. }
  1260. if (!tmp.isEmpty()) {
  1261. args += tmp;
  1262. }
  1263. return args;
  1264. }
  1265. const QTreeWidgetItem *VUtils::topLevelTreeItem(const QTreeWidgetItem *p_item)
  1266. {
  1267. if (!p_item) {
  1268. return NULL;
  1269. }
  1270. if (p_item->parent()) {
  1271. return p_item->parent();
  1272. } else {
  1273. return p_item;
  1274. }
  1275. }
  1276. QImage VUtils::imageFromFile(const QString &p_filePath)
  1277. {
  1278. QImage img;
  1279. QFile file(p_filePath);
  1280. if (!file.open(QIODevice::ReadOnly)) {
  1281. qWarning() << "fail to open image file" << p_filePath;
  1282. return img;
  1283. }
  1284. img.loadFromData(file.readAll());
  1285. qDebug() << "imageFromFile" << p_filePath << img.isNull() << img.format();
  1286. return img;
  1287. }
  1288. QPixmap VUtils::pixmapFromFile(const QString &p_filePath)
  1289. {
  1290. QPixmap pixmap;
  1291. QFile file(p_filePath);
  1292. if (!file.open(QIODevice::ReadOnly)) {
  1293. qWarning() << "fail to open pixmap file" << p_filePath;
  1294. return pixmap;
  1295. }
  1296. pixmap.loadFromData(file.readAll());
  1297. qDebug() << "pixmapFromFile" << p_filePath << pixmap.isNull();
  1298. return pixmap;
  1299. }
  1300. QFormLayout *VUtils::getFormLayout()
  1301. {
  1302. QFormLayout *layout = new QFormLayout();
  1303. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  1304. layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
  1305. layout->setFormAlignment(Qt::AlignLeft | Qt::AlignTop);
  1306. #endif
  1307. return layout;
  1308. }
  1309. bool VUtils::inSameDrive(const QString &p_a, const QString &p_b)
  1310. {
  1311. #if defined(Q_OS_WIN)
  1312. QChar sep(':');
  1313. int ai = p_a.indexOf(sep);
  1314. int bi = p_b.indexOf(sep);
  1315. if (ai == -1 || bi == -1) {
  1316. return false;
  1317. }
  1318. return p_a.left(ai).toLower() == p_b.left(bi).toLower();
  1319. #else
  1320. return true;
  1321. #endif
  1322. }
  1323. QString VUtils::promptForFileName(const QString &p_title,
  1324. const QString &p_label,
  1325. const QString &p_default,
  1326. const QString &p_dir,
  1327. QWidget *p_parent)
  1328. {
  1329. QString name = p_default;
  1330. QString text = p_label;
  1331. QDir paDir(p_dir);
  1332. while (true) {
  1333. bool ok;
  1334. name = QInputDialog::getText(p_parent,
  1335. p_title,
  1336. text,
  1337. QLineEdit::Normal,
  1338. name,
  1339. &ok);
  1340. if (!ok || name.isEmpty()) {
  1341. return "";
  1342. }
  1343. if (!VUtils::checkFileNameLegal(name)) {
  1344. text = QObject::tr("Illegal name. Please try again:");
  1345. continue;
  1346. }
  1347. if (paDir.exists(name)) {
  1348. text = QObject::tr("Name already exists. Please try again:");
  1349. continue;
  1350. }
  1351. break;
  1352. }
  1353. return name;
  1354. }
  1355. QString VUtils::promptForFileName(const QString &p_title,
  1356. const QString &p_label,
  1357. const QString &p_default,
  1358. std::function<bool(const QString &p_name)> p_checkExistsFunc,
  1359. QWidget *p_parent)
  1360. {
  1361. QString name = p_default;
  1362. QString text = p_label;
  1363. while (true) {
  1364. bool ok;
  1365. name = QInputDialog::getText(p_parent,
  1366. p_title,
  1367. text,
  1368. QLineEdit::Normal,
  1369. name,
  1370. &ok);
  1371. if (!ok || name.isEmpty()) {
  1372. return "";
  1373. }
  1374. if (!VUtils::checkFileNameLegal(name)) {
  1375. text = QObject::tr("Illegal name. Please try again:");
  1376. continue;
  1377. }
  1378. if (p_checkExistsFunc(name)) {
  1379. text = QObject::tr("Name already exists. Please try again:");
  1380. continue;
  1381. }
  1382. break;
  1383. }
  1384. return name;
  1385. }
  1386. bool VUtils::onlyHasImgInHtml(const QString &p_html)
  1387. {
  1388. // Tricky.
  1389. QRegExp reg("<(?:p|span|div) ");
  1390. return !p_html.contains(reg);
  1391. }
  1392. int VUtils::elapsedTime(bool p_reset)
  1393. {
  1394. static QTime tm;
  1395. if (p_reset) {
  1396. tm = QTime();
  1397. return 0;
  1398. }
  1399. if (tm.isNull()) {
  1400. tm.start();
  1401. return 0;
  1402. }
  1403. return tm.restart();
  1404. }
  1405. QPixmap VUtils::svgToPixmap(const QByteArray &p_content,
  1406. const QString &p_background,
  1407. qreal p_factor)
  1408. {
  1409. QSvgRenderer renderer(p_content);
  1410. QSize deSz = renderer.defaultSize();
  1411. if (p_factor > 0) {
  1412. deSz *= p_factor;
  1413. }
  1414. QPixmap pm(deSz);
  1415. if (!p_background.isEmpty()) {
  1416. pm.fill(p_background);
  1417. }
  1418. QPainter painter(&pm);
  1419. renderer.render(&painter);
  1420. return pm;
  1421. }
  1422. QString VUtils::fetchImageLinkUrlToPreview(const QString &p_text, int &p_width, int &p_height)
  1423. {
  1424. QRegExp regExp(VUtils::c_imageLinkRegExp);
  1425. p_width = p_height = -1;
  1426. int index = regExp.indexIn(p_text);
  1427. if (index == -1) {
  1428. return QString();
  1429. }
  1430. int lastIndex = regExp.lastIndexIn(p_text);
  1431. if (lastIndex != index) {
  1432. return QString();
  1433. }
  1434. QString tmp(regExp.cap(7));
  1435. if (!tmp.isEmpty()) {
  1436. p_width = tmp.toInt();
  1437. if (p_width <= 0) {
  1438. p_width = -1;
  1439. }
  1440. }
  1441. tmp = regExp.cap(8);
  1442. if (!tmp.isEmpty()) {
  1443. p_height = tmp.toInt();
  1444. if (p_height <= 0) {
  1445. p_height = -1;
  1446. }
  1447. }
  1448. return regExp.cap(2).trimmed();
  1449. }
  1450. QString VUtils::fetchImageLinkUrl(const QString &p_link)
  1451. {
  1452. QRegExp regExp(VUtils::c_imageLinkRegExp);
  1453. int index = regExp.indexIn(p_link);
  1454. if (index == -1) {
  1455. return QString();
  1456. }
  1457. return regExp.cap(2).trimmed();
  1458. }
  1459. QString VUtils::fetchLinkUrl(const QString &p_link)
  1460. {
  1461. QRegExp regExp(VUtils::c_linkRegExp);
  1462. int index = regExp.indexIn(p_link);
  1463. if (index == -1) {
  1464. return QString();
  1465. }
  1466. return regExp.cap(2).trimmed();
  1467. }
  1468. QUrl VUtils::pathToUrl(const QString &p_path)
  1469. {
  1470. QUrl url;
  1471. if (QFileInfo::exists(p_path)) {
  1472. url = QUrl::fromLocalFile(p_path);
  1473. } else {
  1474. url = QUrl(p_path);
  1475. }
  1476. return url;
  1477. }