vnotebookselector.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  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. #include "vmainwindow.h"
  24. #include "utils/vimnavigationforwidget.h"
  25. #include "utils/viconutils.h"
  26. extern VConfigManager *g_config;
  27. extern VNote *g_vnote;
  28. extern VMainWindow *g_mainWin;
  29. VNotebookSelector::VNotebookSelector(QWidget *p_parent)
  30. : QComboBox(p_parent),
  31. VNavigationMode(),
  32. m_notebooks(g_vnote->getNotebooks()),
  33. m_lastValidIndex(-1),
  34. m_muted(false),
  35. m_naviLabel(NULL)
  36. {
  37. m_listWidget = new QListWidget(this);
  38. m_listWidget->setItemDelegate(new VNoFocusItemDelegate(this));
  39. m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
  40. connect(m_listWidget, &QListWidget::customContextMenuRequested,
  41. this, &VNotebookSelector::popupListContextMenuRequested);
  42. setModel(m_listWidget->model());
  43. setView(m_listWidget);
  44. m_listWidget->viewport()->installEventFilter(this);
  45. m_listWidget->installEventFilter(this);
  46. initActions();
  47. connect(this, SIGNAL(currentIndexChanged(int)),
  48. this, SLOT(handleCurIndexChanged(int)));
  49. }
  50. void VNotebookSelector::initActions()
  51. {
  52. m_deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"),
  53. tr("&Delete"), this);
  54. m_deleteNotebookAct->setToolTip(tr("Delete current notebook"));
  55. connect(m_deleteNotebookAct, SIGNAL(triggered(bool)),
  56. this, SLOT(deleteNotebook()));
  57. m_notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"),
  58. tr("&Info"), this);
  59. m_notebookInfoAct->setToolTip(tr("View and edit current notebook's information"));
  60. connect(m_notebookInfoAct, SIGNAL(triggered(bool)),
  61. this, SLOT(editNotebookInfo()));
  62. m_openLocationAct = new QAction(tr("&Open Notebook Location"), this);
  63. m_openLocationAct->setToolTip(tr("Open the root folder of this notebook in operating system"));
  64. connect(m_openLocationAct, &QAction::triggered,
  65. this, [this]() {
  66. QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
  67. if (items.isEmpty()) {
  68. return;
  69. }
  70. Q_ASSERT(items.size() == 1);
  71. VNotebook *notebook = getNotebook(items[0]);
  72. QUrl url = QUrl::fromLocalFile(notebook->getPath());
  73. QDesktopServices::openUrl(url);
  74. });
  75. m_recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"),
  76. tr("&Recycle Bin"), this);
  77. m_recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook"));
  78. connect(m_recycleBinAct, &QAction::triggered,
  79. this, [this]() {
  80. QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
  81. if (items.isEmpty()) {
  82. return;
  83. }
  84. Q_ASSERT(items.size() == 1);
  85. VNotebook *notebook = getNotebook(items[0]);
  86. QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath());
  87. QDesktopServices::openUrl(url);
  88. });
  89. m_emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"),
  90. tr("&Empty Recycle Bin"), this);
  91. m_emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook"));
  92. connect(m_emptyRecycleBinAct, &QAction::triggered,
  93. this, [this]() {
  94. QList<QListWidgetItem *> items = this->m_listWidget->selectedItems();
  95. if (items.isEmpty()) {
  96. return;
  97. }
  98. Q_ASSERT(items.size() == 1);
  99. VNotebook *notebook = getNotebook(items[0]);
  100. QString binPath = notebook->getRecycleBinFolderPath();
  101. int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  102. tr("Are you sure to empty recycle bin of notebook "
  103. "<span style=\"%1\">%2</span>?")
  104. .arg(g_config->c_dataTextStyle)
  105. .arg(notebook->getName()),
  106. tr("<span style=\"%1\">WARNING</span>: "
  107. "VNote will delete all the files in directory "
  108. "<span style=\"%2\">%3</span>."
  109. "<br>It may be UNRECOVERABLE!")
  110. .arg(g_config->c_warningTextStyle)
  111. .arg(g_config->c_dataTextStyle)
  112. .arg(binPath),
  113. QMessageBox::Ok | QMessageBox::Cancel,
  114. QMessageBox::Ok,
  115. this,
  116. MessageBoxType::Danger);
  117. if (ret == QMessageBox::Ok) {
  118. QString info;
  119. if (VUtils::emptyDirectory(notebook, binPath, true)) {
  120. info = tr("Successfully emptied recycle bin of notebook "
  121. "<span style=\"%1\">%2</span>!")
  122. .arg(g_config->c_dataTextStyle)
  123. .arg(notebook->getName());
  124. } else {
  125. info = tr("Fail to empty recycle bin of notebook "
  126. "<span style=\"%1\">%2</span>!")
  127. .arg(g_config->c_dataTextStyle)
  128. .arg(notebook->getName());
  129. }
  130. VUtils::showMessage(QMessageBox::Information,
  131. tr("Information"),
  132. info,
  133. "",
  134. QMessageBox::Ok,
  135. QMessageBox::Ok,
  136. this);
  137. }
  138. });
  139. }
  140. void VNotebookSelector::updateComboBox()
  141. {
  142. m_muted = true;
  143. int index = g_config->getCurNotebookIndex();
  144. clear();
  145. m_listWidget->clear();
  146. insertAddNotebookItem();
  147. for (int i = 0; i < m_notebooks.size(); ++i) {
  148. addNotebookItem(m_notebooks[i]);
  149. }
  150. setCurrentIndex(-1);
  151. m_muted = false;
  152. if (m_notebooks.isEmpty()) {
  153. g_config->setCurNotebookIndex(-1);
  154. setCurrentIndex(0);
  155. } else {
  156. const VNotebook *nb = NULL;
  157. if (index >= 0 && index < m_notebooks.size()) {
  158. nb = m_notebooks[index];
  159. }
  160. setCurrentItemToNotebook(nb);
  161. }
  162. qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
  163. }
  164. void VNotebookSelector::setCurrentItemToNotebook(const VNotebook *p_notebook)
  165. {
  166. setCurrentIndex(itemIndexOfNotebook(p_notebook));
  167. }
  168. int VNotebookSelector::itemIndexOfNotebook(const VNotebook *p_notebook) const
  169. {
  170. if (!p_notebook) {
  171. return -1;
  172. }
  173. qulonglong ptr = (qulonglong)p_notebook;
  174. int cnt = m_listWidget->count();
  175. for (int i = 0; i < cnt; ++i) {
  176. QListWidgetItem *item = m_listWidget->item(i);
  177. if (item->data(Qt::UserRole).toULongLong() == ptr) {
  178. return i;
  179. }
  180. }
  181. return -1;
  182. }
  183. void VNotebookSelector::insertAddNotebookItem()
  184. {
  185. QListWidgetItem *item = new QListWidgetItem();
  186. item->setIcon(VIconUtils::comboBoxIcon(":/resources/icons/create_notebook.svg"));
  187. item->setText(tr("Add Notebook"));
  188. QFont font;
  189. font.setItalic(true);
  190. item->setData(Qt::FontRole, font);
  191. item->setToolTip(tr("Create or import a notebook"));
  192. m_listWidget->insertItem(0, item);
  193. }
  194. void VNotebookSelector::handleCurIndexChanged(int p_index)
  195. {
  196. if (m_muted) {
  197. return;
  198. }
  199. QString tooltip = tr("View and edit notebooks");
  200. VNotebook *nb = NULL;
  201. if (p_index > -1) {
  202. nb = getNotebook(p_index);
  203. if (!nb) {
  204. // Add notebook.
  205. setToolTip(tooltip);
  206. if (m_lastValidIndex != p_index && m_lastValidIndex > -1) {
  207. setCurrentIndex(m_lastValidIndex);
  208. }
  209. if (!m_notebooks.isEmpty()) {
  210. newNotebook();
  211. }
  212. return;
  213. }
  214. }
  215. m_lastValidIndex = p_index;
  216. int nbIdx = -1;
  217. if (nb) {
  218. tooltip = nb->getName();
  219. nbIdx = m_notebooks.indexOf(nb);
  220. Q_ASSERT(nbIdx > -1);
  221. }
  222. g_config->setCurNotebookIndex(nbIdx);
  223. setToolTip(tooltip);
  224. emit curNotebookChanged(nb);
  225. }
  226. void VNotebookSelector::update()
  227. {
  228. updateComboBox();
  229. }
  230. bool VNotebookSelector::newNotebook()
  231. {
  232. QString info(tr("Please type the name of the notebook and "
  233. "choose a folder as the Root Folder of the notebook."));
  234. info += "\n";
  235. info += tr("* The root folder should be used EXCLUSIVELY by VNote and "
  236. "it is recommended to be EMPTY.");
  237. info += "\n";
  238. info += tr("* A previously created notebook could be imported into VNote "
  239. "by choosing its root folder.");
  240. // Use empty default name and path to let the dialog to auto generate a name
  241. // under the default VNote notebook folder.
  242. VNewNotebookDialog dialog(tr("Add Notebook"),
  243. info,
  244. "",
  245. "",
  246. m_notebooks,
  247. this);
  248. if (dialog.exec() == QDialog::Accepted) {
  249. createNotebook(dialog.getNameInput(),
  250. dialog.getPathInput(),
  251. dialog.isImportExistingNotebook(),
  252. dialog.getImageFolder(),
  253. dialog.getAttachmentFolder());
  254. emit notebookCreated(dialog.getNameInput(), dialog.isImportExistingNotebook());
  255. return true;
  256. }
  257. return false;
  258. }
  259. void VNotebookSelector::createNotebook(const QString &p_name,
  260. const QString &p_path,
  261. bool p_import,
  262. const QString &p_imageFolder,
  263. const QString &p_attachmentFolder)
  264. {
  265. VNotebook *nb = VNotebook::createNotebook(p_name, p_path, p_import,
  266. p_imageFolder, p_attachmentFolder,
  267. g_vnote);
  268. if (!nb) {
  269. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  270. tr("Fail to create notebook "
  271. "<span style=\"%1\">%2</span> in <span style=\"%1\">%3</span>.")
  272. .arg(g_config->c_dataTextStyle).arg(p_name).arg(p_path), "",
  273. QMessageBox::Ok, QMessageBox::Ok, this);
  274. return;
  275. }
  276. m_notebooks.append(nb);
  277. g_config->setNotebooks(m_notebooks);
  278. addNotebookItem(nb);
  279. setCurrentItemToNotebook(nb);
  280. }
  281. void VNotebookSelector::deleteNotebook()
  282. {
  283. QList<QListWidgetItem *> items = m_listWidget->selectedItems();
  284. if (items.isEmpty()) {
  285. return;
  286. }
  287. Q_ASSERT(items.size() == 1);
  288. VNotebook *notebook = getNotebook(items[0]);
  289. Q_ASSERT(notebook);
  290. VDeleteNotebookDialog dialog(tr("Delete Notebook"), notebook, this);
  291. if (dialog.exec() == QDialog::Accepted) {
  292. bool deleteFiles = dialog.getDeleteFiles();
  293. g_mainWin->getEditArea()->closeFile(notebook, true);
  294. deleteNotebook(notebook, deleteFiles);
  295. }
  296. }
  297. void VNotebookSelector::deleteNotebook(VNotebook *p_notebook, bool p_deleteFiles)
  298. {
  299. Q_ASSERT(p_notebook);
  300. m_notebooks.removeOne(p_notebook);
  301. g_config->setNotebooks(m_notebooks);
  302. int idx = itemIndexOfNotebook(p_notebook);
  303. QListWidgetItem *item = m_listWidget->takeItem(idx);
  304. Q_ASSERT(item);
  305. delete item;
  306. QString name(p_notebook->getName());
  307. QString path(p_notebook->getPath());
  308. bool ret = VNotebook::deleteNotebook(p_notebook, p_deleteFiles);
  309. if (!ret) {
  310. // Notebook could not be deleted completely.
  311. int cho = VUtils::showMessage(QMessageBox::Information,
  312. tr("Delete Notebook Folder From Disk"),
  313. tr("Fail to delete the root folder of notebook "
  314. "<span style=\"%1\">%2</span> from disk. You may open "
  315. "the folder and check it manually.")
  316. .arg(g_config->c_dataTextStyle).arg(name),
  317. "",
  318. QMessageBox::Open | QMessageBox::Ok,
  319. QMessageBox::Ok,
  320. this);
  321. if (cho == QMessageBox::Open) {
  322. // Open the notebook location.
  323. QUrl url = QUrl::fromLocalFile(path);
  324. QDesktopServices::openUrl(url);
  325. }
  326. }
  327. if (m_notebooks.isEmpty()) {
  328. m_muted = true;
  329. setCurrentIndex(0);
  330. m_muted = false;
  331. }
  332. }
  333. void VNotebookSelector::editNotebookInfo()
  334. {
  335. QList<QListWidgetItem *> items = m_listWidget->selectedItems();
  336. if (items.isEmpty()) {
  337. return;
  338. }
  339. Q_ASSERT(items.size() == 1);
  340. VNotebook *notebook = getNotebook(items[0]);
  341. VNotebookInfoDialog dialog(tr("Notebook Information"),
  342. "",
  343. notebook,
  344. m_notebooks,
  345. this);
  346. if (dialog.exec() == QDialog::Accepted) {
  347. bool updated = false;
  348. bool configUpdated = false;
  349. QString name = dialog.getName();
  350. if (name != notebook->getName()) {
  351. updated = true;
  352. notebook->rename(name);
  353. g_config->setNotebooks(m_notebooks);
  354. }
  355. QString imageFolder = dialog.getImageFolder();
  356. if (imageFolder != notebook->getImageFolderConfig()) {
  357. configUpdated = true;
  358. notebook->setImageFolder(imageFolder);
  359. }
  360. if (configUpdated) {
  361. updated = true;
  362. notebook->writeConfigNotebook();
  363. }
  364. if (updated) {
  365. fillItem(items[0], notebook);
  366. emit notebookUpdated(notebook);
  367. }
  368. }
  369. }
  370. void VNotebookSelector::addNotebookItem(const VNotebook *p_notebook)
  371. {
  372. QListWidgetItem *item = new QListWidgetItem(m_listWidget);
  373. fillItem(item, p_notebook);
  374. }
  375. void VNotebookSelector::fillItem(QListWidgetItem *p_item,
  376. const VNotebook *p_notebook) const
  377. {
  378. p_item->setText(p_notebook->getName());
  379. p_item->setToolTip(p_notebook->getName());
  380. p_item->setIcon(VIconUtils::comboBoxIcon(":/resources/icons/notebook_item.svg"));
  381. p_item->setData(Qt::UserRole, (qulonglong)p_notebook);
  382. }
  383. void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos)
  384. {
  385. QListWidgetItem *item = m_listWidget->itemAt(p_pos);
  386. if (!item) {
  387. return;
  388. }
  389. const VNotebook *nb = getNotebook(item);
  390. if (!nb) {
  391. return;
  392. }
  393. m_listWidget->clearSelection();
  394. item->setSelected(true);
  395. QMenu menu(this);
  396. menu.setToolTipsVisible(true);
  397. menu.addAction(m_deleteNotebookAct);
  398. if (nb->isValid()) {
  399. menu.addSeparator();
  400. menu.addAction(m_recycleBinAct);
  401. menu.addAction(m_emptyRecycleBinAct);
  402. }
  403. menu.addSeparator();
  404. menu.addAction(m_openLocationAct);
  405. if (nb->isValid()) {
  406. menu.addAction(m_notebookInfoAct);
  407. }
  408. menu.exec(m_listWidget->mapToGlobal(p_pos));
  409. }
  410. bool VNotebookSelector::eventFilter(QObject *watched, QEvent *event)
  411. {
  412. QEvent::Type type = event->type();
  413. if (type == QEvent::KeyPress && watched == m_listWidget) {
  414. if (handlePopupKeyPress(static_cast<QKeyEvent *>(event))) {
  415. return true;
  416. }
  417. } else if (type == QEvent::MouseButtonRelease) {
  418. if (static_cast<QMouseEvent *>(event)->button() == Qt::RightButton) {
  419. return true;
  420. }
  421. }
  422. return QComboBox::eventFilter(watched, event);
  423. }
  424. bool VNotebookSelector::locateNotebook(const VNotebook *p_notebook)
  425. {
  426. bool ret = false;
  427. int index = itemIndexOfNotebook(p_notebook);
  428. if (index > -1) {
  429. setCurrentIndex(index);
  430. ret = true;
  431. }
  432. return ret;
  433. }
  434. void VNotebookSelector::showPopup()
  435. {
  436. if (m_notebooks.isEmpty()) {
  437. // No normal notebook items. Just add notebook.
  438. newNotebook();
  439. return;
  440. }
  441. resizeListWidgetToContent();
  442. QComboBox::showPopup();
  443. }
  444. void VNotebookSelector::resizeListWidgetToContent()
  445. {
  446. static QRect screenRect = QGuiApplication::primaryScreen()->geometry();
  447. static int maxMinWidth = screenRect.width() < 400 ? screenRect.width() : screenRect.width() / 2;
  448. static int maxMinHeight = screenRect.height() < 400 ? screenRect.height() : screenRect.height() / 2;
  449. int minWidth = 0;
  450. int minHeight = 0;
  451. if (m_listWidget->count() > 0) {
  452. // Width
  453. minWidth = m_listWidget->sizeHintForColumn(0);
  454. minWidth = qMin(minWidth, maxMinWidth);
  455. // Height
  456. minHeight = m_listWidget->sizeHintForRow(0) * m_listWidget->count() + 10;
  457. minHeight = qMin(minHeight, maxMinHeight);
  458. }
  459. m_listWidget->setMinimumSize(minWidth, minHeight);
  460. }
  461. void VNotebookSelector::registerNavigation(QChar p_majorKey)
  462. {
  463. Q_ASSERT(!m_naviLabel);
  464. m_majorKey = p_majorKey;
  465. }
  466. void VNotebookSelector::showNavigation()
  467. {
  468. if (!isVisible()) {
  469. return;
  470. }
  471. V_ASSERT(!m_naviLabel);
  472. m_naviLabel = new QLabel(m_majorKey, this);
  473. m_naviLabel->setStyleSheet(g_vnote->getNavigationLabelStyle(m_majorKey));
  474. m_naviLabel->show();
  475. }
  476. void VNotebookSelector::hideNavigation()
  477. {
  478. if (m_naviLabel) {
  479. delete m_naviLabel;
  480. m_naviLabel = NULL;
  481. }
  482. }
  483. bool VNotebookSelector::handleKeyNavigation(int p_key, bool &p_succeed)
  484. {
  485. bool ret = false;
  486. p_succeed = false;
  487. QChar keyChar = VUtils::keyToChar(p_key);
  488. if (keyChar == m_majorKey) {
  489. // Hit.
  490. p_succeed = true;
  491. ret = true;
  492. if (m_naviLabel) {
  493. showPopup();
  494. }
  495. }
  496. return ret;
  497. }
  498. bool VNotebookSelector::handlePopupKeyPress(QKeyEvent *p_event)
  499. {
  500. if (VimNavigationForWidget::injectKeyPressEventForVim(m_listWidget,
  501. p_event)) {
  502. return true;
  503. }
  504. return false;
  505. }
  506. VNotebook *VNotebookSelector::getNotebook(int p_itemIdx) const
  507. {
  508. VNotebook *nb = NULL;
  509. QListWidgetItem *item = m_listWidget->item(p_itemIdx);
  510. if (item) {
  511. nb = (VNotebook *)item->data(Qt::UserRole).toULongLong();
  512. }
  513. return nb;
  514. }
  515. VNotebook *VNotebookSelector::getNotebook(const QListWidgetItem *p_item) const
  516. {
  517. if (p_item) {
  518. return (VNotebook *)p_item->data(Qt::UserRole).toULongLong();
  519. }
  520. return NULL;
  521. }