vtagexplorer.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. #include "vtagexplorer.h"
  2. #include <QtWidgets>
  3. #include "utils/viconutils.h"
  4. #include "vmainwindow.h"
  5. #include "vlistwidget.h"
  6. #include "vnotebook.h"
  7. #include "vconfigmanager.h"
  8. #include "vsearch.h"
  9. #include "vnote.h"
  10. #include "vcart.h"
  11. #include "vhistorylist.h"
  12. #include "vnotefile.h"
  13. #include "utils/vutils.h"
  14. #include "vnavigationmode.h"
  15. #include "vcaptain.h"
  16. extern VMainWindow *g_mainWin;
  17. extern VConfigManager *g_config;
  18. extern VNote *g_vnote;
  19. #define MAX_DISPLAY_LENGTH 10
  20. VTagExplorer::VTagExplorer(QWidget *p_parent)
  21. : QWidget(p_parent),
  22. m_uiInitialized(false),
  23. m_notebook(NULL),
  24. m_notebookChanged(true),
  25. m_search(NULL)
  26. {
  27. }
  28. void VTagExplorer::setupUI()
  29. {
  30. if (m_uiInitialized) {
  31. return;
  32. }
  33. m_uiInitialized = true;
  34. m_notebookLabel = new QLabel(tr("Tags"), this);
  35. m_notebookLabel->setProperty("TitleLabel", true);
  36. m_tagList = new VListWidget(this);
  37. m_tagList->setAttribute(Qt::WA_MacShowFocusRect, false);
  38. connect(m_tagList, &QListWidget::itemActivated,
  39. this, [this](const QListWidgetItem *p_item) {
  40. QString tag;
  41. if (p_item) {
  42. tag = p_item->text();
  43. }
  44. bool ret = activateTag(tag);
  45. if (ret && !tag.isEmpty() && m_fileList->count() == 0) {
  46. promptToRemoveEmptyTag(tag);
  47. }
  48. });
  49. QVBoxLayout *tagLayout = new QVBoxLayout();
  50. tagLayout->addWidget(m_notebookLabel);
  51. tagLayout->addWidget(m_tagList);
  52. tagLayout->setContentsMargins(0, 0, 0, 0);
  53. QWidget *tagWidget = new QWidget(this);
  54. tagWidget->setLayout(tagLayout);
  55. m_tagLabel = new QLabel(tr("Notes"), this);
  56. m_tagLabel->setProperty("TitleLabel", true);
  57. m_splitBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/split_window.svg"),
  58. "",
  59. this);
  60. m_splitBtn->setToolTip(tr("Split"));
  61. m_splitBtn->setCheckable(true);
  62. m_splitBtn->setProperty("CornerBtn", true);
  63. m_splitBtn->setFocusPolicy(Qt::NoFocus);
  64. connect(m_splitBtn, &QPushButton::clicked,
  65. this, [this](bool p_checked) {
  66. g_config->setEnableSplitTagFileList(p_checked);
  67. setupFileListSplitOut(p_checked);
  68. });
  69. QHBoxLayout *titleLayout = new QHBoxLayout();
  70. titleLayout->addWidget(m_tagLabel);
  71. titleLayout->addWidget(m_splitBtn);
  72. titleLayout->addStretch();
  73. titleLayout->setContentsMargins(0, 0, 0, 0);
  74. m_fileList = new VListWidget(this);
  75. m_fileList->setAttribute(Qt::WA_MacShowFocusRect, false);
  76. m_fileList->setContextMenuPolicy(Qt::CustomContextMenu);
  77. m_fileList->setSelectionMode(QAbstractItemView::ExtendedSelection);
  78. connect(m_fileList, &QListWidget::itemActivated,
  79. this, &VTagExplorer::openFileItem);
  80. connect(m_fileList, &QListWidget::customContextMenuRequested,
  81. this, &VTagExplorer::handleFileListContextMenuRequested);
  82. QVBoxLayout *fileLayout = new QVBoxLayout();
  83. fileLayout->addLayout(titleLayout);
  84. fileLayout->addWidget(m_fileList);
  85. fileLayout->setContentsMargins(0, 0, 0, 0);
  86. QWidget *fileWidget = new QWidget(this);
  87. fileWidget->setLayout(fileLayout);
  88. m_splitter = new QSplitter(this);
  89. m_splitter->setObjectName("TagExplorerSplitter");
  90. m_splitter->addWidget(tagWidget);
  91. m_splitter->addWidget(fileWidget);
  92. setupFileListSplitOut(g_config->getEnableSplitTagFileList());
  93. QVBoxLayout *mainLayout = new QVBoxLayout();
  94. mainLayout->addWidget(m_splitter);
  95. mainLayout->setContentsMargins(0, 0, 0, 0);
  96. setLayout(mainLayout);
  97. restoreStateAndGeometry();
  98. }
  99. void VTagExplorer::showEvent(QShowEvent *p_event)
  100. {
  101. setupUI();
  102. QWidget::showEvent(p_event);
  103. updateContent();
  104. }
  105. void VTagExplorer::focusInEvent(QFocusEvent *p_event)
  106. {
  107. setupUI();
  108. QWidget::focusInEvent(p_event);
  109. m_tagList->setFocus();
  110. }
  111. void VTagExplorer::setNotebook(VNotebook *p_notebook)
  112. {
  113. if (p_notebook == m_notebook) {
  114. return;
  115. }
  116. setupUI();
  117. m_notebook = p_notebook;
  118. m_notebookChanged = true;
  119. if (!isVisible()) {
  120. return;
  121. }
  122. updateContent();
  123. }
  124. void VTagExplorer::updateContent()
  125. {
  126. if (m_notebook) {
  127. updateNotebookLabel();
  128. const QStringList &tags = m_notebook->getTags();
  129. if (m_notebookChanged || tagListObsolete(tags)) {
  130. updateTagList(tags);
  131. }
  132. } else {
  133. clear();
  134. }
  135. m_notebookChanged = false;
  136. }
  137. void VTagExplorer::clear()
  138. {
  139. setupUI();
  140. m_fileList->clearAll();
  141. m_tagList->clearAll();
  142. updateTagLabel("");
  143. updateNotebookLabel();
  144. }
  145. void VTagExplorer::updateNotebookLabel()
  146. {
  147. QString text = tr("Tags");
  148. QString tooltip;
  149. if (m_notebook) {
  150. QString name = m_notebook->getName();
  151. tooltip = name;
  152. if (name.size() > MAX_DISPLAY_LENGTH) {
  153. name = name.left(MAX_DISPLAY_LENGTH) + QStringLiteral("...");
  154. }
  155. text = tr("Tags (%1)").arg(name);
  156. }
  157. m_notebookLabel->setText(text);
  158. m_notebookLabel->setToolTip(tooltip);
  159. }
  160. bool VTagExplorer::tagListObsolete(const QStringList &p_tags) const
  161. {
  162. if (m_tagList->count() != p_tags.size()) {
  163. return true;
  164. }
  165. for (int i = 0; i < p_tags.size(); ++i) {
  166. if (p_tags[i] != m_tagList->item(i)->text()) {
  167. return true;
  168. }
  169. }
  170. return false;
  171. }
  172. void VTagExplorer::updateTagLabel(const QString &p_tag)
  173. {
  174. QString text = tr("Notes");
  175. QString tooltip;
  176. if (!p_tag.isEmpty()) {
  177. QString name = p_tag;
  178. tooltip = name;
  179. if (name.size() > MAX_DISPLAY_LENGTH) {
  180. name = name.left(MAX_DISPLAY_LENGTH) + QStringLiteral("...");
  181. }
  182. text = tr("Notes (%1)").arg(name);
  183. }
  184. m_tagLabel->setText(text);
  185. m_tagLabel->setToolTip(tooltip);
  186. }
  187. bool VTagExplorer::activateTag(const QString &p_tag)
  188. {
  189. updateTagLabel(p_tag);
  190. m_fileList->clearAll();
  191. if (p_tag.isEmpty()) {
  192. return false;
  193. }
  194. // Search this tag within current notebook.
  195. g_mainWin->showStatusMessage(tr("Searching for tag \"%1\"").arg(p_tag));
  196. QVector<VNotebook *> notebooks;
  197. notebooks.append(m_notebook);
  198. getVSearch()->clear();
  199. // We could not use WholeWordOnly here, since "c#" won't match a word.
  200. int opts = VSearchConfig::CaseSensitive | VSearchConfig::RegularExpression;
  201. QString pattern = QRegExp::escape(p_tag);
  202. pattern = "^" + pattern + "$";
  203. QSharedPointer<VSearchConfig> config(new VSearchConfig(VSearchConfig::CurrentNotebook,
  204. VSearchConfig::Tag,
  205. VSearchConfig::Note,
  206. VSearchConfig::Internal,
  207. opts,
  208. pattern,
  209. QString()));
  210. getVSearch()->setConfig(config);
  211. QSharedPointer<VSearchResult> result = getVSearch()->search(notebooks);
  212. bool ret = result->m_state == VSearchState::Success;
  213. handleSearchFinished(result);
  214. return ret;
  215. }
  216. void VTagExplorer::updateTagList(const QStringList &p_tags)
  217. {
  218. // Clear.
  219. m_tagList->clearAll();
  220. activateTag("");
  221. for (auto const & tag : p_tags) {
  222. addTagItem(tag);
  223. }
  224. if (m_tagList->count() > 0) {
  225. m_tagList->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
  226. }
  227. }
  228. void VTagExplorer::addTagItem(const QString &p_tag)
  229. {
  230. QListWidgetItem *item = new QListWidgetItem(VIconUtils::treeViewIcon(":/resources/icons/tag.svg"),
  231. p_tag);
  232. item->setToolTip(p_tag);
  233. m_tagList->addItem(item);
  234. }
  235. void VTagExplorer::saveStateAndGeometry()
  236. {
  237. if (!m_uiInitialized) {
  238. return;
  239. }
  240. g_config->setTagExplorerSplitterState(m_splitter->saveState());
  241. }
  242. void VTagExplorer::restoreStateAndGeometry()
  243. {
  244. const QByteArray state = g_config->getTagExplorerSplitterState();
  245. if (!state.isEmpty()) {
  246. m_splitter->restoreState(state);
  247. }
  248. }
  249. void VTagExplorer::initVSearch()
  250. {
  251. m_search = new VSearch(this);
  252. connect(m_search, &VSearch::resultItemAdded,
  253. this, &VTagExplorer::handleSearchItemAdded);
  254. connect(m_search, &VSearch::resultItemsAdded,
  255. this, &VTagExplorer::handleSearchItemsAdded);
  256. connect(m_search, &VSearch::finished,
  257. this, &VTagExplorer::handleSearchFinished);
  258. m_noteIcon = VIconUtils::treeViewIcon(":/resources/icons/note_item.svg");
  259. }
  260. void VTagExplorer::handleSearchItemAdded(const QSharedPointer<VSearchResultItem> &p_item)
  261. {
  262. appendItemToFileList(p_item);
  263. }
  264. void VTagExplorer::appendItemToFileList(const QSharedPointer<VSearchResultItem> &p_item)
  265. {
  266. Q_ASSERT(p_item->m_type == VSearchResultItem::Note);
  267. QListWidgetItem *item = new QListWidgetItem(m_noteIcon,
  268. p_item->m_text.isEmpty() ? p_item->m_path : p_item->m_text);
  269. item->setData(Qt::UserRole, p_item->m_path);
  270. item->setToolTip(p_item->m_path);
  271. m_fileList->addItem(item);
  272. }
  273. void VTagExplorer::handleSearchItemsAdded(const QList<QSharedPointer<VSearchResultItem> > &p_items)
  274. {
  275. for (auto const & it : p_items) {
  276. appendItemToFileList(it);
  277. }
  278. }
  279. void VTagExplorer::handleSearchFinished(const QSharedPointer<VSearchResult> &p_result)
  280. {
  281. Q_ASSERT(p_result->m_state != VSearchState::Idle);
  282. QString msg;
  283. switch (p_result->m_state) {
  284. case VSearchState::Busy:
  285. // Only synchronized search.
  286. Q_ASSERT(false);
  287. msg = tr("Invalid busy state when searching for tag");
  288. break;
  289. case VSearchState::Success:
  290. qDebug() << "search succeeded";
  291. msg = tr("Search for tag succeeded");
  292. break;
  293. case VSearchState::Fail:
  294. qDebug() << "search failed";
  295. msg = tr("Search for tag failed");
  296. break;
  297. case VSearchState::Cancelled:
  298. qDebug() << "search cancelled";
  299. msg = tr("Search for tag calcelled");
  300. break;
  301. default:
  302. break;
  303. }
  304. m_search->clear();
  305. if (!msg.isEmpty()) {
  306. g_mainWin->showStatusMessage(msg);
  307. }
  308. }
  309. void VTagExplorer::openFileItem(QListWidgetItem *p_item) const
  310. {
  311. if (!p_item) {
  312. return;
  313. }
  314. QStringList files;
  315. files << getFilePath(p_item);
  316. g_mainWin->openFiles(files);
  317. }
  318. void VTagExplorer::openSelectedFileItems() const
  319. {
  320. QStringList files;
  321. QList<QListWidgetItem *> selectedItems = m_fileList->selectedItems();
  322. for (auto it : selectedItems) {
  323. files << getFilePath(it);
  324. }
  325. if (!files.isEmpty()) {
  326. g_mainWin->openFiles(files);
  327. }
  328. }
  329. QString VTagExplorer::getFilePath(const QListWidgetItem *p_item) const
  330. {
  331. return p_item->data(Qt::UserRole).toString();
  332. }
  333. void VTagExplorer::handleFileListContextMenuRequested(QPoint p_pos)
  334. {
  335. QListWidgetItem *item = m_fileList->itemAt(p_pos);
  336. if (!item) {
  337. return;
  338. }
  339. QMenu menu(this);
  340. menu.setToolTipsVisible(true);
  341. QAction *openAct = new QAction(tr("&Open"), &menu);
  342. openAct->setToolTip(tr("Open selected notes"));
  343. connect(openAct, &QAction::triggered,
  344. this, &VTagExplorer::openSelectedFileItems);
  345. menu.addAction(openAct);
  346. QList<QListWidgetItem *> selectedItems = m_fileList->selectedItems();
  347. if (selectedItems.size() == 1) {
  348. QAction *locateAct = new QAction(VIconUtils::menuIcon(":/resources/icons/locate_note.svg"),
  349. tr("&Locate To Folder"),
  350. &menu);
  351. locateAct->setToolTip(tr("Locate the folder of current note"));
  352. connect(locateAct, &QAction::triggered,
  353. this, &VTagExplorer::locateCurrentFileItem);
  354. menu.addAction(locateAct);
  355. }
  356. menu.addSeparator();
  357. QAction *addToCartAct = new QAction(VIconUtils::menuIcon(":/resources/icons/cart.svg"),
  358. tr("Add To Cart"),
  359. &menu);
  360. addToCartAct->setToolTip(tr("Add selected notes to Cart for further processing"));
  361. connect(addToCartAct, &QAction::triggered,
  362. this, &VTagExplorer::addFileToCart);
  363. menu.addAction(addToCartAct);
  364. QAction *pinToHistoryAct = new QAction(VIconUtils::menuIcon(":/resources/icons/pin.svg"),
  365. tr("Pin To History"),
  366. &menu);
  367. pinToHistoryAct->setToolTip(tr("Pin selected notes to History"));
  368. connect(pinToHistoryAct, &QAction::triggered,
  369. this, &VTagExplorer::pinFileToHistory);
  370. menu.addAction(pinToHistoryAct);
  371. menu.exec(m_fileList->mapToGlobal(p_pos));
  372. }
  373. void VTagExplorer::locateCurrentFileItem() const
  374. {
  375. auto item = m_fileList->currentItem();
  376. if (!item) {
  377. return;
  378. }
  379. VFile *file = g_vnote->getInternalFile(getFilePath(item));
  380. if (file) {
  381. g_mainWin->locateFile(file);
  382. }
  383. }
  384. void VTagExplorer::addFileToCart() const
  385. {
  386. QList<QListWidgetItem *> items = m_fileList->selectedItems();
  387. VCart *cart = g_mainWin->getCart();
  388. for (int i = 0; i < items.size(); ++i) {
  389. cart->addFile(getFilePath(items[i]));
  390. }
  391. g_mainWin->showStatusMessage(tr("%1 %2 added to Cart")
  392. .arg(items.size())
  393. .arg(items.size() > 1 ? tr("notes") : tr("note")));
  394. }
  395. void VTagExplorer::pinFileToHistory() const
  396. {
  397. QList<QListWidgetItem *> items = m_fileList->selectedItems();
  398. QStringList files;
  399. for (int i = 0; i < items.size(); ++i) {
  400. files << getFilePath(items[i]);
  401. }
  402. g_mainWin->getHistoryList()->pinFiles(files);
  403. g_mainWin->showStatusMessage(tr("%1 %2 pinned to History")
  404. .arg(items.size())
  405. .arg(items.size() > 1 ? tr("notes") : tr("note")));
  406. }
  407. void VTagExplorer::promptToRemoveEmptyTag(const QString &p_tag)
  408. {
  409. Q_ASSERT(!p_tag.isEmpty());
  410. int ret = VUtils::showMessage(QMessageBox::Warning,
  411. tr("Warning"),
  412. tr("Empty tag detected! Do you want to remove it?"),
  413. tr("The tag <span style=\"%1\">%2</span> seems not to "
  414. "be assigned to any note currently.")
  415. .arg(g_config->c_dataTextStyle)
  416. .arg(p_tag),
  417. QMessageBox::Ok | QMessageBox::Cancel,
  418. QMessageBox::Cancel,
  419. this,
  420. MessageBoxType::Danger);
  421. if (ret == QMessageBox::Cancel) {
  422. return;
  423. }
  424. // Remove the tag from m_notebook.
  425. m_notebook->removeTag(p_tag);
  426. updateContent();
  427. }
  428. void VTagExplorer::registerNavigationTarget()
  429. {
  430. setupUI();
  431. VNavigationModeListWidgetWrapper *tagWrapper = new VNavigationModeListWidgetWrapper(m_tagList, this);
  432. g_mainWin->getCaptain()->registerNavigationTarget(tagWrapper);
  433. VNavigationModeListWidgetWrapper *fileWrapper = new VNavigationModeListWidgetWrapper(m_fileList, this);
  434. g_mainWin->getCaptain()->registerNavigationTarget(fileWrapper);
  435. }
  436. void VTagExplorer::setupFileListSplitOut(bool p_enabled)
  437. {
  438. m_splitBtn->setChecked(p_enabled);
  439. if (p_enabled) {
  440. m_splitter->setOrientation(Qt::Horizontal);
  441. m_splitter->setStretchFactor(0, 1);
  442. m_splitter->setStretchFactor(1, 1);
  443. } else {
  444. m_splitter->setOrientation(Qt::Vertical);
  445. m_splitter->setStretchFactor(0, 1);
  446. m_splitter->setStretchFactor(1, 2);
  447. }
  448. }