vdirectorytree.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. #include <QtWidgets>
  2. #include "vdirectorytree.h"
  3. #include "dialog/vnewdirdialog.h"
  4. #include "vconfigmanager.h"
  5. #include "dialog/vdirinfodialog.h"
  6. #include "vnote.h"
  7. #include "vdirectory.h"
  8. #include "utils/vutils.h"
  9. #include "veditarea.h"
  10. VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
  11. : QTreeWidget(parent), vnote(vnote), m_editArea(NULL)
  12. {
  13. setColumnCount(1);
  14. setHeaderHidden(true);
  15. setContextMenuPolicy(Qt::CustomContextMenu);
  16. initActions();
  17. connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
  18. this, SLOT(updateChildren(QTreeWidgetItem*)));
  19. connect(this, SIGNAL(customContextMenuRequested(QPoint)),
  20. this, SLOT(contextMenuRequested(QPoint)));
  21. connect(this, &VDirectoryTree::currentItemChanged,
  22. this, &VDirectoryTree::currentDirectoryItemChanged);
  23. }
  24. void VDirectoryTree::initActions()
  25. {
  26. newRootDirAct = new QAction(QIcon(":/resources/icons/create_rootdir.svg"),
  27. tr("New &root directory"), this);
  28. newRootDirAct->setStatusTip(tr("Create a new root directory in current notebook"));
  29. connect(newRootDirAct, &QAction::triggered,
  30. this, &VDirectoryTree::newRootDirectory);
  31. newSubDirAct = new QAction(tr("&New sub-directory"), this);
  32. newSubDirAct->setStatusTip(tr("Create a new sub-directory"));
  33. connect(newSubDirAct, &QAction::triggered,
  34. this, &VDirectoryTree::newSubDirectory);
  35. deleteDirAct = new QAction(QIcon(":/resources/icons/delete_dir.svg"),
  36. tr("&Delete"), this);
  37. deleteDirAct->setStatusTip(tr("Delete selected directory"));
  38. connect(deleteDirAct, &QAction::triggered,
  39. this, &VDirectoryTree::deleteDirectory);
  40. dirInfoAct = new QAction(QIcon(":/resources/icons/dir_info.svg"),
  41. tr("&Info"), this);
  42. dirInfoAct->setStatusTip(tr("View and edit current directory's information"));
  43. connect(dirInfoAct, &QAction::triggered,
  44. this, &VDirectoryTree::editDirectoryInfo);
  45. copyAct = new QAction(QIcon(":/resources/icons/copy.svg"),
  46. tr("&Copy"), this);
  47. copyAct->setStatusTip(tr("Copy selected directories"));
  48. connect(copyAct, &QAction::triggered,
  49. this, &VDirectoryTree::copySelectedDirectories);
  50. cutAct = new QAction(QIcon(":/resources/icons/cut.svg"),
  51. tr("&Cut"), this);
  52. cutAct->setStatusTip(tr("Cut selected directories"));
  53. connect(cutAct, &QAction::triggered,
  54. this, &VDirectoryTree::cutSelectedDirectories);
  55. pasteAct = new QAction(QIcon(":/resources/icons/paste.svg"),
  56. tr("&Paste"), this);
  57. pasteAct->setStatusTip(tr("Paste directories"));
  58. connect(pasteAct, &QAction::triggered,
  59. this, &VDirectoryTree::pasteDirectoriesInCurDir);
  60. }
  61. void VDirectoryTree::setNotebook(VNotebook *p_notebook)
  62. {
  63. if (m_notebook == p_notebook) {
  64. return;
  65. }
  66. if (m_notebook) {
  67. // Disconnect
  68. disconnect((VNotebook *)m_notebook, &VNotebook::contentChanged,
  69. this, &VDirectoryTree::updateDirectoryTree);
  70. }
  71. m_notebook = p_notebook;
  72. if (m_notebook) {
  73. connect((VNotebook *)m_notebook, &VNotebook::contentChanged,
  74. this, &VDirectoryTree::updateDirectoryTree);
  75. } else {
  76. clear();
  77. return;
  78. }
  79. if (!m_notebook->open()) {
  80. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  81. QString("Failed to open notebook %1").arg(m_notebook->getName()), "",
  82. QMessageBox::Ok, QMessageBox::Ok, this);
  83. clear();
  84. return;
  85. }
  86. updateDirectoryTree();
  87. }
  88. void VDirectoryTree::fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name,
  89. VDirectory *p_directory, const QIcon &p_icon)
  90. {
  91. p_item.setText(0, p_name);
  92. p_item.setData(0, Qt::UserRole, QVariant::fromValue(p_directory));
  93. p_item.setIcon(0, p_icon);
  94. }
  95. void VDirectoryTree::updateDirectoryTree()
  96. {
  97. clear();
  98. VDirectory *rootDir = m_notebook->getRootDir();
  99. const QVector<VDirectory *> &subDirs = rootDir->getSubDirs();
  100. for (int i = 0; i < subDirs.size(); ++i) {
  101. VDirectory *dir = subDirs[i];
  102. QTreeWidgetItem *item = new QTreeWidgetItem(this);
  103. fillTreeItem(*item, dir->getName(), dir,
  104. QIcon(":/resources/icons/dir_item.svg"));
  105. updateDirectoryTreeOne(item, 1);
  106. }
  107. setCurrentItem(topLevelItem(0));
  108. }
  109. void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem *p_parent, int depth)
  110. {
  111. Q_ASSERT(p_parent->childCount() == 0);
  112. if (depth <= 0) {
  113. return;
  114. }
  115. VDirectory *dir = getVDirectory(p_parent);
  116. if (!dir->open()) {
  117. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  118. QString("Failed to open directory %1").arg(dir->getName()), "",
  119. QMessageBox::Ok, QMessageBox::Ok, this);
  120. return;
  121. }
  122. const QVector<VDirectory *> &subDirs = dir->getSubDirs();
  123. for (int i = 0; i < subDirs.size(); ++i) {
  124. VDirectory *subDir = subDirs[i];
  125. QTreeWidgetItem *item = new QTreeWidgetItem(p_parent);
  126. fillTreeItem(*item, subDir->getName(), subDir,
  127. QIcon(":/resources/icons/dir_item.svg"));
  128. updateDirectoryTreeOne(item, depth - 1);
  129. }
  130. }
  131. // Update @p_item's children items
  132. void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item)
  133. {
  134. Q_ASSERT(p_item);
  135. int nrChild = p_item->childCount();
  136. if (nrChild == 0) {
  137. return;
  138. }
  139. for (int i = 0; i < nrChild; ++i) {
  140. QTreeWidgetItem *childItem = p_item->child(i);
  141. if (childItem->childCount() > 0) {
  142. continue;
  143. }
  144. updateDirectoryTreeOne(childItem, 1);
  145. }
  146. }
  147. void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item)
  148. {
  149. QPointer<VDirectory> parentDir;
  150. if (p_item) {
  151. parentDir = getVDirectory(p_item);
  152. } else {
  153. parentDir = m_notebook->getRootDir();
  154. }
  155. const QVector<VDirectory *> &dirs = parentDir->getSubDirs();
  156. QHash<VDirectory *, QTreeWidgetItem *> itemDirMap;
  157. int nrChild = p_item ? p_item->childCount() : topLevelItemCount();
  158. for (int i = 0; i < nrChild; ++i) {
  159. QTreeWidgetItem *item = p_item ? p_item->child(i) : topLevelItem(i);
  160. itemDirMap.insert(getVDirectory(item), item);
  161. }
  162. for (int i = 0; i < dirs.size(); ++i) {
  163. VDirectory *dir = dirs[i];
  164. QTreeWidgetItem *item = itemDirMap.value(dir, NULL);
  165. if (item) {
  166. if (p_item) {
  167. p_item->removeChild(item);
  168. p_item->insertChild(i, item);
  169. } else {
  170. int topIdx = indexOfTopLevelItem(item);
  171. takeTopLevelItem(topIdx);
  172. insertTopLevelItem(i, item);
  173. }
  174. itemDirMap.remove(dir);
  175. } else {
  176. // Insert a new item
  177. if (p_item) {
  178. item = new QTreeWidgetItem(p_item);
  179. } else {
  180. item = new QTreeWidgetItem(this);
  181. }
  182. fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg"));
  183. updateDirectoryTreeOne(item, 1);
  184. }
  185. }
  186. // Delete items without corresponding VDirectory
  187. for (auto iter = itemDirMap.begin(); iter != itemDirMap.end(); ++iter) {
  188. QTreeWidgetItem *item = iter.value();
  189. if (p_item) {
  190. p_item->removeChild(item);
  191. } else {
  192. int topIdx = indexOfTopLevelItem(item);
  193. takeTopLevelItem(topIdx);
  194. }
  195. delete item;
  196. }
  197. }
  198. void VDirectoryTree::contextMenuRequested(QPoint pos)
  199. {
  200. QTreeWidgetItem *item = itemAt(pos);
  201. if (!m_notebook) {
  202. return;
  203. }
  204. QMenu menu(this);
  205. if (!item) {
  206. // Context menu on the free space of the QTreeWidget
  207. menu.addAction(newRootDirAct);
  208. } else {
  209. // Context menu on a QTreeWidgetItem
  210. if (item->parent()) {
  211. // Low-level item
  212. menu.addAction(newSubDirAct);
  213. } else {
  214. // Top-level item
  215. menu.addAction(newRootDirAct);
  216. menu.addAction(newSubDirAct);
  217. }
  218. menu.addAction(deleteDirAct);
  219. menu.addSeparator();
  220. menu.addAction(copyAct);
  221. menu.addAction(cutAct);
  222. }
  223. if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyDir
  224. && !m_copiedDirs.isEmpty()) {
  225. if (!item) {
  226. menu.addSeparator();
  227. }
  228. menu.addAction(pasteAct);
  229. }
  230. if (item) {
  231. menu.addSeparator();
  232. menu.addAction(dirInfoAct);
  233. }
  234. menu.exec(mapToGlobal(pos));
  235. }
  236. void VDirectoryTree::newSubDirectory()
  237. {
  238. if (!m_notebook) {
  239. return;
  240. }
  241. QTreeWidgetItem *curItem = currentItem();
  242. if (!curItem) {
  243. return;
  244. }
  245. VDirectory *curDir = getVDirectory(curItem);
  246. QString info = QString("Create sub-directory under %1.").arg(curDir->getName());
  247. QString text("&Directory name:");
  248. QString defaultText("new_directory");
  249. do {
  250. VNewDirDialog dialog(tr("Create directory"), info, text, defaultText, this);
  251. if (dialog.exec() == QDialog::Accepted) {
  252. QString name = dialog.getNameInput();
  253. if (curDir->findSubDirectory(name)) {
  254. info = QString("Name already exists under %1.\nPlease choose another name.").arg(curDir->getName());
  255. defaultText = name;
  256. continue;
  257. }
  258. VDirectory *subDir = curDir->createSubDirectory(name);
  259. if (!subDir) {
  260. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  261. QString("Failed to create directory %1.").arg(name), "",
  262. QMessageBox::Ok, QMessageBox::Ok, this);
  263. return;
  264. }
  265. updateItemChildren(curItem);
  266. }
  267. break;
  268. } while (true);
  269. }
  270. void VDirectoryTree::newRootDirectory()
  271. {
  272. if (!m_notebook) {
  273. return;
  274. }
  275. QString info = QString("Create root directory in notebook %1.").arg(m_notebook->getName());
  276. QString text("&Directory name:");
  277. QString defaultText("new_directory");
  278. VDirectory *rootDir = m_notebook->getRootDir();
  279. do {
  280. VNewDirDialog dialog(tr("Create root directory"), info, text, defaultText, this);
  281. if (dialog.exec() == QDialog::Accepted) {
  282. QString name = dialog.getNameInput();
  283. if (rootDir->findSubDirectory(name)) {
  284. info = QString("Name already exists in notebook %1.\nPlease choose another name.").arg(m_notebook->getName());
  285. defaultText = name;
  286. continue;
  287. }
  288. VDirectory *subDir = rootDir->createSubDirectory(name);
  289. if (!subDir) {
  290. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  291. QString("Failed to create directory %1.").arg(name), "",
  292. QMessageBox::Ok, QMessageBox::Ok, this);
  293. return;
  294. }
  295. updateItemChildren(NULL);
  296. }
  297. break;
  298. } while (true);
  299. }
  300. void VDirectoryTree::deleteDirectory()
  301. {
  302. QTreeWidgetItem *curItem = currentItem();
  303. if (!curItem) {
  304. return;
  305. }
  306. VDirectory *curDir = getVDirectory(curItem);
  307. int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  308. QString("Are you sure to delete directory %1?").arg(curDir->getName()),
  309. tr("This will delete any files under this directory."), QMessageBox::Ok | QMessageBox::Cancel,
  310. QMessageBox::Ok, this);
  311. if (ret == QMessageBox::Ok) {
  312. m_editArea->closeFile(curDir, true);
  313. VDirectory *parentDir = curDir->getParentDirectory();
  314. Q_ASSERT(parentDir);
  315. parentDir->deleteSubDirectory(curDir);
  316. delete curItem;
  317. }
  318. }
  319. void VDirectoryTree::currentDirectoryItemChanged(QTreeWidgetItem *currentItem)
  320. {
  321. if (!currentItem) {
  322. emit currentDirectoryChanged(NULL);
  323. return;
  324. }
  325. emit currentDirectoryChanged(getVDirectory(currentItem));
  326. }
  327. void VDirectoryTree::editDirectoryInfo()
  328. {
  329. QTreeWidgetItem *curItem = currentItem();
  330. if (!curItem) {
  331. return;
  332. }
  333. VDirectory *curDir = getVDirectory(curItem);
  334. VDirectory *parentDir = curDir->getParentDirectory();
  335. QString curName = curDir->getName();
  336. QString info;
  337. QString defaultName = curName;
  338. do {
  339. VDirInfoDialog dialog(tr("Directory Information"), info, defaultName, this);
  340. if (dialog.exec() == QDialog::Accepted) {
  341. QString name = dialog.getNameInput();
  342. if (name == curName) {
  343. return;
  344. }
  345. if (parentDir->findSubDirectory(name)) {
  346. info = "Name already exists.\nPlease choose another name.";
  347. defaultName = name;
  348. continue;
  349. }
  350. if (!curDir->rename(name)) {
  351. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  352. QString("Failed to rename directory %1.").arg(curName), "",
  353. QMessageBox::Ok, QMessageBox::Ok, this);
  354. return;
  355. }
  356. curItem->setText(0, name);
  357. emit directoryUpdated(curDir);
  358. }
  359. break;
  360. } while (true);
  361. }
  362. void VDirectoryTree::copySelectedDirectories(bool p_cut)
  363. {
  364. QList<QTreeWidgetItem *> items = selectedItems();
  365. if (items.isEmpty()) {
  366. return;
  367. }
  368. QJsonArray dirs;
  369. m_copiedDirs.clear();
  370. for (int i = 0; i < items.size(); ++i) {
  371. VDirectory *dir = getVDirectory(items[i]);
  372. QJsonObject dirJson;
  373. dirJson["notebook"] = dir->getNotebook();
  374. dirJson["path"] = dir->retrivePath();
  375. dirs.append(dirJson);
  376. m_copiedDirs.append(dir);
  377. }
  378. copyDirectoryInfoToClipboard(dirs, p_cut);
  379. }
  380. void VDirectoryTree::copyDirectoryInfoToClipboard(const QJsonArray &p_dirs, bool p_cut)
  381. {
  382. QJsonObject clip;
  383. clip["operation"] = (int)ClipboardOpType::CopyDir;
  384. clip["is_cut"] = p_cut;
  385. clip["sources"] = p_dirs;
  386. QClipboard *clipboard = QApplication::clipboard();
  387. clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact));
  388. }
  389. void VDirectoryTree::cutSelectedDirectories()
  390. {
  391. copySelectedDirectories(true);
  392. }
  393. void VDirectoryTree::pasteDirectoriesInCurDir()
  394. {
  395. QTreeWidgetItem *item = currentItem();
  396. VDirectory *destDir = m_notebook->getRootDir();
  397. if (item) {
  398. destDir = getVDirectory(item);
  399. }
  400. pasteDirectories(destDir);
  401. }
  402. void VDirectoryTree::pasteDirectories(VDirectory *p_destDir)
  403. {
  404. QClipboard *clipboard = QApplication::clipboard();
  405. QString text = clipboard->text();
  406. QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object();
  407. Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyDir);
  408. bool isCut = clip["is_cut"].toBool();
  409. int nrPasted = 0;
  410. for (int i = 0; i < m_copiedDirs.size(); ++i) {
  411. QPointer<VDirectory> srcDir = m_copiedDirs[i];
  412. if (!srcDir) {
  413. continue;
  414. }
  415. QString dirName = srcDir->getName();
  416. VDirectory *srcParentDir = srcDir->getParentDirectory();
  417. if (srcParentDir == p_destDir && !isCut) {
  418. // Copy and paste in the same directory.
  419. // Rename it to xx_copy
  420. dirName = VUtils::generateCopiedDirName(srcParentDir->retrivePath(), dirName);
  421. }
  422. if (copyDirectory(p_destDir, dirName, srcDir, isCut)) {
  423. nrPasted++;
  424. }
  425. }
  426. qDebug() << "pasted" << nrPasted << "files successfully";
  427. clipboard->clear();
  428. m_copiedDirs.clear();
  429. }
  430. void VDirectoryTree::mousePressEvent(QMouseEvent *event)
  431. {
  432. QTreeWidgetItem *item = itemAt(event->pos());
  433. if (!item) {
  434. setCurrentItem(NULL);
  435. }
  436. QTreeWidget::mousePressEvent(event);
  437. }
  438. void VDirectoryTree::keyPressEvent(QKeyEvent *event)
  439. {
  440. if (event->key() == Qt::Key_Return) {
  441. QTreeWidgetItem *item = currentItem();
  442. if (item) {
  443. item->setExpanded(!item->isExpanded());
  444. }
  445. }
  446. QTreeWidget::keyPressEvent(event);
  447. }
  448. bool VDirectoryTree::copyDirectory(VDirectory *p_destDir, const QString &p_destName,
  449. VDirectory *p_srcDir, bool p_cut)
  450. {
  451. qDebug() << "copy" << p_srcDir->getName() << "to" << p_destDir->getName()
  452. << "as" << p_destName;
  453. QString srcName = p_srcDir->getName();
  454. QString srcPath = QDir::cleanPath(p_srcDir->retrivePath());
  455. QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName));
  456. if (srcPath == destPath) {
  457. return true;
  458. }
  459. VDirectory *srcParentDir = p_srcDir->getParentDirectory();
  460. VDirectory *destDir = VDirectory::copyDirectory(p_destDir, p_destName, p_srcDir, p_cut);
  461. if (destDir) {
  462. // Update QTreeWidget
  463. bool isWidget;
  464. QTreeWidgetItem *destItem = findVDirectory(p_destDir, isWidget);
  465. if (destItem || isWidget) {
  466. updateItemChildren(destItem);
  467. }
  468. if (p_cut) {
  469. QTreeWidgetItem *srcItem = findVDirectory(srcParentDir, isWidget);
  470. if (srcItem || isWidget) {
  471. updateItemChildren(srcItem);
  472. }
  473. }
  474. // Broadcast this update
  475. emit directoryUpdated(destDir);
  476. } else {
  477. VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
  478. QString("Failed to copy directory %1.").arg(srcName),
  479. QString("Please check if there alread exists a directory with the same name"),
  480. QMessageBox::Ok, QMessageBox::Ok, this);
  481. }
  482. return destDir;
  483. }
  484. QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool &p_widget)
  485. {
  486. p_widget = false;
  487. if (!p_dir) {
  488. return NULL;
  489. } else if (p_dir->getNotebook() != m_notebook->getName()) {
  490. return NULL;
  491. } else if (p_dir == m_notebook->getRootDir()) {
  492. p_widget = true;
  493. return NULL;
  494. }
  495. bool isWidget;
  496. QTreeWidgetItem *pItem = findVDirectory(p_dir->getParentDirectory(), isWidget);
  497. if (pItem) {
  498. // Iterate all its children to find the match.
  499. int nrChild = pItem->childCount();
  500. for (int i = 0; i < nrChild; ++i) {
  501. QTreeWidgetItem *item = pItem->child(i);
  502. if (getVDirectory(item) == p_dir) {
  503. return item;
  504. }
  505. }
  506. } else if (isWidget) {
  507. // Iterate all the top-level items.
  508. int nrChild = topLevelItemCount();
  509. for (int i = 0; i < nrChild; ++i) {
  510. QTreeWidgetItem *item = topLevelItem(i);
  511. if (getVDirectory(item) == p_dir) {
  512. return item;
  513. }
  514. }
  515. }
  516. return NULL;
  517. }