From fb4e818e20fb0dd72df3eb6fb81c232f2bfe2941 Mon Sep 17 00:00:00 2001 From: Le Tan Date: Tue, 26 Sep 2017 19:27:47 +0800 Subject: [PATCH] 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; --- src/resources/icons/attachment_full.svg | 14 --- src/vattachmentlist.cpp | 158 +++++++++++++++++++++++- src/vattachmentlist.h | 27 +++- src/vbuttonwithwidget.cpp | 81 ++++++++++++ src/vbuttonwithwidget.h | 61 +++++++++ src/vcaptain.cpp | 7 ++ src/veditwindow.cpp | 6 + src/vmainwindow.cpp | 24 ++-- src/vmainwindow.h | 3 + src/vnote.qrc | 1 - 10 files changed, 343 insertions(+), 39 deletions(-) delete mode 100644 src/resources/icons/attachment_full.svg diff --git a/src/resources/icons/attachment_full.svg b/src/resources/icons/attachment_full.svg deleted file mode 100644 index 7e400019..00000000 --- a/src/resources/icons/attachment_full.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - Layer 1 - - - - - - - - Layer 2 - - - diff --git a/src/vattachmentlist.cpp b/src/vattachmentlist.cpp index ab03830a..504e2c6f 100644 --- a/src/vattachmentlist.cpp +++ b/src/vattachmentlist.cpp @@ -28,6 +28,7 @@ void VAttachmentList::setupUI() m_addBtn = new QPushButton(QIcon(":/resources/icons/add_attachment.svg"), ""); m_addBtn->setToolTip(tr("Add")); m_addBtn->setProperty("FlatBtn", true); + m_addBtn->setDefault(true); connect(m_addBtn, &QPushButton::clicked, this, &VAttachmentList::addAttachment); @@ -68,6 +69,8 @@ void VAttachmentList::setupUI() } m_attachmentList->clear(); + + updateButtonState(); } } }); @@ -138,7 +141,8 @@ void VAttachmentList::initActions() void VAttachmentList::setFile(VNoteFile *p_file) { m_file = p_file; - updateContent(); + + updateButtonState(); } void VAttachmentList::updateContent() @@ -169,8 +173,13 @@ void VAttachmentList::updateContent() int cnt = m_attachmentList->count(); if (cnt > 0) { m_numLabel->setText(tr("%1 %2").arg(cnt).arg(cnt > 1 ? tr("Files") : tr("File"))); + m_attachmentList->setFocus(); } else { m_numLabel->setText(""); + + if (m_file) { + m_addBtn->setFocus(); + } } } @@ -204,13 +213,23 @@ void VAttachmentList::addAttachment() // Update lastPath lastPath = QFileInfo(files[0]).path(); + addAttachments(files); + + updateButtonState(); + + updateContent(); +} + +void VAttachmentList::addAttachments(const QStringList &p_files) +{ + Q_ASSERT(m_file); int addedFiles = 0; - for (int i = 0; i < files.size(); ++i) { - if (!m_file->addAttachment(files[i])) { + for (int i = 0; i < p_files.size(); ++i) { + if (!m_file->addAttachment(p_files[i])) { VUtils::showMessage(QMessageBox::Warning, tr("Warning"), tr("Fail to add attachment %1 for note %3.") - .arg(files[i]) + .arg(p_files[i]) .arg(g_config->c_dataTextStyle) .arg(m_file->getName()), "", @@ -222,8 +241,6 @@ void VAttachmentList::addAttachment() } } - updateContent(); - if (addedFiles > 0) { g_vnote->getMainWindow()->showStatusMessage(tr("Added %1 %2 as attachments") .arg(addedFiles) @@ -322,6 +339,8 @@ void VAttachmentList::deleteSelectedItems() g_vnote->getMainWindow()); } + updateButtonState(); + 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 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); +} diff --git a/src/vattachmentlist.h b/src/vattachmentlist.h index 27d7709c..9ed2be3a 100644 --- a/src/vattachmentlist.h +++ b/src/vattachmentlist.h @@ -3,7 +3,9 @@ #include #include +#include #include "vnotefile.h" +#include "vbuttonwithwidget.h" class QPushButton; class QListWidget; @@ -12,14 +14,29 @@ class QLabel; class VNoteFile; class QAction; -class VAttachmentList : public QWidget +class VAttachmentList : public QWidget, public VButtonPopupWidget { Q_OBJECT public: explicit VAttachmentList(QWidget *p_parent = 0); + // Need to call updateContent() to update the list. 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: void addAttachment(); @@ -38,11 +55,13 @@ private: void initActions(); - // Update attachment info of m_file. - void updateContent(); - void fillAttachmentList(const QVector &p_attachments); + void addAttachments(const QStringList &p_files); + + // Update the state of VButtonWithWidget. + void updateButtonState() const; + QPushButton *m_addBtn; QPushButton *m_clearBtn; QPushButton *m_locateBtn; diff --git a/src/vbuttonwithwidget.cpp b/src/vbuttonwithwidget.cpp index 53170d10..0506d4a1 100644 --- a/src/vbuttonwithwidget.cpp +++ b/src/vbuttonwithwidget.cpp @@ -1,6 +1,13 @@ #include "vbuttonwithwidget.h" #include +#include +#include +#include +#include +#include +#include +#include VButtonWithWidget::VButtonWithWidget(QWidget *p_widget, QWidget *p_parent) @@ -30,6 +37,9 @@ void VButtonWithWidget::init() { m_popupWidget->setParent(this); + m_bubbleFg = QColor(Qt::white); + m_bubbleBg = QColor("#15AE67"); + QMenu *menu = new QMenu(this); VButtonWidgetAction *act = new VButtonWidgetAction(m_popupWidget, menu); menu->addAction(act); @@ -39,6 +49,16 @@ void VButtonWithWidget::init() }); 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 @@ -50,3 +70,64 @@ void VButtonWithWidget::showPopupWidget() { 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(); +} diff --git a/src/vbuttonwithwidget.h b/src/vbuttonwithwidget.h index 42e1516e..42c3be50 100644 --- a/src/vbuttonwithwidget.h +++ b/src/vbuttonwithwidget.h @@ -6,6 +6,39 @@ #include #include +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 { Q_OBJECT @@ -47,14 +80,42 @@ public: // Show the popup widget. void showPopupWidget(); + // Set the bubble to display a number @p_num. + // @p_num: -1 to hide the bubble. + void setBubbleNumber(int p_num); + signals: // Emit when popup widget is about to show. 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: void init(); + // Get VButtonWithWidget from m_popupWidget. + VButtonPopupWidget *getButtonPopupWidget() const; + 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(m_popupWidget); +} + #endif // VBUTTONWITHWIDGET_H diff --git a/src/vcaptain.cpp b/src/vcaptain.cpp index 4b238f6b..9fc49504 100644 --- a/src/vcaptain.cpp +++ b/src/vcaptain.cpp @@ -171,6 +171,13 @@ bool VCaptain::handleKeyPress(int p_key, Qt::KeyboardModifiers p_modifiers) break; } + case Qt::Key_A: + { + // Show attachment list of current note. + m_mainWindow->showAttachmentList(); + break; + } + case Qt::Key_D: // Locate current tab. if (m_mainWindow->locateCurrentFile()) { diff --git a/src/veditwindow.cpp b/src/veditwindow.cpp index 0648c13c..2f692013 100644 --- a/src/veditwindow.cpp +++ b/src/veditwindow.cpp @@ -976,7 +976,10 @@ void VEditWindow::dragEnterEvent(QDragEnterEvent *p_event) { if (p_event->mimeData()->hasFormat("text/uri-list")) { p_event->acceptProposedAction(); + return; } + + QTabWidget::dragEnterEvent(p_event); } void VEditWindow::dropEvent(QDropEvent *p_event) @@ -1004,5 +1007,8 @@ void VEditWindow::dropEvent(QDropEvent *p_event) } p_event->acceptProposedAction(); + return; } + + QTabWidget::dropEvent(p_event); } diff --git a/src/vmainwindow.cpp b/src/vmainwindow.cpp index 39049bba..787ef81a 100644 --- a/src/vmainwindow.cpp +++ b/src/vmainwindow.cpp @@ -344,16 +344,9 @@ void VMainWindow::initNoteToolBar(QSize p_iconSize) "", m_attachmentList, this); - m_attachmentBtn->setToolTip(tr("Attachments")); - m_attachmentBtn->setStatusTip(tr("Manage current note's attachments")); + m_attachmentBtn->setToolTip(tr("Attachments (drag files here to add attachments)")); m_attachmentBtn->setProperty("CornerBtn", true); m_attachmentBtn->setFocusPolicy(Qt::NoFocus); - - connect(m_attachmentBtn, &VButtonWithWidget::popupWidgetAboutToShow, - this, [this]() { - m_attachmentList->setFile(dynamic_cast(m_curFile.data())); - }); - m_attachmentBtn->setEnabled(false); noteToolBar->addWidget(m_attachmentBtn); @@ -1552,12 +1545,6 @@ void VMainWindow::updateActionStateFromTabStatusChange(const VFile *p_file, noteInfoAct->setEnabled(p_file && !systemFile); m_attachmentBtn->setEnabled(p_file && p_file->getType() == FileType::Note); - if (m_attachmentBtn->isEnabled() - && !dynamic_cast(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); @@ -1589,6 +1576,8 @@ void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info) updateActionStateFromTabStatusChange(m_curFile, editMode); + m_attachmentList->setFile(dynamic_cast(m_curFile.data())); + QString title; if (m_curFile) { m_findReplaceDialog->updateState(m_curFile->getDocType(), editMode); @@ -2193,3 +2182,10 @@ void VMainWindow::showMainWindow() this->activateWindow(); } + +void VMainWindow::showAttachmentList() +{ + if (m_attachmentBtn->isEnabled()) { + m_attachmentBtn->showPopupWidget(); + } +} diff --git a/src/vmainwindow.h b/src/vmainwindow.h index bc505bf4..8b755200 100644 --- a/src/vmainwindow.h +++ b/src/vmainwindow.h @@ -70,6 +70,9 @@ public: // Show a temporary message in status bar. void showStatusMessage(const QString &p_msg); + // Popup the attachment list if it is enabled. + void showAttachmentList(); + private slots: void importNoteFromFile(); void viewSettings(); diff --git a/src/vnote.qrc b/src/vnote.qrc index 4293ae18..7c2b6ce6 100644 --- a/src/vnote.qrc +++ b/src/vnote.qrc @@ -124,7 +124,6 @@ resources/icons/recycle_bin.svg resources/icons/empty_recycle_bin.svg resources/icons/attachment.svg - resources/icons/attachment_full.svg resources/icons/add_attachment.svg resources/icons/clear_attachment.svg resources/icons/locate_attachment.svg