vmainwindow.cpp 73 KB

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