notebooknodeexplorer.cpp 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328
  1. #include "notebooknodeexplorer.h"
  2. #include <QtWidgets>
  3. #include <notebook/notebook.h>
  4. #include <notebook/node.h>
  5. #include "exception.h"
  6. #include "messageboxhelper.h"
  7. #include "vnotex.h"
  8. #include "mainwindow.h"
  9. #include <utils/iconutils.h>
  10. #include "treewidget.h"
  11. #include "dialogs/notepropertiesdialog.h"
  12. #include "dialogs/folderpropertiesdialog.h"
  13. #include "dialogs/deleteconfirmdialog.h"
  14. #include <utils/widgetutils.h>
  15. #include <utils/pathutils.h>
  16. #include <utils/clipboardutils.h>
  17. #include "notebookmgr.h"
  18. #include "widgetsfactory.h"
  19. #include "navigationmodemgr.h"
  20. #include <core/fileopenparameters.h>
  21. #include <core/events.h>
  22. using namespace vnotex;
  23. const QString NotebookNodeExplorer::c_nodeIconForegroundName = "widgets#notebookexplorer#node_icon#fg";
  24. QIcon NotebookNodeExplorer::s_folderNodeIcon;
  25. QIcon NotebookNodeExplorer::s_fileNodeIcon;
  26. QIcon NotebookNodeExplorer::s_recycleBinNodeIcon;
  27. NotebookNodeExplorer::NodeData::NodeData()
  28. {
  29. }
  30. NotebookNodeExplorer::NodeData::NodeData(Node *p_node, bool p_loaded)
  31. : m_type(NodeType::Node),
  32. m_node(p_node),
  33. m_loaded(p_loaded)
  34. {
  35. }
  36. NotebookNodeExplorer::NodeData::NodeData(const QString &p_name)
  37. : m_type(NodeType::Attachment),
  38. m_name(p_name),
  39. m_loaded(true)
  40. {
  41. }
  42. NotebookNodeExplorer::NodeData::NodeData(const NodeData &p_other)
  43. {
  44. m_type = p_other.m_type;
  45. switch (m_type) {
  46. case NodeType::Node:
  47. m_node = p_other.m_node;
  48. break;
  49. case NodeType::Attachment:
  50. m_name = p_other.m_name;
  51. break;
  52. default:
  53. m_node = p_other.m_node;
  54. m_name = p_other.m_name;
  55. break;
  56. }
  57. m_loaded = p_other.m_loaded;
  58. }
  59. NotebookNodeExplorer::NodeData::~NodeData()
  60. {
  61. }
  62. NotebookNodeExplorer::NodeData &NotebookNodeExplorer::NodeData::operator=(const NodeData &p_other)
  63. {
  64. if (&p_other == this) {
  65. return *this;
  66. }
  67. m_type = p_other.m_type;
  68. switch (m_type) {
  69. case NodeType::Node:
  70. m_node = p_other.m_node;
  71. break;
  72. case NodeType::Attachment:
  73. m_name = p_other.m_name;
  74. break;
  75. default:
  76. m_node = p_other.m_node;
  77. m_name = p_other.m_name;
  78. break;
  79. }
  80. m_loaded = p_other.m_loaded;
  81. return *this;
  82. }
  83. bool NotebookNodeExplorer::NodeData::isValid() const
  84. {
  85. return m_type != NodeType::Invalid;
  86. }
  87. bool NotebookNodeExplorer::NodeData::isNode() const
  88. {
  89. return m_type == NodeType::Node;
  90. }
  91. bool NotebookNodeExplorer::NodeData::isAttachment() const
  92. {
  93. return m_type == NodeType::Attachment;
  94. }
  95. NotebookNodeExplorer::NodeData::NodeType NotebookNodeExplorer::NodeData::getType() const
  96. {
  97. return m_type;
  98. }
  99. Node *NotebookNodeExplorer::NodeData::getNode() const
  100. {
  101. Q_ASSERT(isNode());
  102. return m_node;
  103. }
  104. const QString &NotebookNodeExplorer::NodeData::getName() const
  105. {
  106. Q_ASSERT(isAttachment());
  107. return m_name;
  108. }
  109. void NotebookNodeExplorer::NodeData::clear()
  110. {
  111. m_type = NodeType::Invalid;
  112. m_node = nullptr;
  113. m_name.clear();
  114. m_loaded = false;
  115. }
  116. bool NotebookNodeExplorer::NodeData::matched(const Node *p_node) const
  117. {
  118. if (isNode() && m_node == p_node) {
  119. return true;
  120. }
  121. return false;
  122. }
  123. bool NotebookNodeExplorer::NodeData::isLoaded() const
  124. {
  125. return m_loaded;
  126. }
  127. NotebookNodeExplorer::NotebookNodeExplorer(QWidget *p_parent)
  128. : QWidget(p_parent)
  129. {
  130. initNodeIcons();
  131. setupUI();
  132. }
  133. void NotebookNodeExplorer::initNodeIcons() const
  134. {
  135. if (!s_folderNodeIcon.isNull()) {
  136. return;
  137. }
  138. const auto &themeMgr = VNoteX::getInst().getThemeMgr();
  139. const auto fg = themeMgr.paletteColor(c_nodeIconForegroundName);
  140. const QString folderIconName("folder_node.svg");
  141. const QString fileIconName("file_node.svg");
  142. const QString recycleBinIconName("recycle_bin.svg");
  143. s_folderNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(folderIconName), fg);
  144. s_fileNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(fileIconName), fg);
  145. s_recycleBinNodeIcon = IconUtils::fetchIcon(themeMgr.getIconFile(recycleBinIconName), fg);
  146. }
  147. void NotebookNodeExplorer::setupUI()
  148. {
  149. auto mainLayout = new QVBoxLayout(this);
  150. mainLayout->setContentsMargins(0, 0, 0, 0);
  151. m_splitter = new QSplitter(this);
  152. mainLayout->addWidget(m_splitter);
  153. setupMasterExplorer(m_splitter);
  154. m_splitter->addWidget(m_masterExplorer);
  155. setFocusProxy(m_masterExplorer);
  156. }
  157. void NotebookNodeExplorer::setupMasterExplorer(QWidget *p_parent)
  158. {
  159. m_masterExplorer = new TreeWidget(TreeWidget::ClickSpaceToClearSelection, p_parent);
  160. TreeWidget::setupSingleColumnHeaderlessTree(m_masterExplorer, true, true);
  161. TreeWidget::showHorizontalScrollbar(m_masterExplorer);
  162. m_navigationWrapper.reset(new NavigationModeWrapper<QTreeWidget, QTreeWidgetItem>(m_masterExplorer));
  163. NavigationModeMgr::getInst().registerNavigationTarget(m_navigationWrapper.data());
  164. connect(m_masterExplorer, &QTreeWidget::itemExpanded,
  165. this, [this](QTreeWidgetItem *p_item) {
  166. auto cnt = p_item->childCount();
  167. for (int i = 0; i < cnt; ++i) {
  168. auto child = p_item->child(i);
  169. auto data = getItemNodeData(child);
  170. if (data.isNode() && !data.isLoaded()) {
  171. loadNode(child, data.getNode(), 1);
  172. }
  173. }
  174. });
  175. connect(m_masterExplorer, &QTreeWidget::customContextMenuRequested,
  176. this, [this](const QPoint &p_pos) {
  177. if (!m_notebook) {
  178. return;
  179. }
  180. auto item = m_masterExplorer->itemAt(p_pos);
  181. auto data = getItemNodeData(item);
  182. QScopedPointer<QMenu> menu(WidgetsFactory::createMenu());
  183. if (!data.isValid()) {
  184. createContextMenuOnRoot(menu.data());
  185. } else {
  186. if (!allSelectedItemsSameType()) {
  187. return;
  188. }
  189. if (data.isNode()) {
  190. createContextMenuOnNode(menu.data(), data.getNode());
  191. } else if (data.isAttachment()) {
  192. createContextMenuOnAttachment(menu.data(), data.getName());
  193. }
  194. }
  195. if (!menu->isEmpty()) {
  196. menu->exec(m_masterExplorer->mapToGlobal(p_pos));
  197. }
  198. });
  199. connect(m_masterExplorer, &QTreeWidget::itemActivated,
  200. this, [this](QTreeWidgetItem *p_item, int p_column) {
  201. Q_UNUSED(p_column);
  202. auto data = getItemNodeData(p_item);
  203. if (!data.isValid()) {
  204. return;
  205. }
  206. if (data.isNode()) {
  207. emit nodeActivated(data.getNode(), QSharedPointer<FileOpenParameters>::create());
  208. } else if (data.isAttachment()) {
  209. // TODO.
  210. }
  211. });
  212. }
  213. void NotebookNodeExplorer::setNotebook(const QSharedPointer<Notebook> &p_notebook)
  214. {
  215. if (p_notebook == m_notebook) {
  216. return;
  217. }
  218. if (m_notebook) {
  219. disconnect(m_notebook.data(), nullptr, this, nullptr);
  220. }
  221. saveNotebookTreeState();
  222. m_notebook = p_notebook;
  223. if (m_notebook) {
  224. connect(m_notebook.data(), &Notebook::nodeUpdated,
  225. this, [this](const Node *p_node) {
  226. updateNode(p_node->getParent());
  227. });
  228. }
  229. generateNodeTree();
  230. }
  231. void NotebookNodeExplorer::clearExplorer()
  232. {
  233. m_masterExplorer->clear();
  234. }
  235. void NotebookNodeExplorer::generateNodeTree()
  236. {
  237. clearExplorer();
  238. if (!m_notebook) {
  239. return;
  240. }
  241. try {
  242. auto rootNode = m_notebook->getRootNode();
  243. loadRootNode(rootNode.data());
  244. } catch (Exception &p_e) {
  245. QString msg = tr("Failed to load nodes of notebook (%1) (%2).")
  246. .arg(m_notebook->getName(), p_e.what());
  247. qCritical() << msg;
  248. MessageBoxHelper::notify(MessageBoxHelper::Critical, msg, VNoteX::getInst().getMainWindow());
  249. }
  250. // Restore current item.
  251. auto currentNode = stateCache()->getCurrentItem();
  252. if (currentNode) {
  253. setCurrentNode(currentNode);
  254. } else {
  255. // Do not focus the recycle bin.
  256. focusNormalNode();
  257. }
  258. stateCache()->clear();
  259. }
  260. void NotebookNodeExplorer::loadRootNode(const Node *p_node) const
  261. {
  262. Q_ASSERT(p_node->isLoaded());
  263. // Render recycle bin node first.
  264. auto recycleBinNode = m_notebook->getRecycleBinNode();
  265. if (recycleBinNode) {
  266. loadRecycleBinNode(recycleBinNode.data());
  267. }
  268. for (auto &child : p_node->getChildren()) {
  269. if (recycleBinNode == child) {
  270. continue;
  271. }
  272. auto item = new QTreeWidgetItem(m_masterExplorer);
  273. loadNode(item, child.data(), 1);
  274. }
  275. }
  276. static void clearTreeWigetItemChildren(QTreeWidgetItem *p_item)
  277. {
  278. auto children = p_item->takeChildren();
  279. for (auto &child : children) {
  280. delete child;
  281. }
  282. }
  283. void NotebookNodeExplorer::loadNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
  284. {
  285. if (!p_node->isLoaded()) {
  286. p_node->load();
  287. }
  288. clearTreeWigetItemChildren(p_item);
  289. fillTreeItem(p_item, p_node, p_level > 0);
  290. loadChildren(p_item, p_node, p_level - 1);
  291. if (stateCache()->contains(p_item)) {
  292. p_item->setExpanded(true);
  293. }
  294. }
  295. void NotebookNodeExplorer::loadChildren(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
  296. {
  297. if (p_level < 0) {
  298. return;
  299. }
  300. for (auto &child : p_node->getChildren()) {
  301. auto item = new QTreeWidgetItem(p_item);
  302. loadNode(item, child.data(), p_level);
  303. }
  304. }
  305. void NotebookNodeExplorer::loadRecycleBinNode(Node *p_node) const
  306. {
  307. auto item = new QTreeWidgetItem();
  308. item->setWhatsThis(Column::Name,
  309. tr("Recycle bin of this notebook. Deleted files could be found here. "
  310. "It is organized in folders named by date. Nodes could be moved to "
  311. "other folders by Cut and Paste."));
  312. m_masterExplorer->insertTopLevelItem(0, item);
  313. loadRecycleBinNode(item, p_node, 1);
  314. }
  315. void NotebookNodeExplorer::loadRecycleBinNode(QTreeWidgetItem *p_item, Node *p_node, int p_level) const
  316. {
  317. if (!p_node->isLoaded()) {
  318. p_node->load();
  319. }
  320. clearTreeWigetItemChildren(p_item);
  321. setItemNodeData(p_item, NodeData(p_node, true));
  322. p_item->setText(Column::Name, tr("Recycle Bin"));
  323. p_item->setIcon(Column::Name, getNodeItemIcon(p_node));
  324. loadChildren(p_item, p_node, p_level - 1);
  325. // No need to restore state.
  326. }
  327. void NotebookNodeExplorer::fillTreeItem(QTreeWidgetItem *p_item, Node *p_node, bool p_loaded) const
  328. {
  329. setItemNodeData(p_item, NodeData(p_node, p_loaded));
  330. p_item->setText(Column::Name, p_node->getName());
  331. p_item->setIcon(Column::Name, getNodeItemIcon(p_node));
  332. p_item->setToolTip(Column::Name, p_node->getName());
  333. }
  334. QIcon NotebookNodeExplorer::getNodeItemIcon(const Node *p_node) const
  335. {
  336. switch (p_node->getType()) {
  337. case Node::Type::File:
  338. return s_fileNodeIcon;
  339. case Node::Type::Folder:
  340. {
  341. if (p_node->getUse() == Node::Use::RecycleBin) {
  342. return s_recycleBinNodeIcon;
  343. }
  344. return s_folderNodeIcon;
  345. }
  346. }
  347. return QIcon();
  348. }
  349. Node *NotebookNodeExplorer::getCurrentNode() const
  350. {
  351. auto item = m_masterExplorer->currentItem();
  352. if (item) {
  353. auto data = getItemNodeData(item);
  354. while (data.isAttachment()) {
  355. item = item->parent();
  356. if (item) {
  357. data = getItemNodeData(item);
  358. } else {
  359. data.clear();
  360. }
  361. }
  362. if (data.isNode()) {
  363. return data.getNode();
  364. }
  365. }
  366. return nullptr;
  367. }
  368. void NotebookNodeExplorer::setItemNodeData(QTreeWidgetItem *p_item, const NodeData &p_data)
  369. {
  370. p_item->setData(Column::Name, Qt::UserRole, QVariant::fromValue(p_data));
  371. }
  372. NotebookNodeExplorer::NodeData NotebookNodeExplorer::getItemNodeData(const QTreeWidgetItem *p_item)
  373. {
  374. if (!p_item) {
  375. return NodeData();
  376. }
  377. return p_item->data(Column::Name, Qt::UserRole).value<NotebookNodeExplorer::NodeData>();
  378. }
  379. void NotebookNodeExplorer::updateNode(Node *p_node)
  380. {
  381. if (p_node && p_node->getNotebook() != m_notebook) {
  382. return;
  383. }
  384. auto item = findNode(p_node);
  385. if (item) {
  386. bool expanded = item->isExpanded();
  387. item->setExpanded(false);
  388. if (m_notebook->isRecycleBinNode(p_node)) {
  389. loadRecycleBinNode(item, p_node, 1);
  390. } else {
  391. loadNode(item, p_node, 1);
  392. }
  393. item->setExpanded(expanded);
  394. } else {
  395. saveNotebookTreeState(false);
  396. generateNodeTree();
  397. }
  398. }
  399. QTreeWidgetItem *NotebookNodeExplorer::findNode(const Node *p_node) const
  400. {
  401. if (!p_node) {
  402. return nullptr;
  403. }
  404. auto cnt = m_masterExplorer->topLevelItemCount();
  405. for (int i = 0; i < cnt; ++i) {
  406. auto item = findNode(m_masterExplorer->topLevelItem(i), p_node);
  407. if (item) {
  408. return item;
  409. }
  410. }
  411. return nullptr;
  412. }
  413. QTreeWidgetItem *NotebookNodeExplorer::findNode(QTreeWidgetItem *p_item, const Node *p_node) const
  414. {
  415. auto data = getItemNodeData(p_item);
  416. if (data.matched(p_node)) {
  417. return p_item;
  418. }
  419. auto cnt = p_item->childCount();
  420. for (int i = 0; i < cnt; ++i) {
  421. auto item = findNode(p_item->child(i), p_node);
  422. if (item) {
  423. return item;
  424. }
  425. }
  426. return nullptr;
  427. }
  428. QTreeWidgetItem *NotebookNodeExplorer::findNodeChild(QTreeWidgetItem *p_item, const Node *p_node) const
  429. {
  430. auto cnt = p_item->childCount();
  431. for (int i = 0; i < cnt; ++i) {
  432. auto child = p_item->child(i);
  433. auto data = getItemNodeData(child);
  434. if (data.matched(p_node)) {
  435. return child;
  436. }
  437. }
  438. return nullptr;
  439. }
  440. QTreeWidgetItem *NotebookNodeExplorer::findNodeTopLevelItem(QTreeWidget *p_tree, const Node *p_node) const
  441. {
  442. auto cnt = p_tree->topLevelItemCount();
  443. for (int i = 0; i < cnt; ++i) {
  444. auto child = p_tree->topLevelItem(i);
  445. auto data = getItemNodeData(child);
  446. if (data.matched(p_node)) {
  447. return child;
  448. }
  449. }
  450. return nullptr;
  451. }
  452. void NotebookNodeExplorer::setCurrentNode(Node *p_node)
  453. {
  454. if (!p_node || !p_node->getParent()) {
  455. m_masterExplorer->setCurrentItem(nullptr);
  456. return;
  457. }
  458. Q_ASSERT(p_node->getNotebook() == m_notebook);
  459. // Nodes from root to p_node.
  460. QList<Node *> nodes;
  461. auto node = p_node;
  462. while (node->getParent()) {
  463. nodes.push_front(node);
  464. node = node->getParent();
  465. }
  466. QList<QTreeWidgetItem *> items;
  467. auto nodeIt = nodes.constBegin();
  468. auto item = findNodeTopLevelItem(m_masterExplorer, *nodeIt);
  469. if (!item) {
  470. return;
  471. }
  472. items.push_back(item);
  473. ++nodeIt;
  474. while (nodeIt != nodes.constEnd()) {
  475. if (!item) {
  476. return;
  477. }
  478. // Find *nodeIt in children of item.
  479. auto data = getItemNodeData(item);
  480. Q_ASSERT(data.isNode());
  481. if (!data.isLoaded()) {
  482. loadNode(item, data.getNode(), 1);
  483. }
  484. auto childItem = findNodeChild(item, *nodeIt);
  485. if (!childItem) {
  486. return;
  487. }
  488. items.push_back(childItem);
  489. item = childItem;
  490. ++nodeIt;
  491. }
  492. Q_ASSERT(getItemNodeData(item).getNode() == p_node);
  493. for (auto &it : items) {
  494. it->setExpanded(true);
  495. }
  496. m_masterExplorer->setCurrentItem(item);
  497. }
  498. void NotebookNodeExplorer::saveNotebookTreeState(bool p_saveCurrentItem)
  499. {
  500. if (m_notebook) {
  501. stateCache()->save(m_masterExplorer, p_saveCurrentItem);
  502. }
  503. }
  504. QSharedPointer<QTreeWidgetStateCache<Node *>> NotebookNodeExplorer::stateCache() const
  505. {
  506. Q_ASSERT(m_notebook);
  507. auto it = m_stateCache.find(m_notebook.data());
  508. if (it == m_stateCache.end()) {
  509. auto keyFunc = [](const QTreeWidgetItem *p_item, bool &p_ok) {
  510. auto data = NotebookNodeExplorer::getItemNodeData(p_item);
  511. if (data.isNode()) {
  512. p_ok = true;
  513. return data.getNode();
  514. }
  515. p_ok = false;
  516. return static_cast<Node *>(nullptr);
  517. };
  518. auto cache = QSharedPointer<QTreeWidgetStateCache<Node *>>::create(keyFunc);
  519. it = const_cast<NotebookNodeExplorer *>(this)->m_stateCache.insert(m_notebook.data(), cache);
  520. }
  521. return it.value();
  522. }
  523. void NotebookNodeExplorer::clearStateCache(const Notebook *p_notebook)
  524. {
  525. auto it = m_stateCache.find(p_notebook);
  526. if (it != m_stateCache.end()) {
  527. it.value()->clear();
  528. }
  529. }
  530. void NotebookNodeExplorer::createContextMenuOnRoot(QMenu *p_menu)
  531. {
  532. auto act = createAction(Action::NewNote, p_menu);
  533. p_menu->addAction(act);
  534. act = createAction(Action::NewFolder, p_menu);
  535. p_menu->addAction(act);
  536. if (isPasteOnNodeAvailable(nullptr)) {
  537. p_menu->addSeparator();
  538. act = createAction(Action::Paste, p_menu);
  539. p_menu->addAction(act);
  540. }
  541. p_menu->addSeparator();
  542. act = createAction(Action::OpenLocation, p_menu);
  543. p_menu->addAction(act);
  544. }
  545. void NotebookNodeExplorer::createContextMenuOnNode(QMenu *p_menu, const Node *p_node)
  546. {
  547. const int selectedSize = m_masterExplorer->selectedItems().size();
  548. QAction *act = nullptr;
  549. if (m_notebook->isRecycleBinNode(p_node)) {
  550. // Recycle bin node.
  551. if (selectedSize == 1) {
  552. act = createAction(Action::EmptyRecycleBin, p_menu);
  553. p_menu->addAction(act);
  554. act = createAction(Action::OpenLocation, p_menu);
  555. p_menu->addAction(act);
  556. }
  557. } else if (m_notebook->isNodeInRecycleBin(p_node)) {
  558. // Node in recycle bin.
  559. act = createAction(Action::Cut, p_menu);
  560. p_menu->addAction(act);
  561. act = createAction(Action::DeleteFromRecycleBin, p_menu);
  562. p_menu->addAction(act);
  563. if (selectedSize == 1) {
  564. p_menu->addSeparator();
  565. act = createAction(Action::CopyPath, p_menu);
  566. p_menu->addAction(act);
  567. act = createAction(Action::OpenLocation, p_menu);
  568. p_menu->addAction(act);
  569. }
  570. } else {
  571. act = createAction(Action::NewNote, p_menu);
  572. p_menu->addAction(act);
  573. act = createAction(Action::NewFolder, p_menu);
  574. p_menu->addAction(act);
  575. p_menu->addSeparator();
  576. act = createAction(Action::Copy, p_menu);
  577. p_menu->addAction(act);
  578. act = createAction(Action::Cut, p_menu);
  579. p_menu->addAction(act);
  580. if (selectedSize == 1 && isPasteOnNodeAvailable(p_node)) {
  581. act = createAction(Action::Paste, p_menu);
  582. p_menu->addAction(act);
  583. }
  584. act = createAction(Action::Delete, p_menu);
  585. p_menu->addAction(act);
  586. act = createAction(Action::RemoveFromConfig, p_menu);
  587. p_menu->addAction(act);
  588. if (selectedSize == 1) {
  589. p_menu->addSeparator();
  590. act = createAction(Action::CopyPath, p_menu);
  591. p_menu->addAction(act);
  592. act = createAction(Action::OpenLocation, p_menu);
  593. p_menu->addAction(act);
  594. act = createAction(Action::Properties, p_menu);
  595. p_menu->addAction(act);
  596. }
  597. }
  598. }
  599. void NotebookNodeExplorer::createContextMenuOnAttachment(QMenu *p_menu, const QString &p_name)
  600. {
  601. Q_UNUSED(p_menu);
  602. Q_UNUSED(p_name);
  603. }
  604. static QIcon generateMenuActionIcon(const QString &p_name)
  605. {
  606. const auto &themeMgr = VNoteX::getInst().getThemeMgr();
  607. return IconUtils::fetchIconWithDisabledState(themeMgr.getIconFile(p_name));
  608. }
  609. QAction *NotebookNodeExplorer::createAction(Action p_act, QObject *p_parent)
  610. {
  611. QAction *act = nullptr;
  612. switch (p_act) {
  613. case Action::NewNote:
  614. act = new QAction(generateMenuActionIcon("new_note.svg"),
  615. tr("New N&ote"),
  616. p_parent);
  617. connect(act, &QAction::triggered,
  618. this, []() {
  619. emit VNoteX::getInst().newNoteRequested();
  620. });
  621. break;
  622. case Action::NewFolder:
  623. act = new QAction(generateMenuActionIcon("new_folder.svg"),
  624. tr("New &Folder"),
  625. p_parent);
  626. connect(act, &QAction::triggered,
  627. this, []() {
  628. emit VNoteX::getInst().newFolderRequested();
  629. });
  630. break;
  631. case Action::Properties:
  632. act = new QAction(generateMenuActionIcon("properties.svg"),
  633. tr("&Properties"),
  634. p_parent);
  635. connect(act, &QAction::triggered,
  636. this, [this]() {
  637. auto node = getCurrentNode();
  638. if (!node) {
  639. return;
  640. }
  641. int ret = QDialog::Rejected;
  642. switch (node->getType()) {
  643. case Node::Type::File:
  644. {
  645. NotePropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
  646. ret = dialog.exec();
  647. break;
  648. }
  649. case Node::Type::Folder:
  650. FolderPropertiesDialog dialog(node, VNoteX::getInst().getMainWindow());
  651. ret = dialog.exec();
  652. break;
  653. }
  654. if (ret == QDialog::Accepted) {
  655. setCurrentNode(node);
  656. }
  657. });
  658. break;
  659. case Action::OpenLocation:
  660. act = new QAction(tr("Open &Location"), p_parent);
  661. connect(act, &QAction::triggered,
  662. this, [this]() {
  663. QString locationPath;
  664. auto node = getCurrentNode();
  665. if (node) {
  666. locationPath = node->fetchAbsolutePath();
  667. if (node->getType() == Node::Type::File) {
  668. locationPath = PathUtils::parentDirPath(locationPath);
  669. }
  670. } else if (m_notebook) {
  671. locationPath = m_notebook->getRootFolderAbsolutePath();
  672. }
  673. if (!locationPath.isEmpty()) {
  674. WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(locationPath));
  675. }
  676. });
  677. break;
  678. case Action::CopyPath:
  679. act = new QAction(tr("Cop&y Path"), p_parent);
  680. connect(act, &QAction::triggered,
  681. this, [this]() {
  682. auto node = getCurrentNode();
  683. if (node) {
  684. auto nodePath = node->fetchAbsolutePath();
  685. ClipboardUtils::setTextToClipboard(nodePath);
  686. VNoteX::getInst().showStatusMessageShort(tr("Copied path: %1").arg(nodePath));
  687. }
  688. });
  689. break;
  690. case Action::Copy:
  691. act = new QAction(tr("&Copy"), p_parent);
  692. connect(act, &QAction::triggered,
  693. this, [this]() {
  694. copySelectedNodes(false);
  695. });
  696. break;
  697. case Action::Cut:
  698. act = new QAction(tr("C&ut"), p_parent);
  699. connect(act, &QAction::triggered,
  700. this, [this]() {
  701. copySelectedNodes(true);
  702. });
  703. break;
  704. case Action::Paste:
  705. act = new QAction(tr("&Paste"), p_parent);
  706. connect(act, &QAction::triggered,
  707. this, [this]() {
  708. pasteNodesFromClipboard();
  709. });
  710. break;
  711. case Action::EmptyRecycleBin:
  712. act = new QAction(tr("&Empty"), p_parent);
  713. connect(act, &QAction::triggered,
  714. this, [this]() {
  715. auto rbNode = m_notebook->getRecycleBinNode().data();
  716. auto rbNodePath = rbNode->fetchAbsolutePath();
  717. int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Warning,
  718. tr("Empty the recycle bin of this notebook?"),
  719. tr("All files in recycle bin will be deleted permanently."),
  720. tr("Location of recycle bin: %1").arg(rbNodePath));
  721. if (ret != QMessageBox::Ok) {
  722. return;
  723. }
  724. try {
  725. m_notebook->emptyNode(rbNode, true);
  726. } catch (Exception &p_e) {
  727. MessageBoxHelper::notify(MessageBoxHelper::Critical,
  728. tr("Failed to empty recycle bin (%1) (%2).")
  729. .arg(rbNodePath, p_e.what()),
  730. VNoteX::getInst().getMainWindow());
  731. }
  732. updateNode(rbNode);
  733. });
  734. break;
  735. case Action::Delete:
  736. act = new QAction(tr("&Delete"), p_parent);
  737. connect(act, &QAction::triggered,
  738. this, [this]() {
  739. removeSelectedNodes(false);
  740. });
  741. break;
  742. case Action::DeleteFromRecycleBin:
  743. act = new QAction(tr("&Delete From Recycle Bin"), p_parent);
  744. connect(act, &QAction::triggered,
  745. this, [this]() {
  746. removeSelectedNodes(true);
  747. });
  748. break;
  749. case Action::RemoveFromConfig:
  750. act = new QAction(tr("&Remove From Index"), p_parent);
  751. connect(act, &QAction::triggered,
  752. this, [this]() {
  753. removeSelectedNodesFromConfig();
  754. });
  755. break;
  756. }
  757. return act;
  758. }
  759. void NotebookNodeExplorer::copySelectedNodes(bool p_move)
  760. {
  761. auto nodes = getSelectedNodes();
  762. if (nodes.isEmpty()) {
  763. return;
  764. }
  765. filterAwayChildrenNodes(nodes);
  766. ClipboardData cdata(VNoteX::getInst().getInstanceId(),
  767. p_move ? ClipboardData::MoveNode : ClipboardData::CopyNode);
  768. for (auto node : nodes) {
  769. auto item = QSharedPointer<NodeClipboardDataItem>::create(node->getNotebook()->getId(),
  770. node->fetchRelativePath());
  771. cdata.addItem(item);
  772. }
  773. auto text = cdata.toJsonText();
  774. ClipboardUtils::setTextToClipboard(text);
  775. size_t nrItems = cdata.getData().size();
  776. VNoteX::getInst().showStatusMessageShort(tr("Copied %n item(s)", "", static_cast<int>(nrItems)));
  777. }
  778. QVector<Node *> NotebookNodeExplorer::getSelectedNodes() const
  779. {
  780. QVector<Node *> nodes;
  781. auto items = m_masterExplorer->selectedItems();
  782. for (auto &item : items) {
  783. auto data = getItemNodeData(item);
  784. if (data.isNode()) {
  785. nodes.push_back(data.getNode());
  786. }
  787. }
  788. return nodes;
  789. }
  790. QSharedPointer<ClipboardData> NotebookNodeExplorer::tryFetchClipboardData()
  791. {
  792. auto text = ClipboardUtils::getTextFromClipboard();
  793. return ClipboardData::fromJsonText(text);
  794. }
  795. static bool isValidClipboardData(const ClipboardData *p_data)
  796. {
  797. if (!p_data) {
  798. return false;
  799. }
  800. if (p_data->getInstanceId() != VNoteX::getInst().getInstanceId()) {
  801. return false;
  802. }
  803. if (p_data->getData().isEmpty()) {
  804. return false;
  805. }
  806. auto act = p_data->getAction();
  807. if (act != ClipboardData::CopyNode && act != ClipboardData::MoveNode) {
  808. return false;
  809. }
  810. return true;
  811. }
  812. bool NotebookNodeExplorer::isPasteOnNodeAvailable(const Node *p_node) const
  813. {
  814. Q_UNUSED(p_node);
  815. auto cdata = tryFetchClipboardData();
  816. return isValidClipboardData(cdata.data());
  817. }
  818. static QSharedPointer<Node> getNodeFromClipboardDataItem(const NodeClipboardDataItem *p_item)
  819. {
  820. Q_ASSERT(p_item);
  821. auto notebook = VNoteX::getInst().getNotebookMgr().findNotebookById(p_item->m_notebookId);
  822. if (!notebook) {
  823. Exception::throwOne(Exception::Type::InvalidArgument,
  824. QString("failed to find notebook by ID (%1)").arg(p_item->m_notebookId));
  825. return nullptr;
  826. }
  827. auto node = notebook->loadNodeByPath(p_item->m_nodeRelativePath);
  828. Q_ASSERT(!node || node->fetchRelativePath() == p_item->m_nodeRelativePath);
  829. return node;
  830. }
  831. void NotebookNodeExplorer::pasteNodesFromClipboard()
  832. {
  833. // Identify the dest node.
  834. auto destNode = getCurrentNode();
  835. if (!destNode) {
  836. destNode = m_notebook->getRootNode().data();
  837. } else {
  838. // Current node may be a file node.
  839. if (destNode->getType() == Node::Type::File) {
  840. destNode = destNode->getParent();
  841. }
  842. }
  843. Q_ASSERT(destNode && destNode->getType() == Node::Type::Folder);
  844. // Fetch source nodes from clipboard.
  845. auto cdata = tryFetchClipboardData();
  846. if (!isValidClipboardData(cdata.data())) {
  847. MessageBoxHelper::notify(MessageBoxHelper::Warning,
  848. tr("Invalid clipboard data to paste."),
  849. VNoteX::getInst().getMainWindow());
  850. return;
  851. }
  852. QVector<QSharedPointer<Node>> srcNodes;
  853. auto items = cdata->getData();
  854. for (auto &item : items) {
  855. auto nodeItem = dynamic_cast<NodeClipboardDataItem *>(item.data());
  856. Q_ASSERT(nodeItem);
  857. auto src = getNodeFromClipboardDataItem(nodeItem);
  858. if (!src) {
  859. continue;
  860. } else if (src == destNode) {
  861. MessageBoxHelper::notify(MessageBoxHelper::Warning,
  862. tr("Destination is detected in sources (%1). Operation is cancelled.")
  863. .arg(destNode->fetchAbsolutePath()),
  864. VNoteX::getInst().getMainWindow());
  865. return;
  866. }
  867. srcNodes.push_back(src);
  868. }
  869. bool isMove = cdata->getAction() == ClipboardData::MoveNode;
  870. QVector<const Node *> pastedNodes;
  871. QSet<Node *> nodesNeedUpdate;
  872. for (auto srcNode : srcNodes) {
  873. if (isMove) {
  874. // Notice the view area to close any opened view windows.
  875. auto event = QSharedPointer<Event>::create();
  876. emit nodeAboutToMove(srcNode.data(), event);
  877. if (!event->m_response.toBool()) {
  878. continue;
  879. }
  880. }
  881. auto srcPath = srcNode->fetchAbsolutePath();
  882. auto srcParentNode = srcNode->getParent();
  883. try {
  884. auto notebook = destNode->getNotebook();
  885. auto pastedNode = notebook->copyNodeAsChildOf(srcNode, destNode, isMove);
  886. pastedNodes.push_back(pastedNode.data());
  887. } catch (Exception &p_e) {
  888. MessageBoxHelper::notify(MessageBoxHelper::Critical,
  889. tr("Failed to copy source (%1) to destination (%2) (%3).")
  890. .arg(srcPath, destNode->fetchAbsolutePath(), p_e.what()),
  891. VNoteX::getInst().getMainWindow());
  892. }
  893. if (isMove) {
  894. nodesNeedUpdate.insert(srcParentNode);
  895. }
  896. }
  897. for (auto node : nodesNeedUpdate) {
  898. updateNode(node);
  899. // Deleted src nodes may be the current node in cache. Clear the cache.
  900. clearStateCache(node->getNotebook());
  901. }
  902. // Update and expand dest node. Select all pasted nodes.
  903. updateAndExpandNode(destNode);
  904. selectNodes(pastedNodes);
  905. if (isMove) {
  906. ClipboardUtils::clearClipboard();
  907. }
  908. VNoteX::getInst().showStatusMessageShort(tr("Pasted %n item(s)", "", pastedNodes.size()));
  909. }
  910. void NotebookNodeExplorer::setNodeExpanded(const Node *p_node, bool p_expanded)
  911. {
  912. auto item = findNode(p_node);
  913. if (item) {
  914. item->setExpanded(p_expanded);
  915. }
  916. }
  917. void NotebookNodeExplorer::selectNodes(const QVector<const Node *> &p_nodes)
  918. {
  919. bool firstItem = true;
  920. for (auto node : p_nodes) {
  921. auto item = findNode(node);
  922. if (item) {
  923. auto flags = firstItem ? QItemSelectionModel::ClearAndSelect : QItemSelectionModel::Select;
  924. m_masterExplorer->setCurrentItem(item, 0, flags);
  925. firstItem = false;
  926. }
  927. }
  928. }
  929. void NotebookNodeExplorer::removeSelectedNodes(bool p_skipRecycleBin)
  930. {
  931. QString text;
  932. QString info;
  933. if (p_skipRecycleBin) {
  934. text = tr("Delete these folders and notes permanently?");
  935. info = tr("Files will be deleted permanently and could not be found even "
  936. "in operating system's recycle bin.");
  937. } else {
  938. text = tr("Delete these folders and notes?");
  939. info = tr("Deleted files could be found in the recycle bin of notebook.");
  940. }
  941. auto nodes = confirmSelectedNodes(tr("Confirm Deletion"), text, info);
  942. removeNodes(nodes, p_skipRecycleBin, false);
  943. }
  944. QVector<Node *> NotebookNodeExplorer::confirmSelectedNodes(const QString &p_title,
  945. const QString &p_text,
  946. const QString &p_info) const
  947. {
  948. auto nodes = getSelectedNodes();
  949. if (nodes.isEmpty()) {
  950. return nodes;
  951. }
  952. QVector<ConfirmItemInfo> items;
  953. for (const auto &node : nodes) {
  954. items.push_back(ConfirmItemInfo(getNodeItemIcon(node),
  955. node->getName(),
  956. node->fetchAbsolutePath(),
  957. node->fetchAbsolutePath(),
  958. (void *)node));
  959. }
  960. DeleteConfirmDialog dialog(p_title,
  961. p_text,
  962. p_info,
  963. items,
  964. DeleteConfirmDialog::Flag::None,
  965. false,
  966. VNoteX::getInst().getMainWindow());
  967. QVector<Node *> nodesToDelete;
  968. if (dialog.exec()) {
  969. items = dialog.getConfirmedItems();
  970. for (const auto &item : items) {
  971. nodesToDelete.push_back(static_cast<Node *>(item.m_data));
  972. }
  973. }
  974. return nodesToDelete;
  975. }
  976. void NotebookNodeExplorer::removeNodes(QVector<Node *> p_nodes,
  977. bool p_skipRecycleBin,
  978. bool p_configOnly)
  979. {
  980. if (p_nodes.isEmpty()) {
  981. return;
  982. }
  983. filterAwayChildrenNodes(p_nodes);
  984. int nrDeleted = 0;
  985. QSet<Node *> nodesNeedUpdate;
  986. for (auto node : p_nodes) {
  987. auto srcName = node->getName();
  988. auto srcPath = node->fetchAbsolutePath();
  989. auto srcParentNode = node->getParent();
  990. try {
  991. auto event = QSharedPointer<Event>::create();
  992. emit nodeAboutToRemove(node, event);
  993. if (!event->m_response.toBool()) {
  994. continue;
  995. }
  996. if (p_configOnly || p_skipRecycleBin) {
  997. m_notebook->removeNode(node, false, p_configOnly);
  998. } else {
  999. m_notebook->moveNodeToRecycleBin(node);
  1000. }
  1001. ++nrDeleted;
  1002. } catch (Exception &p_e) {
  1003. MessageBoxHelper::notify(MessageBoxHelper::Critical,
  1004. tr("Failed to delete/remove item (%1) (%2) (%3).")
  1005. .arg(srcName, srcPath, p_e.what()),
  1006. VNoteX::getInst().getMainWindow());
  1007. }
  1008. nodesNeedUpdate.insert(srcParentNode);
  1009. }
  1010. for (auto node : nodesNeedUpdate) {
  1011. updateNode(node);
  1012. }
  1013. if (!p_configOnly && !p_skipRecycleBin) {
  1014. updateNode(m_notebook->getRecycleBinNode().data());
  1015. }
  1016. VNoteX::getInst().showStatusMessageShort(tr("Deleted/Removed %n item(s)", "", nrDeleted));
  1017. }
  1018. void NotebookNodeExplorer::removeSelectedNodesFromConfig()
  1019. {
  1020. auto nodes = confirmSelectedNodes(tr("Confirm Removal"),
  1021. tr("Remove these folders and notes from index?"),
  1022. tr("Files are not touched but just removed from notebook index."));
  1023. removeNodes(nodes, false, true);
  1024. }
  1025. void NotebookNodeExplorer::filterAwayChildrenNodes(QVector<Node *> &p_nodes)
  1026. {
  1027. for (int i = p_nodes.size() - 1; i > 0; --i) {
  1028. // Check if j is i's ancestor.
  1029. for (int j = p_nodes.size() - 1; j >= 0; --j) {
  1030. if (i == j) {
  1031. continue;
  1032. }
  1033. if (Node::isAncestor(p_nodes[j], p_nodes[i])) {
  1034. p_nodes.remove(i);
  1035. break;
  1036. }
  1037. }
  1038. }
  1039. }
  1040. void NotebookNodeExplorer::updateAndExpandNode(Node *p_node)
  1041. {
  1042. setNodeExpanded(p_node, false);
  1043. updateNode(p_node);
  1044. setNodeExpanded(p_node, true);
  1045. }
  1046. bool NotebookNodeExplorer::allSelectedItemsSameType() const
  1047. {
  1048. auto items = m_masterExplorer->selectedItems();
  1049. if (items.size() < 2) {
  1050. return true;
  1051. }
  1052. auto type = getItemNodeData(items.first()).getType();
  1053. for (int i = 1; i < items.size(); ++i) {
  1054. auto itype = getItemNodeData(items[i]).getType();
  1055. if (itype != type) {
  1056. return false;
  1057. }
  1058. }
  1059. if (type == NodeData::NodeType::Node) {
  1060. bool hasNormalNode = false;
  1061. bool hasNodeInRecycleBin = false;
  1062. for (auto &item : items) {
  1063. auto node = getItemNodeData(item).getNode();
  1064. if (m_notebook->isRecycleBinNode(node)) {
  1065. return false;
  1066. } else if (m_notebook->isNodeInRecycleBin(node)) {
  1067. if (hasNormalNode) {
  1068. return false;
  1069. }
  1070. hasNodeInRecycleBin = true;
  1071. } else {
  1072. if (hasNodeInRecycleBin) {
  1073. return false;
  1074. }
  1075. hasNormalNode = true;
  1076. }
  1077. }
  1078. }
  1079. return true;
  1080. }
  1081. void NotebookNodeExplorer::reload()
  1082. {
  1083. updateNode(nullptr);
  1084. }
  1085. void NotebookNodeExplorer::focusNormalNode()
  1086. {
  1087. auto item = m_masterExplorer->currentItem();
  1088. if (item && item != m_masterExplorer->topLevelItem(0)) {
  1089. // Not recycle bin.
  1090. return;
  1091. }
  1092. auto cnt = m_masterExplorer->topLevelItemCount();
  1093. if (cnt > 1) {
  1094. m_masterExplorer->setCurrentItem(m_masterExplorer->topLevelItem(1));
  1095. }
  1096. }