vutils.cpp 49 KB

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