diff --git a/src/dialog/vfileinfodialog.cpp b/src/dialog/vfileinfodialog.cpp new file mode 100644 index 00000000..d37b4f40 --- /dev/null +++ b/src/dialog/vfileinfodialog.cpp @@ -0,0 +1,60 @@ +#include +#include "vfileinfodialog.h" + +VFileInfoDialog::VFileInfoDialog(const QString &title, const QString &info, + const QString &defaultName, + QWidget *parent) + : QDialog(parent), infoLabel(NULL), title(title), info(info), defaultName(defaultName) +{ + setupUI(); + + connect(nameEdit, &QLineEdit::textChanged, this, &VFileInfoDialog::enableOkButton); + connect(okBtn, &QPushButton::clicked, this, &VFileInfoDialog::accept); + connect(cancelBtn, &QPushButton::clicked, this, &VFileInfoDialog::reject); + + enableOkButton(); +} + +void VFileInfoDialog::setupUI() +{ + if (!info.isEmpty()) { + infoLabel = new QLabel(info); + } + nameLabel = new QLabel(tr("&Name")); + nameEdit = new QLineEdit(defaultName); + nameEdit->selectAll(); + nameLabel->setBuddy(nameEdit); + + okBtn = new QPushButton(tr("&OK")); + okBtn->setDefault(true); + cancelBtn = new QPushButton(tr("&Cancel")); + + QVBoxLayout *topLayout = new QVBoxLayout(); + if (infoLabel) { + topLayout->addWidget(infoLabel); + } + topLayout->addWidget(nameLabel); + topLayout->addWidget(nameEdit); + + QHBoxLayout *btmLayout = new QHBoxLayout(); + btmLayout->addStretch(); + btmLayout->addWidget(okBtn); + btmLayout->addWidget(cancelBtn); + + QVBoxLayout *mainLayout = new QVBoxLayout(); + mainLayout->addLayout(topLayout); + mainLayout->addLayout(btmLayout); + setLayout(mainLayout); + + setWindowTitle(title); +} + +void VFileInfoDialog::enableOkButton() +{ + okBtn->setEnabled(!nameEdit->text().isEmpty()); +} + +QString VFileInfoDialog::getNameInput() const +{ + return nameEdit->text(); +} diff --git a/src/dialog/vfileinfodialog.h b/src/dialog/vfileinfodialog.h new file mode 100644 index 00000000..e4f461c1 --- /dev/null +++ b/src/dialog/vfileinfodialog.h @@ -0,0 +1,36 @@ +#ifndef VFILEINFODIALOG_H +#define VFILEINFODIALOG_H + +#include + +class QLabel; +class QLineEdit; +class QPushButton; +class QString; + +class VFileInfoDialog : public QDialog +{ + Q_OBJECT +public: + VFileInfoDialog(const QString &title, const QString &info, const QString &defaultName, + QWidget *parent = 0); + QString getNameInput() const; + +private slots: + void enableOkButton(); + +private: + void setupUI(); + + QLabel *infoLabel; + QLabel *nameLabel; + QLineEdit *nameEdit; + QPushButton *okBtn; + QPushButton *cancelBtn; + + QString title; + QString info; + QString defaultName; +}; + +#endif // VFILEINFODIALOG_H diff --git a/src/resources/icons/create_note.png b/src/resources/icons/create_note.png new file mode 100755 index 00000000..a76887a0 Binary files /dev/null and b/src/resources/icons/create_note.png differ diff --git a/src/resources/icons/delete_note.png b/src/resources/icons/delete_note.png new file mode 100755 index 00000000..67337156 Binary files /dev/null and b/src/resources/icons/delete_note.png differ diff --git a/src/resources/icons/note_info.png b/src/resources/icons/note_info.png new file mode 100755 index 00000000..8e03190a Binary files /dev/null and b/src/resources/icons/note_info.png differ diff --git a/src/src.pro b/src/src.pro index 67f36a91..4152078a 100644 --- a/src/src.pro +++ b/src/src.pro @@ -35,7 +35,9 @@ SOURCES += main.cpp\ dialog/vnewnotebookdialog.cpp \ vmarkdownconverter.cpp \ dialog/vnotebookinfodialog.cpp \ - dialog/vdirinfodialog.cpp + dialog/vdirinfodialog.cpp \ + vfilelistpanel.cpp \ + dialog/vfileinfodialog.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -61,7 +63,9 @@ HEADERS += vmainwindow.h \ dialog/vnewnotebookdialog.h \ vmarkdownconverter.h \ dialog/vnotebookinfodialog.h \ - dialog/vdirinfodialog.h + dialog/vdirinfodialog.h \ + vfilelistpanel.h \ + dialog/vfileinfodialog.h RESOURCES += \ vnote.qrc diff --git a/src/veditor.cpp b/src/veditor.cpp index dedb013d..5e90889b 100644 --- a/src/veditor.cpp +++ b/src/veditor.cpp @@ -175,9 +175,20 @@ bool VEditor::saveFile() if (!isEditMode || !noteFile->modifiable || !textEditor->isModified()) { return true; } + // Make sure the file already exists. Temporary deal with cases when user delete or move + // a file. + QString filePath = QDir(noteFile->basePath).filePath(noteFile->fileName); + if (!QFile(filePath).exists()) { + qWarning() << "error:" << filePath << "being written has been removed"; + QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"), + QString("%1 being written has been removed.").arg(filePath), + QMessageBox::Ok, this); + msgBox.setDefaultButton(QMessageBox::Ok); + msgBox.exec(); + return false; + } textEditor->saveFile(); - bool ret = VUtils::writeFileToDisk(QDir(noteFile->basePath).filePath(noteFile->fileName), - noteFile->content); + bool ret = VUtils::writeFileToDisk(filePath, noteFile->content); if (!ret) { QMessageBox msgBox(QMessageBox::Warning, tr("Fail to save to file"), QString("Fail to write to disk when saving a note. Please try it again."), diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index 3645658b..ee0478d2 100644 --- a/src/vfilelist.cpp +++ b/src/vfilelist.cpp @@ -55,7 +55,6 @@ void VFileList::setDirectory(QJsonObject dirJson) void VFileList::clearDirectoryInfo() { notebook = relativePath = rootPath = ""; - clear(); } void VFileList::updateFileList() @@ -357,6 +356,7 @@ void VFileList::handleNotebookRenamed(const QVector ¬ebooks, } } +// FIXME: when @oldRelativePath is part of relativePath, we also need to update relativePath partialy void VFileList::handleDirectoryRenamed(const QString ¬ebook, const QString &oldRelativePath, const QString &newRelativePath) { diff --git a/src/vfilelist.h b/src/vfilelist.h index ee0676ea..790f8a25 100644 --- a/src/vfilelist.h +++ b/src/vfilelist.h @@ -23,8 +23,6 @@ signals: void fileCreated(QJsonObject fileJson); private slots: - void newFile(); - void deleteFile(); void contextMenuRequested(QPoint pos); void handleItemClicked(QListWidgetItem *currentItem); @@ -34,6 +32,8 @@ public slots: const QString &newName); void handleDirectoryRenamed(const QString ¬ebook, const QString &oldRelativePath, const QString &newRelativePath); + void newFile(); + void deleteFile(); private: void updateFileList(); diff --git a/src/vfilelistpanel.cpp b/src/vfilelistpanel.cpp new file mode 100644 index 00000000..24cf99e8 --- /dev/null +++ b/src/vfilelistpanel.cpp @@ -0,0 +1,232 @@ +#include +#include "vfilelistpanel.h" +#include "vfilelist.h" +#include "dialog/vfileinfodialog.h" +#include "vnotebook.h" +#include "vnote.h" +#include "vconfigmanager.h" + +VFileListPanel::VFileListPanel(VNote *vnote, QWidget *parent) + : QWidget(parent), vnote(vnote) +{ + setupUI(); +} + +void VFileListPanel::setupUI() +{ + newFileBtn = new QPushButton(QIcon(":/resources/icons/create_note.png"), ""); + newFileBtn->setToolTip(tr("Create a new note")); + deleteFileBtn = new QPushButton(QIcon(":/resources/icons/delete_note.png"), ""); + deleteFileBtn->setToolTip(tr("Delete current note")); + fileInfoBtn = new QPushButton(QIcon(":/resources/icons/note_info.png"), ""); + fileInfoBtn->setToolTip(tr("View and edit current note's information")); + + fileList = new VFileList(vnote); + + QHBoxLayout *topLayout = new QHBoxLayout; + topLayout->addStretch(); + topLayout->addWidget(newFileBtn); + topLayout->addWidget(deleteFileBtn); + topLayout->addWidget(fileInfoBtn); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(topLayout); + mainLayout->addWidget(fileList); + + // Signals + connect(fileList, &VFileList::fileClicked, + this, &VFileListPanel::fileClicked); + connect(fileList, &VFileList::fileDeleted, + this, &VFileListPanel::fileDeleted); + connect(fileList, &VFileList::fileCreated, + this, &VFileListPanel::fileCreated); + + connect(newFileBtn, &QPushButton::clicked, + this, &VFileListPanel::onNewFileBtnClicked); + connect(deleteFileBtn, &QPushButton::clicked, + this, &VFileListPanel::onDeleteFileBtnClicked); + connect(fileInfoBtn, &QPushButton::clicked, + this, &VFileListPanel::onFileInfoBtnClicked); + + setLayout(mainLayout); +} + +void VFileListPanel::setDirectory(QJsonObject dirJson) +{ + if (dirJson.isEmpty()) { + clearDirectoryInfo(); + fileList->setDirectory(dirJson); + return; + } + + notebook = dirJson["notebook"].toString(); + relativePath = dirJson["relative_path"].toString(); + rootPath = ""; + const QVector ¬ebooks = vnote->getNotebooks(); + for (int i = 0; i < notebooks.size(); ++i) { + if (notebooks[i].getName() == notebook) { + rootPath = notebooks[i].getPath(); + break; + } + } + Q_ASSERT(!rootPath.isEmpty()); + + fileList->setDirectory(dirJson); +} + +void VFileListPanel::clearDirectoryInfo() +{ + notebook = relativePath = rootPath = ""; + fileList->clear(); +} + +void VFileListPanel::handleNotebookRenamed(const QVector ¬ebooks, + const QString &oldName, const QString &newName) +{ + if (oldName == notebook) { + // Update treePath (though treePath actually will not be changed) + notebook = newName; + rootPath.clear(); + const QVector ¬ebooks = vnote->getNotebooks(); + for (int i = 0; i < notebooks.size(); ++i) { + if (notebooks[i].getName() == notebook) { + rootPath = notebooks[i].getPath(); + break; + } + } + Q_ASSERT(!rootPath.isEmpty()); + } + fileList->handleNotebookRenamed(notebooks, oldName, newName); +} + +void VFileListPanel::handleDirectoryRenamed(const QString ¬ebook, + const QString &oldRelativePath, const QString &newRelativePath) +{ + if (notebook == this->notebook + && oldRelativePath == relativePath) { + relativePath = newRelativePath; + } + fileList->handleDirectoryRenamed(notebook, oldRelativePath, + newRelativePath); +} + +bool VFileListPanel::importFile(const QString &name) +{ + return fileList->importFile(name); +} + +void VFileListPanel::onNewFileBtnClicked() +{ + if (relativePath.isEmpty()) { + return; + } + fileList->newFile(); +} + +void VFileListPanel::onDeleteFileBtnClicked() +{ + QListWidgetItem *curItem = fileList->currentItem(); + if (!curItem) { + return; + } + fileList->deleteFile(); +} + +void VFileListPanel::onFileInfoBtnClicked() +{ + QListWidgetItem *curItem = fileList->currentItem(); + if (!curItem) { + return; + } + QJsonObject curItemJson = curItem->data(Qt::UserRole).toJsonObject(); + Q_ASSERT(!curItemJson.isEmpty()); + QString curItemName = curItemJson["name"].toString(); + + QString info; + QString defaultName = curItemName; + + do { + VFileInfoDialog dialog(tr("Note Information"), info, defaultName, this); + if (dialog.exec() == QDialog::Accepted) { + QString name = dialog.getNameInput(); + if (name == curItemName) { + return; + } + if (isConflictNameWithExisting(name)) { + info = "Name already exists.\nPlease choose another name:"; + defaultName = name; + continue; + } + renameFile(curItem, name); + } + break; + } while (true); +} + +bool VFileListPanel::isConflictNameWithExisting(const QString &name) +{ + int nrChild = fileList->count(); + for (int i = 0; i < nrChild; ++i) { + QListWidgetItem *item = fileList->item(i); + QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject(); + Q_ASSERT(!itemJson.isEmpty()); + if (itemJson["name"].toString() == name) { + return true; + } + } + return false; +} + +void VFileListPanel::renameFile(QListWidgetItem *item, const QString &newName) +{ + if (!item) { + return; + } + QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject(); + Q_ASSERT(!itemJson.isEmpty()); + QString name = itemJson["name"].toString(); + QString path = QDir(rootPath).filePath(relativePath); + QFile file(QDir(path).filePath(name)); + QString newFilePath(QDir(path).filePath(newName)); + Q_ASSERT(file.exists()); + if (!file.rename(newFilePath)) { + qWarning() << "error: fail to rename file" << name << "under" << path; + QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not rename note \"%1\" under \"%2\".") + .arg(name).arg(path), QMessageBox::Ok, this); + msgBox.setInformativeText(QString("Please check if there already exists a file named \"%1\".").arg(newName)); + msgBox.exec(); + return; + } + + // Update directory's config file + QJsonObject dirJson = VConfigManager::readDirectoryConfig(path); + Q_ASSERT(!dirJson.isEmpty()); + QJsonArray fileArray = dirJson["files"].toArray(); + int index = 0; + for (index = 0; index < fileArray.size(); ++index) { + QJsonObject tmp = fileArray[index].toObject(); + if (tmp["name"].toString() == name) { + tmp["name"] = newName; + fileArray[index] = tmp; + break; + } + } + Q_ASSERT(index != fileArray.size()); + dirJson["files"] = fileArray; + if (!VConfigManager::writeDirectoryConfig(path, dirJson)) { + qWarning() << "error: fail to rename file" + << name << "to" << newName; + file.rename(name); + return; + } + + // Update item + itemJson["name"] = newName; + item->setData(Qt::UserRole, itemJson); + item->setText(newName); + + QString oldPath = QDir::cleanPath(QDir(relativePath).filePath(name)); + QString newPath = QDir::cleanPath(QDir(relativePath).filePath(newName)); + qDebug() << "file renamed" << oldPath << "to" << newPath; + emit fileRenamed(notebook, oldPath, newPath); +} diff --git a/src/vfilelistpanel.h b/src/vfilelistpanel.h new file mode 100644 index 00000000..48ad86af --- /dev/null +++ b/src/vfilelistpanel.h @@ -0,0 +1,70 @@ +#ifndef VFILELISTPANEL_H +#define VFILELISTPANEL_H + +#include +#include +#include +#include +#include +#include +#include "vnotebook.h" + +class VNote; +class VFileList; +class QPushButton; +class QListWidgetItem; + +class VFileListPanel : public QWidget +{ + Q_OBJECT +public: + VFileListPanel(VNote *vnote, QWidget *parent = 0); + bool importFile(const QString &name); + +signals: + void fileClicked(QJsonObject fileJson); + void fileDeleted(QJsonObject fileJson); + void fileCreated(QJsonObject fileJson); + void fileRenamed(const QString ¬ebook, const QString &oldPath, + const QString &newPath); + +public slots: + void setDirectory(QJsonObject dirJson); + void handleNotebookRenamed(const QVector ¬ebooks, const QString &oldName, + const QString &newName); + void handleDirectoryRenamed(const QString ¬ebook, const QString &oldRelativePath, + const QString &newRelativePath); + +private slots: + void onNewFileBtnClicked(); + void onDeleteFileBtnClicked(); + void onFileInfoBtnClicked(); + +private: + void setupUI(); + bool isConflictNameWithExisting(const QString &name); + void renameFile(QListWidgetItem *item, const QString &newName); + void clearDirectoryInfo(); + inline QString getDirectoryName(); + + VNote *vnote; + QString notebook; + // Current directory's relative path + QString relativePath; + // Used for cache + QString rootPath; + + VFileList *fileList; + QPushButton *newFileBtn; + QPushButton *deleteFileBtn; + QPushButton *fileInfoBtn; +}; + +inline QString VFileListPanel::getDirectoryName() +{ + if (relativePath.isEmpty()) { + return ""; + } + return QFileInfo(QDir::cleanPath(relativePath)).fileName(); +} +#endif // VFILELISTPANEL_H diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 152ea528..96c41199 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -2,7 +2,7 @@ #include "vmainwindow.h" #include "vdirectorytree.h" #include "vnote.h" -#include "vfilelist.h" +#include "vfilelistpanel.h" #include "vtabwidget.h" #include "vconfigmanager.h" #include "dialog/vnewnotebookdialog.h" @@ -73,9 +73,8 @@ void VMainWindow::setupUI() nbContainer->setLayout(nbLayout); nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); - // File list widget - fileList = new VFileList(vnote); - fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + fileListPanel = new VFileListPanel(vnote); + fileListPanel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); // Editor tab widget tabs = new VTabWidget(vnote); @@ -85,7 +84,7 @@ void VMainWindow::setupUI() // Main Splitter mainSplitter = new QSplitter(); mainSplitter->addWidget(nbContainer); - mainSplitter->addWidget(fileList); + mainSplitter->addWidget(fileListPanel); mainSplitter->addWidget(tabs); mainSplitter->setStretchFactor(0, 1); mainSplitter->setStretchFactor(1, 1); @@ -96,17 +95,17 @@ void VMainWindow::setupUI() SLOT(setCurNotebookIndex(int))); connect(vnote, &VNote::notebooksRenamed, - fileList, &VFileList::handleNotebookRenamed); + fileListPanel, &VFileListPanel::handleNotebookRenamed); connect(directoryTree, &VDirectoryTree::currentDirectoryChanged, - fileList, &VFileList::setDirectory); + fileListPanel, &VFileListPanel::setDirectory); connect(directoryTree, &VDirectoryTree::directoryRenamed, - fileList, &VFileList::handleDirectoryRenamed); + fileListPanel, &VFileListPanel::handleDirectoryRenamed); - connect(fileList, &VFileList::fileClicked, + connect(fileListPanel, &VFileListPanel::fileClicked, tabs, &VTabWidget::openFile); - connect(fileList, &VFileList::fileDeleted, + connect(fileListPanel, &VFileListPanel::fileDeleted, tabs, &VTabWidget::closeFile); - connect(fileList, &VFileList::fileCreated, + connect(fileListPanel, &VFileListPanel::fileCreated, tabs, &VTabWidget::openFile); connect(newNotebookBtn, &QPushButton::clicked, @@ -383,14 +382,18 @@ void VMainWindow::onNotebookInfoBtnClicked() void VMainWindow::importNoteFromFile() { + static QString lastPath = QDir::homePath(); QStringList files = QFileDialog::getOpenFileNames(this,tr("Select files(HTML or Markdown) to be imported as notes"), - QDir::homePath()); + lastPath); if (files.isEmpty()) { return; } + // Update lastPath + lastPath = QFileInfo(files[0]).path(); + QStringList failedFiles; for (int i = 0; i < files.size(); ++i) { - bool ret = fileList->importFile(files[i]); + bool ret = fileListPanel->importFile(files[i]); if (!ret) { failedFiles.append(files[i]); } diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 7478b060..c902fbfc 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -11,12 +11,12 @@ class QListWidget; class QTabWidget; class QToolBar; class VNote; -class VFileList; class VTabWidget; class QAction; class QPushButton; class VNotebook; class QActionGroup; +class VFileListPanel; class VMainWindow : public QMainWindow { @@ -63,8 +63,8 @@ private: QPushButton *newRootDirBtn; QPushButton *deleteDirBtn; QPushButton *dirInfoBtn; + VFileListPanel *fileListPanel; VDirectoryTree *directoryTree; - VFileList *fileList; VTabWidget *tabs; QSplitter *mainSplitter; VNote *vnote; diff --git a/src/vnote.qrc b/src/vnote.qrc index f54a8060..24f42089 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -42,5 +42,8 @@ resources/icons/create_rootdir.png resources/icons/delete_dir.png resources/icons/dir_info.png + resources/icons/create_note.png + resources/icons/delete_note.png + resources/icons/note_info.png diff --git a/src/vtabwidget.cpp b/src/vtabwidget.cpp index 9ec870d7..81c095ac 100644 --- a/src/vtabwidget.cpp +++ b/src/vtabwidget.cpp @@ -54,7 +54,7 @@ void VTabWidget::openFile(QJsonObject fileJson) mode = fileJson["mode"].toInt(); } - qDebug() << "open notebook[" << notebook << "] path[" << relativePath << "]" << mode; + qDebug() << "open notebook" << notebook << "path" << relativePath << mode; // Find if it has been opened already int idx = findTabByFile(notebook, relativePath);