vopenedlistmenu.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #include "vopenedlistmenu.h"
  2. #include <QActionGroup>
  3. #include <QAction>
  4. #include <QFont>
  5. #include <QVector>
  6. #include <QTimer>
  7. #include <QString>
  8. #include <QStyleFactory>
  9. #include "veditwindow.h"
  10. #include "vnotefile.h"
  11. #include "vedittab.h"
  12. #include "vdirectory.h"
  13. #include "utils/vutils.h"
  14. static const int c_cmdTime = 1 * 1000;
  15. static bool fileComp(const VOpenedListMenu::ItemInfo &a,
  16. const VOpenedListMenu::ItemInfo &b)
  17. {
  18. QString notebooka, notebookb;
  19. if (a.file->getType() == FileType::Note) {
  20. notebooka = dynamic_cast<const VNoteFile *>(a.file)->getNotebookName().toLower();
  21. } else {
  22. notebooka = "EXTERNAL_FILES";
  23. }
  24. if (b.file->getType() == FileType::Note) {
  25. notebookb = dynamic_cast<const VNoteFile *>(b.file)->getNotebookName().toLower();
  26. } else {
  27. notebookb = "EXTERNAL_FILES";
  28. }
  29. if (notebooka < notebookb) {
  30. return true;
  31. } else if (notebooka > notebookb) {
  32. return false;
  33. } else {
  34. QString patha = a.file->fetchBasePath();
  35. QString pathb = b.file->fetchBasePath();
  36. #if defined(Q_OS_WIN)
  37. patha = patha.toLower();
  38. pathb = pathb.toLower();
  39. #endif
  40. if (patha == pathb) {
  41. return a.index < b.index;
  42. } else {
  43. return patha < pathb;
  44. }
  45. }
  46. }
  47. VOpenedListMenu::VOpenedListMenu(VEditWindow *p_editWin)
  48. : QMenu(p_editWin), m_editWin(p_editWin), m_cmdNum(0)
  49. {
  50. // Force to display separator text on Windows and macOS.
  51. setStyle(QStyleFactory::create("Fusion"));
  52. int separatorHeight = 20 * VUtils::calculateScaleFactor();
  53. QString style = QString("::separator { color: #009688; height: %1px; padding-top: 3px; }").arg(separatorHeight);
  54. setStyleSheet(style);
  55. setToolTipsVisible(true);
  56. m_cmdTimer = new QTimer(this);
  57. m_cmdTimer->setSingleShot(true);
  58. m_cmdTimer->setInterval(c_cmdTime);
  59. connect(m_cmdTimer, &QTimer::timeout,
  60. this, &VOpenedListMenu::cmdTimerTimeout);
  61. connect(this, &QMenu::aboutToShow,
  62. this, &VOpenedListMenu::updateOpenedList);
  63. connect(this, &QMenu::triggered,
  64. this, &VOpenedListMenu::handleItemTriggered);
  65. }
  66. void VOpenedListMenu::updateOpenedList()
  67. {
  68. // Regenerate the opened list.
  69. m_seqActionMap.clear();
  70. clear();
  71. int curTab = m_editWin->currentIndex();
  72. int nrTab = m_editWin->count();
  73. QVector<ItemInfo> files(nrTab);
  74. for (int i = 0; i < nrTab; ++i) {
  75. files[i].file = m_editWin->getTab(i)->getFile();
  76. files[i].index = i;
  77. }
  78. std::sort(files.begin(), files.end(), fileComp);
  79. QString notebook;
  80. const VDirectory *directory = NULL;
  81. QFont sepFont;
  82. sepFont.setItalic(true);
  83. for (int i = 0; i < nrTab; ++i) {
  84. QPointer<VFile> file = files[i].file;
  85. int index = files[i].index;
  86. // Whether add separator.
  87. QString curNotebook;
  88. const VDirectory *curDirectory = NULL;
  89. if (file->getType() == FileType::Note) {
  90. const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>((VFile *)file);
  91. curNotebook = tmpFile->getNotebookName();
  92. curDirectory = tmpFile->getDirectory();
  93. } else {
  94. curNotebook = "EXTERNAL_FILES";
  95. }
  96. if (curNotebook != notebook
  97. || curDirectory != directory) {
  98. notebook = curNotebook;
  99. directory = curDirectory;
  100. QString dirName;
  101. if (directory) {
  102. dirName = directory->getName();
  103. }
  104. QString text;
  105. if (dirName.isEmpty()) {
  106. text = QString("[%1]").arg(notebook);
  107. } else {
  108. text = QString("[%1] %2").arg(notebook).arg(dirName);
  109. }
  110. QAction *sepAct = addSection(text);
  111. sepAct->setFont(sepFont);
  112. }
  113. QAction *action = new QAction(m_editWin->tabIcon(index),
  114. m_editWin->tabText(index));
  115. action->setToolTip(generateDescription(file));
  116. action->setData(QVariant::fromValue(file));
  117. if (index == curTab) {
  118. QFont boldFont;
  119. boldFont.setBold(true);
  120. action->setFont(boldFont);
  121. }
  122. addAction(action);
  123. m_seqActionMap[index + c_tabSequenceBase] = action;
  124. }
  125. }
  126. QString VOpenedListMenu::generateDescription(const VFile *p_file) const
  127. {
  128. if (!p_file) {
  129. return "";
  130. }
  131. // [Notebook]path
  132. if (p_file->getType() == FileType::Note) {
  133. const VNoteFile *tmpFile = dynamic_cast<const VNoteFile *>(p_file);
  134. return QString("[%1] %2").arg(tmpFile->getNotebookName()).arg(tmpFile->fetchPath());
  135. } else {
  136. return QString("%1").arg(p_file->fetchPath());
  137. }
  138. }
  139. void VOpenedListMenu::handleItemTriggered(QAction *p_action)
  140. {
  141. if (!p_action) {
  142. return;
  143. }
  144. QPointer<VFile> file = p_action->data().value<QPointer<VFile>>();
  145. emit fileTriggered(file);
  146. }
  147. void VOpenedListMenu::keyPressEvent(QKeyEvent *p_event)
  148. {
  149. int key = p_event->key();
  150. int modifiers = p_event->modifiers();
  151. switch (key) {
  152. case Qt::Key_0:
  153. case Qt::Key_1:
  154. case Qt::Key_2:
  155. case Qt::Key_3:
  156. case Qt::Key_4:
  157. case Qt::Key_5:
  158. case Qt::Key_6:
  159. case Qt::Key_7:
  160. case Qt::Key_8:
  161. case Qt::Key_9:
  162. {
  163. addDigit(key - Qt::Key_0);
  164. return;
  165. }
  166. case Qt::Key_BracketLeft:
  167. {
  168. m_cmdTimer->stop();
  169. m_cmdNum = 0;
  170. if (modifiers == Qt::ControlModifier) {
  171. hide();
  172. return;
  173. }
  174. break;
  175. }
  176. case Qt::Key_J:
  177. {
  178. m_cmdTimer->stop();
  179. m_cmdNum = 0;
  180. if (modifiers == Qt::ControlModifier) {
  181. QList<QAction *> acts = actions();
  182. if (acts.size() == 0) {
  183. return;
  184. }
  185. int idx = 0;
  186. QAction *act = activeAction();
  187. if (act) {
  188. for (int i = 0; i < acts.size(); ++i) {
  189. if (acts.at(i) == act) {
  190. idx = i + 1;
  191. break;
  192. }
  193. }
  194. }
  195. while (true) {
  196. if (idx >= acts.size()) {
  197. idx = 0;
  198. }
  199. act = acts.at(idx);
  200. if (act->isSeparator() || !act->isVisible()) {
  201. ++idx;
  202. } else {
  203. break;
  204. }
  205. }
  206. setActiveAction(act);
  207. return;
  208. }
  209. break;
  210. }
  211. case Qt::Key_K:
  212. {
  213. m_cmdTimer->stop();
  214. m_cmdNum = 0;
  215. if (modifiers == Qt::ControlModifier) {
  216. QList<QAction *> acts = actions();
  217. if (acts.size() == 0) {
  218. return;
  219. }
  220. int idx = acts.size() - 1;
  221. QAction *act = activeAction();
  222. if (act) {
  223. for (int i = 0; i < acts.size(); ++i) {
  224. if (acts.at(i) == act) {
  225. idx = i - 1;
  226. break;
  227. }
  228. }
  229. }
  230. while (true) {
  231. if (idx < 0) {
  232. idx = acts.size() - 1;
  233. }
  234. act = acts.at(idx);
  235. if (act->isSeparator() || !act->isVisible()) {
  236. --idx;
  237. } else {
  238. break;
  239. }
  240. }
  241. setActiveAction(act);
  242. return;
  243. }
  244. break;
  245. }
  246. default:
  247. m_cmdTimer->stop();
  248. m_cmdNum = 0;
  249. break;
  250. }
  251. QMenu::keyPressEvent(p_event);
  252. }
  253. void VOpenedListMenu::cmdTimerTimeout()
  254. {
  255. if (m_cmdNum > 0) {
  256. triggerItem(m_cmdNum);
  257. m_cmdNum = 0;
  258. }
  259. }
  260. void VOpenedListMenu::addDigit(int p_digit)
  261. {
  262. V_ASSERT(p_digit >= 0 && p_digit <= 9);
  263. m_cmdTimer->stop();
  264. m_cmdNum = m_cmdNum * 10 + p_digit;
  265. int totalItem = m_seqActionMap.size();
  266. // Try to trigger it ASAP.
  267. if (m_cmdNum > 0) {
  268. if (getNumOfDigit(m_cmdNum) == getNumOfDigit(totalItem)) {
  269. triggerItem(m_cmdNum);
  270. m_cmdNum = 0;
  271. return;
  272. }
  273. // Set active action to the candidate.
  274. auto it = m_seqActionMap.find(m_cmdNum);
  275. if (it != m_seqActionMap.end()) {
  276. QAction *act = it.value();
  277. setActiveAction(act);
  278. }
  279. }
  280. m_cmdTimer->start();
  281. }
  282. int VOpenedListMenu::getNumOfDigit(int p_num)
  283. {
  284. int nrDigit = 1;
  285. while (true) {
  286. p_num /= 10;
  287. if (p_num == 0) {
  288. return nrDigit;
  289. } else {
  290. ++nrDigit;
  291. }
  292. }
  293. }
  294. void VOpenedListMenu::triggerItem(int p_seq)
  295. {
  296. auto it = m_seqActionMap.find(p_seq);
  297. if (it != m_seqActionMap.end()) {
  298. QAction *act = it.value();
  299. act->trigger();
  300. hide();
  301. }
  302. }