support tags of notes

- Adding/Removing tags of notes;
- Auto-completion of tags;
This commit is contained in:
Le Tan 2018-06-16 09:00:59 +08:00
parent 3bd80387fa
commit ffd653ef55
27 changed files with 746 additions and 29 deletions

View File

@ -59,11 +59,18 @@ void VFileInfoDialog::setupUI(const QString &p_title, const QString &p_info)
QLabel *modifiedTimeLabel = new QLabel(createdTimeStr); QLabel *modifiedTimeLabel = new QLabel(createdTimeStr);
modifiedTimeLabel->setToolTip(tr("Last modified time within VNote")); 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(); QFormLayout *topLayout = new QFormLayout();
topLayout->addRow(tr("Note &name:"), m_nameEdit); topLayout->addRow(tr("Note &name:"), m_nameEdit);
topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit); topLayout->addRow(tr("Attachment folder:"), attachmentFolderEdit);
topLayout->addRow(tr("Created time:"), createdTimeLabel); topLayout->addRow(tr("Created time:"), createdTimeLabel);
topLayout->addRow(tr("Modified time:"), modifiedTimeLabel); topLayout->addRow(tr("Modified time:"), modifiedTimeLabel);
topLayout->addRow(tr("Tags:"), tagEdit);
m_warnLabel = new QLabel(); m_warnLabel = new QLabel();
m_warnLabel->setWordWrap(true); m_warnLabel->setWordWrap(true);

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
<g>
<g>
<polygon fill="#000000" points="464,64 464,201.377 227.153,467.48 240,480 480,208 480,64 "/>
</g>
<g>
<path fill="#000000" d="M288,32L32,320l160,160l23.471-23.904l11.348-11.375L448,192V80V64V32H288z M352,160c-17.645,0-32-14.355-32-32
s14.355-32,32-32s32,14.355,32,32S369.645,160,352,160z"/>
<circle fill="#000000" cx="352" cy="128" r="16"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 851 B

View File

@ -9,7 +9,7 @@ mdhl_file=v_detorte.mdhl
css_file=v_detorte.css css_file=v_detorte.css
codeblock_css_file=v_detorte_codeblock.css codeblock_css_file=v_detorte_codeblock.css
mermaid_css_file=v_detorte_mermaid.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 ; 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. ; without background. You could just specify the foreground colors mapping here.
@ -108,6 +108,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg
; VTabIndicator. ; VTabIndicator.
tab_indicator_label_fg=@base_fg tab_indicator_label_fg=@base_fg
tab_indicator_tag_label_fg=#222222
tab_indicator_tag_label_bg=#ce93db
; Html template. ; Html template.
template_title_flash_light_fg=@master_light_bg template_title_flash_light_fg=@master_light_bg

View File

@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] {
border-top: 1px solid @menu_separator_bg 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"] { VVimIndicator QLabel[VimIndicatorKeyLabel="true"] {
font: bold; font: bold;
color: @vim_indicator_key_label_fg; color: @vim_indicator_key_label_fg;

View File

@ -7,7 +7,7 @@ mdhl_file=v_moonlight.mdhl
css_file=v_moonlight.css css_file=v_moonlight.css
codeblock_css_file=v_moonlight_codeblock.css codeblock_css_file=v_moonlight_codeblock.css
mermaid_css_file=v_moonlight_mermaid.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 ; 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. ; without background. You could just specify the foreground colors mapping here.
@ -106,6 +106,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg
; VTabIndicator. ; VTabIndicator.
tab_indicator_label_fg=@base_fg tab_indicator_label_fg=@base_fg
tab_indicator_tag_label_fg=#222222
tab_indicator_tag_label_bg=#ce93db
; Html template. ; Html template.
template_title_flash_light_fg=@master_light_bg template_title_flash_light_fg=@master_light_bg

View File

@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] {
border-top: 1px solid @menu_separator_bg 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"] { VVimIndicator QLabel[VimIndicatorKeyLabel="true"] {
font: bold; font: bold;
color: @vim_indicator_key_label_fg; color: @vim_indicator_key_label_fg;

View File

@ -7,7 +7,7 @@ mdhl_file=v_native.mdhl
css_file=v_native.css css_file=v_native.css
codeblock_css_file=v_native_codeblock.css codeblock_css_file=v_native_codeblock.css
mermaid_css_file=v_native_mermaid.css mermaid_css_file=v_native_mermaid.css
version=11 version=12
[phony] [phony]
; Abstract color attributes. ; Abstract color attributes.
@ -74,6 +74,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg
; VTabIndicator. ; VTabIndicator.
tab_indicator_label_fg=@base_fg tab_indicator_label_fg=@base_fg
tab_indicator_tag_label_fg=#222222
tab_indicator_tag_label_bg=#ce93db
; Html template. ; Html template.
template_title_flash_light_fg=#80CBC4 template_title_flash_light_fg=#80CBC4

View File

@ -411,6 +411,15 @@ QLabel[MenuSeparator="true"] {
border-top: 1px solid @menu_separator_bg 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"] { VVimIndicator QLabel[VimIndicatorKeyLabel="true"] {
font: bold; font: bold;
color: @vim_indicator_key_label_fg; color: @vim_indicator_key_label_fg;

View File

@ -7,7 +7,7 @@ mdhl_file=v_pure.mdhl
css_file=v_pure.css css_file=v_pure.css
codeblock_css_file=v_pure_codeblock.css codeblock_css_file=v_pure_codeblock.css
mermaid_css_file=v_pure_mermaid.css mermaid_css_file=v_pure_mermaid.css
version=12 version=13
[phony] [phony]
; Abstract color attributes. ; Abstract color attributes.
@ -100,6 +100,8 @@ vim_indicator_cmd_edit_pending_bg=@selected_bg
; VTabIndicator. ; VTabIndicator.
tab_indicator_label_fg=@base_fg tab_indicator_label_fg=@base_fg
tab_indicator_tag_label_fg=#222222
tab_indicator_tag_label_bg=#ce93db
; Html template. ; Html template.
template_title_flash_light_fg=@master_light_bg template_title_flash_light_fg=@master_light_bg

View File

@ -568,6 +568,15 @@ QLabel[MenuSeparator="true"] {
border-top: 1px solid @menu_separator_bg 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"] { VVimIndicator QLabel[VimIndicatorKeyLabel="true"] {
font: bold; font: bold;
color: @vim_indicator_key_label_fg; color: @vim_indicator_key_label_fg;

View File

@ -136,7 +136,10 @@ SOURCES += main.cpp\
vexplorer.cpp \ vexplorer.cpp \
vlistue.cpp \ vlistue.cpp \
vuetitlecontentpanel.cpp \ vuetitlecontentpanel.cpp \
utils/vprocessutils.cpp utils/vprocessutils.cpp \
vtagpanel.cpp \
valltagspanel.cpp \
vtaglabel.cpp
HEADERS += vmainwindow.h \ HEADERS += vmainwindow.h \
vdirectorytree.h \ vdirectorytree.h \
@ -266,7 +269,10 @@ HEADERS += vmainwindow.h \
vexplorerentry.h \ vexplorerentry.h \
vlistue.h \ vlistue.h \
vuetitlecontentpanel.h \ vuetitlecontentpanel.h \
utils/vprocessutils.h utils/vprocessutils.h \
vtagpanel.h \
valltagspanel.h \
vtaglabel.h
RESOURCES += \ RESOURCES += \
vnote.qrc \ vnote.qrc \

55
src/valltagspanel.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "valltagspanel.h"
#include <QtWidgets>
#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;
}

29
src/valltagspanel.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef VALLTAGSPANEL_H
#define VALLTAGSPANEL_H
#include <QWidget>
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

View File

@ -72,6 +72,11 @@ void VButtonWithWidget::showPopupWidget()
showMenu(); showMenu();
} }
void VButtonWithWidget::hidePopupWidget()
{
menu()->hide();
}
void VButtonWithWidget::dragEnterEvent(QDragEnterEvent *p_event) void VButtonWithWidget::dragEnterEvent(QDragEnterEvent *p_event)
{ {
VButtonPopupWidget *popup = getButtonPopupWidget(); VButtonPopupWidget *popup = getButtonPopupWidget();

View File

@ -73,6 +73,8 @@ public:
// Show the popup widget. // Show the popup widget.
void showPopupWidget(); void showPopupWidget();
void hidePopupWidget();
// Set the bubble to display a number @p_num. // Set the bubble to display a number @p_num.
// @p_num: -1 to hide the bubble. // @p_num: -1 to hide the bubble.
void setBubbleNumber(int p_num); void setBubbleNumber(int p_num);

View File

@ -60,6 +60,7 @@ namespace DirConfig
static const QString c_imageFolder = "image_folder"; static const QString c_imageFolder = "image_folder";
static const QString c_attachmentFolder = "attachment_folder"; static const QString c_attachmentFolder = "attachment_folder";
static const QString c_recycleBinFolder = "recycle_bin_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_name = "name";
static const QString c_createdTime = "created_time"; static const QString c_createdTime = "created_time";
static const QString c_modifiedTime = "modified_time"; static const QString c_modifiedTime = "modified_time";

View File

@ -258,5 +258,6 @@
<file>resources/themes/v_detorte/v_detorte.qss</file> <file>resources/themes/v_detorte/v_detorte.qss</file>
<file>resources/themes/v_detorte/v_detorte_codeblock.css</file> <file>resources/themes/v_detorte/v_detorte_codeblock.css</file>
<file>resources/themes/v_detorte/v_detorte_mermaid.css</file> <file>resources/themes/v_detorte/v_detorte_mermaid.css</file>
<file>resources/icons/tags.svg</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -2,6 +2,7 @@
#include <QDir> #include <QDir>
#include <QDebug> #include <QDebug>
#include <QCoreApplication> #include <QCoreApplication>
#include <QJsonArray>
#include "vdirectory.h" #include "vdirectory.h"
#include "utils/vutils.h" #include "utils/vutils.h"
@ -57,6 +58,12 @@ bool VNotebook::readConfigNotebook()
m_recycleBinFolder = it.value().toString(); 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. // [attachment_folder] section.
// SHOULD be processed at last. // SHOULD be processed at last.
it = configJson.find(DirConfig::c_attachmentFolder); it = configJson.find(DirConfig::c_attachmentFolder);
@ -88,6 +95,14 @@ QJsonObject VNotebook::toConfigJsonNotebook() const
// [recycle_bin_folder] section. // [recycle_bin_folder] section.
json[DirConfig::c_recycleBinFolder] = m_recycleBinFolder; 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; return json;
} }
@ -408,3 +423,22 @@ void VNotebook::updatePath(const QString &p_path)
readConfigNotebook(); 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;
}

View File

@ -4,6 +4,7 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QDateTime> #include <QDateTime>
#include <QStringList>
class VDirectory; class VDirectory;
class VFile; class VFile;
@ -55,6 +56,12 @@ public:
void rename(const QString &p_name); 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, static VNotebook *createNotebook(const QString &p_name,
const QString &p_path, const QString &p_path,
bool p_import, bool p_import,
@ -132,6 +139,10 @@ private:
// Could be relative or absolute. // Could be relative or absolute.
QString m_recycleBinFolder; 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 // Parent is NULL for root directory
VDirectory *m_rootDir; VDirectory *m_rootDir;
@ -170,4 +181,13 @@ inline const QString &VNotebook::getName() const
return m_name; 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 #endif // VNOTEBOOK_H

View File

@ -15,12 +15,8 @@ VNoteFile::VNoteFile(VDirectory *p_directory,
FileType p_type, FileType p_type,
bool p_modifiable, bool p_modifiable,
QDateTime p_createdTimeUtc, QDateTime p_createdTimeUtc,
QDateTime p_modifiedTimeUtc, QDateTime p_modifiedTimeUtc)
const QString &p_attachmentFolder, : VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc)
const QVector<VAttachment> &p_attachments)
: VFile(p_directory, p_name, p_type, p_modifiable, p_createdTimeUtc, p_modifiedTimeUtc),
m_attachmentFolder(p_attachmentFolder),
m_attachments(p_attachments)
{ {
} }
@ -129,24 +125,32 @@ VNoteFile *VNoteFile::fromJson(VDirectory *p_directory,
FileType p_type, FileType p_type,
bool p_modifiable) 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. // Attachments.
QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray(); QJsonArray attachmentJson = p_json[DirConfig::c_attachments].toArray();
QVector<VAttachment> attachments;
for (int i = 0; i < attachmentJson.size(); ++i) { for (int i = 0; i < attachmentJson.size(); ++i) {
QJsonObject attachmentItem = attachmentJson[i].toObject(); 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, // Tags.
p_json[DirConfig::c_name].toString(), QJsonArray tagsJson = p_json[DirConfig::c_tags].toArray();
p_type, for (int i = 0; i < tagsJson.size(); ++i) {
p_modifiable, file->m_tags.append(tagsJson[i].toString());
QDateTime::fromString(p_json[DirConfig::c_createdTime].toString(), }
Qt::ISODate),
QDateTime::fromString(p_json[DirConfig::c_modifiedTime].toString(), return file;
Qt::ISODate),
p_json[DirConfig::c_attachmentFolder].toString(),
attachments);
} }
QJsonObject VNoteFile::toConfigJson() const QJsonObject VNoteFile::toConfigJson() const
@ -168,6 +172,14 @@ QJsonObject VNoteFile::toConfigJson() const
item[DirConfig::c_attachments] = attachmentJson; item[DirConfig::c_attachments] = attachmentJson;
// Tags.
QJsonArray tags;
for (auto const & tag : m_tags) {
tags.append(tag);
}
item[DirConfig::c_tags] = tags;
return item; return item;
} }
@ -606,3 +618,37 @@ bool VNoteFile::copyInternalImages(const QVector<ImageLink> &p_images,
*p_nrImageCopied = nrImageCopied; *p_nrImageCopied = nrImageCopied;
return ret; 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;
}

View File

@ -35,9 +35,7 @@ public:
FileType p_type, FileType p_type,
bool p_modifiable, bool p_modifiable,
QDateTime p_createdTimeUtc, QDateTime p_createdTimeUtc,
QDateTime p_modifiedTimeUtc, QDateTime p_modifiedTimeUtc);
const QString &p_attachmentFolder = "",
const QVector<VAttachment> &p_attachments = QVector<VAttachment>());
QString fetchPath() const Q_DECL_OVERRIDE; QString fetchPath() const Q_DECL_OVERRIDE;
@ -109,6 +107,14 @@ public:
// Return the missing attachments' names. // Return the missing attachments' names.
QVector<QString> checkAttachments(); QVector<QString> 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. // Create a VNoteFile from @p_json Json object.
static VNoteFile *fromJson(VDirectory *p_directory, static VNoteFile *fromJson(VDirectory *p_directory,
const QJsonObject &p_json, const QJsonObject &p_json,
@ -151,6 +157,9 @@ private:
// Attachments. // Attachments.
QVector<VAttachment> m_attachments; QVector<VAttachment> m_attachments;
// Tags of this file.
QStringList m_tags;
}; };
inline const QString &VNoteFile::getAttachmentFolder() const inline const QString &VNoteFile::getAttachmentFolder() const
@ -173,4 +182,13 @@ inline void VNoteFile::setAttachments(const QVector<VAttachment> &p_attas)
m_attachments = 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 #endif // VNOTEFILE_H

View File

@ -7,6 +7,8 @@
#include "vbuttonwithwidget.h" #include "vbuttonwithwidget.h"
#include "vwordcountinfo.h" #include "vwordcountinfo.h"
#include "utils/vutils.h" #include "utils/vutils.h"
#include "vtagpanel.h"
#include "vnotefile.h"
VWordCountPanel::VWordCountPanel(QWidget *p_parent) VWordCountPanel::VWordCountPanel(QWidget *p_parent)
: QWidget(p_parent) : QWidget(p_parent)
@ -99,6 +101,8 @@ VTabIndicator::VTabIndicator(QWidget *p_parent)
void VTabIndicator::setupUI() void VTabIndicator::setupUI()
{ {
m_tagPanel = new VTagPanel(this);
m_docTypeLabel = new QLabel(this); m_docTypeLabel = new QLabel(this);
m_docTypeLabel->setToolTip(tr("The type of the file")); m_docTypeLabel->setToolTip(tr("The type of the file"));
m_docTypeLabel->setProperty("ColorGreyLabel", true); m_docTypeLabel->setProperty("ColorGreyLabel", true);
@ -129,6 +133,7 @@ void VTabIndicator::setupUI()
this, &VTabIndicator::updateWordCountInfo); this, &VTabIndicator::updateWordCountInfo);
QHBoxLayout *mainLayout = new QHBoxLayout(this); QHBoxLayout *mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(m_tagPanel);
mainLayout->addWidget(m_cursorLabel); mainLayout->addWidget(m_cursorLabel);
mainLayout->addWidget(m_wordCountBtn); mainLayout->addWidget(m_wordCountBtn);
mainLayout->addWidget(m_externalLabel); mainLayout->addWidget(m_externalLabel);
@ -171,7 +176,7 @@ static QString docTypeToString(DocType p_type)
void VTabIndicator::update(const VEditTabInfo &p_info) void VTabIndicator::update(const VEditTabInfo &p_info)
{ {
const VFile *file = NULL; VFile *file = NULL;
DocType docType = DocType::Html; DocType docType = DocType::Html;
bool readonly = false; bool readonly = false;
bool external = false; bool external = false;
@ -186,7 +191,7 @@ void VTabIndicator::update(const VEditTabInfo &p_info)
docType = file->getDocType(); docType = file->getDocType();
readonly = !file->isModifiable(); readonly = !file->isModifiable();
external = file->getType() == FileType::Orphan; external = file->getType() == FileType::Orphan;
system = external && dynamic_cast<const VOrphanFile *>(file)->isSystemFile(); system = external && dynamic_cast<VOrphanFile *>(file)->isSystemFile();
if (m_editTab->isEditMode()) { if (m_editTab->isEditMode()) {
int line = p_info.m_cursorBlockNumber + 1; 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<VNoteFile *>(file));
}
updateWordCountBtn(p_info); updateWordCountBtn(p_info);
if (p_info.m_wordCountInfo.m_mode == VWordCountInfo::Read) { if (p_info.m_wordCountInfo.m_mode == VWordCountInfo::Read) {

View File

@ -9,6 +9,7 @@ class VButtonWithWidget;
class VEditTab; class VEditTab;
class VWordCountPanel; class VWordCountPanel;
class QGroupBox; class QGroupBox;
class VTagPanel;
class VWordCountPanel : public QWidget class VWordCountPanel : public QWidget
{ {
@ -54,6 +55,9 @@ private:
void updateWordCountBtn(const VEditTabInfo &p_info); void updateWordCountBtn(const VEditTabInfo &p_info);
// Tag panel.
VTagPanel *m_tagPanel;
// Indicate the doc type. // Indicate the doc type.
QLabel *m_docTypeLabel; QLabel *m_docTypeLabel;

96
src/vtaglabel.cpp Normal file
View File

@ -0,0 +1,96 @@
#include "vtaglabel.h"
#include <QtWidgets>
#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);
}

49
src/vtaglabel.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef VTAGLABEL_H
#define VTAGLABEL_H
#include <QWidget>
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

212
src/vtagpanel.cpp Normal file
View File

@ -0,0 +1,212 @@
#include "vtagpanel.h"
#include <QtWidgets>
#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<QFocusEvent *>(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());
}

60
src/vtagpanel.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef VTAGPANEL_H
#define VTAGPANEL_H
#include <QWidget>
#include <QVector>
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<VTagLabel *> 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