Forráskód Böngészése

support editing external files

Le Tan 8 éve
szülő
commit
8a56dc8b87

+ 74 - 2
src/dialog/vsettingsdialog.cpp

@@ -257,6 +257,10 @@ void VReadEditTab::customWebZoomChanged(int p_state)
 VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
     : QWidget(p_parent)
 {
+    m_noteBox = new QGroupBox(tr("Notes"));
+    m_externalBox = new QGroupBox(tr("External Files"));
+
+    // Note.
     // Image folder.
     m_customImageFolder = new QCheckBox(tr("Custom image folder"), this);
     m_customImageFolder->setToolTip(tr("Set the global name of the image folder to store images "
@@ -273,8 +277,35 @@ VNoteManagementTab::VNoteManagementTab(QWidget *p_parent)
     imageFolderLayout->addWidget(m_customImageFolder);
     imageFolderLayout->addWidget(m_imageFolderEdit);
 
-    QFormLayout *mainLayout = new QFormLayout();
-    mainLayout->addRow(imageFolderLayout);
+    QFormLayout *noteLayout = new QFormLayout();
+    noteLayout->addRow(imageFolderLayout);
+    m_noteBox->setLayout(noteLayout);
+
+    // External File.
+    // Image folder.
+    m_customImageFolderExt = new QCheckBox(tr("Custom image folder"), this);
+    m_customImageFolderExt->setToolTip(tr("Set the global name of the image folder to store images "
+                                          "of external files (restart VNote to make it work).\nYou "
+                                          "could use both absolute or relative path here. If you "
+                                          "use an absolute path, VNote will not manage\nthose images, "
+                                          "so you need to clean up unused images manually."));
+    connect(m_customImageFolderExt, &QCheckBox::stateChanged,
+            this, &VNoteManagementTab::customImageFolderExtChanged);
+
+    m_imageFolderEditExt = new QLineEdit(this);
+    m_imageFolderEditExt->setPlaceholderText(tr("Name of the image folder"));
+
+    QHBoxLayout *imageFolderExtLayout = new QHBoxLayout();
+    imageFolderExtLayout->addWidget(m_customImageFolderExt);
+    imageFolderExtLayout->addWidget(m_imageFolderEditExt);
+
+    QFormLayout *externalLayout = new QFormLayout();
+    externalLayout->addRow(imageFolderExtLayout);
+    m_externalBox->setLayout(externalLayout);
+
+    QVBoxLayout *mainLayout = new QVBoxLayout();
+    mainLayout->addWidget(m_noteBox);
+    mainLayout->addWidget(m_externalBox);
 
     setLayout(mainLayout);
 }
@@ -285,6 +316,10 @@ bool VNoteManagementTab::loadConfiguration()
         return false;
     }
 
+    if (!loadImageFolderExt()) {
+        return false;
+    }
+
     return true;
 }
 
@@ -294,6 +329,10 @@ bool VNoteManagementTab::saveConfiguration()
         return false;
     }
 
+    if (!saveImageFolderExt()) {
+        return false;
+    }
+
     return true;
 }
 
@@ -329,3 +368,36 @@ void VNoteManagementTab::customImageFolderChanged(int p_state)
         m_imageFolderEdit->setEnabled(false);
     }
 }
+
+bool VNoteManagementTab::loadImageFolderExt()
+{
+    bool isCustom = vconfig.isCustomImageFolderExt();
+
+    m_customImageFolderExt->setChecked(isCustom);
+    m_imageFolderEditExt->setText(vconfig.getImageFolderExt());
+    m_imageFolderEditExt->setEnabled(isCustom);
+
+    return true;
+}
+
+bool VNoteManagementTab::saveImageFolderExt()
+{
+    if (m_customImageFolderExt->isChecked()) {
+        vconfig.setImageFolderExt(m_imageFolderEditExt->text());
+    } else {
+        vconfig.setImageFolderExt("");
+    }
+
+    return true;
+}
+
+void VNoteManagementTab::customImageFolderExtChanged(int p_state)
+{
+    if (p_state == Qt::Checked) {
+        m_imageFolderEditExt->setEnabled(true);
+        m_imageFolderEditExt->selectAll();
+        m_imageFolderEditExt->setFocus();
+    } else {
+        m_imageFolderEditExt->setEnabled(false);
+    }
+}

+ 11 - 0
src/dialog/vsettingsdialog.h

@@ -62,16 +62,27 @@ public:
     bool loadConfiguration();
     bool saveConfiguration();
 
+    QGroupBox *m_noteBox;
+    QGroupBox *m_externalBox;
+
     // Image folder.
     QCheckBox *m_customImageFolder;
     QLineEdit *m_imageFolderEdit;
 
+    // Image folder of External File.
+    QCheckBox *m_customImageFolderExt;
+    QLineEdit *m_imageFolderEditExt;
+
 private slots:
     void customImageFolderChanged(int p_state);
+    void customImageFolderExtChanged(int p_state);
 
 private:
     bool loadImageFolder();
     bool saveImageFolder();
+
+    bool loadImageFolderExt();
+    bool saveImageFolderExt();
 };
 
 class VSettingsDialog : public QDialog

+ 3 - 0
src/resources/vnote.ini

@@ -44,6 +44,9 @@ enable_image_caption=false
 ; Image folder name for the notes
 image_folder=_v_images
 
+; Image folder name for the external files
+external_image_folder=_v_images
+
 ; Enable trailing space highlight
 enable_trailing_space_highlight=true
 

+ 36 - 0
src/resources/vnote.qss

@@ -228,6 +228,42 @@ QLabel[TitleLabel="true"] {
     background-color: @base-color;
 }
 
+QLabel[ColorRedLabel="true"] {
+    padding-left: 3px;
+    padding-right: 3px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: @Red7;
+}
+
+QLabel[ColorGreenLabel="true"] {
+    padding-left: 3px;
+    padding-right: 3px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: @Green7;
+}
+
+QLabel[ColorGreyLabel="true"] {
+    padding-left: 3px;
+    padding-right: 3px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: @Grey7;
+}
+
+QLabel[ColorTealLabel="true"] {
+    padding-left: 3px;
+    padding-right: 3px;
+    font: bold;
+    color: white;
+    border-radius: 2px;
+    background-color: @Teal7;
+}
+
 QWidget[NotebookPanel="true"] {
     padding-left: 3px;
 }

+ 3 - 0
src/vconfigmanager.cpp

@@ -142,6 +142,9 @@ void VConfigManager::initialize()
     m_imageFolder = getConfigFromSettings("global",
                                           "image_folder").toString();
 
+    m_imageFolderExt = getConfigFromSettings("global",
+                                             "external_image_folder").toString();
+
     m_enableTrailingSpaceHighlight = getConfigFromSettings("global",
                                                            "enable_trailing_space_highlight").toBool();
 

+ 35 - 0
src/vconfigmanager.h

@@ -194,6 +194,11 @@ public:
     void setImageFolder(const QString &p_folder);
     bool isCustomImageFolder() const;
 
+    const QString &getImageFolderExt() const;
+    // Empty string to reset the default folder.
+    void setImageFolderExt(const QString &p_folder);
+    bool isCustomImageFolderExt() const;
+
     bool getEnableTrailingSpaceHighlight() const;
     void setEnableTrailingSapceHighlight(bool p_enabled);
 
@@ -398,6 +403,10 @@ private:
     // Each notebook can specify its custom folder.
     QString m_imageFolder;
 
+    // Global default folder name to store images of all external files.
+    // Each file can specify its custom folder.
+    QString m_imageFolderExt;
+
     // Enable trailing-space highlight.
     bool m_enableTrailingSpaceHighlight;
 
@@ -992,6 +1001,32 @@ inline bool VConfigManager::isCustomImageFolder() const
     return m_imageFolder != getDefaultConfig("global", "image_folder").toString();
 }
 
+inline const QString &VConfigManager::getImageFolderExt() const
+{
+    return m_imageFolderExt;
+}
+
+inline void VConfigManager::setImageFolderExt(const QString &p_folder)
+{
+    if (p_folder.isEmpty()) {
+        // Reset the default folder.
+        m_imageFolderExt = resetDefaultConfig("global", "external_image_folder").toString();
+        return;
+    }
+
+    if (m_imageFolderExt == p_folder) {
+        return;
+    }
+
+    m_imageFolderExt = p_folder;
+    setConfigToSettings("global", "external_image_folder", m_imageFolderExt);
+}
+
+inline bool VConfigManager::isCustomImageFolderExt() const
+{
+    return m_imageFolderExt != getDefaultConfig("global", "external_image_folder").toString();
+}
+
 inline bool VConfigManager::getEnableTrailingSpaceHighlight() const
 {
     return m_enableTrailingSpaceHighlight;

+ 10 - 0
src/vfile.cpp

@@ -275,3 +275,13 @@ bool VFile::rename(const QString &p_name)
 
     return true;
 }
+
+bool VFile::isRelativeImageFolder() const
+{
+    return true;
+}
+
+QString VFile::getImageFolderInLink() const
+{
+    return getNotebook()->getImageFolder();
+}

+ 9 - 0
src/vfile.h

@@ -35,7 +35,10 @@ public:
     virtual QString retrivePath() const;
     virtual QString retriveRelativePath() const;
     virtual QString retriveBasePath() const;
+
+    // The path of the image folder.
     virtual QString retriveImagePath() const;
+
     bool isModified() const;
     bool isModifiable() const;
     bool isOpened() const;
@@ -52,6 +55,12 @@ public:
     // Rename the file.
     virtual bool rename(const QString &p_name);
 
+    // Whether the image folder is a relative path.
+    virtual bool isRelativeImageFolder() const;
+
+    // Return the image folder part in an image link.
+    virtual QString getImageFolderInLink() const;
+
 public slots:
     void setModified(bool p_modified);
 

+ 37 - 2
src/vmainwindow.cpp

@@ -23,6 +23,7 @@
 #include "vvimindicator.h"
 #include "vtabindicator.h"
 #include "dialog/vupdater.h"
+#include "vorphanfile.h"
 
 extern VConfigManager vconfig;
 
@@ -616,10 +617,32 @@ void VMainWindow::initFileMenu()
     QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
     fileMenu->setToolTipsVisible(true);
 
+    // Open external files.
+    QAction *openAct = new QAction(tr("&Open"), this);
+    openAct->setToolTip(tr("Open external file to edit"));
+    connect(openAct, &QAction::triggered,
+            this, [this](){
+                static QString lastPath = QDir::homePath();
+                QStringList files = QFileDialog::getOpenFileNames(this,
+                                                                  tr("Select External Files To Open"),
+                                                                  lastPath);
+
+                if (files.isEmpty()) {
+                    return;
+                }
+
+                // Update lastPath
+                lastPath = QFileInfo(files[0]).path();
+
+                openExternalFiles(files);
+            });
+
+    fileMenu->addAction(openAct);
+
     // Import notes from files.
     m_importNoteAct = newAction(QIcon(":/resources/icons/import_note.svg"),
                                 tr("&Import Notes From Files"), this);
-    m_importNoteAct->setToolTip(tr("Import notes from external files into current folder"));
+    m_importNoteAct->setToolTip(tr("Import notes from external files into current folder by copy"));
     connect(m_importNoteAct, &QAction::triggered,
             this, &VMainWindow::importNoteFromFile);
     m_importNoteAct->setEnabled(false);
@@ -952,6 +975,7 @@ void VMainWindow::importNoteFromFile()
     if (files.isEmpty()) {
         return;
     }
+
     // Update lastPath
     lastPath = QFileInfo(files[0]).path();
 
@@ -1695,7 +1719,9 @@ void VMainWindow::shortcutHelp()
     if (locale == "zh_CN") {
         docName = VNote::c_shortcutsDocFile_zh;
     }
-    VFile *file = vnote->getOrphanFile(docName);
+
+    VFile *file = vnote->getOrphanFile(docName, false);
+    (dynamic_cast<VOrphanFile *>(file))->setNotebookName(tr("[Help]"));
     editArea->openFile(file, OpenFileMode::Read);
 }
 
@@ -1780,3 +1806,12 @@ void VMainWindow::handleVimStatusUpdated(const VVim *p_vim)
         m_vimIndicator->show();
     }
 }
+
+void VMainWindow::openExternalFiles(const QStringList &p_files)
+{
+    qDebug() << "open external files" << p_files;
+    for (int i = 0; i < p_files.size(); ++i) {
+        VFile *file = vnote->getOrphanFile(p_files[i], true);
+        editArea->openFile(file, OpenFileMode::Read);
+    }
+}

+ 3 - 0
src/vmainwindow.h

@@ -149,6 +149,9 @@ private:
                        const QString &p_text,
                        QObject *p_parent = nullptr);
 
+    // Open external files @p_files as orphan files.
+    void openExternalFiles(const QStringList &p_files);
+
     VNote *vnote;
     QPointer<VFile> m_curFile;
     QPointer<VEditTab> m_curTab;

+ 8 - 2
src/vmdedit.cpp

@@ -197,7 +197,11 @@ void VMdEdit::imageInserted(const QString &p_path)
 {
     ImageLink link;
     link.m_path = p_path;
-    link.m_type = ImageLink::LocalRelativeInternal;
+    if (m_file->isRelativeImageFolder()) {
+        link.m_type = ImageLink::LocalRelativeInternal;
+    } else {
+        link.m_type = ImageLink::LocalAbsolute;
+    }
 
     m_insertedImages.append(link);
 }
@@ -217,7 +221,9 @@ void VMdEdit::clearUnusedImages()
         for (int i = 0; i < m_insertedImages.size(); ++i) {
             const ImageLink &link = m_insertedImages[i];
 
-            V_ASSERT(link.m_type == ImageLink::LocalRelativeInternal);
+            if (link.m_type != ImageLink::LocalRelativeInternal) {
+                continue;
+            }
 
             int j;
             for (j = 0; j < images.size(); ++j) {

+ 15 - 7
src/vmdeditoperations.cpp

@@ -44,13 +44,16 @@ bool VMdEditOperations::insertImageFromMimeData(const QMimeData *source)
     dialog.setBrowseable(false);
     dialog.setImage(image);
     if (dialog.exec() == QDialog::Accepted) {
-        insertImageFromQImage(dialog.getImageTitleInput(), m_file->retriveImagePath(), image);
+        insertImageFromQImage(dialog.getImageTitleInput(),
+                              m_file->retriveImagePath(),
+                              m_file->getImageFolderInLink(),
+                              image);
     }
     return true;
 }
 
 void VMdEditOperations::insertImageFromQImage(const QString &title, const QString &path,
-                                              const QImage &image)
+                                              const QString &folderInLink, const QImage &image)
 {
     QString fileName = VUtils::generateImageFileName(path, title);
     QString filePath = QDir(path).filePath(fileName);
@@ -79,7 +82,7 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
         return;
     }
 
-    QString md = QString("![%1](%2/%3)").arg(title).arg(VUtils::directoryNameFromPath(path)).arg(fileName);
+    QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
     insertTextAtCurPos(md);
 
     qDebug() << "insert image" << title << filePath;
@@ -89,8 +92,8 @@ void VMdEditOperations::insertImageFromQImage(const QString &title, const QStrin
     mdEditor->imageInserted(filePath);
 }
 
-void VMdEditOperations::insertImageFromPath(const QString &title,
-                                            const QString &path, const QString &oriImagePath)
+void VMdEditOperations::insertImageFromPath(const QString &title, const QString &path,
+                                            const QString &folderInLink, const QString &oriImagePath)
 {
     QString fileName = VUtils::generateImageFileName(path, title, QFileInfo(oriImagePath).suffix());
     QString filePath = QDir(path).filePath(fileName);
@@ -119,7 +122,7 @@ void VMdEditOperations::insertImageFromPath(const QString &title,
         return;
     }
 
-    QString md = QString("![%1](%2/%3)").arg(title).arg(VUtils::directoryNameFromPath(path)).arg(fileName);
+    QString md = QString("![%1](%2/%3)").arg(title).arg(folderInLink).arg(fileName);
     insertTextAtCurPos(md);
 
     qDebug() << "insert image" << title << filePath;
@@ -168,10 +171,12 @@ bool VMdEditOperations::insertImageFromURL(const QUrl &imageUrl)
         if (isLocal) {
             insertImageFromPath(dialog.getImageTitleInput(),
                                 m_file->retriveImagePath(),
+                                m_file->getImageFolderInLink(),
                                 imagePath);
         } else {
             insertImageFromQImage(dialog.getImageTitleInput(),
                                   m_file->retriveImagePath(),
+                                  m_file->getImageFolderInLink(),
                                   dialog.getImage());
         }
     }
@@ -186,7 +191,10 @@ bool VMdEditOperations::insertImage()
         QString title = dialog.getImageTitleInput();
         QString imagePath = dialog.getPathInput();
         qDebug() << "insert image from" << imagePath << "as" << title;
-        insertImageFromPath(title, m_file->retriveImagePath(), imagePath);
+        insertImageFromPath(title,
+                            m_file->retriveImagePath(),
+                            m_file->getImageFolderInLink(),
+                            imagePath);
     }
     return true;
 }

+ 7 - 2
src/vmdeditoperations.h

@@ -26,12 +26,17 @@ public:
     void decorateText(TextDecoration p_decoration) Q_DECL_OVERRIDE;
 
 private:
-    void insertImageFromPath(const QString &title, const QString &path, const QString &oriImagePath);
+    // Insert image from @oriImagePath as @path.
+    // @folderInLink: the folder part in the image link.
+    void insertImageFromPath(const QString &title, const QString &path,
+                             const QString &folderInLink, const QString &oriImagePath);
 
     // @title: title of the inserted image;
     // @path: the image folder path to insert the image in;
+    // @folderInLink: the folder part in the image link.
     // @image: the image to be inserted;
-    void insertImageFromQImage(const QString &title, const QString &path, const QImage &image);
+    void insertImageFromQImage(const QString &title, const QString &path,
+                               const QString &folderInLink, const QImage &image);
 
     // Key press handlers.
     bool handleKeyTab(QKeyEvent *p_event);

+ 18 - 5
src/vnote.cpp

@@ -77,6 +77,10 @@ void VNote::initPalette(QPalette palette)
     m_palette.append(QPair<QString, QString>("Teal3", "#4DB6AC"));
     m_palette.append(QPair<QString, QString>("Teal4", "#26A69A"));
     m_palette.append(QPair<QString, QString>("Teal5", "#009688"));
+    m_palette.append(QPair<QString, QString>("Teal6", "#00897B"));
+    m_palette.append(QPair<QString, QString>("Teal7", "#00796B"));
+    m_palette.append(QPair<QString, QString>("Teal8", "#00695C"));
+    m_palette.append(QPair<QString, QString>("Teal9", "#004D40"));
 
     m_palette.append(QPair<QString, QString>("Indigo0", "#E8EAF6"));
     m_palette.append(QPair<QString, QString>("Indigo1", "#C5CAE9"));
@@ -272,7 +276,7 @@ const QString &VNote::getMonospacedFont() const
     return font;
 }
 
-VFile *VNote::getOrphanFile(const QString &p_path)
+VFile *VNote::getOrphanFile(const QString &p_path, bool p_modifiable)
 {
     if (p_path.isEmpty()) {
         return NULL;
@@ -280,16 +284,25 @@ VFile *VNote::getOrphanFile(const QString &p_path)
 
     // See if the file has already been opened before.
     for (auto const &file : m_externalFiles) {
-        if (file->getType() == FileType::Orphan && file->retrivePath() == p_path) {
-            qDebug() << "find a VFile for path" << p_path;
+        Q_ASSERT(file->getType() == FileType::Orphan);
+        if (file->retrivePath() == p_path
+            && file->isModifiable() == p_modifiable) {
             return file;
         }
     }
 
-    // TODO: Clean up unopened file here.
+    for (int i = 0; i < m_externalFiles.size(); ++i) {
+        VFile *file = m_externalFiles[i];
+        if (!file->isOpened()) {
+            qDebug() << "release orphan file" << file;
+            m_externalFiles.removeAt(i);
+            delete file;
+            --i;
+        }
+    }
 
     // Create a VOrphanFile for p_path.
-    VOrphanFile *file = new VOrphanFile(p_path, this);
+    VOrphanFile *file = new VOrphanFile(p_path, this, p_modifiable);
     m_externalFiles.append(file);
     return file;
 }

+ 1 - 1
src/vnote.h

@@ -75,7 +75,7 @@ public:
     QString getNavigationLabelStyle(const QString &p_str) const;
 
     // Given the path of an external file, create a VFile struct.
-    VFile *getOrphanFile(const QString &p_path);
+    VFile *getOrphanFile(const QString &p_path, bool p_modifiable);
 
 public slots:
     void updateTemplate();

+ 11 - 5
src/vopenedlistmenu.cpp

@@ -80,16 +80,22 @@ void VOpenedListMenu::updateOpenedList()
 
         // Whether add separator.
         QString curNotebook = file->getNotebookName();
-        if (curNotebook != notebook || file->getDirectory() != directory) {
+        if (curNotebook != notebook
+            || file->getDirectory() != directory) {
             notebook = curNotebook;
             directory = file->getDirectory();
             QString dirName;
-            if (!directory) {
-                dirName = file->retriveBasePath();
-            } else {
+            if (directory) {
                 dirName = directory->getName();
             }
-            QString text = QString("[%1] %2").arg(notebook).arg(dirName);
+
+            QString text;
+            if (dirName.isEmpty()) {
+                text = QString("[%1]").arg(notebook);
+            } else {
+                text = QString("[%1] %2").arg(notebook).arg(dirName);
+            }
+
             QAction *sepAct = addSection(text);
             sepAct->setFont(sepFont);
         }

+ 58 - 12
src/vorphanfile.cpp

@@ -4,12 +4,15 @@
 #include <QFileInfo>
 #include <QDir>
 #include "utils/vutils.h"
+#include "vconfigmanager.h"
 
-VOrphanFile::VOrphanFile(const QString &p_path, QObject *p_parent)
-    : VFile(VUtils::fileNameFromPath(p_path), p_parent, FileType::Orphan, false),
-      m_path(p_path)
+extern VConfigManager vconfig;
+
+VOrphanFile::VOrphanFile(const QString &p_path, QObject *p_parent, bool p_modifiable)
+    : VFile(VUtils::fileNameFromPath(p_path), p_parent, FileType::Orphan, p_modifiable),
+      m_path(p_path), m_notebookName("[EXTERNAL]")
 {
-    qDebug() << "VOrphanFile" << p_path << m_name;
+    qDebug() << "VOrphanFile" << p_path << m_name << p_modifiable;
 }
 
 bool VOrphanFile::open()
@@ -18,8 +21,10 @@ bool VOrphanFile::open()
     if (m_opened) {
         return true;
     }
+
     Q_ASSERT(m_content.isEmpty());
     Q_ASSERT(QFileInfo::exists(m_path));
+
     m_content = VUtils::readFileFromDisk(m_path);
     m_modified = false;
     m_opened = true;
@@ -43,14 +48,24 @@ QString VOrphanFile::retriveBasePath() const
 
 QString VOrphanFile::retriveImagePath() const
 {
-    V_ASSERT(false);
-    return "";
+    QString folder = m_imageFolder;
+    if (m_imageFolder.isEmpty()) {
+        folder = vconfig.getImageFolderExt();
+    }
+
+    QFileInfo fi(folder);
+    if (fi.isAbsolute()) {
+        return folder;
+    } else {
+        return QDir(retriveBasePath()).filePath(folder);
+    }
 }
 
 bool VOrphanFile::save()
 {
-    V_ASSERT(false);
-    return false;
+    Q_ASSERT(m_opened);
+    Q_ASSERT(m_modifiable);
+    return VUtils::writeFileToDisk(retrivePath(), m_content);
 }
 
 void VOrphanFile::convert(DocType /* p_curType */, DocType /* p_targetType */)
@@ -75,7 +90,12 @@ const VDirectory *VOrphanFile::getDirectory() const
 
 QString VOrphanFile::getNotebookName() const
 {
-    return "[EMPTY]";
+    return m_notebookName;
+}
+
+void VOrphanFile::setNotebookName(const QString &p_notebook)
+{
+    m_notebookName = p_notebook;
 }
 
 VNotebook *VOrphanFile::getNotebook()
@@ -83,15 +103,16 @@ VNotebook *VOrphanFile::getNotebook()
     return NULL;
 }
 
-void VOrphanFile::setContent(const QString & /* p_content */)
+void VOrphanFile::setContent(const QString & p_content)
 {
-    V_ASSERT(false);
+    m_content = p_content;
 }
 
 bool VOrphanFile::isInternalImageFolder(const QString &p_path) const
 {
     return VUtils::equalPath(VUtils::basePathFromPath(p_path),
-                             VUtils::basePathFromPath(m_path));
+                             retriveBasePath())
+           || VUtils::equalPath(p_path, retriveImagePath());
 }
 
 bool VOrphanFile::rename(const QString &p_name)
@@ -106,3 +127,28 @@ bool VOrphanFile::rename(const QString &p_name)
     m_path = dir.filePath(m_name);
     return true;
 }
+
+void VOrphanFile::setImageFolder(const QString &p_path)
+{
+    m_imageFolder = p_path;
+}
+
+bool VOrphanFile::isRelativeImageFolder() const
+{
+    QString folder = m_imageFolder;
+    if (m_imageFolder.isEmpty()) {
+        folder = vconfig.getImageFolderExt();
+    }
+
+    return !QFileInfo(folder).isAbsolute();
+}
+
+QString VOrphanFile::getImageFolderInLink() const
+{
+    QString folder = m_imageFolder;
+    if (m_imageFolder.isEmpty()) {
+        folder = vconfig.getImageFolderExt();
+    }
+
+    return folder;
+}

+ 21 - 2
src/vorphanfile.h

@@ -4,12 +4,12 @@
 #include "vfile.h"
 
 // VOrphanFile is file not belong to any notebooks or directories.
-// Given the path of the file, VNote could load its content as read-only.
 class VOrphanFile : public VFile
 {
     Q_OBJECT
 public:
-    VOrphanFile(const QString &p_path, QObject *p_parent);
+    VOrphanFile(const QString &p_path, QObject *p_parent, bool p_modifiable);
+
     bool open() Q_DECL_OVERRIDE;
     QString retrivePath() const Q_DECL_OVERRIDE;
     QString retriveRelativePath() const Q_DECL_OVERRIDE;
@@ -17,11 +17,22 @@ public:
     VDirectory *getDirectory() Q_DECL_OVERRIDE;
     const VDirectory *getDirectory() const Q_DECL_OVERRIDE;
     QString getNotebookName() const Q_DECL_OVERRIDE;
+
+    void setNotebookName(const QString &p_notebook);
+
     VNotebook *getNotebook() Q_DECL_OVERRIDE;
 
     // Rename file.
     bool rename(const QString &p_name) Q_DECL_OVERRIDE;
 
+    void setImageFolder(const QString &p_path);
+
+    // Whether the image folder is a relative path.
+    bool isRelativeImageFolder() const Q_DECL_OVERRIDE;
+
+    // Return the image folder part in an image link.
+    QString getImageFolderInLink() const Q_DECL_OVERRIDE;
+
 private:
     bool save() Q_DECL_OVERRIDE;
     void convert(DocType p_curType, DocType p_targetType) Q_DECL_OVERRIDE;
@@ -31,6 +42,14 @@ private:
     bool isInternalImageFolder(const QString &p_path) const Q_DECL_OVERRIDE;
 
     QString m_path;
+
+    QString m_notebookName;
+
+    // Image folder path of this file.
+    // It could be an absolute or relative path.
+    // Empty to use the global default config.
+    QString m_imageFolder;
+
     friend class VDirectory;
 };
 

+ 16 - 3
src/vtabindicator.cpp

@@ -14,12 +14,22 @@ VTabIndicator::VTabIndicator(QWidget *p_parent)
 void VTabIndicator::setupUI()
 {
     m_docTypeLabel = new QLabel(this);
-    m_readonlyLabel = new QLabel(tr("<span style=\"font-weight:bold; color:red;\">ReadOnly</span>"),
-                                 this);
+    m_docTypeLabel->setToolTip(tr("The type of the file"));
+    m_docTypeLabel->setProperty("ColorGreyLabel", true);
+
+    m_readonlyLabel = new QLabel(tr("ReadOnly"), this);
+    m_readonlyLabel->setToolTip(tr("This file is read-only"));
+    m_readonlyLabel->setProperty("ColorRedLabel", true);
+
+    m_externalLabel = new QLabel(tr("Standalone"), this);
+    m_externalLabel->setToolTip(tr("This file is not managed by any notebook or folder"));
+    m_externalLabel->setProperty("ColorTealLabel", true);
+
     m_cursorLabel = new QLabel(this);
 
     QHBoxLayout *mainLayout = new QHBoxLayout(this);
     mainLayout->addWidget(m_cursorLabel);
+    mainLayout->addWidget(m_externalLabel);
     mainLayout->addWidget(m_readonlyLabel);
     mainLayout->addWidget(m_docTypeLabel);
     mainLayout->setContentsMargins(0, 0, 0, 0);
@@ -61,7 +71,8 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
     const VEditTab *editTab = NULL;
     const VFile *file = NULL;
     DocType docType = DocType::Html;
-    bool readonly = true;
+    bool readonly = false;
+    bool external = false;
     QString cursorStr;
 
     if (p_info.m_editTab)
@@ -70,6 +81,7 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
         file = editTab->getFile();
         docType = file->getDocType();
         readonly = !file->isModifiable();
+        external = file->getType() == FileType::Orphan;
 
         if (editTab->isEditMode()) {
             int line = p_info.m_cursorBlockNumber + 1;
@@ -94,4 +106,5 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
 
     m_docTypeLabel->setText(docTypeToString(docType));
     m_readonlyLabel->setVisible(readonly);
+    m_externalLabel->setVisible(external);
 }

+ 3 - 0
src/vtabindicator.h

@@ -25,6 +25,9 @@ private:
     // Indicate the readonly property.
     QLabel *m_readonlyLabel;
 
+    // Indicate whether it is a normal note or an external file.
+    QLabel *m_externalLabel;
+
     // Indicate the position of current cursor.
     QLabel *m_cursorLabel;
 };