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