#include #include "vdirectorytree.h" #include "dialog/vnewdirdialog.h" #include "vconfigmanager.h" #include "dialog/vdirinfodialog.h" #include "vnote.h" #include "vdirectory.h" #include "utils/vutils.h" #include "veditarea.h" #include "vconfigmanager.h" extern VConfigManager vconfig; extern VNote *g_vnote; VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent) : QTreeWidget(parent), VNavigationMode(), vnote(vnote), m_editArea(NULL) { setColumnCount(1); setHeaderHidden(true); setContextMenuPolicy(Qt::CustomContextMenu); initActions(); connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(handleItemExpanded(QTreeWidgetItem*))); connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(handleItemCollapsed(QTreeWidgetItem*))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(this, &VDirectoryTree::currentItemChanged, this, &VDirectoryTree::currentDirectoryItemChanged); } void VDirectoryTree::initActions() { newRootDirAct = new QAction(QIcon(":/resources/icons/create_rootdir.svg"), tr("New &Root Directory"), this); newRootDirAct->setToolTip(tr("Create a new root directory in current notebook")); connect(newRootDirAct, &QAction::triggered, this, &VDirectoryTree::newRootDirectory); newSubDirAct = new QAction(tr("&New Sub-Directory"), this); newSubDirAct->setToolTip(tr("Create a new sub-directory")); connect(newSubDirAct, &QAction::triggered, this, &VDirectoryTree::newSubDirectory); deleteDirAct = new QAction(QIcon(":/resources/icons/delete_dir.svg"), tr("&Delete"), this); deleteDirAct->setToolTip(tr("Delete selected directory")); connect(deleteDirAct, &QAction::triggered, this, &VDirectoryTree::deleteDirectory); dirInfoAct = new QAction(QIcon(":/resources/icons/dir_info.svg"), tr("&Info"), this); dirInfoAct->setToolTip(tr("View and edit current directory's information")); connect(dirInfoAct, &QAction::triggered, this, &VDirectoryTree::editDirectoryInfo); copyAct = new QAction(QIcon(":/resources/icons/copy.svg"), tr("&Copy"), this); copyAct->setToolTip(tr("Copy selected directories")); connect(copyAct, &QAction::triggered, this, &VDirectoryTree::copySelectedDirectories); cutAct = new QAction(QIcon(":/resources/icons/cut.svg"), tr("C&ut"), this); cutAct->setToolTip(tr("Cut selected directories")); connect(cutAct, &QAction::triggered, this, &VDirectoryTree::cutSelectedDirectories); pasteAct = new QAction(QIcon(":/resources/icons/paste.svg"), tr("&Paste"), this); pasteAct->setToolTip(tr("Paste directories under this directory")); connect(pasteAct, &QAction::triggered, this, &VDirectoryTree::pasteDirectoriesInCurDir); m_openLocationAct = new QAction(tr("&Open Directory Location"), this); m_openLocationAct->setToolTip(tr("Open the folder containing this directory in operating system")); connect(m_openLocationAct, &QAction::triggered, this, &VDirectoryTree::openDirectoryLocation); } void VDirectoryTree::setNotebook(VNotebook *p_notebook) { setFocus(); if (m_notebook == p_notebook) { return; } if (m_notebook) { // Disconnect disconnect((VNotebook *)m_notebook, &VNotebook::contentChanged, this, &VDirectoryTree::updateDirectoryTree); } m_notebook = p_notebook; if (m_notebook) { connect((VNotebook *)m_notebook, &VNotebook::contentChanged, this, &VDirectoryTree::updateDirectoryTree); } else { clear(); return; } if (!m_notebook->open()) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to open notebook %2.") .arg(vconfig.c_dataTextStyle).arg(m_notebook->getName()), "", QMessageBox::Ok, QMessageBox::Ok, this); clear(); return; } updateDirectoryTree(); } void VDirectoryTree::fillTreeItem(QTreeWidgetItem &p_item, const QString &p_name, VDirectory *p_directory, const QIcon &p_icon) { p_item.setText(0, p_name); p_item.setToolTip(0, p_name); p_item.setData(0, Qt::UserRole, QVariant::fromValue(p_directory)); p_item.setIcon(0, p_icon); } void VDirectoryTree::updateDirectoryTree() { clear(); VDirectory *rootDir = m_notebook->getRootDir(); const QVector &subDirs = rootDir->getSubDirs(); for (int i = 0; i < subDirs.size(); ++i) { VDirectory *dir = subDirs[i]; QTreeWidgetItem *item = new QTreeWidgetItem(this); fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg")); updateDirectoryTreeOne(item, 1); } if (!restoreCurrentItem()) { setCurrentItem(topLevelItem(0)); } } bool VDirectoryTree::restoreCurrentItem() { auto it = m_notebookCurrentDirMap.find(m_notebook); if (it != m_notebookCurrentDirMap.end()) { bool rootDirectory; QTreeWidgetItem *item = findVDirectory(it.value(), rootDirectory); if (item) { setCurrentItem(item); return true; } } return false; } void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem *p_parent, int depth) { Q_ASSERT(p_parent->childCount() == 0); if (depth <= 0) { return; } VDirectory *dir = getVDirectory(p_parent); if (!dir->open()) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to open directory %2.") .arg(vconfig.c_dataTextStyle).arg(dir->getName()), "", QMessageBox::Ok, QMessageBox::Ok, this); return; } const QVector &subDirs = dir->getSubDirs(); for (int i = 0; i < subDirs.size(); ++i) { VDirectory *subDir = subDirs[i]; QTreeWidgetItem *item = new QTreeWidgetItem(p_parent); fillTreeItem(*item, subDir->getName(), subDir, QIcon(":/resources/icons/dir_item.svg")); updateDirectoryTreeOne(item, depth - 1); } if (dir->isExpanded()) { expandItem(p_parent); } } void VDirectoryTree::handleItemCollapsed(QTreeWidgetItem *p_item) { VDirectory *dir = getVDirectory(p_item); dir->setExpanded(false); } void VDirectoryTree::handleItemExpanded(QTreeWidgetItem *p_item) { updateChildren(p_item); VDirectory *dir = getVDirectory(p_item); dir->setExpanded(true); } // Update @p_item's children items void VDirectoryTree::updateChildren(QTreeWidgetItem *p_item) { Q_ASSERT(p_item); int nrChild = p_item->childCount(); if (nrChild == 0) { return; } for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *childItem = p_item->child(i); if (childItem->childCount() > 0) { continue; } updateDirectoryTreeOne(childItem, 1); } } void VDirectoryTree::updateItemChildren(QTreeWidgetItem *p_item) { QPointer parentDir; if (p_item) { parentDir = getVDirectory(p_item); } else { parentDir = m_notebook->getRootDir(); } const QVector &dirs = parentDir->getSubDirs(); QHash itemDirMap; int nrChild = p_item ? p_item->childCount() : topLevelItemCount(); for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *item = p_item ? p_item->child(i) : topLevelItem(i); itemDirMap.insert(getVDirectory(item), item); } for (int i = 0; i < dirs.size(); ++i) { VDirectory *dir = dirs[i]; QTreeWidgetItem *item = itemDirMap.value(dir, NULL); if (item) { if (p_item) { p_item->removeChild(item); p_item->insertChild(i, item); } else { int topIdx = indexOfTopLevelItem(item); takeTopLevelItem(topIdx); insertTopLevelItem(i, item); } itemDirMap.remove(dir); } else { // Insert a new item if (p_item) { item = new QTreeWidgetItem(p_item); } else { item = new QTreeWidgetItem(this); } fillTreeItem(*item, dir->getName(), dir, QIcon(":/resources/icons/dir_item.svg")); updateDirectoryTreeOne(item, 1); } expandItemTree(item); } // Delete items without corresponding VDirectory for (auto iter = itemDirMap.begin(); iter != itemDirMap.end(); ++iter) { QTreeWidgetItem *item = iter.value(); if (p_item) { p_item->removeChild(item); } else { int topIdx = indexOfTopLevelItem(item); takeTopLevelItem(topIdx); } delete item; } } void VDirectoryTree::contextMenuRequested(QPoint pos) { QTreeWidgetItem *item = itemAt(pos); if (!m_notebook) { return; } QMenu menu(this); menu.setToolTipsVisible(true); if (!item) { // Context menu on the free space of the QTreeWidget menu.addAction(newRootDirAct); } else { // Context menu on a QTreeWidgetItem if (item->parent()) { // Low-level item menu.addAction(newSubDirAct); } else { // Top-level item menu.addAction(newRootDirAct); menu.addAction(newSubDirAct); } menu.addAction(deleteDirAct); menu.addSeparator(); menu.addAction(copyAct); menu.addAction(cutAct); } if (VUtils::opTypeInClipboard() == ClipboardOpType::CopyDir && !m_copiedDirs.isEmpty()) { if (!item) { menu.addSeparator(); } menu.addAction(pasteAct); } if (item) { menu.addSeparator(); menu.addAction(m_openLocationAct); menu.addAction(dirInfoAct); } menu.exec(mapToGlobal(pos)); } void VDirectoryTree::newSubDirectory() { if (!m_notebook) { return; } QTreeWidgetItem *curItem = currentItem(); if (!curItem) { return; } VDirectory *curDir = getVDirectory(curItem); QString info = tr("Create sub-directory under %2.") .arg(vconfig.c_dataTextStyle).arg(curDir->getName()); QString text(tr("Directory &name:")); QString defaultText("new_directory"); do { VNewDirDialog dialog(tr("Create Directory"), info, text, defaultText, this); if (dialog.exec() == QDialog::Accepted) { QString name = dialog.getNameInput(); if (curDir->findSubDirectory(name)) { info = tr("Name already exists under %2. Please choose another name.") .arg(vconfig.c_dataTextStyle).arg(curDir->getName()); defaultText = name; continue; } VDirectory *subDir = curDir->createSubDirectory(name); if (!subDir) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to create directory %2.") .arg(vconfig.c_dataTextStyle).arg(name), "", QMessageBox::Ok, QMessageBox::Ok, this); return; } updateItemChildren(curItem); locateDirectory(subDir); } break; } while (true); } void VDirectoryTree::newRootDirectory() { if (!m_notebook) { return; } QString info = tr("Create root directory in notebook %2.") .arg(vconfig.c_dataTextStyle).arg(m_notebook->getName()); QString text(tr("Directory &name:")); QString defaultText("new_directory"); VDirectory *rootDir = m_notebook->getRootDir(); do { VNewDirDialog dialog(tr("Create Root Directory"), info, text, defaultText, this); if (dialog.exec() == QDialog::Accepted) { QString name = dialog.getNameInput(); if (rootDir->findSubDirectory(name)) { info = tr("Name already exists in notebook %2. Please choose another name.") .arg(vconfig.c_dataTextStyle).arg(m_notebook->getName()); defaultText = name; continue; } VDirectory *subDir = rootDir->createSubDirectory(name); if (!subDir) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to create directory %2.") .arg(vconfig.c_dataTextStyle).arg(name), "", QMessageBox::Ok, QMessageBox::Ok, this); return; } updateItemChildren(NULL); locateDirectory(subDir); } break; } while (true); } void VDirectoryTree::deleteDirectory() { QTreeWidgetItem *curItem = currentItem(); if (!curItem) { return; } VDirectory *curDir = getVDirectory(curItem); int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Are you sure to delete directory %2?") .arg(vconfig.c_dataTextStyle).arg(curDir->getName()), tr("WARNING: " "VNote will delete the whole directory (ANY files) " "%3." "
It may be UNRECOVERABLE!") .arg(vconfig.c_warningTextStyle).arg(vconfig.c_dataTextStyle).arg(curDir->retrivePath()), QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this, MessageBoxType::Danger); if (ret == QMessageBox::Ok) { m_editArea->closeFile(curDir, true); VDirectory *parentDir = curDir->getParentDirectory(); Q_ASSERT(parentDir); parentDir->deleteSubDirectory(curDir); delete curItem; } } void VDirectoryTree::currentDirectoryItemChanged(QTreeWidgetItem *currentItem) { if (!currentItem) { emit currentDirectoryChanged(NULL); return; } QPointer dir = getVDirectory(currentItem); m_notebookCurrentDirMap[m_notebook] = dir; emit currentDirectoryChanged(dir); } void VDirectoryTree::editDirectoryInfo() { QTreeWidgetItem *curItem = currentItem(); if (!curItem) { return; } VDirectory *curDir = getVDirectory(curItem); VDirectory *parentDir = curDir->getParentDirectory(); QString curName = curDir->getName(); QString info; QString defaultName = curName; do { VDirInfoDialog dialog(tr("Directory Information"), info, defaultName, this); if (dialog.exec() == QDialog::Accepted) { QString name = dialog.getNameInput(); if (name == curName) { return; } if (parentDir->findSubDirectory(name)) { info = "Name already exists. Please choose another name."; defaultName = name; continue; } if (!curDir->rename(name)) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to rename directory %2.") .arg(vconfig.c_dataTextStyle).arg(curName), "", QMessageBox::Ok, QMessageBox::Ok, this); return; } curItem->setText(0, name); curItem->setToolTip(0, name); emit directoryUpdated(curDir); } break; } while (true); } void VDirectoryTree::openDirectoryLocation() const { QTreeWidgetItem *curItem = currentItem(); V_ASSERT(curItem); QUrl url = QUrl::fromLocalFile(getVDirectory(curItem)->retriveBasePath()); QDesktopServices::openUrl(url); } void VDirectoryTree::copySelectedDirectories(bool p_cut) { QList items = selectedItems(); if (items.isEmpty()) { return; } QJsonArray dirs; m_copiedDirs.clear(); for (int i = 0; i < items.size(); ++i) { VDirectory *dir = getVDirectory(items[i]); QJsonObject dirJson; dirJson["notebook"] = dir->getNotebookName(); dirJson["path"] = dir->retrivePath(); dirs.append(dirJson); m_copiedDirs.append(dir); } copyDirectoryInfoToClipboard(dirs, p_cut); } void VDirectoryTree::copyDirectoryInfoToClipboard(const QJsonArray &p_dirs, bool p_cut) { QJsonObject clip; clip["operation"] = (int)ClipboardOpType::CopyDir; clip["is_cut"] = p_cut; clip["sources"] = p_dirs; QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(QJsonDocument(clip).toJson(QJsonDocument::Compact)); } void VDirectoryTree::cutSelectedDirectories() { copySelectedDirectories(true); } void VDirectoryTree::pasteDirectoriesInCurDir() { QTreeWidgetItem *item = currentItem(); VDirectory *destDir = m_notebook->getRootDir(); if (item) { destDir = getVDirectory(item); } pasteDirectories(destDir); } void VDirectoryTree::pasteDirectories(VDirectory *p_destDir) { QClipboard *clipboard = QApplication::clipboard(); QString text = clipboard->text(); QJsonObject clip = QJsonDocument::fromJson(text.toLocal8Bit()).object(); Q_ASSERT(!clip.isEmpty() && clip["operation"] == (int)ClipboardOpType::CopyDir); bool isCut = clip["is_cut"].toBool(); int nrPasted = 0; for (int i = 0; i < m_copiedDirs.size(); ++i) { QPointer srcDir = m_copiedDirs[i]; if (!srcDir) { continue; } QString dirName = srcDir->getName(); VDirectory *srcParentDir = srcDir->getParentDirectory(); if (srcParentDir == p_destDir && !isCut) { // Copy and paste in the same directory. // Rename it to xx_copy dirName = VUtils::generateCopiedDirName(srcParentDir->retrivePath(), dirName); } if (copyDirectory(p_destDir, dirName, srcDir, isCut)) { nrPasted++; } } qDebug() << "pasted" << nrPasted << "files successfully"; clipboard->clear(); m_copiedDirs.clear(); } void VDirectoryTree::mousePressEvent(QMouseEvent *event) { QTreeWidgetItem *item = itemAt(event->pos()); if (!item) { setCurrentItem(NULL); } QTreeWidget::mousePressEvent(event); } void VDirectoryTree::keyPressEvent(QKeyEvent *event) { int key = event->key(); int modifiers = event->modifiers(); switch (key) { case Qt::Key_Return: { QTreeWidgetItem *item = currentItem(); if (item) { item->setExpanded(!item->isExpanded()); } break; } case Qt::Key_J: { if (modifiers == Qt::ControlModifier) { event->accept(); QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier); QCoreApplication::postEvent(this, downEvent); return; } break; } case Qt::Key_K: { if (modifiers == Qt::ControlModifier) { event->accept(); QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier); QCoreApplication::postEvent(this, upEvent); return; } break; } default: break; } QTreeWidget::keyPressEvent(event); } bool VDirectoryTree::copyDirectory(VDirectory *p_destDir, const QString &p_destName, VDirectory *p_srcDir, bool p_cut) { qDebug() << "copy" << p_srcDir->getName() << "to" << p_destDir->getName() << "as" << p_destName; QString srcName = p_srcDir->getName(); QString srcPath = QDir::cleanPath(p_srcDir->retrivePath()); QString destPath = QDir::cleanPath(QDir(p_destDir->retrivePath()).filePath(p_destName)); if (srcPath == destPath) { return true; } VDirectory *srcParentDir = p_srcDir->getParentDirectory(); VDirectory *destDir = VDirectory::copyDirectory(p_destDir, p_destName, p_srcDir, p_cut); if (destDir) { // Update QTreeWidget bool isWidget; QTreeWidgetItem *destItem = findVDirectory(p_destDir, isWidget); if (destItem || isWidget) { updateItemChildren(destItem); } if (p_cut) { QTreeWidgetItem *srcItem = findVDirectory(srcParentDir, isWidget); if (srcItem || isWidget) { updateItemChildren(srcItem); } } // Broadcast this update emit directoryUpdated(destDir); } else { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to copy directory %2.") .arg(vconfig.c_dataTextStyle).arg(srcName), tr("Please check if there already exists a directory with the same name."), QMessageBox::Ok, QMessageBox::Ok, this); } return destDir; } QTreeWidgetItem *VDirectoryTree::findVDirectory(const VDirectory *p_dir, bool &p_widget) { p_widget = false; if (!p_dir) { return NULL; } else if (p_dir->getNotebookName() != m_notebook->getName()) { return NULL; } else if (p_dir == m_notebook->getRootDir()) { p_widget = true; return NULL; } bool isWidget; QTreeWidgetItem *pItem = findVDirectory(p_dir->getParentDirectory(), isWidget); if (pItem) { // Iterate all its children to find the match. int nrChild = pItem->childCount(); for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *item = pItem->child(i); if (getVDirectory(item) == p_dir) { return item; } } } else if (isWidget) { // Iterate all the top-level items. int nrChild = topLevelItemCount(); for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *item = topLevelItem(i); if (getVDirectory(item) == p_dir) { return item; } } } return NULL; } bool VDirectoryTree::locateDirectory(const VDirectory *p_directory) { if (p_directory) { qDebug() << "locate directory" << p_directory->retrivePath() << "in" << m_notebook->getName(); if (p_directory->getNotebook() != m_notebook) { return false; } QTreeWidgetItem *item = expandToVDirectory(p_directory); if (item) { setCurrentItem(item); } return item; } return false; } QTreeWidgetItem *VDirectoryTree::expandToVDirectory(const VDirectory *p_directory) { if (!p_directory || p_directory->getNotebook() != m_notebook || p_directory == m_notebook->getRootDir()) { return NULL; } if (p_directory->getParentDirectory() == m_notebook->getRootDir()) { // Top-level items. int nrChild = topLevelItemCount(); for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *item = topLevelItem(i); if (getVDirectory(item) == p_directory) { return item; } } } else { QTreeWidgetItem *pItem = expandToVDirectory(p_directory->getParentDirectory()); if (!pItem) { return NULL; } int nrChild = pItem->childCount(); if (nrChild == 0) { updateDirectoryTreeOne(pItem, 1); } nrChild = pItem->childCount(); for (int i = 0; i < nrChild; ++i) { QTreeWidgetItem *item = pItem->child(i); if (getVDirectory(item) == p_directory) { return item; } } } return NULL; } void VDirectoryTree::expandItemTree(QTreeWidgetItem *p_item) { if (!p_item) { return; } VDirectory *dir = getVDirectory(p_item); int nrChild = p_item->childCount(); for (int i = 0; i < nrChild; ++i) { expandItemTree(p_item->child(i)); } if (dir->isExpanded()) { Q_ASSERT(nrChild > 0); expandItem(p_item); } } void VDirectoryTree::registerNavigation(QChar p_majorKey) { m_majorKey = p_majorKey; V_ASSERT(m_keyMap.empty()); V_ASSERT(m_naviLabels.empty()); } void VDirectoryTree::showNavigation() { // Clean up. m_keyMap.clear(); for (auto label : m_naviLabels) { delete label; } m_naviLabels.clear(); if (!isVisible()) { return; } // Generate labels for visible items. auto items = getVisibleItems(); for (int i = 0; i < 26 && i < items.size(); ++i) { QChar key('a' + i); m_keyMap[key] = items[i]; QString str = QString(m_majorKey) + key; QLabel *label = new QLabel(str, this); label->setStyleSheet(g_vnote->getNavigationLabelStyle(str)); label->move(visualItemRect(items[i]).topLeft()); label->show(); m_naviLabels.append(label); } } void VDirectoryTree::hideNavigation() { m_keyMap.clear(); for (auto label : m_naviLabels) { delete label; } m_naviLabels.clear(); } bool VDirectoryTree::handleKeyNavigation(int p_key, bool &p_succeed) { static bool secondKey = false; bool ret = false; p_succeed = false; QChar keyChar = VUtils::keyToChar(p_key); if (secondKey && !keyChar.isNull()) { secondKey = false; p_succeed = true; ret = true; auto it = m_keyMap.find(keyChar); if (it != m_keyMap.end()) { setCurrentItem(it.value()); setFocus(); } } else if (keyChar == m_majorKey) { // Major key pressed. // Need second key if m_keyMap is not empty. if (m_keyMap.isEmpty()) { p_succeed = true; } else { secondKey = true; } ret = true; } return ret; } QList VDirectoryTree::getVisibleItems() const { QList items; for (int i = 0; i < topLevelItemCount(); ++i) { QTreeWidgetItem *item = topLevelItem(i); if (!item->isHidden()) { items.append(item); if (item->isExpanded()) { items.append(getVisibleChildItems(item)); } } } return items; } QList VDirectoryTree::getVisibleChildItems(const QTreeWidgetItem *p_item) const { QList items; if (p_item && !p_item->isHidden() && p_item->isExpanded()) { for (int i = 0; i < p_item->childCount(); ++i) { QTreeWidgetItem *child = p_item->child(i); if (!child->isHidden()) { items.append(child); if (child->isExpanded()) { items.append(getVisibleChildItems(child)); } } } } return items; }