浏览代码

refactor: add VNotebookSelector for notebook management

Signed-off-by: Le Tan <[email protected]>
Le Tan 9 年之前
父节点
当前提交
9a197b5087

+ 11 - 14
src/dialog/vnewnotebookdialog.cpp

@@ -24,40 +24,37 @@ void VNewNotebookDialog::setupUI()
     if (!info.isEmpty()) {
         infoLabel = new QLabel(info);
     }
-    nameLabel = new QLabel(tr("&Name"));
+    nameLabel = new QLabel(tr("Notebook &name:"));
     nameEdit = new QLineEdit(defaultName);
     nameEdit->selectAll();
     nameLabel->setBuddy(nameEdit);
+    QHBoxLayout *nameLayout = new QHBoxLayout();
+    nameLayout->addWidget(nameLabel);
+    nameLayout->addWidget(nameEdit);
 
-    QLabel *pathLabel = new QLabel(tr("&Path"));
+    QLabel *pathLabel = new QLabel(tr("Notebook &path:"));
     pathEdit = new QLineEdit(defaultPath);
     pathLabel->setBuddy(pathEdit);
     browseBtn = new QPushButton(tr("&Browse"));
-
     QHBoxLayout *pathLayout = new QHBoxLayout();
+    pathLayout->addWidget(pathLabel);
     pathLayout->addWidget(pathEdit);
     pathLayout->addWidget(browseBtn);
 
     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);
-    topLayout->addWidget(pathLabel);
-    topLayout->addLayout(pathLayout);
-
     QHBoxLayout *btmLayout = new QHBoxLayout();
     btmLayout->addStretch();
     btmLayout->addWidget(okBtn);
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
-    mainLayout->addLayout(topLayout);
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
+    mainLayout->addLayout(nameLayout);
+    mainLayout->addLayout(pathLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);
 

+ 14 - 14
src/dialog/vnotebookinfodialog.cpp

@@ -21,36 +21,36 @@ void VNotebookInfoDialog::setupUI()
     if (!info.isEmpty()) {
         infoLabel = new QLabel(info);
     }
-    nameLabel = new QLabel(tr("&Name"));
+    nameLabel = new QLabel(tr("Notebook &name:"));
     nameEdit = new QLineEdit(defaultName);
     nameEdit->selectAll();
     nameLabel->setBuddy(nameEdit);
+    QHBoxLayout *nameLayout = new QHBoxLayout();
+    nameLayout->addWidget(nameLabel);
+    nameLayout->addWidget(nameEdit);
 
-    QLabel *pathLabel = new QLabel(tr("&Path"));
+    QLabel *pathLabel = new QLabel(tr("Notebook &path:"));
     pathEdit = new QLineEdit(defaultPath);
     pathLabel->setBuddy(pathEdit);
-    pathEdit->setEnabled(false);
+    pathEdit->setReadOnly(true);
+    QHBoxLayout *pathLayout = new QHBoxLayout();
+    pathLayout->addWidget(pathLabel);
+    pathLayout->addWidget(pathEdit);
 
     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);
-    topLayout->addWidget(pathLabel);
-    topLayout->addWidget(pathEdit);
-
     QHBoxLayout *btmLayout = new QHBoxLayout();
     btmLayout->addStretch();
     btmLayout->addWidget(okBtn);
     btmLayout->addWidget(cancelBtn);
 
     QVBoxLayout *mainLayout = new QVBoxLayout();
-    mainLayout->addLayout(topLayout);
+    if (infoLabel) {
+        mainLayout->addWidget(infoLabel);
+    }
+    mainLayout->addLayout(nameLayout);
+    mainLayout->addLayout(pathLayout);
     mainLayout->addLayout(btmLayout);
     setLayout(mainLayout);
 

+ 4 - 2
src/src.pro

@@ -44,7 +44,8 @@ SOURCES += main.cpp\
     vtoc.cpp \
     vsingleinstanceguard.cpp \
     vdirectory.cpp \
-    vfile.cpp
+    vfile.cpp \
+    vnotebookselector.cpp
 
 HEADERS  += vmainwindow.h \
     vdirectorytree.h \
@@ -77,7 +78,8 @@ HEADERS  += vmainwindow.h \
     vtoc.h \
     vsingleinstanceguard.h \
     vdirectory.h \
-    vfile.h
+    vfile.h \
+    vnotebookselector.h
 
 RESOURCES += \
     vnote.qrc

+ 1 - 1
src/vdirectory.cpp

@@ -97,7 +97,7 @@ QString VDirectory::retriveRelativePath(const VDirectory *p_dir) const
     }
 }
 
-QJsonObject VDirectory::createDirectoryJson() const
+QJsonObject VDirectory::createDirectoryJson()
 {
     QJsonObject dirJson;
     dirJson["version"] = "1";

+ 2 - 1
src/vdirectory.h

@@ -56,12 +56,13 @@ public:
     inline QString retriveRelativePath() const;
     inline QString getNotebook() const;
 
+    static QJsonObject createDirectoryJson();
+
 private:
     // Get the path of @p_dir recursively
     QString retrivePath(const VDirectory *p_dir) const;
     // Get teh relative path of @p_dir recursively related to the notebook path
     QString retriveRelativePath(const VDirectory *p_dir) const;
-    QJsonObject createDirectoryJson() const;
     bool createFileInConfig(const QString &p_name, int p_index = -1);
     bool createSubDirectoryInConfig(const QString &p_name, int p_index = -1);
 

+ 3 - 1
src/vdirectorytree.cpp

@@ -6,9 +6,10 @@
 #include "vnote.h"
 #include "vdirectory.h"
 #include "utils/vutils.h"
+#include "veditarea.h"
 
 VDirectoryTree::VDirectoryTree(VNote *vnote, QWidget *parent)
-    : QTreeWidget(parent), vnote(vnote)
+    : QTreeWidget(parent), vnote(vnote), m_editArea(NULL)
 {
     setColumnCount(1);
     setHeaderHidden(true);
@@ -343,6 +344,7 @@ void VDirectoryTree::deleteDirectory()
                                   tr("This will delete any files under this directory."), QMessageBox::Ok | QMessageBox::Cancel,
                                   QMessageBox::Ok, this);
     if (ret == QMessageBox::Ok) {
+        m_editArea->closeFile(curDir, true);
         VDirectory *parentDir = curDir->getParentDirectory();
         Q_ASSERT(parentDir);
         parentDir->deleteSubDirectory(curDir);

+ 8 - 0
src/vdirectorytree.h

@@ -9,12 +9,14 @@
 #include "vnotebook.h"
 
 class VNote;
+class VEditArea;
 
 class VDirectoryTree : public QTreeWidget
 {
     Q_OBJECT
 public:
     explicit VDirectoryTree(VNote *vnote, QWidget *parent = 0);
+    inline void setEditArea(VEditArea *p_editArea);
 
 signals:
     void currentDirectoryChanged(VDirectory *p_directory);
@@ -59,6 +61,7 @@ private:
     VNote *vnote;
     QPointer<VNotebook> m_notebook;
     QVector<QPointer<VDirectory> > m_copiedDirs;
+    VEditArea *m_editArea;
 
     // Actions
     QAction *newRootDirAct;
@@ -77,4 +80,9 @@ inline QPointer<VDirectory> VDirectoryTree::getVDirectory(QTreeWidgetItem *p_ite
     return p_item->data(0, Qt::UserRole).value<VDirectory *>();
 }
 
+inline void VDirectoryTree::setEditArea(VEditArea *p_editArea)
+{
+    m_editArea = p_editArea;
+}
+
 #endif // VDIRECTORYTREE_H

+ 90 - 36
src/veditarea.cpp

@@ -7,7 +7,7 @@
 #include "vfile.h"
 
 VEditArea::VEditArea(VNote *vnote, QWidget *parent)
-    : QWidget(parent), vnote(vnote), curWindowIndex(0)
+    : QWidget(parent), vnote(vnote), curWindowIndex(-1)
 {
     setupUI();
 }
@@ -16,9 +16,8 @@ void VEditArea::setupUI()
 {
     splitter = new QSplitter(this);
 
-    // Add a window
     insertSplitWindow(0);
-    setCurrentWindow(0, true);
+    setCurrentWindow(0, false);
 
     QHBoxLayout *mainLayout = new QHBoxLayout();
     mainLayout->addWidget(splitter);
@@ -43,17 +42,6 @@ void VEditArea::insertSplitWindow(int idx)
             this, &VEditArea::handleOutlineChanged);
     connect(win, &VEditWindow::curHeaderChanged,
             this, &VEditArea::handleCurHeaderChanged);
-
-    int nrWin = splitter->count();
-    if (nrWin == 1) {
-        // Disable removing split
-        win->setRemoveSplitEnable(false);
-    } else {
-        // Enable removing split
-        for (int i = 0; i < nrWin; ++i) {
-            getWindow(i)->setRemoveSplitEnable(true);
-        }
-    }
 }
 
 void VEditArea::removeSplitWindow(VEditWindow *win)
@@ -61,21 +49,12 @@ void VEditArea::removeSplitWindow(VEditWindow *win)
     if (!win) {
         return;
     }
+
     win->hide();
+    win->setParent(this);
+    disconnect(win, 0, this, 0);
     // Should be deleted later
     win->deleteLater();
-
-    int nrWin = splitter->count();
-    if (nrWin == 2) {
-        // Disable removing split
-        getWindow(0)->setRemoveSplitEnable(false);
-        getWindow(1)->setRemoveSplitEnable(false);
-    } else {
-        // Enable removing split
-        for (int i = 0; i < nrWin; ++i) {
-            getWindow(i)->setRemoveSplitEnable(true);
-        }
-    }
 }
 
 // A given file can be opened in multiple split windows. A given file could be
@@ -107,6 +86,11 @@ void VEditArea::openFile(VFile *p_file, OpenFileMode p_mode)
     }
 
     // Open it in current window
+    if (curWindowIndex == -1) {
+        // insert a new split
+        insertSplitWindow(0);
+        curWindowIndex = 0;
+    }
     winIdx = curWindowIndex;
     tabIdx = openFileInWindow(winIdx, p_file, p_mode);
 
@@ -151,9 +135,8 @@ void VEditArea::setCurrentWindow(int windowIndex, bool setFocus)
     if (curWindowIndex == windowIndex) {
         goto out;
     }
-    qDebug() << "current window" << windowIndex;
     curWindowIndex = windowIndex;
-    if (setFocus) {
+    if (curWindowIndex > -1 && setFocus) {
         getWindow(windowIndex)->focusWindow();
     }
 
@@ -164,6 +147,13 @@ out:
 
 void VEditArea::updateWindowStatus()
 {
+    if (curWindowIndex == -1) {
+        Q_ASSERT(splitter->count() == 0);
+        emit curTabStatusChanged(NULL, false);
+        emit outlineChanged(VToc());
+        emit curHeaderChanged(VAnchor());
+        return;
+    }
     VEditWindow *win = getWindow(curWindowIndex);
     win->requestUpdateTabStatus();
     win->requestUpdateOutline();
@@ -175,25 +165,83 @@ bool VEditArea::closeFile(const VFile *p_file, bool p_forced)
     if (!p_file) {
         return true;
     }
-    int nrWin = splitter->count();
     bool ret = false;
-    for (int i = 0; i < nrWin; ++i) {
+    int i = 0;
+    while (i < splitter->count()) {
         VEditWindow *win = getWindow(i);
+        int nrWin = splitter->count();
         ret = ret || win->closeFile(p_file, p_forced);
+        if (nrWin == splitter->count()) {
+            ++i;
+        }
     }
     updateWindowStatus();
     return ret;
 }
 
+bool VEditArea::closeFile(const VDirectory *p_dir, bool p_forced)
+{
+    if (!p_dir) {
+        return true;
+    }
+    int i = 0;
+    while (i < splitter->count()) {
+        VEditWindow *win = getWindow(i);
+        if (!p_forced) {
+            setCurrentWindow(i, false);
+        }
+        int nrWin = splitter->count();
+        if (!win->closeFile(p_dir, p_forced)) {
+            return false;
+        }
+        // win may be removed after closeFile()
+        if (nrWin == splitter->count()) {
+            ++i;
+        }
+    }
+    updateWindowStatus();
+    return true;
+}
+
+bool VEditArea::closeFile(const VNotebook *p_notebook, bool p_forced)
+{
+    if (!p_notebook) {
+        return true;
+    }
+    int i = 0;
+    while (i < splitter->count()) {
+        VEditWindow *win = getWindow(i);
+        if (!p_forced) {
+            setCurrentWindow(i, false);
+        }
+        int nrWin = splitter->count();
+        if (!win->closeFile(p_notebook, p_forced)) {
+            return false;
+        }
+        // win may be removed after closeFile()
+        if (nrWin == splitter->count()) {
+            ++i;
+        }
+    }
+    updateWindowStatus();
+    return true;
+}
+
 bool VEditArea::closeAllFiles(bool p_forced)
 {
-    int nrWin = splitter->count();
-    for (int i = 0; i < nrWin; ++i) {
+    int i = 0;
+    while (i < splitter->count()) {
         VEditWindow *win = getWindow(i);
-        setCurrentWindow(i, false);
+        if (!p_forced) {
+            setCurrentWindow(i, false);
+        }
+        int nrWin = splitter->count();
         if (!win->closeAllFiles(p_forced)) {
             return false;
         }
+        if (nrWin == splitter->count()) {
+            ++i;
+        }
     }
     updateWindowStatus();
     return true;
@@ -241,14 +289,12 @@ void VEditArea::handleRemoveSplitRequest(VEditWindow *curWindow)
     }
     int idx = splitter->indexOf(curWindow);
 
+    // curWindow will be deleted later
     removeSplitWindow(curWindow);
 
     if (idx >= splitter->count()) {
         idx = splitter->count() - 1;
     }
-
-    // At least one split window left
-    Q_ASSERT(idx >= 0);
     setCurrentWindow(idx, true);
 }
 
@@ -321,3 +367,11 @@ void VEditArea::handleDirectoryUpdated(const VDirectory *p_dir)
         getWindow(i)->updateDirectoryInfo(p_dir);
     }
 }
+
+void VEditArea::handleNotebookUpdated(const VNotebook *p_notebook)
+{
+    int nrWin = splitter->count();
+    for (int i = 0; i < nrWin; ++i) {
+        getWindow(i)->updateNotebookInfo(p_notebook);
+    }
+}

+ 5 - 1
src/veditarea.h

@@ -25,6 +25,9 @@ public:
     explicit VEditArea(VNote *vnote, QWidget *parent = 0);
     bool isFileOpened(const VFile *p_file);
     bool closeAllFiles(bool p_forced);
+    bool closeFile(const VFile *p_file, bool p_forced);
+    bool closeFile(const VDirectory *p_dir, bool p_forced);
+    bool closeFile(const VNotebook *p_notebook, bool p_forced);
 
 signals:
     void curTabStatusChanged(const VFile *p_file, bool p_editMode);
@@ -36,7 +39,6 @@ protected:
 
 public slots:
     void openFile(VFile *p_file, OpenFileMode p_mode);
-    bool closeFile(const VFile *p_file, bool p_forced);
     void editFile();
     void saveFile();
     void readFile();
@@ -44,6 +46,7 @@ public slots:
     void handleOutlineItemActivated(const VAnchor &anchor);
     void handleFileUpdated(const VFile *p_file);
     void handleDirectoryUpdated(const VDirectory *p_dir);
+    void handleNotebookUpdated(const VNotebook *p_notebook);
 
 private slots:
     void handleSplitWindowRequest(VEditWindow *curWindow);
@@ -72,6 +75,7 @@ private:
 
 inline VEditWindow* VEditArea::getWindow(int windowIndex) const
 {
+    Q_ASSERT(windowIndex < splitter->count());
     return dynamic_cast<VEditWindow *>(splitter->widget(windowIndex));
 }
 

+ 2 - 1
src/vedittab.cpp

@@ -22,7 +22,8 @@ VEditTab::VEditTab(VFile *p_file, OpenFileMode p_mode, QWidget *p_parent)
     : QStackedWidget(p_parent), m_file(p_file), isEditMode(false),
       mdConverterType(vconfig.getMdConverterType())
 {
-    qDebug() << "ready to open" << p_file->getName();
+    tableOfContent.filePath = p_file->retrivePath();
+    curHeader.filePath = p_file->retrivePath();
     Q_ASSERT(!m_file->isOpened());
     m_file->open();
     setupUI();

+ 92 - 25
src/veditwindow.cpp

@@ -50,6 +50,8 @@ void VEditWindow::setupCornerWidget()
     rightMenu->addAction(removeSplitAct);
     rightBtn->setMenu(rightMenu);
     setCornerWidget(rightBtn, Qt::TopRightCorner);
+    connect(rightMenu, &QMenu::aboutToShow,
+            this, &VEditWindow::updateSplitMenu);
 
     // Left corner button
     tabListAct = new QActionGroup(this);
@@ -78,21 +80,13 @@ void VEditWindow::removeSplit()
     closeAllFiles(false);
 }
 
-void VEditWindow::setRemoveSplitEnable(bool enabled)
-{
-    removeSplitAct->setVisible(enabled);
-}
-
 void VEditWindow::removeEditTab(int p_index)
 {
-    if (p_index > -1 && p_index < tabBar()->count()) {
-        VEditTab *editor = getTab(p_index);
-        removeTab(p_index);
-        delete editor;
-        if (tabBar()->count() == 0) {
-            emit requestRemoveSplit(this);
-        }
-    }
+    Q_ASSERT(p_index > -1 && p_index < tabBar()->count());
+
+    VEditTab *editor = getTab(p_index);
+    removeTab(p_index);
+    delete editor;
 }
 
 int VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
@@ -100,7 +94,6 @@ int VEditWindow::insertEditTab(int p_index, VFile *p_file, QWidget *p_page)
     int idx = insertTab(p_index, p_page, p_file->getName());
     QTabBar *tabs = tabBar();
     tabs->setTabToolTip(idx, generateTooltip(p_file));
-    noticeStatus(currentIndex());
     return idx;
 }
 
@@ -121,7 +114,6 @@ int VEditWindow::openFile(VFile *p_file, OpenFileMode p_mode)
 out:
     setCurrentIndex(idx);
     focusWindow();
-    noticeStatus(idx);
     return idx;
 }
 
@@ -145,9 +137,50 @@ bool VEditWindow::closeFile(const VFile *p_file, bool p_forced)
     if (ok) {
         removeEditTab(idx);
     }
+    if (count() == 0) {
+        emit requestRemoveSplit(this);
+    }
     return ok;
 }
 
+bool VEditWindow::closeFile(const VDirectory *p_dir, bool p_forced)
+{
+    Q_ASSERT(p_dir);
+    int i = 0;
+    while (i < count()) {
+        VEditTab *editor = getTab(i);
+        QPointer<VFile> file = editor->getFile();
+        if (p_dir->containsFile(file)) {
+            if (!closeFile(file, p_forced)) {
+                return false;
+            }
+            // Closed a file, so don't need to add i.
+        } else {
+            ++i;
+        }
+    }
+    return true;
+}
+
+bool VEditWindow::closeFile(const VNotebook *p_notebook, bool p_forced)
+{
+    Q_ASSERT(p_notebook);
+    int i = 0;
+    while (i < count()) {
+        VEditTab *editor = getTab(i);
+        QPointer<VFile> file = editor->getFile();
+        if (p_notebook->containsFile(file)) {
+            if (!closeFile(file, p_forced)) {
+                return false;
+            }
+            // Closed a file, so don't need to add i.
+        } else {
+            ++i;
+        }
+    }
+    return true;
+}
+
 bool VEditWindow::closeAllFiles(bool p_forced)
 {
     int nrTab = count();
@@ -168,7 +201,7 @@ bool VEditWindow::closeAllFiles(bool p_forced)
             break;
         }
     }
-    if (ret) {
+    if (count() == 0) {
         emit requestRemoveSplit(this);
     }
     return ret;
@@ -192,7 +225,7 @@ int VEditWindow::openFileInTab(VFile *p_file, OpenFileMode p_mode)
 
 int VEditWindow::findTabByFile(const VFile *p_file) const
 {
-    int nrTabs = tabBar()->count();
+    int nrTabs = count();
     for (int i = 0; i < nrTabs; ++i) {
         if (getTab(i)->getFile() == p_file) {
             return i;
@@ -209,10 +242,13 @@ bool VEditWindow::handleTabCloseRequest(int index)
     if (ok) {
         removeEditTab(index);
     }
-    noticeStatus(currentIndex());
+
     // User clicks the close button. We should make this window
     // to be current window.
     emit getFocused();
+    if (count() == 0) {
+        emit requestRemoveSplit(this);
+    }
     return ok;
 }
 
@@ -221,14 +257,12 @@ void VEditWindow::readFile()
     VEditTab *editor = getTab(currentIndex());
     Q_ASSERT(editor);
     editor->readFile();
-    noticeStatus(currentIndex());
 }
 
 void VEditWindow::saveAndReadFile()
 {
     saveFile();
     readFile();
-    noticeStatus(currentIndex());
 }
 
 void VEditWindow::editFile()
@@ -236,7 +270,6 @@ void VEditWindow::editFile()
     VEditTab *editor = getTab(currentIndex());
     Q_ASSERT(editor);
     editor->editFile();
-    noticeStatus(currentIndex());
 }
 
 void VEditWindow::saveFile()
@@ -307,7 +340,6 @@ void VEditWindow::handleTabbarClicked(int index)
 {
     // The child will emit getFocused here
     focusWindow();
-    noticeStatus(index);
 }
 
 void VEditWindow::mousePressEvent(QMouseEvent *event)
@@ -320,8 +352,10 @@ void VEditWindow::contextMenuRequested(QPoint pos)
 {
     QMenu menu(this);
 
-    menu.addAction(removeSplitAct);
-    menu.exec(this->mapToGlobal(pos));
+    if (canRemoveSplit()) {
+        menu.addAction(removeSplitAct);
+        menu.exec(this->mapToGlobal(pos));
+    }
 }
 
 void VEditWindow::tabListJump(QAction *action)
@@ -362,6 +396,22 @@ void VEditWindow::updateTabListMenu()
     }
 }
 
+void VEditWindow::updateSplitMenu()
+{
+    if (canRemoveSplit()) {
+        removeSplitAct->setVisible(true);
+    } else {
+        removeSplitAct->setVisible(false);
+    }
+}
+
+bool VEditWindow::canRemoveSplit()
+{
+    QSplitter *splitter = dynamic_cast<QSplitter *>(parent());
+    Q_ASSERT(splitter);
+    return splitter->count() > 1;
+}
+
 void VEditWindow::handleOutlineChanged(const VToc &p_toc)
 {
     // Only propagate it if it is current tab
@@ -406,6 +456,7 @@ void VEditWindow::scrollCurTab(const VAnchor &p_anchor)
 // Update tab status, outline and current header.
 void VEditWindow::noticeStatus(int index)
 {
+    qDebug() << "tab" << index;
     noticeTabStatus(index);
 
     if (index == -1) {
@@ -440,7 +491,7 @@ void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
         return;
     }
 
-    int nrTab = tabBar()->count();
+    int nrTab = count();
     for (int i = 0; i < nrTab; ++i) {
         VEditTab *editor = getTab(i);
         QPointer<VFile> file = editor->getFile();
@@ -449,3 +500,19 @@ void VEditWindow::updateDirectoryInfo(const VDirectory *p_dir)
         }
     }
 }
+
+void VEditWindow::updateNotebookInfo(const VNotebook *p_notebook)
+{
+    if (!p_notebook) {
+        return;
+    }
+
+    int nrTab = count();
+    for (int i = 0; i < nrTab; ++i) {
+        VEditTab *editor = getTab(i);
+        QPointer<VFile> file = editor->getFile();
+        if (p_notebook->containsFile(file)) {
+            noticeStatus(i);
+        }
+    }
+}

+ 5 - 1
src/veditwindow.h

@@ -23,12 +23,13 @@ public:
     int findTabByFile(const VFile *p_file) const;
     int openFile(VFile *p_file, OpenFileMode p_mode);
     bool closeFile(const VFile *p_file, bool p_forced);
+    bool closeFile(const VDirectory *p_dir, bool p_forced);
+    bool closeFile(const VNotebook *p_notebook, bool p_forced);
     void editFile();
     void saveFile();
     void readFile();
     void saveAndReadFile();
     bool closeAllFiles(bool p_forced);
-    void setRemoveSplitEnable(bool enabled);
     void requestUpdateTabStatus();
     void requestUpdateOutline();
     void requestUpdateCurHeader();
@@ -37,6 +38,7 @@ public:
     void scrollCurTab(const VAnchor &p_anchor);
     void updateFileInfo(const VFile *p_file);
     void updateDirectoryInfo(const VDirectory *p_dir);
+    void updateNotebookInfo(const VNotebook *p_notebook);
 
 protected:
     void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
@@ -61,6 +63,7 @@ private slots:
     void handleCurHeaderChanged(const VAnchor &p_anchor);
     void handleTabStatusChanged();
     void updateTabListMenu();
+    void updateSplitMenu();
 
 private:
     void setupCornerWidget();
@@ -73,6 +76,7 @@ private:
     void noticeStatus(int index);
     inline QString generateTooltip(const VFile *p_file) const;
     inline QString generateTabText(const QString &p_name, bool p_modified) const;
+    bool canRemoveSplit();
 
     VNote *vnote;
     // Button in the right corner

+ 31 - 182
src/vmainwindow.cpp

@@ -5,16 +5,15 @@
 #include "vnote.h"
 #include "vfilelist.h"
 #include "vconfigmanager.h"
-#include "dialog/vnewnotebookdialog.h"
-#include "dialog/vnotebookinfodialog.h"
 #include "utils/vutils.h"
 #include "veditarea.h"
 #include "voutline.h"
+#include "vnotebookselector.h"
 
 extern VConfigManager vconfig;
 
 VMainWindow::VMainWindow(QWidget *parent)
-    : QMainWindow(parent), notebookComboMuted(false)
+    : QMainWindow(parent)
 {
     setWindowIcon(QIcon(":/resources/icons/vnote.ico"));
     // Must be called before those who uses VConfigManager
@@ -28,7 +27,7 @@ VMainWindow::VMainWindow(QWidget *parent)
     initDockWindows();
     restoreStateAndGeometry();
 
-    updateNotebookComboBox(vnote->getNotebooks());
+    notebookSelector->update();
 }
 
 void VMainWindow::setupUI()
@@ -41,6 +40,8 @@ void VMainWindow::setupUI()
     editArea = new VEditArea(vnote);
     editArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
     fileList->setEditArea(editArea);
+    directoryTree->setEditArea(editArea);
+    notebookSelector->setEditArea(editArea);
 
     // Main Splitter
     mainSplitter = new QSplitter();
@@ -52,15 +53,13 @@ void VMainWindow::setupUI()
     mainSplitter->setStretchFactor(2, 1);
 
     // Signals
-    connect(notebookComboBox, SIGNAL(currentIndexChanged(int)), this,
-            SLOT(setCurNotebookIndex(int)));
-
     connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
             fileList, &VFileList::setDirectory);
     connect(directoryTree, &VDirectoryTree::directoryUpdated,
             editArea, &VEditArea::handleDirectoryUpdated);
-    connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
-            this, &VMainWindow::handleCurrentDirectoryChanged);
+
+    connect(notebookSelector, &VNotebookSelector::notebookUpdated,
+            editArea, &VEditArea::handleNotebookUpdated);
 
     connect(fileList, &VFileList::fileClicked,
             editArea, &VEditArea::openFile);
@@ -71,23 +70,6 @@ void VMainWindow::setupUI()
     connect(editArea, &VEditArea::curTabStatusChanged,
             this, &VMainWindow::handleCurTabStatusChanged);
 
-    connect(newNotebookBtn, &QPushButton::clicked,
-            this, &VMainWindow::onNewNotebookBtnClicked);
-    connect(deleteNotebookBtn, &QPushButton::clicked,
-            this, &VMainWindow::onDeleteNotebookBtnClicked);
-    connect(notebookInfoBtn, &QPushButton::clicked,
-            this, &VMainWindow::onNotebookInfoBtnClicked);
-
-    connect(vnote, &VNote::notebookDeleted,
-            this, &VMainWindow::notebookComboBoxDeleted);
-    connect(vnote, &VNote::notebookRenamed,
-            this, &VMainWindow::notebookComboBoxRenamed);
-    connect(vnote, &VNote::notebookAdded,
-            this, &VMainWindow::notebookComboBoxAdded);
-
-    connect(this, &VMainWindow::curNotebookChanged,
-            directoryTree, &VDirectoryTree::setNotebook);
-
     setCentralWidget(mainSplitter);
     // Create and show the status bar
     statusBar();
@@ -108,9 +90,9 @@ QWidget *VMainWindow::setupDirectoryPanel()
     notebookInfoBtn->setToolTip(tr("View and edit current notebook's information"));
     notebookInfoBtn->setProperty("OnMainWindow", true);
 
-    notebookComboBox = new QComboBox();
-    notebookComboBox->setProperty("OnMainWindow", true);
-    notebookComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+    notebookSelector = new VNotebookSelector(vnote);
+    notebookSelector->setProperty("OnMainWindow", true);
+    notebookSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
     directoryTree = new VDirectoryTree(vnote);
 
     QHBoxLayout *nbBtnLayout = new QHBoxLayout;
@@ -122,7 +104,7 @@ QWidget *VMainWindow::setupDirectoryPanel()
     nbBtnLayout->setContentsMargins(0, 0, 0, 0);
     QVBoxLayout *nbLayout = new QVBoxLayout;
     nbLayout->addLayout(nbBtnLayout);
-    nbLayout->addWidget(notebookComboBox);
+    nbLayout->addWidget(notebookSelector);
     nbLayout->addWidget(directoryLabel);
     nbLayout->addWidget(directoryTree);
     nbLayout->setContentsMargins(5, 0, 0, 0);
@@ -130,6 +112,20 @@ QWidget *VMainWindow::setupDirectoryPanel()
     nbContainer->setLayout(nbLayout);
     nbContainer->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
 
+    connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
+            directoryTree, &VDirectoryTree::setNotebook);
+    connect(notebookSelector, &VNotebookSelector::curNotebookChanged,
+            this, &VMainWindow::handleCurrentNotebookChanged);
+
+    connect(newNotebookBtn, &QPushButton::clicked,
+            notebookSelector, &VNotebookSelector::newNotebook);
+    connect(deleteNotebookBtn, SIGNAL(clicked(bool)),
+            notebookSelector, SLOT(deleteNotebook()));
+    connect(notebookInfoBtn, SIGNAL(clicked(bool)),
+            notebookSelector, SLOT(editNotebookInfo()));
+
+    connect(directoryTree, &VDirectoryTree::currentDirectoryChanged,
+            this, &VMainWindow::handleCurrentDirectoryChanged);
     return nbContainer;
 }
 
@@ -366,158 +362,6 @@ void VMainWindow::initDockWindows()
     viewMenu->addAction(toolDock->toggleViewAction());
 }
 
-void VMainWindow::updateNotebookComboBox(const QVector<VNotebook *> &p_notebooks)
-{
-    notebookComboMuted = true;
-    notebookComboBox->clear();
-    for (int i = 0; i < p_notebooks.size(); ++i) {
-        notebookComboBox->addItem(QIcon(":/resources/icons/notebook_item.svg"),
-                                  p_notebooks[i]->getName());
-    }
-    notebookComboMuted = false;
-
-    int index = vconfig.getCurNotebookIndex();
-    if (p_notebooks.isEmpty()) {
-        index = -1;
-    }
-    if (notebookComboBox->currentIndex() == index) {
-        setCurNotebookIndex(index);
-    } else {
-        notebookComboBox->setCurrentIndex(index);
-    }
-}
-
-void VMainWindow::notebookComboBoxAdded(const VNotebook *p_notebook, int p_idx)
-{
-    notebookComboMuted = true;
-    notebookComboBox->insertItem(p_idx, QIcon(":/resources/icons/notebook_item.svg"),
-                                 p_notebook->getName());
-    notebookComboMuted = false;
-    if (notebookComboBox->currentIndex() == vconfig.getCurNotebookIndex()) {
-        setCurNotebookIndex(vconfig.getCurNotebookIndex());
-    } else {
-        notebookComboBox->setCurrentIndex(vconfig.getCurNotebookIndex());
-    }
-}
-
-void VMainWindow::notebookComboBoxDeleted(int p_oriIdx)
-{
-    Q_ASSERT(p_oriIdx >= 0 && p_oriIdx < notebookComboBox->count());
-    notebookComboMuted = true;
-    notebookComboBox->removeItem(p_oriIdx);
-    notebookComboMuted = false;
-
-    if (notebookComboBox->currentIndex() == vconfig.getCurNotebookIndex()) {
-        setCurNotebookIndex(vconfig.getCurNotebookIndex());
-    } else {
-        notebookComboBox->setCurrentIndex(vconfig.getCurNotebookIndex());
-    }
-}
-
-void VMainWindow::notebookComboBoxRenamed(const VNotebook *p_notebook, int p_idx)
-{
-    Q_ASSERT(p_idx >= 0 && p_idx < notebookComboBox->count());
-    notebookComboBox->setItemText(p_idx, p_notebook->getName());
-    // Renaming a notebook won't change current index
-}
-
-// Maybe called multiple times
-void VMainWindow::setCurNotebookIndex(int index)
-{
-    if (notebookComboMuted) {
-        return;
-    }
-    Q_ASSERT(index < vnote->getNotebooks().size());
-    // Update directoryTree
-    VNotebook *notebook = NULL;
-    if (index > -1) {
-        vconfig.setCurNotebookIndex(index);
-        notebook = vnote->getNotebooks()[index];
-        newRootDirAct->setEnabled(true);
-    } else {
-        newRootDirAct->setEnabled(false);
-    }
-    emit curNotebookChanged(notebook);
-}
-
-void VMainWindow::onNewNotebookBtnClicked()
-{
-    QString info;
-    QString defaultName("new_notebook");
-    QString defaultPath;
-    do {
-        VNewNotebookDialog dialog(tr("Create a new notebook"), info, defaultName,
-                                  defaultPath, this);
-        if (dialog.exec() == QDialog::Accepted) {
-            QString name = dialog.getNameInput();
-            QString path = dialog.getPathInput();
-            if (isConflictWithExistingNotebooks(name)) {
-                info = "Name already exists.";
-                defaultName = name;
-                defaultPath = path;
-                continue;
-            }
-            vnote->createNotebook(name, path);
-        }
-        break;
-    } while (true);
-}
-
-bool VMainWindow::isConflictWithExistingNotebooks(const QString &name)
-{
-    const QVector<VNotebook *> &notebooks = vnote->getNotebooks();
-    for (int i = 0; i < notebooks.size(); ++i) {
-        if (notebooks[i]->getName() == name) {
-            return true;
-        }
-    }
-    return false;
-}
-
-void VMainWindow::onDeleteNotebookBtnClicked()
-{
-    int curIndex = notebookComboBox->currentIndex();
-    QString curName = vnote->getNotebooks()[curIndex]->getName();
-    QString curPath = vnote->getNotebooks()[curIndex]->getPath();
-
-    QMessageBox msgBox(QMessageBox::Warning, tr("Warning"), QString("Are you sure you want to delete notebook \"%1\"?")
-                       .arg(curName), QMessageBox::Ok | QMessageBox::Cancel, this);
-    msgBox.setInformativeText(QString("This will delete any files in this notebook (\"%1\").").arg(curPath));
-    msgBox.setDefaultButton(QMessageBox::Cancel);
-    if (msgBox.exec() == QMessageBox::Ok) {
-        vnote->removeNotebook(curIndex);
-    }
-}
-
-void VMainWindow::onNotebookInfoBtnClicked()
-{
-    int curIndex = notebookComboBox->currentIndex();
-    if (curIndex < 0) {
-        return;
-    }
-    QString info;
-    QString curName = vnote->getNotebooks()[curIndex]->getName();
-    QString defaultPath = vnote->getNotebooks()[curIndex]->getPath();
-    QString defaultName(curName);
-    do {
-        VNotebookInfoDialog dialog(tr("Notebook information"), info, defaultName,
-                                  defaultPath, this);
-        if (dialog.exec() == QDialog::Accepted) {
-            QString name = dialog.getNameInput();
-            if (name == curName) {
-                return;
-            }
-            if (isConflictWithExistingNotebooks(name)) {
-                info = "Name already exists.";
-                defaultName = name;
-                continue;
-            }
-            vnote->renameNotebook(curIndex, name);
-        }
-        break;
-    } while (true);
-}
-
 void VMainWindow::importNoteFromFile()
 {
     static QString lastPath = QDir::homePath();
@@ -806,3 +650,8 @@ void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir)
 {
     newNoteAct->setEnabled(p_dir);
 }
+
+void VMainWindow::handleCurrentNotebookChanged(const VNotebook *p_notebook)
+{
+    newRootDirAct->setEnabled(p_notebook);
+}

+ 3 - 16
src/vmainwindow.h

@@ -24,6 +24,7 @@ class VFileList;
 class VEditArea;
 class QToolBox;
 class VOutline;
+class VNotebookSelector;
 
 class VMainWindow : public QMainWindow
 {
@@ -34,15 +35,6 @@ public:
     const QVector<QPair<QString, QString> > &getPalette() const;
 
 private slots:
-    void setCurNotebookIndex(int index);
-    // Create a notebook
-    void onNewNotebookBtnClicked();
-    void onDeleteNotebookBtnClicked();
-    void onNotebookInfoBtnClicked();
-    void updateNotebookComboBox(const QVector<VNotebook *> &p_notebooks);
-    void notebookComboBoxAdded(const VNotebook *p_notebook, int p_idx);
-    void notebookComboBoxDeleted(int p_oriIdx);
-    void notebookComboBoxRenamed(const VNotebook *p_notebook, int p_idx);
     void importNoteFromFile();
     void changeMarkdownConverter(QAction *action);
     void aboutMessage();
@@ -55,9 +47,7 @@ private slots:
     void curEditFileInfo();
     void deleteCurNote();
     void handleCurrentDirectoryChanged(const VDirectory *p_dir);
-
-signals:
-    void curNotebookChanged(VNotebook *p_notebook);
+    void handleCurrentNotebookChanged(const VNotebook *p_notebook);
 
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
@@ -69,7 +59,6 @@ private:
     void initToolBar();
     void initMenuBar();
     void initDockWindows();
-    bool isConflictWithExistingNotebooks(const QString &name);
     void initPredefinedColorPixmaps();
     void initRenderBackgroundMenu(QMenu *menu);
     void initEditorBackgroundMenu(QMenu *menu);
@@ -79,14 +68,12 @@ private:
     void saveStateAndGeometry();
     void restoreStateAndGeometry();
 
-    // If true, comboBox changes will not trigger any signal out
-    bool notebookComboMuted;
     VNote *vnote;
     QPointer<VFile> m_curFile;
 
     QLabel *notebookLabel;
     QLabel *directoryLabel;
-    QComboBox *notebookComboBox;
+    VNotebookSelector *notebookSelector;
     QPushButton *newNotebookBtn;
     QPushButton *deleteNotebookBtn;
     QPushButton *notebookInfoBtn;

+ 3 - 80
src/vnote.cpp

@@ -75,89 +75,12 @@ void VNote::updateTemplate()
     postTemplateHtml = VUtils::readFileFromDisk(vconfig.getPostTemplatePath());
 }
 
-const QVector<VNotebook *> &VNote::getNotebooks()
+const QVector<VNotebook *> &VNote::getNotebooks() const
 {
     return m_notebooks;
 }
 
-void VNote::createNotebook(const QString &name, const QString &path)
+QVector<VNotebook *> &VNote::getNotebooks()
 {
-    // Create a directory config file in @path
-    QJsonObject configJson;
-    configJson["version"] = "1";
-    configJson["sub_directories"] = QJsonArray();
-    configJson["files"] = QJsonArray();
-    if (!vconfig.writeDirectoryConfig(path, configJson)) {
-        return;
-    }
-
-    // Update notebooks settings
-    VNotebook *nb = new VNotebook(name, path, this);
-    m_notebooks.prepend(nb);
-    vconfig.setNotebooks(m_notebooks);
-
-    // Set current index to the new notebook
-    vconfig.setCurNotebookIndex(0);
-
-    emit notebookAdded(nb, 0);
-}
-
-void VNote::removeNotebook(int idx)
-{
-    Q_ASSERT(idx >= 0 && idx < m_notebooks.size());
-    VNotebook *nb = m_notebooks[idx];
-    QString name = nb->getName();
-    QString path = nb->getPath();
-
-    // Update notebook settings
-    int curIndex = vconfig.getCurNotebookIndex();
-    m_notebooks.remove(idx);
-    vconfig.setNotebooks(m_notebooks);
-    if (m_notebooks.isEmpty()) {
-        vconfig.setCurNotebookIndex(-1);
-    } else if (idx == curIndex) {
-        vconfig.setCurNotebookIndex(0);
-    }
-
-    // Close all the directory and files
-    notebookPathHash.remove(name);
-    nb->close();
-    delete nb;
-    qDebug() << "notebook" << name << "deleted";
-
-    // Delete the directory
-    QDir dir(path);
-    if (!dir.removeRecursively()) {
-        qWarning() << "failed to delete" << path << "recursively";
-    }
-    emit notebookDeleted(idx);
-}
-
-void VNote::renameNotebook(int idx, const QString &newName)
-{
-    Q_ASSERT(idx >= 0 && idx < m_notebooks.size());
-    VNotebook *nb = m_notebooks[idx];
-    QString name = nb->getName();
-    notebookPathHash.remove(name);
-    nb->setName(newName);
-    vconfig.setNotebooks(m_notebooks);
-
-    emit notebookRenamed(nb, idx);
-}
-
-QString VNote::getNotebookPath(const QString &name)
-{
-    QString path = notebookPathHash.value(name);
-    if (path.isEmpty()) {
-        for (int i = 0; i < m_notebooks.size(); ++i) {
-            if (m_notebooks[i]->getName() == name) {
-                path = m_notebooks[i]->getPath();
-                break;
-            }
-        }
-        if (!path.isEmpty()) {
-            notebookPathHash.insert(name, path);
-        }
-    }
-    return path;
+    return m_notebooks;
 }

+ 2 - 13
src/vnote.h

@@ -18,7 +18,8 @@ class VNote : public QObject
 public:
     VNote(QObject *parent = 0);
 
-    const QVector<VNotebook *>& getNotebooks();
+    const QVector<VNotebook *> &getNotebooks() const;
+    QVector<VNotebook *> &getNotebooks();
 
     void initTemplate();
 
@@ -29,27 +30,15 @@ public:
     static QString preTemplateHtml;
     static QString postTemplateHtml;
 
-    void createNotebook(const QString &name, const QString &path);
-    void removeNotebook(int idx);
-    void renameNotebook(int idx, const QString &newName);
-
-    QString getNotebookPath(const QString &name);
-
     inline const QVector<QPair<QString, QString> > &getPallete() const;
     void initPalette(QPalette palette);
 
 public slots:
     void updateTemplate();
 
-signals:
-    void notebookAdded(const VNotebook *p_notebook, int p_idx);
-    void notebookDeleted(int p_oriIdx);
-    void notebookRenamed(const VNotebook *p_notebook, int p_idx);
-
 private:
     // Maintain all the notebooks. Other holder should use QPointer.
     QVector<VNotebook *> m_notebooks;
-    QHash<QString, QString> notebookPathHash;
     QVector<QPair<QString, QString> > m_palette;
 };
 

+ 47 - 8
src/vnotebook.cpp

@@ -1,6 +1,10 @@
 #include "vnotebook.h"
+#include <QDir>
+#include <QDebug>
 #include "vdirectory.h"
 #include "utils/vutils.h"
+#include "vconfigmanager.h"
+#include "vfile.h"
 
 VNotebook::VNotebook(const QString &name, const QString &path, QObject *parent)
     : QObject(parent), m_name(name), m_path(path)
@@ -23,22 +27,57 @@ QString VNotebook::getPath() const
     return m_path;
 }
 
-void VNotebook::setName(const QString &name)
+void VNotebook::close()
 {
-    m_name = name;
+    m_rootDir->close();
 }
 
-void VNotebook::setPath(const QString &path)
+bool VNotebook::open()
 {
-    m_path = path;
+    return m_rootDir->open();
 }
 
-void VNotebook::close()
+VNotebook *VNotebook::createNotebook(const QString &p_name, const QString &p_path,
+                                     QObject *p_parent)
 {
-    m_rootDir->close();
+    VNotebook *nb = new VNotebook(p_name, p_path, p_parent);
+    if (!nb) {
+        return nb;
+    }
+
+    // Create directory config in @p_path
+    QJsonObject configJson = VDirectory::createDirectoryJson();
+    if (!VConfigManager::writeDirectoryConfig(p_path, configJson)) {
+        delete nb;
+        return NULL;
+    }
+    return nb;
 }
 
-bool VNotebook::open()
+void VNotebook::deleteNotebook(VNotebook *p_notebook)
 {
-    return m_rootDir->open();
+    if (!p_notebook) {
+        return;
+    }
+    p_notebook->close();
+    delete p_notebook;
+
+    QString path = p_notebook->getPath();
+    QDir dir(path);
+    if (!dir.removeRecursively()) {
+        qWarning() << "failed to delete" << path;
+    }
+}
+
+void VNotebook::rename(const QString &p_name)
+{
+    if (p_name == m_name || p_name.isEmpty()) {
+        return;
+    }
+    m_name = p_name;
+}
+
+bool VNotebook::containsFile(const VFile *p_file) const
+{
+    return m_rootDir->containsFile(p_file);
 }

+ 7 - 2
src/vnotebook.h

@@ -5,6 +5,7 @@
 #include <QString>
 
 class VDirectory;
+class VFile;
 
 class VNotebook : public QObject
 {
@@ -18,12 +19,16 @@ public:
     // Close all the directory and files of this notebook.
     // Please make sure all files belonging to this notebook have been closed in the tab.
     void close();
+    bool containsFile(const VFile *p_file) const;
 
     QString getName() const;
     QString getPath() const;
-    void setName(const QString &name);
-    void setPath(const QString &path);
     inline VDirectory *getRootDir();
+    void rename(const QString &p_name);
+
+    static VNotebook *createNotebook(const QString &p_name, const QString &p_path,
+                                     QObject *p_parent = 0);
+    static void deleteNotebook(VNotebook *p_notebook);
 
 signals:
     void contentChanged();

+ 173 - 0
src/vnotebookselector.cpp

@@ -0,0 +1,173 @@
+#include "vnotebookselector.h"
+#include <QDebug>
+#include <QJsonObject>
+#include "vnotebook.h"
+#include "vconfigmanager.h"
+#include "dialog/vnewnotebookdialog.h"
+#include "dialog/vnotebookinfodialog.h"
+#include "vnotebook.h"
+#include "vdirectory.h"
+#include "utils/vutils.h"
+#include "vnote.h"
+#include "veditarea.h"
+
+extern VConfigManager vconfig;
+
+VNotebookSelector::VNotebookSelector(VNote *vnote, QWidget *p_parent)
+    : QComboBox(p_parent), m_vnote(vnote), m_notebooks(m_vnote->getNotebooks()),
+      m_editArea(NULL)
+{
+    connect(this, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(handleCurIndexChanged(int)));
+}
+
+void VNotebookSelector::updateComboBox()
+{
+    int index = vconfig.getCurNotebookIndex();
+
+    disconnect(this, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(handleCurIndexChanged(int)));
+    clear();
+    for (int i = 0; i < m_notebooks.size(); ++i) {
+        addItem(QIcon(":/resources/icons/notebook_item.svg"), m_notebooks[i]->getName());
+    }
+    setCurrentIndex(-1);
+    connect(this, SIGNAL(currentIndexChanged(int)),
+            this, SLOT(handleCurIndexChanged(int)));
+
+    if (m_notebooks.isEmpty() && index != -1) {
+        index = -1;
+        vconfig.setCurNotebookIndex(index);
+    }
+    setCurrentIndex(index);
+    qDebug() << "notebooks" << m_notebooks.size() << "current index" << index;
+}
+
+void VNotebookSelector::handleCurIndexChanged(int p_index)
+{
+    qDebug() << "current index changed" << p_index;
+    VNotebook *nb = NULL;
+    if (p_index > -1) {
+        nb = m_notebooks[p_index];
+    }
+    vconfig.setCurNotebookIndex(p_index);
+    emit curNotebookChanged(nb);
+}
+
+void VNotebookSelector::update()
+{
+    updateComboBox();
+}
+
+void VNotebookSelector::newNotebook()
+{
+    QString info;
+    QString defaultName("new_notebook");
+    QString defaultPath;
+    do {
+        VNewNotebookDialog dialog(tr("Create notebook"), info, defaultName,
+                                  defaultPath, this);
+        if (dialog.exec() == QDialog::Accepted) {
+            QString name = dialog.getNameInput();
+            QString path = dialog.getPathInput();
+            if (findNotebook(name)) {
+                info = "Name already exists. Please choose another name.";
+                defaultName = name;
+                defaultPath = path;
+                continue;
+            }
+            createNotebook(name, path);
+        }
+        break;
+    } while (true);
+}
+
+VNotebook *VNotebookSelector::findNotebook(const QString &p_name)
+{
+    for (int i = 0; i < m_notebooks.size(); ++i) {
+        if (m_notebooks[i]->getName() == p_name) {
+            return m_notebooks[i];
+        }
+    }
+    return NULL;
+}
+
+void VNotebookSelector::createNotebook(const QString &p_name, const QString &p_path)
+{
+    VNotebook *nb = VNotebook::createNotebook(p_name, p_path, m_vnote);
+    m_notebooks.append(nb);
+    vconfig.setNotebooks(m_notebooks);
+
+    addItem(QIcon(":/resources/icons/notebook_item.svg"), nb->getName());
+    setCurrentIndex(m_notebooks.size() - 1);
+}
+
+void VNotebookSelector::deleteNotebook()
+{
+    int curIndex = currentIndex();
+    VNotebook *notebook = m_notebooks[curIndex];
+    QString curName = notebook->getName();
+    QString curPath = notebook->getPath();
+
+    int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"),
+                                  QString("Are you sure to delete notebook %1?").arg(curName),
+                                  QString("This will delete any files in this notebook (%1).").arg(curPath),
+                                  QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok, this);
+    if (ret == QMessageBox::Ok) {
+        m_editArea->closeFile(notebook, true);
+        deleteNotebook(notebook);
+    }
+}
+
+void VNotebookSelector::deleteNotebook(VNotebook *p_notebook)
+{
+    int idx = indexOfNotebook(p_notebook);
+
+    m_notebooks.remove(idx);
+    vconfig.setNotebooks(m_notebooks);
+
+    removeItem(idx);
+
+    VNotebook::deleteNotebook(p_notebook);
+}
+
+int VNotebookSelector::indexOfNotebook(const VNotebook *p_notebook)
+{
+    for (int i = 0; i < m_notebooks.size(); ++i) {
+        if (m_notebooks[i] == p_notebook) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+void VNotebookSelector::editNotebookInfo()
+{
+    int curIndex = currentIndex();
+    Q_ASSERT(curIndex > -1);
+    VNotebook *notebook = m_notebooks[curIndex];
+    QString info;
+    QString curName = notebook->getName();
+    QString defaultPath = notebook->getPath();
+    QString defaultName(curName);
+    do {
+        VNotebookInfoDialog dialog(tr("Notebook information"), info, defaultName,
+                                   defaultPath, this);
+        if (dialog.exec() == QDialog::Accepted) {
+            QString name = dialog.getNameInput();
+            if (name == curName) {
+                return;
+            }
+            if (findNotebook(name)) {
+                info = "Name already exists. Please choose another name.";
+                defaultName = name;
+                continue;
+            }
+            notebook->rename(name);
+            setItemText(curIndex, name);
+            vconfig.setNotebooks(m_notebooks);
+            emit notebookUpdated(notebook);
+        }
+        break;
+    } while (true);
+}

+ 50 - 0
src/vnotebookselector.h

@@ -0,0 +1,50 @@
+#ifndef VNOTEBOOKSELECTOR_H
+#define VNOTEBOOKSELECTOR_H
+
+#include <QComboBox>
+#include <QVector>
+#include <QString>
+
+class VNotebook;
+class VNote;
+class VEditArea;
+
+class VNotebookSelector : public QComboBox
+{
+    Q_OBJECT
+public:
+    explicit VNotebookSelector(VNote *vnote, QWidget *p_parent = 0);
+    void update();
+    inline void setEditArea(VEditArea *p_editArea);
+
+signals:
+    void curNotebookChanged(VNotebook *p_notebook);
+    // Info of current notebook was changed.
+    void notebookUpdated(const VNotebook *p_notebook);
+
+public slots:
+    void newNotebook();
+    void deleteNotebook();
+    void editNotebookInfo();
+
+private slots:
+    void handleCurIndexChanged(int p_index);
+
+private:
+    void updateComboBox();
+    VNotebook *findNotebook(const QString &p_name);
+    int indexOfNotebook(const VNotebook *p_notebook);
+    void createNotebook(const QString &p_name, const QString &p_path);
+    void deleteNotebook(VNotebook *p_notebook);
+
+    VNote *m_vnote;
+    QVector<VNotebook *> &m_notebooks;
+    VEditArea *m_editArea;
+};
+
+inline void VNotebookSelector::setEditArea(VEditArea *p_editArea)
+{
+    m_editArea = p_editArea;
+}
+
+#endif // VNOTEBOOKSELECTOR_H