Browse Source

support opening external files from context menu in system browser

Le Tan 8 years ago
parent
commit
0b9d259de6

+ 44 - 8
src/main.cpp

@@ -6,6 +6,8 @@
 #include <QFile>
 #include <QTextCodec>
 #include <QFileInfo>
+#include <QStringList>
+#include <QDir>
 #include "utils/vutils.h"
 #include "vsingleinstanceguard.h"
 #include "vconfigmanager.h"
@@ -91,16 +93,18 @@ void VLogger(QtMsgType type, const QMessageLogContext &context, const QString &m
 
 int main(int argc, char *argv[])
 {
+    VSingleInstanceGuard guard;
+    bool canRun = guard.tryRun();
+
 #if defined(QT_NO_DEBUG)
-    g_logFile.setFileName(VConfigManager::getLogFilePath());
-    g_logFile.open(QIODevice::WriteOnly);
+    if (canRun) {
+        g_logFile.setFileName(VConfigManager::getLogFilePath());
+        g_logFile.open(QIODevice::WriteOnly);
+    }
 #endif
 
-    qInstallMessageHandler(VLogger);
-
-    VSingleInstanceGuard guard;
-    if (!guard.tryRun()) {
-        return 0;
+    if (canRun) {
+        qInstallMessageHandler(VLogger);
     }
 
     QTextCodec *codec = QTextCodec::codecForName("UTF8");
@@ -109,6 +113,35 @@ int main(int argc, char *argv[])
     }
 
     QApplication app(argc, argv);
+
+    // The file path passed via command line arguments.
+    QStringList filePaths;
+    QStringList args = app.arguments();
+    for (int i = 1; i < args.size(); ++i) {
+        if (QFileInfo::exists(args[i])) {
+            QString filePath = args[i];
+            QFileInfo fi(filePath);
+            if (fi.isFile()) {
+                // Need to use absolute path here since VNote may be launched
+                // in different working directory.
+                filePath = QDir::cleanPath(fi.absoluteFilePath());
+            }
+
+            filePaths.append(filePath);
+        }
+    }
+
+    qDebug() << "command line arguments" << args;
+
+    if (!canRun) {
+        // Ask another instance to open files passed in.
+        if (!filePaths.isEmpty()) {
+            guard.openExternalFiles(filePaths);
+        }
+
+        return 0;
+    }
+
     vconfig.initialize();
 
     QString locale = VUtils::getLocale();
@@ -119,6 +152,7 @@ int main(int argc, char *argv[])
     if (!qtTranslator.load("qt_" + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) {
         qtTranslator.load("qt_" + locale, "translations");
     }
+
     app.installTranslator(&qtTranslator);
 
     // load translation for vnote
@@ -128,7 +162,7 @@ int main(int argc, char *argv[])
         app.installTranslator(&translator);
     }
 
-    VMainWindow w;
+    VMainWindow w(&guard);
     QString style = VUtils::readFileFromDisk(":/resources/vnote.qss");
     if (!style.isEmpty()) {
         VUtils::processStyle(style, w.getPalette());
@@ -137,5 +171,7 @@ int main(int argc, char *argv[])
 
     w.show();
 
+    w.openExternalFiles(filePaths);
+
     return app.exec();
 }

+ 2 - 2
src/resources/docs/shortcuts_en.md

@@ -6,7 +6,7 @@
 - `Ctrl+E E`  
 Toggle expanding the edit area.
 - `Ctrl+Alt+N`  
-Create a note in current directory.
+Create a note in current folder.
 - `Ctrl+F`  
 Find/Replace in current note.
 - `Ctrl+Q`  
@@ -135,7 +135,7 @@ Number key 1 to 9 will jump to the tabs with corresponding sequence number.
 - `0`   
 Jump to previous tab. Alternate between current and previous tab.
 - `D`   
-Locate to the directory of current note.
+Locate to the folder of current note.
 - `Q`   
 Discard current changes and exit edit mode.
 - `V`   

+ 1 - 1
src/resources/docs/shortcuts_zh.md

@@ -136,7 +136,7 @@ size=8
 - `0`   
 跳转到前一个标签页(即前一个当前标签页)。实现当前标签页和前一个标签页之间的轮换。
 - `D`   
-定位当前笔记所在目录
+定位当前笔记所在文件夹
 - `Q`   
 放弃当前更改并退出编辑模式。
 - `V`   

+ 1 - 1
src/resources/styles/default.mdhl

@@ -34,7 +34,7 @@ background: 005fff
 editor-current-line
 background: c5cae9
 # [VNote] Vim insert mode cursor line background
-vim-insert-background: cdc0b0
+vim-insert-background: bcbcbc
 # [VNote] Vim normal mode cursor line background
 vim-normal-background: b0bec5
 # [VNote] Vim visual mode cursor line background

+ 21 - 0
src/veditwindow.cpp

@@ -131,6 +131,20 @@ void VEditWindow::initTabActions()
                     g_vnote->getMainWindow()->editOrphanFileInfo(file);
                 }
             });
+
+    m_openLocationAct = new QAction(tr("Open Note Location"), this);
+    m_openLocationAct->setToolTip(tr("Open the folder containing this note in operating system"));
+    connect(m_openLocationAct, &QAction::triggered,
+            this, [this](){
+                int tab = this->m_closeTabAct->data().toInt();
+                Q_ASSERT(tab != -1);
+
+                VEditTab *editor = getTab(tab);
+                QPointer<VFile> file = editor->getFile();
+                Q_ASSERT(file);
+                QUrl url = QUrl::fromLocalFile(file->retriveBasePath());
+                QDesktopServices::openUrl(url);
+            });
 }
 
 void VEditWindow::setupCornerWidget()
@@ -527,10 +541,17 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos)
         // Locate to folder.
         m_locateAct->setData(tab);
         menu.addAction(m_locateAct);
+
+        m_openLocationAct->setData(tab);
+        menu.addAction(m_openLocationAct);
+
         m_noteInfoAct->setData(tab);
         menu.addAction(m_noteInfoAct);
     } else if (file->getType() == FileType::Orphan
                && !dynamic_cast<VOrphanFile *>(file)->isSystemFile()) {
+        m_openLocationAct->setData(tab);
+        menu.addAction(m_openLocationAct);
+
         m_noteInfoAct->setData(tab);
         menu.addAction(m_noteInfoAct);
     }

+ 3 - 0
src/veditwindow.h

@@ -168,6 +168,9 @@ private:
 
     // View and edit info about this note.
     QAction *m_noteInfoAct;
+
+    // Open the location (the folder containing this file) of this note.
+    QAction *m_openLocationAct;
 };
 
 inline QString VEditWindow::generateTooltip(const VFile *p_file) const

+ 49 - 8
src/vmainwindow.cpp

@@ -25,17 +25,20 @@
 #include "dialog/vupdater.h"
 #include "vorphanfile.h"
 #include "dialog/vorphanfileinfodialog.h"
+#include "vsingleinstanceguard.h"
 
 extern VConfigManager vconfig;
 
 VNote *g_vnote;
 
+const int VMainWindow::c_sharedMemTimerInterval = 1000;
+
 #if defined(QT_NO_DEBUG)
 extern QFile g_logFile;
 #endif
 
-VMainWindow::VMainWindow(QWidget *parent)
-    : QMainWindow(parent), m_onePanel(false)
+VMainWindow::VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent)
+    : QMainWindow(p_parent), m_onePanel(false), m_guard(p_guard)
 {
     setWindowIcon(QIcon(":/resources/icons/vnote.ico"));
     vnote = new VNote(this);
@@ -56,6 +59,19 @@ VMainWindow::VMainWindow(QWidget *parent)
     notebookSelector->update();
 
     initCaptain();
+
+    initSharedMemoryWatcher();
+}
+
+void VMainWindow::initSharedMemoryWatcher()
+{
+    m_sharedMemTimer = new QTimer(this);
+    m_sharedMemTimer->setSingleShot(false);
+    m_sharedMemTimer->setInterval(c_sharedMemTimerInterval);
+    connect(m_sharedMemTimer, &QTimer::timeout,
+            this, &VMainWindow::checkSharedMemory);
+
+    m_sharedMemTimer->start();
 }
 
 void VMainWindow::initCaptain()
@@ -1349,6 +1365,10 @@ void VMainWindow::setEditorStyle(QAction *p_action)
 void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
                                                        bool p_editMode)
 {
+    bool systemFile = p_file
+                      && p_file->getType() == FileType::Orphan
+                      && dynamic_cast<const VOrphanFile *>(p_file)->isSystemFile();
+
     m_printAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
     m_exportAsPDFAct->setEnabled(p_file && p_file->getDocType() == DocType::Markdown);
 
@@ -1356,8 +1376,8 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
     discardExitAct->setVisible(p_file && p_editMode);
     saveExitAct->setVisible(p_file && p_editMode);
     saveNoteAct->setVisible(p_file && p_editMode);
-    deleteNoteAct->setVisible(p_file && p_file->isModifiable());
-    noteInfoAct->setVisible(p_file && p_file->getType() == FileType::Normal);
+    deleteNoteAct->setVisible(p_file && p_file->getType() == FileType::Normal);
+    noteInfoAct->setVisible(p_file && !systemFile);
     m_closeNoteAct->setVisible(p_file);
 
     m_insertImageAct->setEnabled(p_file && p_editMode);
@@ -1483,17 +1503,25 @@ void VMainWindow::updateWindowTitle(const QString &str)
 
 void VMainWindow::curEditFileInfo()
 {
-    if (!m_curFile || m_curFile->getType() != FileType::Normal) {
-        return;
+    Q_ASSERT(m_curFile);
+
+    if (m_curFile->getType() == FileType::Normal) {
+        fileList->fileInfo(m_curFile);
+    } else if (m_curFile->getType() == FileType::Orphan) {
+        VOrphanFile *file = dynamic_cast<VOrphanFile *>((VFile *)m_curFile);
+        Q_ASSERT(file);
+        if (!file->isSystemFile()) {
+            editOrphanFileInfo(m_curFile);
+        }
     }
-    fileList->fileInfo(m_curFile);
 }
 
 void VMainWindow::deleteCurNote()
 {
-    if (!m_curFile || !m_curFile->isModifiable()) {
+    if (!m_curFile || m_curFile->getType() != FileType::Normal) {
         return;
     }
+
     fileList->deleteFile(m_curFile);
 }
 
@@ -1828,3 +1856,16 @@ void VMainWindow::editOrphanFileInfo(VFile *p_file)
         file->setImageFolder(imgFolder);
     }
 }
+
+void VMainWindow::checkSharedMemory()
+{
+    QStringList files = m_guard->fetchFilesToOpen();
+    if (!files.isEmpty()) {
+        qDebug() << "shared memory fetch files" << files;
+        openExternalFiles(files);
+
+        show();
+        activateWindow();
+        QApplication::alert(this, 5000);
+    }
+}

+ 22 - 3
src/vmainwindow.h

@@ -31,6 +31,8 @@ class VFindReplaceDialog;
 class VCaptain;
 class VVimIndicator;
 class VTabIndicator;
+class VSingleInstanceGuard;
+class QTimer;
 
 class VMainWindow : public QMainWindow
 {
@@ -39,7 +41,7 @@ class VMainWindow : public QMainWindow
 public:
     friend class VCaptain;
 
-    VMainWindow(QWidget *parent = 0);
+    VMainWindow(VSingleInstanceGuard *p_guard, QWidget *p_parent = 0);
     const QVector<QPair<QString, QString> > &getPalette() const;
     void locateFile(VFile *p_file);
     void locateCurrentFile();
@@ -49,6 +51,9 @@ public:
     // View and edit the information of @p_file, which is an orphan file.
     void editOrphanFileInfo(VFile *p_file);
 
+    // Open external files @p_files as orphan files.
+    void openExternalFiles(const QStringList &p_files);
+
 private slots:
     void importNoteFromFile();
     void viewSettings();
@@ -100,6 +105,10 @@ private slots:
     // Handle the status update of the current tab of VEditArea.
     void handleAreaTabStatusUpdated(const VEditTabInfo &p_info);
 
+    // Check the shared memory between different instances to see if we have
+    // files to open.
+    void checkSharedMemory();
+
 protected:
     void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE;
     void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE;
@@ -154,8 +163,9 @@ private:
                        const QString &p_text,
                        QObject *p_parent = nullptr);
 
-    // Open external files @p_files as orphan files.
-    void openExternalFiles(const QStringList &p_files);
+    // Init a timer to watch the change of the shared memory between instances of
+    // VNote.
+    void initSharedMemoryWatcher();
 
     VNote *vnote;
     QPointer<VFile> m_curFile;
@@ -216,6 +226,15 @@ private:
     QToolBar *m_editToolBar;
 
     QVector<QPixmap> predefinedColorPixmaps;
+
+    // Single instance guard.
+    VSingleInstanceGuard *m_guard;
+
+    // Timer to check the shared memory between instances of VNote.
+    QTimer *m_sharedMemTimer;
+
+    // Interval of the shared memory timer in ms.
+    static const int c_sharedMemTimerInterval;
 };
 
 inline VFileList *VMainWindow::getFileList() const

+ 5 - 2
src/vmdeditoperations.cpp

@@ -530,6 +530,7 @@ bool VMdEditOperations::handleKeyEsc(QKeyEvent *p_event)
 
 bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
 {
+    bool autolist = true;
     if (p_event->modifiers() & Qt::ControlModifier) {
         m_autoIndentPos = -1;
         return false;
@@ -543,7 +544,9 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
         cursor.insertText("  ");
         cursor.endEditBlock();
 
-        // Let remaining logics handle inserting the new block.
+        // Let remaining logics handle inserting the new block except that we
+        // do not need to insert auto list.
+        autolist = false;
     }
 
     // See if we need to cancel auto indent.
@@ -585,7 +588,7 @@ bool VMdEditOperations::handleKeyReturn(QKeyEvent *p_event)
         textInserted = VEditUtils::insertBlockWithIndent(cursor);
 
         // Continue the list from previous line.
-        if (vconfig.getAutoList()) {
+        if (vconfig.getAutoList() && autolist) {
             textInserted = VEditUtils::insertListMarkAsPreviousBlock(cursor) || textInserted;
         }
 

+ 4 - 3
src/vnote.cpp

@@ -282,11 +282,12 @@ VFile *VNote::getOrphanFile(const QString &p_path, bool p_modifiable, bool p_sys
         return NULL;
     }
 
+    QString path = QDir::cleanPath(p_path);
     // See if the file has already been opened before.
     for (auto const &file : m_externalFiles) {
         Q_ASSERT(file->getType() == FileType::Orphan);
         VOrphanFile *oFile = dynamic_cast<VOrphanFile *>(file);
-        if (oFile->retrivePath() == p_path) {
+        if (VUtils::equalPath(QDir::cleanPath(oFile->retrivePath()), path)) {
             Q_ASSERT(oFile->isModifiable() == p_modifiable);
             Q_ASSERT(oFile->isSystemFile() == p_systemFile);
             return file;
@@ -303,8 +304,8 @@ VFile *VNote::getOrphanFile(const QString &p_path, bool p_modifiable, bool p_sys
         }
     }
 
-    // Create a VOrphanFile for p_path.
-    VOrphanFile *file = new VOrphanFile(p_path, this, p_modifiable, p_systemFile);
+    // Create a VOrphanFile for path.
+    VOrphanFile *file = new VOrphanFile(path, this, p_modifiable, p_systemFile);
     m_externalFiles.append(file);
     return file;
 }

+ 104 - 45
src/vsingleinstanceguard.cpp

@@ -1,73 +1,132 @@
 #include "vsingleinstanceguard.h"
 #include <QDebug>
 
-const QString VSingleInstanceGuard::m_memKey = "vnote_shared_memory";
-const QString VSingleInstanceGuard::m_semKey = "vnote_semaphore";
-const int VSingleInstanceGuard::m_magic = 133191933;
+#include "utils/vutils.h"
+
+const QString VSingleInstanceGuard::c_memKey = "vnote_shared_memory";
+const int VSingleInstanceGuard::c_magic = 133191933;
 
 VSingleInstanceGuard::VSingleInstanceGuard()
-    : m_sharedMemory(m_memKey), m_sem(m_semKey, 1)
+    : m_sharedMemory(c_memKey)
 {
 }
 
-VSingleInstanceGuard::~VSingleInstanceGuard()
+bool VSingleInstanceGuard::tryRun()
 {
-    if (m_sharedMemory.isAttached()) {
-        detachMemory();
+    // If we can attach to the sharedmemory, there is another instance running.
+    // In Linux, crashes may cause the shared memory segment remains. In this case,
+    // this will attach to the old segment, then exit, freeing the old segment.
+    if (m_sharedMemory.attach()) {
+        qDebug() << "another instance is running";
+        return false;
     }
-}
 
-void VSingleInstanceGuard::detachMemory()
-{
-    m_sem.acquire();
-    m_sharedMemory.detach();
-    m_sem.release();
-}
-
-bool VSingleInstanceGuard::tryAttach()
-{
-    m_sem.acquire();
-    bool ret = m_sharedMemory.attach();
-    m_sem.release();
+    // Try to create it.
+    bool ret = m_sharedMemory.create(sizeof(SharedStruct));
     if (ret) {
+        // We created it.
         m_sharedMemory.lock();
         SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
-        str->m_activeRequest = 1;
+        str->m_magic = c_magic;
+        str->m_filesBufIdx = 0;
         m_sharedMemory.unlock();
-        detachMemory();
+        return true;
+    } else {
+        qDebug() << "fail to create shared memory segment";
+        return false;
     }
-    return ret;
 }
 
-bool VSingleInstanceGuard::tryRun()
+void VSingleInstanceGuard::openExternalFiles(const QStringList &p_files)
 {
-    // If we can attach to the sharedmemory, there is another instance running.
-    if (tryAttach()) {
-        qDebug() << "another instance is running";
-        return false;
+    if (p_files.isEmpty()) {
+        return;
     }
 
-    // Try to create it
-    m_sem.acquire();
-    bool ret = m_sharedMemory.create(sizeof(SharedStruct));
-    m_sem.release();
-    if (ret) {
-        // We created it
+    if (!m_sharedMemory.isAttached()) {
+        if (!m_sharedMemory.attach()) {
+            qDebug() << "fail to attach to the shared memory segment"
+                     << (m_sharedMemory.error() ? m_sharedMemory.errorString() : "");
+            return;
+        }
+    }
+
+    qDebug() << "try to request another instance to open files" << p_files;
+
+    int idx = 0;
+    int tryCount = 100;
+    while (tryCount--) {
+        qDebug() << "set shared memory one round" << idx << "of" << p_files.size();
         m_sharedMemory.lock();
         SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
-        str->m_magic = m_magic;
-        str->m_activeRequest = 0;
+        V_ASSERT(str->m_magic == c_magic);
+        for (; idx < p_files.size(); ++idx) {
+            if (p_files[idx].size() + 1 > FilesBufCount) {
+                qDebug() << "skip file since its long name" << p_files[idx];
+                // Skip this long long name file.
+                continue;
+            }
+
+            if (!appendFileToBuffer(str, p_files[idx])) {
+                break;
+            }
+        }
+
         m_sharedMemory.unlock();
-        return true;
-    } else {
-        // Maybe another thread create it
-        if (tryAttach()) {
-            qDebug() << "another instance is running";
-            return false;
+
+        if (idx < p_files.size()) {
+            VUtils::sleepWait(500);
         } else {
-            // Something wrong here
-            qWarning() << "fail to create or attach shared memory segment";
-            return false;
+            break;
+        }
+    }
+}
+
+bool VSingleInstanceGuard::appendFileToBuffer(SharedStruct *p_str, const QString &p_file)
+{
+    if (p_file.isEmpty()) {
+        return true;
+    }
+
+    int strSize = p_file.size();
+    if (strSize + 1 > FilesBufCount - p_str->m_filesBufIdx) {
+        qDebug() << "no enough space for" << p_file;
+        return false;
+    }
+
+    // Put the size first.
+    p_str->m_filesBuf[p_str->m_filesBufIdx++] = (ushort)strSize;
+    const QChar *data = p_file.constData();
+    for (int i = 0; i < strSize; ++i) {
+        p_str->m_filesBuf[p_str->m_filesBufIdx++] = data[i].unicode();
+    }
+
+    qDebug() << "after appended one file" << p_str->m_filesBufIdx << p_file;
+    return true;
+}
+
+QStringList VSingleInstanceGuard::fetchFilesToOpen()
+{
+    QStringList files;
+    Q_ASSERT(m_sharedMemory.isAttached());
+    m_sharedMemory.lock();
+    SharedStruct *str = (SharedStruct *)m_sharedMemory.data();
+    Q_ASSERT(str->m_magic == c_magic);
+    Q_ASSERT(str->m_filesBufIdx <= FilesBufCount);
+    int idx = 0;
+    while (idx < str->m_filesBufIdx) {
+        int strSize = str->m_filesBuf[idx++];
+        Q_ASSERT(strSize <= str->m_filesBufIdx - idx);
+        QString file;
+        for (int i = 0; i < strSize; ++i) {
+            file.append(QChar(str->m_filesBuf[idx++]));
         }
+
+        files.append(file);
     }
+
+    str->m_filesBufIdx = 0;
+    m_sharedMemory.unlock();
+
+    return files;
 }

+ 30 - 10
src/vsingleinstanceguard.h

@@ -3,31 +3,51 @@
 
 #include <QString>
 #include <QSharedMemory>
-#include <QSystemSemaphore>
+#include <QStringList>
 
 class VSingleInstanceGuard
 {
 public:
     VSingleInstanceGuard();
-    ~VSingleInstanceGuard();
+
+    // Return ture if this is the only instance of VNote.
     bool tryRun();
 
+    // There is already another instance running.
+    // Call this to ask that instance to open external files passed in
+    // via command line arguments.
+    void openExternalFiles(const QStringList &p_files);
+
+    // Fetch files from shared memory to open.
+    // Will clear the shared memory.
+    QStringList fetchFilesToOpen();
+
 private:
-    void detachMemory();
-    bool tryAttach();
+    // The count of the entries in the buffer to hold the path of the files to open.
+    enum { FilesBufCount = 1024 };
 
     struct SharedStruct {
         // A magic number to identify if this struct is initialized
         int m_magic;
-        // If it is 1, then another instance ask this instance to show itself
-        int m_activeRequest;
+
+        // Next empty entry in m_filesBuf.
+        int m_filesBufIdx;
+
+        // File paths to be opened.
+        // Encoded in this way with 2 bytes for each size part.
+        // [size of file1][file1][size of file2][file 2]
+        // Unicode representation of QString.
+        ushort m_filesBuf[FilesBufCount];
     };
 
-    static const QString m_memKey;
-    static const QString m_semKey;
-    static const int m_magic;
+    // Append @p_file to the shared struct files buffer.
+    // Returns true if succeeds or false if there is no enough space.
+    bool appendFileToBuffer(SharedStruct *p_str, const QString &p_file);
+
     QSharedMemory m_sharedMemory;
-    QSystemSemaphore m_sem;
+
+    static const QString c_memKey;
+    static const int c_magic;
 };
 
 #endif // VSINGLEINSTANCEGUARD_H