1 | /* |
2 | * Copyright 2007 Matt Broadstone <mbroadst@gmail.com> |
3 | * Copyright 2007-2011 Aaron Seigo <aseigo@kde.org> |
4 | * Copyright 2007 Riccardo Iaconelli <riccardo@kde.org> |
5 | * Copyright (c) 2009 Chani Armitage <chani@kde.org> |
6 | * |
7 | * This program is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU Library General Public License as |
9 | * published by the Free Software Foundation; either version 2, or |
10 | * (at your option) any later version. |
11 | * |
12 | * This program is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details |
16 | * |
17 | * You should have received a copy of the GNU Library General Public |
18 | * License along with this program; if not, write to the |
19 | * Free Software Foundation, Inc., |
20 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
21 | */ |
22 | |
23 | #include "corona.h" |
24 | #include "private/corona_p.h" |
25 | |
26 | #include <QApplication> |
27 | #include <QDesktopWidget> |
28 | #include <QGraphicsView> |
29 | #include <QGraphicsSceneDragDropEvent> |
30 | #include <QGraphicsGridLayout> |
31 | #include <QMimeData> |
32 | #include <QPainter> |
33 | #include <QTimer> |
34 | |
35 | #include <cmath> |
36 | |
37 | #include <kaction.h> |
38 | #include <kauthorized.h> |
39 | #include <kdebug.h> |
40 | #include <kglobal.h> |
41 | #include <klocale.h> |
42 | #include <kmimetype.h> |
43 | #include <kshortcutsdialog.h> |
44 | #include <kwindowsystem.h> |
45 | |
46 | #include "animator.h" |
47 | #include "abstracttoolbox.h" |
48 | #include "containment.h" |
49 | #include "containmentactionspluginsconfig.h" |
50 | #include "view.h" |
51 | #include "private/animator_p.h" |
52 | #include "private/applet_p.h" |
53 | #include "private/containment_p.h" |
54 | #include "tooltipmanager.h" |
55 | #include "abstractdialogmanager.h" |
56 | |
57 | using namespace Plasma; |
58 | |
59 | namespace Plasma |
60 | { |
61 | |
62 | bool CoronaPrivate::s_positioningContainments = false; |
63 | |
64 | Corona::Corona(QObject *parent) |
65 | : QGraphicsScene(parent), |
66 | d(new CoronaPrivate(this)) |
67 | { |
68 | kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Corona ctor start" ; |
69 | d->init(); |
70 | ToolTipManager::self()->m_corona = this; |
71 | //setViewport(new QGLWidget(QGLFormat(QGL::StencilBuffer | QGL::AlphaChannel))); |
72 | } |
73 | |
74 | Corona::~Corona() |
75 | { |
76 | KConfigGroup trans(KGlobal::config(), "PlasmaTransientsConfig" ); |
77 | trans.deleteGroup(); |
78 | |
79 | // FIXME: Same fix as in Plasma::View - make sure that when the focused widget is |
80 | // destroyed we don't try to transfer it to something that's already been |
81 | // deleted. |
82 | clearFocus(); |
83 | delete d; |
84 | } |
85 | |
86 | void Corona::setAppletMimeType(const QString &type) |
87 | { |
88 | d->mimetype = type; |
89 | } |
90 | |
91 | QString Corona::appletMimeType() |
92 | { |
93 | return d->mimetype; |
94 | } |
95 | |
96 | void Corona::setDefaultContainmentPlugin(const QString &name) |
97 | { |
98 | // we could check if it is in: |
99 | // Containment::listContainments().contains(name) || |
100 | // Containment::listContainments(QString(), KGlobal::mainComponent().componentName()).contains(name) |
101 | // but that seems like overkill |
102 | d->defaultContainmentPlugin = name; |
103 | } |
104 | |
105 | QString Corona::defaultContainmentPlugin() const |
106 | { |
107 | return d->defaultContainmentPlugin; |
108 | } |
109 | |
110 | void Corona::saveLayout(const QString &configName) const |
111 | { |
112 | KSharedConfigPtr c; |
113 | |
114 | if (configName.isEmpty() || configName == d->configName) { |
115 | c = config(); |
116 | } else { |
117 | c = KSharedConfig::openConfig(configName, KConfig::SimpleConfig); |
118 | } |
119 | |
120 | d->saveLayout(c); |
121 | } |
122 | |
123 | void Corona::exportLayout(KConfigGroup &config, QList<Containment*> containments) |
124 | { |
125 | foreach (const QString &group, config.groupList()) { |
126 | KConfigGroup cg(&config, group); |
127 | cg.deleteGroup(); |
128 | } |
129 | |
130 | //temporarily unlock so that removal works |
131 | ImmutabilityType oldImm = immutability(); |
132 | d->immutability = Mutable; |
133 | |
134 | KConfigGroup dest(&config, "Containments" ); |
135 | KConfigGroup dummy; |
136 | foreach (Plasma::Containment *c, containments) { |
137 | c->save(dummy); |
138 | c->config().reparent(&dest); |
139 | |
140 | //ensure the containment is unlocked |
141 | //this is done directly because we have to bypass any SystemImmutable checks |
142 | c->Applet::d->immutability = Mutable; |
143 | foreach (Applet *a, c->applets()) { |
144 | a->d->immutability = Mutable; |
145 | } |
146 | |
147 | c->destroy(false); |
148 | } |
149 | |
150 | //restore immutability |
151 | d->immutability = oldImm; |
152 | |
153 | config.sync(); |
154 | } |
155 | |
156 | void Corona::requestConfigSync() |
157 | { |
158 | // constant controlling how long between requesting a configuration sync |
159 | // and one happening should occur. currently 10 seconds |
160 | static const int CONFIG_SYNC_TIMEOUT = 10000; |
161 | |
162 | // TODO: should we check into our immutability before doing this? |
163 | |
164 | //NOTE: this is a pretty simplistic model: we simply save no more than CONFIG_SYNC_TIMEOUT |
165 | // after the first time this is called. not much of a heuristic for save points, but |
166 | // it should at least compress these activities a bit and provide a way for applet |
167 | // authors to ween themselves from the sync() disease. A more interesting/dynamic |
168 | // algorithm for determining when to actually sync() to disk might be better, though. |
169 | if (!d->configSyncTimer.isActive()) { |
170 | d->configSyncTimer.start(CONFIG_SYNC_TIMEOUT); |
171 | } |
172 | } |
173 | |
174 | void Corona::requireConfigSync() |
175 | { |
176 | d->syncConfig(); |
177 | } |
178 | |
179 | void Corona::initializeLayout(const QString &configName) |
180 | { |
181 | clearContainments(); |
182 | loadLayout(configName); |
183 | |
184 | if (d->containments.isEmpty()) { |
185 | loadDefaultLayout(); |
186 | if (!d->containments.isEmpty()) { |
187 | requestConfigSync(); |
188 | } |
189 | } |
190 | |
191 | if (config()->isImmutable() || |
192 | !KAuthorized::authorize("plasma/" + KGlobal::mainComponent().aboutData()->appName() + |
193 | "/unlockedDesktop" )) { |
194 | setImmutability(SystemImmutable); |
195 | } else { |
196 | KConfigGroup coronaConfig(config(), "General" ); |
197 | setImmutability((ImmutabilityType)coronaConfig.readEntry("immutability" , (int)Mutable)); |
198 | } |
199 | } |
200 | |
201 | bool containmentSortByPosition(const Containment *c1, const Containment *c2) |
202 | { |
203 | return c1->id() < c2->id(); |
204 | } |
205 | |
206 | void Corona::layoutContainments() |
207 | { |
208 | if (CoronaPrivate::s_positioningContainments) { |
209 | return; |
210 | } |
211 | |
212 | CoronaPrivate::s_positioningContainments = true; |
213 | |
214 | //TODO: we should avoid running this too often; consider compressing requests |
215 | // with a timer. |
216 | QList<Containment*> c = containments(); |
217 | QMutableListIterator<Containment*> it(c); |
218 | |
219 | while (it.hasNext()) { |
220 | Containment *containment = it.next(); |
221 | if (containment->containmentType() == Containment::PanelContainment || |
222 | containment->containmentType() == Containment::CustomPanelContainment || |
223 | offscreenWidgets().contains(containment)) { |
224 | // weed out all containments we don't care about at all |
225 | // e.g. Panels and ourself |
226 | it.remove(); |
227 | continue; |
228 | } |
229 | } |
230 | |
231 | qSort(c.begin(), c.end(), containmentSortByPosition); |
232 | |
233 | if (c.isEmpty()) { |
234 | CoronaPrivate::s_positioningContainments = false; |
235 | return; |
236 | } |
237 | |
238 | int column = 0; |
239 | int x = 0; |
240 | int y = 0; |
241 | int rowHeight = 0; |
242 | |
243 | it.toFront(); |
244 | while (it.hasNext()) { |
245 | Containment *containment = it.next(); |
246 | containment->setPos(x, y); |
247 | //kDebug() << ++count << "setting to" << x << y; |
248 | |
249 | int height = containment->size().height(); |
250 | if (height > rowHeight) { |
251 | rowHeight = height; |
252 | } |
253 | |
254 | ++column; |
255 | |
256 | if (column == CONTAINMENT_COLUMNS) { |
257 | column = 0; |
258 | x = 0; |
259 | y += rowHeight + INTER_CONTAINMENT_MARGIN + TOOLBOX_MARGIN; |
260 | rowHeight = 0; |
261 | } else { |
262 | x += containment->size().width() + INTER_CONTAINMENT_MARGIN; |
263 | } |
264 | //kDebug() << "column: " << column << "; x " << x << "; y" << y << "; width was" |
265 | // << containment->size().width(); |
266 | } |
267 | |
268 | CoronaPrivate::s_positioningContainments = false; |
269 | } |
270 | |
271 | |
272 | void Corona::loadLayout(const QString &configName) |
273 | { |
274 | if (!configName.isEmpty() && configName != d->configName) { |
275 | // if we have a new config name passed in, then use that as the config file for this Corona |
276 | d->config = 0; |
277 | d->configName = configName; |
278 | } |
279 | |
280 | KSharedConfigPtr conf = config(); |
281 | d->importLayout(*conf, false); |
282 | } |
283 | |
284 | QList<Plasma::Containment *> Corona::importLayout(const KConfigGroup &conf) |
285 | { |
286 | return d->importLayout(conf, true); |
287 | } |
288 | |
289 | #ifndef KDE_NO_DEPRECATED |
290 | QList<Plasma::Containment *> Corona::importLayout(const KConfigBase &conf) |
291 | { |
292 | return d->importLayout(conf, true); |
293 | } |
294 | #endif |
295 | |
296 | Containment *Corona::containmentForScreen(int screen, int desktop) const |
297 | { |
298 | foreach (Containment *containment, d->containments) { |
299 | if (containment->screen() == screen && |
300 | (desktop < 0 || containment->desktop() == desktop) && |
301 | (containment->containmentType() == Containment::DesktopContainment || |
302 | containment->containmentType() == Containment::CustomContainment)) { |
303 | return containment; |
304 | } |
305 | } |
306 | |
307 | return 0; |
308 | } |
309 | |
310 | Containment *Corona::containmentForScreen(int screen, int desktop, |
311 | const QString &defaultPluginIfNonExistent, const QVariantList &defaultArgs) |
312 | { |
313 | Containment *containment = containmentForScreen(screen, desktop); |
314 | if (!containment && !defaultPluginIfNonExistent.isEmpty()) { |
315 | // screen requests are allowed to bypass immutability |
316 | if (screen >= 0 && screen < numScreens() && |
317 | desktop >= -1 && desktop < KWindowSystem::numberOfDesktops()) { |
318 | containment = d->addContainment(defaultPluginIfNonExistent, defaultArgs, 0, false); |
319 | if (containment) { |
320 | containment->setScreen(screen, desktop); |
321 | } |
322 | } |
323 | } |
324 | |
325 | return containment; |
326 | } |
327 | |
328 | QList<Containment*> Corona::containments() const |
329 | { |
330 | return d->containments; |
331 | } |
332 | |
333 | void Corona::clearContainments() |
334 | { |
335 | foreach (Containment *containment, d->containments) { |
336 | containment->clearApplets(); |
337 | } |
338 | } |
339 | |
340 | KSharedConfigPtr Corona::config() const |
341 | { |
342 | if (!d->config) { |
343 | d->config = KSharedConfig::openConfig(d->configName, KConfig::SimpleConfig); |
344 | } |
345 | |
346 | return d->config; |
347 | } |
348 | |
349 | Containment *Corona::addContainment(const QString &name, const QVariantList &args) |
350 | { |
351 | if (d->immutability == Mutable) { |
352 | return d->addContainment(name, args, 0, false); |
353 | } |
354 | |
355 | return 0; |
356 | } |
357 | |
358 | Containment *Corona::addContainmentDelayed(const QString &name, const QVariantList &args) |
359 | { |
360 | if (d->immutability == Mutable) { |
361 | return d->addContainment(name, args, 0, true); |
362 | } |
363 | |
364 | return 0; |
365 | } |
366 | |
367 | void Corona::mapAnimation(Animator::Animation from, Animator::Animation to) |
368 | { |
369 | AnimatorPrivate::mapAnimation(from, to); |
370 | } |
371 | |
372 | void Corona::mapAnimation(Animator::Animation from, const QString &to) |
373 | { |
374 | AnimatorPrivate::mapAnimation(from, to); |
375 | } |
376 | |
377 | void Corona::addOffscreenWidget(QGraphicsWidget *widget) |
378 | { |
379 | foreach (QGraphicsWidget *w, d->offscreenWidgets) { |
380 | if (w == widget) { |
381 | kDebug() << "widget is already an offscreen widget!" ; |
382 | return; |
383 | } |
384 | } |
385 | |
386 | //search for an empty spot in the topleft quadrant of the scene. each 'slot' is QWIDGETSIZE_MAX |
387 | //x QWIDGETSIZE_MAX, so we're guaranteed to never have to move widgets once they're placed here. |
388 | int i = 0; |
389 | while (d->offscreenWidgets.contains(i)) { |
390 | i++; |
391 | } |
392 | |
393 | d->offscreenWidgets[i] = widget; |
394 | widget->setPos((-i - 1) * QWIDGETSIZE_MAX, -QWIDGETSIZE_MAX); |
395 | |
396 | QGraphicsWidget *pw = widget->parentWidget(); |
397 | widget->setParentItem(0); |
398 | if (pw) { |
399 | widget->setParent(pw); |
400 | } |
401 | |
402 | //kDebug() << "adding offscreen widget at slot " << i; |
403 | if (!widget->scene()) { |
404 | addItem(widget); |
405 | } |
406 | |
407 | connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(offscreenWidgetDestroyed(QObject*))); |
408 | } |
409 | |
410 | void Corona::removeOffscreenWidget(QGraphicsWidget *widget) |
411 | { |
412 | QMutableHashIterator<uint, QGraphicsWidget *> it(d->offscreenWidgets); |
413 | |
414 | while (it.hasNext()) { |
415 | if (it.next().value() == widget) { |
416 | it.remove(); |
417 | return; |
418 | } |
419 | } |
420 | } |
421 | |
422 | QList <QGraphicsWidget *> Corona::offscreenWidgets() const |
423 | { |
424 | return d->offscreenWidgets.values(); |
425 | } |
426 | |
427 | void CoronaPrivate::offscreenWidgetDestroyed(QObject *o) |
428 | { |
429 | // at this point, it's just a QObject, not a QGraphicsWidget, but we still need |
430 | // a pointer of the appropriate type. |
431 | // WARNING: DO NOT USE THE WIDGET POINTER FOR ANYTHING OTHER THAN POINTER COMPARISONS |
432 | QGraphicsWidget *widget = static_cast<QGraphicsWidget *>(o); |
433 | q->removeOffscreenWidget(widget); |
434 | } |
435 | |
436 | int Corona::numScreens() const |
437 | { |
438 | return 1; |
439 | } |
440 | |
441 | QRect Corona::screenGeometry(int id) const |
442 | { |
443 | Q_UNUSED(id); |
444 | QGraphicsView *v = views().value(0); |
445 | if (v) { |
446 | QRect r = sceneRect().toRect(); |
447 | r.moveTo(v->mapToGlobal(QPoint(0, 0))); |
448 | return r; |
449 | } |
450 | |
451 | return sceneRect().toRect(); |
452 | } |
453 | |
454 | QRegion Corona::availableScreenRegion(int id) const |
455 | { |
456 | return QRegion(screenGeometry(id)); |
457 | } |
458 | |
459 | QPoint Corona::(const QGraphicsItem *item, const QSize &s) |
460 | { |
461 | return popupPosition(item, s, Qt::AlignLeft); |
462 | } |
463 | |
464 | QPoint Corona::(const QGraphicsItem *item, const QSize &s, Qt::AlignmentFlag alignment) |
465 | { |
466 | // TODO: merge both methods (also these in Applet) into one (with optional alignment) when we can break compatibility |
467 | // TODO: add support for more flags in the future? |
468 | |
469 | const QGraphicsItem *actualItem = item; |
470 | |
471 | const QGraphicsView *v = viewFor(item); |
472 | |
473 | if (!v) { |
474 | return QPoint(0, 0); |
475 | } |
476 | |
477 | //its own view could be hidden, for instance if item is in an hidden Dialog |
478 | //try to position it using the parent applet as the item |
479 | if (!v->isVisible()) { |
480 | actualItem = item->parentItem(); |
481 | if (!actualItem) { |
482 | const QGraphicsWidget *widget = qgraphicsitem_cast<const QGraphicsWidget*>(item); |
483 | if (widget) { |
484 | actualItem = qobject_cast<QGraphicsItem*>(widget->parent()); |
485 | } |
486 | } |
487 | |
488 | //kDebug() << actualItem; |
489 | |
490 | if (actualItem) { |
491 | v = viewFor(actualItem); |
492 | if (!v) { |
493 | return QPoint(0, 0); |
494 | } |
495 | } |
496 | } |
497 | |
498 | if (!actualItem) { |
499 | actualItem = item; |
500 | } |
501 | |
502 | QPoint pos; |
503 | QTransform sceneTransform = actualItem->sceneTransform(); |
504 | |
505 | //swap direction if necessary |
506 | if (QApplication::isRightToLeft() && alignment != Qt::AlignCenter) { |
507 | if (alignment == Qt::AlignRight) { |
508 | alignment = Qt::AlignLeft; |
509 | } else { |
510 | alignment = Qt::AlignRight; |
511 | } |
512 | } |
513 | |
514 | //if the applet is rotated the popup position has to be un-transformed |
515 | if (sceneTransform.isRotating()) { |
516 | qreal angle = acos(sceneTransform.m11()); |
517 | QTransform newTransform; |
518 | QPointF center = actualItem->sceneBoundingRect().center(); |
519 | |
520 | newTransform.translate(center.x(), center.y()); |
521 | newTransform.rotateRadians(-angle); |
522 | newTransform.translate(-center.x(), -center.y()); |
523 | pos = v->mapFromScene(newTransform.inverted().map(actualItem->scenePos())); |
524 | } else { |
525 | pos = v->mapFromScene(actualItem->scenePos()); |
526 | } |
527 | |
528 | pos = v->mapToGlobal(pos); |
529 | //kDebug() << "==> position is" << actualItem->scenePos() << v->mapFromScene(actualItem->scenePos()) << pos; |
530 | const Plasma::View *pv = qobject_cast<const Plasma::View *>(v); |
531 | |
532 | Plasma::Location loc = Floating; |
533 | if (pv && pv->containment()) { |
534 | loc = pv->containment()->location(); |
535 | } |
536 | |
537 | switch (loc) { |
538 | case BottomEdge: |
539 | case TopEdge: { |
540 | if (alignment == Qt::AlignCenter) { |
541 | pos.setX(pos.x() + actualItem->boundingRect().width()/2 - s.width()/2); |
542 | } else if (alignment == Qt::AlignRight) { |
543 | pos.setX(pos.x() + actualItem->boundingRect().width() - s.width()); |
544 | } |
545 | |
546 | if (pos.x() + s.width() > v->geometry().x() + v->geometry().width()) { |
547 | pos.setX((v->geometry().x() + v->geometry().width()) - s.width()); |
548 | } else { |
549 | pos.setX(qMax(pos.x(), v->geometry().left())); |
550 | } |
551 | break; |
552 | } |
553 | case LeftEdge: |
554 | case RightEdge: { |
555 | if (alignment == Qt::AlignCenter) { |
556 | pos.setY(pos.y() + actualItem->boundingRect().height()/2 - s.height()/2); |
557 | } else if (alignment == Qt::AlignRight) { |
558 | pos.setY(pos.y() + actualItem->boundingRect().height() - s.height()); |
559 | } |
560 | |
561 | if (pos.y() + s.height() > v->geometry().y() + v->geometry().height()) { |
562 | pos.setY((v->geometry().y() + v->geometry().height()) - s.height()); |
563 | } else { |
564 | pos.setY(qMax(pos.y(), v->geometry().top())); |
565 | } |
566 | break; |
567 | } |
568 | default: |
569 | if (alignment == Qt::AlignCenter) { |
570 | pos.setX(pos.x() + actualItem->boundingRect().width()/2 - s.width()/2); |
571 | } else if (alignment == Qt::AlignRight) { |
572 | pos.setX(pos.x() + actualItem->boundingRect().width() - s.width()); |
573 | } |
574 | break; |
575 | } |
576 | |
577 | |
578 | //are we out of screen? |
579 | int screen = ((pv && pv->containment()) ? pv->containment()->screen() : -1); |
580 | if (screen == -1) { |
581 | if (pv) { |
582 | screen = pv->screen(); |
583 | } else { |
584 | // fall back to asking the actual system what screen the view is on |
585 | // in the case we are dealing with a non-PlasmaView QGraphicsView |
586 | screen = QApplication::desktop()->screenNumber(v); |
587 | } |
588 | } |
589 | |
590 | QRect screenRect = screenGeometry(screen); |
591 | |
592 | switch (loc) { |
593 | case BottomEdge: |
594 | pos.setY(v->geometry().y() - s.height()); |
595 | break; |
596 | case TopEdge: |
597 | pos.setY(v->geometry().y() + v->geometry().height()); |
598 | break; |
599 | case LeftEdge: |
600 | pos.setX(v->geometry().x() + v->geometry().width()); |
601 | break; |
602 | case RightEdge: |
603 | pos.setX(v->geometry().x() - s.width()); |
604 | break; |
605 | default: |
606 | if (pos.y() - s.height() > screenRect.top()) { |
607 | pos.ry() = pos.y() - s.height(); |
608 | } else { |
609 | pos.ry() = pos.y() + (int)actualItem->boundingRect().size().height() + 1; |
610 | } |
611 | } |
612 | |
613 | //kDebug() << "==> rect for" << screen << "is" << screenRect; |
614 | |
615 | if (loc != LeftEdge && pos.x() + s.width() > screenRect.x() + screenRect.width()) { |
616 | pos.rx() -= ((pos.x() + s.width()) - (screenRect.x() + screenRect.width())); |
617 | } |
618 | |
619 | if (loc != TopEdge && pos.y() + s.height() > screenRect.y() + screenRect.height()) { |
620 | pos.ry() -= ((pos.y() + s.height()) - (screenRect.y() + screenRect.height())); |
621 | } |
622 | |
623 | pos.rx() = qMax(0, pos.x()); |
624 | pos.ry() = qMax(0, pos.y()); |
625 | return pos; |
626 | } |
627 | |
628 | void Corona::loadDefaultLayout() |
629 | { |
630 | } |
631 | |
632 | void Corona::setPreferredToolBoxPlugin(const Containment::Type type, const QString &plugin) |
633 | { |
634 | d->toolBoxPlugins[type] = plugin; |
635 | //TODO: react to plugin changes on the fly? still don't see the use case (maybe for laptops that become tablets?) |
636 | } |
637 | |
638 | QString Corona::preferredToolBoxPlugin(const Containment::Type type) const |
639 | { |
640 | return d->toolBoxPlugins.value(type); |
641 | } |
642 | |
643 | void Corona::dragEnterEvent(QGraphicsSceneDragDropEvent *event) |
644 | { |
645 | QGraphicsScene::dragEnterEvent(event); |
646 | } |
647 | |
648 | void Corona::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) |
649 | { |
650 | QGraphicsScene::dragLeaveEvent(event); |
651 | } |
652 | |
653 | void Corona::dragMoveEvent(QGraphicsSceneDragDropEvent *event) |
654 | { |
655 | QGraphicsScene::dragMoveEvent(event); |
656 | } |
657 | |
658 | ImmutabilityType Corona::immutability() const |
659 | { |
660 | return d->immutability; |
661 | } |
662 | |
663 | void Corona::setImmutability(const ImmutabilityType immutable) |
664 | { |
665 | if (d->immutability == immutable || d->immutability == SystemImmutable) { |
666 | return; |
667 | } |
668 | |
669 | kDebug() << "setting immutability to" << immutable; |
670 | d->immutability = immutable; |
671 | d->updateContainmentImmutability(); |
672 | //tell non-containments that might care (like plasmaapp or a custom corona) |
673 | emit immutabilityChanged(immutable); |
674 | |
675 | //update our actions |
676 | QAction *action = d->actions.action("lock widgets" ); |
677 | if (action) { |
678 | if (d->immutability == SystemImmutable) { |
679 | action->setEnabled(false); |
680 | action->setVisible(false); |
681 | } else { |
682 | bool unlocked = d->immutability == Mutable; |
683 | action->setText(unlocked ? i18n("Lock Widgets" ) : i18n("Unlock Widgets" )); |
684 | action->setIcon(KIcon(unlocked ? "object-locked" : "object-unlocked" )); |
685 | action->setEnabled(true); |
686 | action->setVisible(true); |
687 | } |
688 | } |
689 | |
690 | if (d->immutability != SystemImmutable) { |
691 | KConfigGroup cg(config(), "General" ); |
692 | |
693 | // we call the dptr member directly for locked since isImmutable() |
694 | // also checks kiosk and parent containers |
695 | cg.writeEntry("immutability" , (int)d->immutability); |
696 | requestConfigSync(); |
697 | } |
698 | } |
699 | |
700 | QList<Plasma::Location> Corona::freeEdges(int screen) const |
701 | { |
702 | QList<Plasma::Location> freeEdges; |
703 | freeEdges << Plasma::TopEdge << Plasma::BottomEdge |
704 | << Plasma::LeftEdge << Plasma::RightEdge; |
705 | |
706 | foreach (Containment *containment, containments()) { |
707 | if (containment->screen() == screen && |
708 | freeEdges.contains(containment->location())) { |
709 | freeEdges.removeAll(containment->location()); |
710 | } |
711 | } |
712 | |
713 | return freeEdges; |
714 | } |
715 | |
716 | QAction *Corona::action(QString name) const |
717 | { |
718 | return d->actions.action(name); |
719 | } |
720 | |
721 | void Corona::addAction(QString name, QAction *action) |
722 | { |
723 | d->actions.addAction(name, action); |
724 | } |
725 | |
726 | KAction* Corona::addAction(QString name) |
727 | { |
728 | return d->actions.addAction(name); |
729 | } |
730 | |
731 | QList<QAction*> Corona::actions() const |
732 | { |
733 | return d->actions.actions(); |
734 | } |
735 | |
736 | void Corona::enableAction(const QString &name, bool enable) |
737 | { |
738 | QAction *action = d->actions.action(name); |
739 | if (action) { |
740 | action->setEnabled(enable); |
741 | action->setVisible(enable); |
742 | } |
743 | } |
744 | |
745 | void Corona::updateShortcuts() |
746 | { |
747 | QMutableListIterator<QWeakPointer<KActionCollection> > it(d->actionCollections); |
748 | while (it.hasNext()) { |
749 | it.next(); |
750 | KActionCollection *collection = it.value().data(); |
751 | if (!collection) { |
752 | // get rid of KActionCollections that have been deleted behind our backs |
753 | it.remove(); |
754 | continue; |
755 | } |
756 | |
757 | collection->readSettings(); |
758 | if (d->shortcutsDlg) { |
759 | d->shortcutsDlg.data()->addCollection(collection); |
760 | } |
761 | } |
762 | } |
763 | |
764 | void Corona::addShortcuts(KActionCollection *newShortcuts) |
765 | { |
766 | d->actionCollections << newShortcuts; |
767 | if (d->shortcutsDlg) { |
768 | d->shortcutsDlg.data()->addCollection(newShortcuts); |
769 | } |
770 | } |
771 | |
772 | void Corona::setContainmentActionsDefaults(Containment::Type containmentType, const ContainmentActionsPluginsConfig &config) |
773 | { |
774 | d->containmentActionsDefaults.insert(containmentType, config); |
775 | } |
776 | |
777 | ContainmentActionsPluginsConfig Corona::containmentActionsDefaults(Containment::Type containmentType) |
778 | { |
779 | return d->containmentActionsDefaults.value(containmentType); |
780 | } |
781 | |
782 | void Corona::setDialogManager(AbstractDialogManager *dialogManager) |
783 | { |
784 | d->dialogManager = dialogManager; |
785 | } |
786 | |
787 | AbstractDialogManager *Corona::dialogManager() |
788 | { |
789 | return d->dialogManager.data(); |
790 | } |
791 | |
792 | CoronaPrivate::CoronaPrivate(Corona *corona) |
793 | : q(corona), |
794 | immutability(Mutable), |
795 | mimetype("text/x-plasmoidservicename" ), |
796 | defaultContainmentPlugin("desktop" ), |
797 | config(0), |
798 | actions(corona) |
799 | { |
800 | if (KGlobal::hasMainComponent()) { |
801 | configName = KGlobal::mainComponent().componentName() + "-appletsrc" ; |
802 | } else { |
803 | configName = "plasma-appletsrc" ; |
804 | } |
805 | } |
806 | |
807 | CoronaPrivate::~CoronaPrivate() |
808 | { |
809 | qDeleteAll(containments); |
810 | } |
811 | |
812 | void CoronaPrivate::init() |
813 | { |
814 | q->setStickyFocus(true); |
815 | configSyncTimer.setSingleShot(true); |
816 | QObject::connect(&configSyncTimer, SIGNAL(timeout()), q, SLOT(syncConfig())); |
817 | |
818 | //some common actions |
819 | actions.setConfigGroup("Shortcuts" ); |
820 | |
821 | KAction *lockAction = actions.addAction("lock widgets" ); |
822 | QObject::connect(lockAction, SIGNAL(triggered(bool)), q, SLOT(toggleImmutability())); |
823 | lockAction->setText(i18n("Lock Widgets" )); |
824 | lockAction->setAutoRepeat(true); |
825 | lockAction->setIcon(KIcon("object-locked" )); |
826 | lockAction->setData(AbstractToolBox::ControlTool); |
827 | lockAction->setShortcut(KShortcut("alt+d, l" )); |
828 | lockAction->setShortcutContext(Qt::ApplicationShortcut); |
829 | |
830 | //FIXME this doesn't really belong here. desktop KCM maybe? |
831 | //but should the shortcuts be per-app or really-global? |
832 | //I don't know how to make kactioncollections use plasmarc |
833 | KAction *action = actions.addAction("configure shortcuts" ); |
834 | QObject::connect(action, SIGNAL(triggered()), q, SLOT(showShortcutConfig())); |
835 | action->setText(i18n("Shortcut Settings" )); |
836 | action->setIcon(KIcon("configure-shortcuts" )); |
837 | action->setAutoRepeat(false); |
838 | action->setData(AbstractToolBox::ConfigureTool); |
839 | //action->setShortcut(KShortcut("ctrl+h")); |
840 | action->setShortcutContext(Qt::ApplicationShortcut); |
841 | |
842 | //fake containment/applet actions |
843 | KActionCollection *containmentActions = AppletPrivate::defaultActions(q); //containment has to start with applet stuff |
844 | ContainmentPrivate::addDefaultActions(containmentActions); //now it's really containment |
845 | actionCollections << &actions << AppletPrivate::defaultActions(q) << containmentActions; |
846 | q->updateShortcuts(); |
847 | } |
848 | |
849 | void CoronaPrivate::showShortcutConfig() |
850 | { |
851 | //show a kshortcutsdialog with the actions |
852 | KShortcutsDialog *dlg = shortcutsDlg.data(); |
853 | if (!dlg) { |
854 | dlg = new KShortcutsDialog(); |
855 | dlg->setModal(false); |
856 | dlg->setAttribute(Qt::WA_DeleteOnClose, true); |
857 | QObject::connect(dlg, SIGNAL(saved()), q, SIGNAL(shortcutsChanged())); |
858 | |
859 | dlg->addCollection(&actions); |
860 | QMutableListIterator<QWeakPointer<KActionCollection> > it(actionCollections); |
861 | while (it.hasNext()) { |
862 | it.next(); |
863 | KActionCollection *collection = it.value().data(); |
864 | if (!collection) { |
865 | // get rid of KActionCollections that have been deleted behind our backs |
866 | it.remove(); |
867 | continue; |
868 | } |
869 | |
870 | dlg->addCollection(collection); |
871 | } |
872 | } |
873 | |
874 | KWindowSystem::setOnDesktop(dlg->winId(), KWindowSystem::currentDesktop()); |
875 | dlg->configure(); |
876 | dlg->raise(); |
877 | } |
878 | |
879 | void CoronaPrivate::toggleImmutability() |
880 | { |
881 | if (immutability == Mutable) { |
882 | q->setImmutability(UserImmutable); |
883 | } else { |
884 | q->setImmutability(Mutable); |
885 | } |
886 | } |
887 | |
888 | void CoronaPrivate::saveLayout(KSharedConfigPtr cg) const |
889 | { |
890 | KConfigGroup containmentsGroup(cg, "Containments" ); |
891 | foreach (const Containment *containment, containments) { |
892 | QString cid = QString::number(containment->id()); |
893 | KConfigGroup containmentConfig(&containmentsGroup, cid); |
894 | containment->save(containmentConfig); |
895 | } |
896 | } |
897 | |
898 | void CoronaPrivate::updateContainmentImmutability() |
899 | { |
900 | foreach (Containment *c, containments) { |
901 | // we need to tell each containment that immutability has been altered |
902 | c->updateConstraints(ImmutableConstraint); |
903 | } |
904 | } |
905 | |
906 | void CoronaPrivate::containmentDestroyed(QObject *obj) |
907 | { |
908 | // we do a static_cast here since it really isn't an Containment by this |
909 | // point anymore since we are in the qobject dtor. we don't actually |
910 | // try and do anything with it, we just need the value of the pointer |
911 | // so this unsafe looking code is actually just fine. |
912 | Containment* containment = static_cast<Plasma::Containment*>(obj); |
913 | int index = containments.indexOf(containment); |
914 | |
915 | if (index > -1) { |
916 | containments.removeAt(index); |
917 | q->requestConfigSync(); |
918 | } |
919 | } |
920 | |
921 | void CoronaPrivate::syncConfig() |
922 | { |
923 | q->config()->sync(); |
924 | emit q->configSynced(); |
925 | } |
926 | |
927 | Containment *CoronaPrivate::addContainment(const QString &name, const QVariantList &args, uint id, bool delayedInit) |
928 | { |
929 | QString pluginName = name; |
930 | Containment *containment = 0; |
931 | Applet *applet = 0; |
932 | |
933 | //kDebug() << "Loading" << name << args << id; |
934 | |
935 | if (pluginName.isEmpty() || pluginName == "default" ) { |
936 | // default to the desktop containment |
937 | pluginName = defaultContainmentPlugin; |
938 | } |
939 | |
940 | bool loadingNull = pluginName == "null" ; |
941 | if (!loadingNull) { |
942 | applet = Applet::load(pluginName, id, args); |
943 | containment = dynamic_cast<Containment*>(applet); |
944 | } |
945 | |
946 | if (!containment) { |
947 | if (!loadingNull) { |
948 | kDebug() << "loading of containment" << name << "failed." ; |
949 | } |
950 | |
951 | // in case we got a non-Containment from Applet::loadApplet or |
952 | // a null containment was requested |
953 | if (applet) { |
954 | // the applet probably doesn't know what's hit it, so let's pretend it can be |
955 | // initialized to make assumptions in the applet's dtor safer |
956 | q->addItem(applet); |
957 | applet->init(); |
958 | q->removeItem(applet); |
959 | delete applet; |
960 | } |
961 | applet = containment = new Containment(0, 0, id); |
962 | |
963 | if (loadingNull) { |
964 | containment->setDrawWallpaper(false); |
965 | } else { |
966 | containment->setFailedToLaunch(false); |
967 | } |
968 | |
969 | // we want to provide something and don't care about the failure to launch |
970 | containment->setFormFactor(Plasma::Planar); |
971 | } |
972 | |
973 | // if this is a new containment, we need to ensure that there are no stale |
974 | // configuration data around |
975 | if (id == 0) { |
976 | KConfigGroup conf(q->config(), "Containments" ); |
977 | conf = KConfigGroup(&conf, QString::number(containment->id())); |
978 | conf.deleteGroup(); |
979 | } |
980 | |
981 | applet->d->isContainment = true; |
982 | containment->setPos(containment->d->preferredPos(q)); |
983 | q->addItem(containment); |
984 | applet->d->setIsContainment(true, true); |
985 | containments.append(containment); |
986 | |
987 | if (!delayedInit) { |
988 | containment->init(); |
989 | KConfigGroup cg = containment->config(); |
990 | containment->restore(cg); |
991 | containment->updateConstraints(Plasma::StartupCompletedConstraint); |
992 | containment->save(cg); |
993 | q->requestConfigSync(); |
994 | containment->flushPendingConstraintsEvents(); |
995 | } |
996 | |
997 | QObject::connect(containment, SIGNAL(destroyed(QObject*)), |
998 | q, SLOT(containmentDestroyed(QObject*))); |
999 | QObject::connect(containment, SIGNAL(configNeedsSaving()), |
1000 | q, SLOT(requestConfigSync())); |
1001 | QObject::connect(containment, SIGNAL(releaseVisualFocus()), |
1002 | q, SIGNAL(releaseVisualFocus())); |
1003 | QObject::connect(containment, SIGNAL(screenChanged(int,int,Plasma::Containment*)), |
1004 | q, SIGNAL(screenOwnerChanged(int,int,Plasma::Containment*))); |
1005 | |
1006 | if (!delayedInit) { |
1007 | emit q->containmentAdded(containment); |
1008 | } |
1009 | |
1010 | return containment; |
1011 | } |
1012 | |
1013 | QList<Plasma::Containment *> CoronaPrivate::importLayout(const KConfigBase &conf, bool mergeConfig) |
1014 | { |
1015 | if (const KConfigGroup *group = dynamic_cast<const KConfigGroup *>(&conf)) { |
1016 | if (!group->isValid()) { |
1017 | return QList<Containment *>(); |
1018 | } |
1019 | } |
1020 | |
1021 | QList<Plasma::Containment *> newContainments; |
1022 | QSet<uint> containmentsIds; |
1023 | |
1024 | foreach (Containment *containment, containments) { |
1025 | containmentsIds.insert(containment->id()); |
1026 | } |
1027 | |
1028 | KConfigGroup containmentsGroup(&conf, "Containments" ); |
1029 | |
1030 | foreach (const QString &group, containmentsGroup.groupList()) { |
1031 | KConfigGroup containmentConfig(&containmentsGroup, group); |
1032 | |
1033 | if (containmentConfig.entryMap().isEmpty()) { |
1034 | continue; |
1035 | } |
1036 | |
1037 | uint cid = group.toUInt(); |
1038 | if (containmentsIds.contains(cid)) { |
1039 | cid = ++AppletPrivate::s_maxAppletId; |
1040 | } else if (cid > AppletPrivate::s_maxAppletId) { |
1041 | AppletPrivate::s_maxAppletId = cid; |
1042 | } |
1043 | |
1044 | if (mergeConfig) { |
1045 | KConfigGroup realConf(q->config(), "Containments" ); |
1046 | realConf = KConfigGroup(&realConf, QString::number(cid)); |
1047 | // in case something was there before us |
1048 | realConf.deleteGroup(); |
1049 | containmentConfig.copyTo(&realConf); |
1050 | } |
1051 | |
1052 | //kDebug() << "got a containment in the config, trying to make a" << containmentConfig.readEntry("plugin", QString()) << "from" << group; |
1053 | kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Adding Containment" << containmentConfig.readEntry("plugin" , QString()); |
1054 | Containment *c = addContainment(containmentConfig.readEntry("plugin" , QString()), QVariantList(), cid, true); |
1055 | if (!c) { |
1056 | continue; |
1057 | } |
1058 | |
1059 | newContainments.append(c); |
1060 | containmentsIds.insert(c->id()); |
1061 | |
1062 | c->init(); |
1063 | kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Init Containment" << c->pluginName(); |
1064 | c->restore(containmentConfig); |
1065 | kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Restored Containment" << c->pluginName(); |
1066 | } |
1067 | |
1068 | foreach (Containment *containment, newContainments) { |
1069 | containment->updateConstraints(Plasma::StartupCompletedConstraint); |
1070 | containment->d->initApplets(); |
1071 | emit q->containmentAdded(containment); |
1072 | kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment" << containment->name(); |
1073 | } |
1074 | |
1075 | return newContainments; |
1076 | } |
1077 | |
1078 | } // namespace Plasma |
1079 | |
1080 | #include "corona.moc" |
1081 | |
1082 | |