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