From 4da9f6d31e4a606da7f0def3a58197096c816e33 Mon Sep 17 00:00:00 2001 From: Tobias Markmann Date: Sat, 4 Oct 2014 21:43:56 +0200 Subject: Fix user experience issues related to blocking list editor. This commit enables complete keyboard accessibility for editing the block list including list navigation via cursor keys, editing via enter key and deletion via backspace. The placeholder item for adding new items is now non-removable and is indicated as such. The 'Save'-button is disabled during processing of a request. The window is closed on 'Save' if no changes have been made or the changes have been applied successfully. On failure the error message is shown in the window. A description text has been added at the top to tell the user about the use of the dialog. Test-Information: Success cases have been tested by running Swift and do change the blocking list via mouse and keyboard and doing no changes at all. Error cases have been tested using a server adjustment which replys with IQ errors on any blocklist change. Change-Id: I028a9dd15e66ba7363a30b66c5d5a15ba2a5a518 diff --git a/Swift/Controllers/BlockListController.cpp b/Swift/Controllers/BlockListController.cpp index d778883..9cd42f0 100644 --- a/Swift/Controllers/BlockListController.cpp +++ b/Swift/Controllers/BlockListController.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include #include @@ -57,6 +63,7 @@ void BlockListController::handleUIEvent(boost::shared_ptr rawEvent) { } blockListBeforeEdit = blockListManager_->getBlockList()->getItems(); blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit); + blockListEditorWidget_->setError(""); blockListEditorWidget_->show(); return; } @@ -85,12 +92,19 @@ void BlockListController::handleBlockResponse(GenericRequest::ref if (!error->getText().empty()) { errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); } - eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + } } if (originEditor) { remainingRequests_--; - if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); } } } @@ -103,12 +117,19 @@ void BlockListController::handleUnblockResponse(GenericRequest:: if (!error->getText().empty()) { errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText()); } - eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + if (blockListEditorWidget_ && originEditor) { + blockListEditorWidget_->setError(errorMessage); + blockListEditorWidget_->setBusy(false); + } + else { + eventController_->handleIncomingEvent(boost::make_shared(request->getReceiver(), errorMessage)); + } } if (originEditor) { remainingRequests_--; - if (blockListEditorWidget_ && (remainingRequests_ == 0)) { + if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) { blockListEditorWidget_->setBusy(false); + blockListEditorWidget_->hide(); } } } @@ -131,9 +152,12 @@ void BlockListController::handleSetNewBlockList(const std::vector &newBlock unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true)); unblockRequest->send(); } - if (!jidsToBlock.empty() || jidsToUnblock.empty()) { + if (!jidsToBlock.empty() || !jidsToUnblock.empty()) { assert(blockListEditorWidget_); blockListEditorWidget_->setBusy(true); + blockListEditorWidget_->setError(""); + } else { + blockListEditorWidget_->hide(); } } diff --git a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h index 60a1c11..f8a4c59 100644 --- a/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h +++ b/Swift/Controllers/UIInterfaces/BlockListEditorWidget.h @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include @@ -20,9 +26,11 @@ namespace Swift { virtual ~BlockListEditorWidget() {} virtual void show() = 0; + virtual void hide() = 0; virtual void setCurrentBlockList(const std::vector& blockedJIDs) = 0; virtual void setBusy(bool isBusy) = 0; + virtual void setError(const std::string&) = 0; virtual std::vector getCurrentBlockList() const = 0; diff --git a/Swift/QtUI/QtBlockListEditorWindow.cpp b/Swift/QtUI/QtBlockListEditorWindow.cpp index a759a3f..2484eb7 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.cpp +++ b/Swift/QtUI/QtBlockListEditorWindow.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include #include @@ -15,10 +21,10 @@ #include #include +#include #include -#include #include -#include +#include #include namespace Swift { @@ -66,18 +72,22 @@ class QtJIDValidatedItemDelegate : public QItemDelegate { } }; -QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow) { +QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlockListEditorWindow), removeItemDelegate(0), editItemDelegate(0) { ui->setupUi(this); + + freshBlockListTemplate = tr("Click to add contact"); + new QShortcut(QKeySequence::Close, this, SLOT(close())); ui->throbberLabel->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), this)); - itemDelegate = new QtRemovableItemDelegate(style()); + removeItemDelegate = new QtRemovableItemDelegate(style()); + editItemDelegate = new QtJIDValidatedItemDelegate(this); connect(ui->savePushButton, SIGNAL(clicked()), SLOT(applyChanges())); ui->blockListTreeWidget->setColumnCount(2); ui->blockListTreeWidget->header()->setStretchLastSection(false); - ui->blockListTreeWidget->header()->resizeSection(1, itemDelegate->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); + ui->blockListTreeWidget->header()->resizeSection(1, removeItemDelegate->sizeHint(QStyleOptionViewItem(), QModelIndex()).width()); #if QT_VERSION >= 0x050000 ui->blockListTreeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch); @@ -87,12 +97,13 @@ QtBlockListEditorWindow::QtBlockListEditorWindow() : QWidget(), ui(new Ui::QtBlo ui->blockListTreeWidget->setHeaderHidden(true); ui->blockListTreeWidget->setRootIsDecorated(false); - ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked); - ui->blockListTreeWidget->setItemDelegateForColumn(0, new QtJIDValidatedItemDelegate(this)); - ui->blockListTreeWidget->setItemDelegateForColumn(1, itemDelegate); + ui->blockListTreeWidget->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::SelectedClicked | QAbstractItemView::EditKeyPressed); + ui->blockListTreeWidget->setItemDelegateForColumn(0, editItemDelegate); + ui->blockListTreeWidget->setItemDelegateForColumn(1, removeItemDelegate); connect(ui->blockListTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(handleItemChanged(QTreeWidgetItem*,int))); + ui->blockListTreeWidget->installEventFilter(this); - QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(freshBlockListTemplate) << "x"); item->setFlags(item->flags() | Qt::ItemIsEditable); ui->blockListTreeWidget->addTopLevelItem(item); } @@ -101,31 +112,49 @@ QtBlockListEditorWindow::~QtBlockListEditorWindow() { } void QtBlockListEditorWindow::show() { - QWidget::show(); + QWidget::showNormal(); QWidget::activateWindow(); + QWidget::raise(); +} + +void QtBlockListEditorWindow::hide() { + QWidget::hide(); } -void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *, int) { +void QtBlockListEditorWindow::handleItemChanged(QTreeWidgetItem *item, int) { + // check whether changed item contains a valid JID and make it removable + if (item && item->text(0) != freshBlockListTemplate) { + item->setText(1, ""); + } + + // check for empty rows and add an empty one so the user can add items bool hasEmptyRow = false; - QList rows = ui->blockListTreeWidget->findItems("", Qt::MatchFixedString); - foreach(QTreeWidgetItem* row, rows) { - if (row->text(0).isEmpty()) { + for( int i = 0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i ) { + QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i); + if (row->text(0) == freshBlockListTemplate) { hasEmptyRow = true; } + else if (row->text(0).isEmpty()) { + ui->blockListTreeWidget->removeItemWidget(row, 0); + } } if (!hasEmptyRow) { - QTreeWidgetItem* item = new QTreeWidgetItem(QStringList("") << ""); + QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(freshBlockListTemplate) << "x"); item->setFlags(item->flags() | Qt::ItemIsEditable); ui->blockListTreeWidget->addTopLevelItem(item); } + + if (!item) { + ui->blockListTreeWidget->setCurrentItem(ui->blockListTreeWidget->topLevelItem(0)); + } } void QtBlockListEditorWindow::applyChanges() { onSetNewBlockList(getCurrentBlockList()); } -void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector &blockedJIDs) { +void QtBlockListEditorWindow::setCurrentBlockList(const std::vector &blockedJIDs) { ui->blockListTreeWidget->clear(); foreach(const JID& jid, blockedJIDs) { @@ -136,13 +165,26 @@ void Swift::QtBlockListEditorWindow::setCurrentBlockList(const std::vector handleItemChanged(0,0); } -void Swift::QtBlockListEditorWindow::setBusy(bool isBusy) { +void QtBlockListEditorWindow::setBusy(bool isBusy) { if (isBusy) { ui->throbberLabel->movie()->start(); ui->throbberLabel->show(); + ui->blockListTreeWidget->setEnabled(false); + ui->savePushButton->setEnabled(false); } else { ui->throbberLabel->movie()->stop(); ui->throbberLabel->hide(); + ui->blockListTreeWidget->setEnabled(true); + ui->savePushButton->setEnabled(true); + } +} + +void QtBlockListEditorWindow::setError(const std::string& error) { + if (!error.empty()) { + ui->errorLabel->setText("" + QtUtilities::htmlEscape(P2QSTRING(error)) + ""); + } + else { + ui->errorLabel->setText(""); } } @@ -151,11 +193,34 @@ std::vector Swift::QtBlockListEditorWindow::getCurrentBlockList() const { for(int i=0; i < ui->blockListTreeWidget->topLevelItemCount(); ++i) { QTreeWidgetItem* row = ui->blockListTreeWidget->topLevelItem(i); - if (!row->text(0).isEmpty()) { - futureBlockedJIDs.push_back(JID(Q2PSTRING(row->text(0)))); + JID jid = JID(Q2PSTRING(row->text(0))); + if (!jid.toString().empty() && jid.isValid()) { + futureBlockedJIDs.push_back(jid); } } return futureBlockedJIDs; } +bool QtBlockListEditorWindow::eventFilter(QObject* target, QEvent* event) { + if (target == ui->blockListTreeWidget) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Backspace) { + // remove currently selected item + QTreeWidgetItem* currentItem = ui->blockListTreeWidget->currentItem(); + if (currentItem->text(0) != freshBlockListTemplate) { + ui->blockListTreeWidget->takeTopLevelItem(ui->blockListTreeWidget->indexOfTopLevelItem(currentItem)); + return true; + } + } + else if (keyEvent->key() == Qt::Key_Return) { + // open editor for return key d + ui->blockListTreeWidget->editItem(ui->blockListTreeWidget->currentItem(), 0); + return true; + } + } + } + return QWidget::eventFilter(target, event); +} + } diff --git a/Swift/QtUI/QtBlockListEditorWindow.h b/Swift/QtUI/QtBlockListEditorWindow.h index 4b124a3..dd083fd 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.h +++ b/Swift/QtUI/QtBlockListEditorWindow.h @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #pragma once #include @@ -18,6 +24,8 @@ namespace Ui { namespace Swift { +class QtJIDValidatedItemDelegate; + class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { Q_OBJECT @@ -26,9 +34,12 @@ class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { virtual ~QtBlockListEditorWindow(); virtual void show(); + virtual void hide(); virtual void setCurrentBlockList(const std::vector& blockedJIDs); virtual void setBusy(bool isBusy); + virtual void setError(const std::string& error); virtual std::vector getCurrentBlockList() const; + virtual bool eventFilter(QObject* target, QEvent* event); private slots: void handleItemChanged(QTreeWidgetItem*, int); @@ -36,7 +47,9 @@ class QtBlockListEditorWindow : public QWidget, public BlockListEditorWidget { private: Ui::QtBlockListEditorWindow* ui; - QtRemovableItemDelegate* itemDelegate; + QtRemovableItemDelegate* removeItemDelegate; + QtJIDValidatedItemDelegate* editItemDelegate; + QString freshBlockListTemplate; }; } diff --git a/Swift/QtUI/QtBlockListEditorWindow.ui b/Swift/QtUI/QtBlockListEditorWindow.ui index f71bbae..a611890 100644 --- a/Swift/QtUI/QtBlockListEditorWindow.ui +++ b/Swift/QtUI/QtBlockListEditorWindow.ui @@ -6,7 +6,7 @@ 0 0 - 348 + 333 262 @@ -17,9 +17,34 @@ 5 - + 5 + + 5 + + + 5 + + + 5 + + + + + <html><head/><body><p align="justify">The follwing list shows all contacts that you have currently blocked. You can add contacts to the list at the buttom of the list and remove contacts by clicking on the right column.</p></body></html> + + + Qt::RichText + + + Qt::AlignJustify|Qt::AlignVCenter + + + true + + + @@ -38,19 +63,6 @@ QLayout::SetDefaultConstraint - - - Qt::Horizontal - - - - 40 - 20 - - - - - @@ -71,6 +83,19 @@ + + + Qt::Horizontal + + + + 40 + 20 + + + + + Save diff --git a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp index 1cae00a..d50585c 100644 --- a/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp +++ b/Swift/QtUI/QtVCardWidget/QtRemovableItemDelegate.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + #include "QtRemovableItemDelegate.h" #include #include @@ -15,7 +21,7 @@ QtRemovableItemDelegate::QtRemovableItemDelegate(const QStyle* style) : style(st } -void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex&) const { +void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QStyleOption opt; opt.state = option.state; opt.state |= QStyle::State_AutoRaise; @@ -25,12 +31,14 @@ void QtRemovableItemDelegate::paint(QPainter* painter, const QStyleOptionViewIte opt.rect = option.rect; painter->save(); painter->fillRect(option.rect, option.state & QStyle::State_Selected ? option.palette.highlight() : option.palette.base()); + if (index.data().toString().isEmpty()) { #ifdef SWIFTEN_PLATFORM_MACOSX - // workaround for Qt not painting relative to the cell we're in, on OS X - int height = style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0); - painter->translate(option.rect.x(), option.rect.y() + (option.rect.height() - height)/2); + // workaround for Qt not painting relative to the cell we're in, on OS X + int height = style->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, 0); + painter->translate(option.rect.x(), option.rect.y() + (option.rect.height() - height)/2); #endif - style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter); + style->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, painter); + } painter->restore(); } @@ -39,7 +47,7 @@ QWidget* QtRemovableItemDelegate::createEditor(QWidget*, const QStyleOptionViewI } bool QtRemovableItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) { - if (event->type() == QEvent::MouseButtonRelease) { + if (index.data().toString().isEmpty() && event->type() == QEvent::MouseButtonRelease) { model->removeRow(index.row()); return true; } else { -- cgit v0.10.2-6-g49f6