CallView.cpp 32.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

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

    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)
            sh.rheight() = widget->sizeHint().height()+4;
      }

      return sh;
    }
private:
   CallView* m_tree;
};

77
78

///Retrieve current and older calls from the daemon, fill history and the calls TreeView and enable drag n' drop
79
CallView::CallView(QWidget* parent) : QTreeWidget(parent),m_pActiveOverlay(0),m_pCallPendingTransfer(0)
80
81
82
83
{
   //Widget part
   setAcceptDrops(true);
   setDragEnabled(true);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
84
   setAnimated   (true);
85
   setUniformRowHeights(false);
86

87
   CallTreeItemDelegate *delegate = new CallTreeItemDelegate(this);
88
   setItemDelegate(delegate);
89
   setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
90

Emmanuel Lepage's avatar
Emmanuel Lepage committed
91
92
93
94
95
96
97
   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              );
98

99
100
   m_pTransferOverlay->setVisible(false);
   m_pTransferOverlay->resize(size());
101
   m_pTransferOverlay->setCornerWidget(lblImg);
102
   m_pTransferOverlay->setAccessMessage(i18n("Please enter a transfer number and press enter, press escape to cancel"));
103

104
   m_pTransferB->setText(i18n("Transfer"));
105
   m_pTransferB->setMaximumSize(70,9000);
106

Emmanuel Lepage's avatar
Emmanuel Lepage committed
107
108
109
110
   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 );
111

112
113
114
115
   foreach(Call* active, SFLPhone::model()->getCallList()) {
      addCall(active);
   }

116
   foreach(Call* active, SFLPhone::model()->getConferenceList()) {
117
118
      if (qobject_cast<Call*>(active)) //As of May 2012, the deamon still produce fake conferences
         addConference(active);
119
120
   }

Emmanuel Lepage's avatar
Emmanuel Lepage committed
121
   //User Interface even
122
123
124
125
126
127
128
129
   //              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*))                    );
130
131
   /**/connect(m_pTransferB      , SIGNAL(clicked()                                            ) , this, SLOT( transfer())                              );
   /**/connect(m_pTransferLE     , SIGNAL(returnPressed()                                      ) , this, SLOT( transfer())                              );
132
   /*                                                                                                                                                   */
133

Emmanuel Lepage's avatar
Emmanuel Lepage committed
134
} //CallView
135

136
137
138
139
140
141
142
143
144
///Destructor
CallView::~CallView()
{
   delete m_pTransferB;
   delete m_pTransferLE;
   if (m_pTransferOverlay) delete m_pTransferOverlay;
   if (m_pActiveOverlay)   delete m_pActiveOverlay;
}

145
146
147
148
149
150
151

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

152
153
154
155
156
157
158
159
160
161
///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 )
{
162
   e->accept();
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
}

///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
186
///A call is dropped on another call
187
bool CallView::callToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
188
189
190
{
   Q_UNUSED(index)
   Q_UNUSED(action)
191
192
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   if (!QString(encodedCallId).isEmpty()) {
193
      if (SFLPhone::model()->getIndex(encodedCallId) && dynamic_cast<Call*>(SFLPhone::model()->getCall(encodedCallId))) //Prevent a race
194
        clearArtefact(SFLPhone::model()->getIndex(encodedCallId));
195

196
      if (!parent) {
197
         kDebug() << "Call dropped on empty space";
198
         if (SFLPhone::model()->getIndex(encodedCallId)->parent()) {
199
            kDebug() << "Detaching participant";
200
            SFLPhone::model()->detachParticipant(SFLPhone::model()->getCall(encodedCallId));
201
202
         }
         else
203
            kDebug() << "The call is not in a conversation (doing nothing)";
204
205
         return true;
      }
206

207
      if (SFLPhone::model()->getCall(parent)->getCallId() == QString(encodedCallId)) {
208
         kDebug() << "Call dropped on itself (doing nothing)";
209
210
         return true;
      }
211
212
213
214
      else if (SFLPhone::model()->getIndex(encodedCallId) == parent) {
         kDebug() << "Dropping conference on itself (doing nothing)";
         return true;
      }
215

216
      if ((parent->childCount()) && (SFLPhone::model()->getIndex(encodedCallId)->childCount())) {
217
         kDebug() << "Merging two conferences";
218
         SFLPhone::model()->mergeConferences(SFLPhone::model()->getCall(parent),SFLPhone::model()->getCall(encodedCallId));
219
220
221
         return true;
      }
      else if ((parent->parent()) || (parent->childCount())) {
222
         kDebug() << "Call dropped on a conference";
223

224
         if (SFLPhone::model()->getIndex(encodedCallId)->childCount() && (SFLPhone::model()->getIndex(encodedCallId)->childCount()) && (!parent->childCount())) {
225
            kDebug() << "Conference dropped on a call (doing nothing)";
226
227
            return true;
         }
228

229
         QTreeWidgetItem* call1 = SFLPhone::model()->getIndex(encodedCallId);
230
         QTreeWidgetItem* call2 = (parent->parent())?parent->parent():parent;
231

232
         if (call1->parent()) {
233
            kDebug() << "Call 1 is part of a conference";
234
            if (call1->parent() == call2) {
235
               kDebug() << "Call dropped on it's own conference (doing nothing)";
236
237
               return true;
            }
238
            else if (SFLPhone::model()->getIndex(call1)->childCount()) {
239
               kDebug() << "Merging two conferences";
240
               SFLPhone::model()->mergeConferences(SFLPhone::model()->getCall(call1),SFLPhone::model()->getCall(call2));
241
242
            }
            else if (call1->parent()) {
243
               kDebug() << "Moving call from a conference to an other";
244
               SFLPhone::model()->detachParticipant(SFLPhone::model()->getCall(encodedCallId));
245
246
            }
         }
247
         kDebug() << "Adding participant";
248
         int state = SFLPhone::model()->getCall(call1)->getState();
249
         if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
250
            SFLPhone::model()->getCall(call1)->actionPerformed(CALL_ACTION_ACCEPT);
251
         }
252
         state = SFLPhone::model()->getCall(call2)->getState();
253
         if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
254
            SFLPhone::model()->getCall(call2)->actionPerformed(CALL_ACTION_ACCEPT);
255
         }
256
         SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call1),SFLPhone::model()->getCall(call2));
257
258
         return true;
      }
259
      else if (SFLPhone::model()->getIndex(encodedCallId) && (SFLPhone::model()->getIndex(encodedCallId)->childCount()) && (!parent->childCount())) {
260
         kDebug() << "Call dropped on it's own conference (doing nothing)";
261
262
263
         return true;
      }

264
      kDebug() << "Call dropped on another call";
265
      SFLPhone::model()->createConferenceFromCall(SFLPhone::model()->getCall(encodedCallId),SFLPhone::model()->getCall(parent));
266
267
      return true;
   }
268
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
269
} //callToCall
270

Emmanuel Lepage's avatar
Emmanuel Lepage committed
271
///A string containing a call number is dropped on a call
272
273
274
275
276
277
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()) {
278
      Contact* contact = AkonadiBackend::getInstance()->getContactByPhone(encodedPhoneNumber,true);
279
      QString name;
280
      name = (contact)?contact->getFormattedName():i18n("Unknown");
281
      Call* call2 = SFLPhone::model()->addDialingCall(name, AccountList::getCurrentAccount());
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
      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));
         }
302
303
      }
      else {
304
         HelperFunctions::displayNoAccountMessageBox(this);
305
306
307
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
308
} //phoneNumberToCall
309

Emmanuel Lepage's avatar
Emmanuel Lepage committed
310
///A contact ID is dropped on a call
311
312
bool CallView::contactToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
{
313
   kDebug() << "contactToCall";
314
315
   Q_UNUSED( index  )
   Q_UNUSED( action )
316
317
318
319
   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
320
         Call* call2 = NULL;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
321
         if (!SFLPhone::app()->view()->selectCallPhoneNumber(&call2,contact))
322
323
324
            return false;
         if (!parent) {
            //Dropped on free space
325
            kDebug() << "Adding new dialing call";
326
327
328
329
         }
         else if (parent->childCount() || parent->parent()) {
            //Dropped on a conversation
            QTreeWidgetItem* call = (parent->parent())?parent->parent():parent;
330
            SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call),call2);
331
332
333
         }
         else {
            //Dropped on call
Emmanuel Lepage's avatar
Emmanuel Lepage committed
334
335
336
337
338
339
340
341
//             if (!call2) {
//                call2 = SFLPhone::model()->addDialingCall(contact->getFormattedName());
//             }
//             QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
//             if (!encodedPhoneNumber.isEmpty()) {
//                call2->setCallNumber(encodedPhoneNumber);
//             }

342
            call2->actionPerformed(CALL_ACTION_ACCEPT);
343
            int state = SFLPhone::model()->getCall(parent)->getState();
344
            if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
345
               SFLPhone::model()->getCall(parent)->actionPerformed(CALL_ACTION_ACCEPT);
346
            }
347
            SFLPhone::model()->createConferenceFromCall(call2,SFLPhone::model()->getCall(parent));
348
349
350
351
         }
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
352
} //contactToCall
353
354

///Action performed when an item is dropped on the TreeView
355
bool CallView::dropMimeData(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
356
357
358
{
   Q_UNUSED(index)
   Q_UNUSED(action)
359

360
361
362
363
364
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
   QByteArray encodedContact     = data->data( MIME_CONTACT     );

   if (!QString(encodedCallId).isEmpty()) {
365
      kDebug() << "CallId dropped"<< QString(encodedCallId);
366
367
368
      callToCall(parent, index, data, action);
   }
   else if (!QString(encodedPhoneNumber).isEmpty()) {
369
      kDebug() << "PhoneNumber dropped"<< QString(encodedPhoneNumber);
370
371
372
      phoneNumberToCall(parent, index, data, action);
   }
   else if (!QString(encodedContact).isEmpty()) {
373
      kDebug() << "Contact dropped"<< QString(encodedContact);
374
375
      contactToCall(parent, index, data, action);
   }
376
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
377
} //dropMimeData
378
379
380

///Encode data to be tranported during the drag n' drop operation
QMimeData* CallView::mimeData( const QList<QTreeWidgetItem *> items) const
381
{
382
   kDebug() << "A call is being dragged";
383
384
385
   if (items.size() < 1) {
      return NULL;
   }
386

387
   QMimeData *mimeData = new QMimeData();
388

389
   //Call ID for internal call merging and spliting
390
391
   if (SFLPhone::model()->getCall(items[0])->isConference()) {
      mimeData->setData(MIME_CALLID, SFLPhone::model()->getCall(items[0])->getConfId().toAscii());
392
393
   }
   else {
394
      mimeData->setData(MIME_CALLID, SFLPhone::model()->getCall(items[0])->getCallId().toAscii());
395
   }
396

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

400
   //TODO Comment this line if you don't want to see ugly artefact, but the caller details will not be visible while dragged
401
   items[0]->setText(0, SFLPhone::model()->getCall(items[0])->getPeerName() + "\n" + SFLPhone::model()->getCall(items[0])->getPeerPhoneNumber());
402
   return mimeData;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
403
} //mimeData
404
405
406
407
408
409
410
411
412


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

///Add a call in the model structure, the call must exist before being added to the model
413
Call* CallView::addCall(Call* call, Call* parent)
414
415
{
   QTreeWidgetItem* callItem = new QTreeWidgetItem();
416
   SFLPhone::model()->updateIndex(call,callItem);
417
   insertItem(callItem,parent);
418

419
   setCurrentItem(callItem);
420

421
422
423
424
   connect(call, SIGNAL(isOver(Call*)), this, SLOT(destroyCall(Call*)));
   return call;
}

425
426
427
///Transfer a call
void CallView::transfer()
{
428
   if (m_pCallPendingTransfer && !m_pTransferLE->text().isEmpty()) {
429
      SFLPhone::model()->transfer(m_pCallPendingTransfer,m_pTransferLE->text());
430
431
432
      if (ConfigurationSkeleton::enableVoiceFeedback()) {
         SFLPhoneAccessibility::getInstance()->say(i18n("You call have been transferred to ")+m_pTransferLE->text());
      }
433
   }
434

435
436
   m_pCallPendingTransfer = 0;
   m_pTransferLE->clear();
437

438
   m_pTransferOverlay->setVisible(false);
439
440
}

441
442
443
444
445
446
/*****************************************************************************
 *                                                                           *
 *                            View related code                              *
 *                                                                           *
 ****************************************************************************/

447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
///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()
{
469
470
   if (m_pActiveOverlay){
      disconnect(m_pCallPendingTransfer,SIGNAL(changed()),this,SLOT(hideOverlay()));
471
      m_pActiveOverlay->setVisible(false);
472
   }
473

474
   m_pActiveOverlay = 0;
475

476
   m_pCallPendingTransfer = 0;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
477
} //hideOverlay
478
479
480
481
482
483
484
485
486

///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);
}

487
///Set the TreeView header text
488
void CallView::setTitle(const QString& title)
489
490
491
492
493
{
   headerItem()->setText(0,title);
}

///Return the current item
494
Call* CallView::getCurrentItem()
495
{
496
497
   if (currentItem() && SFLPhone::model()->getCall(QTreeWidget::currentItem()))
      return SFLPhone::model()->getCall(QTreeWidget::currentItem());
498
499
500
501
502
   else
      return false;
}

///Remove a TreeView item and delete it
503
bool CallView::removeItem(Call* item)
504
{
505
506
507
508
509
510
   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;
511
512
513
514
515
516
   }
   else
      return false;
}

///Return the TreeView, this
517
QWidget* CallView::getWidget()
518
519
520
521
522
{
   return this;
}

///Convenience wrapper around extractItem(QTreeWidgetItem*)
523
QTreeWidgetItem* CallView::extractItem(const QString& callId)
524
{
525
   QTreeWidgetItem* currentItem = SFLPhone::model()->getIndex(callId);
526
527
528
529
   return extractItem(currentItem);
}

///Extract an item from the TreeView and return it, the item is -not- deleted
530
QTreeWidgetItem* CallView::extractItem(QTreeWidgetItem* item)
531
{
532
533
   if (!item)
      return nullptr;
534
   QTreeWidgetItem* parentItem = item->parent();
535

536
537
   if (parentItem) {
      if ((indexOfTopLevelItem(parentItem) == -1 ) || (parentItem->indexOfChild(item) == -1)) {
538
         kDebug() << "The conversation does not exist";
539
540
         return 0;
      }
541

542
543
544
545
546
547
      QTreeWidgetItem* toReturn = parentItem->takeChild(parentItem->indexOfChild(item));

      return toReturn;
   }
   else
      return takeTopLevelItem(indexOfTopLevelItem(item));
Emmanuel Lepage's avatar
Emmanuel Lepage committed
548
} //extractItem
549
550

///Convenience wrapper around insertItem(QTreeWidgetItem*, QTreeWidgetItem*)
551
CallTreeItem* CallView::insertItem(QTreeWidgetItem* item, Call* parent)
552
{
553
   return insertItem(item,(parent)?SFLPhone::model()->getIndex(parent):0);
554
555
556
}

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

562
   if (!item) {
563
      kDebug() << "This is not a valid call";
564
565
      return 0;
   }
566

567
568
569
570
   if (!parent)
      insertTopLevelItem(0,item);
   else
      parent->addChild(item);
571

572
   CallTreeItem* callItem = new CallTreeItem();
573
574
575
   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*) ));
576

577
578
   SFLPhone::model()->updateWidget(SFLPhone::model()->getCall(item), callItem);
   callItem->setCall(SFLPhone::model()->getCall(item));
579

580
   setItemWidget(item,0,callItem);
581

582
583
   expandAll();
   return callItem;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
584
} //insertItem
585
586

///Remove a call from the interface
587
void CallView::destroyCall(Call* toDestroy)
588
{
589
   if (SFLPhone::model()->getIndex(toDestroy) == currentItem())
590
      setCurrentItem(0);
591

592
   if (!SFLPhone::model()->getIndex(toDestroy))
593
       kDebug() << "Call not found";
594
595
   else if (indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)) != -1)
      takeTopLevelItem(indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)));
596
   else if (SFLPhone::model()->getIndex(toDestroy)->parent()) {
597
598
      QTreeWidgetItem* callIndex = SFLPhone::model()->getIndex(toDestroy);
      QTreeWidgetItem* parent = callIndex->parent();
599
600
601
602
603
      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));
      }
604
   }
605
   else
606
      kDebug() << "Call not found";
Emmanuel Lepage's avatar
Emmanuel Lepage committed
607
} //destroyCall
608
609

/// @todo Remove the text partially covering the TreeView item widget when it is being dragged, a beter implementation is needed
610
void CallView::clearArtefact(QTreeWidgetItem* item)
611
{
612
613
   if (item)
      item->setText(0,"");
614
615
616
617
618
619
620
621
622
623
624
}


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

void CallView::itemDoubleClicked(QTreeWidgetItem* item, int column) {
   Q_UNUSED(column)
625
   kDebug() << "Item doubleclicked" << SFLPhone::model()->getCall(item)->getState();
626
627
628
629
   switch(SFLPhone::model()->getCall(item)->getState()) {
      case CALL_STATE_INCOMING:
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_ACCEPT);
         break;
630
      case CALL_STATE_HOLD:
631
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_HOLD);
632
633
         break;
      case CALL_STATE_DIALING:
634
         SFLPhone::model()->getCall(item)->actionPerformed(CALL_ACTION_ACCEPT);
635
636
         break;
      default:
637
         kDebug() << "Double clicked an item with no action on double click.";
638
    }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
639
} //itemDoubleClicked
640
641
642

void CallView::itemClicked(QTreeWidgetItem* item, int column) {
   Q_UNUSED(column)
643
644
   Call* call = SFLPhone::model()->getCall(item);
   call->setSelected(true);
645
646
647
648
649

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

650
   emit itemChanged(call);
651
   kDebug() << "Item clicked";
652
653
654
655
656
657
658
659
660
661
}


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

///Add a new conference, get the call list and update the interface as needed
662
Call* CallView::addConference(Call* conf)
663
{
664
665
   kDebug() << "Conference created";
   Call* newConf =  conf;
666

667
   QTreeWidgetItem* confItem = new QTreeWidgetItem();
668
   SFLPhone::model()->updateIndex(conf,confItem);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
669

670
   insertItem(confItem,(QTreeWidgetItem*)0);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
671

672

673
674
675
   setCurrentItem(confItem);

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

678
   foreach (QString callId, callList) {
679
      kDebug() << "Adding " << callId << "to the conversation";
680
      insertItem(extractItem(SFLPhone::model()->getIndex(callId)),confItem);
681
   }
682
   
683
   return newConf;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
684
} //addConference
685
686

///Executed when the daemon signal a modification in an existing conference. Update the call list and update the TreeView
687
bool CallView::conferenceChanged(Call* conf)
688
{
689
   if (!dynamic_cast<Call*>(conf)) return false;
690
   kDebug() << "Conference changed";
691
692

   CallManagerInterface& callManager = CallManagerInterfaceSingleton::getInstance();
693
   QStringList callList = callManager.getParticipantList(conf->getConfId());
694
695
696

   QList<QTreeWidgetItem*> buffer;
   foreach (QString callId, callList) {
697
698
699
700
      if (SFLPhone::model()->getCall(callId)) {
         QTreeWidgetItem* item3 = extractItem(SFLPhone::model()->getIndex(callId));
         insertItem(item3, SFLPhone::model()->getIndex(conf));
         buffer << SFLPhone::model()->getIndex(callId);
701
702
      }
      else
703
         kDebug() << "Call " << callId << " does not exist";
704
705
   }

706
707
708
709
710
   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)));
711
      }
712

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

715
   return true;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
716
} //conferenceChanged
717
718

///Remove a conference from the model and the TreeView
719
void CallView::conferenceRemoved(Call* conf)
720
{
721
   kDebug() << "Attempting to remove conference";
722
723
724
725
   QTreeWidgetItem* idx = SFLPhone::model()->getIndex(conf);
   if (idx) {
   while (idx->childCount()) {
      insertItem(extractItem(SFLPhone::model()->getIndex(conf)->child(0)));
726
   }
727
   takeTopLevelItem(indexOfTopLevelItem(SFLPhone::model()->getIndex(conf)));
728
   kDebug() << "Conference removed";
729
730
   }
   else {
731
      kDebug() << "Conference not found";
732
   }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
733
} //conferenceRemoved
734
735
736
737

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

741
742
743
744
///Redirect keypresses to parent
void CallView::keyPressEvent(QKeyEvent* event) {
   SFLPhone::app()->view()->keyPressEvent(event);
}
745

746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
///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));
   }
}
763
764
765
766
767
768
769
770
771
772
773
774
775

/*****************************************************************************
 *                                                                           *
 *                                 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
776
///Destructor
777
778
CallViewOverlay::~CallViewOverlay()
{
779

780
781
}

782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
///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);
797
         delete m_pTimer;
798
      }
799
      m_pTimer = new QTimer(this);
800
801
802
803
804
805
806
807
      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);
808
809
810
   if (!m_accessMessage.isEmpty() && enabled == true && ConfigurationSkeleton::enableReadLabel()) {
      SFLPhoneAccessibility::getInstance()->say(m_accessMessage);
   }
Emmanuel Lepage's avatar
Emmanuel Lepage committed
811
} //setVisible
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835

///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();
836
837
838
839
840
841
}

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