vdirectorytree.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #include <QtWidgets>
  2. #include "vdirectorytree.h"
  3. #include "vnewdirdialog.h"
  4. #include "vconfigmanager.h"
  5. VDirectoryTree::VDirectoryTree(QWidget *parent)
  6. : QTreeWidget(parent)
  7. {
  8. setColumnCount(1);
  9. setHeaderHidden(true);
  10. setContextMenuPolicy(Qt::CustomContextMenu);
  11. initActions();
  12. connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
  13. this, SLOT(updateItemSubtree(QTreeWidgetItem*)));
  14. connect(this, SIGNAL(customContextMenuRequested(QPoint)),
  15. this, SLOT(contextMenuRequested(QPoint)));
  16. connect(this, &VDirectoryTree::currentItemChanged,
  17. this, &VDirectoryTree::currentDirectoryItemChanged);
  18. }
  19. void VDirectoryTree::initActions()
  20. {
  21. newRootDirAct = new QAction(tr("New &root directory"), this);
  22. newRootDirAct->setStatusTip(tr("Create a new root directory in current notebook"));
  23. connect(newRootDirAct, &QAction::triggered,
  24. this, &VDirectoryTree::newRootDirectory);
  25. newSiblingDirAct = new QAction(tr("New &sibling directory"), this);
  26. newSiblingDirAct->setStatusTip(tr("Create a new sibling directory at current level"));
  27. connect(newSiblingDirAct, &QAction::triggered,
  28. this, &VDirectoryTree::newSiblingDirectory);
  29. newSubDirAct = new QAction(tr("&New sub-directory"), this);
  30. newSubDirAct->setStatusTip(tr("Create a new sub-directory"));
  31. connect(newSubDirAct, &QAction::triggered,
  32. this, &VDirectoryTree::newSubDirectory);
  33. deleteDirAct = new QAction(tr("&Delete"), this);
  34. deleteDirAct->setStatusTip(tr("Delete selected directory"));
  35. connect(deleteDirAct, &QAction::triggered,
  36. this, &VDirectoryTree::deleteDirectory);
  37. }
  38. void VDirectoryTree::setTreePath(const QString& path)
  39. {
  40. if (path == treePath) {
  41. return;
  42. }
  43. treePath = path;
  44. qDebug() << "set directory tree path:" << path;
  45. updateDirectoryTree();
  46. if (topLevelItemCount() > 0) {
  47. setCurrentItem(topLevelItem(0));
  48. }
  49. }
  50. bool VDirectoryTree::validatePath(const QString &path)
  51. {
  52. return QDir(path).exists();
  53. }
  54. void VDirectoryTree::updateDirectoryTree()
  55. {
  56. updateDirectoryTreeTopLevel();
  57. int nrTopLevelItems = topLevelItemCount();
  58. for (int i = 0; i < nrTopLevelItems; ++i) {
  59. QTreeWidgetItem *item = topLevelItem(i);
  60. Q_ASSERT(item);
  61. updateDirectoryTreeOne(*item, 1);
  62. }
  63. }
  64. // QJsonObject stored in each item's data[UserRole]:
  65. // 1. @item's related item in its parent's [sub_directories] section;
  66. // 2. "relative_path": the path where this item exists, relative to the treePath.
  67. void VDirectoryTree::fillDirectoryTreeItem(QTreeWidgetItem &item, QJsonObject itemJson, const QString &relativePath)
  68. {
  69. item.setText(0, itemJson["name"].toString());
  70. QString description = itemJson["description"].toString();
  71. if (!description.isEmpty()) {
  72. item.setToolTip(0, description);
  73. }
  74. itemJson["relative_path"] = relativePath;
  75. item.setData(0, Qt::UserRole, itemJson);
  76. }
  77. QTreeWidgetItem* VDirectoryTree::insertDirectoryTreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *preceding,
  78. const QJsonObject &newItem)
  79. {
  80. QTreeWidgetItem *item;
  81. QString relativePath;
  82. if (parent) {
  83. if (preceding) {
  84. item = new QTreeWidgetItem(parent, preceding);
  85. } else {
  86. item = new QTreeWidgetItem(parent);
  87. }
  88. QJsonObject parentJson = parent->data(0, Qt::UserRole).toJsonObject();
  89. Q_ASSERT(!parentJson.isEmpty());
  90. QString parentRelativePath = parentJson["relative_path"].toString();
  91. QString parentName = parentJson["name"].toString();
  92. relativePath = QDir(parentRelativePath).filePath(parentName);
  93. } else {
  94. if (preceding) {
  95. item = new QTreeWidgetItem(this, preceding);
  96. } else {
  97. item = new QTreeWidgetItem(this);
  98. }
  99. relativePath = "";
  100. }
  101. fillDirectoryTreeItem(*item, newItem, relativePath);
  102. qDebug() << "insert new Item name:" << newItem["name"].toString()
  103. << "relative_path:" << relativePath;
  104. return item;
  105. }
  106. void VDirectoryTree::removeDirectoryTreeItem(QTreeWidgetItem *item)
  107. {
  108. delete item;
  109. }
  110. void VDirectoryTree::updateDirectoryTreeTopLevel()
  111. {
  112. const QString &path = treePath;
  113. clear();
  114. if (!validatePath(path)) {
  115. qDebug() << "invalid notebook path:" << path;
  116. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook path."));
  117. msgBox.setInformativeText(QString("Notebook path \"%1\" either does not exist or is not valid.")
  118. .arg(path));
  119. msgBox.exec();
  120. return;
  121. }
  122. QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
  123. if (configJson.isEmpty()) {
  124. qDebug() << "invalid notebook configuration for path:" << path;
  125. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook configuration."));
  126. msgBox.setInformativeText(QString("Notebook path \"%1\" does not contain a valid configuration file.")
  127. .arg(path));
  128. msgBox.exec();
  129. return;
  130. }
  131. // Handle sub_directories section
  132. QJsonArray dirJson = configJson["sub_directories"].toArray();
  133. QTreeWidgetItem *preItem = NULL;
  134. for (int i = 0; i < dirJson.size(); ++i) {
  135. QJsonObject dirItem = dirJson[i].toObject();
  136. QTreeWidgetItem *treeItem = insertDirectoryTreeItem(NULL, preItem, dirItem);
  137. preItem = treeItem;
  138. }
  139. qDebug() << "updated" << dirJson.size() << "top-level items";
  140. }
  141. void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem &parent, int depth)
  142. {
  143. Q_ASSERT(parent.childCount() == 0);
  144. // Going deep enough
  145. if (depth <= 0) {
  146. return;
  147. }
  148. QJsonObject parentJson = parent.data(0, Qt::UserRole).toJsonObject();
  149. QString relativePath = QDir(parentJson["relative_path"].toString()).filePath(parentJson["name"].toString());
  150. QString path(QDir::cleanPath(treePath + QDir::separator() + relativePath));
  151. if (!validatePath(path)) {
  152. qDebug() << "invalide notebook directory:" << path;
  153. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory."));
  154. msgBox.setInformativeText(QString("Notebook directory \"%1\" either does not exist or is not a valid notebook directory.")
  155. .arg(path));
  156. msgBox.exec();
  157. return;
  158. }
  159. QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
  160. if (configJson.isEmpty()) {
  161. qDebug() << "invalid notebook configuration for directory:" << path;
  162. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."));
  163. msgBox.setInformativeText(QString("Notebook directory \"%1\" does not contain a valid configuration file.")
  164. .arg(path));
  165. msgBox.exec();
  166. return;
  167. }
  168. // Handle sub_directories section
  169. QJsonArray dirJson = configJson["sub_directories"].toArray();
  170. QTreeWidgetItem *preItem = NULL;
  171. for (int i = 0; i < dirJson.size(); ++i) {
  172. QJsonObject dirItem = dirJson[i].toObject();
  173. QTreeWidgetItem *treeItem = insertDirectoryTreeItem(&parent, preItem, dirItem);
  174. preItem = treeItem;
  175. // Update its sub-directory recursively
  176. updateDirectoryTreeOne(*treeItem, depth - 1);
  177. }
  178. }
  179. void VDirectoryTree::updateItemSubtree(QTreeWidgetItem *item)
  180. {
  181. QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
  182. Q_ASSERT(!itemJson.isEmpty());
  183. int nrChild = item->childCount();
  184. if (nrChild == 0) {
  185. updateDirectoryTreeOne(*item, 2);
  186. } else {
  187. for (int i = 0; i < nrChild; ++i) {
  188. QTreeWidgetItem *childItem = item->child(i);
  189. if (childItem->childCount() > 0) {
  190. continue;
  191. }
  192. updateDirectoryTreeOne(*childItem, 1);
  193. }
  194. }
  195. }
  196. void VDirectoryTree::contextMenuRequested(QPoint pos)
  197. {
  198. QTreeWidgetItem *item = itemAt(pos);
  199. QMenu menu(this);
  200. if (!item) {
  201. // Context menu on the free space of the QTreeWidget
  202. menu.addAction(newRootDirAct);
  203. } else {
  204. // Context menu on a QTreeWidgetItem
  205. if (item->parent()) {
  206. // Low-level item
  207. menu.addAction(newSubDirAct);
  208. menu.addAction(newSiblingDirAct);
  209. } else {
  210. // Top-level item
  211. menu.addAction(newRootDirAct);
  212. menu.addAction(newSubDirAct);
  213. }
  214. menu.addAction(deleteDirAct);
  215. }
  216. menu.exec(mapToGlobal(pos));
  217. }
  218. void VDirectoryTree::newSiblingDirectory()
  219. {
  220. QTreeWidgetItem *parentItem = currentItem()->parent();
  221. Q_ASSERT(parentItem);
  222. QJsonObject parentItemJson = parentItem->data(0, Qt::UserRole).toJsonObject();
  223. QString parentItemName = parentItemJson["name"].toString();
  224. QString text("&Directory name:");
  225. QString defaultText("new_directory");
  226. QString defaultDescription("");
  227. do {
  228. VNewDirDialog dialog(QString("Create a new directory under %1").arg(parentItemName), text,
  229. defaultText, tr("&Description:"), defaultDescription, this);
  230. if (dialog.exec() == QDialog::Accepted) {
  231. QString name = dialog.getNameInput();
  232. QString description = dialog.getDescriptionInput();
  233. if (isConflictNameWithChildren(parentItem, name)) {
  234. text = "Name already exists.\nPlease choose another name:";
  235. defaultText = name;
  236. defaultDescription = description;
  237. continue;
  238. }
  239. QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(parentItem, name, description);
  240. if (newItem) {
  241. this->setCurrentItem(newItem);
  242. }
  243. }
  244. break;
  245. } while (true);
  246. }
  247. void VDirectoryTree::newSubDirectory()
  248. {
  249. QTreeWidgetItem *curItem = currentItem();
  250. QJsonObject curItemJson = curItem->data(0, Qt::UserRole).toJsonObject();
  251. QString curItemName = curItemJson["name"].toString();
  252. QString text("&Directory name:");
  253. QString defaultText("new_directory");
  254. QString defaultDescription("");
  255. do {
  256. VNewDirDialog dialog(QString("Create a new directory under %1").arg(curItemName), text,
  257. defaultText, tr("&Description:"), defaultDescription, this);
  258. if (dialog.exec() == QDialog::Accepted) {
  259. QString name = dialog.getNameInput();
  260. QString description = dialog.getDescriptionInput();
  261. if (isConflictNameWithChildren(curItem, name)) {
  262. text = "Name already exists.\nPlease choose another name:";
  263. defaultText = name;
  264. defaultDescription = description;
  265. continue;
  266. }
  267. QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(curItem, name, description);
  268. if (newItem) {
  269. this->setCurrentItem(newItem);
  270. }
  271. }
  272. break;
  273. } while (true);
  274. }
  275. void VDirectoryTree::newRootDirectory()
  276. {
  277. QString text("&Directory name:");
  278. QString defaultText("new_directory");
  279. QString defaultDescription("");
  280. do {
  281. VNewDirDialog dialog(tr("Create a new root directory"), text,
  282. defaultText, tr("&Description:"), defaultDescription, this);
  283. if (dialog.exec() == QDialog::Accepted) {
  284. QString name = dialog.getNameInput();
  285. QString description = dialog.getDescriptionInput();
  286. if (isConflictNameWithChildren(NULL, name)) {
  287. text = "Name already exists.\nPlease choose another name:";
  288. defaultText = name;
  289. defaultDescription = description;
  290. continue;
  291. }
  292. QTreeWidgetItem *newItem = createDirectoryAndUpdateTree(NULL, name, description);
  293. if (newItem) {
  294. this->setCurrentItem(newItem);
  295. }
  296. }
  297. break;
  298. } while (true);
  299. }
  300. void VDirectoryTree::deleteDirectory()
  301. {
  302. QTreeWidgetItem *curItem = currentItem();
  303. QJsonObject curItemJson = curItem->data(0, Qt::UserRole).toJsonObject();
  304. QString curItemName = curItemJson["name"].toString();
  305. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Are you sure you want to delete directory \"%1\"?")
  306. .arg(curItemName));
  307. msgBox.setInformativeText(tr("This will delete any files under this directory."));
  308. msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
  309. msgBox.setDefaultButton(QMessageBox::Ok);
  310. if (msgBox.exec() == QMessageBox::Ok) {
  311. deleteDirectoryAndUpdateTree(curItem);
  312. }
  313. }
  314. QTreeWidgetItem* VDirectoryTree::createDirectoryAndUpdateTree(QTreeWidgetItem *parent,
  315. const QString &name, const QString &description)
  316. {
  317. QString relativePath("");
  318. QJsonObject parentJson;
  319. if (parent) {
  320. parentJson = parent->data(0, Qt::UserRole).toJsonObject();
  321. relativePath = QDir(parentJson["relative_path"].toString()).filePath(parentJson["name"].toString());
  322. }
  323. QString path = QDir(treePath).filePath(relativePath);
  324. QDir dir(path);
  325. if (!dir.mkdir(name)) {
  326. qWarning() << "error: fail to create directory" << name << "under" << path;
  327. QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create directory \"%1\" under \"%2\".")
  328. .arg(name).arg(path));
  329. msgBox.setInformativeText(QString("Please check if there already exists a directory named \"%1\".").arg(name));
  330. msgBox.exec();
  331. return NULL;
  332. }
  333. QJsonObject configJson;
  334. configJson["version"] = "1";
  335. configJson["name"] = name;
  336. configJson["sub_directories"] = QJsonArray();
  337. configJson["files"] = QJsonArray();
  338. if (!VConfigManager::writeDirectoryConfig(QDir(path).filePath(name), configJson)) {
  339. return NULL;
  340. }
  341. // Update parent's config file to include this new directory
  342. configJson = VConfigManager::readDirectoryConfig(path);
  343. Q_ASSERT(!configJson.isEmpty());
  344. QJsonObject itemJson;
  345. itemJson["name"] = name;
  346. itemJson["description"] = description;
  347. QJsonArray subDirArray = configJson["sub_directories"].toArray();
  348. subDirArray.append(itemJson);
  349. configJson["sub_directories"] = subDirArray;
  350. if (!VConfigManager::writeDirectoryConfig(path, configJson)) {
  351. VConfigManager::deleteDirectoryConfig(QDir(path).filePath(name));
  352. dir.rmdir(name);
  353. return NULL;
  354. }
  355. return insertDirectoryTreeItem(parent, NULL, itemJson);
  356. }
  357. void VDirectoryTree::deleteDirectoryAndUpdateTree(QTreeWidgetItem *item)
  358. {
  359. QJsonObject itemJson = item->data(0, Qt::UserRole).toJsonObject();
  360. QString itemName = itemJson["name"].toString();
  361. QString relativePath = itemJson["relative_path"].toString();
  362. // Update parent's config file to exclude this directory
  363. QString path = QDir(treePath).filePath(relativePath);
  364. QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
  365. Q_ASSERT(!configJson.isEmpty());
  366. QJsonArray subDirArray = configJson["sub_directories"].toArray();
  367. bool deleted = false;
  368. for (int i = 0; i < subDirArray.size(); ++i) {
  369. QJsonObject ele = subDirArray[i].toObject();
  370. if (ele["name"].toString() == itemName) {
  371. subDirArray.removeAt(i);
  372. deleted = true;
  373. break;
  374. }
  375. }
  376. if (!deleted) {
  377. qWarning() << "error: fail to find" << itemName << "to delete in its parent's configuration file";
  378. return;
  379. }
  380. configJson["sub_directories"] = subDirArray;
  381. if (!VConfigManager::writeDirectoryConfig(path, configJson)) {
  382. qWarning() << "error: fail to update parent's configuration file to delete" << itemName;
  383. return;
  384. }
  385. // Delete the entire directory
  386. QString dirName = QDir(path).filePath(itemName);
  387. QDir dir(dirName);
  388. if (!dir.removeRecursively()) {
  389. qWarning() << "error: fail to delete" << dirName << "recursively";
  390. } else {
  391. qDebug() << "delete" << dirName << "recursively";
  392. }
  393. // Update the tree
  394. removeDirectoryTreeItem(item);
  395. }
  396. bool VDirectoryTree::isConflictNameWithChildren(const QTreeWidgetItem *parent, const QString &name)
  397. {
  398. if (parent) {
  399. int nrChild = parent->childCount();
  400. for (int i = 0; i < nrChild; ++i) {
  401. QJsonObject childItemJson = parent->child(i)->data(0, Qt::UserRole).toJsonObject();
  402. Q_ASSERT(!childItemJson.isEmpty());
  403. if (childItemJson["name"].toString() == name) {
  404. return true;
  405. }
  406. }
  407. } else {
  408. int nrTopLevelItems = topLevelItemCount();
  409. for (int i = 0; i < nrTopLevelItems; ++i) {
  410. QJsonObject itemJson = topLevelItem(i)->data(0, Qt::UserRole).toJsonObject();
  411. Q_ASSERT(!itemJson.isEmpty());
  412. if (itemJson["name"].toString() == name) {
  413. return true;
  414. }
  415. }
  416. }
  417. return false;
  418. }
  419. void VDirectoryTree::currentDirectoryItemChanged(QTreeWidgetItem *currentItem)
  420. {
  421. if (!currentItem) {
  422. emit currentDirectoryChanged(QJsonObject());
  423. return;
  424. }
  425. QJsonObject itemJson = currentItem->data(0, Qt::UserRole).toJsonObject();
  426. Q_ASSERT(!itemJson.isEmpty());
  427. itemJson["root_path"] = treePath;
  428. qDebug() << "click dir:" << itemJson;
  429. emit currentDirectoryChanged(itemJson);
  430. }