vattachmentlist.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. #include "vattachmentlist.h"
  2. #include <QtWidgets>
  3. #include "vconfigmanager.h"
  4. #include "utils/vutils.h"
  5. #include "vbuttonwithwidget.h"
  6. #include "vmainwindow.h"
  7. #include "dialog/vconfirmdeletiondialog.h"
  8. #include "dialog/vsortdialog.h"
  9. #include "utils/vimnavigationforwidget.h"
  10. #include "utils/viconutils.h"
  11. extern VConfigManager *g_config;
  12. extern VMainWindow *g_mainWin;
  13. VAttachmentList::VAttachmentList(QWidget *p_parent)
  14. : QWidget(p_parent), m_file(NULL)
  15. {
  16. setupUI();
  17. initActions();
  18. updateContent();
  19. }
  20. void VAttachmentList::setupUI()
  21. {
  22. m_addBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/add_attachment.svg"), "");
  23. m_addBtn->setToolTip(tr("Add"));
  24. m_addBtn->setProperty("FlatBtn", true);
  25. m_addBtn->setDefault(true);
  26. connect(m_addBtn, &QPushButton::clicked,
  27. this, &VAttachmentList::addAttachment);
  28. m_clearBtn = new QPushButton(VIconUtils::buttonDangerIcon(":/resources/icons/clear_attachment.svg"), "");
  29. m_clearBtn->setToolTip(tr("Clear"));
  30. m_clearBtn->setProperty("FlatBtn", true);
  31. connect(m_clearBtn, &QPushButton::clicked,
  32. this, [this]() {
  33. if (m_file && m_attachmentList->count() > 0) {
  34. int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  35. tr("Are you sure to clear attachments of note "
  36. "<span style=\"%1\">%2</span>?")
  37. .arg(g_config->c_dataTextStyle)
  38. .arg(m_file->getName()),
  39. tr("<span style=\"%1\">WARNING</span>: "
  40. "VNote will delete all the files in directory "
  41. "<span style=\"%2\">%3</span>."
  42. "Deleted files could be found in the recycle bin "
  43. "of this note.<br>The operation is IRREVERSIBLE!")
  44. .arg(g_config->c_warningTextStyle)
  45. .arg(g_config->c_dataTextStyle)
  46. .arg(m_file->fetchAttachmentFolderPath()),
  47. QMessageBox::Ok | QMessageBox::Cancel,
  48. QMessageBox::Ok,
  49. g_mainWin,
  50. MessageBoxType::Danger);
  51. if (ret == QMessageBox::Ok) {
  52. if (!m_file->deleteAttachments()) {
  53. VUtils::showMessage(QMessageBox::Warning,
  54. tr("Warning"),
  55. tr("Fail to clear attachments of note <span style=\"%1\">%2</span>.")
  56. .arg(g_config->c_dataTextStyle)
  57. .arg(m_file->getName()),
  58. tr("Please check the attachments folder and "
  59. "maintain the configuration file manually."),
  60. QMessageBox::Ok,
  61. QMessageBox::Ok,
  62. g_mainWin);
  63. }
  64. m_attachmentList->clear();
  65. updateButtonState();
  66. }
  67. }
  68. });
  69. m_locateBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/locate_attachment.svg"), "");
  70. m_locateBtn->setToolTip(tr("Open Folder"));
  71. m_locateBtn->setProperty("FlatBtn", true);
  72. connect(m_locateBtn, &QPushButton::clicked,
  73. this, [this]() {
  74. if (m_file && !m_file->getAttachmentFolder().isEmpty()) {
  75. QUrl url = QUrl::fromLocalFile(m_file->fetchAttachmentFolderPath());
  76. QDesktopServices::openUrl(url);
  77. }
  78. });
  79. m_numLabel = new QLabel();
  80. QHBoxLayout *btnLayout = new QHBoxLayout;
  81. btnLayout->addWidget(m_addBtn);
  82. btnLayout->addWidget(m_clearBtn);
  83. btnLayout->addWidget(m_locateBtn);
  84. btnLayout->addStretch();
  85. btnLayout->addWidget(m_numLabel);
  86. m_attachmentList = new QListWidget;
  87. m_attachmentList->setContextMenuPolicy(Qt::CustomContextMenu);
  88. m_attachmentList->setSelectionMode(QAbstractItemView::ExtendedSelection);
  89. m_attachmentList->setEditTriggers(QAbstractItemView::SelectedClicked);
  90. m_attachmentList->setAttribute(Qt::WA_MacShowFocusRect, false);
  91. connect(m_attachmentList, &QListWidget::customContextMenuRequested,
  92. this, &VAttachmentList::handleContextMenuRequested);
  93. connect(m_attachmentList, &QListWidget::itemActivated,
  94. this, &VAttachmentList::handleItemActivated);
  95. connect(m_attachmentList->itemDelegate(), &QAbstractItemDelegate::commitData,
  96. this, &VAttachmentList::handleListItemCommitData);
  97. QVBoxLayout *mainLayout = new QVBoxLayout();
  98. mainLayout->addLayout(btnLayout);
  99. mainLayout->addWidget(m_attachmentList);
  100. setLayout(mainLayout);
  101. }
  102. void VAttachmentList::initActions()
  103. {
  104. m_openAct = new QAction(tr("&Open"), this);
  105. m_openAct->setToolTip(tr("Open current attachment file"));
  106. connect(m_openAct, &QAction::triggered,
  107. this, [this]() {
  108. QListWidgetItem *item = m_attachmentList->currentItem();
  109. handleItemActivated(item);
  110. });
  111. m_deleteAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_attachment.svg"),
  112. tr("&Delete"),
  113. this);
  114. m_deleteAct->setToolTip(tr("Delete selected attachments"));
  115. connect(m_deleteAct, &QAction::triggered,
  116. this, &VAttachmentList::deleteSelectedItems);
  117. m_sortAct = new QAction(VIconUtils::menuIcon(":/resources/icons/sort.svg"),
  118. tr("&Sort"),
  119. this);
  120. m_sortAct->setToolTip(tr("Sort attachments manually"));
  121. connect(m_sortAct, &QAction::triggered,
  122. this, &VAttachmentList::sortItems);
  123. }
  124. void VAttachmentList::setFile(VNoteFile *p_file)
  125. {
  126. m_file = p_file;
  127. updateButtonState();
  128. }
  129. void VAttachmentList::updateContent()
  130. {
  131. bool enableAdd = true, enableDelete = true, enableClear = true, enableLocate = true;
  132. m_attachmentList->clear();
  133. if (!m_file) {
  134. enableAdd = enableDelete = enableClear = enableLocate = false;
  135. } else {
  136. QString folder = m_file->getAttachmentFolder();
  137. const QVector<VAttachment> &attas = m_file->getAttachments();
  138. if (folder.isEmpty()) {
  139. Q_ASSERT(attas.isEmpty());
  140. enableDelete = enableClear = enableLocate = false;
  141. } else if (attas.isEmpty()) {
  142. enableDelete = enableClear = false;
  143. } else {
  144. fillAttachmentList(attas);
  145. }
  146. }
  147. m_addBtn->setEnabled(enableAdd);
  148. m_clearBtn->setEnabled(enableClear);
  149. m_locateBtn->setEnabled(enableLocate);
  150. int cnt = m_attachmentList->count();
  151. if (cnt > 0) {
  152. m_numLabel->setText(tr("%1 %2").arg(cnt).arg(cnt > 1 ? tr("Files") : tr("File")));
  153. m_attachmentList->setFocus();
  154. } else {
  155. m_numLabel->setText("");
  156. if (m_file) {
  157. m_addBtn->setFocus();
  158. }
  159. }
  160. }
  161. void VAttachmentList::fillAttachmentList(const QVector<VAttachment> &p_attachments)
  162. {
  163. Q_ASSERT(m_attachmentList->count() == 0);
  164. for (int i = 0; i < p_attachments.size(); ++i) {
  165. const VAttachment &atta = p_attachments[i];
  166. QListWidgetItem *item = new QListWidgetItem(atta.m_name);
  167. item->setFlags(item->flags() | Qt::ItemIsEditable);
  168. item->setData(Qt::UserRole, atta.m_name);
  169. m_attachmentList->addItem(item);
  170. }
  171. }
  172. void VAttachmentList::addAttachment()
  173. {
  174. if (!m_file) {
  175. return;
  176. }
  177. static QString lastPath = QDir::homePath();
  178. QStringList files = QFileDialog::getOpenFileNames(g_mainWin,
  179. tr("Select Files As Attachments"),
  180. lastPath);
  181. if (files.isEmpty()) {
  182. return;
  183. }
  184. // Update lastPath
  185. lastPath = QFileInfo(files[0]).path();
  186. addAttachments(files);
  187. updateButtonState();
  188. updateContent();
  189. }
  190. void VAttachmentList::addAttachments(const QStringList &p_files)
  191. {
  192. Q_ASSERT(m_file);
  193. int addedFiles = 0;
  194. for (int i = 0; i < p_files.size(); ++i) {
  195. if (!m_file->addAttachment(p_files[i])) {
  196. VUtils::showMessage(QMessageBox::Warning,
  197. tr("Warning"),
  198. tr("Fail to add attachment %1 for note <span style=\"%2\">%3</span>.")
  199. .arg(p_files[i])
  200. .arg(g_config->c_dataTextStyle)
  201. .arg(m_file->getName()),
  202. "",
  203. QMessageBox::Ok,
  204. QMessageBox::Ok,
  205. g_mainWin);
  206. } else {
  207. ++addedFiles;
  208. }
  209. }
  210. if (addedFiles > 0) {
  211. g_mainWin->showStatusMessage(tr("%1 %2 added as attachments")
  212. .arg(addedFiles)
  213. .arg(addedFiles > 1 ? tr("files") : tr("file")));
  214. }
  215. }
  216. void VAttachmentList::handleContextMenuRequested(QPoint p_pos)
  217. {
  218. // @p_pos is the position in the coordinate of VAttachmentList, no m_attachmentList.
  219. QListWidgetItem *item = m_attachmentList->itemAt(m_attachmentList->mapFromParent(p_pos));
  220. QMenu menu(this);
  221. menu.setToolTipsVisible(true);
  222. if (!m_file) {
  223. return;
  224. }
  225. if (item) {
  226. if (!item->isSelected()) {
  227. m_attachmentList->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
  228. }
  229. if (m_attachmentList->selectedItems().size() == 1) {
  230. menu.addAction(m_openAct);
  231. }
  232. menu.addAction(m_deleteAct);
  233. }
  234. m_attachmentList->update();
  235. if (m_file->getAttachments().size() > 1) {
  236. if (!menu.actions().isEmpty()) {
  237. menu.addSeparator();
  238. }
  239. menu.addAction(m_sortAct);
  240. }
  241. if (!menu.actions().isEmpty()) {
  242. menu.exec(mapToGlobal(p_pos));
  243. }
  244. }
  245. void VAttachmentList::handleItemActivated(QListWidgetItem *p_item)
  246. {
  247. if (p_item) {
  248. Q_ASSERT(m_file);
  249. QString name = p_item->text();
  250. QString folderPath = m_file->fetchAttachmentFolderPath();
  251. QUrl url = QUrl::fromLocalFile(QDir(folderPath).filePath(name));
  252. QDesktopServices::openUrl(url);
  253. }
  254. }
  255. void VAttachmentList::deleteSelectedItems()
  256. {
  257. QVector<ConfirmItemInfo> items;
  258. const QList<QListWidgetItem *> selectedItems = m_attachmentList->selectedItems();
  259. if (selectedItems.isEmpty()) {
  260. return;
  261. }
  262. for (auto const & item : selectedItems) {
  263. items.push_back(ConfirmItemInfo(item->text(),
  264. item->text(),
  265. "",
  266. NULL));
  267. }
  268. QString text = tr("Are you sure to delete these attachments of note "
  269. "<span style=\"%1\">%2</span>?")
  270. .arg(g_config->c_dataTextStyle).arg(m_file->getName());
  271. QString info = tr("Deleted files could be found in the recycle "
  272. "bin of this note.<br>"
  273. "Click \"Cancel\" to leave them untouched.");
  274. VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
  275. text,
  276. info,
  277. items,
  278. false,
  279. false,
  280. false,
  281. g_mainWin);
  282. if (dialog.exec()) {
  283. items = dialog.getConfirmedItems();
  284. QVector<QString> names;
  285. for (auto const & item : items) {
  286. names.push_back(item.m_name);
  287. }
  288. if (!m_file->deleteAttachments(names)) {
  289. VUtils::showMessage(QMessageBox::Warning,
  290. tr("Warning"),
  291. tr("Fail to delete attachments of note <span style=\"%1\">%2</span>.")
  292. .arg(g_config->c_dataTextStyle)
  293. .arg(m_file->getName()),
  294. tr("Please check the attachments folder and "
  295. "maintain the configuration file manually."),
  296. QMessageBox::Ok,
  297. QMessageBox::Ok,
  298. g_mainWin);
  299. }
  300. updateButtonState();
  301. updateContent();
  302. }
  303. }
  304. void VAttachmentList::sortItems()
  305. {
  306. const QVector<VAttachment> &attas = m_file->getAttachments();
  307. if (attas.size() < 2) {
  308. return;
  309. }
  310. VSortDialog dialog(tr("Sort Attachments"),
  311. tr("Sort attachments of note <span style=\"%1\">%2</span> "
  312. "in the configuration file.")
  313. .arg(g_config->c_dataTextStyle)
  314. .arg(m_file->getName()),
  315. g_mainWin);
  316. QTreeWidget *tree = dialog.getTreeWidget();
  317. tree->clear();
  318. tree->setColumnCount(1);
  319. QStringList headers;
  320. headers << tr("Name");
  321. tree->setHeaderLabels(headers);
  322. for (int i = 0; i < attas.size(); ++i) {
  323. QTreeWidgetItem *item = new QTreeWidgetItem(tree, QStringList(attas[i].m_name));
  324. item->setData(0, Qt::UserRole, i);
  325. }
  326. dialog.treeUpdated();
  327. if (dialog.exec()) {
  328. QVector<QVariant> data = dialog.getSortedData();
  329. Q_ASSERT(data.size() == attas.size());
  330. QVector<int> sortedIdx(data.size(), -1);
  331. for (int i = 0; i < data.size(); ++i) {
  332. sortedIdx[i] = data[i].toInt();
  333. }
  334. if (!m_file->sortAttachments(sortedIdx)) {
  335. VUtils::showMessage(QMessageBox::Warning,
  336. tr("Warning"),
  337. tr("Fail to sort attachments of note <span style=\"%1\">%2</span>.")
  338. .arg(g_config->c_dataTextStyle)
  339. .arg(m_file->getName()),
  340. "",
  341. QMessageBox::Ok,
  342. QMessageBox::Ok,
  343. this);
  344. }
  345. }
  346. }
  347. void VAttachmentList::handleListItemCommitData(QWidget *p_itemEdit)
  348. {
  349. QString text = reinterpret_cast<QLineEdit *>(p_itemEdit)->text();
  350. QListWidgetItem *item = m_attachmentList->currentItem();
  351. Q_ASSERT(item && item->text() == text);
  352. QString oldText = item->data(Qt::UserRole).toString();
  353. if (oldText == text) {
  354. return;
  355. }
  356. bool legalName = true;
  357. if (text.isEmpty()) {
  358. legalName = false;
  359. } else {
  360. QRegExp reg(VUtils::c_fileNameRegExp);
  361. if (!reg.exactMatch(text)) {
  362. legalName = false;
  363. }
  364. }
  365. if (!legalName) {
  366. // Recover to old name.
  367. item->setText(oldText);
  368. return;
  369. }
  370. if (!(oldText.toLower() == text.toLower())
  371. && m_file->findAttachment(text, false) > -1) {
  372. // Name conflict.
  373. // Recover to old name.
  374. item->setText(oldText);
  375. } else {
  376. if (!m_file->renameAttachment(oldText, text)) {
  377. VUtils::showMessage(QMessageBox::Information,
  378. tr("Rename Attachment"),
  379. tr("Fail to rename attachment <span style=\"%1\">%2</span>.")
  380. .arg(g_config->c_dataTextStyle)
  381. .arg(oldText),
  382. "",
  383. QMessageBox::Ok,
  384. QMessageBox::Ok,
  385. this);
  386. // Recover to old name.
  387. item->setText(oldText);
  388. } else {
  389. // Change the data.
  390. item->setData(Qt::UserRole, text);
  391. }
  392. }
  393. }
  394. void VAttachmentList::keyPressEvent(QKeyEvent *p_event)
  395. {
  396. if (VimNavigationForWidget::injectKeyPressEventForVim(m_attachmentList,
  397. p_event,
  398. this)) {
  399. return;
  400. }
  401. QWidget::keyPressEvent(p_event);
  402. }
  403. bool VAttachmentList::isAcceptDrops() const
  404. {
  405. return true;
  406. }
  407. bool VAttachmentList::handleDragEnterEvent(QDragEnterEvent *p_event)
  408. {
  409. if (!m_file) {
  410. return false;
  411. }
  412. if (p_event->mimeData()->hasFormat("text/uri-list")) {
  413. p_event->acceptProposedAction();
  414. return true;
  415. }
  416. return false;
  417. }
  418. bool VAttachmentList::handleDropEvent(QDropEvent *p_event)
  419. {
  420. if (!m_file) {
  421. return false;
  422. }
  423. const QMimeData *mime = p_event->mimeData();
  424. if (mime->hasFormat("text/uri-list") && mime->hasUrls()) {
  425. // Add attachments.
  426. QStringList files;
  427. QList<QUrl> urls = mime->urls();
  428. for (int i = 0; i < urls.size(); ++i) {
  429. QString file;
  430. if (urls[i].isLocalFile()) {
  431. file = urls[i].toLocalFile();
  432. QFileInfo fi(file);
  433. if (fi.exists() && fi.isFile()) {
  434. file = QDir::cleanPath(fi.absoluteFilePath());
  435. files.append(file);
  436. }
  437. }
  438. }
  439. if (!files.isEmpty()) {
  440. addAttachments(files);
  441. updateButtonState();
  442. }
  443. p_event->acceptProposedAction();
  444. return true;
  445. }
  446. return false;
  447. }
  448. void VAttachmentList::handleAboutToShow()
  449. {
  450. updateContent();
  451. checkAttachments();
  452. }
  453. void VAttachmentList::updateButtonState() const
  454. {
  455. VButtonWithWidget *btn = getButton();
  456. Q_ASSERT(btn);
  457. if (!btn) {
  458. return;
  459. }
  460. int numOfAttachments = -1;
  461. if (m_file) {
  462. numOfAttachments = m_file->getAttachments().size();
  463. if (numOfAttachments == 0) {
  464. numOfAttachments = -1;
  465. }
  466. }
  467. btn->setBubbleNumber(numOfAttachments);
  468. }
  469. void VAttachmentList::checkAttachments()
  470. {
  471. if (!m_file) {
  472. return;
  473. }
  474. QVector<QString> missingAttas = m_file->checkAttachments();
  475. if (missingAttas.isEmpty()) {
  476. return;
  477. }
  478. QVector<ConfirmItemInfo> items;
  479. for (auto const & atta : missingAttas) {
  480. items.push_back(ConfirmItemInfo(atta,
  481. atta,
  482. "",
  483. NULL));
  484. }
  485. QString text = tr("VNote detects that these attachments of note "
  486. "<span style=\"%1\">%2</span> are missing in disk. "
  487. "Would you like to remove them from the note?")
  488. .arg(g_config->c_dataTextStyle)
  489. .arg(m_file->getName());
  490. QString info = tr("Click \"Cancel\" to leave them untouched.");
  491. VConfirmDeletionDialog dialog(tr("Confirm Deleting Attachments"),
  492. text,
  493. info,
  494. items,
  495. false,
  496. false,
  497. false,
  498. g_mainWin);
  499. if (dialog.exec()) {
  500. items = dialog.getConfirmedItems();
  501. QVector<QString> names;
  502. for (auto const & item : items) {
  503. names.push_back(item.m_name);
  504. }
  505. if (!m_file->deleteAttachments(names, true)) {
  506. VUtils::showMessage(QMessageBox::Warning,
  507. tr("Warning"),
  508. tr("Fail to delete attachments of note <span style=\"%1\">%2</span>.")
  509. .arg(g_config->c_dataTextStyle)
  510. .arg(m_file->getName()),
  511. tr("Please check the attachments folder and "
  512. "maintain the configuration file manually."),
  513. QMessageBox::Ok,
  514. QMessageBox::Ok,
  515. g_mainWin);
  516. }
  517. updateButtonState();
  518. updateContent();
  519. }
  520. }