From 2ff9c607ddef7c05afda7b225c494d7a8e63134d Mon Sep 17 00:00:00 2001 From: Le Tan Date: Wed, 23 May 2018 20:09:41 +0800 Subject: [PATCH] explorer: support exploring system's files --- src/dialog/vsettingsdialog.cpp | 4 +- src/resources/icons/create_note_tb.svg | 23 +- src/resources/icons/create_rootdir_tb.svg | 24 +- src/resources/icons/explore_root.svg | 17 + src/resources/icons/explorer.svg | 14 + src/resources/icons/open_location.svg | 12 + src/resources/icons/remove_split.svg | 10 +- src/resources/icons/search_console.svg | 7 +- src/resources/icons/split_window.svg | 20 +- src/resources/icons/star.svg | 10 + src/resources/icons/unstar.svg | 10 + .../themes/v_moonlight/v_moonlight.palette | 2 +- .../themes/v_moonlight/v_moonlight.qss | 18 + .../themes/v_native/v_native.palette | 2 +- src/resources/themes/v_native/v_native.qss | 8 + src/resources/themes/v_pure/v_pure.palette | 2 +- src/resources/themes/v_pure/v_pure.qss | 18 + src/src.pro | 7 +- src/utils/vutils.cpp | 38 + src/utils/vutils.h | 6 + src/vconfigmanager.cpp | 42 +- src/vconfigmanager.h | 48 +- src/vdirectorytree.cpp | 11 +- src/veditwindow.cpp | 6 +- src/vexplorer.cpp | 737 ++++++++++++++++++ src/vexplorer.h | 95 +++ src/vexplorerentry.h | 51 ++ src/vfilelist.cpp | 12 +- src/vmainwindow.cpp | 38 +- src/vmainwindow.h | 13 +- src/vnote.qrc | 5 + src/vnotebookselector.cpp | 216 ++--- src/vnotebookselector.h | 10 - src/vsearcher.cpp | 74 +- src/vsearcher.h | 10 + src/vtoolbox.cpp | 6 +- src/vtoolbox.h | 7 + src/vtreewidget.cpp | 6 + 38 files changed, 1431 insertions(+), 208 deletions(-) create mode 100644 src/resources/icons/explore_root.svg create mode 100644 src/resources/icons/explorer.svg create mode 100644 src/resources/icons/open_location.svg create mode 100644 src/resources/icons/star.svg create mode 100644 src/resources/icons/unstar.svg create mode 100644 src/vexplorer.cpp create mode 100644 src/vexplorer.h create mode 100644 src/vexplorerentry.h diff --git a/src/dialog/vsettingsdialog.cpp b/src/dialog/vsettingsdialog.cpp index 69098501..5592aec9 100644 --- a/src/dialog/vsettingsdialog.cpp +++ b/src/dialog/vsettingsdialog.cpp @@ -922,7 +922,7 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent) { // Default note open mode. m_openModeCombo = VUtils::getComboBox(); - m_openModeCombo->setToolTip(tr("Default mode to open an internal note")); + m_openModeCombo->setToolTip(tr("Default mode to open a file")); m_openModeCombo->addItem(tr("Read Mode"), (int)OpenFileMode::Read); m_openModeCombo->addItem(tr("Edit Mode"), (int)OpenFileMode::Edit); @@ -992,7 +992,7 @@ VMarkdownTab::VMarkdownTab(QWidget *p_parent) m_graphvizDotEdit->setToolTip(tr("Location to the GraphViz dot executable")); QFormLayout *mainLayout = new QFormLayout(); - mainLayout->addRow(tr("Note open mode:"), m_openModeCombo); + mainLayout->addRow(tr("Open mode:"), m_openModeCombo); mainLayout->addRow(tr("Heading sequence:"), headingSequenceLayout); mainLayout->addRow(colorColumnLabel, m_colorColumnEdit); mainLayout->addRow(tr("MathJax configuration:"), m_mathjaxConfigEdit); diff --git a/src/resources/icons/create_note_tb.svg b/src/resources/icons/create_note_tb.svg index 411ae214..962ef8ad 100644 --- a/src/resources/icons/create_note_tb.svg +++ b/src/resources/icons/create_note_tb.svg @@ -1,10 +1,15 @@ - - - - - + + + + + + diff --git a/src/resources/icons/create_rootdir_tb.svg b/src/resources/icons/create_rootdir_tb.svg index 672e65f5..1c680678 100644 --- a/src/resources/icons/create_rootdir_tb.svg +++ b/src/resources/icons/create_rootdir_tb.svg @@ -1,10 +1,16 @@ - - - - - + + + + + + + diff --git a/src/resources/icons/explore_root.svg b/src/resources/icons/explore_root.svg new file mode 100644 index 00000000..05c4011f --- /dev/null +++ b/src/resources/icons/explore_root.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/src/resources/icons/explorer.svg b/src/resources/icons/explorer.svg new file mode 100644 index 00000000..464707da --- /dev/null +++ b/src/resources/icons/explorer.svg @@ -0,0 +1,14 @@ + + + + + diff --git a/src/resources/icons/open_location.svg b/src/resources/icons/open_location.svg new file mode 100644 index 00000000..d279ec4c --- /dev/null +++ b/src/resources/icons/open_location.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/src/resources/icons/remove_split.svg b/src/resources/icons/remove_split.svg index fdc83bd6..ce23b83e 100644 --- a/src/resources/icons/remove_split.svg +++ b/src/resources/icons/remove_split.svg @@ -2,9 +2,9 @@ - + width="512px" height="512px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve"> + diff --git a/src/resources/icons/search_console.svg b/src/resources/icons/search_console.svg index 0a3d2842..7308917e 100644 --- a/src/resources/icons/search_console.svg +++ b/src/resources/icons/search_console.svg @@ -3,10 +3,5 @@ - - - - + diff --git a/src/resources/icons/split_window.svg b/src/resources/icons/split_window.svg index b778bb9f..e269d0a8 100644 --- a/src/resources/icons/split_window.svg +++ b/src/resources/icons/split_window.svg @@ -1,9 +1,15 @@ - - + + - - + + + + diff --git a/src/resources/icons/star.svg b/src/resources/icons/star.svg new file mode 100644 index 00000000..e857a234 --- /dev/null +++ b/src/resources/icons/star.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/src/resources/icons/unstar.svg b/src/resources/icons/unstar.svg new file mode 100644 index 00000000..c2d9b453 --- /dev/null +++ b/src/resources/icons/unstar.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/src/resources/themes/v_moonlight/v_moonlight.palette b/src/resources/themes/v_moonlight/v_moonlight.palette index 9c0d9293..f90cebd1 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.palette +++ b/src/resources/themes/v_moonlight/v_moonlight.palette @@ -7,7 +7,7 @@ mdhl_file=v_moonlight.mdhl css_file=v_moonlight.css codeblock_css_file=v_moonlight_codeblock.css mermaid_css_file=v_moonlight_mermaid.css -version=10 +version=11 ; This mapping will be used to translate colors when the content of HTML is copied ; without background. You could just specify the foreground colors mapping here. diff --git a/src/resources/themes/v_moonlight/v_moonlight.qss b/src/resources/themes/v_moonlight/v_moonlight.qss index ffe44efa..49a9c756 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.qss +++ b/src/resources/themes/v_moonlight/v_moonlight.qss @@ -630,6 +630,24 @@ QLineEdit[VimCommandLine="true"]:hover { background: @lineedit_hover_bg; } +QLineEdit[EmbeddedEdit="true"] { + padding: 0px; + margin: 0px; + border: none; + color: @lineedit_fg; + background: transparent; +} + +QLineEdit[EmbeddedEdit="true"]:focus { + background: @lineedit_focus_bg; + border: none; +} + +QLineEdit[EmbeddedEdit="true"]:hover { + background: @lineedit_hover_bg; + border: none; +} + QLineEdit { border: 1px solid @lineedit_border; padding: 3px; diff --git a/src/resources/themes/v_native/v_native.palette b/src/resources/themes/v_native/v_native.palette index f5048174..52e8e14e 100644 --- a/src/resources/themes/v_native/v_native.palette +++ b/src/resources/themes/v_native/v_native.palette @@ -7,7 +7,7 @@ mdhl_file=v_native.mdhl css_file=v_native.css codeblock_css_file=v_native_codeblock.css mermaid_css_file=v_native_mermaid.css -version=10 +version=11 [phony] ; Abstract color attributes. diff --git a/src/resources/themes/v_native/v_native.qss b/src/resources/themes/v_native/v_native.qss index 6ac088ae..93533b06 100644 --- a/src/resources/themes/v_native/v_native.qss +++ b/src/resources/themes/v_native/v_native.qss @@ -452,6 +452,14 @@ QLineEdit[VimCommandLine="true"] { background: @lineedit_bg; } +QLineEdit[EmbeddedEdit="true"] { + padding: 0px; + margin: 0px; + border: none; + color: @lineedit_fg; + background: transparent; +} + VUniversalEntry VMetaWordLineEdit { border: 1px solid @lineedit_border; } diff --git a/src/resources/themes/v_pure/v_pure.palette b/src/resources/themes/v_pure/v_pure.palette index 04ace16f..1d83d1f6 100644 --- a/src/resources/themes/v_pure/v_pure.palette +++ b/src/resources/themes/v_pure/v_pure.palette @@ -7,7 +7,7 @@ mdhl_file=v_pure.mdhl css_file=v_pure.css codeblock_css_file=v_pure_codeblock.css mermaid_css_file=v_pure_mermaid.css -version=10 +version=11 [phony] ; Abstract color attributes. diff --git a/src/resources/themes/v_pure/v_pure.qss b/src/resources/themes/v_pure/v_pure.qss index 4e9713b2..40c882e5 100644 --- a/src/resources/themes/v_pure/v_pure.qss +++ b/src/resources/themes/v_pure/v_pure.qss @@ -630,6 +630,24 @@ QLineEdit[VimCommandLine="true"]:hover { border: none; } +QLineEdit[EmbeddedEdit="true"] { + padding: 0px; + margin: 0px; + border: none; + color: @lineedit_fg; + background: transparent; +} + +QLineEdit[EmbeddedEdit="true"]:focus { + background: @lineedit_focus_bg; + border: none; +} + +QLineEdit[EmbeddedEdit="true"]:hover { + background: @lineedit_hover_bg; + border: none; +} + QLineEdit { border: 1px solid @lineedit_border; padding: 3px; diff --git a/src/src.pro b/src/src.pro index d7e3ebf6..f07a2c94 100644 --- a/src/src.pro +++ b/src/src.pro @@ -132,7 +132,8 @@ SOURCES += main.cpp\ vmathjaxpreviewhelper.cpp \ vmathjaxwebdocument.cpp \ vmathjaxinplacepreviewhelper.cpp \ - vhistorylist.cpp + vhistorylist.cpp \ + vexplorer.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -257,7 +258,9 @@ HEADERS += vmainwindow.h \ vmathjaxinplacepreviewhelper.h \ markdownitoption.h \ vhistorylist.h \ - vhistoryentry.h + vhistoryentry.h \ + vexplorer.h \ + vexplorerentry.h RESOURCES += \ vnote.qrc \ diff --git a/src/utils/vutils.cpp b/src/utils/vutils.cpp index fa1ee986..9faaf9a1 100644 --- a/src/utils/vutils.cpp +++ b/src/utils/vutils.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "vorphanfile.h" #include "vnote.h" @@ -1542,3 +1543,40 @@ bool VUtils::inSameDrive(const QString &p_a, const QString &p_b) return true; #endif } + +QString VUtils::promptForFileName(const QString &p_title, + const QString &p_label, + const QString &p_default, + const QString &p_dir, + QWidget *p_parent) +{ + QString name = p_default; + QString text = p_label; + QDir paDir(p_dir); + while (true) { + bool ok; + name = QInputDialog::getText(p_parent, + p_title, + text, + QLineEdit::Normal, + name, + &ok); + if (!ok || name.isEmpty()) { + return ""; + } + + if (!VUtils::checkFileNameLegal(name)) { + text = QObject::tr("Illegal name. Please try again:"); + continue; + } + + if (paDir.exists(name)) { + text = QObject::tr("Name already exists. Please try again:"); + continue; + } + + break; + } + + return name; +} diff --git a/src/utils/vutils.h b/src/utils/vutils.h index 31fbbbd5..6de552c8 100644 --- a/src/utils/vutils.h +++ b/src/utils/vutils.h @@ -333,6 +333,12 @@ public: static bool inSameDrive(const QString &p_a, const QString &p_b); + static QString promptForFileName(const QString &p_title, + const QString &p_label, + const QString &p_default, + const QString &p_dir, + QWidget *p_parent = nullptr); + // Regular expression for image link. // ![image title]( http://github.com/tamlok/vnote.jpg "alt text" =200x100) // Captured texts (need to be trimmed): diff --git a/src/vconfigmanager.cpp b/src/vconfigmanager.cpp index 55f19556..89f73fc9 100644 --- a/src/vconfigmanager.cpp +++ b/src/vconfigmanager.cpp @@ -52,6 +52,7 @@ const QString VConfigManager::c_exportFolderName = QString("vnote_exports"); VConfigManager::VConfigManager(QObject *p_parent) : QObject(p_parent), m_noteListViewOrder(-1), + m_explorerCurrentIndex(-1), m_hasReset(false), userSettings(NULL), defaultSettings(NULL), @@ -1218,7 +1219,7 @@ void VConfigManager::setLastOpenedFiles(const QVector &p_files m_sessionSettings->endArray(); } -void VConfigManager::getHistory(QLinkedList &p_history) +void VConfigManager::getHistory(QLinkedList &p_history) const { p_history.clear(); @@ -1254,6 +1255,45 @@ void VConfigManager::setHistory(const QLinkedList &p_history) m_sessionSettings->endArray(); } +void VConfigManager::getExplorerEntries(QVector &p_entries) const +{ + p_entries.clear(); + + int size = m_sessionSettings->beginReadArray("explorer_starred"); + for (int i = 0; i < size; ++i) { + m_sessionSettings->setArrayIndex(i); + p_entries.append(VExplorerEntry::fromSettings(m_sessionSettings)); + } + + m_sessionSettings->endArray(); +} + +void VConfigManager::setExplorerEntries(const QVector &p_entries) +{ + if (m_hasReset) { + return; + } + + const QString section("explorer_starred"); + + // Clear it first + m_sessionSettings->beginGroup(section); + m_sessionSettings->remove(""); + m_sessionSettings->endGroup(); + + m_sessionSettings->beginWriteArray(section); + int idx = 0; + for (auto const & entry : p_entries) { + if (entry.m_isStarred) { + m_sessionSettings->setArrayIndex(idx); + entry.toSettings(m_sessionSettings); + ++idx; + } + } + + m_sessionSettings->endArray(); +} + QVector VConfigManager::getCustomMagicWords() { QVector words; diff --git a/src/vconfigmanager.h b/src/vconfigmanager.h index 9dc016a1..148f534e 100644 --- a/src/vconfigmanager.h +++ b/src/vconfigmanager.h @@ -17,6 +17,7 @@ #include "utils/vmetawordmanager.h" #include "markdownitoption.h" #include "vhistoryentry.h" +#include "vexplorerentry.h" class QJsonObject; class QString; @@ -131,6 +132,9 @@ public: int getCurNotebookIndex() const; void setCurNotebookIndex(int index); + int getNaviBoxCurrentIndex() const; + void setNaviBoxCurrentIndex(int p_index); + // Read [notebooks] section from settings into @p_notebooks. void getNotebooks(QVector &p_notebooks, QObject *p_parent); @@ -359,12 +363,22 @@ public: void setLastOpenedFiles(const QVector &p_files); // Read history from [history] of session.ini. - void getHistory(QLinkedList &p_history); + void getHistory(QLinkedList &p_history) const; void setHistory(const QLinkedList &p_history); int getHistorySize() const; + // Read explorer's starred entries from [explorer_starred] of session.ini. + void getExplorerEntries(QVector &p_entries) const; + + // Output starred entries to [explorer_starred] of session.ini. + void setExplorerEntries(const QVector &p_entries); + + int getExplorerCurrentIndex() const; + + void setExplorerCurrentIndex(int p_idx); + // Read custom magic words from [magic_words] section. QVector getCustomMagicWords(); @@ -899,6 +913,9 @@ private: // View order of note list. int m_noteListViewOrder; + // Current entry index of explorer entries. + int m_explorerCurrentIndex; + // Whether user has reset the configurations. bool m_hasReset; @@ -1004,6 +1021,16 @@ inline void VConfigManager::setCurNotebookIndex(int index) setConfigToSessionSettings("global", "current_notebook", index); } +inline int VConfigManager::getNaviBoxCurrentIndex() const +{ + return getConfigFromSessionSettings("global", "navibox_current_index").toInt(); +} + +inline void VConfigManager::setNaviBoxCurrentIndex(int p_index) +{ + setConfigToSessionSettings("global", "navibox_current_index", p_index); +} + inline void VConfigManager::getNotebooks(QVector &p_notebooks, QObject *p_parent) { @@ -2326,4 +2353,23 @@ inline void VConfigManager::setNoteListViewOrder(int p_order) m_noteListViewOrder = p_order; setConfigToSettings("global", "note_list_view_order", m_noteListViewOrder); } + +inline int VConfigManager::getExplorerCurrentIndex() const +{ + if (m_explorerCurrentIndex == -1) { + const_cast(this)->m_explorerCurrentIndex = getConfigFromSessionSettings("global", "explorer_current_entry").toInt(); + } + + return m_explorerCurrentIndex; +} + +inline void VConfigManager::setExplorerCurrentIndex(int p_idx) +{ + if (p_idx == m_explorerCurrentIndex) { + return; + } + + m_explorerCurrentIndex = p_idx; + setConfigToSessionSettings("global", "explorer_current_entry", m_explorerCurrentIndex); +} #endif // VCONFIGMANAGER_H diff --git a/src/vdirectorytree.cpp b/src/vdirectorytree.cpp index 16f048b0..fc9177be 100644 --- a/src/vdirectorytree.cpp +++ b/src/vdirectorytree.cpp @@ -33,6 +33,7 @@ VDirectoryTree::VDirectoryTree(QWidget *parent) setColumnCount(1); setHeaderHidden(true); setContextMenuPolicy(Qt::CustomContextMenu); + setFitContent(true); initShortcuts(); @@ -147,6 +148,8 @@ void VDirectoryTree::updateDirectoryTree() if (!restoreCurrentItem() && topLevelItemCount() > 0) { setCurrentItem(topLevelItem(0)); } + + resizeColumnToContents(0); } bool VDirectoryTree::restoreCurrentItem() @@ -425,8 +428,10 @@ void VDirectoryTree::contextMenuRequested(QPoint pos) menu.addAction(reloadAct); if (item) { - QAction *openLocationAct = new QAction(tr("&Open Folder Location"), &menu); - openLocationAct->setToolTip(tr("Open the folder containing this folder in operating system")); + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("&Open Folder Location"), + &menu); + openLocationAct->setToolTip(tr("Explore this folder in operating system")); connect(openLocationAct, &QAction::triggered, this, &VDirectoryTree::openDirectoryLocation); menu.addAction(openLocationAct); @@ -645,7 +650,7 @@ void VDirectoryTree::openDirectoryLocation() const { QTreeWidgetItem *curItem = currentItem(); V_ASSERT(curItem); - QUrl url = QUrl::fromLocalFile(getVDirectory(curItem)->fetchBasePath()); + QUrl url = QUrl::fromLocalFile(getVDirectory(curItem)->fetchPath()); QDesktopServices::openUrl(url); } diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 54461207..4c7a5ebf 100644 --- a/src/veditwindow.cpp +++ b/src/veditwindow.cpp @@ -524,8 +524,10 @@ void VEditWindow::tabbarContextMenuRequested(QPoint p_pos) QDesktopServices::openUrl(url); }); - QAction *openLocationAct = new QAction(tr("Open Note Location"), &menu); - openLocationAct->setToolTip(tr("Open the folder containing this note in operating system")); + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("Open Note Location"), + &menu); + openLocationAct->setToolTip(tr("Explore the folder containing this note in operating system")); connect(openLocationAct, &QAction::triggered, this, [this](){ int tab = GET_TAB_FROM_SENDER(); diff --git a/src/vexplorer.cpp b/src/vexplorer.cpp new file mode 100644 index 00000000..a20a91fe --- /dev/null +++ b/src/vexplorer.cpp @@ -0,0 +1,737 @@ +#include "vexplorer.h" + +#include +#include +#include + +#include "utils/viconutils.h" +#include "utils/vutils.h" +#include "vconfigmanager.h" +#include "vmainwindow.h" +#include "vcart.h" +#include "vlineedit.h" +#include "vhistorylist.h" +#include "vorphanfile.h" + +extern VMainWindow *g_mainWin; + +extern VConfigManager *g_config; + +const QString VExplorer::c_infoShortcutSequence = "F2"; + +VExplorer::VExplorer(QWidget *p_parent) + : QWidget(p_parent), + m_initialized(false), + m_uiInitialized(false), + m_index(-1) +{ +} + +void VExplorer::setupUI() +{ + if (m_uiInitialized) { + return; + } + + m_uiInitialized = true; + + QLabel *dirLabel = new QLabel(tr("Directory"), this); + + m_openBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/dir_item.svg"), + "", + this); + m_openBtn->setToolTip(tr("Open")); + m_openBtn->setProperty("FlatBtn", true); + connect(m_openBtn, &QPushButton::clicked, + this, [this]() { + static QString defaultPath = g_config->getDocumentPathOrHomePath(); + QString dirPath = QFileDialog::getExistingDirectory(this, + tr("Select Root Directory To Explore"), + defaultPath, + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + if (!dirPath.isEmpty()) { + defaultPath = VUtils::basePathFromPath(dirPath); + + int idx = addEntry(dirPath); + updateDirectoryComboBox(); + if (idx != -1) { + setCurrentEntry(idx); + } + } + }); + + m_openLocationBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/open_location.svg"), + "", + this); + m_openLocationBtn->setToolTip(tr("Open Directory Location")); + m_openLocationBtn->setProperty("FlatBtn", true); + connect(m_openLocationBtn, &QPushButton::clicked, + this, [this]() { + if (checkIndex()) { + QUrl url = QUrl::fromLocalFile(m_entries[m_index].m_directory); + QDesktopServices::openUrl(url); + } + }); + + m_starBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/star.svg"), + "", + this); + m_starBtn->setToolTip(tr("Star")); + m_starBtn->setProperty("FlatBtn", true); + connect(m_starBtn, &QPushButton::clicked, + this, [this]() { + if (checkIndex()) { + m_entries[m_index].m_isStarred = !m_entries[m_index].m_isStarred; + + g_config->setExplorerEntries(m_entries); + + updateExplorerEntryIndexInConfig(); + + updateStarButton(); + } + }); + + QHBoxLayout *dirLayout = new QHBoxLayout(); + dirLayout->addWidget(dirLabel); + dirLayout->addStretch(); + dirLayout->addWidget(m_openBtn); + dirLayout->addWidget(m_openLocationBtn); + dirLayout->addWidget(m_starBtn); + dirLayout->setContentsMargins(0, 0, 0, 0); + + m_dirCB = VUtils::getComboBox(this); + m_dirCB->setEditable(true); + m_dirCB->setLineEdit(new VLineEdit(this)); + m_dirCB->setToolTip(tr("Path of the root directory to explore")); + m_dirCB->lineEdit()->setPlaceholderText(tr("Root path to explore")); + m_dirCB->lineEdit()->setProperty("EmbeddedEdit", true); + connect(m_dirCB->lineEdit(), &QLineEdit::editingFinished, + this, [this]() { + QString text = m_dirCB->currentText(); + int idx = addEntry(text); + updateDirectoryComboBox(); + if (idx != -1) { + setCurrentEntry(idx); + } + }); + QCompleter *completer = new QCompleter(this); + QFileSystemModel *fsModel = new QFileSystemModel(completer); + fsModel->setRootPath(""); + completer->setModel(fsModel); + // Enable styling the popup list via QListView::item. + completer->popup()->setItemDelegate(new QStyledItemDelegate(this)); + completer->setCompletionMode(QCompleter::PopupCompletion); +#if defined(Q_OS_WIN) + completer->setCaseSensitivity(Qt::CaseInsensitive); +#else + completer->setCaseSensitivity(Qt::CaseSensitive); +#endif + m_dirCB->lineEdit()->setCompleter(completer); + connect(m_dirCB, SIGNAL(activated(int)), + this, SLOT(handleEntryActivated(int))); + m_dirCB->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); + + QLabel *imgLabel = new QLabel(tr("Image Folder"), this); + + m_imgFolderEdit = new VLineEdit(this); + m_imgFolderEdit->setPlaceholderText(tr("Use global configuration (%1)") + .arg(g_config->getImageFolderExt())); + QString imgFolderTip = tr("Set the path of the image folder to store images " + "of files within the root directory.\nIf absolute path is used, " + "VNote will not manage those images." + "(empty to use global configuration)"); + m_imgFolderEdit->setToolTip(imgFolderTip); + connect(m_imgFolderEdit, &QLineEdit::editingFinished, + this, [this]() { + if (checkIndex()) { + QString folder = m_imgFolderEdit->text(); + if (folder.isEmpty() || VUtils::checkPathLegal(folder)) { + m_entries[m_index].m_imageFolder = folder; + if (m_entries[m_index].m_isStarred) { + g_config->setExplorerEntries(m_entries); + } + } else { + m_imgFolderEdit->setText(m_entries[m_index].m_imageFolder); + } + } + }); + + // New file/dir. + m_newFileBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/create_note_tb.svg"), + "", + this); + m_newFileBtn->setToolTip(tr("New File")); + m_newFileBtn->setProperty("FlatBtn", true); + connect(m_newFileBtn, &QPushButton::clicked, + this, &VExplorer::newFile); + + m_newDirBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/create_rootdir_tb.svg"), + "", + this); + m_newDirBtn->setToolTip(tr("New Folder")); + m_newDirBtn->setProperty("FlatBtn", true); + connect(m_newDirBtn, &QPushButton::clicked, + this, &VExplorer::newFolder); + + QHBoxLayout *btnLayout = new QHBoxLayout(); + btnLayout->addStretch(); + btnLayout->addWidget(m_newFileBtn); + btnLayout->addWidget(m_newDirBtn); + btnLayout->setContentsMargins(0, 0, 0, 0); + + QFileSystemModel *dirModel = new QFileSystemModel(this); + dirModel->setRootPath(""); + m_tree = new QTreeView(this); + m_tree->setModel(dirModel); + m_tree->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_tree->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_tree, &QTreeView::customContextMenuRequested, + this, &VExplorer::handleContextMenuRequested); + connect(m_tree, &QTreeView::activated, + this, [this](const QModelIndex &p_index) { + QFileSystemModel *model = static_cast(m_tree->model()); + if (!model->isDir(p_index)) { + QStringList files; + files << model->filePath(p_index); + openFiles(files, g_config->getNoteOpenMode()); + } + }); + connect(m_tree, &QTreeView::expanded, + this, &VExplorer::resizeTreeToContents); + connect(m_tree, &QTreeView::collapsed, + this, &VExplorer::resizeTreeToContents); + connect(dirModel, &QFileSystemModel::directoryLoaded, + this, &VExplorer::resizeTreeToContents); + + m_tree->setHeaderHidden(true); + // Show only the Name column. + for (int i = 1; i < dirModel->columnCount(); ++i) { + m_tree->hideColumn(i); + } + + QVBoxLayout *mainLayout = new QVBoxLayout(); + mainLayout->addLayout(dirLayout); + mainLayout->addWidget(m_dirCB); + mainLayout->addWidget(imgLabel); + mainLayout->addWidget(m_imgFolderEdit); + mainLayout->addLayout(btnLayout); + mainLayout->addWidget(m_tree); + mainLayout->setContentsMargins(3, 0, 3, 0); + + setLayout(mainLayout); +} + +void VExplorer::init() +{ + if (m_initialized) { + return; + } + + m_initialized = true; + + setupUI(); + + QShortcut *infoShortcut = new QShortcut(QKeySequence(c_infoShortcutSequence), this); + infoShortcut->setContext(Qt::WidgetWithChildrenShortcut); + connect(infoShortcut, &QShortcut::activated, + this, [this]() { + }); + + g_config->getExplorerEntries(m_entries); + m_index = g_config->getExplorerCurrentIndex(); + if (m_entries.isEmpty()) { + m_index = -1; + g_config->setExplorerCurrentIndex(m_index); + } else if (m_index < 0 || m_index >= m_entries.size()) { + m_index = 0; + g_config->setExplorerCurrentIndex(m_index); + } + + updateDirectoryComboBox(); + + setCurrentEntry(m_index); +} + +void VExplorer::showEvent(QShowEvent *p_event) +{ + init(); + + QWidget::showEvent(p_event); +} + +void VExplorer::focusInEvent(QFocusEvent *p_event) +{ + init(); + + QWidget::focusInEvent(p_event); + + if (m_index < 0) { + m_openBtn->setFocus(); + } else { + m_tree->setFocus(); + } +} + +void VExplorer::updateUI() +{ + +} + +void VExplorer::updateDirectoryComboBox() +{ + m_dirCB->clear(); + + for (int i = 0; i < m_entries.size(); ++i) { + m_dirCB->addItem(QDir::toNativeSeparators(m_entries[i].m_directory)); + m_dirCB->setItemData(i, m_entries[i].m_directory, Qt::ToolTipRole); + } + + m_dirCB->setCurrentIndex(m_index); +} + +int VExplorer::addEntry(const QString &p_dir) +{ + if (p_dir.isEmpty() || !QFileInfo::exists(p_dir) || !QDir::isAbsolutePath(p_dir)) { + return -1; + } + + QString dir(QDir::cleanPath(p_dir)); + int idx = -1; + for (idx = 0; idx < m_entries.size(); ++idx) { + if (VUtils::equalPath(dir, m_entries[idx].m_directory)) { + break; + } + } + + if (idx < m_entries.size()) { + return idx; + } + + m_entries.append(VExplorerEntry(QFileInfo(dir).absoluteFilePath(), "")); + return m_entries.size() - 1; +} + +void VExplorer::handleEntryActivated(int p_idx) +{ + bool valid = false; + QString imgFolder; + + if (p_idx >= 0 && p_idx < m_entries.size()) { + valid = true; + imgFolder = m_entries[p_idx].m_imageFolder; + } else { + p_idx = -1; + } + + m_index = p_idx; + + updateExplorerEntryIndexInConfig(); + + m_imgFolderEdit->setText(imgFolder); + m_imgFolderEdit->setEnabled(valid); + + m_openLocationBtn->setEnabled(valid); + m_newFileBtn->setEnabled(valid); + m_newDirBtn->setEnabled(valid); + + updateStarButton(); + + updateTree(); +} + +void VExplorer::updateStarButton() +{ + // -1 for disabled, 0 for star, 1 for unstar. + int star = -1; + + if (checkIndex()) { + star = m_entries[m_index].m_isStarred ? 1 : 0; + } + + bool enabled = true; + switch (star) { + default: + enabled = false; + V_FALLTHROUGH; + case 0: + m_starBtn->setEnabled(enabled); + m_starBtn->setIcon(VIconUtils::buttonIcon(":/resources/icons/star.svg")); + m_starBtn->setToolTip(tr("Star")); + break; + + case 1: + m_starBtn->setEnabled(enabled); + m_starBtn->setIcon(VIconUtils::buttonIcon(":/resources/icons/unstar.svg")); + m_starBtn->setToolTip(tr("Unstar")); + break; + } +} + +void VExplorer::setCurrentEntry(int p_index) +{ + m_dirCB->setCurrentIndex(p_index); + + handleEntryActivated(p_index); +} + +void VExplorer::updateExplorerEntryIndexInConfig() +{ + int idx = -1; + if (m_index >= 0 && m_index < m_entries.size()) { + int starIdx = -1; + for (int j = 0; j <= m_index; ++j) { + if (m_entries[j].m_isStarred) { + ++starIdx; + } + } + + if (starIdx > -1) { + idx = starIdx; + } + } + + g_config->setExplorerCurrentIndex(idx); +} + +void VExplorer::updateTree() +{ + if (checkIndex()) { + QString pa = QDir::cleanPath(m_entries[m_index].m_directory); + QFileSystemModel *model = static_cast(m_tree->model()); + model->setRootPath(pa); + const QModelIndex rootIndex = model->index(pa); + if (rootIndex.isValid()) { + m_tree->setRootIndex(rootIndex); + resizeTreeToContents(); + return; + } else { + model->setRootPath(""); + } + } + + m_tree->reset(); + resizeTreeToContents(); +} + +void VExplorer::handleContextMenuRequested(QPoint p_pos) +{ + QModelIndex index = m_tree->indexAt(p_pos); + if (!index.isValid()) { + return; + } + + QMenu menu(this); + menu.setToolTipsVisible(true); + + QFileSystemModel *model = static_cast(m_tree->model()); + QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows(); + if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) { + QString filePath = model->filePath(selectedIdx[0]); + + QAction *setRootAct = new QAction(VIconUtils::menuIcon(":/resources/icons/explore_root.svg"), + tr("Set As Root"), + &menu); + setRootAct->setToolTip(tr("Set current folder as the root directory to explore")); + connect(setRootAct, &QAction::triggered, + this, [this, filePath]() { + int idx = addEntry(filePath); + updateDirectoryComboBox(); + if (idx != -1) { + setCurrentEntry(idx); + } + }); + menu.addAction(setRootAct); + + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("Open Folder Location"), + &menu); + openLocationAct->setToolTip(tr("Explore this folder in operating system")); + connect(openLocationAct, &QAction::triggered, + this, [this, filePath]() { + QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); + }); + menu.addAction(openLocationAct); + + menu.addSeparator(); + + QAction *fileInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/note_info.svg"), + tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), + &menu); + fileInfoAct->setToolTip(tr("View and edit current folder's information")); + connect(fileInfoAct, &QAction::triggered, + this, [this, filePath]() { + renameFile(filePath); + }); + menu.addAction(fileInfoAct); + + menu.exec(m_tree->mapToGlobal(p_pos)); + return; + } + + bool allFiles = true; + QStringList selectedFiles; + for (auto const & it : selectedIdx) { + if (model->isDir(it)) { + allFiles = false; + break; + } + + selectedFiles << model->filePath(it); + } + + if (!allFiles) { + return; + } + + // Only files are selected. + // We do not support copy/cut/deletion of files. + QAction *openInReadAct = new QAction(VIconUtils::menuIcon(":/resources/icons/reading.svg"), + tr("Open In Read Mode"), + &menu); + openInReadAct->setToolTip(tr("Open selected files in read mode")); + connect(openInReadAct, &QAction::triggered, + this, [this, selectedFiles]() { + openFiles(selectedFiles, OpenFileMode::Read, true); + }); + menu.addAction(openInReadAct); + + QAction *openInEditAct = new QAction(VIconUtils::menuIcon(":/resources/icons/editing.svg"), + tr("Open In Edit Mode"), + &menu); + openInEditAct->setToolTip(tr("Open selected files in edit mode")); + connect(openInEditAct, &QAction::triggered, + this, [this, selectedFiles]() { + openFiles(selectedFiles, OpenFileMode::Edit, true); + }); + menu.addAction(openInEditAct); + + menu.addSeparator(); + + if (selectedFiles.size() == 1) { + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("Open File Location"), + &menu); + openLocationAct->setToolTip(tr("Explore the folder containing this file in operating system")); + connect(openLocationAct, &QAction::triggered, + this, [this, filePath = selectedFiles[0]] () { + QDesktopServices::openUrl(QUrl::fromLocalFile(VUtils::basePathFromPath(filePath))); + }); + menu.addAction(openLocationAct); + + menu.addSeparator(); + } + + QAction *addToCartAct = new QAction(VIconUtils::menuIcon(":/resources/icons/cart.svg"), + tr("Add To Cart"), + &menu); + addToCartAct->setToolTip(tr("Add selected files to Cart for further processing")); + connect(addToCartAct, &QAction::triggered, + this, [this, selectedFiles]() { + VCart *cart = g_mainWin->getCart(); + for (int i = 0; i < selectedFiles.size(); ++i) { + cart->addFile(selectedFiles[i]); + } + + g_mainWin->showStatusMessage(tr("%1 %2 added to Cart") + .arg(selectedFiles.size()) + .arg(selectedFiles.size() > 1 ? tr("files") : tr("file"))); + }); + menu.addAction(addToCartAct); + + QAction *pinToHistoryAct = new QAction(VIconUtils::menuIcon(":/resources/icons/pin.svg"), + tr("Pin To History"), + &menu); + pinToHistoryAct->setToolTip(tr("Pin selected files to History")); + connect(pinToHistoryAct, &QAction::triggered, + this, [this, selectedFiles]() { + g_mainWin->getHistoryList()->pinFiles(selectedFiles); + g_mainWin->showStatusMessage(tr("%1 %2 pinned to History") + .arg(selectedFiles.size()) + .arg(selectedFiles.size() > 1 ? tr("files") : tr("file"))); + }); + menu.addAction(pinToHistoryAct); + + if (selectedFiles.size() == 1) { + QAction *fileInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/note_info.svg"), + tr("&Info\t%1").arg(VUtils::getShortcutText(c_infoShortcutSequence)), + &menu); + fileInfoAct->setToolTip(tr("View and edit current file's information")); + connect(fileInfoAct, &QAction::triggered, + this, [this, filePath = selectedFiles[0]]() { + renameFile(filePath); + }); + menu.addAction(fileInfoAct); + } + + menu.exec(m_tree->mapToGlobal(p_pos)); +} + +void VExplorer::openFiles(const QStringList &p_files, + OpenFileMode p_mode, + bool p_forceMode) +{ + if (!p_files.isEmpty()) { + Q_ASSERT(checkIndex()); + QString imgFolder = m_entries[m_index].m_imageFolder; + + QVector vfiles = g_mainWin->openFiles(p_files, false, p_mode, p_forceMode, false); + + // Set image folder. + for (auto it : vfiles) { + if (it->getType() == FileType::Orphan) { + static_cast(it)->setImageFolder(imgFolder); + } + } + } +} + +void VExplorer::resizeTreeToContents() +{ + m_tree->resizeColumnToContents(0); +} + +// 1. When only one folder is selected, create a file in that folder. +// 2. Otherwise, create a file in the root directory. +void VExplorer::newFile() +{ + Q_ASSERT(checkIndex()); + + QString parentDir; + + QFileSystemModel *model = static_cast(m_tree->model()); + QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows(); + if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) { + parentDir = model->filePath(selectedIdx[0]); + } else { + parentDir = m_entries[m_index].m_directory; + } + + qDebug() << "new file in" << parentDir; + + QString name = VUtils::promptForFileName(tr("New File"), + tr("File name (%1):").arg(parentDir), + "", + parentDir, + g_mainWin); + + if (name.isEmpty()) { + return; + } + + QDir paDir(parentDir); + QString filePath = paDir.filePath(name); + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly)) { + qWarning() << "Fail to create file" << filePath; + VUtils::showMessage(QMessageBox::Warning, + tr("New File"), + tr("Fail to create file %2.") + .arg(g_config->c_dataTextStyle) + .arg(filePath), + "", + QMessageBox::Ok, + QMessageBox::Ok, + g_mainWin); + return; + } + + file.close(); + + m_tree->setFocus(); + + // Select the file. + const QModelIndex fileIndex = model->index(filePath); + Q_ASSERT(fileIndex.isValid()); + m_tree->scrollTo(fileIndex); + m_tree->clearSelection(); + m_tree->setCurrentIndex(fileIndex); +} + +// 1. When only one folder is selected, create a folder in that folder. +// 2. Otherwise, create a folder in the root directory. +void VExplorer::newFolder() +{ + Q_ASSERT(checkIndex()); + + QString parentDir; + + QFileSystemModel *model = static_cast(m_tree->model()); + QModelIndexList selectedIdx = m_tree->selectionModel()->selectedRows(); + if (selectedIdx.size() == 1 && model->isDir(selectedIdx[0])) { + parentDir = model->filePath(selectedIdx[0]); + } else { + parentDir = m_entries[m_index].m_directory; + } + + qDebug() << "new folder in" << parentDir; + + QString name = VUtils::promptForFileName(tr("New Folder"), + tr("Folder name (%1):").arg(parentDir), + "", + parentDir, + g_mainWin); + + if (name.isEmpty()) { + return; + } + + QDir paDir(parentDir); + QString folderPath = paDir.filePath(name); + if (!paDir.mkdir(name)) { + qWarning() << "Fail to create folder" << folderPath; + VUtils::showMessage(QMessageBox::Warning, + tr("New Folder"), + tr("Fail to create folder %2.") + .arg(g_config->c_dataTextStyle) + .arg(folderPath), + "", + QMessageBox::Ok, + QMessageBox::Ok, + g_mainWin); + return; + } + + m_tree->setFocus(); + + // Select the folder. + const QModelIndex folderIndex = model->index(folderPath); + Q_ASSERT(folderIndex.isValid()); + m_tree->scrollTo(folderIndex); + m_tree->clearSelection(); + m_tree->setCurrentIndex(folderIndex); +} + +void VExplorer::renameFile(const QString &p_filePath) +{ + Q_ASSERT(checkIndex()); + + QFileInfo fi(p_filePath); + QString parentDir = fi.path(); + QString oldName = fi.fileName(); + + QString name = VUtils::promptForFileName(tr("File Information"), + tr("Rename file (%1):").arg(p_filePath), + oldName, + parentDir, + g_mainWin); + + if (name.isEmpty()) { + return; + } + + QDir paDir(parentDir); + if (!paDir.rename(oldName, name)) { + qWarning() << "Fail to rename" << p_filePath; + VUtils::showMessage(QMessageBox::Warning, + tr("File Information"), + tr("Fail to rename file %2.") + .arg(g_config->c_dataTextStyle) + .arg(p_filePath), + "", + QMessageBox::Ok, + QMessageBox::Ok, + g_mainWin); + return; + } +} diff --git a/src/vexplorer.h b/src/vexplorer.h new file mode 100644 index 00000000..b9ff6e91 --- /dev/null +++ b/src/vexplorer.h @@ -0,0 +1,95 @@ +#ifndef VEXPLORER_H +#define VEXPLORER_H + +#include +#include + +#include "vexplorerentry.h" +#include "vconstants.h" + +class QPushButton; +class QTreeView; +class QTreeWidgetItem; +class QShowEvent; +class QFocusEvent; +class QComboBox; +class VLineEdit; + +class VExplorer : public QWidget +{ + Q_OBJECT +public: + explicit VExplorer(QWidget *p_parent = nullptr); + +protected: + void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + + void focusInEvent(QFocusEvent *p_event) Q_DECL_OVERRIDE; + +private slots: + void handleEntryActivated(int p_idx); + + void handleContextMenuRequested(QPoint p_pos); + +private: + void setupUI(); + + void init(); + + void updateUI(); + + void updateDirectoryComboBox(); + + // Add an entry to m_entries with @p_dir being the path of the entry. + // Return the index of the corresponding entry if succeed; otherwise, + // return -1. + int addEntry(const QString &p_dir); + + void setCurrentEntry(int p_index); + + bool checkIndex() const; + + void updateExplorerEntryIndexInConfig(); + + void updateStarButton(); + + void updateTree(); + + void openFiles(const QStringList &p_files, + OpenFileMode p_mode, + bool p_forceMode = false); + + void resizeTreeToContents(); + + void newFile(); + + void newFolder(); + + void renameFile(const QString &p_filePath); + + bool m_initialized; + + bool m_uiInitialized; + + QVector m_entries; + + int m_index; + + QPushButton *m_openBtn; + QPushButton *m_openLocationBtn; + QPushButton *m_starBtn; + QComboBox *m_dirCB; + VLineEdit *m_imgFolderEdit; + QPushButton *m_newFileBtn; + QPushButton *m_newDirBtn; + QTreeView *m_tree; + + static const QString c_infoShortcutSequence; +}; + +inline bool VExplorer::checkIndex() const +{ + return m_index >= 0 && m_index < m_entries.size(); +} + +#endif // VEXPLORER_H diff --git a/src/vexplorerentry.h b/src/vexplorerentry.h new file mode 100644 index 00000000..939f3a48 --- /dev/null +++ b/src/vexplorerentry.h @@ -0,0 +1,51 @@ +#ifndef VEXPLORERENTRY_H +#define VEXPLORERENTRY_H + +#include + +namespace ExplorerConfig +{ + static const QString c_directory = "directory"; + static const QString c_imageFolder = "image_folder"; +} + +class VExplorerEntry +{ +public: + VExplorerEntry() + : m_isStarred(false) + { + } + + VExplorerEntry(const QString &p_directory, + const QString &p_imageFolder, + bool p_isStarred = false) + : m_directory(p_directory), + m_imageFolder(p_imageFolder), + m_isStarred(p_isStarred) + { + } + + static VExplorerEntry fromSettings(const QSettings *p_settings) + { + VExplorerEntry entry; + entry.m_directory = p_settings->value(ExplorerConfig::c_directory).toString(); + entry.m_imageFolder = p_settings->value(ExplorerConfig::c_imageFolder).toString(); + entry.m_isStarred = true; + return entry; + } + + void toSettings(QSettings *p_settings) const + { + p_settings->setValue(ExplorerConfig::c_directory, m_directory); + p_settings->setValue(ExplorerConfig::c_imageFolder, m_imageFolder); + } + + QString m_directory; + + QString m_imageFolder; + + bool m_isStarred; +}; + +#endif // VEXPLORERENTRY_H diff --git a/src/vfilelist.cpp b/src/vfilelist.cpp index 2ef94e00..12e38e23 100644 --- a/src/vfilelist.cpp +++ b/src/vfilelist.cpp @@ -609,8 +609,10 @@ void VFileList::contextMenuRequested(QPoint pos) if (item) { menu.addSeparator(); if (selectedSize == 1) { - QAction *openLocationAct = new QAction(tr("&Open Note Location"), &menu); - openLocationAct->setToolTip(tr("Open the folder containing this note in operating system")); + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("&Open Note Location"), + &menu); + openLocationAct->setToolTip(tr("Explore the folder containing this note in operating system")); connect(openLocationAct, &QAction::triggered, this, &VFileList::openFileLocation); menu.addAction(openLocationAct); @@ -1359,7 +1361,7 @@ void VFileList::sortFiles(QVector &p_files, ViewOrder p_order) case ViewOrder::NameReverse: reverse = true; - V_FALLTHROUGH + V_FALLTHROUGH; case ViewOrder::Name: std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) { if (reverse) { @@ -1372,7 +1374,7 @@ void VFileList::sortFiles(QVector &p_files, ViewOrder p_order) case ViewOrder::CreatedTimeReverse: reverse = true; - V_FALLTHROUGH + V_FALLTHROUGH; case ViewOrder::CreatedTime: std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) { if (reverse) { @@ -1385,7 +1387,7 @@ void VFileList::sortFiles(QVector &p_files, ViewOrder p_order) case ViewOrder::ModifiedTimeReverse: reverse = true; - V_FALLTHROUGH + V_FALLTHROUGH; case ViewOrder::ModifiedTime: std::sort(p_files.begin(), p_files.end(), [reverse](const VNoteFile *p_a, const VNoteFile *p_b) { if (reverse) { diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index dc1e1174..b2b38ee4 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -46,6 +46,7 @@ #include "vlistfolderue.h" #include "dialog/vfixnotebookdialog.h" #include "vhistorylist.h" +#include "vexplorer.h" extern VConfigManager *g_config; @@ -280,6 +281,11 @@ void VMainWindow::setupNaviBox() m_naviBox->addItem(m_historyList, ":/resources/icons/history.svg", tr("History")); + + m_explorer = new VExplorer(); + m_naviBox->addItem(m_explorer, + ":/resources/icons/explorer.svg", + tr("Explorer")); } void VMainWindow::setupNotebookPanel() @@ -930,7 +936,11 @@ void VMainWindow::initFileMenu() // Update lastPath lastPath = QFileInfo(files[0]).path(); - openFiles(VUtils::filterFilePathsToOpen(files)); + openFiles(VUtils::filterFilePathsToOpen(files), + false, + g_config->getNoteOpenMode(), + false, + false); }); fileMenu->addAction(openAct); @@ -2130,6 +2140,7 @@ void VMainWindow::saveStateAndGeometry() g_config->setSearchDockChecked(m_searchDock->isVisible()); g_config->setNotebookSplitterState(m_nbSplitter->saveState()); g_config->setMainSplitterState(m_mainSplitter->saveState()); + g_config->setNaviBoxCurrentIndex(m_naviBox->currentIndex()); } void VMainWindow::restoreStateAndGeometry() @@ -2156,6 +2167,8 @@ void VMainWindow::restoreStateAndGeometry() if (!nbSplitterState.isEmpty()) { m_nbSplitter->restoreState(nbSplitterState); } + + m_naviBox->setCurrentIndex(g_config->getNaviBoxCurrentIndex()); } void VMainWindow::handleCurrentDirectoryChanged(const VDirectory *p_dir) @@ -2457,12 +2470,15 @@ bool VMainWindow::tryOpenInternalFile(const QString &p_filePath) return false; } -void VMainWindow::openFiles(const QStringList &p_files, - bool p_forceOrphan, - OpenFileMode p_mode, - bool p_forceMode, - bool p_oneByOne) +QVector VMainWindow::openFiles(const QStringList &p_files, + bool p_forceOrphan, + OpenFileMode p_mode, + bool p_forceMode, + bool p_oneByOne) { + QVector vfiles; + vfiles.reserve(p_files.size()); + for (int i = 0; i < p_files.size(); ++i) { VFile *file = NULL; if (!p_forceOrphan) { @@ -2474,10 +2490,14 @@ void VMainWindow::openFiles(const QStringList &p_files, } m_editArea->openFile(file, p_mode, p_forceMode); + vfiles.append(file); + if (p_oneByOne) { QCoreApplication::sendPostedEvents(); } } + + return vfiles; } void VMainWindow::editOrphanFileInfo(VFile *p_file) @@ -2497,7 +2517,7 @@ void VMainWindow::checkSharedMemory() QStringList files = m_guard->fetchFilesToOpen(); if (!files.isEmpty()) { qDebug() << "shared memory fetch files" << files; - openFiles(files); + openFiles(files, false, g_config->getNoteOpenMode(), false, false); // Eliminate the signal. m_guard->fetchAskedToShow(); @@ -2579,7 +2599,7 @@ void VMainWindow::openStartupPages() { QStringList pagesToOpen = VUtils::filterFilePathsToOpen(g_config->getStartupPages()); qDebug() << "open startup pages" << pagesToOpen; - openFiles(pagesToOpen, false, OpenFileMode::Read, false, true); + openFiles(pagesToOpen, false, g_config->getNoteOpenMode(), false, true); break; } @@ -3149,7 +3169,7 @@ void VMainWindow::kickOffStartUpTimer(const QStringList &p_files) promptNewNotebookIfEmpty(); QCoreApplication::sendPostedEvents(); openStartupPages(); - openFiles(p_files, false, OpenFileMode::Read, false, true); + openFiles(p_files, false, g_config->getNoteOpenMode(), false, true); }); } diff --git a/src/vmainwindow.h b/src/vmainwindow.h index 86159688..67d3412a 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -43,6 +43,7 @@ class VSearcher; class QPrinter; class VUniversalEntry; class VHistoryList; +class VExplorer; enum class PanelViewState { @@ -88,11 +89,11 @@ public: // Open files @p_files as orphan files or internal note files. // If @p_forceOrphan is false, for each file, VNote will try to find out if // it is a note inside VNote. If yes, VNote will open it as internal file. - void openFiles(const QStringList &p_files, - bool p_forceOrphan = false, - OpenFileMode p_mode = OpenFileMode::Read, - bool p_forceMode = false, - bool p_oneByOne = false); + QVector openFiles(const QStringList &p_files, + bool p_forceOrphan = false, + OpenFileMode p_mode = OpenFileMode::Read, + bool p_forceMode = false, + bool p_oneByOne = false); // Try to open @p_filePath as internal note. bool tryOpenInternalFile(const QString &p_filePath); @@ -449,6 +450,8 @@ private: VHistoryList *m_historyList; + VExplorer *m_explorer; + // Interval of the shared memory timer in ms. static const int c_sharedMemTimerInterval; }; diff --git a/src/vnote.qrc b/src/vnote.qrc index 32ba666f..9061676c 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -218,5 +218,10 @@ resources/icons/clear_history.svg resources/icons/pin.svg resources/icons/view.svg + resources/icons/explorer.svg + resources/icons/star.svg + resources/icons/open_location.svg + resources/icons/unstar.svg + resources/icons/explore_root.svg diff --git a/src/vnotebookselector.cpp b/src/vnotebookselector.cpp index 129013a0..4b786e8d 100644 --- a/src/vnotebookselector.cpp +++ b/src/vnotebookselector.cpp @@ -50,112 +50,10 @@ VNotebookSelector::VNotebookSelector(QWidget *p_parent) m_listWidget->viewport()->installEventFilter(this); m_listWidget->installEventFilter(this); - initActions(); - connect(this, SIGNAL(currentIndexChanged(int)), this, SLOT(handleCurIndexChanged(int))); } -void VNotebookSelector::initActions() -{ - m_deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"), - tr("&Delete"), this); - m_deleteNotebookAct->setToolTip(tr("Delete current notebook")); - connect(m_deleteNotebookAct, SIGNAL(triggered(bool)), - this, SLOT(deleteNotebook())); - - m_notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"), - tr("&Info"), this); - m_notebookInfoAct->setToolTip(tr("View and edit current notebook's information")); - connect(m_notebookInfoAct, SIGNAL(triggered(bool)), - this, SLOT(editNotebookInfo())); - - m_openLocationAct = new QAction(tr("&Open Notebook Location"), this); - m_openLocationAct->setToolTip(tr("Open the root folder of this notebook in operating system")); - connect(m_openLocationAct, &QAction::triggered, - this, [this]() { - QList items = this->m_listWidget->selectedItems(); - if (items.isEmpty()) { - return; - } - - Q_ASSERT(items.size() == 1); - VNotebook *notebook = getNotebook(items[0]); - QUrl url = QUrl::fromLocalFile(notebook->getPath()); - QDesktopServices::openUrl(url); - }); - - m_recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"), - tr("&Recycle Bin"), this); - m_recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook")); - connect(m_recycleBinAct, &QAction::triggered, - this, [this]() { - QList items = this->m_listWidget->selectedItems(); - if (items.isEmpty()) { - return; - } - - Q_ASSERT(items.size() == 1); - VNotebook *notebook = getNotebook(items[0]); - QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath()); - QDesktopServices::openUrl(url); - }); - - m_emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"), - tr("&Empty Recycle Bin"), this); - m_emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook")); - connect(m_emptyRecycleBinAct, &QAction::triggered, - this, [this]() { - QList items = this->m_listWidget->selectedItems(); - if (items.isEmpty()) { - return; - } - - Q_ASSERT(items.size() == 1); - VNotebook *notebook = getNotebook(items[0]); - QString binPath = notebook->getRecycleBinFolderPath(); - - int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"), - tr("Are you sure to empty recycle bin of notebook " - "%2?") - .arg(g_config->c_dataTextStyle) - .arg(notebook->getName()), - tr("WARNING: " - "VNote will delete all the files in directory " - "%3." - "
It may be UNRECOVERABLE!") - .arg(g_config->c_warningTextStyle) - .arg(g_config->c_dataTextStyle) - .arg(binPath), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Ok, - this, - MessageBoxType::Danger); - if (ret == QMessageBox::Ok) { - QString info; - if (VUtils::emptyDirectory(notebook, binPath, true)) { - info = tr("Successfully emptied recycle bin of notebook " - "%2!") - .arg(g_config->c_dataTextStyle) - .arg(notebook->getName()); - } else { - info = tr("Fail to empty recycle bin of notebook " - "%2!") - .arg(g_config->c_dataTextStyle) - .arg(notebook->getName()); - } - - VUtils::showMessage(QMessageBox::Information, - tr("Information"), - info, - "", - QMessageBox::Ok, - QMessageBox::Ok, - this); - } - }); -} - void VNotebookSelector::updateComboBox() { m_muted = true; @@ -479,18 +377,122 @@ void VNotebookSelector::popupListContextMenuRequested(QPoint p_pos) QMenu menu(this); menu.setToolTipsVisible(true); - menu.addAction(m_deleteNotebookAct); + + QAction *deleteNotebookAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/delete_notebook.svg"), + tr("&Delete"), + &menu); + deleteNotebookAct->setToolTip(tr("Delete current notebook")); + connect(deleteNotebookAct, SIGNAL(triggered(bool)), + this, SLOT(deleteNotebook())); + menu.addAction(deleteNotebookAct); + if (nb->isValid()) { menu.addSeparator(); - menu.addAction(m_recycleBinAct); - menu.addAction(m_emptyRecycleBinAct); + + QAction *recycleBinAct = new QAction(VIconUtils::menuIcon(":/resources/icons/recycle_bin.svg"), + tr("&Recycle Bin"), + &menu); + recycleBinAct->setToolTip(tr("Open the recycle bin of this notebook")); + connect(recycleBinAct, &QAction::triggered, + this, [this]() { + QList items = this->m_listWidget->selectedItems(); + if (items.isEmpty()) { + return; + } + + Q_ASSERT(items.size() == 1); + VNotebook *notebook = getNotebook(items[0]); + QUrl url = QUrl::fromLocalFile(notebook->getRecycleBinFolderPath()); + QDesktopServices::openUrl(url); + }); + + menu.addAction(recycleBinAct); + + QAction *emptyRecycleBinAct = new QAction(VIconUtils::menuDangerIcon(":/resources/icons/empty_recycle_bin.svg"), + tr("&Empty Recycle Bin"), + &menu); + emptyRecycleBinAct->setToolTip(tr("Empty the recycle bin of this notebook")); + connect(emptyRecycleBinAct, &QAction::triggered, + this, [this]() { + QList items = this->m_listWidget->selectedItems(); + if (items.isEmpty()) { + return; + } + + Q_ASSERT(items.size() == 1); + VNotebook *notebook = getNotebook(items[0]); + QString binPath = notebook->getRecycleBinFolderPath(); + + int ret = VUtils::showMessage(QMessageBox::Warning, tr("Warning"), + tr("Are you sure to empty recycle bin of notebook " + "%2?") + .arg(g_config->c_dataTextStyle) + .arg(notebook->getName()), + tr("WARNING: " + "VNote will delete all the files in directory " + "%3." + "
It may be UNRECOVERABLE!") + .arg(g_config->c_warningTextStyle) + .arg(g_config->c_dataTextStyle) + .arg(binPath), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Ok, + this, + MessageBoxType::Danger); + if (ret == QMessageBox::Ok) { + QString info; + if (VUtils::emptyDirectory(notebook, binPath, true)) { + info = tr("Successfully emptied recycle bin of notebook " + "%2!") + .arg(g_config->c_dataTextStyle) + .arg(notebook->getName()); + } else { + info = tr("Fail to empty recycle bin of notebook " + "%2!") + .arg(g_config->c_dataTextStyle) + .arg(notebook->getName()); + } + + VUtils::showMessage(QMessageBox::Information, + tr("Information"), + info, + "", + QMessageBox::Ok, + QMessageBox::Ok, + this); + } + }); + menu.addAction(emptyRecycleBinAct); } menu.addSeparator(); - menu.addAction(m_openLocationAct); + + QAction *openLocationAct = new QAction(VIconUtils::menuIcon(":/resources/icons/open_location.svg"), + tr("&Open Notebook Location"), + &menu); + openLocationAct->setToolTip(tr("Explore the root folder of this notebook in operating system")); + connect(openLocationAct, &QAction::triggered, + this, [this]() { + QList items = this->m_listWidget->selectedItems(); + if (items.isEmpty()) { + return; + } + + Q_ASSERT(items.size() == 1); + VNotebook *notebook = getNotebook(items[0]); + QUrl url = QUrl::fromLocalFile(notebook->getPath()); + QDesktopServices::openUrl(url); + }); + menu.addAction(openLocationAct); if (nb->isValid()) { - menu.addAction(m_notebookInfoAct); + QAction *notebookInfoAct = new QAction(VIconUtils::menuIcon(":/resources/icons/notebook_info.svg"), + tr("&Info"), + &menu); + notebookInfoAct->setToolTip(tr("View and edit current notebook's information")); + connect(notebookInfoAct, SIGNAL(triggered(bool)), + this, SLOT(editNotebookInfo())); + menu.addAction(notebookInfoAct); } menu.exec(m_listWidget->mapToGlobal(p_pos)); diff --git a/src/vnotebookselector.h b/src/vnotebookselector.h index 6f3cf889..43985a2c 100644 --- a/src/vnotebookselector.h +++ b/src/vnotebookselector.h @@ -8,7 +8,6 @@ class VNotebook; class QListWidget; -class QAction; class QListWidgetItem; class QLabel; @@ -66,8 +65,6 @@ private slots: void editNotebookInfo(); private: - void initActions(); - // Update Combox from m_notebooks. void updateComboBox(); @@ -115,13 +112,6 @@ private: // Whether it is muted from currentIndexChanged(). bool m_muted; - // Actions - QAction *m_deleteNotebookAct; - QAction *m_notebookInfoAct; - QAction *m_openLocationAct; - QAction *m_recycleBinAct; - QAction *m_emptyRecycleBinAct; - QLabel *m_naviLabel; }; diff --git a/src/vsearcher.cpp b/src/vsearcher.cpp index 0468cadd..3f6135ce 100644 --- a/src/vsearcher.cpp +++ b/src/vsearcher.cpp @@ -25,36 +25,23 @@ extern VConfigManager *g_config; VSearcher::VSearcher(QWidget *p_parent) : QWidget(p_parent), + m_initialized(false), + m_uiInitialized(false), m_inSearch(false), m_askedToStop(false), m_search(this) { qRegisterMetaType>>("QList>"); - - setupUI(); - - initUIFields(); - - handleInputChanged(); - - connect(&m_search, &VSearch::resultItemAdded, - this, [this](const QSharedPointer &p_item) { - // Not sure if it works. - QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease); - m_results->addResultItem(p_item); - }); - connect(&m_search, &VSearch::resultItemsAdded, - this, [this](const QList > &p_items) { - // Not sure if it works. - QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease); - m_results->addResultItems(p_items); - }); - connect(&m_search, &VSearch::finished, - this, &VSearcher::handleSearchFinished); } void VSearcher::setupUI() { + if (m_uiInitialized) { + return; + } + + m_uiInitialized = true; + // Search button. m_searchBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/search.svg"), "", this); m_searchBtn->setToolTip(tr("Search")); @@ -112,6 +99,7 @@ void VSearcher::setupUI() m_keywordCB->setLineEdit(new VLineEdit(this)); m_keywordCB->setToolTip(tr("Keywords to search for")); m_keywordCB->lineEdit()->setPlaceholderText(tr("Supports space, &&, and ||")); + m_keywordCB->lineEdit()->setProperty("EmbeddedEdit", true); connect(m_keywordCB, &QComboBox::currentTextChanged, this, &VSearcher::handleInputChanged); connect(m_keywordCB->lineEdit(), &QLineEdit::returnPressed, @@ -141,6 +129,7 @@ void VSearcher::setupUI() m_filePatternCB->setEditable(true); m_filePatternCB->setLineEdit(new VLineEdit(this)); m_filePatternCB->setToolTip(tr("Wildcard pattern to filter the files to be searched")); + m_filePatternCB->lineEdit()->setProperty("EmbeddedEdit", true); m_filePatternCB->completer()->setCaseSensitivity(Qt::CaseSensitive); // Engine. @@ -544,19 +533,62 @@ void VSearcher::updateNumLabel(int p_count) void VSearcher::focusToSearch() { + init(); + m_keywordCB->setFocus(Qt::OtherFocusReason); } void VSearcher::showNavigation() { + setupUI(); + VNavigationMode::showNavigation(m_results); } bool VSearcher::handleKeyNavigation(int p_key, bool &p_succeed) { static bool secondKey = false; + setupUI(); + return VNavigationMode::handleKeyNavigation(m_results, secondKey, p_key, p_succeed); } + +void VSearcher::init() +{ + if (m_initialized) { + return; + } + + m_initialized = true; + + setupUI(); + + initUIFields(); + + handleInputChanged(); + + connect(&m_search, &VSearch::resultItemAdded, + this, [this](const QSharedPointer &p_item) { + // Not sure if it works. + QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease); + m_results->addResultItem(p_item); + }); + connect(&m_search, &VSearch::resultItemsAdded, + this, [this](const QList > &p_items) { + // Not sure if it works. + QCoreApplication::sendPostedEvents(NULL, QEvent::MouseButtonRelease); + m_results->addResultItems(p_items); + }); + connect(&m_search, &VSearch::finished, + this, &VSearcher::handleSearchFinished); +} + +void VSearcher::showEvent(QShowEvent *p_event) +{ + init(); + + QWidget::showEvent(p_event); +} diff --git a/src/vsearcher.h b/src/vsearcher.h index 6f8c231b..a7d7c999 100644 --- a/src/vsearcher.h +++ b/src/vsearcher.h @@ -15,6 +15,7 @@ class QLabel; class VSearchResultTree; class QProgressBar; class QPlainTextEdit; +class QShowEvent; class VSearcher : public QWidget, public VNavigationMode { @@ -28,6 +29,9 @@ public: void showNavigation() Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; +protected: + void showEvent(QShowEvent *p_event) Q_DECL_OVERRIDE; + private slots: void handleSearchFinished(const QSharedPointer &p_result); @@ -36,6 +40,8 @@ private: void setupUI(); + void init(); + void initUIFields(); void setProgressVisible(bool p_visible); @@ -98,6 +104,10 @@ private: QPlainTextEdit *m_consoleEdit; + bool m_initialized; + + bool m_uiInitialized; + bool m_inSearch; bool m_askedToStop; diff --git a/src/vtoolbox.cpp b/src/vtoolbox.cpp index 1fc0a389..59df5991 100644 --- a/src/vtoolbox.cpp +++ b/src/vtoolbox.cpp @@ -86,7 +86,11 @@ int VToolBox::addItem(QWidget *p_widget, void VToolBox::setCurrentIndex(int p_idx, bool p_focus) { if (p_idx < 0 || p_idx >= m_items.size()) { - m_currentIndex = -1; + if (m_items.isEmpty()) { + m_currentIndex = -1; + } else { + m_currentIndex = 0; + } } else { m_currentIndex = p_idx; } diff --git a/src/vtoolbox.h b/src/vtoolbox.h index 533f342a..65d13119 100644 --- a/src/vtoolbox.h +++ b/src/vtoolbox.h @@ -27,6 +27,8 @@ public: void setCurrentWidget(QWidget *p_widget, bool p_focus = true); + int currentIndex() const; + // Implementations for VNavigationMode. void showNavigation() Q_DECL_OVERRIDE; bool handleKeyNavigation(int p_key, bool &p_succeed) Q_DECL_OVERRIDE; @@ -78,4 +80,9 @@ private: QVector m_items; }; +inline int VToolBox::currentIndex() const +{ + return m_currentIndex; +} + #endif // VTOOLBOX_H diff --git a/src/vtreewidget.cpp b/src/vtreewidget.cpp index d98c7886..e635b120 100644 --- a/src/vtreewidget.cpp +++ b/src/vtreewidget.cpp @@ -53,6 +53,12 @@ VTreeWidget::VTreeWidget(QWidget *p_parent) resizeColumnToContents(0); } }); + connect(this, &VTreeWidget::itemCollapsed, + this, [this]() { + if (m_fitContent) { + resizeColumnToContents(0); + } + }); } void VTreeWidget::keyPressEvent(QKeyEvent *p_event)