refine attachment list

- Add shortcut Ctrl+E A to show attachment list;
- Add Vim-like navigation shortcut to attachment list;
- Support drag-and-drop to add attachments;
- Add bubble to indicate the number of attachments at the right top corner;
This commit is contained in:
Le Tan 2017-09-26 19:27:47 +08:00
parent e5cd014762
commit fb4e818e20
10 changed files with 343 additions and 39 deletions

View File

@ -1,14 +0,0 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<g id="Icon_3_">
<g id="svg_1">
<path d="m341.334,128l0,234.666c0,46.938 -38.396,85.334 -85.334,85.334c-46.937,0 -85.333,-38.396 -85.333,-85.334l0,-245.332c0,-29.865 23.468,-53.334 53.333,-53.334c29.864,0 53.333,23.469 53.333,53.334l0,245.333c0,11.729 -9.605,21.333 -21.334,21.333c-11.729,0 -21.333,-9.604 -21.333,-21.333l0,-202.667l-32,0l0,202.667c0.001,29.864 23.469,53.333 53.334,53.333c29.865,0 53.334,-23.469 53.334,-53.333l0,-245.333c0,-46.933 -38.396,-85.334 -85.334,-85.334c-46.938,0 -85.334,38.401 -85.334,85.334l0,245.332c0.001,65.063 52.272,117.334 117.334,117.334c65.062,0 117.334,-52.271 117.334,-117.334l0,-234.666l-32,0z" id="svg_2"/>
</g>
</g>
</g>
<g>
<title>Layer 2</title>
<circle stroke="#000000" fill="#15ae67" stroke-width="5" stroke-opacity="0" cx="435.5" cy="75.50001" r="70.05334" id="svg_3"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1001 B

View File

@ -28,6 +28,7 @@ void VAttachmentList::setupUI()
m_addBtn = new QPushButton(QIcon(":/resources/icons/add_attachment.svg"), ""); m_addBtn = new QPushButton(QIcon(":/resources/icons/add_attachment.svg"), "");
m_addBtn->setToolTip(tr("Add")); m_addBtn->setToolTip(tr("Add"));
m_addBtn->setProperty("FlatBtn", true); m_addBtn->setProperty("FlatBtn", true);
m_addBtn->setDefault(true);
connect(m_addBtn, &QPushButton::clicked, connect(m_addBtn, &QPushButton::clicked,
this, &VAttachmentList::addAttachment); this, &VAttachmentList::addAttachment);
@ -68,6 +69,8 @@ void VAttachmentList::setupUI()
} }
m_attachmentList->clear(); m_attachmentList->clear();
updateButtonState();
} }
} }
}); });
@ -138,7 +141,8 @@ void VAttachmentList::initActions()
void VAttachmentList::setFile(VNoteFile *p_file) void VAttachmentList::setFile(VNoteFile *p_file)
{ {
m_file = p_file; m_file = p_file;
updateContent();
updateButtonState();
} }
void VAttachmentList::updateContent() void VAttachmentList::updateContent()
@ -169,8 +173,13 @@ void VAttachmentList::updateContent()
int cnt = m_attachmentList->count(); int cnt = m_attachmentList->count();
if (cnt > 0) { if (cnt > 0) {
m_numLabel->setText(tr("%1 %2").arg(cnt).arg(cnt > 1 ? tr("Files") : tr("File"))); m_numLabel->setText(tr("%1 %2").arg(cnt).arg(cnt > 1 ? tr("Files") : tr("File")));
m_attachmentList->setFocus();
} else { } else {
m_numLabel->setText(""); m_numLabel->setText("");
if (m_file) {
m_addBtn->setFocus();
}
} }
} }
@ -204,13 +213,23 @@ void VAttachmentList::addAttachment()
// Update lastPath // Update lastPath
lastPath = QFileInfo(files[0]).path(); lastPath = QFileInfo(files[0]).path();
addAttachments(files);
updateButtonState();
updateContent();
}
void VAttachmentList::addAttachments(const QStringList &p_files)
{
Q_ASSERT(m_file);
int addedFiles = 0; int addedFiles = 0;
for (int i = 0; i < files.size(); ++i) { for (int i = 0; i < p_files.size(); ++i) {
if (!m_file->addAttachment(files[i])) { if (!m_file->addAttachment(p_files[i])) {
VUtils::showMessage(QMessageBox::Warning, VUtils::showMessage(QMessageBox::Warning,
tr("Warning"), tr("Warning"),
tr("Fail to add attachment %1 for note <span style=\"%2\">%3</span>.") tr("Fail to add attachment %1 for note <span style=\"%2\">%3</span>.")
.arg(files[i]) .arg(p_files[i])
.arg(g_config->c_dataTextStyle) .arg(g_config->c_dataTextStyle)
.arg(m_file->getName()), .arg(m_file->getName()),
"", "",
@ -222,8 +241,6 @@ void VAttachmentList::addAttachment()
} }
} }
updateContent();
if (addedFiles > 0) { if (addedFiles > 0) {
g_vnote->getMainWindow()->showStatusMessage(tr("Added %1 %2 as attachments") g_vnote->getMainWindow()->showStatusMessage(tr("Added %1 %2 as attachments")
.arg(addedFiles) .arg(addedFiles)
@ -322,6 +339,8 @@ void VAttachmentList::deleteSelectedItems()
g_vnote->getMainWindow()); g_vnote->getMainWindow());
} }
updateButtonState();
updateContent(); updateContent();
} }
} }
@ -418,3 +437,130 @@ void VAttachmentList::handleListItemCommitData(QWidget *p_itemEdit)
} }
} }
} }
void VAttachmentList::keyPressEvent(QKeyEvent *p_event)
{
int key = p_event->key();
int modifiers = p_event->modifiers();
switch (key) {
case Qt::Key_BracketLeft:
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *escEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape,
Qt::NoModifier);
QCoreApplication::postEvent(this, escEvent);
return;
}
break;
}
case Qt::Key_J:
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down,
Qt::NoModifier);
QCoreApplication::postEvent(m_attachmentList, downEvent);
return;
}
break;
}
case Qt::Key_K:
{
if (modifiers == Qt::ControlModifier) {
QKeyEvent *upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up,
Qt::NoModifier);
QCoreApplication::postEvent(m_attachmentList, upEvent);
return;
}
break;
}
default:
break;
}
QWidget::keyPressEvent(p_event);
}
bool VAttachmentList::isAcceptDrops() const
{
return true;
}
bool VAttachmentList::handleDragEnterEvent(QDragEnterEvent *p_event)
{
if (!m_file) {
return false;
}
if (p_event->mimeData()->hasFormat("text/uri-list")) {
p_event->acceptProposedAction();
return true;
}
return false;
}
bool VAttachmentList::handleDropEvent(QDropEvent *p_event)
{
if (!m_file) {
return false;
}
const QMimeData *mime = p_event->mimeData();
if (mime->hasFormat("text/uri-list") && mime->hasUrls()) {
// Add attachments.
QStringList files;
QList<QUrl> urls = mime->urls();
for (int i = 0; i < urls.size(); ++i) {
QString file;
if (urls[i].isLocalFile()) {
file = urls[i].toLocalFile();
QFileInfo fi(file);
if (fi.exists() && fi.isFile()) {
file = QDir::cleanPath(fi.absoluteFilePath());
files.append(file);
}
}
}
if (!files.isEmpty()) {
addAttachments(files);
updateButtonState();
}
p_event->acceptProposedAction();
return true;
}
return false;
}
void VAttachmentList::handleAboutToShow()
{
updateContent();
}
void VAttachmentList::updateButtonState() const
{
VButtonWithWidget *btn = getButton();
Q_ASSERT(btn);
if (!btn) {
return;
}
int numOfAttachments = -1;
if (m_file) {
numOfAttachments = m_file->getAttachments().size();
if (numOfAttachments == 0) {
numOfAttachments = -1;
}
}
btn->setBubbleNumber(numOfAttachments);
}

View File

@ -3,7 +3,9 @@
#include <QWidget> #include <QWidget>
#include <QVector> #include <QVector>
#include <QStringList>
#include "vnotefile.h" #include "vnotefile.h"
#include "vbuttonwithwidget.h"
class QPushButton; class QPushButton;
class QListWidget; class QListWidget;
@ -12,14 +14,29 @@ class QLabel;
class VNoteFile; class VNoteFile;
class QAction; class QAction;
class VAttachmentList : public QWidget class VAttachmentList : public QWidget, public VButtonPopupWidget
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit VAttachmentList(QWidget *p_parent = 0); explicit VAttachmentList(QWidget *p_parent = 0);
// Need to call updateContent() to update the list.
void setFile(VNoteFile *p_file); void setFile(VNoteFile *p_file);
// Update attachment info of m_file.
void updateContent();
bool isAcceptDrops() const Q_DECL_OVERRIDE;
bool handleDragEnterEvent(QDragEnterEvent *p_event) Q_DECL_OVERRIDE;
bool handleDropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
void handleAboutToShow() Q_DECL_OVERRIDE;
protected:
void keyPressEvent(QKeyEvent *p_event) Q_DECL_OVERRIDE;
private slots: private slots:
void addAttachment(); void addAttachment();
@ -38,11 +55,13 @@ private:
void initActions(); void initActions();
// Update attachment info of m_file.
void updateContent();
void fillAttachmentList(const QVector<VAttachment> &p_attachments); void fillAttachmentList(const QVector<VAttachment> &p_attachments);
void addAttachments(const QStringList &p_files);
// Update the state of VButtonWithWidget.
void updateButtonState() const;
QPushButton *m_addBtn; QPushButton *m_addBtn;
QPushButton *m_clearBtn; QPushButton *m_clearBtn;
QPushButton *m_locateBtn; QPushButton *m_locateBtn;

View File

@ -1,6 +1,13 @@
#include "vbuttonwithwidget.h" #include "vbuttonwithwidget.h"
#include <QMenu> #include <QMenu>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QRect>
#include <QPainter>
#include <QPainterPath>
#include <QBrush>
VButtonWithWidget::VButtonWithWidget(QWidget *p_widget, VButtonWithWidget::VButtonWithWidget(QWidget *p_widget,
QWidget *p_parent) QWidget *p_parent)
@ -30,6 +37,9 @@ void VButtonWithWidget::init()
{ {
m_popupWidget->setParent(this); m_popupWidget->setParent(this);
m_bubbleFg = QColor(Qt::white);
m_bubbleBg = QColor("#15AE67");
QMenu *menu = new QMenu(this); QMenu *menu = new QMenu(this);
VButtonWidgetAction *act = new VButtonWidgetAction(m_popupWidget, menu); VButtonWidgetAction *act = new VButtonWidgetAction(m_popupWidget, menu);
menu->addAction(act); menu->addAction(act);
@ -39,6 +49,16 @@ void VButtonWithWidget::init()
}); });
setMenu(menu); setMenu(menu);
VButtonPopupWidget *popup = getButtonPopupWidget();
if (popup) {
popup->setButton(this);
setAcceptDrops(popup->isAcceptDrops());
connect(this, &VButtonWithWidget::popupWidgetAboutToShow,
this, [this]() {
getButtonPopupWidget()->handleAboutToShow();
});
}
} }
QWidget *VButtonWithWidget::getPopupWidget() const QWidget *VButtonWithWidget::getPopupWidget() const
@ -50,3 +70,64 @@ void VButtonWithWidget::showPopupWidget()
{ {
showMenu(); showMenu();
} }
void VButtonWithWidget::dragEnterEvent(QDragEnterEvent *p_event)
{
VButtonPopupWidget *popup = getButtonPopupWidget();
Q_ASSERT(popup);
if (popup->handleDragEnterEvent(p_event)) {
return;
}
QPushButton::dragEnterEvent(p_event);
}
void VButtonWithWidget::dropEvent(QDropEvent *p_event)
{
VButtonPopupWidget *popup = getButtonPopupWidget();
Q_ASSERT(popup);
if (popup->handleDropEvent(p_event)) {
return;
}
QPushButton::dropEvent(p_event);
}
void VButtonWithWidget::paintEvent(QPaintEvent *p_event)
{
QPushButton::paintEvent(p_event);
if (!isEnabled() || m_bubbleStr.isEmpty()) {
return;
}
QRect re = rect();
int bubbleWidth = re.width() * 3.0 / 7;
int x = re.width() - bubbleWidth;
int y = 0;
QRect bubbleRect(x, y, bubbleWidth, bubbleWidth);
QPainter painter(this);
QPainterPath bgPath;
bgPath.addEllipse(bubbleRect);
painter.fillPath(bgPath, m_bubbleBg);
QFont font = painter.font();
font.setPixelSize(bubbleWidth / 1.3);
painter.setFont(font);
painter.setPen(m_bubbleFg);
painter.drawText(bubbleRect, Qt::AlignCenter, m_bubbleStr);
}
void VButtonWithWidget::setBubbleNumber(int p_num)
{
if (p_num < 0) {
m_bubbleStr.clear();
} else {
m_bubbleStr = QString::number(p_num);
}
update();
}

View File

@ -6,6 +6,39 @@
#include <QIcon> #include <QIcon>
#include <QWidgetAction> #include <QWidgetAction>
class QDragEnterEvent;
class QDropEvent;
class QPaintEvent;
class VButtonWithWidget;
// Abstract class for the widget used by VButtonWithWidget.
// Widget need to inherit this class if drag/drop is needed.
class VButtonPopupWidget
{
public:
VButtonPopupWidget() : m_btn(NULL)
{
}
virtual bool isAcceptDrops() const = 0;
virtual bool handleDragEnterEvent(QDragEnterEvent *p_event) = 0;
virtual bool handleDropEvent(QDropEvent *p_event) = 0;
virtual void handleAboutToShow() = 0;
void setButton(VButtonWithWidget *p_btn)
{
m_btn = p_btn;
}
VButtonWithWidget *getButton() const
{
return m_btn;
}
private:
VButtonWithWidget *m_btn;
};
class VButtonWidgetAction : public QWidgetAction class VButtonWidgetAction : public QWidgetAction
{ {
Q_OBJECT Q_OBJECT
@ -47,14 +80,42 @@ public:
// Show the popup widget. // Show the popup widget.
void showPopupWidget(); void showPopupWidget();
// Set the bubble to display a number @p_num.
// @p_num: -1 to hide the bubble.
void setBubbleNumber(int p_num);
signals: signals:
// Emit when popup widget is about to show. // Emit when popup widget is about to show.
void popupWidgetAboutToShow(QWidget *p_widget); void popupWidgetAboutToShow(QWidget *p_widget);
protected:
// To accept specific drop.
void dragEnterEvent(QDragEnterEvent *p_event) Q_DECL_OVERRIDE;
// Drop the data.
void dropEvent(QDropEvent *p_event) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent *p_event) Q_DECL_OVERRIDE;
private: private:
void init(); void init();
// Get VButtonWithWidget from m_popupWidget.
VButtonPopupWidget *getButtonPopupWidget() const;
QWidget *m_popupWidget; QWidget *m_popupWidget;
QColor m_bubbleBg;
QColor m_bubbleFg;
// String to display in the bubble.
// Empty to hide bubble.
QString m_bubbleStr;
}; };
inline VButtonPopupWidget *VButtonWithWidget::getButtonPopupWidget() const
{
return dynamic_cast<VButtonPopupWidget *>(m_popupWidget);
}
#endif // VBUTTONWITHWIDGET_H #endif // VBUTTONWITHWIDGET_H

View File

@ -171,6 +171,13 @@ bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers)
break; break;
} }
case Qt::Key_A:
{
// Show attachment list of current note.
m_mainWindow->showAttachmentList();
break;
}
case Qt::Key_D: case Qt::Key_D:
// Locate current tab. // Locate current tab.
if (m_mainWindow->locateCurrentFile()) { if (m_mainWindow->locateCurrentFile()) {

View File

@ -976,7 +976,10 @@ void VEditWindow::dragEnterEvent(QDragEnterEvent *p_event)
{ {
if (p_event->mimeData()->hasFormat("text/uri-list")) { if (p_event->mimeData()->hasFormat("text/uri-list")) {
p_event->acceptProposedAction(); p_event->acceptProposedAction();
return;
} }
QTabWidget::dragEnterEvent(p_event);
} }
void VEditWindow::dropEvent(QDropEvent *p_event) void VEditWindow::dropEvent(QDropEvent *p_event)
@ -1004,5 +1007,8 @@ void VEditWindow::dropEvent(QDropEvent *p_event)
} }
p_event->acceptProposedAction(); p_event->acceptProposedAction();
return;
} }
QTabWidget::dropEvent(p_event);
} }

View File

@ -344,16 +344,9 @@ void VMainWindow::initNoteToolBar(QSize p_iconSize)
"", "",
m_attachmentList, m_attachmentList,
this); this);
m_attachmentBtn->setToolTip(tr("Attachments")); m_attachmentBtn->setToolTip(tr("Attachments (drag files here to add attachments)"));
m_attachmentBtn->setStatusTip(tr("Manage current note's attachments"));
m_attachmentBtn->setProperty("CornerBtn", true); m_attachmentBtn->setProperty("CornerBtn", true);
m_attachmentBtn->setFocusPolicy(Qt::NoFocus); m_attachmentBtn->setFocusPolicy(Qt::NoFocus);
connect(m_attachmentBtn, &VButtonWithWidget::popupWidgetAboutToShow,
this, [this]() {
m_attachmentList->setFile(dynamic_cast<VNoteFile *>(m_curFile.data()));
});
m_attachmentBtn->setEnabled(false); m_attachmentBtn->setEnabled(false);
noteToolBar->addWidget(m_attachmentBtn); noteToolBar->addWidget(m_attachmentBtn);
@ -1552,12 +1545,6 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file,
noteInfoAct->setEnabled(p_file && !systemFile); noteInfoAct->setEnabled(p_file && !systemFile);
m_attachmentBtn->setEnabled(p_file && p_file->getType() == FileType::Note); m_attachmentBtn->setEnabled(p_file && p_file->getType() == FileType::Note);
if (m_attachmentBtn->isEnabled()
&& !dynamic_cast<const VNoteFile *>(p_file)->getAttachments().isEmpty()) {
m_attachmentBtn->setIcon(QIcon(":/resources/icons/attachment_full.svg"));
} else {
m_attachmentBtn->setIcon(QIcon(":/resources/icons/attachment.svg"));
}
m_insertImageAct->setEnabled(p_file && p_editMode); m_insertImageAct->setEnabled(p_file && p_editMode);
@ -1589,6 +1576,8 @@ void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
updateActionStateFromTabStatusChange(m_curFile, editMode); updateActionStateFromTabStatusChange(m_curFile, editMode);
m_attachmentList->setFile(dynamic_cast<VNoteFile *>(m_curFile.data()));
QString title; QString title;
if (m_curFile) { if (m_curFile) {
m_findReplaceDialog->updateState(m_curFile->getDocType(), editMode); m_findReplaceDialog->updateState(m_curFile->getDocType(), editMode);
@ -2193,3 +2182,10 @@ void VMainWindow::showMainWindow()
this->activateWindow(); this->activateWindow();
} }
void VMainWindow::showAttachmentList()
{
if (m_attachmentBtn->isEnabled()) {
m_attachmentBtn->showPopupWidget();
}
}

View File

@ -70,6 +70,9 @@ public:
// Show a temporary message in status bar. // Show a temporary message in status bar.
void showStatusMessage(const QString &p_msg); void showStatusMessage(const QString &p_msg);
// Popup the attachment list if it is enabled.
void showAttachmentList();
private slots: private slots:
void importNoteFromFile(); void importNoteFromFile();
void viewSettings(); void viewSettings();

View File

@ -124,7 +124,6 @@
<file>resources/icons/recycle_bin.svg</file> <file>resources/icons/recycle_bin.svg</file>
<file>resources/icons/empty_recycle_bin.svg</file> <file>resources/icons/empty_recycle_bin.svg</file>
<file>resources/icons/attachment.svg</file> <file>resources/icons/attachment.svg</file>
<file>resources/icons/attachment_full.svg</file>
<file>resources/icons/add_attachment.svg</file> <file>resources/icons/add_attachment.svg</file>
<file>resources/icons/clear_attachment.svg</file> <file>resources/icons/clear_attachment.svg</file>
<file>resources/icons/locate_attachment.svg</file> <file>resources/icons/locate_attachment.svg</file>