1
/***************************************************************************
2
* Copyright (C) 2013-2014 by Savoir-Faire Linux *
3
* Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com>*
5
* This program is free software; you can redistribute it and/or modify *
6
* it under the terms of the GNU General Public License as published by *
7
* the Free Software Foundation; either version 3 of the License, or *
8
* (at your option) any later version. *
10
* This program is distributed in the hope that it will be useful, *
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13
* GNU General Public License for more details. *
15
* You should have received a copy of the GNU General Public License *
16
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
17
**************************************************************************/
18
#include "canvasobjectmanager.h"
21
#include <QtCore/QDebug>
22
#include <QtCore/QTimer>
23
#include <QtGui/QApplication>
27
// #include <xcb/xcb.h>
28
// #include <xcb/xcb_atom.h>
32
#include <KNotification>
36
#include <klib/tipmanager.h>
37
#include <widgets/tips/tipcollection.h>
41
CanvasObjectManager::Object CanvasObjectManager::m_slEvents[EVENT_COUNT] = { CanvasObjectManager::Object::NoObject };
43
#define OBJ_DEF(obj) elements[static_cast<int>(obj)]
44
#define _(NAME) CanvasObjectManager::Object##NAME::
46
const CanvasObjectManager::CanvasElement CanvasObjectManager::elements[ELEMENT_COUNT] = {
47
/* ObjectType ObjectLifeCycle ObjectPriority IN-EVENT OUT-EVENT STACK NOTIFY */
48
/*0 NoObject */ {_(Type)OBJECT , _(LifeCycle)STATIC , _(Priority)NO_PRIORITY, E::NONE , E::ANY , false , false , false},
49
/*1 DialInfo */ {_(Type)OBJECT , _(LifeCycle)STATIC , _(Priority)LOW , E::NO_CALLS , E::ANY , false , false , false},
50
/*2 EndCall */ {_(Type)OBJECT , _(LifeCycle)TIMED , _(Priority)LOW , E::CALL_ENDED , E::ANY , false , true , false},
51
/*3 Ringing */ {_(Type)OBJECT , _(LifeCycle)EVENT , _(Priority)MEDIUM , E::CALL_RINGING , E::CALL_STATE_CHANGED
53
| E::CALL_BUSY , true , false , false},
54
/*4 Network */ {_(Type)OBJECT , _(LifeCycle)EVENT , _(Priority)MEDIUM , E::NETWORK_ERROR , E::NETWORK_BACK
55
|E::REGISTERED_ACCOUNT
56
|E::CALL_RINGING , false , true , false},
57
/*5 AutoComplete */ {_(Type)WIDGET , _(LifeCycle)EVENT , _(Priority)HIGH , E::CALL_DIALING_CHANGED , E::CALL_STATE_CHANGED
60
| E::CALL_BUSY , false , false , true },
61
/*6 DropInfo */ {_(Type)OBJECT , _(LifeCycle)EVENT , _(Priority)MEDIUM , E::DRAG_ENTER|E::DRAG_MOVE , E::DRAG_LEAVE|E::DROP, false , false , false},
62
/*7 ConfInfo */ {_(Type)OBJECT , _(LifeCycle)EVENT , _(Priority)LOW , E::CALL_COUNT_CHANGED , E::ANY , false , false , false},
63
/*8 AccountDown */ {_(Type)OBJECT , _(LifeCycle)EVENT , _(Priority)HIGH , E::UNREGISTERED_ACCOUNT , E::ANY , false , true , false},
68
void CanvasObjectManager::hInitEvents() {
69
static bool init = false;
71
for (int i=0;i<EVENT_COUNT-1;i++) {
72
for (int j=1;j<ELEMENT_COUNT;j++) {
73
if (OBJ_DEF(j).inEvent & (0x01<<i))
74
CanvasObjectManager::m_slEvents[i+1] = static_cast<CanvasObjectManager::Object>(j);
81
CanvasObjectManager::CanvasObjectManager(QObject* parent) : QObject(parent),m_DisableTransition(false),
82
m_CurrentObject(CanvasObjectManager::Object::NoObject),m_NextObject(CanvasObjectManager::Object::NoObject)
83
,m_pTimer(nullptr),m_CurrentState(ObjectState::NO_OBJECT),m_Minimized(false)
87
// m_DisableTransition = true;
88
connect(TipCollection::manager(),SIGNAL(transitionStarted(QAbstractAnimation::Direction,QAbstractAnimation::State)),
89
this,SLOT(slotTransitionEvents(QAbstractAnimation::Direction,QAbstractAnimation::State)));
93
// testEventToEvent ();
94
// testEvenToObject ();
95
// testFirstInEvent ();
96
// testObjectPriority ();
97
// testObjectDiscarding();
100
CanvasObjectManager::~CanvasObjectManager()
105
///This function take a *SINGLE* flag as parameter, return corresponding object
106
CanvasObjectManager::Object CanvasObjectManager::eventToObject(CanvasEvent event) const
108
const int power = eventFlagToIndex(event);
109
return m_slEvents[power];
112
///This function take a *MULTIPLE* flag as parameter, return corresponding object
113
QList<CanvasObjectManager::Object> CanvasObjectManager::eventsToObjects(CanvasEvent events) const
117
return QList<CanvasObjectManager::Object>();
120
///Get the first event triggering an object (for testing purpose only)
121
CanvasObjectManager::CanvasEvent CanvasObjectManager::firstInEvent( Object object ) const
123
if (!OBJ_DEF(object).inEvent)
124
return CanvasObjectManager::CanvasEvent::NONE;
126
for (int power=0;power<EVENT_COUNT-1;power++) {
127
if (m_slEvents[power+1] == object)
128
return static_cast<CanvasObjectManager::CanvasEvent>(0x01<<power);
130
return CanvasObjectManager::CanvasEvent::NONE;
134
CanvasObjectManager::CanvasEvent CanvasObjectManager::eventIndexToFlag(int index) const
136
if (!index || index > EVENT_COUNT-1)
137
return CanvasEvent::NONE;
138
return index==1?CanvasEvent::ANY:static_cast<CanvasEvent>(0x01<<(index-1));
141
int CanvasObjectManager::eventFlagToIndex(CanvasObjectManager::CanvasEvent events) const
145
else if (events == CanvasEvent::ANY)
148
for (int power = EVENT_COUNT;power;power--)
149
if (events & (0x01<<power)) return power+1;
154
void CanvasObjectManager::initiateOutTransition(bool skipAnimation)
156
// qDebug() << "IN OUT" << (int)m_NextObject << (int)m_CurrentObject;
157
if (m_NextObject != m_CurrentObject) {
158
if (m_DisableTransition || skipAnimation) {
159
if (m_pTimer && m_pTimer->isActive())
161
m_CurrentObject = m_NextObject;
162
TipCollection::manager()->hideCurrentTip(true);
163
m_NextObject = CanvasObjectManager::Object::NoObject;
167
if (m_CurrentObject != Object::NoObject) {
168
switch (OBJ_DEF(m_CurrentObject).type) {
169
case ObjectType::OBJECT: {
170
//If hideCurrentTip return true, then act as if the transition was over
171
// qDebug() << "\n\n\nMMMMM" << OBJ_DEF(m_CurrentObject).skipAnimation << (int)m_NextObject;
172
if (TipCollection::manager()->hideCurrentTip(OBJ_DEF(m_NextObject).skipAnimation)) {
173
// qDebug() << "PROBLEM";
174
m_CurrentObject = m_NextObject;
175
m_NextObject = CanvasObjectManager::Object::NoObject;
178
case ObjectType::WIDGET: {
179
// qDebug() << "OUT WIDGET";
180
QWidget* w = TipCollection::canvasWidgetsToTip(m_CurrentObject);
182
w->setVisible(false);
183
m_CurrentObject = m_NextObject;
184
m_NextObject = CanvasObjectManager::Object::NoObject;
187
// qDebug() << "SHOULD HIDE TIP" << (int)m_NextObject;
194
void CanvasObjectManager::initiateInTransition(Object nextObj,const QString& message)
196
// qDebug() << "HERE" << (int)m_CurrentState << m_DisableTransition << (int)nextObj;
197
if (nextObj != m_CurrentObject) {
198
if (m_DisableTransition || m_CurrentState == ObjectState::NO_OBJECT) {
199
if (m_pTimer && m_pTimer->isActive())
201
m_CurrentObject = nextObj;
202
nextObj = CanvasObjectManager::Object::NoObject;
203
switch (OBJ_DEF(m_CurrentObject).type) {
204
case ObjectType::OBJECT: {
205
Tip* currentTip = TipCollection::canvasObjectToTip(m_CurrentObject);
207
if (OBJ_DEF(m_CurrentObject).lifeCycle == ObjectLifeCycle::TIMED && currentTip->timeout()) {
209
m_pTimer = new QTimer(this);
210
m_pTimer->setSingleShot(true);
211
connect(m_pTimer,SIGNAL(timeout()),this,SLOT(slotTimeout()));
213
if (!message.isEmpty()) {
214
currentTip->setText(message);
216
m_pTimer->setInterval(currentTip->timeout());
219
//If the window is minimized, use system notification
220
const QString m = message.isEmpty()?currentTip->text():message;
221
if (m_Minimized && !m.isEmpty())
222
KNotification::event(KNotification::Notification, i18n("SFLPhone"), m);
223
//If the window is not focused and the object request notification, use system
224
//disabled as it is not implemented by many top tier window managers
226
// Doesn't work on most window managers
227
// static xcb_connection_t *c = nullptr;
228
// if (c || (c = xcb_connect(nullptr,nullptr))) {
229
// xcb_window_t win = SFLPhone::app()->winId();
230
// xcb_generic_error_t *error;
231
// xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, 0, strlen("_NET_WM_STATE_FOCUSED"), "_NET_WM_STATE_FOCUSED");
232
// xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, cookie, &error);
233
// static xcb_atom_t _NET_WM_STATE_FOCUSED = reply->atom;
234
// xcb_get_property_cookie_t propC = xcb_get_property_unchecked(c, false, win,_NET_WM_STATE_FOCUSED, XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
235
// xcb_get_property_reply_t *replyP = xcb_get_property_reply(c, propC, NULL);
237
// bool isFocused = *((bool*)xcb_get_property_value(replyP));
238
// qDebug() << "\n\n\nClient is focused" <<isFocused;
242
// qDebug() << "\n\nNOTIF" << QApplication::activeWindow() << OBJ_DEF(m_CurrentObject).systemNotification;
243
// if (!QApplication::activeWindow() && OBJ_DEF(m_CurrentObject).systemNotification) {
244
// KNotification::event(KNotification::Notification, i18n("SFLPhone"), message.isEmpty()?currentTip->text():message);
249
TipCollection::manager()->setCurrentTip(currentTip);
253
case ObjectType::WIDGET:
254
QWidget* w = TipCollection::canvasWidgetsToTip(m_CurrentObject);
262
// qDebug() << "CURRENT IS VISIBLE" <<(int)m_CurrentObject << "next" << (int)nextObj;
263
m_NextObject = nextObj;
264
initiateOutTransition();
269
bool CanvasObjectManager::newEvent(CanvasEvent events, const QString& message)
271
// qDebug() << "NEW EVENT" << events << message << (int)m_CurrentState << (int)m_CurrentObject << (OBJ_DEF(m_CurrentObject).outEvent & events);
273
const bool singleEvent = (events & (events - 1)) == 0;
274
CanvasObjectManager::Object nextObj = eventToObject(events);
276
//First, notify the current object of the new event, it may be discarded
277
if (m_CurrentObject != CanvasObjectManager::Object::NoObject && OBJ_DEF(m_CurrentObject).outEvent & events) {
278
// qDebug() << "OUT (dsfsdf)";
279
initiateOutTransition(singleEvent && OBJ_DEF(nextObj).skipAnimation);
282
//Detect if there is multiple flags set
284
// qDebug() << "IS SINGLE" << (OBJ_DEF(nextObj).priority >= OBJ_DEF(m_CurrentObject).priority) << (int)nextObj << (int)m_CurrentObject << (int) m_CurrentState;
285
if (OBJ_DEF(nextObj).priority >= OBJ_DEF(m_CurrentObject).priority || m_CurrentState == ObjectState::TRANSITION_OUT) {
286
// qDebug() << "In IF";
287
initiateInTransition(nextObj,message);
292
QList<CanvasObjectManager::Object> nextObjs = CanvasObjectManager::eventsToObjects(events);
293
CanvasObjectManager::Object highestPriorityNextObj = CanvasObjectManager::Object::NoObject;
294
foreach(const CanvasObjectManager::Object& nextObj, nextObjs) {
295
if (OBJ_DEF(nextObj).priority > OBJ_DEF(highestPriorityNextObj).priority)
296
highestPriorityNextObj = nextObj;
298
if (highestPriorityNextObj != CanvasObjectManager::Object::NoObject) {
299
initiateInTransition(highestPriorityNextObj,message);
301
// qDebug() << "ret";
304
// qDebug() << "ret";
308
void CanvasObjectManager::reset()
310
if (m_CurrentObject != CanvasObjectManager::Object::NoObject) {
313
if (m_pTimer && m_pTimer->isActive())
315
m_CurrentObject = CanvasObjectManager::Object::NoObject;
316
TipCollection::manager()->hideCurrentTip(true);
319
bool CanvasObjectManager::operator<<(CanvasEvent events)
321
return newEvent(events);
324
CanvasObjectManager::Object CanvasObjectManager::currentObject() const
326
return m_CurrentObject;
329
CanvasObjectManager::Object CanvasObjectManager::nextObject () const
336
void CanvasObjectManager::slotTimeout()
338
//Hide the canvas object, stopping the countdown should be handled elsewhere
339
initiateOutTransition();
342
void CanvasObjectManager::slotTransitionEvents(QAbstractAnimation::Direction direction, QAbstractAnimation::State state)
344
// qDebug() << "IN TRANSITION EVENT" << direction << state;
346
case QAbstractAnimation::Stopped:
347
if (m_CurrentObject == Object::NoObject) {
348
m_CurrentState = ObjectState::NO_OBJECT;
349
// qDebug() << "HERE4" << (int)m_NextObject;
350
if (m_NextObject != Object::NoObject) {
351
initiateInTransition(m_NextObject);
352
m_NextObject = Object::NoObject;
357
case QAbstractAnimation::Forward:
358
m_CurrentState = ObjectState::VISIBLE;
359
// qDebug() << "HERE2";
361
case QAbstractAnimation::Backward:
362
m_CurrentState = ObjectState::NO_OBJECT;
363
// qDebug() << "HERE3" << (int) m_NextObject;
364
initiateInTransition(m_NextObject);
365
m_NextObject = Object::NoObject;
369
case QAbstractAnimation::Paused:
372
case QAbstractAnimation::Running:
374
case QAbstractAnimation::Forward:
375
m_CurrentState = ObjectState::TRANSITION_IN;
377
case QAbstractAnimation::Backward:
378
m_CurrentState = ObjectState::TRANSITION_OUT;
385
void CanvasObjectManager::slotMinimized(bool isMinimized)
387
m_Minimized = isMinimized;
398
bool CanvasObjectManager::testEventToEvent() const
400
/* This test is to validate the the event table is correctly generated
401
* from the structured elements transition table ("elements"). Each
402
* event can be mapped to ONE AND ONLY ONE canvas object
404
* INPUT: element structure
412
for (int j=0;j<ELEMENT_COUNT;j++) {
413
CanvasObjectManager::Object obj = static_cast<CanvasObjectManager::Object>(j);
414
const CanvasElement& element = OBJ_DEF(j);
415
for (int power=0;power<EVENT_COUNT;power++) {
416
if (element.inEvent & (0x01<<power)) {
417
if (m_slEvents[power+1] != obj) {
418
qDebug() << "testEventToEvent failed, event:" << power << "should map to:"
419
<< j << "but map to" << (int)m_slEvents[power+1];
425
qDebug() << "\n\n\ntestEventToEvent" << success;
429
bool CanvasObjectManager::testEvenToObject() const
431
/* This test validate the translation between events and object.
441
//0 is none and 1 any, so those need to be tested separately
442
for (int index=2;index<EVENT_COUNT;index++) {
443
CanvasObjectManager::CanvasEvent ev = eventIndexToFlag(index);
444
if (eventToObject(ev) != m_slEvents[index]) {
445
qDebug() << "testEvenToObject failed, power map to " << (int) m_slEvents[index]
446
<< "but eventToObject return" << (int)eventToObject(ev);
451
qDebug() << "\n\n\ntestEvenToObject" << success;
457
bool CanvasObjectManager::testFirstInEvent() const
460
for (int j=0;j<ELEMENT_COUNT;j++) {
461
CanvasObjectManager::CanvasEvent ev = firstInEvent(static_cast<CanvasObjectManager::Object>(j));
462
if ((!(OBJ_DEF(j).inEvent & ev)) && OBJ_DEF(j).inEvent) {
463
qDebug() << "testFirstInEvent failed" << j << "event is " << ev << " valid events:" << OBJ_DEF(j).inEvent;
467
qDebug() << "\n\n\ntestFirstInEvent" << success;
471
bool CanvasObjectManager::testObjectPriority()
473
/* This test validate every possible transition to see if the event
474
* always end up changing the currentObject to something of higher priority
477
m_DisableTransition = true;
479
for (int j=0;j<ELEMENT_COUNT;j++) {
480
CanvasObjectManager::Object obj = static_cast<CanvasObjectManager::Object>(j);
481
for (int i=0;i<ELEMENT_COUNT;i++) {
482
CanvasObjectManager::Object next = static_cast<CanvasObjectManager::Object>(i);
484
initiateInTransition(obj);
485
if(obj!=m_CurrentObject) {
486
qDebug() << "Current object mismatch";
489
Q_ASSERT(obj == m_CurrentObject);
490
const bool overrideObj = newEvent(firstInEvent(next));
491
if ((OBJ_DEF(next).priority >= OBJ_DEF(obj).priority) != overrideObj && !(obj != CanvasObjectManager::Object::NoObject && OBJ_DEF(obj).outEvent & firstInEvent(next))) {
492
qDebug() << "PRE" << OBJ_DEF(obj).outEvent << firstInEvent(next) << (OBJ_DEF(obj).outEvent&&firstInEvent(next));
493
qDebug() << "testObjectPriority failed" << (int)next << ((overrideObj)?"override":"doesn't override")
494
<< (int)obj << "current priority:" << OBJ_DEF(obj).priority << "next priority" << OBJ_DEF(next).priority << "..." << (int)m_CurrentObject;
499
qDebug() << "\n\n\ntestObjectPriority" << success;
503
bool CanvasObjectManager::testObjectDiscarding()
505
/* This test validate if every objects is discarded by the right events
509
m_DisableTransition = true;
512
//First, lets test "ANY"
513
for (int j=0;j<ELEMENT_COUNT;j++) {
514
if (OBJ_DEF(j).outEvent == CanvasEvent::ANY) {
515
for (int index=1;index<EVENT_COUNT;index++) {
516
const CanvasEvent event = eventIndexToFlag(index);
517
CanvasObjectManager::Object next = static_cast<CanvasObjectManager::Object>(j);
519
initiateInTransition(next);
520
newEvent(static_cast<CanvasEvent>(event));
521
if ((next == currentObject() && m_slEvents[index] != next)
522
&& !(next == currentObject() && currentObject() == CanvasObjectManager::Object::NoObject )) {
523
qDebug() << "testObjectDiscarding failed with ANY, was" << j << "is" << (int)m_CurrentObject << event << (event == CanvasEvent::ANY);
530
for (int j=0;j<ELEMENT_COUNT;j++) {
531
if (OBJ_DEF(j).outEvent != CanvasEvent::ANY) {
532
for (int index=2;index<EVENT_COUNT;index++) {
533
CanvasObjectManager::Object next = static_cast<CanvasObjectManager::Object>(j);
535
initiateInTransition(next);
536
const CanvasEvent event = eventIndexToFlag(index);
537
newEvent(static_cast<CanvasEvent>(event));
538
if ( OBJ_DEF(next).outEvent & event && next == currentObject() ) {
539
qDebug() << "ntestObjectDiscarding failed, event #" << index << "should discard" << (int)next;
546
qDebug() << "\n\n\ntestObjectDiscarding"<<success;