Browse Source

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;
Le Tan 8 years ago
parent
commit
fb4e818e20

+ 0 - 14
src/resources/icons/attachment_full.svg

@@ -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>

+ 152 - 6
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 <span style=\"%2\">%3</span>.")
-                                  .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<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);
+}

+ 23 - 4
src/vattachmentlist.h

@@ -3,7 +3,9 @@
 
 #include <QWidget>
 #include <QVector>
+#include <QStringList>
 #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<VAttachment> &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;

+ 81 - 0
src/vbuttonwithwidget.cpp

@@ -1,6 +1,13 @@
 #include "vbuttonwithwidget.h"
 
 #include <QMenu>
+#include <QDragEnterEvent>
+#include <QDropEvent>
+#include <QMimeData>
+#include <QRect>
+#include <QPainter>
+#include <QPainterPath>
+#include <QBrush>
 
 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();
+}

+ 61 - 0
src/vbuttonwithwidget.h

@@ -6,6 +6,39 @@
 #include <QIcon>
 #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
 {
     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<VButtonPopupWidget *>(m_popupWidget);
+}
+
 #endif // VBUTTONWITHWIDGET_H

+ 7 - 0
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()) {

+ 6 - 0
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);
 }

+ 10 - 14
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<VNoteFile *>(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<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);
 
@@ -1589,6 +1576,8 @@ void VMainWindow::handleAreaTabStatusUpdated(const VEditTabInfo &p_info)
 
     updateActionStateFromTabStatusChange(m_curFile, editMode);
 
+    m_attachmentList->setFile(dynamic_cast<VNoteFile *>(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();
+    }
+}

+ 3 - 0
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();

+ 0 - 1
src/vnote.qrc

@@ -124,7 +124,6 @@
         <file>resources/icons/recycle_bin.svg</file>
         <file>resources/icons/empty_recycle_bin.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/clear_attachment.svg</file>
         <file>resources/icons/locate_attachment.svg</file>