Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
useractionmodel.cpp 24.22 KiB
/****************************************************************************
 *   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/>.  *
 ***************************************************************************/
#include "useractionmodel.h"

//Qt
#include <QtCore/QItemSelection>
#include <QtCore/QSortFilterProxyModel>

//Ring
#include "call.h"
#include "callmodel.h"
#include "account.h"
#include "accountmodel.h"
#include "availableaccountmodel.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
{
   Q_OBJECT
public:
   enum class SelectionState {
      NONE  , /*!< No Item is selected         */
      UNIQUE, /*!< A single item is selected   */
      MULTI , /*!< Multiple items are selected */
      COUNT__,
   };

   enum class UserActionModelMode {
      CALL     , /*!< Model the react on a single call element    */
      CALLMODEL, /*!< Model that react on the callmodel selection */
   };

   //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< 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
   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     ;
   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(); //TODO

   //CallModel mode
   void updateActions();
};


/*
 * This algorithm implement a matrix multiplication of the 4 sources of relevant states.
 * The result of the multiplication indicate is the option is enabled in a given scenario.
 */

//Enabled actions
const TypedStateMachine< TypedStateMachine< bool , Call::State > , UserActionModel::Action > UserActionModelPrivate::availableActionMap = {{
 /*                   INCOMING  RINGING CURRENT DIALING  HOLD  FAILURE BUSY  TRANSFERRED TRANSF_HOLD  OVER  ERROR CONFERENCE CONFERENCE_HOLD  INITIALIZATION*/
 /*ACCEPT          */ {{ true   , false,  false,  true , false, false, false,   false,     false,    false, false,  false,       false,           false}},
 /*HOLD            */ {{ false  , false,  true ,  false, true , false, false,   false,     false,    false, false,  true ,       false,           false}},
 /*MUTE_AUDIO      */ {{ false  , true ,  true ,  false, false, false, false,   false,     false,    false, false,  false,       false,           false}},
 /*MUTE_VIDEO      */ {{ false  , true ,  true ,  false, false, false, false,   false,     false,    false, false,  false,       false,           false}},
 /*SERVER_TRANSFER */ {{ false  , false,  true ,  false, true , false, false,   false,     false,    false, false,  false,       false,           false}},
 /*RECORD          */ {{ false  , true ,  true ,  false, true , false, false,   true ,     true ,    false, false,  true ,       true ,           false}},
 /*HANGUP          */ {{ true   , true ,  true ,  true , true , true , true ,   true ,     true ,    false, true ,  true ,       true ,           true }},

 /*JOIN            */ {{ false  , true ,  true ,  false, true , false, false,   true ,     true ,    false, false,  true ,       true ,           false}},

 /*ADD_NEW         */ {{ false  , false,  false,  false, false, false, false,   false,     false,    false, false,  false,       false,           false}},
}};

/**
 * Assuming a call is in progress, the communication can still be valid if the account is down, however,
 * this will impact the available actions
 */
const TypedStateMachine< TypedStateMachine< bool , Account::RegistrationState > , UserActionModel::Action > UserActionModelPrivate::availableAccountActionMap = {{
   /*                       READY  UNREGISTERED  TRYING    ERROR  */
   /* ACCEPT          */ {{ true ,    false,     false,    false  }},
   /* HOLD            */ {{ true ,    false,     false,    false  }},
   /* MUTE_AUDIO      */ {{ true ,    true ,     true ,    true   }},
   /* MUTE_VIDEO      */ {{ true ,    true ,     true ,    true   }},
   /* SERVER_TRANSFER */ {{ true ,    false,     false,    false  }},
   /* RECORD          */ {{ true ,    true ,     true ,    true   }},
   /* HANGUP          */ {{ true ,    true ,     true ,    true   }},

   /* JOIN            */ {{ true ,    true ,     true ,    true   }},

   /* ADD_NEW         */ {{ true ,    false,     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 ,  false }},
   /* MUTE_AUDIO      */ {{ false,  true ,  true  }},
   /* MUTE_VIDEO      */ {{ false,  true ,  true  }},
   /* SERVER_TRANSFER */ {{ false,  true ,  true  }},
   /* RECORD          */ {{ false,  true ,  true  }},
   /* HANGUP          */ {{ false,  true ,  true  }},

   /* JOIN            */ {{ false,  false,  true  }},

   /* ADD_NEW         */ {{ true ,  false,  false }},
}};

/**
 * 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                                       */

   /* ADD_NEW         */ false , /* 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  }},
   /* HOLD            */ {{ true , true , true  }},
   /* MUTE_AUDIO      */ {{ true , true , true  }},
   /* MUTE_VIDEO      */ {{ true , true , true  }},
   /* SERVER_TRANSFER */ {{ true , false, false }},
   /* RECORD          */ {{ true , true , true  }},
   /* HANGUP          */ {{ true , true , true  }},

   /* JOIN            */ {{ true , true , true  }},

   /* ADD_NEW         */ {{ 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     */
   /* ACCEPT          */ {{ ST UNISTATE,  ST UNISTATE     ,  ST UNISTATE  }},
   /* HOLD            */ {{ ST UNISTATE,  ST CHECKABLE    ,  ST TRISTATE  }},
   /* MUTE_AUDIO      */ {{ ST UNISTATE,  ST CHECKABLE    ,  ST TRISTATE  }},
   /* MUTE_VIDEO      */ {{ ST UNISTATE,  ST CHECKABLE    ,  ST TRISTATE  }},
   /* 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  }},

   /* ADD_NEW         */ {{ 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"            ),

   /* JOIN            */ QObject::tr("ADD_NEW"         ),
}};

UserActionModelPrivate::UserActionModelPrivate(UserActionModel* parent) : QObject(parent),q_ptr(parent),
m_pCall(nullptr), m_pActiveModel(nullptr)
{
}

/**
 * Create an UserActionModel around a single call. This wont take advantage
 * of the multiselection feature.
 */
UserActionModel::UserActionModel(Call* parent) : QAbstractListModel(parent),d_ptr(new UserActionModelPrivate(this))
{
   Q_ASSERT(parent != nullptr);
   Q_ASSERT(parent->state() != Call::State::OVER);
   d_ptr->m_SelectionState = UserActionModelPrivate::SelectionState::UNIQUE;
   d_ptr->m_Mode = UserActionModelPrivate::UserActionModelMode::CALL;
   d_ptr->m_pCall = parent;

   connect(AccountModel::instance(), SIGNAL(accountStateChanged(Account*,Account::RegistrationState)), d_ptr.data(), SLOT(slotStateChanged()));
   d_ptr->updateActions();
}

/**
 * Create an UserActionModel around the CallModel selected call(s)
 */
UserActionModel::UserActionModel(CallModel* parent) : QAbstractListModel(parent),d_ptr(new UserActionModelPrivate(this))
{
   Q_ASSERT(parent != nullptr);
   d_ptr->m_Mode = UserActionModelPrivate::UserActionModelMode::CALLMODEL;
   d_ptr->m_SelectionState = UserActionModelPrivate::SelectionState::UNIQUE;

   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()));
   d_ptr->updateActions();
}

UserActionModel::~UserActionModel()
{

}

QHash<int,QByteArray> UserActionModel::roleNames() const
{
   static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
   /*static bool initRoles = false;
   if (!initRoles) {
      initRoles = true;

   }*/
   return roles;
}

QVariant UserActionModel::data(const QModelIndex& idx, int role ) const
{
   if (!idx.isValid() && (idx.row()>=0 && idx.row() < enum_class_size<UserActionModel::Action>()))
      return QVariant();

   UserActionModel::Action action = static_cast<UserActionModel::Action>(idx.row());

   switch(role) {
      case Qt::DisplayRole:
         return UserActionModelPrivate::m_ActionNames[action];
      case Qt::CheckStateRole:
         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);
      }
      case UserActionModel::Role::ACTION:
         return QVariant::fromValue(static_cast<Action>(idx.row()));
   };

   return QVariant();
}

int UserActionModel::rowCount(const QModelIndex& parent ) const
{
   return parent.isValid() ? 0 : static_cast<int>(Action::COUNT__);
}

///For now, this model probably wont be used that way
Qt::ItemFlags UserActionModel::flags(const QModelIndex& idx ) const
{
   if ((!idx.isValid()) || !(idx.row()>=0 && idx.row() < enum_class_size<UserActionModel::Action>()))
      return Qt::NoItemFlags;

   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::UNISTATE
      ? Qt::ItemIsUserCheckable : Qt::NoItemFlags);
}

// ///This model is read only
bool UserActionModel::setData(const QModelIndex& index, const QVariant &value, int role)
{
   Q_UNUSED( index )
   Q_UNUSED( value )
   Q_UNUSED( role  )
   return false;
}

bool UserActionModel::isActionEnabled( UserActionModel::Action action ) const
{
   return d_ptr->availableActionMap[action][d_ptr->m_pCall->state()];
}

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::ADD_NEW         :
         ret += 0;
         break;
      case UserActionModel::Action::COUNT__:
         break;
   };
}

bool UserActionModelPrivate::updateByCall(UserActionModel::Action action, const Call* c)
{
   return (!c) ? false : (
      availableActionMap        [action] [c->state()                       ] &&
      availableAccountActionMap [action] [c->account()->registrationState()] &&
      multi_call_options        [action] [m_SelectionState                 ] &&
      availableProtocolActions  [action] [c->account()->protocol()         ] //
   );
}

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;

         //Aggregate and reduce the action state for each selected calls
         if (CallModel::instance()->selectionModel()->selectedRows().size()) {
            for (const QModelIndex& idx : CallModel::instance()->selectionModel()->selectedRows()) {
               const Call* c = qvariant_cast<Call*>(idx.data(static_cast<int>(Call::Role::Object)));
               updateCheckMask    ( state ,action, c );
               ret &= updateByCall( action       , c );
            }
         }
         else {
            Account* a = AvailableAccountModel::instance()->currentDefaultAccount();
            ret = multi_call_options[action][UserActionModelPrivate::SelectionState::NONE]
               && (a?availableAccountActionMap[action][a->registrationState()]:false);
         }
         //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;
}

void UserActionModelPrivate::updateActions()
{
   for (UserActionModel::Action action : EnumIterator<UserActionModel::Action>())
      m_CurrentActions[action] = updateAction(action);
   emit q_ptr->dataChanged(q_ptr->index(0,0),q_ptr->index(enum_class_size<UserActionModel::Action>()-1,0));
}

uint UserActionModel::relativeIndex( UserActionModel::Action action ) 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;
}

bool UserActionModel::execute(const UserActionModel::Action action) const
{
   /*
    * 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(static_cast<int>(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::hangup(selected))
            d_ptr->updateActions();
         break;
      case UserActionModel::Action::JOIN            :
         //TODO unimplemented
         break;
      case UserActionModel::Action::ADD_NEW         :
         if (UserActions::addNew())
            d_ptr->updateActions();
         break;
      case UserActionModel::Action::COUNT__:
         break;
   };

   return true; //TODO handle errors
}

UserActionModel* UserActionModel::operator<<(UserActionModel::Action& action)
{
   execute(action);
   return this;
}


UserActionModel* operator<<(UserActionModel* m,UserActionModel::Action action)
{
   return (!m)? nullptr : (*m) << action;
}

/**
 * Execute an action
 * @param idx A model index. It can be from proxies.
 */
bool UserActionModel::execute(const QModelIndex& idx) const
{
   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);
}

///Get a model filter with only the available actions
QSortFilterProxyModel* UserActionModel::activeActionModel() const
{
   if (!d_ptr->m_pActiveModel)
      d_ptr->m_pActiveModel = new ActiveUserActionModel(const_cast<UserActionModel*>(this));
   return d_ptr->m_pActiveModel;
}

//Do not display disabled account
bool ActiveUserActionModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
{
   return sourceModel()->index(source_row,0,source_parent).flags() & Qt::ItemIsEnabled;
}

#include <useractionmodel.moc>