vmainwindow.cpp 68 KB

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