diff --git a/kmymoney/widgets/kmymoneyaccountcombo.cpp b/kmymoney/widgets/kmymoneyaccountcombo.cpp index 708c6493a..275db9ab5 100644 --- a/kmymoney/widgets/kmymoneyaccountcombo.cpp +++ b/kmymoney/widgets/kmymoneyaccountcombo.cpp @@ -1,356 +1,370 @@ /*************************************************************************** kmymoneyaccountbutton - description ------------------- begin : Mon May 31 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kmymoneyaccountcombo.h" // ---------------------------------------------------------------------------- // QT Includes #include #include #include #include // ---------------------------------------------------------------------------- // KDE Includes // ---------------------------------------------------------------------------- // Project Includes #include "mymoneyfile.h" #include "modelenums.h" class KMyMoneyAccountCombo::Private { public: Private(KMyMoneyAccountCombo* q) : m_q(q) , m_popupView(0) , m_inMakeCompletion(false) { m_q->setInsertPolicy(QComboBox::NoInsert); m_q->setMinimumWidth(m_q->fontMetrics().width(QLatin1Char('W')) * 15); m_q->setMaxVisibleItems(15); } KMyMoneyAccountCombo* m_q; QTreeView* m_popupView; QString m_lastSelectedAccount; bool m_inMakeCompletion; QString fullAccountName(const QAbstractItemModel* model, const QModelIndex& index, bool includeMainCategory = false) const; void selectFirstMatchingItem(); }; QString KMyMoneyAccountCombo::Private::fullAccountName(const QAbstractItemModel* model, const QModelIndex& _index, bool includeMainCategory) const { QString rc; if(_index.isValid()) { QModelIndex index = _index; QString sep; do { rc = QString("%1%2%3").arg(model->data(index).toString()).arg(sep).arg(rc); sep = QLatin1String(":"); index = index.parent(); } while(index.isValid()); if(!includeMainCategory) { QRegExp mainCategory(QString("[^%1]+%2(.*)").arg(sep).arg(sep)); if(mainCategory.exactMatch(rc)) { rc = mainCategory.cap(1); } } } return rc; } void KMyMoneyAccountCombo::Private::selectFirstMatchingItem() { if(m_popupView) { bool isBlocked = m_popupView->blockSignals(true); m_popupView->setCurrentIndex(QModelIndex()); for (auto i = 0; i < m_q->model()->rowCount(QModelIndex()); ++i) { QModelIndex childIndex = m_q->model()->index(i, 0); if (m_q->model()->hasChildren(childIndex)) { // search the first leaf do { childIndex = m_q->model()->index(0, 0, childIndex); } while(m_q->model()->hasChildren(childIndex)); // make it the current selection if it's selectable if(m_q->model()->flags(childIndex) & Qt::ItemIsSelectable) { m_popupView->setCurrentIndex(childIndex); } break; } } m_popupView->blockSignals(isBlocked); } } KMyMoneyAccountCombo::KMyMoneyAccountCombo(QSortFilterProxyModel *model, QWidget *parent) : KComboBox(parent) , d(new Private(this)) { setModel(model); } KMyMoneyAccountCombo::KMyMoneyAccountCombo(QWidget *parent) : KComboBox(parent) , d(new Private(this)) { } KMyMoneyAccountCombo::~KMyMoneyAccountCombo() { } void KMyMoneyAccountCombo::setEditable(bool isEditable) { KComboBox::setEditable(isEditable); // don't do the standard behavior if(lineEdit()) { lineEdit()->setObjectName("AccountComboLineEdit"); connect(lineEdit(), &QLineEdit::textEdited, this, &KMyMoneyAccountCombo::makeCompletion); } } void KMyMoneyAccountCombo::wheelEvent(QWheelEvent *ev) { Q_UNUSED(ev) // don't change anything with the help of the wheel, yet (due to the tree model) } void KMyMoneyAccountCombo::expandAll() { if (d->m_popupView) d->m_popupView->expandAll(); } void KMyMoneyAccountCombo::collapseAll() { if (d->m_popupView) d->m_popupView->collapseAll(); } void KMyMoneyAccountCombo::activated() { QVariant data = view()->currentIndex().data((int)eAccountsModel::Role::ID); if (data.isValid()) { setSelected(data.toString()); } } bool KMyMoneyAccountCombo::eventFilter(QObject* o, QEvent* e) { if(isEditable() && o == d->m_popupView) { // propagate all relevant key press events to the lineEdit widget if(e->type() == QEvent::KeyPress) { QKeyEvent* kev = static_cast(e); bool forLineEdit = (kev->text().length() > 0); switch(kev->key()) { case Qt::Key_Escape: case Qt::Key_Up: case Qt::Key_Down: forLineEdit = false; break; default: break; } if(forLineEdit) { return lineEdit()->event(e); } } else if(e->type() == QEvent::KeyRelease) { QKeyEvent* kev = static_cast(e); switch(kev->key()) { case Qt::Key_Enter: case Qt::Key_Return: activated(); hidePopup(); break; } } else if(e->type() == QEvent::FocusOut) { // if we tab out and have a selection in the popup view // than we use that entry completely activated(); hidePopup(); } } return KComboBox::eventFilter(o, e); } void KMyMoneyAccountCombo::setSelected(const QString& id) { // make sure, we have all items available for search if(isEditable()) { lineEdit()->clear(); } - // find which item has this id and set is as the current item + // find which item has this id and set it as the current item QModelIndexList list = model()->match(model()->index(0, 0), (int)eAccountsModel::Role::ID, QVariant(id), 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap | Qt::MatchRecursive)); // CAUTION: Without Qt::MatchWrap no results for credit card, so nothing happens in ledger view if (!list.isEmpty()) { // make sure the popup is closed from here on hidePopup(); d->m_lastSelectedAccount = id; QModelIndex index = list.front(); if(isEditable()) { lineEdit()->setText(d->fullAccountName(model(), index)); } else { // ensure that combobox is properly set when KMyMoneyAccountCombo::setSelected is called programmatically blockSignals(true); setRootModelIndex(index.parent()); setCurrentIndex(index.row()); setRootModelIndex(QModelIndex()); blockSignals(false); } emit accountSelected(id); } } const QString& KMyMoneyAccountCombo::getSelected() const { return d->m_lastSelectedAccount; } void KMyMoneyAccountCombo::setModel(QSortFilterProxyModel *model) { + // CAUTION! Assumption is being made that Account column number is always 0 + if ((int)eAccountsModel::Column::Account != 0) { + qFatal("eAccountsModel::Column::Account must be 0 in modelenums.h"); + } + + // since we create a new popup view, we get rid of an existing one delete d->m_popupView; + // call base class implementation KComboBox::setModel(model); - model->setFilterKeyColumn((int)eAccountsModel::Column::Account); // CAUTION! Assumption is being made that Account column number is always 0 + + // setup filtering criteria + model->setFilterKeyColumn((int)eAccountsModel::Column::Account); model->setFilterRole((int)eAccountsModel::Role::FullName); + // create popup view, attach model and allow to select a single item d->m_popupView = new QTreeView(this); d->m_popupView->setModel(model); d->m_popupView->setSelectionMode(QAbstractItemView::SingleSelection); setView(d->m_popupView); + // setup view parameters d->m_popupView->setHeaderHidden(true); d->m_popupView->setRootIsDecorated(true); d->m_popupView->setAlternatingRowColors(true); d->m_popupView->setAnimated(true); d->m_popupView->expandAll(); + // setup signal connections connect(d->m_popupView, &QAbstractItemView::activated, this, &KMyMoneyAccountCombo::selectItem); if(isEditable()) { connect(lineEdit(), &QLineEdit::textEdited, this, &KMyMoneyAccountCombo::makeCompletion); } else { connect(this, static_cast(&KMyMoneyAccountCombo::KComboBox::activated), this, &KMyMoneyAccountCombo::activated); } } void KMyMoneyAccountCombo::selectItem(const QModelIndex& index) { if(index.isValid() && (model()->flags(index) & Qt::ItemIsSelectable)) { setSelected(model()->data(index, (int)eAccountsModel::Role::ID).toString()); } } void KMyMoneyAccountCombo::makeCompletion(const QString& txt) { if(!d->m_inMakeCompletion) { d->m_inMakeCompletion = true; AccountNamesFilterProxyModel* filterModel = qobject_cast(model()); if(filterModel) { const auto completionStr = QStringLiteral(".*"); if (txt.contains(MyMoneyFile::AccountSeperator) == 0) { // for some reason it helps to avoid internal errors if we // clear the filter before setting it to a new value filterModel->setFilterFixedString(QString()); const auto filterString = QString::fromLatin1("%1%2%3").arg(completionStr).arg(QRegExp::escape(txt)).arg(completionStr); filterModel->setFilterRegExp(QRegExp(filterString, Qt::CaseInsensitive)); } else { QStringList parts = txt.split(MyMoneyFile::AccountSeperator /*, QString::SkipEmptyParts */); QString pattern; QStringList::iterator it; for (it = parts.begin(); it != parts.end(); ++it) { if (pattern.length() > 1) pattern += MyMoneyFile::AccountSeperator; pattern += QRegExp::escape(QString(*it).trimmed()) + completionStr; } // for some reason it helps to avoid internal errors if we // clear the filter before setting it to a new value filterModel->setFilterFixedString(QString()); filterModel->setFilterRegExp(QRegExp(pattern, Qt::CaseInsensitive)); // if we don't have a match, we try it again, but this time // we add a wildcard for the top level if (filterModel->visibleItems() == 0) { // for some reason it helps to avoid internal errors if we // clear the filter before setting it to a new value pattern = pattern.prepend(completionStr + MyMoneyFile::AccountSeperator); filterModel->setFilterFixedString(QString()); filterModel->setFilterRegExp(QRegExp(pattern, Qt::CaseInsensitive)); } } // if nothing is shown, we might as well close the popup switch(filterModel->visibleItems()) { case 0: hidePopup(); break; default: setMaxVisibleItems(15); expandAll(); showPopup(); break; } d->selectFirstMatchingItem(); // keep current text in edit widget no matter what bool blocked = lineEdit()->signalsBlocked(); lineEdit()->blockSignals(true); lineEdit()->setText(txt); lineEdit()->blockSignals(blocked); } d->m_inMakeCompletion = false; } } void KMyMoneyAccountCombo::showPopup() { if(d->m_popupView) { d->m_popupView->show(); d->m_popupView->installEventFilter(this); } KComboBox::showPopup(); } void KMyMoneyAccountCombo::hidePopup() { if(d->m_popupView) { d->m_popupView->hide(); d->m_popupView->removeEventFilter(this); } KComboBox::hidePopup(); } + +// kate: space-indent on; indent-width 2; remove-trailing-space on; remove-trailing-space-save on; diff --git a/kmymoney/widgets/kmymoneyaccountcombo.h b/kmymoney/widgets/kmymoneyaccountcombo.h index 0a0d0cd5c..5a38acb1d 100644 --- a/kmymoney/widgets/kmymoneyaccountcombo.h +++ b/kmymoney/widgets/kmymoneyaccountcombo.h @@ -1,168 +1,180 @@ /*************************************************************************** kmymoneyaccountcombo - description ------------------- begin : Mon May 31 2004 copyright : (C) 2000-2004 by Michael Edwardes email : mte@users.sourceforge.net Javier Campos Morales Felix Rodriguez John C Thomas Baumgart Kevin Tambascio (C) 2017 by Łukasz Wojniłowicz ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KMYMONEYACCOUNTCOMBO_H #define KMYMONEYACCOUNTCOMBO_H // ---------------------------------------------------------------------------- // QT Includes // ---------------------------------------------------------------------------- // KDE Includes #include // ---------------------------------------------------------------------------- // Project Includes #include "accountsproxymodel.h" #include "onlinebankingaccountsfilterproxymodel.h" /** * A proxy model used to filter all the data from the core accounts model leaving * only the name of the accounts so this model can be used in the account * completion combo. * * It shows only the first column (account name) and makes top level items non-selectable. * * @see AccountsModel * @see AccountsFilterProxyModel * * @author Cristian Onet 2010 * @author Christian David */ template class AccountNamesFilterProxyModelTpl : public baseProxyModel { public: explicit AccountNamesFilterProxyModelTpl(QObject *parent = 0); virtual Qt::ItemFlags flags(const QModelIndex &index) const; protected: bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const; }; /** * @brief "typedef" for AccountNamesFilterProxyModelTpl * * To create valid Qt moc data this class inherits the template and uses Q_OBJECT. * - * @code typedef AccountNamesFilterProxyModelTpl AccountNamesFilterProxyModel; - * should work as well + * @code + * typedef AccountNamesFilterProxyModelTpl AccountNamesFilterProxyModel; + * @endcode + * + * should work as well. */ class AccountNamesFilterProxyModel : public AccountNamesFilterProxyModelTpl { Q_OBJECT public: explicit AccountNamesFilterProxyModel(QObject* parent = 0) : AccountNamesFilterProxyModelTpl< AccountsProxyModel >(parent) {} }; /** * @brief OnlineBankingAccountFilterProxyModel showing only the name column * * Is equivalent to AccountNamesFilterProxyModel using OnlineBankingAccountFilterProxyModel as base. */ typedef AccountNamesFilterProxyModelTpl OnlineBankingAccountNamesFilterProxyModel; /** + * @brief A general account selection widget based on a KComboBox + * + * This widget allows to select an account from the provided set of accounts. This + * set is passed as model in the constructor or via setModel(). In case the widget + * is configured to be editable via setEditable() the combo box contains a lineedit + * widget. This lineedit provides auto completion. * + * In addition to the KComboBox which supports a list view popup, this widget + * provides a tree view popup to show the account hierarchy. * * @author Cristian Onet */ class KMyMoneyAccountCombo : public KComboBox { Q_OBJECT Q_DISABLE_COPY(KMyMoneyAccountCombo) public: explicit KMyMoneyAccountCombo(QSortFilterProxyModel *model, QWidget* parent = nullptr); explicit KMyMoneyAccountCombo(QWidget* parent = nullptr); ~KMyMoneyAccountCombo(); void setSelected(const QString& id); const QString& getSelected() const; void setModel(QSortFilterProxyModel *model); /** * Overridden to get specific behavior */ void setEditable(bool isEditable); bool eventFilter(QObject* o, QEvent* e) override; public Q_SLOTS: void expandAll(); void collapseAll(); void showPopup() override; void hidePopup() override; protected: void wheelEvent(QWheelEvent *ev) override; protected Q_SLOTS: void activated(); void makeCompletion(const QString& txt) override; void selectItem(const QModelIndex& index); Q_SIGNALS: void accountSelected(const QString&); private: class Private; QScopedPointer const d; }; template AccountNamesFilterProxyModelTpl::AccountNamesFilterProxyModelTpl(QObject *parent) : baseProxyModel(parent) { } /** * Top items are not selectable because they are not real accounts but are only used for grouping. */ template Qt::ItemFlags AccountNamesFilterProxyModelTpl::flags(const QModelIndex &index) const { if (!index.parent().isValid()) return baseProxyModel::flags(index) & ~Qt::ItemIsSelectable; return baseProxyModel::flags(index); } /** * Filter all but the first column. */ template bool AccountNamesFilterProxyModelTpl::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const { Q_UNUSED(source_parent) if (source_column == 0) return true; return false; } #endif +// kate: space-indent on; indent-width 2; remove-trailing-space on; remove-trailing-space-save on;