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