vmainwindow.cpp 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147
  1. #include <QtWidgets>
  2. #include <QList>
  3. #include <QPrinter>
  4. #include <QPrintDialog>
  5. #include <QPainter>
  6. #include "vmainwindow.h"
  7. #include "vdirectorytree.h"
  8. #include "vnote.h"
  9. #include "vfilelist.h"
  10. #include "vconfigmanager.h"
  11. #include "utils/vutils.h"
  12. #include "veditarea.h"
  13. #include "voutline.h"
  14. #include "vnotebookselector.h"
  15. #include "vavatar.h"
  16. #include "dialog/vfindreplacedialog.h"
  17. #include "dialog/vsettingsdialog.h"
  18. #include "vcaptain.h"
  19. #include "vedittab.h"
  20. #include "vwebview.h"
  21. #include "vexporter.h"
  22. #include "vmdtab.h"
  23. #include "vvimindicator.h"
  24. #include "vtabindicator.h"
  25. #include "dialog/vupdater.h"
  26. #include "vorphanfile.h"
  27. #include "dialog/vorphanfileinfodialog.h"
  28. #include "vsingleinstanceguard.h"
  29. #include "vnotefile.h"
  30. extern VConfigManager *g_config;
  31. VNote *g_vnote;
  32. const int VMainWindow::c_sharedMemTimerInterval = 1000;
  33. #if defined(QT_NO_DEBUG)
  34. extern QFile g_logFile;
  35. #endif
  36. VMainWindow::VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent)
  37. : QMainWindow(p_parent), m_onePanel(false), m_guard(p_guard),
  38. m_windowOldState(Qt::WindowNoState), m_requestQuit(false)
  39. {
  40. setWindowIcon(QIcon(":/resources/icons/vnote.ico"));
  41. vnote = new VNote(this);
  42. g_vnote = vnote;
  43. vnote->initPalette(palette());
  44. initPredefinedColorPixmaps();
  45. setupUI();
  46. initMenuBar();
  47. initToolBar();
  48. initDockWindows();
  49. initAvatar();
  50. restoreStateAndGeometry();
  51. setContextMenuPolicy(Qt::NoContextMenu);
  52. notebookSelector->update();
  53. initCaptain();
  54. initSharedMemoryWatcher();
  55. }
  56. void VMainWindow::initSharedMemoryWatcher()
  57. {
  58. m_sharedMemTimer = new QTimer(this);
  59. m_sharedMemTimer->setSingleShot(false);
  60. m_sharedMemTimer->setInterval(c_sharedMemTimerInterval);
  61. connect(m_sharedMemTimer, &QTimer::timeout,
  62. this, &VMainWindow::checkSharedMemory);
  63. m_sharedMemTimer->start();
  64. }
  65. void VMainWindow::initCaptain()
  66. {
  67. // VCaptain should be visible to accpet key focus. But VCaptain
  68. // may hide other widgets.
  69. m_captain = new VCaptain(this);
  70. connect(m_captain, &VCaptain::captainModeChanged,
  71. this, &VMainWindow::handleCaptainModeChanged);
  72. m_captain->registerNavigationTarget(notebookSelector);
  73. m_captain->registerNavigationTarget(directoryTree);
  74. m_captain->registerNavigationTarget(fileList);
  75. m_captain->registerNavigationTarget(editArea);
  76. m_captain->registerNavigationTarget(outline);
  77. }
  78. void VMainWindow::setupUI()
  79. {
  80. QWidget *directoryPanel = setupDirectoryPanel();
  81. fileList = new VFileList();
  82. fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
  83. editArea = new VEditArea(vnote);
  84. editArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  85. m_findReplaceDialog = editArea->getFindReplaceDialog();
  86. fileList->setEditArea(editArea);
  87. directoryTree->setEditArea(editArea);
  88. notebookSelector->setEditArea(editArea);
  89. // Main Splitter
  90. mainSplitter = new QSplitter();
  91. mainSplitter->setObjectName("MainSplitter");
  92. mainSplitter->addWidget(directoryPanel);
  93. mainSplitter->addWidget(fileList);
  94. mainSplitter->addWidget(editArea);
  95. mainSplitter->setStretchFactor(0, 0);
  96. mainSplitter->setStretchFactor(1, 0);
  97. mainSplitter->setStretchFactor(2, 1);
  98. // Signals
  99. connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
  100. fileList, &VFileList::setDirectory);
  101. connect(directoryTree, &VDirectoryTree::directoryUpdated,
  102. editArea, &VEditArea::handleDirectoryUpdated);
  103. connect(notebookSelector, &VNotebookSelector::notebookUpdated,
  104. editArea, &VEditArea::handleNotebookUpdated);
  105. connect(notebookSelector, &VNotebookSelector::notebookCreated,
  106. directoryTree, &VDirectoryTree::newRootDirectory);
  107. connect(fileList, &VFileList::fileClicked,
  108. editArea, &VEditArea::openFile);
  109. connect(fileList, &VFileList::fileCreated,
  110. editArea, &VEditArea::openFile);
  111. connect(fileList, &VFileList::fileUpdated,
  112. editArea, &VEditArea::handleFileUpdated);
  113. connect(editArea, &VEditArea::tabStatusUpdated,
  114. this, &VMainWindow::handleAreaTabStatusUpdated);
  115. connect(editArea, &VEditArea::statusMessage,
  116. this, &VMainWindow::showStatusMessage);
  117. connect(editArea, &VEditArea::vimStatusUpdated,
  118. this, &VMainWindow::handleVimStatusUpdated);
  119. connect(m_findReplaceDialog, &VFindReplaceDialog::findTextChanged,
  120. this, &VMainWindow::handleFindDialogTextChanged);
  121. setCentralWidget(mainSplitter);
  122. m_vimIndicator = new VVimIndicator(this);
  123. m_vimIndicator->hide();
  124. m_tabIndicator = new VTabIndicator(this);
  125. m_tabIndicator->hide();
  126. // Create and show the status bar
  127. statusBar()->addPermanentWidget(m_vimIndicator);
  128. statusBar()->addPermanentWidget(m_tabIndicator);
  129. initTrayIcon();
  130. }
  131. QWidget *VMainWindow::setupDirectoryPanel()
  132. {
  133. notebookLabel = new QLabel(tr("Notebooks"));
  134. notebookLabel->setProperty("TitleLabel", true);
  135. notebookLabel->setProperty("NotebookPanel", true);
  136. directoryLabel = new QLabel(tr("Folders"));
  137. directoryLabel->setProperty("TitleLabel", true);
  138. directoryLabel->setProperty("NotebookPanel", true);
  139. notebookSelector = new VNotebookSelector(vnote);
  140. notebookSelector->setObjectName("NotebookSelector");
  141. notebookSelector->setProperty("NotebookPanel", true);
  142. notebookSelector->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
  143. directoryTree = new VDirectoryTree(vnote);
  144. directoryTree->setProperty("NotebookPanel", true);
  145. QVBoxLayout *nbLayout = new QVBoxLayout;
  146. nbLayout->addWidget(notebookLabel);
  147. nbLayout->addWidget(notebookSelector);
  148. nbLayout->addWidget(directoryLabel);
  149. nbLayout->addWidget(directoryTree);
  150. nbLayout->setContentsMargins(0, 0, 0, 0);
  151. nbLayout->setSpacing(0);
  152. QWidget *nbContainer = new QWidget();
  153. nbContainer->setObjectName("NotebookPanel");
  154. nbContainer->setLayout(nbLayout);
  155. nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
  156. connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
  157. directoryTree, &VDirectoryTree::setNotebook);
  158. connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
  159. this, &VMainWindow::handleCurrentNotebookChanged);
  160. connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
  161. this, &VMainWindow::handleCurrentDirectoryChanged);
  162. return nbContainer;
  163. }
  164. void VMainWindow::initToolBar()
  165. {
  166. const int tbIconSize = g_config->getToolBarIconSize() * VUtils::calculateScaleFactor();
  167. QSize iconSize(tbIconSize, tbIconSize);
  168. initFileToolBar(iconSize);
  169. initViewToolBar(iconSize);
  170. initEditToolBar(iconSize);
  171. }
  172. void VMainWindow::initViewToolBar(QSize p_iconSize)
  173. {
  174. QToolBar *viewToolBar = addToolBar(tr("View"));
  175. viewToolBar->setObjectName("ViewToolBar");
  176. viewToolBar->setMovable(false);
  177. if (p_iconSize.isValid()) {
  178. viewToolBar->setIconSize(p_iconSize);
  179. }
  180. QAction *onePanelViewAct = new QAction(QIcon(":/resources/icons/one_panel.svg"),
  181. tr("&Single Panel"), this);
  182. onePanelViewAct->setStatusTip(tr("Display only the notes list panel"));
  183. onePanelViewAct->setToolTip(tr("Single Panel (Ctrl+E P)"));
  184. connect(onePanelViewAct, &QAction::triggered,
  185. this, &VMainWindow::onePanelView);
  186. QAction *twoPanelViewAct = new QAction(QIcon(":/resources/icons/two_panels.svg"),
  187. tr("&Two Panels"), this);
  188. twoPanelViewAct->setStatusTip(tr("Display both the folders and notes list panel"));
  189. twoPanelViewAct->setToolTip(tr("Two Panels (Ctrl+E P)"));
  190. connect(twoPanelViewAct, &QAction::triggered,
  191. this, &VMainWindow::twoPanelView);
  192. QMenu *panelMenu = new QMenu(this);
  193. panelMenu->setToolTipsVisible(true);
  194. panelMenu->addAction(onePanelViewAct);
  195. panelMenu->addAction(twoPanelViewAct);
  196. expandViewAct = new QAction(QIcon(":/resources/icons/expand.svg"),
  197. tr("Expand (Ctrl+E E)"), this);
  198. expandViewAct->setStatusTip(tr("Expand the edit area"));
  199. expandViewAct->setCheckable(true);
  200. expandViewAct->setMenu(panelMenu);
  201. connect(expandViewAct, &QAction::triggered,
  202. this, &VMainWindow::expandPanelView);
  203. viewToolBar->addAction(expandViewAct);
  204. }
  205. static void setActionsEnabled(QWidget *p_widget, bool p_enabled)
  206. {
  207. Q_ASSERT(p_widget);
  208. QList<QAction *> actions = p_widget->actions();
  209. for (auto const & act : actions) {
  210. act->setEnabled(p_enabled);
  211. }
  212. }
  213. void VMainWindow::initEditToolBar(QSize p_iconSize)
  214. {
  215. m_editToolBar = addToolBar(tr("Edit Toolbar"));
  216. m_editToolBar->setObjectName("EditToolBar");
  217. m_editToolBar->setMovable(false);
  218. if (p_iconSize.isValid()) {
  219. m_editToolBar->setIconSize(p_iconSize);
  220. }
  221. m_editToolBar->addSeparator();
  222. QAction *boldAct = new QAction(QIcon(":/resources/icons/bold.svg"),
  223. tr("Bold (Ctrl+B)"), this);
  224. boldAct->setStatusTip(tr("Insert bold text or change selected text to bold"));
  225. connect(boldAct, &QAction::triggered,
  226. this, [this](){
  227. if (m_curTab) {
  228. m_curTab->decorateText(TextDecoration::Bold);
  229. }
  230. });
  231. m_editToolBar->addAction(boldAct);
  232. QAction *italicAct = new QAction(QIcon(":/resources/icons/italic.svg"),
  233. tr("Italic (Ctrl+I)"), this);
  234. italicAct->setStatusTip(tr("Insert italic text or change selected text to italic"));
  235. connect(italicAct, &QAction::triggered,
  236. this, [this](){
  237. if (m_curTab) {
  238. m_curTab->decorateText(TextDecoration::Italic);
  239. }
  240. });
  241. m_editToolBar->addAction(italicAct);
  242. QAction *strikethroughAct = new QAction(QIcon(":/resources/icons/strikethrough.svg"),
  243. tr("Strikethrough (Ctrl+D)"), this);
  244. strikethroughAct->setStatusTip(tr("Insert strikethrough text or change selected text to strikethroughed"));
  245. connect(strikethroughAct, &QAction::triggered,
  246. this, [this](){
  247. if (m_curTab) {
  248. m_curTab->decorateText(TextDecoration::Strikethrough);
  249. }
  250. });
  251. m_editToolBar->addAction(strikethroughAct);
  252. QAction *inlineCodeAct = new QAction(QIcon(":/resources/icons/inline_code.svg"),
  253. tr("Inline Code (Ctrl+O)"), this);
  254. inlineCodeAct->setStatusTip(tr("Insert inline-code text or change selected text to inline-coded"));
  255. connect(inlineCodeAct, &QAction::triggered,
  256. this, [this](){
  257. if (m_curTab) {
  258. m_curTab->decorateText(TextDecoration::InlineCode);
  259. }
  260. });
  261. m_editToolBar->addAction(inlineCodeAct);
  262. QAction *toggleAct = m_editToolBar->toggleViewAction();
  263. toggleAct->setToolTip(tr("Toggle the edit toolbar"));
  264. viewMenu->addAction(toggleAct);
  265. setActionsEnabled(m_editToolBar, false);
  266. }
  267. void VMainWindow::initFileToolBar(QSize p_iconSize)
  268. {
  269. QToolBar *fileToolBar = addToolBar(tr("Note"));
  270. fileToolBar->setObjectName("NoteToolBar");
  271. fileToolBar->setMovable(false);
  272. if (p_iconSize.isValid()) {
  273. fileToolBar->setIconSize(p_iconSize);
  274. }
  275. newRootDirAct = new QAction(QIcon(":/resources/icons/create_rootdir_tb.svg"),
  276. tr("New &Root Folder"), this);
  277. newRootDirAct->setStatusTip(tr("Create a root folder in current notebook"));
  278. connect(newRootDirAct, &QAction::triggered,
  279. directoryTree, &VDirectoryTree::newRootDirectory);
  280. newNoteAct = new QAction(QIcon(":/resources/icons/create_note_tb.svg"),
  281. tr("New &Note"), this);
  282. newNoteAct->setStatusTip(tr("Create a note in current folder"));
  283. QString keySeq = g_config->getShortcutKeySequence("NewNote");
  284. qDebug() << "set NewNote shortcut to" << keySeq;
  285. newNoteAct->setShortcut(QKeySequence(keySeq));
  286. connect(newNoteAct, &QAction::triggered,
  287. fileList, &VFileList::newFile);
  288. noteInfoAct = new QAction(QIcon(":/resources/icons/note_info_tb.svg"),
  289. tr("Note &Info"), this);
  290. noteInfoAct->setStatusTip(tr("View and edit current note's information"));
  291. connect(noteInfoAct, &QAction::triggered,
  292. this, &VMainWindow::curEditFileInfo);
  293. deleteNoteAct = new QAction(QIcon(":/resources/icons/delete_note_tb.svg"),
  294. tr("&Delete Note"), this);
  295. deleteNoteAct->setStatusTip(tr("Delete current note"));
  296. connect(deleteNoteAct, &QAction::triggered,
  297. this, &VMainWindow::deleteCurNote);
  298. keySeq = g_config->getShortcutKeySequence("CloseNote");
  299. qDebug() << "set CloseNote shortcut to" << keySeq;
  300. m_closeNoteShortcut = new QShortcut(QKeySequence(keySeq), this);
  301. m_closeNoteShortcut->setContext(Qt::WidgetWithChildrenShortcut);
  302. connect(m_closeNoteShortcut, &QShortcut::activated,
  303. this, [this](){
  304. if (m_curFile) {
  305. editArea->closeFile(m_curFile, false);
  306. }
  307. });
  308. editNoteAct = new QAction(QIcon(":/resources/icons/edit_note.svg"),
  309. tr("&Edit"), this);
  310. editNoteAct->setStatusTip(tr("Edit current note"));
  311. keySeq = g_config->getShortcutKeySequence("EditNote");
  312. qDebug() << "set EditNote shortcut to" << keySeq;
  313. editNoteAct->setShortcut(QKeySequence(keySeq));
  314. connect(editNoteAct, &QAction::triggered,
  315. editArea, &VEditArea::editFile);
  316. discardExitAct = new QAction(QIcon(":/resources/icons/discard_exit.svg"),
  317. tr("Discard Changes And Read"), this);
  318. discardExitAct->setStatusTip(tr("Discard changes and exit edit mode"));
  319. discardExitAct->setToolTip(tr("Discard Changes And Read (Ctrl+E Q)"));
  320. connect(discardExitAct, &QAction::triggered,
  321. editArea, &VEditArea::readFile);
  322. QMenu *exitEditMenu = new QMenu(this);
  323. exitEditMenu->setToolTipsVisible(true);
  324. exitEditMenu->addAction(discardExitAct);
  325. saveExitAct = new QAction(QIcon(":/resources/icons/save_exit.svg"),
  326. tr("Save Changes And Read (Ctrl+T)"), this);
  327. saveExitAct->setStatusTip(tr("Save changes and exit edit mode"));
  328. saveExitAct->setMenu(exitEditMenu);
  329. keySeq = g_config->getShortcutKeySequence("SaveAndRead");
  330. qDebug() << "set SaveAndRead shortcut to" << keySeq;
  331. saveExitAct->setShortcut(QKeySequence(keySeq));
  332. connect(saveExitAct, &QAction::triggered,
  333. editArea, &VEditArea::saveAndReadFile);
  334. saveNoteAct = new QAction(QIcon(":/resources/icons/save_note.svg"),
  335. tr("Save"), this);
  336. saveNoteAct->setStatusTip(tr("Save changes to current note"));
  337. keySeq = g_config->getShortcutKeySequence("SaveNote");
  338. qDebug() << "set SaveNote shortcut to" << keySeq;
  339. saveNoteAct->setShortcut(QKeySequence(keySeq));
  340. connect(saveNoteAct, &QAction::triggered,
  341. editArea, &VEditArea::saveFile);
  342. newRootDirAct->setEnabled(false);
  343. newNoteAct->setEnabled(false);
  344. noteInfoAct->setEnabled(false);
  345. deleteNoteAct->setEnabled(false);
  346. editNoteAct->setEnabled(false);
  347. saveExitAct->setVisible(false);
  348. discardExitAct->setVisible(false);
  349. saveNoteAct->setEnabled(false);
  350. fileToolBar->addAction(newRootDirAct);
  351. fileToolBar->addAction(newNoteAct);
  352. fileToolBar->addSeparator();
  353. fileToolBar->addAction(noteInfoAct);
  354. fileToolBar->addAction(deleteNoteAct);
  355. fileToolBar->addAction(editNoteAct);
  356. fileToolBar->addAction(saveExitAct);
  357. fileToolBar->addAction(saveNoteAct);
  358. fileToolBar->addSeparator();
  359. }
  360. void VMainWindow::initMenuBar()
  361. {
  362. initFileMenu();
  363. initEditMenu();
  364. initViewMenu();
  365. initMarkdownMenu();
  366. initHelpMenu();
  367. }
  368. void VMainWindow::initHelpMenu()
  369. {
  370. QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
  371. helpMenu->setToolTipsVisible(true);
  372. #if defined(QT_NO_DEBUG)
  373. QAction *logAct = new QAction(tr("View &Log"), this);
  374. logAct->setToolTip(tr("View VNote's debug log (%1)").arg(g_config->getLogFilePath()));
  375. connect(logAct, &QAction::triggered,
  376. this, [](){
  377. QUrl url = QUrl::fromLocalFile(g_config->getLogFilePath());
  378. QDesktopServices::openUrl(url);
  379. });
  380. #endif
  381. QAction *shortcutAct = new QAction(tr("&Shortcuts Help"), this);
  382. shortcutAct->setToolTip(tr("View information about shortcut keys"));
  383. connect(shortcutAct, &QAction::triggered,
  384. this, &VMainWindow::shortcutHelp);
  385. QAction *mdGuideAct = new QAction(tr("&Markdown Guide"), this);
  386. mdGuideAct->setToolTip(tr("A quick guide of Markdown syntax"));
  387. connect(mdGuideAct, &QAction::triggered,
  388. this, [this](){
  389. QString locale = VUtils::getLocale();
  390. QString docName = VNote::c_markdownGuideDocFile_en;
  391. if (locale == "zh_CN") {
  392. docName = VNote::c_markdownGuideDocFile_zh;
  393. }
  394. VFile *file = vnote->getOrphanFile(docName, false, true);
  395. editArea->openFile(file, OpenFileMode::Read);
  396. });
  397. QAction *updateAct = new QAction(tr("Check For &Updates"), this);
  398. updateAct->setToolTip(tr("Check for updates of VNote"));
  399. connect(updateAct, &QAction::triggered,
  400. this, [this](){
  401. VUpdater updater(this);
  402. updater.exec();
  403. });
  404. QAction *starAct = new QAction(tr("Star VNote on &Github"), this);
  405. starAct->setToolTip(tr("Give a star to VNote on Github project"));
  406. connect(starAct, &QAction::triggered,
  407. this, [this]() {
  408. QString url("https://github.com/tamlok/vnote");
  409. QDesktopServices::openUrl(url);
  410. });
  411. QAction *feedbackAct = new QAction(tr("&Feedback"), this);
  412. feedbackAct->setToolTip(tr("Open an issue on Github"));
  413. connect(feedbackAct, &QAction::triggered,
  414. this, [this]() {
  415. QString url("https://github.com/tamlok/vnote/issues");
  416. QDesktopServices::openUrl(url);
  417. });
  418. QAction *aboutAct = new QAction(tr("&About VNote"), this);
  419. aboutAct->setToolTip(tr("View information about VNote"));
  420. aboutAct->setMenuRole(QAction::AboutRole);
  421. connect(aboutAct, &QAction::triggered,
  422. this, &VMainWindow::aboutMessage);
  423. QAction *aboutQtAct = new QAction(tr("About &Qt"), this);
  424. aboutQtAct->setToolTip(tr("View information about Qt"));
  425. aboutQtAct->setMenuRole(QAction::AboutQtRole);
  426. connect(aboutQtAct, &QAction::triggered,
  427. qApp, &QApplication::aboutQt);
  428. helpMenu->addAction(shortcutAct);
  429. helpMenu->addAction(mdGuideAct);
  430. helpMenu->addAction(updateAct);
  431. helpMenu->addAction(starAct);
  432. helpMenu->addAction(feedbackAct);
  433. #if defined(QT_NO_DEBUG)
  434. helpMenu->addAction(logAct);
  435. #endif
  436. helpMenu->addAction(aboutQtAct);
  437. helpMenu->addAction(aboutAct);
  438. }
  439. void VMainWindow::initMarkdownMenu()
  440. {
  441. QMenu *markdownMenu = menuBar()->addMenu(tr("&Markdown"));
  442. markdownMenu->setToolTipsVisible(true);
  443. initConverterMenu(markdownMenu);
  444. initMarkdownitOptionMenu(markdownMenu);
  445. markdownMenu->addSeparator();
  446. initRenderStyleMenu(markdownMenu);
  447. initRenderBackgroundMenu(markdownMenu);
  448. QAction *constrainImageAct = new QAction(tr("Constrain The Width of Images"), this);
  449. constrainImageAct->setToolTip(tr("Constrain the width of images to the window in read mode (re-open current tabs to make it work)"));
  450. constrainImageAct->setCheckable(true);
  451. connect(constrainImageAct, &QAction::triggered,
  452. this, &VMainWindow::enableImageConstraint);
  453. markdownMenu->addAction(constrainImageAct);
  454. constrainImageAct->setChecked(g_config->getEnableImageConstraint());
  455. QAction *imageCaptionAct = new QAction(tr("Enable Image Caption"), this);
  456. imageCaptionAct->setToolTip(tr("Center the images and display the alt text as caption (re-open current tabs to make it work)"));
  457. imageCaptionAct->setCheckable(true);
  458. connect(imageCaptionAct, &QAction::triggered,
  459. this, &VMainWindow::enableImageCaption);
  460. markdownMenu->addAction(imageCaptionAct);
  461. imageCaptionAct->setChecked(g_config->getEnableImageCaption());
  462. markdownMenu->addSeparator();
  463. QAction *mermaidAct = new QAction(tr("&Mermaid Diagram"), this);
  464. mermaidAct->setToolTip(tr("Enable Mermaid for graph and diagram"));
  465. mermaidAct->setCheckable(true);
  466. connect(mermaidAct, &QAction::triggered,
  467. this, &VMainWindow::enableMermaid);
  468. markdownMenu->addAction(mermaidAct);
  469. mermaidAct->setChecked(g_config->getEnableMermaid());
  470. QAction *flowchartAct = new QAction(tr("&Flowchart.js"), this);
  471. flowchartAct->setToolTip(tr("Enable Flowchart.js for flowchart diagram"));
  472. flowchartAct->setCheckable(true);
  473. connect(flowchartAct, &QAction::triggered,
  474. this, [this](bool p_enabled){
  475. g_config->setEnableFlowchart(p_enabled);
  476. });
  477. markdownMenu->addAction(flowchartAct);
  478. flowchartAct->setChecked(g_config->getEnableFlowchart());
  479. QAction *mathjaxAct = new QAction(tr("Math&Jax"), this);
  480. mathjaxAct->setToolTip(tr("Enable MathJax for math support in Markdown"));
  481. mathjaxAct->setCheckable(true);
  482. connect(mathjaxAct, &QAction::triggered,
  483. this, &VMainWindow::enableMathjax);
  484. markdownMenu->addAction(mathjaxAct);
  485. mathjaxAct->setChecked(g_config->getEnableMathjax());
  486. markdownMenu->addSeparator();
  487. QAction *codeBlockAct = new QAction(tr("Highlight Code Blocks In Edit Mode"), this);
  488. codeBlockAct->setToolTip(tr("Enable syntax highlight within code blocks in edit mode"));
  489. codeBlockAct->setCheckable(true);
  490. connect(codeBlockAct, &QAction::triggered,
  491. this, &VMainWindow::enableCodeBlockHighlight);
  492. markdownMenu->addAction(codeBlockAct);
  493. codeBlockAct->setChecked(g_config->getEnableCodeBlockHighlight());
  494. QAction *lineNumberAct = new QAction(tr("Display Line Number In Code Blocks"), this);
  495. lineNumberAct->setToolTip(tr("Enable line number in code blocks in read mode"));
  496. lineNumberAct->setCheckable(true);
  497. connect(lineNumberAct, &QAction::triggered,
  498. this, [this](bool p_checked){
  499. g_config->setEnableCodeBlockLineNumber(p_checked);
  500. });
  501. markdownMenu->addAction(lineNumberAct);
  502. lineNumberAct->setChecked(g_config->getEnableCodeBlockLineNumber());
  503. QAction *previewImageAct = new QAction(tr("Preview Images In Edit Mode"), this);
  504. previewImageAct->setToolTip(tr("Enable image preview in edit mode (re-open current tabs to make it work)"));
  505. previewImageAct->setCheckable(true);
  506. connect(previewImageAct, &QAction::triggered,
  507. this, &VMainWindow::enableImagePreview);
  508. markdownMenu->addAction(previewImageAct);
  509. previewImageAct->setChecked(g_config->getEnablePreviewImages());
  510. QAction *previewWidthAct = new QAction(tr("Constrain The Width Of Previewed Images"), this);
  511. previewWidthAct->setToolTip(tr("Constrain the width of previewed images to the edit window in edit mode"));
  512. previewWidthAct->setCheckable(true);
  513. connect(previewWidthAct, &QAction::triggered,
  514. this, &VMainWindow::enableImagePreviewConstraint);
  515. markdownMenu->addAction(previewWidthAct);
  516. previewWidthAct->setChecked(g_config->getEnablePreviewImageConstraint());
  517. }
  518. void VMainWindow::initViewMenu()
  519. {
  520. viewMenu = menuBar()->addMenu(tr("&View"));
  521. viewMenu->setToolTipsVisible(true);
  522. }
  523. void VMainWindow::initFileMenu()
  524. {
  525. QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
  526. fileMenu->setToolTipsVisible(true);
  527. // Open external files.
  528. QAction *openAct = new QAction(tr("&Open"), this);
  529. openAct->setToolTip(tr("Open external file to edit"));
  530. connect(openAct, &QAction::triggered,
  531. this, [this](){
  532. static QString lastPath = QDir::homePath();
  533. QStringList files = QFileDialog::getOpenFileNames(this,
  534. tr("Select External Files To Open"),
  535. lastPath);
  536. if (files.isEmpty()) {
  537. return;
  538. }
  539. // Update lastPath
  540. lastPath = QFileInfo(files[0]).path();
  541. openExternalFiles(files);
  542. });
  543. fileMenu->addAction(openAct);
  544. // Import notes from files.
  545. m_importNoteAct = newAction(QIcon(":/resources/icons/import_note.svg"),
  546. tr("&New Notes From Files"), this);
  547. m_importNoteAct->setToolTip(tr("Create notes from external files in current folder by copy"));
  548. connect(m_importNoteAct, &QAction::triggered,
  549. this, &VMainWindow::importNoteFromFile);
  550. m_importNoteAct->setEnabled(false);
  551. fileMenu->addAction(m_importNoteAct);
  552. fileMenu->addSeparator();
  553. // Export as PDF.
  554. m_exportAsPDFAct = new QAction(tr("Export As &PDF"), this);
  555. m_exportAsPDFAct->setToolTip(tr("Export current note as PDF file"));
  556. connect(m_exportAsPDFAct, &QAction::triggered,
  557. this, &VMainWindow::exportAsPDF);
  558. m_exportAsPDFAct->setEnabled(false);
  559. fileMenu->addAction(m_exportAsPDFAct);
  560. fileMenu->addSeparator();
  561. // Print.
  562. m_printAct = new QAction(QIcon(":/resources/icons/print.svg"),
  563. tr("&Print"), this);
  564. m_printAct->setToolTip(tr("Print current note"));
  565. connect(m_printAct, &QAction::triggered,
  566. this, &VMainWindow::printNote);
  567. m_printAct->setEnabled(false);
  568. // Settings.
  569. QAction *settingsAct = newAction(QIcon(":/resources/icons/settings.svg"),
  570. tr("&Settings"), this);
  571. settingsAct->setToolTip(tr("View and change settings for VNote"));
  572. settingsAct->setMenuRole(QAction::PreferencesRole);
  573. connect(settingsAct, &QAction::triggered,
  574. this, &VMainWindow::viewSettings);
  575. fileMenu->addAction(settingsAct);
  576. QAction *editConfigAct = new QAction(tr("Edit Configuration File"), this);
  577. editConfigAct->setToolTip(tr("View and edit configuration file of VNote (vnote.ini)"));
  578. connect(editConfigAct, &QAction::triggered,
  579. this, [this](){
  580. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  581. // On macOS, it seems that we could not open that ini file directly.
  582. QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
  583. #else
  584. QUrl url = QUrl::fromLocalFile(g_config->getConfigFilePath());
  585. #endif
  586. QDesktopServices::openUrl(url);
  587. });
  588. fileMenu->addAction(editConfigAct);
  589. QAction *customShortcutAct = new QAction(tr("Custom Shortcuts"), this);
  590. customShortcutAct->setToolTip(tr("Custom some standard shortcuts"));
  591. connect(customShortcutAct, &QAction::triggered,
  592. this, [this](){
  593. int ret = VUtils::showMessage(QMessageBox::Information,
  594. tr("Custom Shortcuts"),
  595. tr("VNote supports customing some standard shorcuts by "
  596. "editing user's configuration file (vnote.ini). Please "
  597. "reference the shortcuts help documentation for more "
  598. "information."),
  599. tr("Click \"OK\" to custom shortcuts."),
  600. QMessageBox::Ok | QMessageBox::Cancel,
  601. QMessageBox::Ok,
  602. this);
  603. if (ret == QMessageBox::Ok) {
  604. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  605. // On macOS, it seems that we could not open that ini file directly.
  606. QUrl url = QUrl::fromLocalFile(g_config->getConfigFolder());
  607. #else
  608. QUrl url = QUrl::fromLocalFile(g_config->getConfigFilePath());
  609. #endif
  610. QDesktopServices::openUrl(url);
  611. }
  612. });
  613. fileMenu->addAction(customShortcutAct);
  614. fileMenu->addSeparator();
  615. // Exit.
  616. QAction *exitAct = new QAction(tr("&Quit"), this);
  617. exitAct->setToolTip(tr("Quit VNote"));
  618. exitAct->setShortcut(QKeySequence("Ctrl+Q"));
  619. exitAct->setMenuRole(QAction::QuitRole);
  620. connect(exitAct, &QAction::triggered,
  621. this, &VMainWindow::quitApp);
  622. fileMenu->addAction(exitAct);
  623. }
  624. void VMainWindow::quitApp()
  625. {
  626. m_requestQuit = true;
  627. close();
  628. }
  629. void VMainWindow::initEditMenu()
  630. {
  631. QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
  632. editMenu->setToolTipsVisible(true);
  633. // Insert image.
  634. m_insertImageAct = newAction(QIcon(":/resources/icons/insert_image.svg"),
  635. tr("Insert &Image"), this);
  636. m_insertImageAct->setToolTip(tr("Insert an image from file into current note"));
  637. connect(m_insertImageAct, &QAction::triggered,
  638. this, &VMainWindow::insertImage);
  639. // Find/Replace.
  640. m_findReplaceAct = newAction(QIcon(":/resources/icons/find_replace.svg"),
  641. tr("Find/Replace"), this);
  642. m_findReplaceAct->setToolTip(tr("Open Find/Replace dialog to search in current note"));
  643. QString keySeq = g_config->getShortcutKeySequence("Find");
  644. qDebug() << "set Find shortcut to" << keySeq;
  645. m_findReplaceAct->setShortcut(QKeySequence(keySeq));
  646. connect(m_findReplaceAct, &QAction::triggered,
  647. this, &VMainWindow::openFindDialog);
  648. m_findNextAct = new QAction(tr("Find Next"), this);
  649. m_findNextAct->setToolTip(tr("Find next occurence"));
  650. keySeq = g_config->getShortcutKeySequence("FindNext");
  651. qDebug() << "set FindNext shortcut to" << keySeq;
  652. m_findNextAct->setShortcut(QKeySequence(keySeq));
  653. connect(m_findNextAct, SIGNAL(triggered(bool)),
  654. m_findReplaceDialog, SLOT(findNext()));
  655. m_findPreviousAct = new QAction(tr("Find Previous"), this);
  656. m_findPreviousAct->setToolTip(tr("Find previous occurence"));
  657. keySeq = g_config->getShortcutKeySequence("FindPrevious");
  658. qDebug() << "set FindPrevious shortcut to" << keySeq;
  659. m_findPreviousAct->setShortcut(QKeySequence(keySeq));
  660. connect(m_findPreviousAct, SIGNAL(triggered(bool)),
  661. m_findReplaceDialog, SLOT(findPrevious()));
  662. m_replaceAct = new QAction(tr("Replace"), this);
  663. m_replaceAct->setToolTip(tr("Replace current occurence"));
  664. connect(m_replaceAct, SIGNAL(triggered(bool)),
  665. m_findReplaceDialog, SLOT(replace()));
  666. m_replaceFindAct = new QAction(tr("Replace && Find"), this);
  667. m_replaceFindAct->setToolTip(tr("Replace current occurence and find the next one"));
  668. connect(m_replaceFindAct, SIGNAL(triggered(bool)),
  669. m_findReplaceDialog, SLOT(replaceFind()));
  670. m_replaceAllAct = new QAction(tr("Replace All"), this);
  671. m_replaceAllAct->setToolTip(tr("Replace all occurences in current note"));
  672. connect(m_replaceAllAct, SIGNAL(triggered(bool)),
  673. m_findReplaceDialog, SLOT(replaceAll()));
  674. QAction *searchedWordAct = new QAction(tr("Highlight Searched Pattern"), this);
  675. searchedWordAct->setToolTip(tr("Highlight all occurences of searched pattern"));
  676. searchedWordAct->setCheckable(true);
  677. connect(searchedWordAct, &QAction::triggered,
  678. this, &VMainWindow::changeHighlightSearchedWord);
  679. // Expand Tab into spaces.
  680. QAction *expandTabAct = new QAction(tr("&Expand Tab"), this);
  681. expandTabAct->setToolTip(tr("Expand entered Tab to spaces"));
  682. expandTabAct->setCheckable(true);
  683. connect(expandTabAct, &QAction::triggered,
  684. this, &VMainWindow::changeExpandTab);
  685. // Tab stop width.
  686. QActionGroup *tabStopWidthAct = new QActionGroup(this);
  687. QAction *twoSpaceTabAct = new QAction(tr("2 Spaces"), tabStopWidthAct);
  688. twoSpaceTabAct->setToolTip(tr("Expand Tab to 2 spaces"));
  689. twoSpaceTabAct->setCheckable(true);
  690. twoSpaceTabAct->setData(2);
  691. QAction *fourSpaceTabAct = new QAction(tr("4 Spaces"), tabStopWidthAct);
  692. fourSpaceTabAct->setToolTip(tr("Expand Tab to 4 spaces"));
  693. fourSpaceTabAct->setCheckable(true);
  694. fourSpaceTabAct->setData(4);
  695. QAction *eightSpaceTabAct = new QAction(tr("8 Spaces"), tabStopWidthAct);
  696. eightSpaceTabAct->setToolTip(tr("Expand Tab to 8 spaces"));
  697. eightSpaceTabAct->setCheckable(true);
  698. eightSpaceTabAct->setData(8);
  699. connect(tabStopWidthAct, &QActionGroup::triggered,
  700. this, &VMainWindow::setTabStopWidth);
  701. // Auto Indent.
  702. m_autoIndentAct = new QAction(tr("Auto Indent"), this);
  703. m_autoIndentAct->setToolTip(tr("Indent automatically when inserting a new line"));
  704. m_autoIndentAct->setCheckable(true);
  705. connect(m_autoIndentAct, &QAction::triggered,
  706. this, &VMainWindow::changeAutoIndent);
  707. // Auto List.
  708. QAction *autoListAct = new QAction(tr("Auto List"), this);
  709. autoListAct->setToolTip(tr("Continue the list automatically when inserting a new line"));
  710. autoListAct->setCheckable(true);
  711. connect(autoListAct, &QAction::triggered,
  712. this, &VMainWindow::changeAutoList);
  713. // Vim Mode.
  714. QAction *vimAct = new QAction(tr("Vim Mode"), this);
  715. vimAct->setToolTip(tr("Enable Vim mode for editing (re-open current tabs to make it work)"));
  716. vimAct->setCheckable(true);
  717. connect(vimAct, &QAction::triggered,
  718. this, &VMainWindow::changeVimMode);
  719. // Smart input method in Vim mode.
  720. QAction *smartImAct = new QAction(tr("Smart Input Method In Vim Mode"), this);
  721. smartImAct->setToolTip(tr("Disable input method when leaving Insert mode in Vim mode"));
  722. smartImAct->setCheckable(true);
  723. connect(smartImAct, &QAction::triggered,
  724. this, [this](bool p_checked){
  725. g_config->setEnableSmartImInVimMode(p_checked);
  726. });
  727. // Highlight current cursor line.
  728. QAction *cursorLineAct = new QAction(tr("Highlight Cursor Line"), this);
  729. cursorLineAct->setToolTip(tr("Highlight current cursor line"));
  730. cursorLineAct->setCheckable(true);
  731. connect(cursorLineAct, &QAction::triggered,
  732. this, &VMainWindow::changeHighlightCursorLine);
  733. // Highlight selected word.
  734. QAction *selectedWordAct = new QAction(tr("Highlight Selected Words"), this);
  735. selectedWordAct->setToolTip(tr("Highlight all occurences of selected words"));
  736. selectedWordAct->setCheckable(true);
  737. connect(selectedWordAct, &QAction::triggered,
  738. this, &VMainWindow::changeHighlightSelectedWord);
  739. // Highlight trailing space.
  740. QAction *trailingSapceAct = new QAction(tr("Highlight Trailing Sapces"), this);
  741. trailingSapceAct->setToolTip(tr("Highlight all the spaces at the end of a line"));
  742. trailingSapceAct->setCheckable(true);
  743. connect(trailingSapceAct, &QAction::triggered,
  744. this, &VMainWindow::changeHighlightTrailingSapce);
  745. editMenu->addAction(m_insertImageAct);
  746. editMenu->addSeparator();
  747. m_insertImageAct->setEnabled(false);
  748. QMenu *findReplaceMenu = editMenu->addMenu(tr("Find/Replace"));
  749. findReplaceMenu->setToolTipsVisible(true);
  750. findReplaceMenu->addAction(m_findReplaceAct);
  751. findReplaceMenu->addAction(m_findNextAct);
  752. findReplaceMenu->addAction(m_findPreviousAct);
  753. findReplaceMenu->addAction(m_replaceAct);
  754. findReplaceMenu->addAction(m_replaceFindAct);
  755. findReplaceMenu->addAction(m_replaceAllAct);
  756. findReplaceMenu->addSeparator();
  757. findReplaceMenu->addAction(searchedWordAct);
  758. searchedWordAct->setChecked(g_config->getHighlightSearchedWord());
  759. m_findReplaceAct->setEnabled(false);
  760. m_findNextAct->setEnabled(false);
  761. m_findPreviousAct->setEnabled(false);
  762. m_replaceAct->setEnabled(false);
  763. m_replaceFindAct->setEnabled(false);
  764. m_replaceAllAct->setEnabled(false);
  765. editMenu->addSeparator();
  766. editMenu->addAction(expandTabAct);
  767. if (g_config->getIsExpandTab()) {
  768. expandTabAct->setChecked(true);
  769. } else {
  770. expandTabAct->setChecked(false);
  771. }
  772. QMenu *tabStopWidthMenu = editMenu->addMenu(tr("Tab Stop Width"));
  773. tabStopWidthMenu->setToolTipsVisible(true);
  774. tabStopWidthMenu->addAction(twoSpaceTabAct);
  775. tabStopWidthMenu->addAction(fourSpaceTabAct);
  776. tabStopWidthMenu->addAction(eightSpaceTabAct);
  777. int tabStopWidth = g_config->getTabStopWidth();
  778. switch (tabStopWidth) {
  779. case 2:
  780. twoSpaceTabAct->setChecked(true);
  781. break;
  782. case 4:
  783. fourSpaceTabAct->setChecked(true);
  784. break;
  785. case 8:
  786. eightSpaceTabAct->setChecked(true);
  787. break;
  788. default:
  789. qWarning() << "unsupported tab stop width" << tabStopWidth << "in config";
  790. }
  791. editMenu->addAction(m_autoIndentAct);
  792. m_autoIndentAct->setChecked(g_config->getAutoIndent());
  793. editMenu->addAction(autoListAct);
  794. if (g_config->getAutoList()) {
  795. // Let the trigger handler to trigger m_autoIndentAct, too.
  796. autoListAct->trigger();
  797. }
  798. Q_ASSERT(!(autoListAct->isChecked() && !m_autoIndentAct->isChecked()));
  799. editMenu->addAction(vimAct);
  800. vimAct->setChecked(g_config->getEnableVimMode());
  801. editMenu->addAction(smartImAct);
  802. smartImAct->setChecked(g_config->getEnableSmartImInVimMode());
  803. editMenu->addSeparator();
  804. initEditorStyleMenu(editMenu);
  805. initEditorBackgroundMenu(editMenu);
  806. initEditorLineNumberMenu(editMenu);
  807. editMenu->addAction(cursorLineAct);
  808. cursorLineAct->setChecked(g_config->getHighlightCursorLine());
  809. editMenu->addAction(selectedWordAct);
  810. selectedWordAct->setChecked(g_config->getHighlightSelectedWord());
  811. editMenu->addAction(trailingSapceAct);
  812. trailingSapceAct->setChecked(g_config->getEnableTrailingSpaceHighlight());
  813. }
  814. void VMainWindow::initDockWindows()
  815. {
  816. toolDock = new QDockWidget(tr("Tools"), this);
  817. toolDock->setObjectName("tools_dock");
  818. toolDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
  819. toolBox = new QToolBox(this);
  820. outline = new VOutline(this);
  821. connect(editArea, &VEditArea::outlineChanged,
  822. outline, &VOutline::updateOutline);
  823. connect(outline, &VOutline::outlineItemActivated,
  824. editArea, &VEditArea::handleOutlineItemActivated);
  825. connect(editArea, &VEditArea::curHeaderChanged,
  826. outline, &VOutline::updateCurHeader);
  827. toolBox->addItem(outline, QIcon(":/resources/icons/outline.svg"), tr("Outline"));
  828. toolDock->setWidget(toolBox);
  829. addDockWidget(Qt::RightDockWidgetArea, toolDock);
  830. QAction *toggleAct = toolDock->toggleViewAction();
  831. toggleAct->setToolTip(tr("Toggle the tools dock widget"));
  832. viewMenu->addAction(toggleAct);
  833. }
  834. void VMainWindow::initAvatar()
  835. {
  836. m_avatar = new VAvatar(this);
  837. m_avatar->setAvatarPixmap(":/resources/icons/vnote.svg");
  838. m_avatar->setColor(vnote->getColorFromPalette("base-color"), vnote->getColorFromPalette("Indigo4"),
  839. vnote->getColorFromPalette("Teal4"));
  840. m_avatar->hide();
  841. }
  842. void VMainWindow::importNoteFromFile()
  843. {
  844. static QString lastPath = QDir::homePath();
  845. QStringList files = QFileDialog::getOpenFileNames(this,
  846. tr("Select Files (HTML or Markdown) To Create Notes"),
  847. lastPath);
  848. if (files.isEmpty()) {
  849. return;
  850. }
  851. // Update lastPath
  852. lastPath = QFileInfo(files[0]).path();
  853. int failedFiles = 0;
  854. for (int i = 0; i < files.size(); ++i) {
  855. bool ret = fileList->importFile(files[i]);
  856. if (!ret) {
  857. ++failedFiles;
  858. }
  859. }
  860. QMessageBox msgBox(QMessageBox::Information, tr("New Notes From Files"),
  861. tr("Created notes: %1 succeed, %2 failed.")
  862. .arg(files.size() - failedFiles).arg(failedFiles),
  863. QMessageBox::Ok, this);
  864. if (failedFiles > 0) {
  865. msgBox.setInformativeText(tr("Fail to create notes from files maybe due to name conflicts."));
  866. }
  867. msgBox.exec();
  868. }
  869. void VMainWindow::changeMarkdownConverter(QAction *action)
  870. {
  871. if (!action) {
  872. return;
  873. }
  874. MarkdownConverterType type = (MarkdownConverterType)action->data().toInt();
  875. qDebug() << "switch to converter" << type;
  876. g_config->setMarkdownConverterType(type);
  877. }
  878. void VMainWindow::aboutMessage()
  879. {
  880. QString info = tr("<span style=\"font-weight: bold;\">v%1</span>").arg(VConfigManager::c_version);
  881. info += "<br/><br/>";
  882. info += tr("VNote is a Vim-inspired note-taking application for Markdown.");
  883. info += "<br/>";
  884. info += tr("Please visit <a href=\"https://github.com/tamlok/vnote.git\">Github</a> for more information.");
  885. QMessageBox::about(this, tr("About VNote"), info);
  886. }
  887. void VMainWindow::changeExpandTab(bool checked)
  888. {
  889. g_config->setIsExpandTab(checked);
  890. }
  891. void VMainWindow::enableMermaid(bool p_checked)
  892. {
  893. g_config->setEnableMermaid(p_checked);
  894. }
  895. void VMainWindow::enableMathjax(bool p_checked)
  896. {
  897. g_config->setEnableMathjax(p_checked);
  898. }
  899. void VMainWindow::changeHighlightCursorLine(bool p_checked)
  900. {
  901. g_config->setHighlightCursorLine(p_checked);
  902. }
  903. void VMainWindow::changeHighlightSelectedWord(bool p_checked)
  904. {
  905. g_config->setHighlightSelectedWord(p_checked);
  906. }
  907. void VMainWindow::changeHighlightSearchedWord(bool p_checked)
  908. {
  909. g_config->setHighlightSearchedWord(p_checked);
  910. }
  911. void VMainWindow::changeHighlightTrailingSapce(bool p_checked)
  912. {
  913. g_config->setEnableTrailingSapceHighlight(p_checked);
  914. }
  915. void VMainWindow::setTabStopWidth(QAction *action)
  916. {
  917. if (!action) {
  918. return;
  919. }
  920. g_config->setTabStopWidth(action->data().toInt());
  921. }
  922. void VMainWindow::setEditorBackgroundColor(QAction *action)
  923. {
  924. if (!action) {
  925. return;
  926. }
  927. g_config->setCurBackgroundColor(action->data().toString());
  928. }
  929. void VMainWindow::initPredefinedColorPixmaps()
  930. {
  931. const QVector<VColor> &bgColors = g_config->getPredefinedColors();
  932. predefinedColorPixmaps.clear();
  933. int size = 256;
  934. for (int i = 0; i < bgColors.size(); ++i) {
  935. // Generate QPixmap filled in this color
  936. QColor color(VUtils::QRgbFromString(bgColors[i].rgb));
  937. QPixmap pixmap(size, size);
  938. pixmap.fill(color);
  939. predefinedColorPixmaps.append(pixmap);
  940. }
  941. }
  942. void VMainWindow::initConverterMenu(QMenu *p_menu)
  943. {
  944. QMenu *converterMenu = p_menu->addMenu(tr("&Converter"));
  945. converterMenu->setToolTipsVisible(true);
  946. QActionGroup *converterAct = new QActionGroup(this);
  947. QAction *markedAct = new QAction(tr("Marked"), converterAct);
  948. markedAct->setToolTip(tr("Use Marked to convert Markdown to HTML (re-open current tabs to make it work)"));
  949. markedAct->setCheckable(true);
  950. markedAct->setData(int(MarkdownConverterType::Marked));
  951. QAction *hoedownAct = new QAction(tr("Hoedown"), converterAct);
  952. hoedownAct->setToolTip(tr("Use Hoedown to convert Markdown to HTML (re-open current tabs to make it work)"));
  953. hoedownAct->setCheckable(true);
  954. hoedownAct->setData(int(MarkdownConverterType::Hoedown));
  955. QAction *markdownitAct = new QAction(tr("Markdown-it"), converterAct);
  956. markdownitAct->setToolTip(tr("Use Markdown-it to convert Markdown to HTML (re-open current tabs to make it work)"));
  957. markdownitAct->setCheckable(true);
  958. markdownitAct->setData(int(MarkdownConverterType::MarkdownIt));
  959. QAction *showdownAct = new QAction(tr("Showdown"), converterAct);
  960. showdownAct->setToolTip(tr("Use Showdown to convert Markdown to HTML (re-open current tabs to make it work)"));
  961. showdownAct->setCheckable(true);
  962. showdownAct->setData(int(MarkdownConverterType::Showdown));
  963. connect(converterAct, &QActionGroup::triggered,
  964. this, &VMainWindow::changeMarkdownConverter);
  965. converterMenu->addAction(hoedownAct);
  966. converterMenu->addAction(markedAct);
  967. converterMenu->addAction(markdownitAct);
  968. converterMenu->addAction(showdownAct);
  969. MarkdownConverterType converterType = g_config->getMdConverterType();
  970. switch (converterType) {
  971. case MarkdownConverterType::Marked:
  972. markedAct->setChecked(true);
  973. break;
  974. case MarkdownConverterType::Hoedown:
  975. hoedownAct->setChecked(true);
  976. break;
  977. case MarkdownConverterType::MarkdownIt:
  978. markdownitAct->setChecked(true);
  979. break;
  980. case MarkdownConverterType::Showdown:
  981. showdownAct->setChecked(true);
  982. break;
  983. default:
  984. Q_ASSERT(false);
  985. }
  986. }
  987. void VMainWindow::initMarkdownitOptionMenu(QMenu *p_menu)
  988. {
  989. QMenu *optMenu = p_menu->addMenu(tr("Markdown-it Options"));
  990. optMenu->setToolTipsVisible(true);
  991. MarkdownitOption opt = g_config->getMarkdownitOption();
  992. QAction *htmlAct = new QAction(tr("HTML"), this);
  993. htmlAct->setToolTip(tr("Enable HTML tags in source"));
  994. htmlAct->setCheckable(true);
  995. htmlAct->setChecked(opt.m_html);
  996. connect(htmlAct, &QAction::triggered,
  997. this, [this](bool p_checked) {
  998. MarkdownitOption opt = g_config->getMarkdownitOption();
  999. opt.m_html = p_checked;
  1000. g_config->setMarkdownitOption(opt);
  1001. });
  1002. QAction *breaksAct = new QAction(tr("Line Break"), this);
  1003. breaksAct->setToolTip(tr("Convert '\\n' in paragraphs into line break"));
  1004. breaksAct->setCheckable(true);
  1005. breaksAct->setChecked(opt.m_breaks);
  1006. connect(breaksAct, &QAction::triggered,
  1007. this, [this](bool p_checked) {
  1008. MarkdownitOption opt = g_config->getMarkdownitOption();
  1009. opt.m_breaks = p_checked;
  1010. g_config->setMarkdownitOption(opt);
  1011. });
  1012. QAction *linkifyAct = new QAction(tr("Linkify"), this);
  1013. linkifyAct->setToolTip(tr("Convert URL-like text into links"));
  1014. linkifyAct->setCheckable(true);
  1015. linkifyAct->setChecked(opt.m_linkify);
  1016. connect(linkifyAct, &QAction::triggered,
  1017. this, [this](bool p_checked) {
  1018. MarkdownitOption opt = g_config->getMarkdownitOption();
  1019. opt.m_linkify = p_checked;
  1020. g_config->setMarkdownitOption(opt);
  1021. });
  1022. optMenu->addAction(htmlAct);
  1023. optMenu->addAction(breaksAct);
  1024. optMenu->addAction(linkifyAct);
  1025. }
  1026. void VMainWindow::initRenderBackgroundMenu(QMenu *menu)
  1027. {
  1028. QActionGroup *renderBackgroundAct = new QActionGroup(this);
  1029. connect(renderBackgroundAct, &QActionGroup::triggered,
  1030. this, &VMainWindow::setRenderBackgroundColor);
  1031. QMenu *renderBgMenu = menu->addMenu(tr("&Rendering Background"));
  1032. renderBgMenu->setToolTipsVisible(true);
  1033. const QString &curBgColor = g_config->getCurRenderBackgroundColor();
  1034. QAction *tmpAct = new QAction(tr("System"), renderBackgroundAct);
  1035. tmpAct->setToolTip(tr("Use system's background color configuration for Markdown rendering"));
  1036. tmpAct->setCheckable(true);
  1037. tmpAct->setData("System");
  1038. if (curBgColor == "System") {
  1039. tmpAct->setChecked(true);
  1040. }
  1041. renderBgMenu->addAction(tmpAct);
  1042. const QVector<VColor> &bgColors = g_config->getPredefinedColors();
  1043. for (int i = 0; i < bgColors.size(); ++i) {
  1044. tmpAct = new QAction(bgColors[i].name, renderBackgroundAct);
  1045. tmpAct->setToolTip(tr("Set as the background color for Markdown rendering"));
  1046. tmpAct->setCheckable(true);
  1047. tmpAct->setData(bgColors[i].name);
  1048. #if !defined(Q_OS_MACOS) && !defined(Q_OS_MAC)
  1049. tmpAct->setIcon(QIcon(predefinedColorPixmaps[i]));
  1050. #endif
  1051. if (curBgColor == bgColors[i].name) {
  1052. tmpAct->setChecked(true);
  1053. }
  1054. renderBgMenu->addAction(tmpAct);
  1055. }
  1056. }
  1057. void VMainWindow::updateRenderStyleMenu()
  1058. {
  1059. QMenu *menu = dynamic_cast<QMenu *>(sender());
  1060. V_ASSERT(menu);
  1061. QList<QAction *> actions = menu->actions();
  1062. // Remove all other actions except the first one.
  1063. for (int i = 1; i < actions.size(); ++i) {
  1064. menu->removeAction(actions[i]);
  1065. m_renderStyleActs->removeAction(actions[i]);
  1066. delete actions[i];
  1067. }
  1068. // Update the menu actions with styles.
  1069. QVector<QString> styles = g_config->getCssStyles();
  1070. for (auto const &style : styles) {
  1071. QAction *act = new QAction(style, m_renderStyleActs);
  1072. act->setToolTip(tr("Set as the CSS style for Markdown rendering"));
  1073. act->setCheckable(true);
  1074. act->setData(style);
  1075. // Add it to the menu.
  1076. menu->addAction(act);
  1077. if (g_config->getTemplateCss() == style) {
  1078. act->setChecked(true);
  1079. }
  1080. }
  1081. }
  1082. void VMainWindow::initRenderStyleMenu(QMenu *p_menu)
  1083. {
  1084. QMenu *styleMenu = p_menu->addMenu(tr("Rendering &Style"));
  1085. styleMenu->setToolTipsVisible(true);
  1086. connect(styleMenu, &QMenu::aboutToShow,
  1087. this, &VMainWindow::updateRenderStyleMenu);
  1088. m_renderStyleActs = new QActionGroup(this);
  1089. connect(m_renderStyleActs, &QActionGroup::triggered,
  1090. this, &VMainWindow::setRenderStyle);
  1091. QAction *addAct = newAction(QIcon(":/resources/icons/add_style.svg"),
  1092. tr("&Add Style"), m_renderStyleActs);
  1093. addAct->setToolTip(tr("Open the folder to add your custom CSS style files"));
  1094. addAct->setCheckable(true);
  1095. addAct->setData("AddStyle");
  1096. styleMenu->addAction(addAct);
  1097. }
  1098. void VMainWindow::initEditorBackgroundMenu(QMenu *menu)
  1099. {
  1100. QMenu *backgroundColorMenu = menu->addMenu(tr("&Background Color"));
  1101. backgroundColorMenu->setToolTipsVisible(true);
  1102. QActionGroup *backgroundColorAct = new QActionGroup(this);
  1103. connect(backgroundColorAct, &QActionGroup::triggered,
  1104. this, &VMainWindow::setEditorBackgroundColor);
  1105. // System background color
  1106. const QString &curBgColor = g_config->getCurBackgroundColor();
  1107. QAction *tmpAct = new QAction(tr("System"), backgroundColorAct);
  1108. tmpAct->setToolTip(tr("Use system's background color configuration for editor"));
  1109. tmpAct->setCheckable(true);
  1110. tmpAct->setData("System");
  1111. if (curBgColor == "System") {
  1112. tmpAct->setChecked(true);
  1113. }
  1114. backgroundColorMenu->addAction(tmpAct);
  1115. const QVector<VColor> &bgColors = g_config->getPredefinedColors();
  1116. for (int i = 0; i < bgColors.size(); ++i) {
  1117. tmpAct = new QAction(bgColors[i].name, backgroundColorAct);
  1118. tmpAct->setToolTip(tr("Set as the background color for editor"));
  1119. tmpAct->setCheckable(true);
  1120. tmpAct->setData(bgColors[i].name);
  1121. #if !defined(Q_OS_MACOS) && !defined(Q_OS_MAC)
  1122. tmpAct->setIcon(QIcon(predefinedColorPixmaps[i]));
  1123. #endif
  1124. if (curBgColor == bgColors[i].name) {
  1125. tmpAct->setChecked(true);
  1126. }
  1127. backgroundColorMenu->addAction(tmpAct);
  1128. }
  1129. }
  1130. void VMainWindow::initEditorLineNumberMenu(QMenu *p_menu)
  1131. {
  1132. QMenu *lineNumMenu = p_menu->addMenu(tr("Line Number"));
  1133. lineNumMenu->setToolTipsVisible(true);
  1134. QActionGroup *lineNumAct = new QActionGroup(lineNumMenu);
  1135. connect(lineNumAct, &QActionGroup::triggered,
  1136. this, [this](QAction *p_action){
  1137. if (!p_action) {
  1138. return;
  1139. }
  1140. g_config->setEditorLineNumber(p_action->data().toInt());
  1141. });
  1142. int lineNumberMode = g_config->getEditorLineNumber();
  1143. QAction *act = lineNumAct->addAction(tr("None"));
  1144. act->setToolTip(tr("Do not display line number in edit mode"));
  1145. act->setCheckable(true);
  1146. act->setData(0);
  1147. lineNumMenu->addAction(act);
  1148. if (lineNumberMode == 0) {
  1149. act->setChecked(true);
  1150. }
  1151. act = lineNumAct->addAction(tr("Absolute"));
  1152. act->setToolTip(tr("Display absolute line number in edit mode"));
  1153. act->setCheckable(true);
  1154. act->setData(1);
  1155. lineNumMenu->addAction(act);
  1156. if (lineNumberMode == 1) {
  1157. act->setChecked(true);
  1158. }
  1159. act = lineNumAct->addAction(tr("Relative"));
  1160. act->setToolTip(tr("Display line number relative to current cursor line in edit mode"));
  1161. act->setCheckable(true);
  1162. act->setData(2);
  1163. lineNumMenu->addAction(act);
  1164. if (lineNumberMode == 2) {
  1165. act->setChecked(true);
  1166. }
  1167. act = lineNumAct->addAction(tr("CodeBlock"));
  1168. act->setToolTip(tr("Display line number in code block in edit mode (for Markdown only)"));
  1169. act->setCheckable(true);
  1170. act->setData(3);
  1171. lineNumMenu->addAction(act);
  1172. if (lineNumberMode == 3) {
  1173. act->setChecked(true);
  1174. }
  1175. }
  1176. void VMainWindow::updateEditorStyleMenu()
  1177. {
  1178. QMenu *menu = dynamic_cast<QMenu *>(sender());
  1179. V_ASSERT(menu);
  1180. QList<QAction *> actions = menu->actions();
  1181. // Remove all other actions except the first one.
  1182. for (int i = 1; i < actions.size(); ++i) {
  1183. menu->removeAction(actions[i]);
  1184. m_editorStyleActs->removeAction(actions[i]);
  1185. delete actions[i];
  1186. }
  1187. // Update the menu actions with styles.
  1188. QVector<QString> styles = g_config->getEditorStyles();
  1189. for (auto const &style : styles) {
  1190. QAction *act = new QAction(style, m_editorStyleActs);
  1191. act->setToolTip(tr("Set as the editor style"));
  1192. act->setCheckable(true);
  1193. act->setData(style);
  1194. // Add it to the menu.
  1195. menu->addAction(act);
  1196. if (g_config->getEditorStyle() == style) {
  1197. act->setChecked(true);
  1198. }
  1199. }
  1200. }
  1201. void VMainWindow::initEditorStyleMenu(QMenu *p_menu)
  1202. {
  1203. QMenu *styleMenu = p_menu->addMenu(tr("Editor &Style"));
  1204. styleMenu->setToolTipsVisible(true);
  1205. connect(styleMenu, &QMenu::aboutToShow,
  1206. this, &VMainWindow::updateEditorStyleMenu);
  1207. m_editorStyleActs = new QActionGroup(this);
  1208. connect(m_editorStyleActs, &QActionGroup::triggered,
  1209. this, &VMainWindow::setEditorStyle);
  1210. QAction *addAct = newAction(QIcon(":/resources/icons/add_style.svg"),
  1211. tr("&Add Style"), m_editorStyleActs);
  1212. addAct->setToolTip(tr("Open the folder to add your custom MDHL style files"));
  1213. addAct->setCheckable(true);
  1214. addAct->setData("AddStyle");
  1215. styleMenu->addAction(addAct);
  1216. }
  1217. void VMainWindow::setRenderBackgroundColor(QAction *action)
  1218. {
  1219. if (!action) {
  1220. return;
  1221. }
  1222. g_config->setCurRenderBackgroundColor(action->data().toString());
  1223. vnote->updateTemplate();
  1224. }
  1225. void VMainWindow::setRenderStyle(QAction *p_action)
  1226. {
  1227. if (!p_action) {
  1228. return;
  1229. }
  1230. QString data = p_action->data().toString();
  1231. if (data == "AddStyle") {
  1232. // Add custom style.
  1233. QUrl url = QUrl::fromLocalFile(g_config->getStyleConfigFolder());
  1234. QDesktopServices::openUrl(url);
  1235. } else {
  1236. g_config->setTemplateCss(data);
  1237. vnote->updateTemplate();
  1238. }
  1239. }
  1240. void VMainWindow::setEditorStyle(QAction *p_action)
  1241. {
  1242. if (!p_action) {
  1243. return;
  1244. }
  1245. QString data = p_action->data().toString();
  1246. if (data == "AddStyle") {
  1247. // Add custom style.
  1248. QUrl url = QUrl::fromLocalFile(g_config->getStyleConfigFolder());
  1249. QDesktopServices::openUrl(url);
  1250. } else {
  1251. g_config->setEditorStyle(data);
  1252. }
  1253. }
  1254. void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
  1255. bool p_editMode)
  1256. {
  1257. bool systemFile = p_file
  1258. && p_file->getType() == FileType::Orphan
  1259. && dynamic_cast<const VOrphanFile *>(p_file)->isSystemFile();
  1260. m_printAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
  1261. m_exportAsPDFAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
  1262. discardExitAct->setVisible(p_file && p_editMode);
  1263. saveExitAct->setVisible(p_file && p_editMode);
  1264. editNoteAct->setEnabled(p_file && p_file->isModifiable() && !p_editMode);
  1265. editNoteAct->setVisible(!saveExitAct->isVisible());
  1266. saveNoteAct->setEnabled(p_file && p_editMode);
  1267. deleteNoteAct->setEnabled(p_file && p_file->getType() == FileType::Note);
  1268. noteInfoAct->setEnabled(p_file && !systemFile);
  1269. m_insertImageAct->setEnabled(p_file && p_editMode);
  1270. setActionsEnabled(m_editToolBar, p_file && p_editMode);
  1271. // Find/Replace
  1272. m_findReplaceAct->setEnabled(p_file);
  1273. m_findNextAct->setEnabled(p_file);
  1274. m_findPreviousAct->setEnabled(p_file);
  1275. m_replaceAct->setEnabled(p_file && p_editMode);
  1276. m_replaceFindAct->setEnabled(p_file && p_editMode);
  1277. m_replaceAllAct->setEnabled(p_file && p_editMode);
  1278. if (!p_file) {
  1279. m_findReplaceDialog->closeDialog();
  1280. }
  1281. }
  1282. void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
  1283. {
  1284. bool editMode = false;
  1285. m_curTab = p_info.m_editTab;
  1286. if (m_curTab) {
  1287. m_curFile = m_curTab->getFile();
  1288. editMode = m_curTab->isEditMode();
  1289. } else {
  1290. m_curFile = NULL;
  1291. }
  1292. updateActionStateFromTabStatusChange(m_curFile, editMode);
  1293. QString title;
  1294. if (m_curFile) {
  1295. m_findReplaceDialog->updateState(m_curFile->getDocType(), editMode);
  1296. if (m_curFile->getType() == FileType::Note) {
  1297. const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)m_curFile);
  1298. title = QString("[%1] %2").arg(tmpFile->getNotebookName()).arg(tmpFile->fetchPath());
  1299. } else {
  1300. title = QString("%1").arg(m_curFile->fetchPath());
  1301. }
  1302. if (m_curFile->isModifiable()) {
  1303. if (m_curFile->isModified()) {
  1304. title.append('*');
  1305. }
  1306. } else {
  1307. title.append('#');
  1308. }
  1309. }
  1310. updateWindowTitle(title);
  1311. updateStatusInfo(p_info);
  1312. }
  1313. void VMainWindow::onePanelView()
  1314. {
  1315. changeSplitterView(1);
  1316. expandViewAct->setChecked(false);
  1317. m_onePanel = true;
  1318. }
  1319. void VMainWindow::twoPanelView()
  1320. {
  1321. changeSplitterView(2);
  1322. expandViewAct->setChecked(false);
  1323. m_onePanel = false;
  1324. }
  1325. void VMainWindow::toggleOnePanelView()
  1326. {
  1327. if (m_onePanel) {
  1328. twoPanelView();
  1329. } else {
  1330. onePanelView();
  1331. }
  1332. }
  1333. void VMainWindow::expandPanelView(bool p_checked)
  1334. {
  1335. int nrSplits = 0;
  1336. if (p_checked) {
  1337. nrSplits = 0;
  1338. } else {
  1339. if (m_onePanel) {
  1340. nrSplits = 1;
  1341. } else {
  1342. nrSplits = 2;
  1343. }
  1344. }
  1345. changeSplitterView(nrSplits);
  1346. }
  1347. void VMainWindow::changeSplitterView(int nrPanel)
  1348. {
  1349. switch (nrPanel) {
  1350. case 0:
  1351. // Expand
  1352. mainSplitter->widget(0)->hide();
  1353. mainSplitter->widget(1)->hide();
  1354. mainSplitter->widget(2)->show();
  1355. break;
  1356. case 1:
  1357. // Single panel
  1358. mainSplitter->widget(0)->hide();
  1359. mainSplitter->widget(1)->show();
  1360. mainSplitter->widget(2)->show();
  1361. break;
  1362. case 2:
  1363. // Two panels
  1364. mainSplitter->widget(0)->show();
  1365. mainSplitter->widget(1)->show();
  1366. mainSplitter->widget(2)->show();
  1367. break;
  1368. default:
  1369. qWarning() << "invalid panel number" << nrPanel;
  1370. }
  1371. }
  1372. void VMainWindow::updateWindowTitle(const QString &str)
  1373. {
  1374. QString title = "VNote";
  1375. if (!str.isEmpty()) {
  1376. title = title + " - " + str;
  1377. }
  1378. setWindowTitle(title);
  1379. }
  1380. void VMainWindow::curEditFileInfo()
  1381. {
  1382. Q_ASSERT(m_curFile);
  1383. if (m_curFile->getType() == FileType::Note) {
  1384. VNoteFile *file = dynamic_cast<VNoteFile *>((VFile *)m_curFile);
  1385. Q_ASSERT(file);
  1386. fileList->fileInfo(file);
  1387. } else if (m_curFile->getType() == FileType::Orphan) {
  1388. VOrphanFile *file = dynamic_cast<VOrphanFile *>((VFile *)m_curFile);
  1389. Q_ASSERT(file);
  1390. if (!file->isSystemFile()) {
  1391. editOrphanFileInfo(m_curFile);
  1392. }
  1393. }
  1394. }
  1395. void VMainWindow::deleteCurNote()
  1396. {
  1397. if (!m_curFile || m_curFile->getType() != FileType::Note) {
  1398. return;
  1399. }
  1400. VNoteFile *file = dynamic_cast<VNoteFile *>((VFile *)m_curFile);
  1401. fileList->deleteFile(file);
  1402. }
  1403. void VMainWindow::closeEvent(QCloseEvent *event)
  1404. {
  1405. bool isExit = m_requestQuit || !g_config->getMinimizeToStystemTray();
  1406. m_requestQuit = false;
  1407. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  1408. // Do not support minimized to tray on macOS.
  1409. isExit = true;
  1410. #endif
  1411. if (!isExit && g_config->getMinimizeToStystemTray() == -1) {
  1412. // Not initialized yet. Prompt for user.
  1413. int ret = VUtils::showMessage(QMessageBox::Information,
  1414. tr("Close VNote"),
  1415. tr("Do you want to minimize VNote to system tray "
  1416. "instead of quitting it when closing VNote?"),
  1417. tr("You could change the option in Settings later."),
  1418. QMessageBox::Ok | QMessageBox::No | QMessageBox::Cancel,
  1419. QMessageBox::Ok,
  1420. this);
  1421. if (ret == QMessageBox::Ok) {
  1422. g_config->setMinimizeToSystemTray(1);
  1423. } else if (ret == QMessageBox::No) {
  1424. g_config->setMinimizeToSystemTray(0);
  1425. isExit = true;
  1426. } else {
  1427. event->ignore();
  1428. return;
  1429. }
  1430. }
  1431. if (isVisible()) {
  1432. saveStateAndGeometry();
  1433. }
  1434. if (isExit || !m_trayIcon->isVisible()) {
  1435. if (!editArea->closeAllFiles(false)) {
  1436. // Fail to close all the opened files, cancel closing app.
  1437. event->ignore();
  1438. return;
  1439. }
  1440. QMainWindow::closeEvent(event);
  1441. } else {
  1442. hide();
  1443. event->ignore();
  1444. }
  1445. }
  1446. void VMainWindow::saveStateAndGeometry()
  1447. {
  1448. // In one panel view, it will save the wrong state that the directory tree
  1449. // panel has a width of zero.
  1450. twoPanelView();
  1451. g_config->setMainWindowGeometry(saveGeometry());
  1452. g_config->setMainWindowState(saveState());
  1453. g_config->setToolsDockChecked(toolDock->isVisible());
  1454. g_config->setMainSplitterState(mainSplitter->saveState());
  1455. }
  1456. void VMainWindow::restoreStateAndGeometry()
  1457. {
  1458. const QByteArray &geometry = g_config->getMainWindowGeometry();
  1459. if (!geometry.isEmpty()) {
  1460. restoreGeometry(geometry);
  1461. }
  1462. const QByteArray &state = g_config->getMainWindowState();
  1463. if (!state.isEmpty()) {
  1464. restoreState(state);
  1465. }
  1466. toolDock->setVisible(g_config->getToolsDockChecked());
  1467. const QByteArray &splitterState = g_config->getMainSplitterState();
  1468. if (!splitterState.isEmpty()) {
  1469. mainSplitter->restoreState(splitterState);
  1470. }
  1471. }
  1472. const QVector<QPair<QString, QString> >& VMainWindow::getPalette() const
  1473. {
  1474. return vnote->getPalette();
  1475. }
  1476. void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir)
  1477. {
  1478. newNoteAct->setEnabled(p_dir);
  1479. m_importNoteAct->setEnabled(p_dir);
  1480. }
  1481. void VMainWindow::handleCurrentNotebookChanged(const VNotebook *p_notebook)
  1482. {
  1483. newRootDirAct->setEnabled(p_notebook);
  1484. }
  1485. void VMainWindow::resizeEvent(QResizeEvent *event)
  1486. {
  1487. repositionAvatar();
  1488. QMainWindow::resizeEvent(event);
  1489. }
  1490. void VMainWindow::keyPressEvent(QKeyEvent *event)
  1491. {
  1492. int key = event->key();
  1493. Qt::KeyboardModifiers modifiers = event->modifiers();
  1494. if (key == Qt::Key_Escape
  1495. || (key == Qt::Key_BracketLeft
  1496. && modifiers == Qt::ControlModifier)) {
  1497. m_findReplaceDialog->closeDialog();
  1498. event->accept();
  1499. return;
  1500. }
  1501. QMainWindow::keyPressEvent(event);
  1502. }
  1503. void VMainWindow::repositionAvatar()
  1504. {
  1505. int diameter = mainSplitter->pos().y();
  1506. int x = width() - diameter - 5;
  1507. int y = 0;
  1508. qDebug() << "avatar:" << diameter << x << y;
  1509. m_avatar->setDiameter(diameter);
  1510. m_avatar->move(x, y);
  1511. m_avatar->show();
  1512. }
  1513. void VMainWindow::insertImage()
  1514. {
  1515. if (!m_curTab) {
  1516. return;
  1517. }
  1518. Q_ASSERT(m_curTab == editArea->currentEditTab());
  1519. m_curTab->insertImage();
  1520. }
  1521. bool VMainWindow::locateFile(VFile *p_file)
  1522. {
  1523. bool ret = false;
  1524. if (!p_file || p_file->getType() != FileType::Note) {
  1525. return ret;
  1526. }
  1527. VNoteFile *file = dynamic_cast<VNoteFile *>(p_file);
  1528. VNotebook *notebook = file->getNotebook();
  1529. if (notebookSelector->locateNotebook(notebook)) {
  1530. while (directoryTree->currentNotebook() != notebook) {
  1531. QCoreApplication::sendPostedEvents();
  1532. }
  1533. VDirectory *dir = file->getDirectory();
  1534. if (directoryTree->locateDirectory(dir)) {
  1535. while (fileList->currentDirectory() != dir) {
  1536. QCoreApplication::sendPostedEvents();
  1537. }
  1538. if (fileList->locateFile(file)) {
  1539. ret = true;
  1540. fileList->setFocus();
  1541. }
  1542. }
  1543. }
  1544. // Open the directory and file panels after location.
  1545. twoPanelView();
  1546. return ret;
  1547. }
  1548. bool VMainWindow::locateCurrentFile()
  1549. {
  1550. if (m_curFile) {
  1551. return locateFile(m_curFile);
  1552. }
  1553. return false;
  1554. }
  1555. void VMainWindow::handleFindDialogTextChanged(const QString &p_text, uint /* p_options */)
  1556. {
  1557. bool enabled = true;
  1558. if (p_text.isEmpty()) {
  1559. enabled = false;
  1560. }
  1561. m_findNextAct->setEnabled(enabled);
  1562. m_findPreviousAct->setEnabled(enabled);
  1563. m_replaceAct->setEnabled(enabled);
  1564. m_replaceFindAct->setEnabled(enabled);
  1565. m_replaceAllAct->setEnabled(enabled);
  1566. }
  1567. void VMainWindow::openFindDialog()
  1568. {
  1569. m_findReplaceDialog->openDialog(editArea->getSelectedText());
  1570. }
  1571. void VMainWindow::viewSettings()
  1572. {
  1573. VSettingsDialog settingsDialog(this);
  1574. settingsDialog.exec();
  1575. }
  1576. void VMainWindow::handleCaptainModeChanged(bool p_enabled)
  1577. {
  1578. static QString normalBaseColor = m_avatar->getBaseColor();
  1579. static QString captainModeColor = vnote->getColorFromPalette("Purple5");
  1580. if (p_enabled) {
  1581. m_avatar->updateBaseColor(captainModeColor);
  1582. } else {
  1583. m_avatar->updateBaseColor(normalBaseColor);
  1584. }
  1585. }
  1586. void VMainWindow::closeCurrentFile()
  1587. {
  1588. if (m_curFile) {
  1589. editArea->closeFile(m_curFile, false);
  1590. }
  1591. }
  1592. void VMainWindow::changeAutoIndent(bool p_checked)
  1593. {
  1594. g_config->setAutoIndent(p_checked);
  1595. }
  1596. void VMainWindow::changeAutoList(bool p_checked)
  1597. {
  1598. g_config->setAutoList(p_checked);
  1599. if (p_checked) {
  1600. if (!m_autoIndentAct->isChecked()) {
  1601. m_autoIndentAct->trigger();
  1602. }
  1603. m_autoIndentAct->setEnabled(false);
  1604. } else {
  1605. m_autoIndentAct->setEnabled(true);
  1606. }
  1607. }
  1608. void VMainWindow::changeVimMode(bool p_checked)
  1609. {
  1610. g_config->setEnableVimMode(p_checked);
  1611. }
  1612. void VMainWindow::enableCodeBlockHighlight(bool p_checked)
  1613. {
  1614. g_config->setEnableCodeBlockHighlight(p_checked);
  1615. }
  1616. void VMainWindow::enableImagePreview(bool p_checked)
  1617. {
  1618. g_config->setEnablePreviewImages(p_checked);
  1619. }
  1620. void VMainWindow::enableImagePreviewConstraint(bool p_checked)
  1621. {
  1622. g_config->setEnablePreviewImageConstraint(p_checked);
  1623. }
  1624. void VMainWindow::enableImageConstraint(bool p_checked)
  1625. {
  1626. g_config->setEnableImageConstraint(p_checked);
  1627. vnote->updateTemplate();
  1628. }
  1629. void VMainWindow::enableImageCaption(bool p_checked)
  1630. {
  1631. g_config->setEnableImageCaption(p_checked);
  1632. }
  1633. void VMainWindow::shortcutHelp()
  1634. {
  1635. QString locale = VUtils::getLocale();
  1636. QString docName = VNote::c_shortcutsDocFile_en;
  1637. if (locale == "zh_CN") {
  1638. docName = VNote::c_shortcutsDocFile_zh;
  1639. }
  1640. VFile *file = vnote->getOrphanFile(docName, false, true);
  1641. editArea->openFile(file, OpenFileMode::Read);
  1642. }
  1643. void VMainWindow::printNote()
  1644. {
  1645. QPrinter printer;
  1646. QPrintDialog dialog(&printer, this);
  1647. dialog.setWindowTitle(tr("Print Note"));
  1648. V_ASSERT(m_curTab);
  1649. if (m_curFile->getDocType() == DocType::Markdown) {
  1650. VMdTab *mdTab = dynamic_cast<VMdTab *>((VEditTab *)m_curTab);
  1651. VWebView *webView = mdTab->getWebViewer();
  1652. V_ASSERT(webView);
  1653. if (webView->hasSelection()) {
  1654. dialog.addEnabledOption(QAbstractPrintDialog::PrintSelection);
  1655. }
  1656. if (dialog.exec() != QDialog::Accepted) {
  1657. return;
  1658. }
  1659. }
  1660. }
  1661. void VMainWindow::exportAsPDF()
  1662. {
  1663. V_ASSERT(m_curTab);
  1664. V_ASSERT(m_curFile);
  1665. if (m_curFile->getDocType() == DocType::Markdown) {
  1666. VMdTab *mdTab = dynamic_cast<VMdTab *>((VEditTab *)m_curTab);
  1667. VExporter exporter(mdTab->getMarkdownConverterType(), this);
  1668. exporter.exportNote(m_curFile, ExportType::PDF);
  1669. exporter.exec();
  1670. }
  1671. }
  1672. QAction *VMainWindow::newAction(const QIcon &p_icon,
  1673. const QString &p_text,
  1674. QObject *p_parent)
  1675. {
  1676. #if defined(Q_OS_MACOS) || defined(Q_OS_MAC)
  1677. Q_UNUSED(p_icon);
  1678. return new QAction(p_text, p_parent);
  1679. #else
  1680. return new QAction(p_icon, p_text, p_parent);
  1681. #endif
  1682. }
  1683. void VMainWindow::showStatusMessage(const QString &p_msg)
  1684. {
  1685. const int timeout = 3000;
  1686. statusBar()->showMessage(p_msg, timeout);
  1687. }
  1688. void VMainWindow::updateStatusInfo(const VEditTabInfo &p_info)
  1689. {
  1690. if (m_curTab) {
  1691. m_tabIndicator->update(p_info);
  1692. m_tabIndicator->show();
  1693. if (m_curTab->isEditMode()) {
  1694. m_curTab->requestUpdateVimStatus();
  1695. } else {
  1696. m_vimIndicator->hide();
  1697. }
  1698. } else {
  1699. m_tabIndicator->hide();
  1700. m_vimIndicator->hide();
  1701. }
  1702. }
  1703. void VMainWindow::handleVimStatusUpdated(const VVim *p_vim)
  1704. {
  1705. m_vimIndicator->update(p_vim, m_curTab);
  1706. if (!p_vim || !m_curTab || !m_curTab->isEditMode()) {
  1707. m_vimIndicator->hide();
  1708. } else {
  1709. m_vimIndicator->show();
  1710. }
  1711. }
  1712. bool VMainWindow::tryOpenInternalFile(const QString &p_filePath)
  1713. {
  1714. if (p_filePath.isEmpty()) {
  1715. return false;
  1716. }
  1717. if (QFileInfo::exists(p_filePath)) {
  1718. VFile *file = vnote->getInternalFile(p_filePath);
  1719. if (file) {
  1720. editArea->openFile(file, OpenFileMode::Read);
  1721. return true;
  1722. }
  1723. }
  1724. return false;
  1725. }
  1726. void VMainWindow::openExternalFiles(const QStringList &p_files, bool p_forceOrphan)
  1727. {
  1728. qDebug() << "open external files" << p_files;
  1729. for (int i = 0; i < p_files.size(); ++i) {
  1730. VFile *file = NULL;
  1731. if (!p_forceOrphan) {
  1732. file = vnote->getInternalFile(p_files[i]);
  1733. }
  1734. if (!file) {
  1735. file = vnote->getOrphanFile(p_files[i], true);
  1736. }
  1737. editArea->openFile(file, OpenFileMode::Read);
  1738. }
  1739. }
  1740. void VMainWindow::editOrphanFileInfo(VFile *p_file)
  1741. {
  1742. VOrphanFile *file = dynamic_cast<VOrphanFile *>(p_file);
  1743. Q_ASSERT(file);
  1744. VOrphanFileInfoDialog dialog(file, this);
  1745. if (dialog.exec() == QDialog::Accepted) {
  1746. QString imgFolder = dialog.getImageFolder();
  1747. file->setImageFolder(imgFolder);
  1748. }
  1749. }
  1750. void VMainWindow::checkSharedMemory()
  1751. {
  1752. QStringList files = m_guard->fetchFilesToOpen();
  1753. if (!files.isEmpty()) {
  1754. qDebug() << "shared memory fetch files" << files;
  1755. openExternalFiles(files);
  1756. // Eliminate the signal.
  1757. m_guard->fetchAskedToShow();
  1758. showMainWindow();
  1759. } else if (m_guard->fetchAskedToShow()) {
  1760. qDebug() << "shared memory asked to show up";
  1761. showMainWindow();
  1762. }
  1763. }
  1764. void VMainWindow::initTrayIcon()
  1765. {
  1766. QMenu *menu = new QMenu(this);
  1767. QAction *showMainWindowAct = menu->addAction(tr("Show VNote"));
  1768. connect(showMainWindowAct, &QAction::triggered,
  1769. this, &VMainWindow::showMainWindow);
  1770. QAction *exitAct = menu->addAction(tr("Quit"));
  1771. connect(exitAct, &QAction::triggered,
  1772. this, &VMainWindow::quitApp);
  1773. m_trayIcon = new QSystemTrayIcon(QIcon(":/resources/icons/32x32/vnote.png"), this);
  1774. m_trayIcon->setToolTip(tr("VNote"));
  1775. m_trayIcon->setContextMenu(menu);
  1776. connect(m_trayIcon, &QSystemTrayIcon::activated,
  1777. this, [this](QSystemTrayIcon::ActivationReason p_reason){
  1778. if (p_reason == QSystemTrayIcon::Trigger) {
  1779. this->showMainWindow();
  1780. }
  1781. });
  1782. m_trayIcon->show();
  1783. }
  1784. void VMainWindow::changeEvent(QEvent *p_event)
  1785. {
  1786. if (p_event->type() == QEvent::WindowStateChange) {
  1787. QWindowStateChangeEvent *eve = dynamic_cast<QWindowStateChangeEvent *>(p_event);
  1788. m_windowOldState = eve->oldState();
  1789. }
  1790. QMainWindow::changeEvent(p_event);
  1791. }
  1792. void VMainWindow::showMainWindow()
  1793. {
  1794. if (this->isMinimized()) {
  1795. if (m_windowOldState & Qt::WindowMaximized) {
  1796. this->showMaximized();
  1797. } else if (m_windowOldState & Qt::WindowFullScreen) {
  1798. this->showFullScreen();
  1799. } else {
  1800. this->showNormal();
  1801. }
  1802. } else {
  1803. this->show();
  1804. // Need to call raise() in macOS.
  1805. this->raise();
  1806. }
  1807. this->activateWindow();
  1808. }