Browse Source

add VFileList to browse the files in current directory

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 năm trước cách đây
mục cha
commit
9dd22760fc
9 tập tin đã thay đổi với 448 bổ sung15 xóa
  1. 6 2
      VNote.pro
  2. 18 5
      vdirectorytree.cpp
  3. 5 4
      vdirectorytree.h
  4. 261 0
      vfilelist.cpp
  5. 46 0
      vfilelist.h
  6. 6 3
      vmainwindow.cpp
  7. 2 1
      vmainwindow.h
  8. 64 0
      vnewfiledialog.cpp
  9. 40 0
      vnewfiledialog.h

+ 6 - 2
VNote.pro

@@ -18,14 +18,18 @@ SOURCES += main.cpp\
     vnote.cpp \
     vnote.cpp \
     vnotebook.cpp \
     vnotebook.cpp \
     vnewdirdialog.cpp \
     vnewdirdialog.cpp \
-    vconfigmanager.cpp
+    vconfigmanager.cpp \
+    vfilelist.cpp \
+    vnewfiledialog.cpp
 
 
 HEADERS  += vmainwindow.h \
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
     vdirectorytree.h \
     vnote.h \
     vnote.h \
     vnotebook.h \
     vnotebook.h \
     vnewdirdialog.h \
     vnewdirdialog.h \
-    vconfigmanager.h
+    vconfigmanager.h \
+    vfilelist.h \
+    vnewfiledialog.h
 
 
 RESOURCES += \
 RESOURCES += \
     vnote.qrc
     vnote.qrc

+ 18 - 5
vdirectorytree.cpp

@@ -1,5 +1,4 @@
 #include <QtWidgets>
 #include <QtWidgets>
-#include <QJsonObject>
 #include "vdirectorytree.h"
 #include "vdirectorytree.h"
 #include "vnewdirdialog.h"
 #include "vnewdirdialog.h"
 #include "vconfigmanager.h"
 #include "vconfigmanager.h"
@@ -10,15 +9,17 @@ VDirectoryTree::VDirectoryTree(QWidget *parent)
     setColumnCount(1);
     setColumnCount(1);
     setHeaderHidden(true);
     setHeaderHidden(true);
     setContextMenuPolicy(Qt::CustomContextMenu);
     setContextMenuPolicy(Qt::CustomContextMenu);
-    initialActions();
+    initActions();
 
 
     connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
     connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)),
             this, SLOT(updateItemSubtree(QTreeWidgetItem*)));
             this, SLOT(updateItemSubtree(QTreeWidgetItem*)));
     connect(this, SIGNAL(customContextMenuRequested(QPoint)),
     connect(this, SIGNAL(customContextMenuRequested(QPoint)),
             this, SLOT(contextMenuRequested(QPoint)));
             this, SLOT(contextMenuRequested(QPoint)));
+    connect(this, &VDirectoryTree::currentItemChanged,
+            this, &VDirectoryTree::currentDirectoryItemChanged);
 }
 }
 
 
-void VDirectoryTree::initialActions()
+void VDirectoryTree::initActions()
 {
 {
     newRootDirAct = new QAction(tr("New &root directory"), this);
     newRootDirAct = new QAction(tr("New &root directory"), this);
     newRootDirAct->setStatusTip(tr("Create a new root directory in current notebook"));
     newRootDirAct->setStatusTip(tr("Create a new root directory in current notebook"));
@@ -51,6 +52,9 @@ void VDirectoryTree::setTreePath(const QString& path)
     qDebug() << "set directory tree path:" << path;
     qDebug() << "set directory tree path:" << path;
 
 
     updateDirectoryTree();
     updateDirectoryTree();
+    if (topLevelItemCount() > 0) {
+       setCurrentItem(topLevelItem(0));
+    }
 }
 }
 
 
 bool VDirectoryTree::validatePath(const QString &path)
 bool VDirectoryTree::validatePath(const QString &path)
@@ -180,7 +184,7 @@ void VDirectoryTree::updateDirectoryTreeOne(QTreeWidgetItem &parent, int depth)
     if (configJson.isEmpty()) {
     if (configJson.isEmpty()) {
         qDebug() << "invalid notebook configuration for directory:" << path;
         qDebug() << "invalid notebook configuration for directory:" << path;
         QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."));
         QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."));
-        msgBox.setInformativeText(QString("Notebook path \"%1\" does not contain a valid configuration file.")
+        msgBox.setInformativeText(QString("Notebook directory \"%1\" does not contain a valid configuration file.")
                                   .arg(path));
                                   .arg(path));
         msgBox.exec();
         msgBox.exec();
         return;
         return;
@@ -356,7 +360,7 @@ QTreeWidgetItem* VDirectoryTree::createDirectoryAndUpdateTree(QTreeWidgetItem *p
     QString path = QDir(treePath).filePath(relativePath);
     QString path = QDir(treePath).filePath(relativePath);
     QDir dir(path);
     QDir dir(path);
     if (!dir.mkdir(name)) {
     if (!dir.mkdir(name)) {
-        qDebug() << "warning: fail to create directory" << name << "under" << path;
+        qWarning() << "error: fail to create directory" << name << "under" << path;
         QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create directory \"%1\" under \"%2\".")
         QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create directory \"%1\" under \"%2\".")
                            .arg(name).arg(path));
                            .arg(name).arg(path));
         msgBox.setInformativeText(QString("Please check if there already exists a directory named \"%1\".").arg(name));
         msgBox.setInformativeText(QString("Please check if there already exists a directory named \"%1\".").arg(name));
@@ -458,3 +462,12 @@ bool VDirectoryTree::isConflictNameWithChildren(const QTreeWidgetItem *parent, c
     }
     }
     return false;
     return false;
 }
 }
+
+void VDirectoryTree::currentDirectoryItemChanged(QTreeWidgetItem *currentItem)
+{
+    QJsonObject itemJson = currentItem->data(0, Qt::UserRole).toJsonObject();
+    Q_ASSERT(!itemJson.isEmpty());
+    itemJson["root_path"] = treePath;
+    qDebug() << "click dir:" << itemJson;
+    emit currentDirectoryChanged(itemJson);
+}

+ 5 - 4
vdirectorytree.h

@@ -2,16 +2,16 @@
 #define VDIRECTORYTREE_H
 #define VDIRECTORYTREE_H
 
 
 #include <QTreeWidget>
 #include <QTreeWidget>
-
-class QJsonObject;
+#include <QJsonObject>
 
 
 class VDirectoryTree : public QTreeWidget
 class VDirectoryTree : public QTreeWidget
 {
 {
     Q_OBJECT
     Q_OBJECT
 public:
 public:
-    VDirectoryTree(QWidget *parent = 0);
+    explicit VDirectoryTree(QWidget *parent = 0);
 
 
 signals:
 signals:
+    void currentDirectoryChanged(QJsonObject itemJson);
 
 
 public slots:
 public slots:
     void setTreePath(const QString& path);
     void setTreePath(const QString& path);
@@ -27,6 +27,7 @@ private slots:
     void newSubDirectory();
     void newSubDirectory();
     void newRootDirectory();
     void newRootDirectory();
     void deleteDirectory();
     void deleteDirectory();
+    void currentDirectoryItemChanged(QTreeWidgetItem *currentItem);
 
 
 private:
 private:
     // Clean and pdate the TreeWidget according to treePath
     // Clean and pdate the TreeWidget according to treePath
@@ -41,7 +42,7 @@ private:
     // Fill the QTreeWidgetItem according to its QJsonObject.
     // Fill the QTreeWidgetItem according to its QJsonObject.
     // @relative_path is the path related to treePath.
     // @relative_path is the path related to treePath.
     void fillDirectoryTreeItem(QTreeWidgetItem &item, QJsonObject itemJson, const QString &relativePath);
     void fillDirectoryTreeItem(QTreeWidgetItem &item, QJsonObject itemJson, const QString &relativePath);
-    void initialActions();
+    void initActions();
     QTreeWidgetItem* createDirectoryAndUpdateTree(QTreeWidgetItem *parent, const QString &name,
     QTreeWidgetItem* createDirectoryAndUpdateTree(QTreeWidgetItem *parent, const QString &name,
                                                   const QString &description);
                                                   const QString &description);
     void deleteDirectoryAndUpdateTree(QTreeWidgetItem *item);
     void deleteDirectoryAndUpdateTree(QTreeWidgetItem *item);

+ 261 - 0
vfilelist.cpp

@@ -0,0 +1,261 @@
+#include <QtDebug>
+#include <QtWidgets>
+#include "vfilelist.h"
+#include "vconfigmanager.h"
+#include "vnewfiledialog.h"
+
+VFileList::VFileList(QWidget *parent)
+    : QListWidget(parent)
+{
+    setContextMenuPolicy(Qt::CustomContextMenu);
+    initActions();
+
+    connect(this, &VFileList::customContextMenuRequested,
+            this, &VFileList::contextMenuRequested);
+    connect(this, &VFileList::currentItemChanged,
+            this, &VFileList::currentFileItemChanged);
+}
+
+void VFileList::initActions()
+{
+    newFileAct = new QAction(tr("&New note"), this);
+    newFileAct->setStatusTip(tr("Create a new note in current directory"));
+    connect(newFileAct, &QAction::triggered,
+            this, &VFileList::newFile);
+
+    deleteFileAct = new QAction(tr("&Delete"), this);
+    deleteFileAct->setStatusTip(tr("Delete selected note"));
+    connect(deleteFileAct, &QAction::triggered,
+            this, &VFileList::deleteFile);
+}
+
+void VFileList::setDirectory(QJsonObject dirJson)
+{
+    Q_ASSERT(!dirJson.isEmpty());
+    directoryName = dirJson["name"].toString();
+    rootPath = dirJson["root_path"].toString();
+    relativePath = QDir(dirJson["relative_path"].toString()).filePath(directoryName);
+    qDebug() << "FileList update:" << rootPath << relativePath << directoryName;
+
+    updateFileList();
+}
+
+
+void VFileList::updateFileList()
+{
+    clear();
+    QString path = QDir(rootPath).filePath(relativePath);
+
+    if (!QDir(path).exists()) {
+        qDebug() << "invalid notebook directory:" << path;
+        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory."));
+        msgBox.setInformativeText(QString("Notebook directory \"%1\" either does not exist or is not valid.")
+                                  .arg(path));
+        msgBox.exec();
+        return;
+    }
+
+    QJsonObject configJson = VConfigManager::readDirectoryConfig(path);
+    if (configJson.isEmpty()) {
+        qDebug() << "invalid notebook configuration for directory:" << path;
+        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), tr("Invalid notebook directory configuration."));
+        msgBox.setInformativeText(QString("Notebook directory \"%1\" does not contain a valid configuration file.")
+                                  .arg(path));
+        msgBox.exec();
+        return;
+    }
+
+    // Handle files section
+    QJsonArray filesJson = configJson["files"].toArray();
+    for (int i = 0; i < filesJson.size(); ++i) {
+        QJsonObject fileItem = filesJson[i].toObject();
+        insertFileListItem(fileItem);
+    }
+}
+
+QListWidgetItem* VFileList::insertFileListItem(QJsonObject fileJson, bool atFront)
+{
+    Q_ASSERT(!fileJson.isEmpty());
+    QListWidgetItem *item = new QListWidgetItem(fileJson["name"].toString());
+    if (!fileJson["description"].toString().isEmpty()) {
+        item->setToolTip(fileJson["description"].toString());
+    }
+    item->setData(Qt::UserRole, fileJson);
+
+    if (atFront) {
+        insertItem(0, item);
+    } else {
+        addItem(item);
+    }
+    qDebug() << "add new list item:" << fileJson["name"].toString();
+    return item;
+}
+
+void VFileList::removeFileListItem(QListWidgetItem *item)
+{
+    // Qt ensures it will be removed from QListWidget automatically
+    delete item;
+}
+
+void VFileList::newFile()
+{
+    QString text("&Note name:");
+    QString defaultText("new_note");
+    QString defaultDescription("");
+    do {
+        VNewFileDialog dialog(QString("Create a new note under %1").arg(directoryName), text,
+                              defaultText, tr("&Description:"), defaultDescription, this);
+        if (dialog.exec() == QDialog::Accepted) {
+            QString name = dialog.getNameInput();
+            QString description = dialog.getDescriptionInput();
+            if (isConflictNameWithExisting(name)) {
+                text = "Name already exists.\nPlease choose another name:";
+                defaultText = name;
+                defaultDescription = description;
+                continue;
+            }
+            QListWidgetItem *newItem = createFileAndUpdateList(name, description);
+            if (newItem) {
+                this->setCurrentItem(newItem);
+            }
+        }
+        break;
+    } while (true);
+}
+
+void VFileList::deleteFile()
+{
+    QListWidgetItem *curItem = currentItem();
+    QJsonObject curItemJson = curItem->data(Qt::UserRole).toJsonObject();
+    QString curItemName = curItemJson["name"].toString();
+
+    QMessageBox msgBox(QMessageBox::Warning, tr("Warning"),
+                       QString("Are you sure you want to delete note \"%1\"?")
+                       .arg(curItemName));
+    msgBox.setInformativeText(tr("This may be not recoverable."));
+    msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
+    msgBox.setDefaultButton(QMessageBox::Ok);
+    if (msgBox.exec() == QMessageBox::Ok) {
+        deleteFileAndUpdateList(curItem);
+    }
+}
+
+void VFileList::contextMenuRequested(QPoint pos)
+{
+    QListWidgetItem *item = itemAt(pos);
+    QMenu menu(this);
+
+    if (directoryName.isEmpty()) {
+        return;
+    }
+    menu.addAction(newFileAct);
+    if (item) {
+        menu.addAction(deleteFileAct);
+    }
+
+    menu.exec(mapToGlobal(pos));
+}
+
+bool VFileList::isConflictNameWithExisting(const QString &name)
+{
+    int nrChild = this->count();
+    for (int i = 0; i < nrChild; ++i) {
+        QListWidgetItem *item = this->item(i);
+        QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject();
+        Q_ASSERT(!itemJson.isEmpty());
+        if (itemJson["name"].toString() == name) {
+            return true;
+        }
+    }
+    return false;
+}
+
+QListWidgetItem* VFileList::createFileAndUpdateList(const QString &name,
+                                                    const QString &description)
+{
+    QString path = QDir(rootPath).filePath(relativePath);
+    QString filePath = QDir(path).filePath(name);
+    QFile file(filePath);
+
+    if (!file.open(QIODevice::WriteOnly)) {
+        qWarning() << "error: fail to create file:" << filePath;
+        QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Could not create file \"%1\" under \"%2\".")
+                           .arg(name).arg(path));
+        msgBox.setInformativeText(QString("Please check if there already exists a file named \"%1\".").arg(name));
+        msgBox.exec();
+        return NULL;
+    }
+    file.close();
+    qDebug() << "create file:" << filePath;
+
+    // Update current directory's config file to include this new file
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    Q_ASSERT(!dirJson.isEmpty());
+    QJsonObject fileJson;
+    fileJson["name"] = name;
+    fileJson["description"] = description;
+    QJsonArray fileArray = dirJson["files"].toArray();
+    fileArray.push_front(fileJson);
+    dirJson["files"] = fileArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        qWarning() << "error: fail to update directory's configuration file to add a new file"
+                   << name;
+        file.remove();
+        return NULL;
+    }
+
+    return insertFileListItem(fileJson, true);
+}
+
+void VFileList::deleteFileAndUpdateList(QListWidgetItem *item)
+{
+    Q_ASSERT(item);
+    QJsonObject itemJson = item->data(Qt::UserRole).toJsonObject();
+    QString path = QDir(rootPath).filePath(relativePath);
+    QString fileName = itemJson["name"].toString();
+    QString filePath = QDir(path).filePath(fileName);
+
+    // Update current directory's config file to exclude this file
+    QJsonObject dirJson = VConfigManager::readDirectoryConfig(path);
+    Q_ASSERT(!dirJson.isEmpty());
+    QJsonArray fileArray = dirJson["files"].toArray();
+    bool deleted = false;
+    for (int i = 0; i < fileArray.size(); ++i) {
+        QJsonObject ele = fileArray[i].toObject();
+        if (ele["name"].toString() == fileName) {
+            fileArray.removeAt(i);
+            deleted = true;
+            break;
+        }
+    }
+    if (!deleted) {
+        qWarning() << "error: fail to find" << fileName << "to delete";
+        return;
+    }
+    dirJson["files"] = fileArray;
+    if (!VConfigManager::writeDirectoryConfig(path, dirJson)) {
+        qWarning() << "error: fail to update directory's configuration file to delete"
+                   << fileName;
+        return;
+    }
+
+
+    // Delete the file
+    QFile file(filePath);
+    if (!file.remove()) {
+        qWarning() << "error: fail to delete" << filePath;
+    } else {
+        qDebug() << "delete" << filePath;
+    }
+
+    removeFileListItem(item);
+}
+
+void VFileList::currentFileItemChanged(QListWidgetItem *currentItem)
+{
+    QJsonObject itemJson = currentItem->data(Qt::UserRole).toJsonObject();
+    Q_ASSERT(!itemJson.isEmpty());
+    itemJson["path"] = QDir::cleanPath(QDir(rootPath).filePath(relativePath));
+    qDebug() << "click file:" << itemJson;
+    emit currentFileChanged(itemJson);
+}

+ 46 - 0
vfilelist.h

@@ -0,0 +1,46 @@
+#ifndef VFILELIST_H
+#define VFILELIST_H
+
+#include <QListWidget>
+#include <QJsonObject>
+
+class QAction;
+
+class VFileList : public QListWidget
+{
+    Q_OBJECT
+public:
+    explicit VFileList(QWidget *parent = 0);
+
+signals:
+    void currentFileChanged(QJsonObject fileJson);
+
+private slots:
+    void newFile();
+    void deleteFile();
+    void contextMenuRequested(QPoint pos);
+    void currentFileItemChanged(QListWidgetItem *currentItem);
+
+public slots:
+    void setDirectory(QJsonObject dirJson);
+
+private:
+    void updateFileList();
+    QListWidgetItem *insertFileListItem(QJsonObject fileJson, bool atFront = false);
+    void removeFileListItem(QListWidgetItem *item);
+    void initActions();
+    bool isConflictNameWithExisting(const QString &name);
+    QListWidgetItem *createFileAndUpdateList(const QString &name,
+                                             const QString &description);
+    void deleteFileAndUpdateList(QListWidgetItem *item);
+
+    QString rootPath;
+    QString relativePath;
+    QString directoryName;
+
+    // Actions
+    QAction *newFileAct;
+    QAction *deleteFileAct;
+};
+
+#endif // VFILELIST_H

+ 6 - 3
vmainwindow.cpp

@@ -2,6 +2,7 @@
 #include "vmainwindow.h"
 #include "vmainwindow.h"
 #include "vdirectorytree.h"
 #include "vdirectorytree.h"
 #include "vnote.h"
 #include "vnote.h"
+#include "vfilelist.h"
 
 
 VMainWindow::VMainWindow(QWidget *parent)
 VMainWindow::VMainWindow(QWidget *parent)
     : QMainWindow(parent)
     : QMainWindow(parent)
@@ -40,8 +41,8 @@ void VMainWindow::setupUI()
     nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
     nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
 
 
     // File list widget
     // File list widget
-    fileListWidget = new QListWidget();
-    fileListWidget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
+    fileList = new VFileList();
+    fileList->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
 
 
     // Editor tab widget
     // Editor tab widget
     editorTabWidget = new QTabWidget();
     editorTabWidget = new QTabWidget();
@@ -60,7 +61,7 @@ void VMainWindow::setupUI()
     // Main Splitter
     // Main Splitter
     mainSplitter = new QSplitter();
     mainSplitter = new QSplitter();
     mainSplitter->addWidget(nbContainer);
     mainSplitter->addWidget(nbContainer);
-    mainSplitter->addWidget(fileListWidget);
+    mainSplitter->addWidget(fileList);
     mainSplitter->addWidget(editorTabWidget);
     mainSplitter->addWidget(editorTabWidget);
     mainSplitter->setStretchFactor(0, 1);
     mainSplitter->setStretchFactor(0, 1);
     mainSplitter->setStretchFactor(1, 1);
     mainSplitter->setStretchFactor(1, 1);
@@ -71,6 +72,8 @@ void VMainWindow::setupUI()
             SLOT(setCurNotebookIndex(int)));
             SLOT(setCurNotebookIndex(int)));
     connect(this, SIGNAL(curNotebookIndexChanged(const QString&)), directoryTree,
     connect(this, SIGNAL(curNotebookIndexChanged(const QString&)), directoryTree,
             SLOT(setTreePath(const QString&)));
             SLOT(setTreePath(const QString&)));
+    connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
+            fileList, &VFileList::setDirectory);
 
 
     setCentralWidget(mainSplitter);
     setCentralWidget(mainSplitter);
     // Create and show the status bar
     // Create and show the status bar

+ 2 - 1
vmainwindow.h

@@ -10,6 +10,7 @@ class QSplitter;
 class QListWidget;
 class QListWidget;
 class QTabWidget;
 class QTabWidget;
 class VNote;
 class VNote;
+class VFileList;
 
 
 class VMainWindow : public QMainWindow
 class VMainWindow : public QMainWindow
 {
 {
@@ -34,7 +35,7 @@ private:
     QLabel *notebookLabel;
     QLabel *notebookLabel;
     QComboBox *notebookComboBox;
     QComboBox *notebookComboBox;
     VDirectoryTree *directoryTree;
     VDirectoryTree *directoryTree;
-    QListWidget *fileListWidget;
+    VFileList *fileList;
     QTabWidget *editorTabWidget;
     QTabWidget *editorTabWidget;
     QSplitter *mainSplitter;
     QSplitter *mainSplitter;
     VNote *vnote;
     VNote *vnote;

+ 64 - 0
vnewfiledialog.cpp

@@ -0,0 +1,64 @@
+#include <QtWidgets>
+#include "vnewfiledialog.h"
+
+VNewFileDialog::VNewFileDialog(const QString &title, const QString &name, const QString &defaultName,
+                             const QString &description, const QString &defaultDescription,
+                             QWidget *parent)
+    : QDialog(parent), title(title), name(name), defaultName(defaultName),
+      description(description), defaultDescription(defaultDescription)
+{
+    setupUI();
+
+    connect(nameEdit, &QLineEdit::textChanged, this, &VNewFileDialog::enableOkButton);
+    connect(okBtn, &QPushButton::clicked, this, &VNewFileDialog::accept);
+    connect(cancelBtn, &QPushButton::clicked, this, &VNewFileDialog::reject);
+}
+
+void VNewFileDialog::setupUI()
+{
+    nameLabel = new QLabel(name);
+    nameEdit = new QLineEdit(defaultName);
+    nameEdit->selectAll();
+    nameLabel->setBuddy(nameEdit);
+
+    descriptionLabel = new QLabel(description);
+    descriptionEdit = new QLineEdit(defaultDescription);
+    descriptionLabel->setBuddy(descriptionEdit);
+
+    okBtn = new QPushButton(tr("&OK"));
+    okBtn->setDefault(true);
+    cancelBtn = new QPushButton(tr("&Cancel"));
+
+    QVBoxLayout *topLayout = new QVBoxLayout();
+    topLayout->addWidget(nameLabel);
+    topLayout->addWidget(nameEdit);
+    topLayout->addWidget(descriptionLabel);
+    topLayout->addWidget(descriptionEdit);
+
+    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 VNewFileDialog::enableOkButton(const QString &editText)
+{
+    okBtn->setEnabled(!editText.isEmpty());
+}
+
+QString VNewFileDialog::getNameInput() const
+{
+    return nameEdit->text();
+}
+
+QString VNewFileDialog::getDescriptionInput() const
+{
+    return descriptionEdit->text();
+}

+ 40 - 0
vnewfiledialog.h

@@ -0,0 +1,40 @@
+#ifndef VNEWFILEDIALOG_H
+#define VNEWFILEDIALOG_H
+
+#include <QDialog>
+
+class QLabel;
+class QLineEdit;
+class QPushButton;
+class QString;
+
+class VNewFileDialog : public QDialog
+{
+    Q_OBJECT
+public:
+    VNewFileDialog(const QString &title, const QString &name, const QString &defaultName,
+                   const QString &description, const QString &defaultDescription, QWidget *parent = 0);
+    QString getNameInput() const;
+    QString getDescriptionInput() const;
+
+private slots:
+    void enableOkButton(const QString &editText);
+
+private:
+    void setupUI();
+
+    QLabel *nameLabel;
+    QLabel *descriptionLabel;
+    QLineEdit *nameEdit;
+    QLineEdit *descriptionEdit;
+    QPushButton *okBtn;
+    QPushButton *cancelBtn;
+
+    QString title;
+    QString name;
+    QString defaultName;
+    QString description;
+    QString defaultDescription;
+};
+
+#endif // VNEWFILEDIALOG_H