From 0b9d259de65cbb4802aa319970c9098d92733cfb Mon Sep 17 00:00:00 2001 From: Le Tan Date: Mon, 17 Jul 2017 22:16:41 +0800 Subject: [PATCH] support opening external files from context menu in system browser --- src/main.cpp | 52 ++++++++-- src/resources/docs/shortcuts_en.md | 4 +- src/resources/docs/shortcuts_zh.md | 2 +- src/resources/styles/default.mdhl | 2 +- src/veditwindow.cpp | 21 ++++ src/veditwindow.h | 3 + src/vmainwindow.cpp | 57 +++++++++-- src/vmainwindow.h | 25 ++++- src/vmdeditoperations.cpp | 7 +- src/vnote.cpp | 7 +- src/vsingleinstanceguard.cpp | 153 ++++++++++++++++++++--------- src/vsingleinstanceguard.h | 40 ++++++-- 12 files changed, 288 insertions(+), 85 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6b43355c..ff29ab68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #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(); } diff --git a/src/resources/docs/shortcuts_en.md b/src/resources/docs/shortcuts_en.md index 7b78f55c..448a5d94 100644 --- a/src/resources/docs/shortcuts_en.md +++ b/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` diff --git a/src/resources/docs/shortcuts_zh.md b/src/resources/docs/shortcuts_zh.md index 6958449d..1bba0c03 100644 --- a/src/resources/docs/shortcuts_zh.md +++ b/src/resources/docs/shortcuts_zh.md @@ -136,7 +136,7 @@ size=8 - `0` 跳转到前一个标签页(即前一个当前标签页)。实现当前标签页和前一个标签页之间的轮换。 - `D` -定位当前笔记所在目录。 +定位当前笔记所在文件夹。 - `Q` 放弃当前更改并退出编辑模式。 - `V` diff --git a/src/resources/styles/default.mdhl b/src/resources/styles/default.mdhl index 2b95e9a8..2f436a4e 100644 --- a/src/resources/styles/default.mdhl +++ b/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 diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 03333c5b..297a6407 100644 --- a/src/veditwindow.cpp +++ b/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 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(file)->isSystemFile()) { + m_openLocationAct->setData(tab); + menu.addAction(m_openLocationAct); + m_noteInfoAct->setData(tab); menu.addAction(m_noteInfoAct); } diff --git a/src/veditwindow.h b/src/veditwindow.h index 25506385..10f84df7 100644 --- a/src/veditwindow.h +++ b/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 diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 2f118a38..0da2915a 100644 --- a/src/vmainwindow.cpp +++ b/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(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((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); + } +} diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 88167a90..958bf1a6 100644 --- a/src/vmainwindow.h +++ b/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 > &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 m_curFile; @@ -216,6 +226,15 @@ private: QToolBar *m_editToolBar; QVector 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 diff --git a/src/vmdeditoperations.cpp b/src/vmdeditoperations.cpp index da9cd732..28c6b673 100644 --- a/src/vmdeditoperations.cpp +++ b/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; } diff --git a/src/vnote.cpp b/src/vnote.cpp index af5c13e0..737884ac 100644 --- a/src/vnote.cpp +++ b/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(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; } diff --git a/src/vsingleinstanceguard.cpp b/src/vsingleinstanceguard.cpp index 3255c3cc..68bc3ae8 100644 --- a/src/vsingleinstanceguard.cpp +++ b/src/vsingleinstanceguard.cpp @@ -1,73 +1,132 @@ #include "vsingleinstanceguard.h" #include -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() -{ - if (m_sharedMemory.isAttached()) { - detachMemory(); - } -} - -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(); - if (ret) { - m_sharedMemory.lock(); - SharedStruct *str = (SharedStruct *)m_sharedMemory.data(); - str->m_activeRequest = 1; - m_sharedMemory.unlock(); - detachMemory(); - } - return ret; -} - bool VSingleInstanceGuard::tryRun() { // If we can attach to the sharedmemory, there is another instance running. - if (tryAttach()) { + // 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; } - // Try to create it - m_sem.acquire(); + // Try to create it. bool ret = m_sharedMemory.create(sizeof(SharedStruct)); - m_sem.release(); if (ret) { - // We created it + // We created it. m_sharedMemory.lock(); SharedStruct *str = (SharedStruct *)m_sharedMemory.data(); - str->m_magic = m_magic; - str->m_activeRequest = 0; + str->m_magic = c_magic; + str->m_filesBufIdx = 0; m_sharedMemory.unlock(); return true; } else { - // Maybe another thread create it - if (tryAttach()) { - qDebug() << "another instance is running"; - return false; + qDebug() << "fail to create shared memory segment"; + return false; + } +} + +void VSingleInstanceGuard::openExternalFiles(const QStringList &p_files) +{ + if (p_files.isEmpty()) { + return; + } + + 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(); + 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(); + + 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; +} diff --git a/src/vsingleinstanceguard.h b/src/vsingleinstanceguard.h index 1a4cd6cb..6cc442c1 100644 --- a/src/vsingleinstanceguard.h +++ b/src/vsingleinstanceguard.h @@ -3,31 +3,51 @@ #include #include -#include +#include 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