diff --git a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp index ac1f86da61..9551a6b2d1 100644 --- a/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp +++ b/src/core/notebookconfigmgr/vxnotebookconfigmgr.cpp @@ -461,6 +461,10 @@ void VXNotebookConfigMgr::addChildNode(Node *p_parent, const QSharedPointer VXNotebookConfigMgr::loadNodeByPath(const QSharedPointer &p_root, const QString &p_relativePath) { auto p = PathUtils::cleanPath(p_relativePath); + if (p == ".") { + return p_root; + } + auto paths = p.split('/', QString::SkipEmptyParts); auto node = p_root; for (auto &pa : paths) { diff --git a/src/core/sessionconfig.cpp b/src/core/sessionconfig.cpp index 8345a0cb37..4386112e1f 100644 --- a/src/core/sessionconfig.cpp +++ b/src/core/sessionconfig.cpp @@ -38,6 +38,34 @@ QJsonObject SessionConfig::NotebookItem::toJson() const return jobj; } +bool SessionConfig::QuickNoteScheme::operator==(const QuickNoteScheme &p_other) const +{ + return m_name == p_other.m_name && + m_folderPath == p_other.m_folderPath && + m_noteName == p_other.m_noteName && + m_template == p_other.m_template; +} + +void SessionConfig::QuickNoteScheme::fromJson(const QJsonObject &p_jobj) +{ + m_name = p_jobj[QStringLiteral("name")].toString(); + m_folderPath = p_jobj[QStringLiteral("folder_path")].toString(); + m_noteName = p_jobj[QStringLiteral("note_name")].toString(); + m_template = p_jobj[QStringLiteral("template")].toString(); +} + +QJsonObject SessionConfig::QuickNoteScheme::toJson() const +{ + QJsonObject jobj; + + jobj[QStringLiteral("name")] = m_name; + jobj[QStringLiteral("folder_path")] = m_folderPath; + jobj[QStringLiteral("note_name")] = m_noteName; + jobj[QStringLiteral("template")] = m_template; + + return jobj; +} + void SessionConfig::ExternalProgram::fromJson(const QJsonObject &p_jobj) { m_name = p_jobj[QStringLiteral("name")].toString(); @@ -97,6 +125,8 @@ void SessionConfig::init() loadHistory(sessionJobj); + loadQuickNoteSchemes(sessionJobj); + if (MainConfig::isVersionChanged()) { doVersionSpecificOverride(); } @@ -235,6 +265,7 @@ QJsonObject SessionConfig::toJson() const writeByteArray(obj, QStringLiteral("notebook_explorer_session"), m_notebookExplorerSession); obj[QStringLiteral("external_programs")] = saveExternalPrograms(); obj[QStringLiteral("history")] = saveHistory(); + obj[QStringLiteral("quick_note_schemes")] = saveQuickNoteSchemes(); return obj; } @@ -458,6 +489,24 @@ QJsonArray SessionConfig::saveExternalPrograms() const return arr; } +void SessionConfig::loadQuickNoteSchemes(const QJsonObject &p_session) +{ + const auto arr = p_session.value(QStringLiteral("quick_note_schemes")).toArray(); + m_quickNoteSchemes.resize(arr.size()); + for (int i = 0; i < arr.size(); ++i) { + m_quickNoteSchemes[i].fromJson(arr[i].toObject()); + } +} + +QJsonArray SessionConfig::saveQuickNoteSchemes() const +{ + QJsonArray arr; + for (const auto &scheme : m_quickNoteSchemes) { + arr.append(scheme.toJson()); + } + return arr; +} + const QVector &SessionConfig::getExternalPrograms() const { return m_externalPrograms; @@ -541,3 +590,13 @@ QJsonObject SessionConfig::saveExportOption() const return obj; } + +const QVector &SessionConfig::getQuickNoteSchemes() const +{ + return m_quickNoteSchemes; +} + +void SessionConfig::setQuickNoteSchemes(const QVector& p_schemes) +{ + updateConfig(m_quickNoteSchemes, p_schemes, this); +} diff --git a/src/core/sessionconfig.h b/src/core/sessionconfig.h index a30903075a..6a854d86e0 100644 --- a/src/core/sessionconfig.h +++ b/src/core/sessionconfig.h @@ -55,6 +55,25 @@ namespace vnotex QByteArray m_locationListState; }; + struct QuickNoteScheme + { + bool operator==(const QuickNoteScheme &p_other) const; + + void fromJson(const QJsonObject &p_jobj); + + QJsonObject toJson() const; + + QString m_name; + + // Where to create the quick note. + QString m_folderPath; + + // Name of the quick note. Snippet is supported. + QString m_noteName; + + QString m_template; + }; + enum OpenGL { None, @@ -149,6 +168,9 @@ namespace vnotex void removeHistory(const QString &p_itemPath); void clearHistory(); + const QVector &getQuickNoteSchemes() const; + void setQuickNoteSchemes(const QVector& p_schemes); + private: void loadCore(const QJsonObject &p_session); @@ -166,6 +188,10 @@ namespace vnotex QJsonArray saveExternalPrograms() const; + void loadQuickNoteSchemes(const QJsonObject &p_session); + + QJsonArray saveQuickNoteSchemes() const; + void doVersionSpecificOverride(); void loadHistory(const QJsonObject &p_session); @@ -215,7 +241,9 @@ namespace vnotex QVector m_history; // Default folder path to open for external media like images and files. - QString m_externalMediaDefaultPath;; + QString m_externalMediaDefaultPath; + + QVector m_quickNoteSchemes; }; } // ns vnotex diff --git a/src/core/templatemgr.cpp b/src/core/templatemgr.cpp index 2b618b4417..0ddc082a86 100644 --- a/src/core/templatemgr.cpp +++ b/src/core/templatemgr.cpp @@ -2,6 +2,8 @@ #include +#include + #include "configmgr.h" using namespace vnotex; @@ -20,5 +22,17 @@ QStringList TemplateMgr::getTemplates() const QString TemplateMgr::getTemplateFilePath(const QString &p_name) const { + if (p_name.isEmpty()) { + return QString(); + } return QDir(getTemplateFolder()).filePath(p_name); } + +QString TemplateMgr::getTemplateContent(const QString &p_name) const +{ + const auto filePath = getTemplateFilePath(p_name); + if (filePath.isEmpty()) { + return QString(); + } + return FileUtils::readTextFile(filePath); +} diff --git a/src/core/templatemgr.h b/src/core/templatemgr.h index aa1fd7704e..9707dc1f33 100644 --- a/src/core/templatemgr.h +++ b/src/core/templatemgr.h @@ -24,6 +24,8 @@ namespace vnotex QString getTemplateFilePath(const QString &p_name) const; + QString getTemplateContent(const QString &p_name) const; + private: TemplateMgr() = default; }; diff --git a/src/core/vnotex.h b/src/core/vnotex.h index 941f4bef76..fa3a4977d3 100644 --- a/src/core/vnotex.h +++ b/src/core/vnotex.h @@ -79,6 +79,9 @@ namespace vnotex // The handler should determine in which folder this note belongs to. void newNoteRequested(); + // Requested to new a quick note (maybe in current folder). + void newQuickNoteRequested(); + // Requested to new a folder in current notebook. void newFolderRequested(); diff --git a/src/widgets/dialogs/newnotedialog.cpp b/src/widgets/dialogs/newnotedialog.cpp index 0d5b36dcc3..953463a998 100644 --- a/src/widgets/dialogs/newnotedialog.cpp +++ b/src/widgets/dialogs/newnotedialog.cpp @@ -14,6 +14,7 @@ #include #include "exception.h" #include "nodeinfowidget.h" +#include "notetemplateselector.h" #include #include #include @@ -43,27 +44,8 @@ void NewNoteDialog::setupUI(const Node *p_node) auto infoLayout = m_infoWidget->getMainLayout(); - { - auto templateLayout = new QHBoxLayout(); - templateLayout->setContentsMargins(0, 0, 0, 0); - infoLayout->addRow(tr("Template:"), templateLayout); - - setupTemplateComboBox(m_infoWidget); - templateLayout->addWidget(m_templateComboBox); - - templateLayout->addStretch(); - - auto manageBtn = new QPushButton(tr("Manage"), m_infoWidget); - templateLayout->addWidget(manageBtn); - connect(manageBtn, &QPushButton::clicked, - this, []() { - WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(TemplateMgr::getInst().getTemplateFolder())); - }); - - m_templateTextEdit = WidgetsFactory::createPlainTextConsole(m_infoWidget); - infoLayout->addRow("", m_templateTextEdit); - m_templateTextEdit->hide(); - } + m_templateSelector = new NoteTemplateSelector(m_infoWidget); + infoLayout->addRow(tr("Template:"), m_templateSelector); setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); @@ -106,41 +88,56 @@ bool NewNoteDialog::validateNameInput(QString &p_msg) void NewNoteDialog::acceptedButtonClicked() { - s_lastTemplate = m_templateComboBox->currentData().toString(); + s_lastTemplate = m_templateSelector->getCurrentTemplate(); { auto fileType = FileTypeHelper::getInst().getFileTypeByName(m_infoWidget->getFileType()).m_type; ConfigMgr::getInst().getWidgetConfig().setNewNoteDefaultFileType(static_cast(fileType)); } - if (validateInputs() && newNote()) { + if (validateInputs()) { + Notebook *notebook = const_cast(m_infoWidget->getNotebook()); + Node *parentNode = const_cast(m_infoWidget->getParentNode()); + QString errMsg; + m_newNode = newNote(notebook, + parentNode, + m_infoWidget->getName(), + m_templateSelector->getTemplateContent(), + errMsg); + if (!m_newNode) { + setInformationText(errMsg, ScrollDialog::InformationLevel::Error); + return; + } accept(); } } -bool NewNoteDialog::newNote() +QSharedPointer NewNoteDialog::newNote(Notebook *p_notebook, + Node *p_parentNode, + const QString &p_name, + const QString &p_templateContent, + QString &p_errMsg) { - m_newNode.clear(); + Q_ASSERT(p_notebook && p_parentNode); + + QSharedPointer newNode; + p_errMsg.clear(); - Notebook *notebook = const_cast(m_infoWidget->getNotebook()); - Node *parentNode = const_cast(m_infoWidget->getParentNode()); try { - m_newNode = notebook->newNode(parentNode, + newNode = p_notebook->newNode(p_parentNode, Node::Flag::Content, - m_infoWidget->getName(), - getTemplateContent()); + p_name, + evaluateTemplateContent(p_templateContent, p_name)); } catch (Exception &p_e) { - QString msg = tr("Failed to create note under (%1) in (%2) (%3).").arg(parentNode->getName(), - notebook->getName(), - p_e.what()); - qCritical() << msg; - setInformationText(msg, ScrollDialog::InformationLevel::Error); - return false; + p_errMsg = tr("Failed to create note under (%1) in (%2) (%3).").arg(p_parentNode->getName(), + p_notebook->getName(), + p_e.what()); + qCritical() << p_errMsg; + return nullptr; } - emit notebook->nodeUpdated(m_newNode.data()); - - return true; + emit p_notebook->nodeUpdated(newNode.data()); + return newNode; } const QSharedPointer &NewNoteDialog::getNewNode() const @@ -166,66 +163,17 @@ void NewNoteDialog::initDefaultValues(const Node *p_node) if (!s_lastTemplate.isEmpty()) { // Restore. - int idx = m_templateComboBox->findData(s_lastTemplate); - if (idx != -1) { - m_templateComboBox->setCurrentIndex(idx); - } else { + if (!m_templateSelector->setCurrentTemplate(s_lastTemplate)) { s_lastTemplate.clear(); } } } -void NewNoteDialog::setupTemplateComboBox(QWidget *p_parent) -{ - m_templateComboBox = WidgetsFactory::createComboBox(p_parent); - - // None. - m_templateComboBox->addItem(tr("None"), ""); - - int idx = 1; - auto templates = TemplateMgr::getInst().getTemplates(); - for (const auto &temp : templates) { - m_templateComboBox->addItem(temp, temp); - m_templateComboBox->setItemData(idx++, temp, Qt::ToolTipRole); - } - - m_templateComboBox->setCurrentIndex(0); - - connect(m_templateComboBox, QOverload::of(&QComboBox::currentIndexChanged), - this, &NewNoteDialog::updateCurrentTemplate); -} - -QString NewNoteDialog::getTemplateContent() const +QString NewNoteDialog::evaluateTemplateContent(const QString &p_content, const QString &p_name) { int cursorOffset = 0; - return SnippetMgr::getInst().applySnippetBySymbol(m_templateContent, + return SnippetMgr::getInst().applySnippetBySymbol(p_content, QString(), cursorOffset, - SnippetMgr::generateOverrides(m_infoWidget->getName())); -} - -void NewNoteDialog::updateCurrentTemplate() -{ - m_templateContent.clear(); - m_templateTextEdit->clear(); - - auto temp = m_templateComboBox->currentData().toString(); - if (temp.isEmpty()) { - m_templateTextEdit->hide(); - return; - } - - const auto filePath = TemplateMgr::getInst().getTemplateFilePath(temp); - try { - m_templateContent = FileUtils::readTextFile(filePath); - m_templateTextEdit->setPlainText(m_templateContent); - m_templateTextEdit->show(); - } catch (Exception &p_e) { - m_templateTextEdit->hide(); - - QString msg = tr("Failed to load template (%1) (%2).") - .arg(filePath, p_e.what()); - qCritical() << msg; - setInformationText(msg, ScrollDialog::InformationLevel::Error); - } + SnippetMgr::generateOverrides(p_name)); } diff --git a/src/widgets/dialogs/newnotedialog.h b/src/widgets/dialogs/newnotedialog.h index 8d6f7b6992..fae6e4b151 100644 --- a/src/widgets/dialogs/newnotedialog.h +++ b/src/widgets/dialogs/newnotedialog.h @@ -3,14 +3,12 @@ #include "scrolldialog.h" -class QComboBox; -class QPlainTextEdit; - namespace vnotex { class Notebook; class Node; class NodeInfoWidget; + class NoteTemplateSelector; class NewNoteDialog : public ScrollDialog { @@ -21,6 +19,12 @@ namespace vnotex const QSharedPointer &getNewNode() const; + static QSharedPointer newNote(Notebook *p_notebook, + Node *p_parentNode, + const QString &p_name, + const QString &p_templateContent, + QString &p_errMsg); + protected: void acceptedButtonClicked() Q_DECL_OVERRIDE; @@ -29,27 +33,17 @@ namespace vnotex void setupNodeInfoWidget(const Node *p_node, QWidget *p_parent); - void setupTemplateComboBox(QWidget *p_parent); - bool validateInputs(); bool validateNameInput(QString &p_msg); - bool newNote(); - void initDefaultValues(const Node *p_node); - QString getTemplateContent() const; - - void updateCurrentTemplate(); + static QString evaluateTemplateContent(const QString &p_content, const QString &p_name); NodeInfoWidget *m_infoWidget = nullptr; - QComboBox *m_templateComboBox = nullptr; - - QPlainTextEdit *m_templateTextEdit = nullptr; - - QString m_templateContent; + NoteTemplateSelector *m_templateSelector = nullptr; QSharedPointer m_newNode; diff --git a/src/widgets/dialogs/notetemplateselector.cpp b/src/widgets/dialogs/notetemplateselector.cpp new file mode 100644 index 0000000000..21e00b7a50 --- /dev/null +++ b/src/widgets/dialogs/notetemplateselector.cpp @@ -0,0 +1,112 @@ +#include "notetemplateselector.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace vnotex; + +NoteTemplateSelector::NoteTemplateSelector(QWidget *p_parent) + : QWidget(p_parent) +{ + setupUI(); +} + +void NoteTemplateSelector::setupUI() +{ + auto mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + + auto selectorLayout = new QHBoxLayout(); + mainLayout->addLayout(selectorLayout); + + setupTemplateComboBox(this); + selectorLayout->addWidget(m_templateComboBox, 1); + + auto manageBtn = new QPushButton(tr("Manage"), this); + selectorLayout->addWidget(manageBtn); + connect(manageBtn, &QPushButton::clicked, + this, []() { + WidgetUtils::openUrlByDesktop(QUrl::fromLocalFile(TemplateMgr::getInst().getTemplateFolder())); + }); + + m_templateTextEdit = WidgetsFactory::createPlainTextConsole(this); + mainLayout->addWidget(m_templateTextEdit); + m_templateTextEdit->hide(); +} + +void NoteTemplateSelector::setupTemplateComboBox(QWidget *p_parent) +{ + m_templateComboBox = WidgetsFactory::createComboBox(p_parent); + + // None. + m_templateComboBox->addItem(tr("None"), ""); + + int idx = 1; + auto templates = TemplateMgr::getInst().getTemplates(); + for (const auto &temp : templates) { + m_templateComboBox->addItem(temp, temp); + m_templateComboBox->setItemData(idx++, temp, Qt::ToolTipRole); + } + + m_templateComboBox->setCurrentIndex(0); + + connect(m_templateComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &NoteTemplateSelector::updateCurrentTemplate); +} + +void NoteTemplateSelector::updateCurrentTemplate() +{ + m_templateContent.clear(); + m_templateTextEdit->clear(); + + auto templateName = m_templateComboBox->currentData().toString(); + if (templateName.isEmpty()) { + m_templateTextEdit->hide(); + emit templateChanged(); + return; + } + + const auto filePath = TemplateMgr::getInst().getTemplateFilePath(templateName); + try { + m_templateContent = FileUtils::readTextFile(filePath); + m_templateTextEdit->setPlainText(m_templateContent); + } catch (Exception &p_e) { + QString msg = tr("Failed to load template (%1) (%2).") + .arg(filePath, p_e.what()); + qCritical() << msg; + m_templateTextEdit->setPlainText(msg); + } + m_templateTextEdit->show(); + emit templateChanged(); +} + +QString NoteTemplateSelector::getCurrentTemplate() const +{ + return m_templateComboBox->currentData().toString(); +} + +bool NoteTemplateSelector::setCurrentTemplate(const QString &p_template) +{ + int idx = m_templateComboBox->findData(p_template); + if (idx != -1) { + m_templateComboBox->setCurrentIndex(idx); + return true; + } else { + return false; + } +} + +const QString& NoteTemplateSelector::getTemplateContent() const +{ + return m_templateContent; +} diff --git a/src/widgets/dialogs/notetemplateselector.h b/src/widgets/dialogs/notetemplateselector.h new file mode 100644 index 0000000000..9018001b84 --- /dev/null +++ b/src/widgets/dialogs/notetemplateselector.h @@ -0,0 +1,40 @@ +#ifndef NOTETEMPLATESELECTOR_H +#define NOTETEMPLATESELECTOR_H + +#include + +class QComboBox; +class QPlainTextEdit; + +namespace vnotex +{ + class NoteTemplateSelector : public QWidget + { + Q_OBJECT + public: + explicit NoteTemplateSelector(QWidget *p_parent = nullptr); + + QString getCurrentTemplate() const; + bool setCurrentTemplate(const QString &p_template); + + const QString& getTemplateContent() const; + + signals: + void templateChanged(); + + private: + void setupUI(); + + void setupTemplateComboBox(QWidget *p_parent); + + void updateCurrentTemplate(); + + QComboBox *m_templateComboBox = nullptr; + + QPlainTextEdit *m_templateTextEdit = nullptr; + + QString m_templateContent; + }; +} + +#endif // NOTETEMPLATESELECTOR_H diff --git a/src/widgets/dialogs/settings/fileassociationpage.cpp b/src/widgets/dialogs/settings/fileassociationpage.cpp index a19e7e899d..5245b7436e 100644 --- a/src/widgets/dialogs/settings/fileassociationpage.cpp +++ b/src/widgets/dialogs/settings/fileassociationpage.cpp @@ -39,6 +39,8 @@ void FileAssociationPage::setupUI() m_externalProgramsBox = new QGroupBox(tr("External Programs"), this); WidgetsFactory::createFormLayout(m_externalProgramsBox); mainLayout->addWidget(m_externalProgramsBox); + + mainLayout->addStretch(); } void FileAssociationPage::loadInternal() diff --git a/src/widgets/dialogs/settings/imagehostpage.cpp b/src/widgets/dialogs/settings/imagehostpage.cpp index 8bef78299d..0844649f9e 100644 --- a/src/widgets/dialogs/settings/imagehostpage.cpp +++ b/src/widgets/dialogs/settings/imagehostpage.cpp @@ -46,6 +46,8 @@ void ImageHostPage::setupUI() auto box = setupGeneralBox(this); m_mainLayout->addWidget(box); + + m_mainLayout->addStretch(); } QGroupBox *ImageHostPage::setupGeneralBox(QWidget *p_parent) @@ -54,9 +56,8 @@ QGroupBox *ImageHostPage::setupGeneralBox(QWidget *p_parent) auto layout = WidgetsFactory::createFormLayout(box); { - m_defaultImageHostComboBox = WidgetsFactory::createComboBox(box); - // Add items in loadInternal(). + m_defaultImageHostComboBox = WidgetsFactory::createComboBox(box); const QString label(tr("Default image host:")); layout->addRow(label, m_defaultImageHostComboBox); @@ -121,11 +122,15 @@ void ImageHostPage::loadInternal() } } + removeLastStretch(); + // Setup boxes. for (const auto &host : hosts) { auto box = setupGroupBoxForImageHost(host, this); addWidgetToLayout(box); } + + m_mainLayout->addStretch(); } bool ImageHostPage::saveInternal() @@ -185,8 +190,12 @@ void ImageHostPage::newImageHost() { NewImageHostDialog dialog(this); if (dialog.exec()) { + removeLastStretch(); + auto box = setupGroupBoxForImageHost(dialog.getNewImageHost(), this); addWidgetToLayout(box); + + m_mainLayout->addStretch(); } } @@ -293,3 +302,12 @@ void ImageHostPage::testImageHost(const QString &p_hostName) QString(), msg); } + +void ImageHostPage::removeLastStretch() +{ + auto item = m_mainLayout->itemAt(m_mainLayout->count() - 1); + if (item) { + m_mainLayout->removeItem(item); + delete item; + } +} diff --git a/src/widgets/dialogs/settings/imagehostpage.h b/src/widgets/dialogs/settings/imagehostpage.h index df9d9ce0d1..9b23dc718b 100644 --- a/src/widgets/dialogs/settings/imagehostpage.h +++ b/src/widgets/dialogs/settings/imagehostpage.h @@ -46,6 +46,8 @@ namespace vnotex QGroupBox *setupGeneralBox(QWidget *p_parent); + void removeLastStretch(); + QVBoxLayout *m_mainLayout = nullptr; // [host] -> list of related fields. diff --git a/src/widgets/dialogs/settings/markdowneditorpage.cpp b/src/widgets/dialogs/settings/markdowneditorpage.cpp index a7dd7e3222..0c808b3f99 100644 --- a/src/widgets/dialogs/settings/markdowneditorpage.cpp +++ b/src/widgets/dialogs/settings/markdowneditorpage.cpp @@ -46,6 +46,8 @@ void MarkdownEditorPage::setupUI() auto editBox = setupEditGroup(); mainLayout->addWidget(editBox); + + mainLayout->addStretch(); } void MarkdownEditorPage::loadInternal() diff --git a/src/widgets/dialogs/settings/quickaccesspage.cpp b/src/widgets/dialogs/settings/quickaccesspage.cpp index 883ee185f8..c7bf55f6c8 100644 --- a/src/widgets/dialogs/settings/quickaccesspage.cpp +++ b/src/widgets/dialogs/settings/quickaccesspage.cpp @@ -6,13 +6,24 @@ #include #include #include +#include +#include +#include +#include +#include -#include #include #include #include +#include +#include #include #include +#include +#include +#include + +#include "../notetemplateselector.h" using namespace vnotex; @@ -31,6 +42,11 @@ void QuickAccessPage::setupUI() auto quickAccessBox = setupQuickAccessGroup(); mainLayout->addWidget(quickAccessBox); + + auto quickNoteBox = setupQuickNoteGroup(); + mainLayout->addWidget(quickNoteBox); + + mainLayout->addStretch(); } void QuickAccessPage::loadInternal() @@ -45,6 +61,26 @@ void QuickAccessPage::loadInternal() m_quickAccessTextEdit->setPlainText(quickAccess.join(QChar('\n'))); } } + + loadQuickNoteSchemes(); +} + +void QuickAccessPage::loadQuickNoteSchemes() +{ + const auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + + m_quickNoteSchemes = sessionConfig.getQuickNoteSchemes(); + m_quickNoteCurrentIndex = -1; + + m_quickNoteSchemeComboBox->clear(); + for (const auto &scheme : m_quickNoteSchemes) { + m_quickNoteSchemeComboBox->addItem(scheme.m_name); + } + if (m_quickNoteSchemeComboBox->count() > 0) { + m_quickNoteSchemeComboBox->setCurrentIndex(0); + // Manually call the handler. + setCurrentQuickNote(0); + } } bool QuickAccessPage::saveInternal() @@ -60,9 +96,20 @@ bool QuickAccessPage::saveInternal() } } + saveQuickNoteSchemes(); + return true; } +void QuickAccessPage::saveQuickNoteSchemes() +{ + // Save current quick note scheme from inputs. + saveCurrentQuickNote(); + + auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + sessionConfig.setQuickNoteSchemes(m_quickNoteSchemes); +} + QString QuickAccessPage::title() const { return tr("Quick Access"); @@ -104,6 +151,7 @@ QGroupBox *QuickAccessPage::setupQuickAccessGroup() { m_quickAccessTextEdit = WidgetsFactory::createPlainTextEdit(box); m_quickAccessTextEdit->setToolTip(tr("Edit the files pinned to Quick Access (one file per line)")); + m_quickAccessTextEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); const QString label(tr("Quick Access:")); layout->addRow(label, m_quickAccessTextEdit); @@ -114,3 +162,180 @@ QGroupBox *QuickAccessPage::setupQuickAccessGroup() return box; } + +QString QuickAccessPage::getDefaultQuickNoteFolderPath() +{ + auto defaultPath = QDir::homePath(); + auto currentNotebook = VNoteX::getInst().getNotebookMgr().getCurrentNotebook(); + if (currentNotebook) { + defaultPath = currentNotebook->getRootFolderAbsolutePath(); + } + return defaultPath; +} + +QGroupBox *QuickAccessPage::setupQuickNoteGroup() +{ + auto box = new QGroupBox(tr("Quick Note"), this); + auto mainLayout = WidgetsFactory::createFormLayout(box); + + { + auto selectorLayout = new QHBoxLayout(); + + // Add items in loadInternal(). + m_quickNoteSchemeComboBox = WidgetsFactory::createComboBox(box); + selectorLayout->addWidget(m_quickNoteSchemeComboBox, 1); + m_quickNoteSchemeComboBox->setPlaceholderText(tr("No scheme to show")); + + auto newBtn = new QPushButton(tr("New"), box); + connect(newBtn, &QPushButton::clicked, + this, &QuickAccessPage::newQuickNoteScheme); + selectorLayout->addWidget(newBtn); + + auto deleteBtn = new QPushButton(tr("Delete"), box); + deleteBtn->setEnabled(false); + connect(deleteBtn, &QPushButton::clicked, + this, &QuickAccessPage::removeQuickNoteScheme); + selectorLayout->addWidget(deleteBtn); + + const QString label(tr("Scheme:")); + mainLayout->addRow(label, selectorLayout); + addSearchItem(label, m_quickNoteSchemeComboBox); + + connect(m_quickNoteSchemeComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, &QuickAccessPage::pageIsChanged); + connect(m_quickNoteSchemeComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [deleteBtn](int idx) { + deleteBtn->setEnabled(idx > -1); + }); + } + + m_quickNoteInfoGroupBox = new QGroupBox(box); + mainLayout->addRow(m_quickNoteInfoGroupBox); + auto infoLayout = WidgetsFactory::createFormLayout(m_quickNoteInfoGroupBox); + + { + const QString label(tr("Folder path:")); + m_quickNoteFolderPathInput = new LocationInputWithBrowseButton(m_quickNoteInfoGroupBox); + m_quickNoteFolderPathInput->setPlaceholderText(tr("Empty to use current explored folder dynamically")); + infoLayout->addRow(label, m_quickNoteFolderPathInput); + addSearchItem(label, m_quickNoteFolderPathInput); + connect(m_quickNoteFolderPathInput, &LocationInputWithBrowseButton::textChanged, + this, &QuickAccessPage::pageIsChanged); + connect(m_quickNoteFolderPathInput, &LocationInputWithBrowseButton::clicked, + this, [this]() { + auto folderPath = QFileDialog::getExistingDirectory(this, + tr("Select Quick Note Folder"), + getDefaultQuickNoteFolderPath()); + if (!folderPath.isEmpty()) { + m_quickNoteFolderPathInput->setText(folderPath); + } + }); + } + + { + const QString label(tr("Note name:")); + m_quickNoteNoteNameLineEdit = WidgetsFactory::createLineEditWithSnippet(m_quickNoteInfoGroupBox); + infoLayout->addRow(label, m_quickNoteNoteNameLineEdit); + connect(m_quickNoteNoteNameLineEdit, &QLineEdit::textChanged, + this, &QuickAccessPage::pageIsChanged); + } + + { + const QString label(tr("Note template:")); + m_quickNoteTemplateSelector = new NoteTemplateSelector(m_quickNoteInfoGroupBox); + infoLayout->addRow(label, m_quickNoteTemplateSelector); + connect(m_quickNoteTemplateSelector, &NoteTemplateSelector::templateChanged, + this, &QuickAccessPage::pageIsChanged); + } + + m_quickNoteInfoGroupBox->setVisible(false); + connect(m_quickNoteSchemeComboBox, QOverload::of(&QComboBox::currentIndexChanged), + this, [this](int idx) { + if (isLoading()) { + return; + } + setCurrentQuickNote(idx); + }); + return box; +} + +void QuickAccessPage::newQuickNoteScheme() +{ + bool isDuplicated = false; + QString schemeName; + do { + schemeName = QInputDialog::getText(this, tr("Quick Note Scheme"), + isDuplicated ? tr("Scheme name already exists! Try again:") : tr("Scheme name:")); + if (schemeName.isEmpty()) { + return; + } + isDuplicated = m_quickNoteSchemeComboBox->findText(schemeName) != -1; + } while (isDuplicated); + + SessionConfig::QuickNoteScheme scheme; + scheme.m_name = schemeName; + scheme.m_folderPath = getDefaultQuickNoteFolderPath(); + scheme.m_noteName = tr("quick_note_%da%.md"); + m_quickNoteSchemes.push_back(scheme); + + m_quickNoteSchemeComboBox->addItem(schemeName); + m_quickNoteSchemeComboBox->setCurrentText(schemeName); + + emit pageIsChanged(); +} + +void QuickAccessPage::removeQuickNoteScheme() +{ + int idx = m_quickNoteSchemeComboBox->currentIndex(); + Q_ASSERT(idx > -1); + + auto& scheme = m_quickNoteSchemes[idx]; + int ret = MessageBoxHelper::questionOkCancel(MessageBoxHelper::Type::Question, + tr("Delete quick note scheme (%1)?").arg(scheme.m_name)); + if (ret != QMessageBox::Ok) { + return; + } + + m_quickNoteCurrentIndex = -1; + m_quickNoteSchemes.removeAt(idx); + m_quickNoteSchemeComboBox->removeItem(idx); + emit pageIsChanged(); +} + +void QuickAccessPage::saveCurrentQuickNote() +{ + if (m_quickNoteCurrentIndex < 0) { + return; + } + Q_ASSERT(m_quickNoteCurrentIndex < m_quickNoteSchemes.size()); + auto& scheme = m_quickNoteSchemes[m_quickNoteCurrentIndex]; + scheme.m_folderPath = m_quickNoteFolderPathInput->text(); + // No need to apply the snippet for now. + scheme.m_noteName = m_quickNoteNoteNameLineEdit->text(); + scheme.m_template = m_quickNoteTemplateSelector->getCurrentTemplate(); +} + +void QuickAccessPage::loadCurrentQuickNote() +{ + if (m_quickNoteCurrentIndex < 0) { + m_quickNoteFolderPathInput->setText(QString()); + m_quickNoteNoteNameLineEdit->setText(QString()); + m_quickNoteTemplateSelector->setCurrentTemplate(QString()); + return; + } + + Q_ASSERT(m_quickNoteCurrentIndex < m_quickNoteSchemes.size()); + const auto& scheme = m_quickNoteSchemes[m_quickNoteCurrentIndex]; + m_quickNoteFolderPathInput->setText(scheme.m_folderPath); + m_quickNoteNoteNameLineEdit->setText(scheme.m_noteName); + m_quickNoteTemplateSelector->setCurrentTemplate(scheme.m_template); +} + +void QuickAccessPage::setCurrentQuickNote(int idx) +{ + saveCurrentQuickNote(); + m_quickNoteCurrentIndex = idx; + loadCurrentQuickNote(); + + m_quickNoteInfoGroupBox->setVisible(idx > -1); +} diff --git a/src/widgets/dialogs/settings/quickaccesspage.h b/src/widgets/dialogs/settings/quickaccesspage.h index 6d1e90bdec..7dee163806 100644 --- a/src/widgets/dialogs/settings/quickaccesspage.h +++ b/src/widgets/dialogs/settings/quickaccesspage.h @@ -3,12 +3,17 @@ #include "settingspage.h" +#include + class QGroupBox; class QPlainTextEdit; +class QComboBox; namespace vnotex { class LocationInputWithBrowseButton; + class LineEditWithSnippet; + class NoteTemplateSelector; class QuickAccessPage : public SettingsPage { @@ -30,9 +35,41 @@ namespace vnotex QGroupBox *setupQuickAccessGroup(); + QGroupBox *setupQuickNoteGroup(); + + void newQuickNoteScheme(); + + void removeQuickNoteScheme(); + + void saveCurrentQuickNote(); + + void loadCurrentQuickNote(); + + void loadQuickNoteSchemes(); + + void saveQuickNoteSchemes(); + + void setCurrentQuickNote(int idx); + + static QString getDefaultQuickNoteFolderPath(); + LocationInputWithBrowseButton *m_flashPageInput = nullptr; QPlainTextEdit *m_quickAccessTextEdit = nullptr; + + QComboBox *m_quickNoteSchemeComboBox = nullptr; + + LocationInputWithBrowseButton *m_quickNoteFolderPathInput = nullptr; + + LineEditWithSnippet *m_quickNoteNoteNameLineEdit = nullptr; + + NoteTemplateSelector *m_quickNoteTemplateSelector = nullptr; + + QGroupBox *m_quickNoteInfoGroupBox = nullptr; + + QVector m_quickNoteSchemes; + + int m_quickNoteCurrentIndex = -1; }; } diff --git a/src/widgets/dialogs/settings/settingspage.cpp b/src/widgets/dialogs/settings/settingspage.cpp index 46e3863fec..feccb03331 100644 --- a/src/widgets/dialogs/settings/settingspage.cpp +++ b/src/widgets/dialogs/settings/settingspage.cpp @@ -73,7 +73,9 @@ void SettingsPage::pageIsChangedWithRestartNeeded() void SettingsPage::load() { + m_loading = true; loadInternal(); + m_loading = false; m_changed = false; m_restartNeeded = false; } @@ -115,3 +117,8 @@ bool SettingsPage::isRestartNeeded() const { return m_restartNeeded; } + +bool SettingsPage::isLoading() const +{ + return m_loading; +} diff --git a/src/widgets/dialogs/settings/settingspage.h b/src/widgets/dialogs/settings/settingspage.h index 9f22553f99..f7fa57c870 100644 --- a/src/widgets/dialogs/settings/settingspage.h +++ b/src/widgets/dialogs/settings/settingspage.h @@ -44,6 +44,8 @@ namespace vnotex void setError(const QString &p_err); + bool isLoading() const; + protected slots: void pageIsChanged(); @@ -70,6 +72,8 @@ namespace vnotex bool m_restartNeeded = false; + bool m_loading = false; + QString m_error; }; } diff --git a/src/widgets/dialogs/settings/themepage.cpp b/src/widgets/dialogs/settings/themepage.cpp index c00867aef6..00110cb8ab 100644 --- a/src/widgets/dialogs/settings/themepage.cpp +++ b/src/widgets/dialogs/settings/themepage.cpp @@ -86,11 +86,7 @@ void ThemePage::setupUI() layout->addWidget(scrollArea, 0, 2, 5, 1); } - // Override. - { - auto box = new QGroupBox(tr("Style Override"), this); - mainLayout->addWidget(box); - } + mainLayout->addStretch(); } void ThemePage::loadInternal() diff --git a/src/widgets/editors/markdowneditor.cpp b/src/widgets/editors/markdowneditor.cpp index 0a39593cd0..5ae3a422c6 100644 --- a/src/widgets/editors/markdowneditor.cpp +++ b/src/widgets/editors/markdowneditor.cpp @@ -374,7 +374,7 @@ bool MarkdownEditor::insertImageToBufferFromLocalFile(const QString &p_title, ba = FileUtils::readFile(p_srcImagePath); } catch (Exception &e) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to read local image file (%1) (%2).").arg(p_srcImagePath, e.what()), + tr("Failed to read local image file (%1) (%2).").arg(p_srcImagePath, e.what()), this); return false; } @@ -387,7 +387,7 @@ bool MarkdownEditor::insertImageToBufferFromLocalFile(const QString &p_title, destFilePath = m_buffer->insertImage(p_srcImagePath, destFileName); } catch (Exception &e) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to insert image from local file (%1) (%2).").arg(p_srcImagePath, e.what()), + tr("Failed to insert image from local file (%1) (%2).").arg(p_srcImagePath, e.what()), this); return false; } @@ -430,7 +430,7 @@ bool MarkdownEditor::insertImageToBufferFromData(const QString &p_title, destFilePath = m_buffer->insertImage(p_image, destFileName); } catch (Exception &e) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to insert image from data (%1).").arg(e.what()), + tr("Failed to insert image from data (%1).").arg(e.what()), this); return false; } @@ -1470,7 +1470,7 @@ QString MarkdownEditor::saveToImageHost(const QByteArray &p_imageData, const QSt if (targetUrl.isEmpty()) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to upload image to image host (%1) as (%2).").arg(m_imageHost->getName(), destPath), + tr("Failed to upload image to image host (%1) as (%2).").arg(m_imageHost->getName(), destPath), QString(), errMsg, this); @@ -1551,7 +1551,7 @@ void MarkdownEditor::uploadImagesToImageHost() ba = FileUtils::readFile(link.m_path); } catch (Exception &e) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to read local image file (%1) (%2).").arg(link.m_path, e.what()), + tr("Failed to read local image file (%1) (%2).").arg(link.m_path, e.what()), this); continue; } @@ -1569,7 +1569,7 @@ void MarkdownEditor::uploadImagesToImageHost() if (targetUrl.isEmpty()) { MessageBoxHelper::notify(MessageBoxHelper::Warning, - QString("Failed to upload image to image host (%1) as (%2).").arg(host->getName(), destPath), + tr("Failed to upload image to image host (%1) as (%2).").arg(host->getName(), destPath), QString(), errMsg, this); diff --git a/src/widgets/locationinputwithbrowsebutton.cpp b/src/widgets/locationinputwithbrowsebutton.cpp index b466b67966..ec5d4f5dbb 100644 --- a/src/widgets/locationinputwithbrowsebutton.cpp +++ b/src/widgets/locationinputwithbrowsebutton.cpp @@ -44,3 +44,8 @@ void LocationInputWithBrowseButton::setToolTip(const QString &p_tip) { m_lineEdit->setToolTip(p_tip); } + +void LocationInputWithBrowseButton::setPlaceholderText(const QString &p_text) +{ + m_lineEdit->setPlaceholderText(p_text); +} diff --git a/src/widgets/locationinputwithbrowsebutton.h b/src/widgets/locationinputwithbrowsebutton.h index a18be952e8..e0f4f8cced 100644 --- a/src/widgets/locationinputwithbrowsebutton.h +++ b/src/widgets/locationinputwithbrowsebutton.h @@ -22,6 +22,8 @@ namespace vnotex void setToolTip(const QString &p_tip); + void setPlaceholderText(const QString &p_text); + signals: void clicked(); diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp index cb13c3c642..3112967963 100644 --- a/src/widgets/mainwindow.cpp +++ b/src/widgets/mainwindow.cpp @@ -332,6 +332,8 @@ void MainWindow::setupNotebookExplorer() m_notebookExplorer, &NotebookExplorer::newFolder); connect(&VNoteX::getInst(), &VNoteX::newNoteRequested, m_notebookExplorer, &NotebookExplorer::newNote); + connect(&VNoteX::getInst(), &VNoteX::newQuickNoteRequested, + m_notebookExplorer, &NotebookExplorer::newQuickNote); connect(&VNoteX::getInst(), &VNoteX::importFileRequested, m_notebookExplorer, &NotebookExplorer::importFile); connect(&VNoteX::getInst(), &VNoteX::importFolderRequested, diff --git a/src/widgets/notebookexplorer.cpp b/src/widgets/notebookexplorer.cpp index e5506973a9..387e7778aa 100644 --- a/src/widgets/notebookexplorer.cpp +++ b/src/widgets/notebookexplorer.cpp @@ -8,6 +8,7 @@ #include #include "titlebar.h" +#include "dialogs/selectdialog.h" #include "dialogs/newnotebookdialog.h" #include "dialogs/newnotebookfromfolderdialog.h" #include "dialogs/newfolderdialog.h" @@ -22,6 +23,7 @@ #include #include #include +#include #include "notebookselector.h" #include "notebooknodeexplorer.h" #include "messageboxhelper.h" @@ -32,6 +34,10 @@ #include #include #include +#include +#include +#include + #include "navigationmodemgr.h" #include "widgetsfactory.h" @@ -269,12 +275,11 @@ void NotebookExplorer::newNote() auto node = checkNotebookAndGetCurrentExploredFolderNode(); if (!node) { return; - } + } - NewNoteDialog dialog(node, VNoteX::getInst().getMainWindow()); + NewNoteDialog dialog(node, VNoteX::getInst().getMainWindow()); if (dialog.exec() == QDialog::Accepted) { m_nodeExplorer->setCurrentNode(dialog.getNewNode().data()); - // Open it right now. auto paras = QSharedPointer::create(); paras->m_mode = ViewWindowMode::Edit; @@ -283,6 +288,69 @@ void NotebookExplorer::newNote() } } +void NotebookExplorer::newQuickNote() +{ + auto &sessionConfig = ConfigMgr::getInst().getSessionConfig(); + const auto& schemes = sessionConfig.getQuickNoteSchemes(); + if (schemes.isEmpty()) { + MessageBoxHelper::notify(MessageBoxHelper::Information, + tr("Please set up quick note schemes in the Settings dialog first."), + VNoteX::getInst().getMainWindow()); + return; + } + + SelectDialog dialog(tr("New Quick Note"), VNoteX::getInst().getMainWindow()); + for (int i = 0; i < schemes.size(); ++i) { + dialog.addSelection(schemes[i].m_name, i); + } + + if (dialog.exec() != QDialog::Accepted) { + return; + } + + int selection = dialog.getSelection(); + const auto &scheme = schemes[selection]; + + Notebook *notebook = m_currentNotebook.data(); + Node *parentNode = currentExploredFolderNode(); + if (!scheme.m_folderPath.isEmpty()) { + auto node = VNoteX::getInst().getNotebookMgr().loadNodeByPath(scheme.m_folderPath); + if (node) { + notebook = node->getNotebook(); + parentNode = node.data(); + } + } + + if (!parentNode) { + MessageBoxHelper::notify(MessageBoxHelper::Information, + tr("The quick note should be created within a notebook."), + VNoteX::getInst().getMainWindow()); + return; + } + + QFileInfo finfo(SnippetMgr::getInst().applySnippetBySymbol(scheme.m_noteName)); + QString newName = FileUtils::generateFileNameWithSequence(parentNode->fetchAbsolutePath(), + finfo.completeBaseName(), finfo.suffix()); + + QString errMsg; + auto newNode = NewNoteDialog::newNote(notebook, parentNode, newName, + TemplateMgr::getInst().getTemplateContent(scheme.m_template), + errMsg); + if (!newNode) { + MessageBoxHelper::notify(MessageBoxHelper::Information, + tr("Failed to create quick note from scheme (%1) (%2)").arg(scheme.m_name, errMsg), + VNoteX::getInst().getMainWindow()); + return; + } + + m_nodeExplorer->setCurrentNode(newNode.data()); + // Open it right now. + auto paras = QSharedPointer::create(); + paras->m_mode = ViewWindowMode::Edit; + paras->m_newFile = true; + emit VNoteX::getInst().openNodeRequested(newNode.data(), paras); +} + Node *NotebookExplorer::currentExploredFolderNode() const { return m_nodeExplorer->currentExploredFolderNode(); diff --git a/src/widgets/notebookexplorer.h b/src/widgets/notebookexplorer.h index adfd8a84ed..7db5079b27 100644 --- a/src/widgets/notebookexplorer.h +++ b/src/widgets/notebookexplorer.h @@ -51,6 +51,8 @@ namespace vnotex void newNote(); + void newQuickNote(); + void importFile(); void importFolder(); diff --git a/src/widgets/viewarea.cpp b/src/widgets/viewarea.cpp index 3ec35ccb07..f3af96991f 100644 --- a/src/widgets/viewarea.cpp +++ b/src/widgets/viewarea.cpp @@ -14,9 +14,6 @@ #include #include -#include "viewwindow.h" -#include "mainwindow.h" -#include "propertydefs.h" #include #include #include @@ -34,6 +31,12 @@ #include "editors/plantumlhelper.h" #include "editors/graphvizhelper.h" #include +#include + +#include "viewwindow.h" +#include "mainwindow.h" +#include "propertydefs.h" +#include "messageboxhelper.h" using namespace vnotex; diff --git a/src/widgets/viewsplit.cpp b/src/widgets/viewsplit.cpp index d400e7140a..8d6fced661 100644 --- a/src/widgets/viewsplit.cpp +++ b/src/widgets/viewsplit.cpp @@ -25,6 +25,7 @@ #include #include "propertydefs.h" #include "fileopenparameters.h" +#include "sessionconfig.h" using namespace vnotex; @@ -80,7 +81,13 @@ void ViewSplit::setupUI() closeTab(p_idx); }); connect(this, &QTabWidget::tabBarDoubleClicked, - this, &ViewSplit::closeTab); + this, [this](int p_idx) { + if (p_idx == -1) { + emit VNoteX::getInst().newQuickNoteRequested(); + } else { + closeTab(p_idx); + } + }); connect(this, &QTabWidget::tabBarClicked, this, [this](int p_idx) { Q_UNUSED(p_idx); diff --git a/src/widgets/viewsplit.h b/src/widgets/viewsplit.h index 7ea0393089..870c5d4c03 100644 --- a/src/widgets/viewsplit.h +++ b/src/widgets/viewsplit.h @@ -92,6 +92,8 @@ namespace vnotex void distributeSplitsRequested(); + void newQuickNoteRequested(); + void removeSplitRequested(ViewSplit *p_split); void removeSplitAndWorkspaceRequested(ViewSplit *p_split); diff --git a/src/widgets/widgets.pri b/src/widgets/widgets.pri index 893c840161..eaf719070c 100644 --- a/src/widgets/widgets.pri +++ b/src/widgets/widgets.pri @@ -42,6 +42,7 @@ SOURCES += \ $$PWD/dialogs/tableinsertdialog.cpp \ $$PWD/dialogs/updater.cpp \ $$PWD/dialogs/viewtagsdialog.cpp \ + $$PWD/dialogs/notetemplateselector.cpp \ $$PWD/dockwidgethelper.cpp \ $$PWD/dragdropareaindicator.cpp \ $$PWD/editors/graphhelper.cpp \ @@ -181,6 +182,7 @@ HEADERS += \ $$PWD/dialogs/tableinsertdialog.h \ $$PWD/dialogs/updater.h \ $$PWD/dialogs/viewtagsdialog.h \ + $$PWD/dialogs/notetemplateselector.h \ $$PWD/dockwidgethelper.h \ $$PWD/dragdropareaindicator.h \ $$PWD/editors/graphhelper.h \