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
//SFLPhone
#include "widgets/CallTreeItem.h"
45
#include "SFLPhone.h"
Emmanuel Lepage's avatar
Emmanuel Lepage committed
46
#include "SFLPhoneView.h"
47
#include "klib/AkonadiBackend.h"
48
49
#include "klib/ConfigurationSkeleton.h"
#include "SFLPhoneAccessibility.h"
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
///@class CallTreeItemDelegate Delegates for CallTreeItem
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);
      /*if (!index.parent().isValid()) {
        sh.rheight() += 2 * m_categoryDrawer.leftMargin();
      } else {
        sh.rheight() += m_categoryDrawer.leftMargin();
      }
      if (index.column() == 0) {
        sh.rwidth() += m_categoryDrawer.leftMargin();
      } else if (index.column() == 1) {
        sh.rwidth() = 150;
      } else {
        sh.rwidth() += m_categoryDrawer.leftMargin();
      }*/
      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;
};

88
89

///Retrieve current and older calls from the daemon, fill history and the calls TreeView and enable drag n' drop
90
CallView::CallView(QWidget* parent) : QTreeWidget(parent),m_pActiveOverlay(0),m_pCallPendingTransfer(0)
91
92
93
94
{
   //Widget part
   setAcceptDrops(true);
   setDragEnabled(true);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
95
   setAnimated   (true);
96
   setUniformRowHeights(false);
97

98
   CallTreeItemDelegate *delegate = new CallTreeItemDelegate(this);
99
   setItemDelegate(delegate);
100
   setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding));
101

Emmanuel Lepage's avatar
Emmanuel Lepage committed
102
103
104
105
106
107
108
   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              );
109

110
111
   m_pTransferOverlay->setVisible(false);
   m_pTransferOverlay->resize(size());
112
   m_pTransferOverlay->setCornerWidget(lblImg);
113
   m_pTransferOverlay->setAccessMessage(i18n("Please enter a transfer number and press enter, press escape to cancel"));
114

115
   m_pTransferB->setText(i18n("Transfer"));
116
   m_pTransferB->setMaximumSize(70,9000);
117

Emmanuel Lepage's avatar
Emmanuel Lepage committed
118
119
120
121
   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 );
122

123
124
125
126
   foreach(Call* active, SFLPhone::model()->getCallList()) {
      addCall(active);
   }

127
   foreach(Call* active, SFLPhone::model()->getConferenceList()) {
128
129
      if (qobject_cast<Call*>(active)) //As of May 2012, the deamon still produce fake conferences
         addConference(active);
130
131
   }

Emmanuel Lepage's avatar
Emmanuel Lepage committed
132
   //User Interface even
133
134
135
136
137
138
139
140
   //              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*))                    );
141
142
   /**/connect(m_pTransferB      , SIGNAL(clicked()                                            ) , this, SLOT( transfer())                              );
   /**/connect(m_pTransferLE     , SIGNAL(returnPressed()                                      ) , this, SLOT( transfer())                              );
143
   /*                                                                                                                                                   */
144

Emmanuel Lepage's avatar
Emmanuel Lepage committed
145
} //CallView
146

147
148
149
150
151
152
153
154
155
///Destructor
CallView::~CallView()
{
   delete m_pTransferB;
   delete m_pTransferLE;
   if (m_pTransferOverlay) delete m_pTransferOverlay;
   if (m_pActiveOverlay)   delete m_pActiveOverlay;
}

156
157
158
159
160
161
162

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

163
164
165
166
167
168
169
170
171
172
///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 )
{
173
   e->accept();
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
}

///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
197
///A call is dropped on another call
198
bool CallView::callToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
199
200
201
{
   Q_UNUSED(index)
   Q_UNUSED(action)
202
203
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   if (!QString(encodedCallId).isEmpty()) {
204
      if (SFLPhone::model()->getIndex(encodedCallId) && dynamic_cast<Call*>(SFLPhone::model()->getCall(encodedCallId))) //Prevent a race
205
        clearArtefact(SFLPhone::model()->getIndex(encodedCallId));
206

207
      if (!parent) {
208
         kDebug() << "Call dropped on empty space";
209
         if (SFLPhone::model()->getIndex(encodedCallId)->parent()) {
210
            kDebug() << "Detaching participant";
211
            SFLPhone::model()->detachParticipant(SFLPhone::model()->getCall(encodedCallId));
212
213
         }
         else
214
            kDebug() << "The call is not in a conversation (doing nothing)";
215
216
         return true;
      }
217

218
      if (SFLPhone::model()->getCall(parent)->getCallId() == QString(encodedCallId)) {
219
         kDebug() << "Call dropped on itself (doing nothing)";
220
221
         return true;
      }
222

223
      if ((parent->childCount()) && (SFLPhone::model()->getIndex(encodedCallId)->childCount())) {
224
         kDebug() << "Merging two conferences";
225
         SFLPhone::model()->mergeConferences(SFLPhone::model()->getCall(parent),SFLPhone::model()->getCall(encodedCallId));
226
227
228
         return true;
      }
      else if ((parent->parent()) || (parent->childCount())) {
229
         kDebug() << "Call dropped on a conference";
230

231
         if (SFLPhone::model()->getIndex(encodedCallId)->childCount() && (SFLPhone::model()->getIndex(encodedCallId)->childCount()) && (!parent->childCount())) {
232
            kDebug() << "Conference dropped on a call (doing nothing)";
233
234
            return true;
         }
235

236
         QTreeWidgetItem* call1 = SFLPhone::model()->getIndex(encodedCallId);
237
         QTreeWidgetItem* call2 = (parent->parent())?parent->parent():parent;
238

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

271
      kDebug() << "Call dropped on another call";
272
      SFLPhone::model()->createConferenceFromCall(SFLPhone::model()->getCall(encodedCallId),SFLPhone::model()->getCall(parent));
273
274
      return true;
   }
275
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
276
} //callToCall
277

Emmanuel Lepage's avatar
Emmanuel Lepage committed
278
///A string containing a call number is dropped on a call
279
280
281
282
283
284
285
286
287
288
289
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()) {
      Contact* contact = AkonadiBackend::getInstance()->getContactByPhone(encodedPhoneNumber);
      QString name;
      if (contact)
         name = contact->getFormattedName();
      else
290
         name = i18n("Unknown");
291
      Call* call2 = SFLPhone::model()->addDialingCall(name, SFLPhone::model()->getCurrentAccountId());
292
293
294
      call2->appendText(QString(encodedPhoneNumber));
      if (!parent) {
         //Dropped on free space
295
         kDebug() << "Adding new dialing call";
296
297
298
299
      }
      else if (parent->childCount() || parent->parent()) {
         //Dropped on a conversation
         QTreeWidgetItem* call = (parent->parent())?parent->parent():parent;
300
         SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call),call2);
301
302
303
304
      }
      else {
         //Dropped on call
         call2->actionPerformed(CALL_ACTION_ACCEPT);
305
         int state = SFLPhone::model()->getCall(parent)->getState();
306
         if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
307
            SFLPhone::model()->getCall(parent)->actionPerformed(CALL_ACTION_ACCEPT);
308
         }
309
         SFLPhone::model()->createConferenceFromCall(call2,SFLPhone::model()->getCall(parent));
310
311
312
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
313
} //phoneNumberToCall
314

Emmanuel Lepage's avatar
Emmanuel Lepage committed
315
///A contact ID is dropped on a call
316
317
bool CallView::contactToCall(QTreeWidgetItem *parent, int index, const QMimeData *data, Qt::DropAction action)
{
318
   kDebug() << "contactToCall";
319
320
   Q_UNUSED( index  )
   Q_UNUSED( action )
321
322
323
324
   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
325
         Call* call2 = NULL;
326
         if (!SFLPhone::app()->view()->selectCallPhoneNumber(call2,contact))
327
328
329
            return false;
         if (!parent) {
            //Dropped on free space
330
            kDebug() << "Adding new dialing call";
331
332
333
334
         }
         else if (parent->childCount() || parent->parent()) {
            //Dropped on a conversation
            QTreeWidgetItem* call = (parent->parent())?parent->parent():parent;
335
            SFLPhone::model()->addParticipant(SFLPhone::model()->getCall(call),call2);
336
337
338
339
         }
         else {
            //Dropped on call
            call2->actionPerformed(CALL_ACTION_ACCEPT);
340
            int state = SFLPhone::model()->getCall(parent)->getState();
341
            if(state == CALL_STATE_INCOMING || state == CALL_STATE_DIALING || state == CALL_STATE_TRANSFER || state == CALL_STATE_TRANSF_HOLD) {
342
               SFLPhone::model()->getCall(parent)->actionPerformed(CALL_ACTION_ACCEPT);
343
            }
344
            SFLPhone::model()->createConferenceFromCall(call2,SFLPhone::model()->getCall(parent));
345
346
347
348
         }
      }
   }
   return false;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
349
} //contactToCall
350
351

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

357
358
359
360
361
   QByteArray encodedCallId      = data->data( MIME_CALLID      );
   QByteArray encodedPhoneNumber = data->data( MIME_PHONENUMBER );
   QByteArray encodedContact     = data->data( MIME_CONTACT     );

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

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

384
   QMimeData *mimeData = new QMimeData();
385

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

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

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


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

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

416
   setCurrentItem(callItem);
417

418
419
420
421
   connect(call, SIGNAL(isOver(Call*)), this, SLOT(destroyCall(Call*)));
   return call;
}

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

432
433
   m_pCallPendingTransfer = 0;
   m_pTransferLE->clear();
434

435
   m_pTransferOverlay->setVisible(false);
436
437
}

438
439
440
441
442
443
/*****************************************************************************
 *                                                                           *
 *                            View related code                              *
 *                                                                           *
 ****************************************************************************/

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

471
   m_pActiveOverlay = 0;
472

473
   m_pCallPendingTransfer = 0;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
474
} //hideOverlay
475
476
477
478
479
480
481
482
483

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

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

///Select an item in the TreeView
491
bool CallView::selectItem(Call* item)
492
{
493
494
   if (SFLPhone::model()->getIndex(item)) {
      setCurrentItem(SFLPhone::model()->getIndex(item));
495
496
497
498
499
500
501
      return true;
   }
   else
      return false;
}

///Return the current item
502
Call* CallView::getCurrentItem()
503
{
504
505
   if (currentItem() && SFLPhone::model()->getCall(QTreeWidget::currentItem()))
      return SFLPhone::model()->getCall(QTreeWidget::currentItem());
506
507
508
509
510
   else
      return false;
}

///Remove a TreeView item and delete it
511
bool CallView::removeItem(Call* item)
512
{
513
514
515
516
517
518
   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;
519
520
521
522
523
524
   }
   else
      return false;
}

///Return the TreeView, this
525
QWidget* CallView::getWidget()
526
527
528
529
530
{
   return this;
}

///Convenience wrapper around extractItem(QTreeWidgetItem*)
531
QTreeWidgetItem* CallView::extractItem(const QString& callId)
532
{
533
   QTreeWidgetItem* currentItem = SFLPhone::model()->getIndex(callId);
534
535
536
537
   return extractItem(currentItem);
}

///Extract an item from the TreeView and return it, the item is -not- deleted
538
QTreeWidgetItem* CallView::extractItem(QTreeWidgetItem* item)
539
{
540
541
   if (!item)
      return nullptr;
542
   QTreeWidgetItem* parentItem = item->parent();
543

544
545
   if (parentItem) {
      if ((indexOfTopLevelItem(parentItem) == -1 ) || (parentItem->indexOfChild(item) == -1)) {
546
         kDebug() << "The conversation does not exist";
547
548
         return 0;
      }
549

550
551
552
553
554
555
      QTreeWidgetItem* toReturn = parentItem->takeChild(parentItem->indexOfChild(item));

      return toReturn;
   }
   else
      return takeTopLevelItem(indexOfTopLevelItem(item));
Emmanuel Lepage's avatar
Emmanuel Lepage committed
556
} //extractItem
557
558

///Convenience wrapper around insertItem(QTreeWidgetItem*, QTreeWidgetItem*)
559
CallTreeItem* CallView::insertItem(QTreeWidgetItem* item, Call* parent)
560
{
561
   return insertItem(item,(parent)?SFLPhone::model()->getIndex(parent):0);
562
563
564
}

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

570
   if (!item) {
571
      kDebug() << "This is not a valid call";
572
573
      return 0;
   }
574

575
576
577
578
   if (!parent)
      insertTopLevelItem(0,item);
   else
      parent->addChild(item);
579

580
   CallTreeItem* callItem = new CallTreeItem();
581
582
583
   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*) ));
584

585
586
   SFLPhone::model()->updateWidget(SFLPhone::model()->getCall(item), callItem);
   callItem->setCall(SFLPhone::model()->getCall(item));
587

588
   setItemWidget(item,0,callItem);
589

590
591
   expandAll();
   return callItem;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
592
} //insertItem
593
594

///Remove a call from the interface
595
void CallView::destroyCall(Call* toDestroy)
596
{
597
   if (SFLPhone::model()->getIndex(toDestroy) == currentItem())
598
      setCurrentItem(0);
599

600
   if (!SFLPhone::model()->getIndex(toDestroy))
601
       kDebug() << "Call not found";
602
603
   else if (indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)) != -1)
      takeTopLevelItem(indexOfTopLevelItem(SFLPhone::model()->getIndex(toDestroy)));
604
605
   else if (SFLPhone::model()->getIndex(toDestroy)->parent()) {
      QTreeWidgetItem* parent = SFLPhone::model()->getIndex(toDestroy)->parent();
606
      SFLPhone::model()->getIndex(toDestroy)->parent()->removeChild(SFLPhone::model()->getIndex(toDestroy));
607
608
609
      if (parent->childCount() == 0) /*This should never happen, but it does*/
         takeTopLevelItem(indexOfTopLevelItem(parent));
   }
610
   else
611
      kDebug() << "Call not found";
Emmanuel Lepage's avatar
Emmanuel Lepage committed
612
} //destroyCall
613
614

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


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

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

void CallView::itemClicked(QTreeWidgetItem* item, int column) {
   Q_UNUSED(column)
648
649
   Call* call = SFLPhone::model()->getCall(item);
   call->setSelected(true);
650
651
652
653
654

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

655
   emit itemChanged(call);
656
   kDebug() << "Item clicked";
657
658
659
660
661
662
663
664
665
666
}


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

///Add a new conference, get the call list and update the interface as needed
667
Call* CallView::addConference(Call* conf)
668
{
669
670
   kDebug() << "Conference created";
   Call* newConf =  conf;
671

672
   QTreeWidgetItem* confItem = new QTreeWidgetItem();
673
   SFLPhone::model()->updateIndex(conf,confItem);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
674

675
   insertItem(confItem,(QTreeWidgetItem*)0);
Emmanuel Lepage's avatar
Emmanuel Lepage committed
676

677

678
679
680
   setCurrentItem(confItem);

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

683
   foreach (QString callId, callList) {
684
      kDebug() << "Adding " << callId << "to the conversation";
685
      insertItem(extractItem(SFLPhone::model()->getIndex(callId)),confItem);
686
   }
687

688
689
   Q_ASSERT_X(confItem->childCount() == 0, "add conference","Conference created, but without any participants");
   return newConf;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
690
} //addConference
691
692

///Executed when the daemon signal a modification in an existing conference. Update the call list and update the TreeView
693
bool CallView::conferenceChanged(Call* conf)
694
{
695
   if (!dynamic_cast<Call*>(conf)) return false;
696
   kDebug() << "Conference changed";
697
698

   CallManagerInterface& callManager = CallManagerInterfaceSingleton::getInstance();
699
   QStringList callList = callManager.getParticipantList(conf->getConfId());
700
701
702

   QList<QTreeWidgetItem*> buffer;
   foreach (QString callId, callList) {
703
704
705
706
      if (SFLPhone::model()->getCall(callId)) {
         QTreeWidgetItem* item3 = extractItem(SFLPhone::model()->getIndex(callId));
         insertItem(item3, SFLPhone::model()->getIndex(conf));
         buffer << SFLPhone::model()->getIndex(callId);
707
708
      }
      else
709
         kDebug() << "Call " << callId << " does not exist";
710
711
   }

712
713
714
715
716
   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)));
717
      }
718

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

721
   return true;
Emmanuel Lepage's avatar
Emmanuel Lepage committed
722
} //conferenceChanged
723
724

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

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

748
749
750
751
///Redirect keypresses to parent
void CallView::keyPressEvent(QKeyEvent* event) {
   SFLPhone::app()->view()->keyPressEvent(event);
}
752

753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
///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));
   }
}
770
771
772
773
774
775
776
777
778
779
780
781
782

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

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

783
784
CallViewOverlay::~CallViewOverlay()
{
785

786
787
}

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

///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();
842
843
844
845
846
847
}

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