diff --git a/src/delegates/pixmapmanipulationdelegate.cpp b/src/delegates/pixmapmanipulationdelegate.cpp index 770cd7c392830d77a7b89b91ff6113c305ee1bc1..0b376defae43932d8002f9a111c78af6d6a43484 100644 --- a/src/delegates/pixmapmanipulationdelegate.cpp +++ b/src/delegates/pixmapmanipulationdelegate.cpp @@ -17,6 +17,8 @@ ***************************************************************************/ #include "pixmapmanipulationdelegate.h" +#include <useractionmodel.h> + #include <QtCore/QSize> PixmapManipulationDelegate* PixmapManipulationDelegate::m_spInstance = new PixmapManipulationDelegate(); @@ -80,3 +82,9 @@ QVariant PixmapManipulationDelegate::profilePhoto(const QByteArray& data) Q_UNUSED(data) return QVariant(); } + +QVariant PixmapManipulationDelegate::userActionIcon(const UserActionElement& state) const +{ + Q_UNUSED(state) + return QVariant(); +} diff --git a/src/delegates/pixmapmanipulationdelegate.h b/src/delegates/pixmapmanipulationdelegate.h index 471421e98ef4cdfbbcca9b3ec366ef918d131d0b..36ef5fe0880f08cfbabe8340b3d58af12664e770 100644 --- a/src/delegates/pixmapmanipulationdelegate.h +++ b/src/delegates/pixmapmanipulationdelegate.h @@ -24,9 +24,10 @@ #include <QtCore/QModelIndex> //Ring -class Person ; -class ContactMethod; -class Call ; +class Person ; +class ContactMethod ; +class Call ; +struct UserActionElement; /** * Different clients can have multiple way of displaying images. Some may @@ -42,14 +43,18 @@ class LIB_EXPORT PixmapManipulationDelegate { public: PixmapManipulationDelegate(); virtual ~PixmapManipulationDelegate() {} - virtual QVariant contactPhoto(Person* c, const QSize& size, bool displayPresence = true); - virtual QVariant callPhoto(Call* c, const QSize& size, bool displayPresence = true); - virtual QVariant callPhoto(const ContactMethod* n, const QSize& size, bool displayPresence = true); - virtual QVariant numberCategoryIcon(const QVariant& p, const QSize& size, bool displayPresence = false, bool isPresent = false); - virtual QVariant serurityIssueIcon(const QModelIndex& index); + virtual QVariant contactPhoto(Person* c, const QSize& size, bool displayPresence = true); + virtual QVariant callPhoto(Call* c, const QSize& size, bool displayPresence = true); + virtual QVariant callPhoto(const ContactMethod* n, const QSize& size, bool displayPresence = true); + virtual QVariant numberCategoryIcon(const QVariant& p, const QSize& size, bool displayPresence = false, bool isPresent = false); + virtual QVariant serurityIssueIcon(const QModelIndex& index); virtual QByteArray toByteArray(const QVariant& pxm); - virtual QVariant profilePhoto(const QByteArray& data); + virtual QVariant profilePhoto(const QByteArray& data); + /** + * Return the icons associated with the action and its state + */ + virtual QVariant userActionIcon(const UserActionElement& state) const; //Singleton static PixmapManipulationDelegate* instance(); diff --git a/src/private/useractions.h b/src/private/useractions.h new file mode 100644 index 0000000000000000000000000000000000000000..c6f7cf4585f8cfc76962b5d8e4d196eb5ca3180b --- /dev/null +++ b/src/private/useractions.h @@ -0,0 +1,180 @@ +/**************************************************************************** + * Copyright (C) 2012-2015 by Savoir-Faire Linux * + * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see <http://www.gnu.org/licenses/>. * + ***************************************************************************/ +#ifndef USERACTIONS_H +#define USERACTIONS_H + +/** + * This code used to be in the KDE client. It doesn't really fit well in well + * in what libringclient is supposed to do, but as it has to be replicated for + * each clients, then UserActionModel will provide an abstract way to call those + * functions. + */ +namespace UserActions { + +bool accept(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Calling when no item is selected. Opening an item."; + CallModel::instance()->dialingCall(); + CallModel::instance()->selectionModel()->setCurrentIndex(CallModel::instance()->getIndex(call), QItemSelectionModel::ClearAndSelect); + } + else { + const Call::State state = call->state(); + //TODO port to lifeCycle code + if (state == Call::State::RINGING || state == Call::State::CURRENT || state == Call::State::HOLD + || state == Call::State::BUSY || state == Call::State::FAILURE || state == Call::State::ERROR) { + qDebug() << "Calling when item currently ringing, current, hold or busy. Opening an item."; + Call* c2 = CallModel::instance()->dialingCall(); + CallModel::instance()->selectionModel()->setCurrentIndex(CallModel::instance()->getIndex(c2), QItemSelectionModel::ClearAndSelect); + } + else { + try { + call->performAction(Call::Action::ACCEPT); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + ret = false; + } + } + } + } + return ret; +} //accept + +///Call +bool hangup(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if (call) { + try { + call->performAction(Call::Action::REFUSE); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} //hangup + +///Refuse call +bool refuse(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Error : Hanging up when no item selected. Should not happen."; + } + else { + try { + call->performAction(Call::Action::REFUSE); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} + +///Put call on hold +bool hold(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Error : Holding when no item selected. Should not happen."; + } + else { + try { + call->performAction(Call::Action::HOLD); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} + +///Remove call from hold +bool unhold(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Error : Un-Holding when no item selected. Should not happen."; + } + else { + try { + call->performAction(Call::Action::HOLD); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} + +///Transfer a call +bool transfer(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Error : Transferring when no item selected. Should not happen."; + } + else { + try { + call->performAction(Call::Action::TRANSFER); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} + +///Record a call +bool record(const QList<Call*> calls) +{ + bool ret = true; + for (Call* call : calls) { + if(!call) { + qDebug() << "Error : Recording when no item selected. Should not happen."; + } + else { + try { + call->performAction(Call::Action::RECORD); + } + catch(const char * msg) { +// KMessageBox::error(Ring::app(),i18n(msg)); + } + } + } + return ret; +} + +}; //namespace UserActions + +#endif \ No newline at end of file diff --git a/src/useractionmodel.cpp b/src/useractionmodel.cpp index 95bb83b8e11a34924c464fa2a41b9043dfdd36be..e3c4f1b9d8e60588d12b10c117e276428d4587ce 100644 --- a/src/useractionmodel.cpp +++ b/src/useractionmodel.cpp @@ -19,12 +19,25 @@ //Qt #include <QtCore/QItemSelection> +#include <QtCore/QSortFilterProxyModel> //Ring #include "call.h" #include "callmodel.h" #include "account.h" #include "accountmodel.h" +#include "delegates/pixmapmanipulationdelegate.h" +#include "private/useractions.h" + +class ActiveUserActionModel : public QSortFilterProxyModel +{ +public: + ActiveUserActionModel(QAbstractItemModel* parent) : QSortFilterProxyModel(parent) + { + setSourceModel(parent); + } + virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; +}; class UserActionModelPrivate : public QObject { @@ -44,38 +57,42 @@ public: //Availability matrices UserActionModelPrivate(UserActionModel* parent); - static const TypedStateMachine< TypedStateMachine< bool , Call::State > , UserActionModel::Action > availableActionMap; - static const TypedStateMachine< TypedStateMachine< bool , Account::RegistrationState > , UserActionModel::Action > availableAccountActionMap; - static const TypedStateMachine< TypedStateMachine< bool , SelectionState > , UserActionModel::Action > multi_call_options; - static const TypedStateMachine< TypedStateMachine< bool , Account::Protocol > , UserActionModel::Action > availableProtocolActions; - static const TypedStateMachine< TypedStateMachine< UserActionModel::ActionStatfulnessLevel , SelectionState > , UserActionModel::Action > actionStatefulness; + static const TypedStateMachine< TypedStateMachine< bool , Call::State > , UserActionModel::Action > availableActionMap ; + static const TypedStateMachine< TypedStateMachine< bool , Account::RegistrationState > , UserActionModel::Action > availableAccountActionMap ; + static const TypedStateMachine< TypedStateMachine< bool , SelectionState > , UserActionModel::Action > multi_call_options ; + static const TypedStateMachine< bool , UserActionModel::Action > heterogenous_call_options ; + static const TypedStateMachine< TypedStateMachine< bool , Account::Protocol > , UserActionModel::Action > availableProtocolActions ; + static const TypedStateMachine< TypedStateMachine< UserActionModel::ActionStatfulnessLevel , SelectionState > , UserActionModel::Action > actionStatefulness ; //Helpers - void updateActions(); - bool updateAction(UserActionModel::Action action); - bool updateByCall(UserActionModel::Action action, const Call* c); + bool updateAction (UserActionModel::Action action ); + bool updateByCall (UserActionModel::Action action, const Call* c ); + void updateCheckMask(int& ret, UserActionModel::Action action, const Call* c ); //Attribues - Call* m_pCall; - UserActionModelMode m_Mode ; - SelectionState m_SelectionState; - TypedStateMachine< bool, UserActionModel::Action> m_CurrentActions; - static const TypedStateMachine< QString, UserActionModel::Action> m_ActionNames; + Call* m_pCall ; + UserActionModelMode m_Mode ; + SelectionState m_SelectionState ; + TypedStateMachine< bool, UserActionModel::Action> m_CurrentActions ; + TypedStateMachine< Qt::CheckState, UserActionModel::Action> m_CurrentActionsState; + static const TypedStateMachine< QString, UserActionModel::Action> m_ActionNames ; + ActiveUserActionModel* m_pActiveModel ; + + //The mute per call, per media is not merged upstream yet, faking it for now + //BEGIN HACK + bool m_MuteAudio_TO_REMOVE; + bool m_MuteVideo_TO_REMOVE; + //END HACK private: UserActionModel* q_ptr; public Q_SLOTS: //Single call mode - void slotStateChanged(); + void slotStateChanged(); //TODO //CallModel mode - void slotCurrentChanged (const QModelIndex& current , const QModelIndex& previous ); - void slotSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected); - void slotCallStateChanged ( Call* call, Call::State previousState ); - - //Common - void slotAccountStateChanged ( Account* account, const Account::RegistrationState state ); + void updateActions(); }; @@ -115,10 +132,13 @@ const TypedStateMachine< TypedStateMachine< bool , Account::RegistrationState > /* JOIN */ {{ true , true , true , true }}, }}; +/** + * This matrix define if an option is available depending on the number of selection elements + */ const TypedStateMachine< TypedStateMachine< bool , UserActionModelPrivate::SelectionState > , UserActionModel::Action > UserActionModelPrivate::multi_call_options = {{ /* NONE UNIQUE MULTI */ /* ACCEPT */ {{ false, true , true }}, - /* HOLD */ {{ false, true , true }}, + /* HOLD */ {{ false, true , false }}, /* MUTE_AUDIO */ {{ false, true , true }}, /* MUTE_VIDEO */ {{ false, true , true }}, /* SERVER_TRANSFER */ {{ false, true , true }}, @@ -128,6 +148,24 @@ const TypedStateMachine< TypedStateMachine< bool , UserActionModelPrivate::Selec /* JOIN */ {{ false, false, true }}, }}; +/** + * This matrix define if an option is available when multiple elements with mismatching CheckState are selected + */ +const TypedStateMachine< bool, UserActionModel::Action > UserActionModelPrivate::heterogenous_call_options = {{ + /* ACCEPT */ true , /* N/A */ + /* HOLD */ false , /* Do not allow to set a state */ + /* MUTE_AUDIO */ true , /* Force mute on all calls */ + /* MUTE_VIDEO */ true , /* Mute all calls */ + /* SERVER_TRANSFER */ false , /* It make no sense to let the user set this */ + /* RECORD */ true , /* Force "record" on all calls */ + /* HANGUP */ true , /* N/A */ + + /* JOIN */ true , /* N/A */ +}}; + +/** + * This matrix allow to enable/disable actions depending on the call protocol + */ const TypedStateMachine< TypedStateMachine< bool , Account::Protocol > , UserActionModel::Action > UserActionModelPrivate::availableProtocolActions = {{ /* SIP IAX DHT */ /* ACCEPT */ {{ true , true , true }}, @@ -141,6 +179,12 @@ const TypedStateMachine< TypedStateMachine< bool , Account::Protocol > , UserAct /* JOIN */ {{ true , true , true }}, }}; +/** + * This matrix define if an action is stateless or stateful. The only state + * supported is "checked", but when multiple items are selected, this can + * cause a heterogenous bunch of checked and unchecked elements, this is + * called "TRISTATE". + */ #define ST UserActionModel::ActionStatfulnessLevel:: const TypedStateMachine< TypedStateMachine< UserActionModel::ActionStatfulnessLevel , UserActionModelPrivate::SelectionState > , UserActionModel::Action > UserActionModelPrivate::actionStatefulness = {{ /* NONE UNIQUE MULTI */ @@ -151,22 +195,25 @@ const TypedStateMachine< TypedStateMachine< UserActionModel::ActionStatfulnessLe /* SERVER_TRANSFER */ {{ ST UNISTATE, ST CHECKABLE , ST TRISTATE }}, /* RECORD */ {{ ST UNISTATE, ST CHECKABLE , ST TRISTATE }}, /* HANGUP */ {{ ST UNISTATE, ST UNISTATE , ST TRISTATE }}, + /* JOIN */ {{ ST UNISTATE, ST UNISTATE , ST UNISTATE }}, }}; #undef ST const TypedStateMachine< QString, UserActionModel::Action> UserActionModelPrivate::m_ActionNames = {{ - /* ACCEPT */ QObject::tr("ACCEPT" ), //TODO use better (and stateful) names - /* HOLD */ QObject::tr("HOLD" ), - /* MUTE_AUDIO */ QObject::tr("MUTE_AUDIO" ), - /* MUTE_VIDEO */ QObject::tr("MUTE_VIDEO" ), - /* SERVER_TRANSFER */ QObject::tr("SERVER_TRANSFER" ), - /* RECORD */ QObject::tr("RECORD" ), - /* HANGUP */ QObject::tr("HANGUP" ), - /* JOIN */ QObject::tr("JOIN" ), + /* ACCEPT */ QObject::tr("ACCEPT" ), //TODO use better (and stateful) names + /* HOLD */ QObject::tr("HOLD" ), + /* MUTE_AUDIO */ QObject::tr("MUTE_AUDIO" ), + /* MUTE_VIDEO */ QObject::tr("MUTE_VIDEO" ), + /* SERVER_TRANSFER */ QObject::tr("SERVER_TRANSFER" ), + /* RECORD */ QObject::tr("RECORD" ), + /* HANGUP */ QObject::tr("HANGUP" ), + + /* JOIN */ QObject::tr("JOIN" ), }}; -UserActionModelPrivate::UserActionModelPrivate(UserActionModel* parent) : QObject(parent),q_ptr(parent),m_pCall(nullptr) +UserActionModelPrivate::UserActionModelPrivate(UserActionModel* parent) : QObject(parent),q_ptr(parent), +m_pCall(nullptr), m_pActiveModel(nullptr) { } @@ -181,7 +228,8 @@ UserActionModel::UserActionModel(Call* parent) : QAbstractListModel(parent),d_pt d_ptr->m_SelectionState = UserActionModelPrivate::SelectionState::UNIQUE; d_ptr->m_Mode = UserActionModelPrivate::UserActionModelMode::CALL; d_ptr->m_pCall = parent; - connect(AccountModel::instance(), &AccountModel::accountStateChanged , d_ptr.data(), &UserActionModelPrivate::slotAccountStateChanged); + + connect(AccountModel::instance(), SIGNAL(accountStateChanged(Account*,Account::RegistrationState)), d_ptr.data(), SLOT(slotStateChanged())); } /** @@ -192,11 +240,11 @@ UserActionModel::UserActionModel(CallModel* parent) : QAbstractListModel(parent) Q_ASSERT(parent != nullptr); d_ptr->m_Mode = UserActionModelPrivate::UserActionModelMode::CALLMODEL; d_ptr->m_SelectionState = UserActionModelPrivate::SelectionState::UNIQUE; - connect(parent->selectionModel(), &QItemSelectionModel::currentRowChanged, d_ptr.data(), &UserActionModelPrivate::slotCurrentChanged ); - connect(parent->selectionModel(), &QItemSelectionModel::selectionChanged , d_ptr.data(), &UserActionModelPrivate::slotSelectionChanged ); - connect(parent, &CallModel::callStateChanged , d_ptr.data(), &UserActionModelPrivate::slotCallStateChanged ); - connect(AccountModel::instance(), &AccountModel::accountStateChanged , d_ptr.data(), &UserActionModelPrivate::slotAccountStateChanged); + connect(parent->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)) , d_ptr.data(), SLOT(updateActions())); + connect(parent->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)) , d_ptr.data(), SLOT(updateActions())); + connect(parent, SIGNAL(callStateChanged(Call*,Call::State)) , d_ptr.data(), SLOT(updateActions())); + connect(AccountModel::instance(), SIGNAL(accountStateChanged(Account*,Account::RegistrationState)), d_ptr.data(), SLOT(updateActions())); } UserActionModel::~UserActionModel() @@ -226,7 +274,16 @@ QVariant UserActionModel::data(const QModelIndex& idx, int role ) const case Qt::DisplayRole: return UserActionModelPrivate::m_ActionNames[action]; case Qt::CheckStateRole: - return Qt::Unchecked; + if (d_ptr->actionStatefulness[action][d_ptr->m_SelectionState] != UserActionModel::ActionStatfulnessLevel::UNISTATE) + return d_ptr->m_CurrentActionsState[action]; + break; + case Qt::DecorationRole: { + UserActionElement state; + state.action = action ; + state.calls = {} ; + state.checkState = Qt::Unchecked; + return PixmapManipulationDelegate::instance()->userActionIcon(state); + } }; return QVariant(); @@ -246,7 +303,7 @@ Qt::ItemFlags UserActionModel::flags(const QModelIndex& idx ) const UserActionModel::Action action = static_cast<UserActionModel::Action>(idx.row()); return (d_ptr->m_CurrentActions[action] ? (Qt::ItemIsEnabled | Qt::ItemIsSelectable) : Qt::NoItemFlags) - | (d_ptr->actionStatefulness[action][d_ptr->m_SelectionState] == UserActionModel::ActionStatfulnessLevel::CHECKABLE + | (d_ptr->actionStatefulness[action][d_ptr->m_SelectionState] != UserActionModel::ActionStatfulnessLevel::UNISTATE ? Qt::ItemIsUserCheckable : Qt::NoItemFlags); } @@ -269,6 +326,38 @@ void UserActionModelPrivate::slotStateChanged() emit q_ptr->actionStateChanged(); } +void UserActionModelPrivate::updateCheckMask(int& ret, UserActionModel::Action action, const Call* c) +{ + switch(action) { + case UserActionModel::Action::ACCEPT : + ret += 0; + break; + case UserActionModel::Action::HOLD : + ret += c->state() == Call::State::HOLD? 100 : 1; + break; + case UserActionModel::Action::MUTE_AUDIO : + ret += m_MuteAudio_TO_REMOVE ? 100 : 1; + break; + case UserActionModel::Action::MUTE_VIDEO : + ret += m_MuteVideo_TO_REMOVE ? 100 : 1; + break; + case UserActionModel::Action::SERVER_TRANSFER : + ret += c->state() == Call::State::TRANSFERRED? 100 : 1; + break; + case UserActionModel::Action::RECORD : + ret += c->isRecording() ? 100 : 1; + break; + case UserActionModel::Action::HANGUP : + ret += 0; + break; + case UserActionModel::Action::JOIN : + ret += 0; + break; + case UserActionModel::Action::COUNT__: + break; + }; +} + bool UserActionModelPrivate::updateByCall(UserActionModel::Action action, const Call* c) { return (!c) ? false : ( @@ -281,15 +370,28 @@ bool UserActionModelPrivate::updateByCall(UserActionModel::Action action, const bool UserActionModelPrivate::updateAction(UserActionModel::Action action) { + int state = 0; switch(m_Mode) { case UserActionModelMode::CALL: + updateCheckMask(state,action,m_pCall); + m_CurrentActionsState[action] = state / 100 ? Qt::Checked : Qt::Unchecked; + return updateByCall(action, m_pCall); case UserActionModelMode::CALLMODEL: { bool ret = true; + m_SelectionState = CallModel::instance()->selectionModel()->selectedRows().size() > 1 ? SelectionState::MULTI : SelectionState::UNIQUE; - for (const QModelIndex& idx : CallModel::instance()->selectionModel()->selectedRows()) - ret &= updateByCall(action, qvariant_cast<Call*>(idx.data(Call::Role::Object))); - return ret; + + //Aggregate and reduce the action state for each selected calls + for (const QModelIndex& idx : CallModel::instance()->selectionModel()->selectedRows()) { + const Call* c = qvariant_cast<Call*>(idx.data(Call::Role::Object)); + updateCheckMask ( state ,action, c ); + ret &= updateByCall( action , c ); + } + + //Detect if the multiple selection has mismatching item states, disable it if necessary + m_CurrentActionsState[action] = (state % 100 && state / 100) ? Qt::PartiallyChecked : (state / 100 ? Qt::Checked : Qt::Unchecked); + return ret && (m_CurrentActionsState[action] != Qt::PartiallyChecked || heterogenous_call_options[action]); } }; return false; @@ -302,42 +404,129 @@ void UserActionModelPrivate::updateActions() emit q_ptr->dataChanged(q_ptr->index(0,0),q_ptr->index(enum_class_size<UserActionModel::Action>()-1,0)); } -void UserActionModelPrivate::slotCurrentChanged(const QModelIndex& current, const QModelIndex& previous) +uint UserActionModel::relativeIndex( UserActionModel::Action action ) const { - Q_UNUSED(current) - Q_UNUSED(previous) - updateActions(); + int i(0),ret(0); + while (i != static_cast<int>(action) && i < enum_class_size<UserActionModel::Action>()) { + ret += isActionEnabled(static_cast<UserActionModel::Action>(i))?1:0; + i++; + } + return ret; } -void UserActionModelPrivate::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +bool UserActionModel::execute(const UserActionModel::Action action) const { - Q_UNUSED(selected) - Q_UNUSED(deselected) - updateActions(); + /* + * The rational behind not expanding Call::Actions to handle this are: + * + * 1) There is some actions that apply _only_ to multiple calls + * 2) There is still a need to abstract the multi call use case + */ + + //1) Build the list of all selected calls + QList<Call*> selected; + switch(d_ptr->m_Mode) { + case UserActionModelPrivate::UserActionModelMode::CALL: + selected << d_ptr->m_pCall; + break; + case UserActionModelPrivate::UserActionModelMode::CALLMODEL: { + for (const QModelIndex& idx : CallModel::instance()->selectionModel()->selectedRows()) { + Call* c = qvariant_cast<Call*>(idx.data(Call::Role::Object)); + if (c) + selected << c; + } + break; + } + }; + + //2) Perform the actions + switch(action) { + case UserActionModel::Action::ACCEPT : + if (UserActions::accept(selected)) + d_ptr->updateActions(); + break; + case UserActionModel::Action::HOLD : + switch(d_ptr->m_CurrentActionsState[UserActionModel::Action::HOLD]) { + case Qt::Checked: + if (UserActions::unhold(selected)) + d_ptr->updateActions(); + break; + case Qt::Unchecked: + if (UserActions::hold(selected)) + d_ptr->updateActions(); + break; + case Qt::PartiallyChecked: + //Invalid + break; + }; + break; + case UserActionModel::Action::MUTE_AUDIO : + d_ptr->m_MuteAudio_TO_REMOVE != d_ptr->m_MuteAudio_TO_REMOVE; //FIXME evil fake property + d_ptr->updateActions(); + break; + case UserActionModel::Action::MUTE_VIDEO : + d_ptr->m_MuteVideo_TO_REMOVE != d_ptr->m_MuteVideo_TO_REMOVE; //FIXME evil fake property + d_ptr->updateActions(); + UserActions::accept(selected); + break; + case UserActionModel::Action::SERVER_TRANSFER : + UserActions::transfer(selected); + break; + case UserActionModel::Action::RECORD : + if (UserActions::record(selected)) + d_ptr->updateActions(); + break; + case UserActionModel::Action::HANGUP : + if (UserActions::accept(selected)) + d_ptr->updateActions(); + break; + case UserActionModel::Action::JOIN : + //TODO unimplemented + break; + case UserActionModel::Action::COUNT__: + break; + }; + + return true; //TODO handle errors } -void UserActionModelPrivate::slotCallStateChanged(Call* call, Call::State previousState) +/** + * Execute an action + * @param idx A model index. It can be from proxies. + */ +bool UserActionModel::execute(const QModelIndex& idx) const { - Q_UNUSED(call) - Q_UNUSED(previousState) - updateActions(); + QModelIndex idx2 = idx; + + //Make this API a little more user friendly and unwind the proxies + QAbstractProxyModel* m = nullptr; + while (idx2.model() != this && idx2.isValid()) { + m = qobject_cast<QAbstractProxyModel*>(const_cast<QAbstractItemModel*>(idx2.model())); + if (m) + idx2 = m->mapToSource(idx2); + else + break; + } + + if (!idx2.isValid()) + return false; + + UserActionModel::Action action = static_cast<UserActionModel::Action>(idx2.row()); + return execute(action); } -void UserActionModelPrivate::slotAccountStateChanged(Account* account, const Account::RegistrationState state) +///Get a model filter with only the available actions +QSortFilterProxyModel* UserActionModel::activeActionModel() const { - Q_UNUSED(account) - Q_UNUSED(state) - updateActions(); + if (!d_ptr->m_pActiveModel) + d_ptr->m_pActiveModel = new ActiveUserActionModel(const_cast<UserActionModel*>(this)); + return d_ptr->m_pActiveModel; } -uint UserActionModel::relativeIndex( UserActionModel::Action action ) const +//Do not display disabled account +bool ActiveUserActionModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { - int i(0),ret(0); - while (i != static_cast<int>(action) && i < enum_class_size<UserActionModel::Action>()) { - ret += isActionEnabled(static_cast<UserActionModel::Action>(i))?1:0; - i++; - } - return ret; + return sourceModel()->index(source_row,0,source_parent).flags() & Qt::ItemIsEnabled; } #include <useractionmodel.moc> diff --git a/src/useractionmodel.h b/src/useractionmodel.h index 8f32bd760557c304a948af10a0b5c7a799655c7b..8323b2b1140035a40f65ed1f0a3afa95c7f7e1d3 100644 --- a/src/useractionmodel.h +++ b/src/useractionmodel.h @@ -22,8 +22,11 @@ #include <QtCore/QAbstractItemModel> #include "typedefs.h" -#include "call.h" +//Qt +class QSortFilterProxyModel; +//Ring +#include "call.h" class Call; class CallModel; class UserActionModelPrivate; @@ -70,6 +73,8 @@ public: }; Q_ENUMS(Action) + Q_PROPERTY(QSortFilterProxyModel* activeActionModel READ activeActionModel); + //Constructor explicit UserActionModel(Call* parent); UserActionModel(CallModel* parent); @@ -85,6 +90,11 @@ public: //Getters Q_INVOKABLE bool isActionEnabled ( UserActionModel::Action action ) const; Q_INVOKABLE uint relativeIndex ( UserActionModel::Action action ) const; + QSortFilterProxyModel* activeActionModel() const; + + //Mutators + bool execute( const Action action ) const; + bool execute( const QModelIndex& idx ) const; private: const QScopedPointer<UserActionModelPrivate> d_ptr; @@ -96,4 +106,14 @@ Q_SIGNALS: }; Q_DECLARE_METATYPE(UserActionModel*) + +/** + * "Java bean" used to avoid having a 5+ parameter pixmap delegate + */ +struct UserActionElement { + UserActionModel::Action action ; + QList<Call*> calls ; + Qt::CheckState checkState ; +}; + #endif