CallView.cpp 37.1 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2009-2012 by Savoir-Faire Linux                         *
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *   Author : Emmanuel Lepage Valle <emmanuel.lepage@savoirfairelinux.com >*
 *                                                                         *
 *   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 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 **************************************************************************/

//Parent
22
#include "CallView.h"
23
24
25
26
27

//Qt
#include <QtGui/QInputDialog>
#include <QtGui/QTreeWidget>
#include <QtGui/QTreeWidgetItem>
28
29
30
#include <QtGui/QPushButton>
#include <QtGui/QSpacerItem>
#include <QtGui/QGridLayout>
31
#include <QtGui/QLabel>
32

33
34
//KDE
#include <KDebug>
35
#include <KLineEdit>
36
#include <KStandardDirs>
37
38

//SFLPhone library
39
#include "lib/Contact.h"
Emmanuel Lepage's avatar
Emmanuel Lepage committed
40
41
#include "lib/sflphone_const.h"
#include "lib/callmanager_interface_singleton.h"
42
43
44
#include "klib/AkonadiBackend.h"
#include "klib/ConfigurationSkeleton.h"
#include "klib/HelperFunctions.h"
45

46
//SFLPhone
47
#include "SFLPhoneView.h"
48
#include "widgets/CallTreeItem.h"
49
#include "SFLPhone.h"
50
#include "SFLPhoneAccessibility.h"
51
#include "widgets/ConferenceBox.h"
52

Emmanuel Lepage's avatar
Emmanuel Lepage committed
53
///CallTreeItemDelegate: Delegates for CallTreeItem
54
55
56
57
58
59
class CallTreeItemDelegate : public QStyledItemDelegate
{
public:
CallTreeItemDelegate(CallView* widget)
      : QStyledItemDelegate(widget)
      , m_tree(widget)
60
      , m_ConferenceDrawer()
61
62
63
64
65
66
67
68
69
    {
    }

    QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
      QSize sh = QStyledItemDelegate::sizeHint(option, index);
      QTreeWidgetItem* item = (m_tree)->itemFromIndex(index);
      if (item) {
         CallTreeItem* widget = (CallTreeItem*)m_tree->itemWidget(item,0);
         if (widget)
70
71
72
73
74
            sh.rheight() = widget->sizeHint().height()+11; //Equal top and bottom padding

         if (index.parent().isValid() && !index.parent().child(index.row()+1,0).isValid())
            sh.rheight() += 15;
            
75
76
77
78
      }

      return sh;
    }
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190

   QRect fullCategoryRect(const QStyleOptionViewItem& option, const QModelIndex& index) const {
      QModelIndex i(index),old(index);
      //BEGIN real sizeHint()
      //Otherwise it would be called too often (thanks to valgrind)
      ((CallTreeItemDelegate*)this)->m_SH          = QStyledItemDelegate::sizeHint(option, index);
      ((CallTreeItemDelegate*)this)->m_LeftMargin  = m_ConferenceDrawer.leftMargin();
      ((CallTreeItemDelegate*)this)->m_RightMargin = m_ConferenceDrawer.rightMargin();
      if (!index.parent().isValid() && index.child(0,0).isValid()) {
         ((QSize)m_SH).rheight() += 2 * m_ConferenceDrawer.leftMargin();
      } else {
         ((QSize)m_SH).rheight() += m_ConferenceDrawer.leftMargin();
      }
      ((QSize)m_SH).rwidth() += m_ConferenceDrawer.leftMargin();
      //END real sizeHint()

      if (i.parent().isValid()) {
         i = i.parent();
      }

      //Avoid repainting the category over and over (optimization)
      //note: 0,0,0,0 is actually wrong, but it wont change anything for this use case
      if (i != old && old.row()>2)
         return QRect(0,0,0,0);

      QTreeWidgetItem* item = m_tree->itemFromIndex(i);
      QRect r = m_tree->visualItemRect(item);

      // adapt width
      r.setLeft(m_ConferenceDrawer.leftMargin());
      r.setWidth(m_tree->viewport()->width() - m_ConferenceDrawer.leftMargin() - m_ConferenceDrawer.rightMargin());

      // adapt height
      if (item->isExpanded() && item->childCount() > 0) {
         const int childCount = item->childCount();
         //There is a massive implact on CPU usage to have massive rect
         for (int i =0;i < childCount;i++) {
            r.setHeight(r.height() + sizeHint(option,index).height());
         }
//          qDebug() << "\n\nFINAL SIZE" << r;
      }

      r.setTop(r.top() + m_ConferenceDrawer.leftMargin());

      return r;
    }

    virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
    {
      Q_ASSERT(index.isValid());

      QStyleOptionViewItem opt(option);
      //BEGIN: draw toplevel items
      if (!index.parent().isValid() && index.child(0,0).isValid()) {
         const QRegion cl = painter->clipRegion();
         painter->setClipRect(opt.rect);
         opt.rect = fullCategoryRect(option, index);
         m_ConferenceDrawer.drawCategory(index, 0, opt, painter);
         painter->setClipRegion(cl);
         return;
      }

      if (!index.parent().parent().isValid()) {
         opt.rect = fullCategoryRect(option, index);
         const QRegion cl = painter->clipRegion();
         QRect cr = option.rect;
         if (index.column() == 0) {
            if (m_tree->layoutDirection() == Qt::LeftToRight) {
               cr.setLeft(5);
            } else {
               cr.setRight(opt.rect.right());
            }
         }
         painter->setClipRect(cr);
         if (index.parent().isValid())
            m_ConferenceDrawer.drawCategory(index, 0, opt, painter);
         painter->setClipRegion(cl);
         painter->setRenderHint(QPainter::Antialiasing, false);
      }

      //END: draw background of category for all other items

      int max = 9999;
      painter->setClipRect(option.rect);
      if (option.state & QStyle::State_Selected) {
         QStyleOptionViewItem opt2(option);
         opt2.rect.setWidth(opt2.rect.width()-15);
         if (index.parent().isValid() && !index.parent().child(index.row()+1,0).isValid()) {
            opt2.rect.setHeight(opt2.rect.height()-15);
            QStyledItemDelegate::paint(painter,opt2,index);
            max = opt2.rect.height();
         }
         else {
            QStyledItemDelegate::paint(painter,index.parent().isValid()?opt2:option,index);
         }
      }

      QTreeWidgetItem* item = m_tree->itemFromIndex(index);
      if (item) {
         QWidget* widget = m_tree->itemWidget(item,0);
         if (widget) {
            widget->setMinimumSize((m_tree->viewport()->width() - m_ConferenceDrawer.leftMargin() - m_ConferenceDrawer.rightMargin())-m_ConferenceDrawer.leftMargin(),10);
            widget->setMaximumSize((m_tree->viewport()->width() - m_ConferenceDrawer.leftMargin() - m_ConferenceDrawer.rightMargin())-m_ConferenceDrawer.leftMargin(),max);
         }
      }
      
      if (index.parent().isValid() && !index.parent().child(index.row()+1,0).isValid()) {
         m_ConferenceDrawer.drawBoxBottom(index, 0, option, painter);
      }
    }

    
191
private:
192
193
194
195
196
   CallView*      m_tree;
   ConferenceBox  m_ConferenceDrawer;
   QSize m_SH;
   int m_LeftMargin;
   int m_RightMargin;
197
198
};

199
200

///Retrieve current and older calls from the daemon, fill history and the calls TreeView and enable drag n' drop
201
CallView::CallView(QWidget* parent) : QTreeWidget(parent),m_pActiveOverlay(0),m_pCallPendingTransfer(0)
202
203
{
   //Widget part
204
205
206
   setAcceptDrops      (true );
   setDragEnabled      (true );
   setAnimated         (false);
207
   setUniformRowHeights(false);
208
209
   setRootIsDecorated  (false);
   setIndentation(15);
210

211
   CallTreeItemDelegate *delegate = new CallTreeItemDelegate(this);
212
   setItemDelegate(delegate);
213
   setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
214

Emmanuel Lepage's avatar
Emmanuel Lepage committed
215
216
217
218
219
220
221
   QString image = "<img width=100 height=100  src='"+KStandardDirs::locate("data","sflphone-client-kde/transferarraw.png")+"' />";

   m_pTransferOverlay = new CallViewOverlay ( this               );
   m_pTransferB       = new QPushButton     ( m_pTransferOverlay );
   m_pTransferLE      = new KLineEdit       ( m_pTransferOverlay );
   QGridLayout* gl    = new QGridLayout     ( m_pTransferOverlay );
   QLabel* lblImg     = new QLabel          ( image              );
222

223
224
   m_pTransferOverlay->setVisible(false);
   m_pTransferOverlay->resize(size());
225
   m_pTransferOverlay->setCornerWidget(lblImg);
226
   m_pTransferOverlay->setAccessMessage(i18n("Please enter a transfer number and press enter, press escape to cancel"));
227

228
   m_pTransferB->setText(i18n("Transfer"));
229
   m_pTransferB->setMaximumSize(70,9000);
230

Emmanuel Lepage's avatar
Emmanuel Lepage committed
231
232
233
234
   gl->addItem  (new QSpacerItem(0,0,QSizePolicy::Expanding,QSizePolicy::Minimum), 0 , 0 , 1 , 3 );
   gl->addWidget(m_pTransferLE                                                   , 1 , 1 , 1 , 2 );
   gl->addWidget(m_pTransferB                                                    , 1 , 4 , 1 , 2 );
   gl->addItem  (new QSpacerItem(0,0,QSizePolicy::Expanding,QSizePolicy::Minimum), 2 , 0 , 1 , 3 );
235

236
237
238
239
   foreach(Call* active, SFLPhone::model()->getCallList()) {
      addCall(active);
   }

240
   foreach(Call* active, SFLPhone::model()->getConferenceList()) {
241
242
      if (qobject_cast<Call*>(active)) //As of May 2012, the deamon still produce fake conferences
         addConference(active);
243
244
   }

Emmanuel Lepage's avatar
Emmanuel Lepage committed
245
   //User Interface even
246
247
248
249
250
251
252
253
   //              SENDER                                   SIGNAL                              RECEIVER                     SLOT                        /
   /**/connect(this              , SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)              ) , this, SLOT( itemDoubleClicked(QTreeWidgetItem*,int)) );
   /**/connect(this              , SIGNAL(itemClicked(QTreeWidgetItem*,int)                    ) , this, SLOT( itemClicked(QTreeWidgetItem*,int))       );
   /**/connect(this              , SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)) , this, SLOT( itemClicked(QTreeWidgetItem*))           );
   /**/connect(SFLPhone::model() , SIGNAL(conferenceCreated(Call*)                             ) , this, SLOT( addConference(Call*))                    );
   /**/connect(SFLPhone::model() , SIGNAL(conferenceChanged(Call*)                             ) , this, SLOT( conferenceChanged(Call*))                );
   /**/connect(SFLPhone::model() , SIGNAL(aboutToRemoveConference(Call*)                       ) , this, SLOT( conferenceRemoved(Call*))                );
   /**/connect(SFLPhone::model() , SIGNAL(callAdded(Call*,Call*)                               ) , this, SLOT( addCall(Call*,Call*))                    );
254
255
   /**/connect(m_pTransferB      , SIGNAL(clicked()                                            ) , this, SLOT( transfer())                              );
   /**/connect(m_pTransferLE     , SIGNAL(returnPressed()                                      ) , this, SLOT( transfer())                              );
256
   /*                                                                                                                                                   */
257

Emmanuel Lepage's avatar
Emmanuel Lepage committed
258
} //CallView
259

260
261
262
263
264
265
266
267
268
///Destructor
CallView::~CallView()
{
   delete m_pTransferB;
   delete m_pTransferLE;
   if (m_pTransferOverlay) delete m_pTransferOverlay;
   if (m_pActiveOverlay)   delete m_pActiveOverlay;
}

269
270
271
272
273
274
275
276
277
///A tree is not a good representation, remove branch and skin everything
void CallView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
{
   Q_UNUSED(painter)
   Q_UNUSED(rect)
   Q_UNUSED(index)
}


278
279
280
281
282
283
284

/*****************************************************************************
 *                                                                           *
 *                        Drag and drop related code                         *
 *                                                                           *
 ****************************************************************************/

285
286
287
288
289
290
291
292
293
294
///Called when someone try to drop something on the tree
void CallView::dragEnterEvent ( QDragEnterEvent *e )
{
   kDebug() << "Potential drag event enter";
   e->accept();
}

///When a drag position change
void CallView::dragMoveEvent  ( QDragMoveEvent  *e )
{
295
   e->accept();
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
}

///When a drag event is leaving the widget
void CallView::dragLeaveEvent ( QDragLeaveEvent *e )
{
   kDebug() << "Potential drag event leave";
   e->accept();
}

///Proxy to handle transfer mime data
void CallView::transferDropEvent(Call* call,QMimeData* data)
{
   QByteArray encodedCallId = data->data( MIME_CALLID );
   SFLPhone::model()->attendedTransfer(SFLPhone::model()->getCall(encodedCallId),call);
}

///Proxy to handle conversation mime data
void CallView::conversationDropEvent(Call* call,QMimeData* data)
{
   kDebug() << "Calling real drag and drop function";
   dropMimeData(SFLPhone::model()->getIndex(call), 0, data, (Qt::DropAction)0);
}

Emmanuel Lepage's avatar
Emmanuel Lepage committed
319
///A call is dropped on another call
320
bool CallView::callToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
321
322
323
{
   Q_UNUSED(index)
   Q_UNUSED(action)
324
325
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   if (!QString(encodedCallId).isEmpty()) {
326
      if (SFLPhone::model()->getIndex(encodedCallId) && dynamic_cast<Call*>(SFLPhone::model()->getCall(encodedCallId))) //Prevent a race
327
        clearArtefact(SFLPhone::model()->getIndex(encodedCallId));
328

329
      if (!parent) {
330
         kDebug() << "Call dropped on empty space";
331
         if (SFLPhone::model()->getIndex(encodedCallId)->parent()) {
332
            kDebug() << "Detaching participant";
333
            SFLPhone::model()->detachParticipant(SFLPhone::model()->getCall(encodedCallId));
334
335
         }
         else
336
            kDebug() << "The call is not in a conversation (doing nothing)";
337
338
         return true;
      }
339

340
      if (SFLPhone::model()->getCall(parent)->getCallId() == QString(encodedCallId)) {
341
         kDebug() << "Call dropped on itself (doing nothing)";
342
343
         return true;
      }
344
345
346
347
      else if (SFLPhone::model()->getIndex(encodedCallId) == parent) {
         kDebug() << "Dropping conference on itself (doing nothing)";
         return true;
      }
348

349
      if ((parent->childCount()) && (SFLPhone::model()->getIndex(encodedCallId)->childCount())) {
350
         kDebug() << "Merging two conferences";
351
         SFLPhone::model()->mergeConferences(SFLPhone::model()->getCall(parent),SFLPhone::model()->getCall(encodedCallId));
352
353
354
         return true;
      }
      else if ((parent->parent()) || (parent->childCount())) {
355
         kDebug() << "Call dropped on a conference";
356

357
         if (SFLPhone::model()->getIndex(encodedCallId)->childCount() && (SFLPhone::model()->getIndex(encodedCallId)->childCount()) && (!parent->childCount())) {
358
            kDebug() << "Conference dropped on a call (doing nothing)";
359
360
            return true;
         }
361

362
         QTreeWidgetItem* call1 = SFLPhone::model()->getIndex(encodedCallId);
363
         QTreeWidgetItem* call2 = (parent->parent())?parent->parent():parent;
364

365
         if (call1->parent()) {
366
            kDebug() << "Call 1 is part of a conference";
367
            if (call1->parent() == call2) {
368
               kDebug() << "Call dropped on it's own conference (doing nothing)";
369
370
               return true;
            }
371
            else if (SFLPhone::model()->getIndex(call1)->childCount()) {
372
               kDebug() << "Merging two conferences";
373
               SFLPhone::model()->mergeConferences(SFLPhone::model()->getCall(call1),SFLPhone::model()->getCall(call2));
374
375
            }
            else if (call1->parent()) {
376
               kDebug() << "Moving call from a conference to an other";
377
               SFLPhone::model()->detachParticipant(SFLPhone::model()->getCall(encodedCallId));
378
379
            }
         }
380
         kDebug() << "Adding participant";
381
         int state = SFLPhone::model()->getCall(call1)->getState();
382
         if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
383
            SFLPhone::model()->getCall(call1)->actionPerformed(CALL_ACTION_ACCEPT);
384
         }
385
         state = SFLPhone::model()->getCall(call2)->getState();
386
         if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
387
            SFLPhone::model()->getCall(call2)->actionPerformed(CALL_ACTION_ACCEPT);
388
         }
389
         SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call1),SFLPhone::model()->getCall(call2));
390
391
         return true;
      }
392
      else if (SFLPhone::model()->getIndex(encodedCallId) && (SFLPhone::model()->getIndex(encodedCallId)->childCount()) && (!parent->childCount())) {
393
         kDebug() << "Call dropped on it's own conference (doing nothing)";
394
395
396
         return true;
      }

397
      kDebug() << "Call dropped on another call";
398
      SFLPhone::model()->createConferenceFromCall(SFLPhone::model()->getCall(encodedCallId),SFLPhone::model()->getCall(parent));
399
400
      return true;
   }
401
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
402
} //callToCall
403

Emmanuel Lepage's avatar
Emmanuel Lepage committed
404
///A string containing a call number is dropped on a call
405
406
407
408
409
410
bool CallView::phoneNumberToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
{
   Q_UNUSED(index)
   Q_UNUSED(action)
   QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
   if (!QString(encodedPhoneNumber).isEmpty()) {
411
      Contact* contact = AkonadiBackend::getInstance()->getContactByPhone(encodedPhoneNumber,true);
412
      QString name;
413
      name = (contact)?contact->getFormattedName():i18n("Unknown");
414
      Call* call2 = SFLPhone::model()->addDialingCall(name, AccountList::getCurrentAccount());
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
      if (call2) {
         call2->appendText(QString(encodedPhoneNumber));
         if (!parent) {
            //Dropped on free space
            kDebug() << "Adding new dialing call";
         }
         else if (parent->childCount() || parent->parent()) {
            //Dropped on a conversation
            QTreeWidgetItem* call = (parent->parent())?parent->parent():parent;
            SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call),call2);
         }
         else {
            //Dropped on call
            call2->actionPerformed(CALL_ACTION_ACCEPT);
            int state = SFLPhone::model()->getCall(parent)->getState();
            if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
               SFLPhone::model()->getCall(parent)->actionPerformed(CALL_ACTION_ACCEPT);
            }
            SFLPhone::model()->createConferenceFromCall(call2,SFLPhone::model()->getCall(parent));
         }
435
436
      }
      else {
437
         HelperFunctions::displayNoAccountMessageBox(this);
438
439
440
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
441
} //phoneNumberToCall
442

Emmanuel Lepage's avatar
Emmanuel Lepage committed
443
///A contact ID is dropped on a call
444
445
bool CallView::contactToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
{
446
   kDebug() << "contactToCall";
447
448
   Q_UNUSED( index  )
   Q_UNUSED( action )
449
450
451
452
   QByteArray encodedContact = data->data( MIME_CONTACT );
   if (!QString(encodedContact).isEmpty()) {
      Contact* contact = AkonadiBackend::getInstance()->getContactByUid(encodedContact);
      if (contact) {
Emmanuel Lepage's avatar
Emmanuel Lepage committed
453
         Call* call2 = NULL;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
454
         if (!SFLPhone::app()->view()->selectCallPhoneNumber(&call2,contact))
455
456
457
            return false;
         if (!parent) {
            //Dropped on free space
458
            kDebug() << "Adding new dialing call";
459
460
461
462
         }
         else if (parent->childCount() || parent->parent()) {
            //Dropped on a conversation
            QTreeWidgetItem* call = (parent->parent())?parent->parent():parent;
463
            SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call),call2);
464
465
466
         }
         else {
            //Dropped on call
Emmanuel Lepage's avatar
Emmanuel Lepage committed
467
468
469
470
471
472
473
474
//             if (!call2) {
//                call2 = SFLPhone::model()->addDialingCall(contact->getFormattedName());
//             }
//             QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
//             if (!encodedPhoneNumber.isEmpty()) {
//                call2->setCallNumber(encodedPhoneNumber);
//             }

475
            call2->actionPerformed(CALL_ACTION_ACCEPT);
476
            int state = SFLPhone::model()->getCall(parent)->getState();
477
            if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
478
               SFLPhone::model()->getCall(parent)->actionPerformed(CALL_ACTION_ACCEPT);
479
            }
480
            SFLPhone::model()->createConferenceFromCall(call2,SFLPhone::model()->getCall(parent));
481
482
483
484
         }
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
485
} //contactToCall
486
487

///Action performed when an item is dropped on the TreeView
488
bool CallView::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
489
490
491
{
   Q_UNUSED(index)
   Q_UNUSED(action)
492

493
494
495
496
497
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
   QByteArray encodedContact     = data->data( MIME_CONTACT     );

   if (!QString(encodedCallId).isEmpty()) {
498
      kDebug() << "CallId dropped"<< QString(encodedCallId);
499
500
501
      callToCall(parent, index, data, action);
   }
   else if (!QString(encodedPhoneNumber).isEmpty()) {
502
      kDebug() << "PhoneNumber dropped"<< QString(encodedPhoneNumber);
503
504
505
      phoneNumberToCall(parent, index, data, action);
   }
   else if (!QString(encodedContact).isEmpty()) {
506
      kDebug() << "Contact dropped"<< QString(encodedContact);
507
508
      contactToCall(parent, index, data, action);
   }
509
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
510
} //dropMimeData
511
512
513

///Encode data to be tranported during the drag n' drop operation
QMimeData* CallView::mimeData( const QList<QTreeWidgetItem *> items) const
514
{
515
   kDebug() << "A call is being dragged";
516
517
518
   if (items.size() < 1) {
      return NULL;
   }
519

520
   QMimeData *mimeData = new QMimeData();
521

522
   //Call ID for internal call merging and spliting
523
524
   if (SFLPhone::model()->getCall(items[0])->isConference()) {
      mimeData->setData(MIME_CALLID, SFLPhone::model()->getCall(items[0])->getConfId().toAscii());
525
526
   }
   else {
527
      mimeData->setData(MIME_CALLID, SFLPhone::model()->getCall(items[0])->getCallId().toAscii());
528
   }
529

530
   //Plain text for other applications
531
   mimeData->setData(MIME_PLAIN_TEXT, QString(SFLPhone::model()->getCall(items[0])->getPeerName()+"\n"+SFLPhone::model()->getCall(items[0])->getPeerPhoneNumber()).toAscii());
532

533
   //TODO Comment this line if you don't want to see ugly artefact, but the caller details will not be visible while dragged
534
   items[0]->setText(0, SFLPhone::model()->getCall(items[0])->getPeerName() + "\n" + SFLPhone::model()->getCall(items[0])->getPeerPhoneNumber());
535
   return mimeData;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
536
} //mimeData
537
538
539
540
541
542
543
544
545


/*****************************************************************************
 *                                                                           *
 *                            Call related code                              *
 *                                                                           *
 ****************************************************************************/

///Add a call in the model structure, the call must exist before being added to the model
546
Call* CallView::addCall(Call* call, Call* parent)
547
548
{
   QTreeWidgetItem* callItem = new QTreeWidgetItem();
549
   SFLPhone::model()->updateIndex(call,callItem);
550
   insertItem(callItem,parent);
551

552
   setCurrentItem(callItem);
553

554
555
556
557
   connect(call, SIGNAL(isOver(Call*)), this, SLOT(destroyCall(Call*)));
   return call;
}

558
559
560
///Transfer a call
void CallView::transfer()
{
561
   if (m_pCallPendingTransfer && !m_pTransferLE->text().isEmpty()) {
562
      SFLPhone::model()->transfer(m_pCallPendingTransfer,m_pTransferLE->text());
563
564
565
      if (ConfigurationSkeleton::enableVoiceFeedback()) {
         SFLPhoneAccessibility::getInstance()->say(i18n("You call have been transferred to ")+m_pTransferLE->text());
      }
566
   }
567

568
569
   m_pCallPendingTransfer = 0;
   m_pTransferLE->clear();
570

571
   m_pTransferOverlay->setVisible(false);
572
573
}

574
575
576
577
578
579
/*****************************************************************************
 *                                                                           *
 *                            View related code                              *
 *                                                                           *
 ****************************************************************************/

580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
///Show the transfer overlay
void CallView::showTransferOverlay(Call* call)
{
   if (!m_pTransferOverlay) {
      kDebug() << "Creating overlay";
   }
   m_pTransferOverlay->setVisible(true);
   m_pCallPendingTransfer = call;
   m_pActiveOverlay = m_pTransferOverlay;
   m_pTransferLE->setFocus();
   connect(call,SIGNAL(changed()),this,SLOT(hideOverlay()));
}

///Is there an active overlay
bool CallView::haveOverlay()
{
   return (m_pActiveOverlay && m_pActiveOverlay->isVisible());
}

///Remove the active overlay
void CallView::hideOverlay()
{
602
603
   if (m_pActiveOverlay){
      disconnect(m_pCallPendingTransfer,SIGNAL(changed()),this,SLOT(hideOverlay()));
604
      m_pActiveOverlay->setVisible(false);
605
   }
606

607
   m_pActiveOverlay = 0;
608

609
   m_pCallPendingTransfer = 0;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
610
} //hideOverlay
611
612
613
614
615
616
617
618
619

///Be sure the size of the overlay stay the same
void CallView::resizeEvent (QResizeEvent *e)
{
   if (m_pTransferOverlay)
      m_pTransferOverlay->resize(size());
   QTreeWidget::resizeEvent(e);
}

620
///Set the TreeView header text
621
void CallView::setTitle(const QString& title)
622
623
624
625
626
{
   headerItem()->setText(0,title);
}

///Return the current item
627
Call* CallView::getCurrentItem()
628
{
629
630
   if (currentItem() && SFLPhone::model()->getCall(QTreeWidget::currentItem()))
      return SFLPhone::model()->getCall(QTreeWidget::currentItem());
631
632
633
634
635
   else
      return false;
}

///Remove a TreeView item and delete it
636
bool CallView::removeItem(Call* item)
637
{
638
639
640
641
642
643
   if (indexOfTopLevelItem(SFLPhone::model()->getIndex(item)) != -1) {
      QTreeWidgetItem* parent = itemAt(indexOfTopLevelItem(SFLPhone::model()->getIndex(item)),0);
      removeItemWidget(SFLPhone::model()->getIndex(item),0);
      if (parent->childCount() == 0) //TODO this have to be done in the daemon, not here, but oops still happen too often to ignore
         removeItemWidget(parent,0);
      return true;
644
645
646
647
648
649
   }
   else
      return false;
}

///Return the TreeView, this
650
QWidget* CallView::getWidget()
651
652
653
654
655
{
   return this;
}

///Convenience wrapper around extractItem(QTreeWidgetItem*)
656
QTreeWidgetItem* CallView::extractItem(const QString& callId)
657
{
658
   QTreeWidgetItem* currentItem = SFLPhone::model()->getIndex(callId);
659
660
661
662
   return extractItem(currentItem);
}

///Extract an item from the TreeView and return it, the item is -not- deleted
663
QTreeWidgetItem* CallView::extractItem(QTreeWidgetItem* item)
664
{
665
666
   if (!item)
      return nullptr;
667
   QTreeWidgetItem* parentItem = item->parent();
668

669
670
   if (parentItem) {
      if ((indexOfTopLevelItem(parentItem) == -1 ) || (parentItem->indexOfChild(item) == -1)) {
671
         kDebug() << "The conversation does not exist";
672
673
         return 0;
      }
674

675
676
677
678
679
680
      QTreeWidgetItem* toReturn = parentItem->takeChild(parentItem->indexOfChild(item));

      return toReturn;
   }
   else
      return takeTopLevelItem(indexOfTopLevelItem(item));
Emmanuel Lepage's avatar
Emmanuel Lepage committed
681
} //extractItem
682
683

///Convenience wrapper around insertItem(QTreeWidgetItem*, QTreeWidgetItem*)
684
CallTreeItem* CallView::insertItem(QTreeWidgetItem* item, Call* parent)
685
{
686
   return insertItem(item,(parent)?SFLPhone::model()->getIndex(parent):0);
687
688
689
}

///Insert a TreeView item in the TreeView as child of parent or as a top level item, also restore the item Widget
690
CallTreeItem* CallView::insertItem(QTreeWidgetItem* item, QTreeWidgetItem* parent)
691
{
692
   if (!dynamic_cast<QTreeWidgetItem*>(item) && SFLPhone::model()->getCall(item) && !dynamic_cast<QTreeWidgetItem*>(parent))
Emmanuel Lepage's avatar
Emmanuel Lepage committed
693
      return nullptr;
694

695
   if (!item) {
696
      kDebug() << "This is not a valid call";
697
698
      return 0;
   }
699

700
701
702
703
   if (!parent)
      insertTopLevelItem(0,item);
   else
      parent->addChild(item);
704

705
   CallTreeItem* callItem = new CallTreeItem();
706
707
708
   connect(callItem, SIGNAL(askTransfer(Call*))                     , this, SLOT(showTransferOverlay(Call*)              ));
   connect(callItem, SIGNAL(transferDropEvent(Call*,QMimeData*))    , this, SLOT(transferDropEvent(Call*,QMimeData*)     ));
   connect(callItem, SIGNAL(conversationDropEvent(Call*,QMimeData*)), this, SLOT(conversationDropEvent(Call*,QMimeData*) ));
709

710
711
   SFLPhone::model()->updateWidget(SFLPhone::model()->getCall(item), callItem);
   callItem->setCall(SFLPhone::model()->getCall(item));
712

713
   setItemWidget(item,0,callItem);
714

715
716
   expandAll();
   return callItem;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
717
} //insertItem
718
719

///Remove a call from the interface
720
void CallView::destroyCall(Call* toDestroy)
721
{
722
   if (SFLPhone::model()->getIndex(toDestroy) == currentItem())
723
      setCurrentItem(0);
724

725
   if (!SFLPhone::model()->getIndex(toDestroy))
726
       kDebug() << "Call not found";
727
728
   else if (indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)) != -1)
      takeTopLevelItem(indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)));
729
   else if (SFLPhone::model()->getIndex(toDestroy)->parent()) {
730
731
      QTreeWidgetItem* callIndex = SFLPhone::model()->getIndex(toDestroy);
      QTreeWidgetItem* parent = callIndex->parent();
732
733
734
735
736
      if (indexOfTopLevelItem(parent) != -1) {
         parent->removeChild(callIndex);
         if (dynamic_cast<QTreeWidgetItem*>(parent) && parent->childCount() == 0) /*This should never happen, but it does*/
            takeTopLevelItem(indexOfTopLevelItem(parent));
      }
737
   }
738
   else
739
      kDebug() << "Call not found";
Emmanuel Lepage's avatar
Emmanuel Lepage committed
740
} //destroyCall
741
742

/// @todo Remove the text partially covering the TreeView item widget when it is being dragged, a beter implementation is needed
743
void CallView::clearArtefact(QTreeWidgetItem* item)
744
{
745
746
   if (item)
      item->setText(0,"");
747
748
749
750
751
752
753
754
755
756
757
}


/*****************************************************************************
 *                                                                           *
 *                           Event related code                              *
 *                                                                           *
 ****************************************************************************/

void CallView::itemDoubleClicked(QTreeWidgetItem* item, int column) {
   Q_UNUSED(column)
758
   kDebug() << "Item doubleclicked" << SFLPhone::model()->getCall(item)->getState();
759
760
761
762
   switch(SFLPhone::model()->getCall(item)->getState()) {
      case CALL_STATE_INCOMING:
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_ACCEPT);
         break;
763
      case CALL_STATE_HOLD:
764
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_HOLD);
765
766
         break;
      case CALL_STATE_DIALING:
767
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_ACCEPT);
768
769
         break;
      default:
770
         kDebug() << "Double clicked an item with no action on double click.";
771
    }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
772
} //itemDoubleClicked
773
774
775

void CallView::itemClicked(QTreeWidgetItem* item, int column) {
   Q_UNUSED(column)
776
777
   Call* call = SFLPhone::model()->getCall(item);
   call->setSelected(true);
778
779
780
781
782

   if (ConfigurationSkeleton::enableReadDetails()) {
      SFLPhoneAccessibility::getInstance()->currentCallDetails();
   }

783
   emit itemChanged(call);
784
   kDebug() << "Item clicked";
785
786
787
788
789
790
791
792
793
794
}


/*****************************************************************************
 *                                                                           *
 *                         Conference related code                           *
 *                                                                           *
 ****************************************************************************/

///Add a new conference, get the call list and update the interface as needed
795
Call* CallView::addConference(Call* conf)
796
{
797
798
   kDebug() << "Conference created";
   Call* newConf =  conf;
799

800
   QTreeWidgetItem* confItem = new QTreeWidgetItem();
801
   SFLPhone::model()->updateIndex(conf,confItem);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
802

803
   insertItem(confItem,(QTreeWidgetItem*)0);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
804

805

806
807
808
   setCurrentItem(confItem);

   CallManagerInterface& callManager = CallManagerInterfaceSingleton::getInstance();
809
   QStringList callList = callManager.getParticipantList(conf->getConfId());
810

811
   foreach (QString callId, callList) {
812
      kDebug() << "Adding " << callId << "to the conversation";
813
      insertItem(extractItem(SFLPhone::model()->getIndex(callId)),confItem);
814
   }
815
   
816
   return newConf;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
817
} //addConference
818
819

///Executed when the daemon signal a modification in an existing conference. Update the call list and update the TreeView
820
bool CallView::conferenceChanged(Call* conf)
821
{
822
   if (!dynamic_cast<Call*>(conf)) return false;
823
   kDebug() << "Conference changed";
824
825

   CallManagerInterface& callManager = CallManagerInterfaceSingleton::getInstance();
826
   QStringList callList = callManager.getParticipantList(conf->getConfId());
827
828
829

   QList<QTreeWidgetItem*> buffer;
   foreach (QString callId, callList) {
830
831
832
833
      if (SFLPhone::model()->getCall(callId)) {
         QTreeWidgetItem* item3 = extractItem(SFLPhone::model()->getIndex(callId));
         insertItem(item3, SFLPhone::model()->getIndex(conf));
         buffer << SFLPhone::model()->getIndex(callId);
834
835
      }
      else
836
         kDebug() << "Call " << callId << " does not exist";
837
838
   }

839
840
841
842
843
   QTreeWidgetItem* item = SFLPhone::model()->getIndex(conf);
   if (item) /*Can happen if the daemon crashed*/
      for (int j =0; j < item->childCount();j++) {
         if (buffer.indexOf(item->child(j)) == -1)
            insertItem(extractItem(item->child(j)));
844
      }
845

846
   Q_ASSERT_X(SFLPhone::model()->getIndex(conf)->childCount() == 0,"changing conference","A conference can't have no participants");
847

848
   return true;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
849
} //conferenceChanged
850
851

///Remove a conference from the model and the TreeView
852
void CallView::conferenceRemoved(Call* conf)
853
{
854
   kDebug() << "Attempting to remove conference";
855
856
857
858
   QTreeWidgetItem* idx = SFLPhone::model()->getIndex(conf);
   if (idx) {
   while (idx->childCount()) {
      insertItem(extractItem(SFLPhone::model()->getIndex(conf)->child(0)));
859
   }
860
   takeTopLevelItem(indexOfTopLevelItem(SFLPhone::model()->getIndex(conf)));
861
   kDebug() << "Conference removed";
862
863
   }
   else {
864
      kDebug() << "Conference not found";
865
   }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
866
} //conferenceRemoved
867
868
869
870

///Clear the list of old calls //TODO Clear them from the daemon
void CallView::clearHistory()
{
871
   //SFLPhone::model()->getHistory().clear();
872
873
}

874
875
876
877
///Redirect keypresses to parent
void CallView::keyPressEvent(QKeyEvent* event) {
   SFLPhone::app()->view()->keyPressEvent(event);
}
878

879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
///Move selection using arrow keys
void CallView::moveSelectedItem( Qt::Key direction )
{
   if (direction == Qt::Key_Left) {
      setCurrentIndex(moveCursor(QAbstractItemView::MoveLeft ,Qt::NoModifier));
   }
   else if (direction == Qt::Key_Right) {
      setCurrentIndex(moveCursor(QAbstractItemView::MoveRight,Qt::NoModifier));
   }
   else if (direction == Qt::Key_Up) {
      qDebug() << "Move up";
      setCurrentIndex(moveCursor(QAbstractItemView::MoveUp   ,Qt::NoModifier));
   }
   else if (direction == Qt::Key_Down) {
      setCurrentIndex(moveCursor(QAbstractItemView::MoveDown ,Qt::NoModifier));
   }
}
896
897
898
899
900
901
902
903
904
905
906
907
908

/*****************************************************************************
 *                                                                           *
 *                                 Overlay                                   *
 *                                                                           *
 ****************************************************************************/

///Constructor
CallViewOverlay::CallViewOverlay(QWidget* parent) : QWidget(parent),m_pIcon(0),m_pTimer(0),m_enabled(true),m_black("black")
{
   m_black.setAlpha(75);
}

Emmanuel Lepage's avatar
Emmanuel Lepage committed
909
///Destructor
910
911
CallViewOverlay::~CallViewOverlay()
{
912

913
914
}

915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
///Add a widget (usually an icon) in the corner
void CallViewOverlay::setCornerWidget(QWidget* wdg) {
   wdg->setParent      ( this                       );
   wdg->setMinimumSize ( 100         , 100          );
   wdg->resize         ( 100         , 100          );
   wdg->move           ( width()-100 , height()-100 );
   m_pIcon = wdg;
}

///Overload the setVisible method to trigger the timer
void CallViewOverlay::setVisible(bool enabled) {
   if (m_enabled != enabled) {
      if (m_pTimer) {
         m_pTimer->stop();
         disconnect(m_pTimer);
930
         delete m_pTimer;
931
      }
932
      m_pTimer = new QTimer(this);
933
934
935
936
937
938
939
940
      connect(m_pTimer, SIGNAL(timeout()), this, SLOT(changeVisibility()));
      m_step = 0;
      m_black.setAlpha(0);
      repaint();
      m_pTimer->start(10);
   }
   m_enabled = enabled;
   QWidget::setVisible(enabled);
941
942
943
   if (!m_accessMessage.isEmpty() && enabled == true && ConfigurationSkeleton::enableReadLabel()) {
      SFLPhoneAccessibility::getInstance()->say(m_accessMessage);
   }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
944
} //setVisible
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968

///How to paint the overlay
void CallViewOverlay::paintEvent(QPaintEvent* event) {
   Q_UNUSED(event)
   QPainter customPainter(this);
   customPainter.fillRect(rect(),m_black);
}

///Be sure the event is always the right size
void CallViewOverlay::resizeEvent(QResizeEvent *e) {
   Q_UNUSED(e)
   if (m_pIcon) {
      m_pIcon->setMinimumSize(100,100);
      m_pIcon->move(width()-100,height()-100);
   }
}

///Step by step animation to fade in/out
void CallViewOverlay::changeVisibility() {
   m_step++;
   m_black.setAlpha(0.1*m_step*m_step);
   repaint();
   if (m_step >= 35)
      m_pTimer->stop();
969
970
971
972
973
974
}

///Set accessibility message
void CallViewOverlay::setAccessMessage(QString message)
{
   m_accessMessage = message;
975
}