vnotebookselector.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. #include "vnotebookselector.h"
  2. #include <QDebug>
  3. #include <QJsonObject>
  4. #include <QListWidget>
  5. #include <QAction>
  6. #include <QMenu>
  7. #include <QGuiApplication>
  8. #include <QScreen>
  9. #include <QLabel>
  10. #include <QDesktopServices>
  11. #include <QUrl>
  12. #include "vnotebook.h"
  13. #include "vconfigmanager.h"
  14. #include "dialog/vnewnotebookdialog.h"
  15. #include "dialog/vnotebookinfodialog.h"
  16. #include "dialog/vdeletenotebookdialog.h"
  17. #include "vnotebook.h"
  18. #include "vdirectory.h"
  19. #include "utils/vutils.h"
  20. #include "vnote.h"
  21. #include "veditarea.h"
  22. #include "vnofocusitemdelegate.h"
  23. extern VConfigManager *g_config;
  24. extern VNote *g_vnote;
  25. const int VNotebookSelector::c_notebookStartIdx = 1;
  26. VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
  27. : QComboBox(p_parent), VNavigationMode(),
  28. m_vnote(vnote), m_notebooks(m_vnote->getNotebooks()),
  29. m_editArea(NULL), m_lastValidIndex(-1), m_naviLabel(NULL)
  30. {
  31. m_listWidget = new QListWidget(this);
  32. m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
  33. m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  34. connect(m_listWidget, &QListWidget::customContextMenuRequested,
  35. this, &VNotebookSelector::requestPopupListContextMenu);
  36. setModel(m_listWidget->model());
  37. setView(m_listWidget);
  38. m_listWidget->viewport()->installEventFilter(this);
  39. m_listWidget->installEventFilter(this);
  40. initActions();
  41. connect(this, SIGNAL(currentIndexChanged(int)),
  42. this, SLOT(handleCurIndexChanged(int)));
  43. connect(this, SIGNAL(activated(int)),
  44. this, SLOT(handleItemActivated(int)));
  45. }
  46. void VNotebookSelector::initActions()
  47. {
  48. m_deleteNotebookAct = new QAction(QIcon(":/resources/icons/delete_notebook.svg"),
  49. tr("&Delete"), this);
  50. m_deleteNotebookAct->setToolTip(tr("Delete current notebook"));
  51. connect(m_deleteNotebookAct, SIGNAL(triggered(bool)),
  52. this, SLOT(deleteNotebook()));
  53. m_notebookInfoAct = new QAction(QIcon(":/resources/icons/notebook_info.svg"),
  54. tr("&Info"), this);
  55. m_notebookInfoAct->setToolTip(tr("View and edit current notebook's information"));
  56. connect(m_notebookInfoAct, SIGNAL(triggered(bool)),
  57. this, SLOT(editNotebookInfo()));
  58. m_openLocationAct = new QAction(tr("&Open Notebook Location"), this);
  59. m_openLocationAct->setToolTip(tr("Open the root folder of this notebook in operating system"));
  60. connect(m_openLocationAct, &QAction::triggered,
  61. this, [this]() {
  62. QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
  63. if (items.isEmpty()) {
  64. return;
  65. }
  66. Q_ASSERT(items.size() == 1);
  67. QListWidgetItem *item = items[0];
  68. int index = this->indexOfListItem(item);
  69. VNotebook *notebook = this->getNotebookFromComboIndex(index);
  70. QUrl url = QUrl::fromLocalFile(notebook->getPath());
  71. QDesktopServices::openUrl(url);
  72. });
  73. }
  74. void VNotebookSelector::updateComboBox()
  75. {
  76. int index = g_config->getCurNotebookIndex();
  77. disconnect(this, SIGNAL(currentIndexChanged(int)),
  78. this, SLOT(handleCurIndexChanged(int)));
  79. clear();
  80. m_listWidget->clear();
  81. insertAddNotebookItem();
  82. for (int i = 0; i < m_notebooks.size(); ++i) {
  83. addNotebookItem(m_notebooks[i]->getName());
  84. }
  85. setCurrentIndex(-1);
  86. connect(this, SIGNAL(currentIndexChanged(int)),
  87. this, SLOT(handleCurIndexChanged(int)));
  88. if (m_notebooks.isEmpty()) {
  89. g_config->setCurNotebookIndex(-1);
  90. setCurrentIndex(0);
  91. } else {
  92. setCurrentIndexNotebook(index);
  93. }
  94. qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
  95. }
  96. void VNotebookSelector::setCurrentIndexNotebook(int p_index)
  97. {
  98. if (p_index > -1) {
  99. p_index += c_notebookStartIdx;
  100. }
  101. setCurrentIndex(p_index);
  102. }
  103. void VNotebookSelector::insertAddNotebookItem()
  104. {
  105. QListWidgetItem *item = new QListWidgetItem();
  106. item->setIcon(QIcon(":/resources/icons/create_notebook.svg"));
  107. item->setText(tr("Add Notebook"));
  108. QFont font;
  109. font.setItalic(true);
  110. item->setData(Qt::FontRole, font);
  111. item->setToolTip(tr("Create or import a notebook"));
  112. m_listWidget->insertItem(0, item);
  113. }
  114. void VNotebookSelector::handleCurIndexChanged(int p_index)
  115. {
  116. qDebug() << "current index changed" << p_index << "startIdx" << c_notebookStartIdx;
  117. VNotebook *nb = NULL;
  118. if (p_index > -1) {
  119. if (p_index < c_notebookStartIdx) {
  120. // Click a special action item.
  121. if (m_listWidget->count() == c_notebookStartIdx) {
  122. // There is no regular notebook item. Just let it be selected.
  123. p_index = -1;
  124. } else {
  125. // handleItemActivated() will handle the logics.
  126. return;
  127. }
  128. } else {
  129. int nbIdx = p_index - c_notebookStartIdx;
  130. Q_ASSERT(nbIdx >= 0);
  131. nb = m_notebooks[nbIdx];
  132. }
  133. }
  134. m_lastValidIndex = p_index;
  135. QString tooltip;
  136. if (p_index > -1) {
  137. p_index -= c_notebookStartIdx;
  138. tooltip = nb->getName();
  139. }
  140. setToolTip(tooltip);
  141. g_config->setCurNotebookIndex(p_index);
  142. emit curNotebookChanged(nb);
  143. }
  144. void VNotebookSelector::handleItemActivated(int p_index)
  145. {
  146. if (p_index > -1 && p_index < c_notebookStartIdx) {
  147. // Click a special action item
  148. if (m_lastValidIndex > -1) {
  149. setCurrentIndex(m_lastValidIndex);
  150. }
  151. newNotebook();
  152. }
  153. }
  154. void VNotebookSelector::update()
  155. {
  156. updateComboBox();
  157. }
  158. bool VNotebookSelector::newNotebook()
  159. {
  160. QString info(tr("Please type the name of the notebook and "
  161. "choose a folder as the Root Folder of the notebook."));
  162. info += "\n";
  163. info += tr("* The root folder should be used EXCLUSIVELY by VNote and "
  164. "it is recommended to be EMPTY.");
  165. info += "\n";
  166. info += tr("* A previously created notebook could be imported into VNote "
  167. "by choosing its root folder.");
  168. QString defaultName;
  169. QString defaultPath;
  170. // Use empty default name and path to let the dialog to auto generate a name
  171. // under the default VNote notebook folder.
  172. VNewNotebookDialog dialog(tr("Add Notebook"), info, defaultName,
  173. defaultPath, m_notebooks, this);
  174. if (dialog.exec() == QDialog::Accepted) {
  175. createNotebook(dialog.getNameInput(),
  176. dialog.getPathInput(),
  177. dialog.isImportExistingNotebook(),
  178. dialog.getImageFolder());
  179. emit notebookCreated();
  180. return true;
  181. }
  182. return false;
  183. }
  184. void VNotebookSelector::createNotebook(const QString &p_name,
  185. const QString &p_path,
  186. bool p_import,
  187. const QString &p_imageFolder)
  188. {
  189. VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
  190. p_imageFolder, m_vnote);
  191. if (!nb) {
  192. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  193. tr("Fail to create notebook "
  194. "<span style=\"%1\">%2</span> in <span style=\"%1\">%3</span>.")
  195. .arg(g_config->c_dataTextStyle).arg(p_name).arg(p_path), "",
  196. QMessageBox::Ok, QMessageBox::Ok, this);
  197. return;
  198. }
  199. m_notebooks.append(nb);
  200. g_config->setNotebooks(m_notebooks);
  201. addNotebookItem(nb->getName());
  202. setCurrentIndexNotebook(m_notebooks.size() - 1);
  203. }
  204. void VNotebookSelector::deleteNotebook()
  205. {
  206. QList<QListWidgetItem *> items = m_listWidget->selectedItems();
  207. if (items.isEmpty()) {
  208. return;
  209. }
  210. Q_ASSERT(items.size() == 1);
  211. QListWidgetItem *item = items[0];
  212. int index = indexOfListItem(item);
  213. VNotebook *notebook = getNotebookFromComboIndex(index);
  214. Q_ASSERT(notebook);
  215. VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook->getName(), notebook->getPath(), this);
  216. if (dialog.exec() == QDialog::Accepted) {
  217. bool deleteFiles = dialog.getDeleteFiles();
  218. m_editArea->closeFile(notebook, true);
  219. deleteNotebook(notebook, deleteFiles);
  220. }
  221. }
  222. void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
  223. {
  224. V_ASSERT(p_notebook);
  225. int idx = indexOfNotebook(p_notebook);
  226. m_notebooks.remove(idx);
  227. g_config->setNotebooks(m_notebooks);
  228. removeNotebookItem(idx);
  229. QString name(p_notebook->getName());
  230. QString path(p_notebook->getPath());
  231. bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
  232. if (!ret) {
  233. // Notebook could not be deleted completely.
  234. int cho = VUtils::showMessage(QMessageBox::Information, tr("Delete Notebook Folder From Disk"),
  235. tr("Fail to delete the root folder of notebook "
  236. "<span style=\"%1\">%2</span> from disk. You may open "
  237. "the folder and check it manually.")
  238. .arg(g_config->c_dataTextStyle).arg(name), "",
  239. QMessageBox::Open | QMessageBox::Ok,
  240. QMessageBox::Ok, this);
  241. if (cho == QMessageBox::Open) {
  242. // Open the notebook location.
  243. QUrl url = QUrl::fromLocalFile(path);
  244. QDesktopServices::openUrl(url);
  245. }
  246. }
  247. }
  248. int VNotebookSelector::indexOfNotebook(const VNotebook *p_notebook)
  249. {
  250. for (int i = 0; i < m_notebooks.size(); ++i) {
  251. if (m_notebooks[i] == p_notebook) {
  252. return i;
  253. }
  254. }
  255. return -1;
  256. }
  257. void VNotebookSelector::editNotebookInfo()
  258. {
  259. QList<QListWidgetItem *> items = m_listWidget->selectedItems();
  260. if (items.isEmpty()) {
  261. return;
  262. }
  263. Q_ASSERT(items.size() == 1);
  264. QListWidgetItem *item = items[0];
  265. int index = indexOfListItem(item);
  266. VNotebook *notebook = getNotebookFromComboIndex(index);
  267. QString curName = notebook->getName();
  268. VNotebookInfoDialog dialog(tr("Notebook Information"), "", notebook,
  269. m_notebooks, this);
  270. if (dialog.exec() == QDialog::Accepted) {
  271. bool updated = false;
  272. QString name = dialog.getName();
  273. if (name != curName) {
  274. updated = true;
  275. notebook->rename(name);
  276. updateComboBoxItem(index, name);
  277. g_config->setNotebooks(m_notebooks);
  278. }
  279. QString imageFolder = dialog.getImageFolder();
  280. if (imageFolder != notebook->getImageFolderConfig()) {
  281. updated = true;
  282. notebook->setImageFolder(imageFolder);
  283. notebook->writeConfig();
  284. }
  285. if (updated) {
  286. emit notebookUpdated(notebook);
  287. }
  288. }
  289. }
  290. void VNotebookSelector::addNotebookItem(const QString &p_name)
  291. {
  292. QListWidgetItem *item = new QListWidgetItem(m_listWidget);
  293. item->setText(p_name);
  294. item->setToolTip(p_name);
  295. item->setIcon(QIcon(":/resources/icons/notebook_item.svg"));
  296. }
  297. void VNotebookSelector::removeNotebookItem(int p_index)
  298. {
  299. QListWidgetItem *item = m_listWidget->item(p_index + c_notebookStartIdx);
  300. m_listWidget->removeItemWidget(item);
  301. delete item;
  302. }
  303. void VNotebookSelector::updateComboBoxItem(int p_index, const QString &p_name)
  304. {
  305. QListWidgetItem *item = m_listWidget->item(p_index);
  306. item->setText(p_name);
  307. item->setToolTip(p_name);
  308. }
  309. void VNotebookSelector::requestPopupListContextMenu(QPoint p_pos)
  310. {
  311. QModelIndex index = m_listWidget->indexAt(p_pos);
  312. if (!index.isValid() || index.row() < c_notebookStartIdx) {
  313. return;
  314. }
  315. QListWidgetItem *item = m_listWidget->itemAt(p_pos);
  316. Q_ASSERT(item);
  317. m_listWidget->clearSelection();
  318. item->setSelected(true);
  319. QMenu menu(this);
  320. menu.setToolTipsVisible(true);
  321. menu.addAction(m_deleteNotebookAct);
  322. menu.addAction(m_openLocationAct);
  323. menu.addAction(m_notebookInfoAct);
  324. menu.exec(m_listWidget->mapToGlobal(p_pos));
  325. }
  326. bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
  327. {
  328. QEvent::Type type = event->type();
  329. if (type == QEvent::KeyPress && watched == m_listWidget) {
  330. if (handlePopupKeyPress(static_cast<QKeyEvent *>(event))) {
  331. return true;
  332. }
  333. } else if (type == QEvent::MouseButtonRelease) {
  334. if (static_cast<QMouseEvent *>(event)->button() == Qt::RightButton) {
  335. return true;
  336. }
  337. }
  338. return QComboBox::eventFilter(watched, event);
  339. }
  340. int VNotebookSelector::indexOfListItem(const QListWidgetItem *p_item)
  341. {
  342. int nrItems = m_listWidget->count();
  343. for (int i = 0; i < nrItems; ++i) {
  344. if (m_listWidget->item(i) == p_item) {
  345. return i;
  346. }
  347. }
  348. return -1;
  349. }
  350. bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
  351. {
  352. if (p_notebook) {
  353. for (int i = 0; i < m_notebooks.size(); ++i) {
  354. if (m_notebooks[i] == p_notebook) {
  355. setCurrentIndexNotebook(i);
  356. return true;
  357. }
  358. }
  359. }
  360. return false;
  361. }
  362. void VNotebookSelector::showPopup()
  363. {
  364. if (count() <= c_notebookStartIdx) {
  365. // No normal notebook items. Just add notebook.
  366. newNotebook();
  367. return;
  368. }
  369. resizeListWidgetToContent();
  370. QComboBox::showPopup();
  371. }
  372. void VNotebookSelector::resizeListWidgetToContent()
  373. {
  374. static QRect screenRect = QGuiApplication::primaryScreen()->geometry();
  375. static int maxMinWidth = screenRect.width() < 400 ? screenRect.width() : screenRect.width() / 2;
  376. static int maxMinHeight = screenRect.height() < 400 ? screenRect.height() : screenRect.height() / 2;
  377. int minWidth = 0;
  378. int minHeight = 0;
  379. if (m_listWidget->count() > 0) {
  380. // Width
  381. minWidth = m_listWidget->sizeHintForColumn(0);
  382. minWidth = qMin(minWidth, maxMinWidth);
  383. // Height
  384. minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
  385. minHeight = qMin(minHeight, maxMinHeight);
  386. }
  387. m_listWidget->setMinimumSize(minWidth, minHeight);
  388. }
  389. void VNotebookSelector::registerNavigation(QChar p_majorKey)
  390. {
  391. Q_ASSERT(!m_naviLabel);
  392. m_majorKey = p_majorKey;
  393. }
  394. void VNotebookSelector::showNavigation()
  395. {
  396. if (!isVisible()) {
  397. return;
  398. }
  399. V_ASSERT(!m_naviLabel);
  400. m_naviLabel = new QLabel(m_majorKey, this);
  401. m_naviLabel->setStyleSheet(g_vnote->getNavigationLabelStyle(m_majorKey));
  402. m_naviLabel->show();
  403. }
  404. void VNotebookSelector::hideNavigation()
  405. {
  406. if (m_naviLabel) {
  407. delete m_naviLabel;
  408. m_naviLabel = NULL;
  409. }
  410. }
  411. bool VNotebookSelector::handleKeyNavigation(int p_key, bool &p_succeed)
  412. {
  413. bool ret = false;
  414. p_succeed = false;
  415. QChar keyChar = VUtils::keyToChar(p_key);
  416. if (keyChar == m_majorKey) {
  417. // Hit.
  418. p_succeed = true;
  419. ret = true;
  420. if (m_naviLabel) {
  421. showPopup();
  422. }
  423. }
  424. return ret;
  425. }
  426. bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
  427. {
  428. int key = p_event->key();
  429. int modifiers = p_event->modifiers();
  430. switch (key) {
  431. case Qt::Key_BracketLeft:
  432. {
  433. if (modifiers == Qt::ControlModifier) {
  434. p_event->accept();
  435. hidePopup();
  436. return true;
  437. }
  438. break;
  439. }
  440. case Qt::Key_J:
  441. {
  442. if (modifiers == Qt::ControlModifier) {
  443. p_event->accept();
  444. QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
  445. Qt::NoModifier);
  446. QCoreApplication::postEvent(m_listWidget, downEvent);
  447. return true;
  448. }
  449. break;
  450. }
  451. case Qt::Key_K:
  452. {
  453. if (modifiers == Qt::ControlModifier) {
  454. p_event->accept();
  455. QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
  456. Qt::NoModifier);
  457. QCoreApplication::postEvent(m_listWidget, upEvent);
  458. return true;
  459. }
  460. break;
  461. }
  462. default:
  463. break;
  464. }
  465. return false;
  466. }