diff --git a/src/dialog/vfileinfodialog.cpp b/src/dialog/vfileinfodialog.cpp index f6d2e723..ba9270cb 100644 --- a/src/dialog/vfileinfodialog.cpp +++ b/src/dialog/vfileinfodialog.cpp @@ -59,11 +59,18 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info) QLabel *modifiedTimeLabel = new QLabel(createdTimeStr); modifiedTimeLabel->setToolTip(tr("Last modified time within VNote")); + // Tags. + QLineEdit *tagEdit = new QLineEdit(); + tagEdit->setToolTip(tr("Tags of this note separated by ,")); + tagEdit->setReadOnly(true); + tagEdit->setText(m_file->getTags().join(", ")); + QFormLayout *topLayout = new QFormLayout(); topLayout->addRow(tr("Note &name:"), m_nameEdit); topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit); topLayout->addRow(tr("Created time:"), createdTimeLabel); topLayout->addRow(tr("Modified time:"), modifiedTimeLabel); + topLayout->addRow(tr("Tags:"), tagEdit); m_warnLabel = new QLabel(); m_warnLabel->setWordWrap(true); diff --git a/src/resources/icons/tags.svg b/src/resources/icons/tags.svg new file mode 100644 index 00000000..8031efbc --- /dev/null +++ b/src/resources/icons/tags.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/resources/themes/v_detorte/v_detorte.palette b/src/resources/themes/v_detorte/v_detorte.palette index 2de7964b..f483b9fb 100644 --- a/src/resources/themes/v_detorte/v_detorte.palette +++ b/src/resources/themes/v_detorte/v_detorte.palette @@ -9,7 +9,7 @@ mdhl_file=v_detorte.mdhl css_file=v_detorte.css codeblock_css_file=v_detorte_codeblock.css mermaid_css_file=v_detorte_mermaid.css -version=2 +version=3 ; 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. @@ -108,6 +108,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg ; VTabIndicator. tab_indicator_label_fg=@base_fg +tab_indicator_tag_label_fg=#222222 +tab_indicator_tag_label_bg=#ce93db ; Html template. template_title_flash_light_fg=@master_light_bg diff --git a/src/resources/themes/v_detorte/v_detorte.qss b/src/resources/themes/v_detorte/v_detorte.qss index 42859965..8c814669 100644 --- a/src/resources/themes/v_detorte/v_detorte.qss +++ b/src/resources/themes/v_detorte/v_detorte.qss @@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] { border-top: 1px solid @menu_separator_bg } +QLabel[TagLabel="true"] { + padding-left: $5px; + padding-right: $5px; + color: @tab_indicator_tag_label_fg; + border-top-left-radius: $10px $8px; + border-bottom-left-radius: $10px $8px; + background-color: @tab_indicator_tag_label_bg; +} + VVimIndicator QLabel[VimIndicatorKeyLabel="true"] { font: bold; color: @vim_indicator_key_label_fg; diff --git a/src/resources/themes/v_moonlight/v_moonlight.palette b/src/resources/themes/v_moonlight/v_moonlight.palette index 84013e94..43998490 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=13 +version=14 ; 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. @@ -106,6 +106,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg ; VTabIndicator. tab_indicator_label_fg=@base_fg +tab_indicator_tag_label_fg=#222222 +tab_indicator_tag_label_bg=#ce93db ; Html template. template_title_flash_light_fg=@master_light_bg diff --git a/src/resources/themes/v_moonlight/v_moonlight.qss b/src/resources/themes/v_moonlight/v_moonlight.qss index 953334b9..8d09975a 100644 --- a/src/resources/themes/v_moonlight/v_moonlight.qss +++ b/src/resources/themes/v_moonlight/v_moonlight.qss @@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] { border-top: 1px solid @menu_separator_bg } +QLabel[TagLabel="true"] { + padding-left: $5px; + padding-right: $5px; + color: @tab_indicator_tag_label_fg; + border-top-left-radius: $10px $8px; + border-bottom-left-radius: $10px $8px; + background-color: @tab_indicator_tag_label_bg; +} + VVimIndicator QLabel[VimIndicatorKeyLabel="true"] { font: bold; color: @vim_indicator_key_label_fg; diff --git a/src/resources/themes/v_native/v_native.palette b/src/resources/themes/v_native/v_native.palette index 52e8e14e..ea9c9655 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=11 +version=12 [phony] ; Abstract color attributes. @@ -74,6 +74,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg ; VTabIndicator. tab_indicator_label_fg=@base_fg +tab_indicator_tag_label_fg=#222222 +tab_indicator_tag_label_bg=#ce93db ; Html template. template_title_flash_light_fg=#80CBC4 diff --git a/src/resources/themes/v_native/v_native.qss b/src/resources/themes/v_native/v_native.qss index 93533b06..43a66ae3 100644 --- a/src/resources/themes/v_native/v_native.qss +++ b/src/resources/themes/v_native/v_native.qss @@ -411,6 +411,15 @@ QLabel[MenuSeparator="true"] { border-top: 1px solid @menu_separator_bg } +QLabel[TagLabel="true"] { + padding-left: $5px; + padding-right: $5px; + color: @tab_indicator_tag_label_fg; + border-top-left-radius: $10px $8px; + border-bottom-left-radius: $10px $8px; + background-color: @tab_indicator_tag_label_bg; +} + VVimIndicator QLabel[VimIndicatorKeyLabel="true"] { font: bold; color: @vim_indicator_key_label_fg; diff --git a/src/resources/themes/v_pure/v_pure.palette b/src/resources/themes/v_pure/v_pure.palette index 8aef9b9a..9d2a2f4b 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=12 +version=13 [phony] ; Abstract color attributes. @@ -100,6 +100,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg ; VTabIndicator. tab_indicator_label_fg=@base_fg +tab_indicator_tag_label_fg=#222222 +tab_indicator_tag_label_bg=#ce93db ; Html template. template_title_flash_light_fg=@master_light_bg diff --git a/src/resources/themes/v_pure/v_pure.qss b/src/resources/themes/v_pure/v_pure.qss index d5274289..6791e389 100644 --- a/src/resources/themes/v_pure/v_pure.qss +++ b/src/resources/themes/v_pure/v_pure.qss @@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] { border-top: 1px solid @menu_separator_bg } +QLabel[TagLabel="true"] { + padding-left: $5px; + padding-right: $5px; + color: @tab_indicator_tag_label_fg; + border-top-left-radius: $10px $8px; + border-bottom-left-radius: $10px $8px; + background-color: @tab_indicator_tag_label_bg; +} + VVimIndicator QLabel[VimIndicatorKeyLabel="true"] { font: bold; color: @vim_indicator_key_label_fg; diff --git a/src/src.pro b/src/src.pro index 89679ede..e7264796 100644 --- a/src/src.pro +++ b/src/src.pro @@ -136,7 +136,10 @@ SOURCES += main.cpp\ vexplorer.cpp \ vlistue.cpp \ vuetitlecontentpanel.cpp \ - utils/vprocessutils.cpp + utils/vprocessutils.cpp \ + vtagpanel.cpp \ + valltagspanel.cpp \ + vtaglabel.cpp HEADERS += vmainwindow.h \ vdirectorytree.h \ @@ -266,7 +269,10 @@ HEADERS += vmainwindow.h \ vexplorerentry.h \ vlistue.h \ vuetitlecontentpanel.h \ - utils/vprocessutils.h + utils/vprocessutils.h \ + vtagpanel.h \ + valltagspanel.h \ + vtaglabel.h RESOURCES += \ vnote.qrc \ diff --git a/src/valltagspanel.cpp b/src/valltagspanel.cpp new file mode 100644 index 00000000..b7ac2bed --- /dev/null +++ b/src/valltagspanel.cpp @@ -0,0 +1,55 @@ +#include "valltagspanel.h" + +#include + +#include "vtaglabel.h" + +VAllTagsPanel::VAllTagsPanel(QWidget *p_parent) + : QWidget(p_parent) +{ + m_list = new QListWidget(this); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(m_list); + + setLayout(layout); +} + +void VAllTagsPanel::clear() +{ + while (m_list->count() > 0) { + removeItem(m_list->item(0)); + } +} + +void VAllTagsPanel::removeItem(QListWidgetItem *p_item) +{ + QWidget *wid = m_list->itemWidget(p_item); + m_list->removeItemWidget(p_item); + wid->deleteLater(); + + int row = m_list->row(p_item); + Q_ASSERT(row >= 0); + m_list->takeItem(row); + delete p_item; +} + +VTagLabel *VAllTagsPanel::addTag(const QString &p_text) +{ + VTagLabel *label = new VTagLabel(p_text, true, this); + QSize sz = label->sizeHint(); + sz.setHeight(sz.height() * 2 + 10); + + QListWidgetItem *item = new QListWidgetItem(); + item->setSizeHint(sz); + + connect(label, &VTagLabel::removalRequested, + this, [this, item](const QString &p_text) { + removeItem(item); + emit tagRemoved(p_text); + }); + + m_list->addItem(item); + m_list->setItemWidget(item, label); + return label; +} diff --git a/src/valltagspanel.h b/src/valltagspanel.h new file mode 100644 index 00000000..c110bac4 --- /dev/null +++ b/src/valltagspanel.h @@ -0,0 +1,29 @@ +#ifndef VALLTAGSPANEL_H +#define VALLTAGSPANEL_H + +#include + +class QListWidget; +class QListWidgetItem; +class VTagLabel; + +class VAllTagsPanel : public QWidget +{ + Q_OBJECT +public: + explicit VAllTagsPanel(QWidget *p_parent = nullptr); + + void clear(); + + VTagLabel *addTag(const QString &p_text); + +signals: + void tagRemoved(const QString &p_text); + +private: + void removeItem(QListWidgetItem *p_item); + + QListWidget *m_list; +}; + +#endif // VALLTAGSPANEL_H diff --git a/src/vbuttonwithwidget.cpp b/src/vbuttonwithwidget.cpp index 2f036dff..722dfc73 100644 --- a/src/vbuttonwithwidget.cpp +++ b/src/vbuttonwithwidget.cpp @@ -72,6 +72,11 @@ void VButtonWithWidget::showPopupWidget() showMenu(); } +void VButtonWithWidget::hidePopupWidget() +{ + menu()->hide(); +} + void VButtonWithWidget::dragEnterEvent(QDragEnterEvent *p_event) { VButtonPopupWidget *popup = getButtonPopupWidget(); diff --git a/src/vbuttonwithwidget.h b/src/vbuttonwithwidget.h index e9f54fb9..2cc1610b 100644 --- a/src/vbuttonwithwidget.h +++ b/src/vbuttonwithwidget.h @@ -73,6 +73,8 @@ public: // Show the popup widget. void showPopupWidget(); + void hidePopupWidget(); + // Set the bubble to display a number @p_num. // @p_num: -1 to hide the bubble. void setBubbleNumber(int p_num); diff --git a/src/vconstants.h b/src/vconstants.h index dcf4c46b..0fc0a04e 100644 --- a/src/vconstants.h +++ b/src/vconstants.h @@ -60,6 +60,7 @@ namespace DirConfig static const QString c_imageFolder = "image_folder"; static const QString c_attachmentFolder = "attachment_folder"; static const QString c_recycleBinFolder = "recycle_bin_folder"; + static const QString c_tags = "tags"; static const QString c_name = "name"; static const QString c_createdTime = "created_time"; static const QString c_modifiedTime = "modified_time"; diff --git a/src/vnote.qrc b/src/vnote.qrc index ca440763..39e27a03 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -258,5 +258,6 @@ resources/themes/v_detorte/v_detorte.qss resources/themes/v_detorte/v_detorte_codeblock.css resources/themes/v_detorte/v_detorte_mermaid.css + resources/icons/tags.svg diff --git a/src/vnotebook.cpp b/src/vnotebook.cpp index 6cceddca..50bf81a0 100644 --- a/src/vnotebook.cpp +++ b/src/vnotebook.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "vdirectory.h" #include "utils/vutils.h" @@ -57,6 +58,12 @@ bool VNotebook::readConfigNotebook() m_recycleBinFolder = it.value().toString(); } + // [tags] section. + QJsonArray tagsJson = configJson[DirConfig::c_tags].toArray(); + for (int i = 0; i < tagsJson.size(); ++i) { + m_tags.append(tagsJson[i].toString()); + } + // [attachment_folder] section. // SHOULD be processed at last. it = configJson.find(DirConfig::c_attachmentFolder); @@ -88,6 +95,14 @@ QJsonObject VNotebook::toConfigJsonNotebook() const // [recycle_bin_folder] section. json[DirConfig::c_recycleBinFolder] = m_recycleBinFolder; + // [tags] section. + QJsonArray tags; + for (auto const & tag : m_tags) { + tags.append(tag); + } + + json[DirConfig::c_tags] = tags; + return json; } @@ -408,3 +423,22 @@ void VNotebook::updatePath(const QString &p_path) readConfigNotebook(); } + +bool VNotebook::addTag(const QString &p_tag) +{ + Q_ASSERT(isOpened()); + + if (p_tag.isEmpty() || hasTag(p_tag)) { + return false; + } + + m_tags.append(p_tag); + if (!writeConfigNotebook()) { + qWarning() << "fail to update config of notebook" << m_name + << "in directory" << m_path; + m_tags.removeAll(p_tag); + return false; + } + + return true; +} diff --git a/src/vnotebook.h b/src/vnotebook.h index 55e8991c..d5bdcde6 100644 --- a/src/vnotebook.h +++ b/src/vnotebook.h @@ -4,6 +4,7 @@ #include #include #include +#include class VDirectory; class VFile; @@ -55,6 +56,12 @@ public: void rename(const QString &p_name); + const QStringList &getTags() const; + + bool addTag(const QString &p_tag); + + bool hasTag(const QString &p_tag) const; + static VNotebook *createNotebook(const QString &p_name, const QString &p_path, bool p_import, @@ -132,6 +139,10 @@ private: // Could be relative or absolute. QString m_recycleBinFolder; + // List of all the tags of notes. + // Used for index and auto-completion. + QStringList m_tags; + // Parent is NULL for root directory VDirectory *m_rootDir; @@ -170,4 +181,13 @@ inline const QString &VNotebook::getName() const return m_name; } +inline bool VNotebook::hasTag(const QString &p_tag) const +{ + return m_tags.contains(p_tag); +} + +inline const QStringList &VNotebook::getTags() const +{ + return m_tags; +} #endif // VNOTEBOOK_H diff --git a/src/vnotefile.cpp b/src/vnotefile.cpp index cde82c56..72753657 100644 --- a/src/vnotefile.cpp +++ b/src/vnotefile.cpp @@ -15,12 +15,8 @@ VNoteFile::VNoteFile(VDirectory *p_directory, FileType p_type, bool p_modifiable, QDateTime p_createdTimeUtc, - QDateTime p_modifiedTimeUtc, - const QString &p_attachmentFolder, - const QVector &p_attachments) - : VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc), - m_attachmentFolder(p_attachmentFolder), - m_attachments(p_attachments) + QDateTime p_modifiedTimeUtc) + : VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc) { } @@ -129,24 +125,32 @@ VNoteFile *VNoteFile::fromJson(VDirectory *p_directory, FileType p_type, bool p_modifiable) { + VNoteFile *file = new VNoteFile(p_directory, + p_json[DirConfig::c_name].toString(), + p_type, + p_modifiable, + QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(), + Qt::ISODate), + QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(), + Qt::ISODate)); + + // Attachment Folder. + file->m_attachmentFolder = p_json[DirConfig::c_attachmentFolder].toString(); + // Attachments. QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray(); - QVector attachments; for (int i = 0; i < attachmentJson.size(); ++i) { QJsonObject attachmentItem = attachmentJson[i].toObject(); - attachments.push_back(VAttachment(attachmentItem[DirConfig::c_name].toString())); + file->m_attachments.push_back(VAttachment(attachmentItem[DirConfig::c_name].toString())); } - return new VNoteFile(p_directory, - p_json[DirConfig::c_name].toString(), - p_type, - p_modifiable, - QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(), - Qt::ISODate), - QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(), - Qt::ISODate), - p_json[DirConfig::c_attachmentFolder].toString(), - attachments); + // Tags. + QJsonArray tagsJson = p_json[DirConfig::c_tags].toArray(); + for (int i = 0; i < tagsJson.size(); ++i) { + file->m_tags.append(tagsJson[i].toString()); + } + + return file; } QJsonObject VNoteFile::toConfigJson() const @@ -168,6 +172,14 @@ QJsonObject VNoteFile::toConfigJson() const item[DirConfig::c_attachments] = attachmentJson; + // Tags. + QJsonArray tags; + for (auto const & tag : m_tags) { + tags.append(tag); + } + + item[DirConfig::c_tags] = tags; + return item; } @@ -606,3 +618,37 @@ bool VNoteFile::copyInternalImages(const QVector &p_images, *p_nrImageCopied = nrImageCopied; return ret; } + +void VNoteFile::removeTag(const QString &p_tag) +{ + if (p_tag.isEmpty() || m_tags.isEmpty()) { + return; + } + + int nr = m_tags.removeAll(p_tag); + if (nr > 0) { + if (!getDirectory()->updateFileConfig(this)) { + qWarning() << "fail to update config of file" << m_name + << "in directory" << fetchBasePath(); + } + } +} + +bool VNoteFile::addTag(const QString &p_tag) +{ + Q_ASSERT(isOpened()); + + if (p_tag.isEmpty() || hasTag(p_tag)) { + return false; + } + + m_tags.append(p_tag); + if (!getDirectory()->updateFileConfig(this)) { + qWarning() << "fail to update config of file" << m_name + << "in directory" << fetchBasePath(); + m_tags.removeAll(p_tag); + return false; + } + + return true; +} diff --git a/src/vnotefile.h b/src/vnotefile.h index 0f615217..b432f586 100644 --- a/src/vnotefile.h +++ b/src/vnotefile.h @@ -35,9 +35,7 @@ public: FileType p_type, bool p_modifiable, QDateTime p_createdTimeUtc, - QDateTime p_modifiedTimeUtc, - const QString &p_attachmentFolder = "", - const QVector &p_attachments = QVector()); + QDateTime p_modifiedTimeUtc); QString fetchPath() const Q_DECL_OVERRIDE; @@ -109,6 +107,14 @@ public: // Return the missing attachments' names. QVector checkAttachments(); + const QStringList &getTags() const; + + void removeTag(const QString &p_tag); + + bool addTag(const QString &p_tag); + + bool hasTag(const QString &p_tag) const; + // Create a VNoteFile from @p_json Json object. static VNoteFile *fromJson(VDirectory *p_directory, const QJsonObject &p_json, @@ -151,6 +157,9 @@ private: // Attachments. QVector m_attachments; + + // Tags of this file. + QStringList m_tags; }; inline const QString &VNoteFile::getAttachmentFolder() const @@ -173,4 +182,13 @@ inline void VNoteFile::setAttachments(const QVector &p_attas) m_attachments = p_attas; } +inline const QStringList &VNoteFile::getTags() const +{ + return m_tags; +} + +inline bool VNoteFile::hasTag(const QString &p_tag) const +{ + return m_tags.contains(p_tag); +} #endif // VNOTEFILE_H diff --git a/src/vtabindicator.cpp b/src/vtabindicator.cpp index b284576d..f7cb6987 100644 --- a/src/vtabindicator.cpp +++ b/src/vtabindicator.cpp @@ -7,6 +7,8 @@ #include "vbuttonwithwidget.h" #include "vwordcountinfo.h" #include "utils/vutils.h" +#include "vtagpanel.h" +#include "vnotefile.h" VWordCountPanel::VWordCountPanel(QWidget *p_parent) : QWidget(p_parent) @@ -99,6 +101,8 @@ VTabIndicator::VTabIndicator(QWidget *p_parent) void VTabIndicator::setupUI() { + m_tagPanel = new VTagPanel(this); + m_docTypeLabel = new QLabel(this); m_docTypeLabel->setToolTip(tr("The type of the file")); m_docTypeLabel->setProperty("ColorGreyLabel", true); @@ -129,6 +133,7 @@ void VTabIndicator::setupUI() this, &VTabIndicator::updateWordCountInfo); QHBoxLayout *mainLayout = new QHBoxLayout(this); + mainLayout->addWidget(m_tagPanel); mainLayout->addWidget(m_cursorLabel); mainLayout->addWidget(m_wordCountBtn); mainLayout->addWidget(m_externalLabel); @@ -171,7 +176,7 @@ static QString docTypeToString(DocType p_type) void VTabIndicator::update(const VEditTabInfo &p_info) { - const VFile *file = NULL; + VFile *file = NULL; DocType docType = DocType::Html; bool readonly = false; bool external = false; @@ -186,7 +191,7 @@ void VTabIndicator::update(const VEditTabInfo &p_info) docType = file->getDocType(); readonly = !file->isModifiable(); external = file->getType() == FileType::Orphan; - system = external && dynamic_cast(file)->isSystemFile(); + system = external && dynamic_cast(file)->isSystemFile(); if (m_editTab->isEditMode()) { int line = p_info.m_cursorBlockNumber + 1; @@ -209,6 +214,13 @@ void VTabIndicator::update(const VEditTabInfo &p_info) } } + m_tagPanel->setVisible(!external); + if (external) { + m_tagPanel->updateTags(NULL); + } else { + m_tagPanel->updateTags(dynamic_cast(file)); + } + updateWordCountBtn(p_info); if (p_info.m_wordCountInfo.m_mode == VWordCountInfo::Read) { diff --git a/src/vtabindicator.h b/src/vtabindicator.h index c12233e7..d55d74e6 100644 --- a/src/vtabindicator.h +++ b/src/vtabindicator.h @@ -9,6 +9,7 @@ class VButtonWithWidget; class VEditTab; class VWordCountPanel; class QGroupBox; +class VTagPanel; class VWordCountPanel : public QWidget { @@ -54,6 +55,9 @@ private: void updateWordCountBtn(const VEditTabInfo &p_info); + // Tag panel. + VTagPanel *m_tagPanel; + // Indicate the doc type. QLabel *m_docTypeLabel; diff --git a/src/vtaglabel.cpp b/src/vtaglabel.cpp new file mode 100644 index 00000000..1212c891 --- /dev/null +++ b/src/vtaglabel.cpp @@ -0,0 +1,96 @@ +#include "vtaglabel.h" + +#include + +#include "utils/viconutils.h" + +#define MAX_DISPLAY_SIZE_PER_LABEL 5 + +VTagLabel::VTagLabel(const QString &p_text, + bool p_useFullText, + QWidget *p_parent) + : QWidget(p_parent), + m_useFullText(p_useFullText), + m_text(p_text) +{ + setupUI(); +} + +VTagLabel::VTagLabel(QWidget *p_parent) + : QWidget(p_parent), + m_useFullText(false) +{ + setupUI(); +} + +void VTagLabel::setupUI() +{ + m_label = new QLabel(this); + m_label->setProperty("TagLabel", true); + + updateLabel(); + + m_closeBtn = new QPushButton(VIconUtils::buttonIcon(":/resources/icons/close.svg"), "", this); + m_closeBtn->setProperty("StatusBtn", true); + m_closeBtn->setToolTip(tr("Remove")); + m_closeBtn->setFocusPolicy(Qt::NoFocus); + m_closeBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + m_closeBtn->hide(); + connect(m_closeBtn, &QPushButton::clicked, + this, [this]() { + emit removalRequested(m_text); + }); + + QHBoxLayout *layout = new QHBoxLayout(); + layout->addWidget(m_label); + layout->addWidget(m_closeBtn); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + setLayout(layout); +} + +void VTagLabel::updateLabel() +{ + QString tag(m_text); + + if (!m_useFullText) { + if (tag.size() > MAX_DISPLAY_SIZE_PER_LABEL) { + tag.resize(MAX_DISPLAY_SIZE_PER_LABEL); + tag += QStringLiteral("..."); + } + } + + m_label->setText(tag); + m_label->setToolTip(m_text); +} + +void VTagLabel::clear() +{ + m_label->clear(); +} + +void VTagLabel::setText(const QString &p_text) +{ + m_text = p_text; + updateLabel(); +} + +bool VTagLabel::event(QEvent *p_event) +{ + switch (p_event->type()) { + case QEvent::Enter: + m_closeBtn->show(); + break; + + case QEvent::Leave: + m_closeBtn->hide(); + break; + + default: + break; + } + + return QWidget::event(p_event); +} diff --git a/src/vtaglabel.h b/src/vtaglabel.h new file mode 100644 index 00000000..aad5ffa5 --- /dev/null +++ b/src/vtaglabel.h @@ -0,0 +1,49 @@ +#ifndef VTAGLABEL_H +#define VTAGLABEL_H + +#include + +class QLabel; +class QPushButton; + +class VTagLabel : public QWidget +{ + Q_OBJECT +public: + explicit VTagLabel(const QString &p_text, + bool p_useFullText, + QWidget *p_parent = nullptr); + + explicit VTagLabel(QWidget *p_parent = nullptr); + + void clear(); + + void setText(const QString &p_text); + + const QString &text() const; + +signals: + void removalRequested(const QString &p_text); + +protected: + virtual bool event(QEvent *p_event) Q_DECL_OVERRIDE; + +private: + void setupUI(); + + void updateLabel(); + + QString m_text; + + bool m_useFullText; + + QLabel *m_label; + QPushButton *m_closeBtn; +}; + +inline const QString &VTagLabel::text() const +{ + return m_text; +} + +#endif // VTAGLABEL_H diff --git a/src/vtagpanel.cpp b/src/vtagpanel.cpp new file mode 100644 index 00000000..32f12acf --- /dev/null +++ b/src/vtagpanel.cpp @@ -0,0 +1,212 @@ +#include "vtagpanel.h" + +#include + +#include "vbuttonwithwidget.h" +#include "utils/viconutils.h" +#include "vnotefile.h" +#include "valltagspanel.h" +#include "vtaglabel.h" +#include "vlineedit.h" +#include "vmainwindow.h" + +extern VPalette *g_palette; + +extern VMainWindow *g_mainWin; + +#define MAX_DISPLAY_LABEL 3 + +VTagPanel::VTagPanel(QWidget *parent) + : QWidget(parent), + m_file(NULL), + m_notebookOfCompleter(NULL) +{ + setupUI(); +} + +void VTagPanel::setupUI() +{ + for (int i = 0; i < MAX_DISPLAY_LABEL; ++i) { + VTagLabel *label = new VTagLabel(this); + connect(label, &VTagLabel::removalRequested, + this, [this](const QString &p_text) { + removeTag(p_text); + updateTags(); + }); + + m_labels.append(label); + } + + m_tagsPanel = new VAllTagsPanel(this); + connect(m_tagsPanel, &VAllTagsPanel::tagRemoved, + this, [this](const QString &p_text) { + removeTag(p_text); + + if (m_file->getTags().size() <= MAX_DISPLAY_LABEL) { + // Hide the more panel. + m_btn->hidePopupWidget(); + m_btn->hide(); + } + }); + + m_btn = new VButtonWithWidget(VIconUtils::icon(":/resources/icons/tags.svg", + g_palette->color("tab_indicator_tag_label_bg")), + "", + m_tagsPanel, + this); + m_btn->setToolTip(tr("View and edit tags of current note")); + m_btn->setProperty("StatusBtn", true); + m_btn->setFocusPolicy(Qt::NoFocus); + connect(m_btn, &VButtonWithWidget::popupWidgetAboutToShow, + this, &VTagPanel::updateAllTagsPanel); + + m_tagEdit = new VLineEdit(this); + m_tagEdit->setToolTip(tr("Press Enter to add a tag")); + m_tagEdit->setPlaceholderText(tr("Add a tag")); + m_tagEdit->setProperty("EmbeddedEdit", true); + m_tagEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + connect(m_tagEdit, &QLineEdit::returnPressed, + this, [this]() { + Q_ASSERT(m_file); + QString text = m_tagEdit->text(); + if (addTag(text)) { + if (m_file->getNotebook()->addTag(text)) { + updateCompleter(); + } + + updateTags(); + + g_mainWin->showStatusMessage(tr("Tag \"%1\" added").arg(text)); + } + + m_tagEdit->clear(); + }); + + QValidator *validator = new QRegExpValidator(QRegExp("[^,]+"), m_tagEdit); + m_tagEdit->setValidator(validator); + + // Completer. + m_tagsModel = new QStringListModel(this); + QCompleter *completer = new QCompleter(m_tagsModel, this); + completer->setCaseSensitivity(Qt::CaseSensitive); + completer->popup()->setItemDelegate(new QStyledItemDelegate(this)); + m_tagEdit->setCompleter(completer); + m_tagEdit->installEventFilter(this); + + QHBoxLayout *mainLayout = new QHBoxLayout(); + for (auto label : m_labels) { + mainLayout->addWidget(label); + label->hide(); + } + + mainLayout->addWidget(m_btn); + mainLayout->addWidget(m_tagEdit); + + mainLayout->setContentsMargins(0, 0, 0, 0); + + setLayout(mainLayout); +} + +void VTagPanel::clearLabels() +{ + for (auto label : m_labels) { + label->clear(); + label->hide(); + } + + m_tagsPanel->clear(); +} + +void VTagPanel::updateTags(VNoteFile *p_file) +{ + if (m_file == p_file) { + return; + } + + m_file = p_file; + + updateTags(); +} + +void VTagPanel::updateTags() +{ + clearLabels(); + + if (!m_file) { + m_btn->setVisible(false); + return; + } + + const QStringList &tags = m_file->getTags(); + int idx = 0; + for (; idx < tags.size() && idx < m_labels.size(); ++idx) { + m_labels[idx]->setText(tags[idx]); + m_labels[idx]->show(); + } + + m_btn->setVisible(idx < tags.size()); +} + +void VTagPanel::updateAllTagsPanel() +{ + Q_ASSERT(m_file); + m_tagsPanel->clear(); + + const QStringList &tags = m_file->getTags(); + for (int idx = MAX_DISPLAY_LABEL; idx < tags.size(); ++idx) { + m_tagsPanel->addTag(tags[idx]); + } +} + +void VTagPanel::removeTag(const QString &p_text) +{ + if (!m_file) { + return; + } + + m_file->removeTag(p_text); + g_mainWin->showStatusMessage(tr("Tag \"%1\" removed").arg(p_text)); +} + +bool VTagPanel::addTag(const QString &p_text) +{ + if (!m_file) { + return false; + } + + bool ret = m_file->addTag(p_text); + return ret; +} + +bool VTagPanel::eventFilter(QObject *p_obj, QEvent *p_event) +{ + Q_ASSERT(p_obj == m_tagEdit); + + if (p_event->type() == QEvent::FocusIn) { + QFocusEvent *eve = static_cast(p_event); + if (eve->gotFocus()) { + // Just check completer. + updateCompleter(m_file); + } + } + + return QWidget::eventFilter(p_obj, p_event); +} + +void VTagPanel::updateCompleter(const VNoteFile *p_file) +{ + const VNotebook *nb = p_file ? p_file->getNotebook() : NULL; + if (nb == m_notebookOfCompleter) { + // No need to update. + return; + } + + m_notebookOfCompleter = nb; + updateCompleter(); +} + +void VTagPanel::updateCompleter() +{ + m_tagsModel->setStringList(m_notebookOfCompleter ? m_notebookOfCompleter->getTags() + : QStringList()); +} diff --git a/src/vtagpanel.h b/src/vtagpanel.h new file mode 100644 index 00000000..cd99a941 --- /dev/null +++ b/src/vtagpanel.h @@ -0,0 +1,60 @@ +#ifndef VTAGPANEL_H +#define VTAGPANEL_H + +#include +#include + +class VTagLabel; +class VButtonWithWidget; +class VNoteFile; +class VAllTagsPanel; +class VLineEdit; +class QStringListModel; +class VNotebook; + +class VTagPanel : public QWidget +{ + Q_OBJECT +public: + explicit VTagPanel(QWidget *parent = nullptr); + + void updateTags(VNoteFile *p_file); + +protected: + bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE; + +private slots: + void updateAllTagsPanel(); + + void removeTag(const QString &p_text); + +private: + void setupUI(); + + void clearLabels(); + + void updateTags(); + + bool addTag(const QString &p_text); + + void updateCompleter(const VNoteFile *p_file); + + void updateCompleter(); + + QVector m_labels; + + VButtonWithWidget *m_btn; + + VAllTagsPanel *m_tagsPanel; + + VLineEdit *m_tagEdit; + + // Used for auto completion. + QStringListModel *m_tagsModel; + + VNoteFile *m_file; + + const VNotebook *m_notebookOfCompleter; +}; + +#endif // VTAGPANEL_H