diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f593f631..ad63c99d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ set(plasma_LIB_SRCS private/windowpreview.cpp private/windowshadows.cpp private/applet_p.cpp + private/containment_p.cpp querymatch.cpp remote/accessmanager.cpp diff --git a/containment.cpp b/containment.cpp index 82cd48ab0..26a9ef749 100644 --- a/containment.cpp +++ b/containment.cpp @@ -2,6 +2,7 @@ * Copyright 2007 by Aaron Seigo * Copyright 2008 by Ménard Alexis * Copyright 2009 Chani Armitage + * Copyright 2012 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as @@ -73,11 +74,6 @@ namespace Plasma { -bool ContainmentPrivate::s_positioningPanels = false; -QHash ContainmentPrivate::globalActionPlugins; -static const char defaultWallpaper[] = "image"; -static const char defaultWallpaperMode[] = "SingleImage"; - Containment::StyleOption::StyleOption() : QStyleOptionGraphicsItem(), view(0) @@ -231,46 +227,6 @@ void Containment::init() } } -void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c) -{ - actions->setConfigGroup("Shortcuts-Containment"); - - //adjust applet actions - KAction *appAction = qobject_cast(actions->action("remove")); - appAction->setShortcut(KShortcut("alt+d, alt+r")); - if (c && c->d->isPanelContainment()) { - appAction->setText(i18n("Remove this Panel")); - } else { - appAction->setText(i18n("Remove this Activity")); - } - - appAction = qobject_cast(actions->action("configure")); - if (appAction) { - appAction->setShortcut(KShortcut("alt+d, alt+s")); - appAction->setText(i18n("Activity Settings")); - } - - //add our own actions - KAction *appletBrowserAction = actions->addAction("add widgets"); - appletBrowserAction->setAutoRepeat(false); - appletBrowserAction->setText(i18n("Add Widgets...")); - appletBrowserAction->setIcon(KDE::icon("list-add")); - appletBrowserAction->setShortcut(KShortcut("alt+d, a")); - appletBrowserAction->setData(AbstractToolBox::AddTool); - - KAction *action = actions->addAction("next applet"); - action->setText(i18n("Next Widget")); - //no icon - action->setShortcut(KShortcut("alt+d, n")); - action->setData(AbstractToolBox::ControlTool); - - action = actions->addAction("previous applet"); - action->setText(i18n("Previous Widget")); - //no icon - action->setShortcut(KShortcut("alt+d, p")); - action->setData(AbstractToolBox::ControlTool); -} - // helper function for sorting the list of applets bool appletConfigLessThan(const KConfigGroup &c1, const KConfigGroup &c2) { @@ -335,8 +291,8 @@ void Containment::restore(KConfigGroup &group) restoreContents(group); setImmutability((ImmutabilityType)group.readEntry("immutability", (int)Mutable)); - setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), - group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); + setWallpaper(group.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper), + group.readEntry("wallpaperpluginmode", ContainmentPrivate::defaultWallpaperMode)); if (d->toolBox) { d->toolBox.data()->restore(group); @@ -456,27 +412,6 @@ void Containment::saveContents(KConfigGroup &group) const } } -void ContainmentPrivate::initApplets() -{ - foreach (Applet *applet, applets) { - applet->restore(*applet->d->mainConfigGroup()); - applet->init(); -#ifndef NDEBUG - kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name(); -#endif - } - - q->flushPendingConstraintsEvents(); - - foreach (Applet *applet, applets) { - applet->flushPendingConstraintsEvents(); - } - -#ifndef NDEBUG - kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name(); -#endif -} - void Containment::restoreContents(KConfigGroup &group) { KConfigGroup applets(&group, "Applets"); @@ -521,14 +456,6 @@ void Containment::setContainmentType(Containment::Type type) d->checkContainmentFurniture(); } -void ContainmentPrivate::checkContainmentFurniture() -{ - if (q->isContainment() && - (type == Containment::DesktopContainment || type == Containment::PanelContainment)) { - createToolBox(); - } -} - Corona *Containment::corona() const { return qobject_cast(scene()); @@ -676,109 +603,6 @@ void Containment::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) } } -void ContainmentPrivate::addContainmentActions(KMenu &desktopMenu, QEvent *event) -{ - if (static_cast(q->scene())->immutability() != Mutable && - !KAuthorized::authorizeKAction("plasma/containment_actions")) { - //kDebug() << "immutability"; - return; - } - - const QString trigger = ContainmentActions::eventToString(event); - prepareContainmentActions(trigger, QPoint(), &desktopMenu); -} - -void ContainmentPrivate::addAppletActions(KMenu &desktopMenu, Applet *applet, QEvent *event) -{ - foreach (QAction *action, applet->contextualActions()) { - if (action) { - desktopMenu.addAction(action); - } - } - - if (!applet->d->failed) { - QAction *configureApplet = applet->d->actions->action("configure"); - if (configureApplet && configureApplet->isEnabled()) { - desktopMenu.addAction(configureApplet); - } - - QAction *runAssociatedApplication = applet->d->actions->action("run associated application"); - if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { - desktopMenu.addAction(runAssociatedApplication); - } - } - - KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); - addContainmentActions(*containmentMenu, event); - if (!containmentMenu->isEmpty()) { - int enabled = 0; - //count number of real actions - QListIterator actionsIt(containmentMenu->actions()); - while (enabled < 3 && actionsIt.hasNext()) { - QAction *action = actionsIt.next(); - if (action->isVisible() && !action->isSeparator()) { - ++enabled; - } - } - - if (enabled) { - //if there is only one, don't create a submenu - if (enabled < 2) { - foreach (QAction *action, containmentMenu->actions()) { - if (action->isVisible() && !action->isSeparator()) { - desktopMenu.addAction(action); - } - } - } else { - desktopMenu.addMenu(containmentMenu); - } - } - } - - if (q->immutability() == Mutable) { - QAction *closeApplet = applet->d->actions->action("remove"); - //kDebug() << "checking for removal" << closeApplet; - if (closeApplet) { - if (!desktopMenu.isEmpty()) { - desktopMenu.addSeparator(); - } - - //kDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); - desktopMenu.addAction(closeApplet); - } - } -} - -Applet* ContainmentPrivate::appletAt(const QPointF &point) -{ - Applet *applet = 0; - - QGraphicsItem *item = q->scene()->itemAt(point); - if (item == q) { - item = 0; - } - - while (item) { - if (item->isWidget()) { - applet = qobject_cast(static_cast(item)); - if (applet) { - if (applet->isContainment()) { - applet = 0; - } - break; - } - } - AppletHandle *handle = dynamic_cast(item); - if (handle) { - //pretend it was on the applet - applet = handle->applet(); - break; - } - item = item->parentItem(); - } - return applet; -} - void Containment::setFormFactor(FormFactor formFactor) { if (d->formFactor == formFactor) { @@ -960,112 +784,6 @@ void Containment::setScreen(int newScreen, int newDesktop) d->setScreen(newScreen, newDesktop); } -void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops) -{ - // What we want to do in here is: - // * claim the screen as our own - // * signal whatever may be watching this containment about the switch - // * if we are a full screen containment, then: - // * resize to match the screen if we're that kind of containment - // * kick other full-screen containments off this screen - // * if we had a screen, then give our screen to the containment - // we kick out - // - // a screen of -1 means no associated screen. - Corona *corona = q->corona(); - Q_ASSERT(corona); - - //if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen - if (corona->offscreenWidgets().contains(q)) { - return; - } - - int numScreens = corona->numScreens(); - if (newScreen < -1) { - newScreen = -1; - } - - // -1 == All desktops - if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) { - newDesktop = -1; - } - - //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type; - - Containment *swapScreensWith(0); - const bool isDesktopContainment = type == Containment::DesktopContainment || - type == Containment::CustomContainment; - if (isDesktopContainment) { - // we want to listen to changes in work area if our screen changes - if (toolBox) { - if (screen < 0 && newScreen > -1) { - QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection); - } else if (newScreen < 0) { - QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition())); - } - } - - if (newScreen > -1) { - // sanity check to make sure someone else doesn't have this screen already! - Containment *currently = corona->containmentForScreen(newScreen, newDesktop); - if (currently && currently != q) { -#ifndef NDEBUG - kDebug() << "currently is on screen" << currently->screen() - << "desktop" << currently->desktop() - << "and is" << currently->activity() - << (QObject*)currently << "i'm" << (QObject*)q; -#endif - currently->setScreen(-1, currently->desktop()); - swapScreensWith = currently; - } - } - } - - if (newScreen < numScreens && newScreen > -1 && isDesktopContainment) { - q->resize(corona->screenGeometry(newScreen).size()); - } - - int oldDesktop = desktop; - desktop = newDesktop; - - int oldScreen = screen; - screen = newScreen; - - q->updateConstraints(Plasma::ScreenConstraint); - - if (oldScreen != newScreen || oldDesktop != newDesktop) { - /* -#ifndef NDEBUG - kDebug() << "going to signal change for" << q -#endif - << ", old screen & desktop:" << oldScreen << oldDesktop - << ", new:" << screen << desktop; - */ - KConfigGroup c = q->config(); - c.writeEntry("screen", screen); - c.writeEntry("desktop", desktop); - if (newScreen != -1) { - lastScreen = newScreen; - lastDesktop = newDesktop; - c.writeEntry("lastScreen", lastScreen); - c.writeEntry("lastDesktop", lastDesktop); - } - emit q->configNeedsSaving(); - emit q->screenChanged(oldScreen, newScreen, q); - } - - if (swapScreensWith) { - //kDebug() << "setScreen due to swap, part 2"; - swapScreensWith->setScreen(oldScreen, oldDesktop); - } - - checkRemoveAction(); - - if (newScreen >= 0) { - emit q->activate(); - } -} - int Containment::screen() const { return d->screen; @@ -1213,13 +931,6 @@ void Containment::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) } } -void ContainmentPrivate::showDropZoneDelayed() -{ - dropZoneStarted = true; - q->showDropZone(dropPoints.value(0).toPoint()); - dropPoints.remove(0); -} - void Containment::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { QGraphicsItem *item = scene()->itemAt(event->scenePos()); @@ -1243,381 +954,6 @@ void Containment::dropEvent(QGraphicsSceneDragDropEvent *event) } } -void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent) -{ - if (q->immutability() != Mutable) { - return; - } - - QPointF pos = q->mapFromScene(scenePos); - const QMimeData *mimeData = 0; - - if (dropEvent) { - mimeData = dropEvent->mimeData(); - } else { - QClipboard *clipboard = QApplication::clipboard(); - mimeData = clipboard->mimeData(QClipboard::Selection); - //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection? - } - - if (!mimeData) { - //Selection is either empty or not supported on this OS -#ifndef NDEBUG - kDebug() << "no mime data"; -#endif - return; - } - - //kDebug() << event->mimeData()->text(); - - QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString()); - - if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) { - QString data = mimeData->data(appletMimetype); - const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); - foreach (const QString &appletName, appletNames) { - //kDebug() << "doing" << appletName; - QRectF geom(pos, QSize(0, 0)); - q->addApplet(appletName, QVariantList(), geom); - } - if (dropEvent) { - dropEvent->acceptProposedAction(); - } - } else if (mimeData->hasUrls()) { - //TODO: collect the mimeTypes of available script engines and offer - // to create widgets out of the matching URLs, if any - const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); - foreach (const QUrl &url, urls) { - if (AccessManager::supportedProtocols().contains(url.scheme())) { - AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url); - if (dropEvent) { - dropPoints[job] = dropEvent->pos(); - } else { - dropPoints[job] = scenePos; - } - QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)), - q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*))); - } -#ifndef PLASMA_NO_KIO - else { - QMimeDatabase db; - QMimeType mime = db.mimeTypeForUrl(url); - QString mimeName = mime.name(); - QRectF geom(pos, QSize()); - QVariantList args; - args << url.toString(); -#ifndef NDEBUG - kDebug() << "can decode" << mimeName << args; -#endif - - // It may be a directory or a file, let's stat - KIO::JobFlags flags = KIO::HideProgressInfo; - KIO::MimetypeJob *job = KIO::mimetype(url, flags); - if (dropEvent) { - dropPoints[job] = dropEvent->pos(); - } else { - dropPoints[job] = scenePos; - } - - QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*))); - QObject::connect(job, SIGNAL(mimetype(KIO::Job*,QString)), - q, SLOT(mimeTypeRetrieved(KIO::Job*,QString))); - - KMenu *choices = new KMenu("Content dropped"); - choices->addAction(KDE::icon("process-working"), i18n("Fetching file type...")); - if (dropEvent) { - choices->popup(dropEvent->screenPos()); - } else { - choices->popup(screenPos); - } - - dropMenus[job] = choices; - } -#endif - } - - if (dropEvent) { - dropEvent->acceptProposedAction(); - } - } else { - QStringList formats = mimeData->formats(); - QHash seenPlugins; - QHash pluginFormats; - - foreach (const QString &format, formats) { - KPluginInfo::List plugins = Applet::listAppletInfoForMimeType(format); - - foreach (const KPluginInfo &plugin, plugins) { - if (seenPlugins.contains(plugin.pluginName())) { - continue; - } - - seenPlugins.insert(plugin.pluginName(), plugin); - pluginFormats.insert(plugin.pluginName(), format); - } - } - //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); - - QString selectedPlugin; - - if (seenPlugins.isEmpty()) { - // do nothing - } else if (seenPlugins.count() == 1) { - selectedPlugin = seenPlugins.constBegin().key(); - } else { - KMenu choices; - QHash actionsToPlugins; - foreach (const KPluginInfo &info, seenPlugins) { - QAction *action; - if (!info.icon().isEmpty()) { - action = choices.addAction(KDE::icon(info.icon()), info.name()); - } else { - action = choices.addAction(info.name()); - } - - actionsToPlugins.insert(action, info.pluginName()); - } - - QAction *choice = choices.exec(screenPos); - if (choice) { - selectedPlugin = actionsToPlugins[choice]; - } - } - - if (!selectedPlugin.isEmpty()) { - if (!dropEvent) { - // since we may have entered an event loop up above with the menu, - // the clipboard item may no longer be valid, as QClipboard resets - // the object behind the back of the application with a zero timer - // so we fetch it again here - QClipboard *clipboard = QApplication::clipboard(); - mimeData = clipboard->mimeData(QClipboard::Selection); - } - - QTemporaryFile tempFile; - if (mimeData && tempFile.open()) { - //TODO: what should we do with files after the applet is done with them?? - tempFile.setAutoRemove(false); - - { - QDataStream stream(&tempFile); - QByteArray data = mimeData->data(pluginFormats[selectedPlugin]); - stream.writeRawData(data, data.size()); - } - - QRectF geom(pos, QSize()); - QVariantList args; - args << tempFile.fileName(); -#ifndef NDEBUG - kDebug() << args; -#endif - tempFile.close(); - - q->addApplet(selectedPlugin, args, geom); - } - } - } -} - -void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job) -{ -#ifndef PLASMA_NO_KIO - QObject::disconnect(job, 0, q, 0); - dropPoints.remove(job); - KMenu *choices = dropMenus.take(job); - delete choices; - job->kill(); -#endif // PLASMA_NO_KIO -} - -void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job) -{ - QPointF pos = dropPoints.take(job); - if (job->error()) { - //TODO: nice user visible error handling (knotification probably?) -#ifndef NDEBUG - kDebug() << "remote applet access failed: " << job->errorText(); -#endif - return; - } - - if (!job->applet()) { -#ifndef NDEBUG - kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero"; -#endif - return; - } - - q->addApplet(job->applet(), pos); -} - -void ContainmentPrivate::dropJobResult(KJob *job) -{ -#ifndef PLASMA_NO_KIO - KIO::TransferJob* tjob = dynamic_cast(job); - if (!tjob) { -#ifndef NDEBUG - kDebug() << "job is not a KIO::TransferJob, won't handle the drop..."; -#endif - clearDataForMimeJob(tjob); - return; - } - if (job->error()) { -#ifndef NDEBUG - kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString(); -#endif - } - // We call mimeTypeRetrieved since there might be other mechanisms - // for finding suitable applets. Cleanup happens there as well. - mimeTypeRetrieved(qobject_cast(job), QString()); -#endif // PLASMA_NO_KIO -} - -void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimeType) -{ -#ifndef PLASMA_NO_KIO -#ifndef NDEBUG - kDebug() << "Mimetype Job returns." << mimeType; -#endif - KIO::TransferJob* tjob = dynamic_cast(job); - if (!tjob) { -#ifndef NDEBUG - kDebug() << "job should be a TransferJob, but isn't"; -#endif - clearDataForMimeJob(job); - return; - } - KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url()); - if (mimeType.isEmpty() && !appletList.count()) { - clearDataForMimeJob(job); -#ifndef NDEBUG - kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimeType (" << mimeType << ")"; -#endif - return; - } else { - - QPointF posi; // will be overwritten with the event's position - if (dropPoints.keys().contains(tjob)) { - posi = dropPoints[tjob]; -#ifndef NDEBUG - kDebug() << "Received a suitable dropEvent at" << posi; -#endif - } else { -#ifndef NDEBUG - kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; -#endif - clearDataForMimeJob(job); - return; - } - - KMenu *choices = dropMenus.value(tjob); - if (!choices) { -#ifndef NDEBUG - kDebug() << "Bailing out. No QMenu found for this job."; -#endif - clearDataForMimeJob(job); - return; - } - - QVariantList args; - args << tjob->url().toString() << mimeType; - -#ifndef NDEBUG - kDebug() << "Creating menu for:" << mimeType << posi << args; -#endif - - appletList << Applet::listAppletInfoForMimeType(mimeType); - KPluginInfo::List wallpaperList; - if (drawWallpaper) { - if (wallpaper && wallpaper->supportsMimetype(mimeType)) { - wallpaperList << wallpaper->d->wallpaperDescription; - } else { - wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimeType); - } - } - - if (!appletList.isEmpty() || !wallpaperList.isEmpty()) { - choices->clear(); - QHash actionsToApplets; - choices->addTitle(i18n("Widgets")); - foreach (const KPluginInfo &info, appletList) { -#ifndef NDEBUG - kDebug() << info.name(); -#endif - QAction *action; - if (!info.icon().isEmpty()) { - action = choices->addAction(KDE::icon(info.icon()), info.name()); - } else { - action = choices->addAction(info.name()); - } - - actionsToApplets.insert(action, info.pluginName()); -#ifndef NDEBUG - kDebug() << info.pluginName(); -#endif - } - actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon"); - - QHash actionsToWallpapers; - if (!wallpaperList.isEmpty()) { - choices->addTitle(i18n("Wallpaper")); - - QMap sorted; - foreach (const KPluginInfo &info, appletList) { - sorted.insert(info.name(), info); - } - - foreach (const KPluginInfo &info, wallpaperList) { - QAction *action; - if (!info.icon().isEmpty()) { - action = choices->addAction(KDE::icon(info.icon()), info.name()); - } else { - action = choices->addAction(info.name()); - } - - actionsToWallpapers.insert(action, info.pluginName()); - } - } - - QAction *choice = choices->exec(); - if (choice) { - // Put the job on hold so it can be recycled to fetch the actual content, - // which is to be expected when something's dropped onto the desktop and - // an applet is to be created with this URL - if (!mimeType.isEmpty() && !tjob->error()) { - tjob->putOnHold(); - KIO::Scheduler::publishSlaveOnHold(); - } - QString plugin = actionsToApplets.value(choice); - if (plugin.isEmpty()) { - //set wallpapery stuff - plugin = actionsToWallpapers.value(choice); - if (!wallpaper || plugin != wallpaper->pluginName()) { - //kDebug() << "Wallpaper dropped:" << tjob->url(); - q->setWallpaper(plugin); - } - - if (wallpaper) { - //kDebug() << "Wallpaper dropped:" << tjob->url(); - wallpaper->addUrls(QList() << tjob->url()); - } - } else { - addApplet(actionsToApplets[choice], args, QRectF(posi, QSize())); - } - - clearDataForMimeJob(job); - return; - } - } else { - // we can at least create an icon as a link to the URL - addApplet("icon", args, QRectF(posi, QSize())); - } - } - - clearDataForMimeJob(job); -#endif // PLASMA_NO_KIO -} - void Containment::setToolBox(AbstractToolBox *toolBox) { if (d->toolBox.data()) { @@ -1802,8 +1138,8 @@ void Containment::setDrawWallpaper(bool drawWallpaper) d->drawWallpaper = drawWallpaper; if (drawWallpaper) { KConfigGroup cfg = config(); - const QString wallpaper = cfg.readEntry("wallpaperplugin", defaultWallpaper); - const QString mode = cfg.readEntry("wallpaperpluginmode", defaultWallpaperMode); + const QString wallpaper = cfg.readEntry("wallpaperplugin", ContainmentPrivate::defaultWallpaper); + const QString mode = cfg.readEntry("wallpaperpluginmode", ContainmentPrivate::defaultWallpaperMode); setWallpaper(wallpaper, mode); } else { delete d->wallpaper; @@ -1961,39 +1297,6 @@ QString Containment::activity() const return d->activityId; } -KActionCollection* ContainmentPrivate::actions() -{ - return static_cast(q)->d->actions; -} - -void ContainmentPrivate::focusApplet(Plasma::Applet *applet) -{ - if (focusedApplet == applet) { - return; - } - - QList widgets = actions()->associatedWidgets(); - if (focusedApplet) { - foreach (QWidget *w, widgets) { - focusedApplet->removeAssociatedWidget(w); - } - } - - if (applet && applets.contains(applet)) { - //kDebug() << "switching to" << applet->name(); - focusedApplet = applet; - foreach (QWidget *w, widgets) { - focusedApplet->addAssociatedWidget(w); - } - - if (!focusedApplet->hasFocus()) { - focusedApplet->setFocus(Qt::ShortcutFocusReason); - } - } else { - focusedApplet = 0; - } -} - void Containment::focusNextApplet() { if (d->applets.isEmpty()) { @@ -2034,41 +1337,6 @@ void Containment::showConfigurationInterface() Applet::showConfigurationInterface(); } -void ContainmentPrivate::configChanged() -{ - if (drawWallpaper) { - KConfigGroup group = q->config(); - q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), - group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); - } -} - -void ContainmentPrivate::requestConfiguration() -{ - emit q->configureRequested(q); -} - -void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus) -{ - //kDebug() << "================== "<< appletStatus << q->status(); - if (appletStatus == q->status()) { - emit q->newStatus(appletStatus); - return; - } - - if (appletStatus < q->status()) { - // check to see if any other applet has a higher status, and stick with that - // if we do - foreach (Applet *applet, applets) { - if (applet->status() > appletStatus) { - appletStatus = applet->status(); - } - } - } - - q->setStatus(appletStatus); -} - void Containment::destroy(bool confirm) { if (immutability() != Mutable || Applet::d->transient) { @@ -2090,316 +1358,6 @@ void Containment::destroy(bool confirm) Applet::destroy(); } -void ContainmentPrivate::createToolBox() -{ - if (!toolBox && KAuthorized::authorizeKAction("plasma/containment_context_menu")) { - toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q); - - if (toolBox) { - QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); - QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility())); - - positionToolBox(); - } - } -} - -void ContainmentPrivate::positionToolBox() -{ - if (toolBox) { - toolBox.data()->reposition(); - } -} - -void ContainmentPrivate::updateToolBoxVisibility() -{ - emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing()); -} - -void ContainmentPrivate::triggerShowAddWidgets() -{ - emit q->showAddWidgetsInterface(QPointF()); -} - -void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) -{ - if (!q->isContainment()) { - return; - } - - //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; - if (constraints & Plasma::ImmutableConstraint) { - //update actions - checkRemoveAction(); - const bool unlocked = q->immutability() == Mutable; - q->setAcceptDrops(unlocked); - q->enableAction("add widgets", unlocked); - - // tell the applets too - foreach (Applet *a, applets) { - a->setImmutability(q->immutability()); - a->updateConstraints(ImmutableConstraint); - } - } - - // pass on the constraints that are relevant here - Constraints appletConstraints = NoConstraint; - if (constraints & FormFactorConstraint) { - appletConstraints |= FormFactorConstraint; - } - - if (constraints & ScreenConstraint) { - appletConstraints |= ScreenConstraint; - } - - if (appletConstraints != NoConstraint) { - foreach (Applet *applet, applets) { - applet->updateConstraints(appletConstraints); - } - } - - if (toolBox && (constraints & Plasma::SizeConstraint || - constraints & Plasma::FormFactorConstraint || - constraints & Plasma::ScreenConstraint || - constraints & Plasma::StartupCompletedConstraint)) { - //kDebug() << "Positioning toolbox"; - positionToolBox(); - } - - if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) { - q->addToolBoxAction(q->action("remove")); - checkRemoveAction(); - } -} - -Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, - const QRectF &appletGeometry, uint id, bool delayInit) -{ - if (!q->isContainment()) { - return 0; - } - - if (!delayInit && q->immutability() != Mutable) { -#ifndef NDEBUG - kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; -#endif - return 0; - } - - QGraphicsView *v = q->view(); - if (v) { - v->setCursor(Qt::BusyCursor); - } - - Applet *applet = PluginLoader::self()->loadApplet(name, id, args); - if (v) { - v->unsetCursor(); - } - - if (!applet) { -#ifndef NDEBUG - kDebug() << "Applet" << name << "could not be loaded."; -#endif - applet = new Applet(0, QString(), id); - applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); - } - - //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); - - q->addApplet(applet, appletGeometry.topLeft(), delayInit); - return applet; -} - -bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const -{ - foreach (Applet *applet, applets) { - if (applet != ignoredApplet && applet->geometry().intersects(region)) { - return false; - } - } - return true; -} - -void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet) -{ - applets.removeAll(applet); - if (focusedApplet == applet) { - focusedApplet = 0; - } - - emit q->appletRemoved(applet); - emit q->configNeedsSaving(); -} - -void ContainmentPrivate::appletAppeared(Applet *applet) -{ - //kDebug() << type << Containment::DesktopContainment; - KConfigGroup *cg = applet->d->mainConfigGroup(); - applet->save(*cg); - emit q->configNeedsSaving(); -} - -void ContainmentPrivate::positionPanel(bool force) -{ - if (!q->scene()) { -#ifndef NDEBUG - kDebug() << "no scene yet"; -#endif - return; - } - - // already positioning the panel - avoid infinite loops - if (ContainmentPrivate::s_positioningPanels) { - return; - } - - // we position panels in negative coordinates, and stack all horizontal - // and all vertical panels with each other. - - - const QPointF p = q->pos(); - - if (!force && - p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && - q->scene()->collidingItems(q).isEmpty()) { - // already positioned and not running into any other panels - return; - } - - - QPointF newPos = preferredPanelPos(q->corona()); - if (p != newPos) { - ContainmentPrivate::s_positioningPanels = true; - q->setPos(newPos); - ContainmentPrivate::s_positioningPanels = false; - } -} - -bool ContainmentPrivate::isPanelContainment() const -{ - return type == Containment::PanelContainment || type == Containment::CustomPanelContainment; -} - -QPointF ContainmentPrivate::preferredPos(Corona *corona) const -{ - Q_ASSERT(corona); - - if (isPanelContainment()) { - //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona); - return preferredPanelPos(corona); - } - - QPointF pos(0, 0); - QTransform t; - while (QGraphicsItem *i = corona->itemAt(pos, t)) { - pos.setX(i->scenePos().x() + i->boundingRect().width() + 10); - } - - //kDebug() << "not a panel, put it at" << pos; - return pos; -} - -QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const -{ - Q_ASSERT(corona); - - //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here - bool horiz = formFactor == Plasma::Horizontal; - qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; - qreal lastHeight = 0; - - // this should be ok for small numbers of panels, but if we ever end - // up managing hundreds of them, this simplistic alogrithm will - // likely be too slow. - foreach (const Containment *other, corona->containments()) { - if (other == q || - !other->d->isPanelContainment() || - horiz != (other->formFactor() == Plasma::Horizontal)) { - // only line up with panels of the same orientation - continue; - } - - if (horiz) { - qreal y = other->pos().y(); - if (y < bottom) { - lastHeight = other->size().height(); - bottom = y; - } - } else { - qreal width = other->size().width(); - qreal x = other->pos().x() + width; - if (x > bottom) { - lastHeight = width; - bottom = x + lastHeight; - } - } - } - - // give a space equal to the height again of the last item so there is - // room to grow. - QPointF newPos; - if (horiz) { - bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; - //TODO: fix x position for non-flush-left panels -#ifndef NDEBUG - kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); -#endif - newPos = QPointF(0, bottom - q->size().height()); - } else { - bottom += lastHeight + INTER_CONTAINMENT_MARGIN; - //TODO: fix y position for non-flush-top panels -#ifndef NDEBUG - kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); -#endif - newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); - } - - return newPos; -} - - -bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu) -{ - ContainmentActions *plugin = actionPlugins()->value(trigger); - if (!plugin) { - return false; - } - plugin->setContainment(q); - - if (!plugin->isInitialized()) { - KConfigGroup cfg = q->containmentActionsConfig(); - KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); - plugin->restore(pluginConfig); - } - - if (plugin->configurationRequired()) { - KMenu *localMenu = menu ? menu : new KMenu(); - - localMenu->addTitle(i18n("This plugin needs to be configured")); - localMenu->addAction(q->action("configure")); - - if (!menu) { - localMenu->exec(screenPos); - delete localMenu; - } - - return false; - } else if (menu) { - QList actions = plugin->contextualActions(); - if (actions.isEmpty()) { - //it probably didn't bother implementing the function. give the user a chance to set - //a better plugin. note that if the user sets no-plugin this won't happen... - if (!isPanelContainment() && q->action("configure")) { - menu->addAction(q->action("configure")); - } - } else { - menu->addActions(actions); - } - } - - return true; -} - KConfigGroup Containment::containmentActionsConfig() { KConfigGroup cfg; @@ -2419,18 +1377,6 @@ KConfigGroup Containment::containmentActionsConfig() return cfg; } -QHash * ContainmentPrivate::actionPlugins() -{ - switch (containmentActionsSource) { - case Activity: - //FIXME - case Local: - return &localActionPlugins; - default: - return &globalActionPlugins; - } -} - } // Plasma namespace diff --git a/private/containment_p.cpp b/private/containment_p.cpp new file mode 100644 index 000000000..1f0f98926 --- /dev/null +++ b/private/containment_p.cpp @@ -0,0 +1,1120 @@ +/* + * Copyright 2007 by Aaron Seigo + * Copyright 2008 by Ménard Alexis + * Copyright 2009 Chani Armitage + * Copyright 2012 Marco Martin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2, 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 Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "private/containment_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef PLASMA_NO_KIO +#include "kio/jobclasses.h" // for KIO::JobFlags +#include "kio/job.h" +#include "kio/scheduler.h" +#endif + +#include "abstracttoolbox.h" +#include "containmentactions.h" +#include "containmentactionspluginsconfig.h" +#include "corona.h" +#include "pluginloader.h" +#include "svg.h" +#include "wallpaper.h" + +#include "remote/accessappletjob.h" +#include "remote/accessmanager.h" + +#include "private/applet_p.h" +#include "private/containmentactionspluginsconfig_p.h" +#include "private/wallpaper_p.h" + + +namespace Plasma +{ + +bool ContainmentPrivate::s_positioningPanels = false; +QHash ContainmentPrivate::globalActionPlugins; + +const char ContainmentPrivate::defaultWallpaper[] = "image"; +const char ContainmentPrivate::defaultWallpaperMode[] = "SingleImage"; + +void ContainmentPrivate::addDefaultActions(KActionCollection *actions, Containment *c) +{ + actions->setConfigGroup("Shortcuts-Containment"); + + //adjust applet actions + KAction *appAction = qobject_cast(actions->action("remove")); + appAction->setShortcut(KShortcut("alt+d, alt+r")); + if (c && c->d->isPanelContainment()) { + appAction->setText(i18n("Remove this Panel")); + } else { + appAction->setText(i18n("Remove this Activity")); + } + + appAction = qobject_cast(actions->action("configure")); + if (appAction) { + appAction->setShortcut(KShortcut("alt+d, alt+s")); + appAction->setText(i18n("Activity Settings")); + } + + //add our own actions + KAction *appletBrowserAction = actions->addAction("add widgets"); + appletBrowserAction->setAutoRepeat(false); + appletBrowserAction->setText(i18n("Add Widgets...")); + appletBrowserAction->setIcon(KDE::icon("list-add")); + appletBrowserAction->setShortcut(KShortcut("alt+d, a")); + appletBrowserAction->setData(AbstractToolBox::AddTool); + + KAction *action = actions->addAction("next applet"); + action->setText(i18n("Next Widget")); + //no icon + action->setShortcut(KShortcut("alt+d, n")); + action->setData(AbstractToolBox::ControlTool); + + action = actions->addAction("previous applet"); + action->setText(i18n("Previous Widget")); + //no icon + action->setShortcut(KShortcut("alt+d, p")); + action->setData(AbstractToolBox::ControlTool); +} + +void ContainmentPrivate::initApplets() +{ + foreach (Applet *applet, applets) { + applet->restore(*applet->d->mainConfigGroup()); + applet->init(); +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Applet" << applet->name(); +#endif + } + + q->flushPendingConstraintsEvents(); + + foreach (Applet *applet, applets) { + applet->flushPendingConstraintsEvents(); + } + +#ifndef NDEBUG + kDebug() << "!!{} STARTUP TIME" << QTime().msecsTo(QTime::currentTime()) << "Containment's applets initialized" << q->name(); +#endif +} + +void ContainmentPrivate::checkContainmentFurniture() +{ + if (q->isContainment() && + (type == Containment::DesktopContainment || type == Containment::PanelContainment)) { + createToolBox(); + } +} + +void ContainmentPrivate::addContainmentActions(KMenu &desktopMenu, QEvent *event) +{ + if (static_cast(q->scene())->immutability() != Mutable && + !KAuthorized::authorizeKAction("plasma/containment_actions")) { + //kDebug() << "immutability"; + return; + } + + const QString trigger = ContainmentActions::eventToString(event); + prepareContainmentActions(trigger, QPoint(), &desktopMenu); +} + +void ContainmentPrivate::addAppletActions(KMenu &desktopMenu, Applet *applet, QEvent *event) +{ + foreach (QAction *action, applet->contextualActions()) { + if (action) { + desktopMenu.addAction(action); + } + } + + if (!applet->d->failed) { + QAction *configureApplet = applet->d->actions->action("configure"); + if (configureApplet && configureApplet->isEnabled()) { + desktopMenu.addAction(configureApplet); + } + + QAction *runAssociatedApplication = applet->d->actions->action("run associated application"); + if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { + desktopMenu.addAction(runAssociatedApplication); + } + } + + KMenu *containmentMenu = new KMenu(i18nc("%1 is the name of the containment", "%1 Options", q->name()), &desktopMenu); + addContainmentActions(*containmentMenu, event); + if (!containmentMenu->isEmpty()) { + int enabled = 0; + //count number of real actions + QListIterator actionsIt(containmentMenu->actions()); + while (enabled < 3 && actionsIt.hasNext()) { + QAction *action = actionsIt.next(); + if (action->isVisible() && !action->isSeparator()) { + ++enabled; + } + } + + if (enabled) { + //if there is only one, don't create a submenu + if (enabled < 2) { + foreach (QAction *action, containmentMenu->actions()) { + if (action->isVisible() && !action->isSeparator()) { + desktopMenu.addAction(action); + } + } + } else { + desktopMenu.addMenu(containmentMenu); + } + } + } + + if (q->immutability() == Mutable) { + QAction *closeApplet = applet->d->actions->action("remove"); + //kDebug() << "checking for removal" << closeApplet; + if (closeApplet) { + if (!desktopMenu.isEmpty()) { + desktopMenu.addSeparator(); + } + + //kDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); + desktopMenu.addAction(closeApplet); + } + } +} + +Applet* ContainmentPrivate::appletAt(const QPointF &point) +{ + Applet *applet = 0; + + QGraphicsItem *item = q->scene()->itemAt(point); + if (item == q) { + item = 0; + } + + while (item) { + if (item->isWidget()) { + applet = qobject_cast(static_cast(item)); + if (applet) { + if (applet->isContainment()) { + applet = 0; + } + break; + } + } + AppletHandle *handle = dynamic_cast(item); + if (handle) { + //pretend it was on the applet + applet = handle->applet(); + break; + } + item = item->parentItem(); + } + return applet; +} + +void ContainmentPrivate::setScreen(int newScreen, int newDesktop, bool preventInvalidDesktops) +{ + // What we want to do in here is: + // * claim the screen as our own + // * signal whatever may be watching this containment about the switch + // * if we are a full screen containment, then: + // * resize to match the screen if we're that kind of containment + // * kick other full-screen containments off this screen + // * if we had a screen, then give our screen to the containment + // we kick out + // + // a screen of -1 means no associated screen. + Corona *corona = q->corona(); + Q_ASSERT(corona); + + //if it's an offscreen widget, don't allow to claim a screen, after all it's *off*screen + if (corona->offscreenWidgets().contains(q)) { + return; + } + + int numScreens = corona->numScreens(); + if (newScreen < -1) { + newScreen = -1; + } + + // -1 == All desktops + if (newDesktop < -1 || (preventInvalidDesktops && newDesktop > KWindowSystem::numberOfDesktops() - 1)) { + newDesktop = -1; + } + + //kDebug() << activity() << "setting screen to " << newScreen << newDesktop << "and type is" << type; + + Containment *swapScreensWith(0); + const bool isDesktopContainment = type == Containment::DesktopContainment || + type == Containment::CustomContainment; + if (isDesktopContainment) { + // we want to listen to changes in work area if our screen changes + if (toolBox) { + if (screen < 0 && newScreen > -1) { + QObject::connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition()), Qt::UniqueConnection); + } else if (newScreen < 0) { + QObject::disconnect(KWindowSystem::self(), SIGNAL(workAreaChanged()), toolBox.data(), SLOT(reposition())); + } + } + + if (newScreen > -1) { + // sanity check to make sure someone else doesn't have this screen already! + Containment *currently = corona->containmentForScreen(newScreen, newDesktop); + if (currently && currently != q) { +#ifndef NDEBUG + kDebug() << "currently is on screen" << currently->screen() + << "desktop" << currently->desktop() + << "and is" << currently->activity() + << (QObject*)currently << "i'm" << (QObject*)q; +#endif + currently->setScreen(-1, currently->desktop()); + swapScreensWith = currently; + } + } + } + + if (newScreen < numScreens && newScreen > -1 && isDesktopContainment) { + q->resize(corona->screenGeometry(newScreen).size()); + } + + int oldDesktop = desktop; + desktop = newDesktop; + + int oldScreen = screen; + screen = newScreen; + + q->updateConstraints(Plasma::ScreenConstraint); + + if (oldScreen != newScreen || oldDesktop != newDesktop) { + /* +#ifndef NDEBUG + kDebug() << "going to signal change for" << q +#endif + << ", old screen & desktop:" << oldScreen << oldDesktop + << ", new:" << screen << desktop; + */ + KConfigGroup c = q->config(); + c.writeEntry("screen", screen); + c.writeEntry("desktop", desktop); + if (newScreen != -1) { + lastScreen = newScreen; + lastDesktop = newDesktop; + c.writeEntry("lastScreen", lastScreen); + c.writeEntry("lastDesktop", lastDesktop); + } + emit q->configNeedsSaving(); + emit q->screenChanged(oldScreen, newScreen, q); + } + + if (swapScreensWith) { + //kDebug() << "setScreen due to swap, part 2"; + swapScreensWith->setScreen(oldScreen, oldDesktop); + } + + checkRemoveAction(); + + if (newScreen >= 0) { + emit q->activate(); + } +} + +void ContainmentPrivate::showDropZoneDelayed() +{ + dropZoneStarted = true; + q->showDropZone(dropPoints.value(0).toPoint()); + dropPoints.remove(0); +} + +void ContainmentPrivate::dropData(QPointF scenePos, QPoint screenPos, QGraphicsSceneDragDropEvent *dropEvent) +{ + if (q->immutability() != Mutable) { + return; + } + + QPointF pos = q->mapFromScene(scenePos); + const QMimeData *mimeData = 0; + + if (dropEvent) { + mimeData = dropEvent->mimeData(); + } else { + QClipboard *clipboard = QApplication::clipboard(); + mimeData = clipboard->mimeData(QClipboard::Selection); + //TODO if that's not supported (ie non-linux) should we try clipboard instead of selection? + } + + if (!mimeData) { + //Selection is either empty or not supported on this OS +#ifndef NDEBUG + kDebug() << "no mime data"; +#endif + return; + } + + //kDebug() << event->mimeData()->text(); + + QString appletMimetype(q->corona() ? q->corona()->appletMimeType() : QString()); + + if (!appletMimetype.isEmpty() && mimeData->hasFormat(appletMimetype)) { + QString data = mimeData->data(appletMimetype); + const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); + foreach (const QString &appletName, appletNames) { + //kDebug() << "doing" << appletName; + QRectF geom(pos, QSize(0, 0)); + q->addApplet(appletName, QVariantList(), geom); + } + if (dropEvent) { + dropEvent->acceptProposedAction(); + } + } else if (mimeData->hasUrls()) { + //TODO: collect the mimeTypes of available script engines and offer + // to create widgets out of the matching URLs, if any + const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); + foreach (const QUrl &url, urls) { + if (AccessManager::supportedProtocols().contains(url.scheme())) { + AccessAppletJob *job = AccessManager::self()->accessRemoteApplet(url); + if (dropEvent) { + dropPoints[job] = dropEvent->pos(); + } else { + dropPoints[job] = scenePos; + } + QObject::connect(AccessManager::self(), SIGNAL(finished(Plasma::AccessAppletJob*)), + q, SLOT(remoteAppletReady(Plasma::AccessAppletJob*))); + } +#ifndef PLASMA_NO_KIO + else { + QMimeDatabase db; + QMimeType mime = db.mimeTypeForUrl(url); + QString mimeName = mime.name(); + QRectF geom(pos, QSize()); + QVariantList args; + args << url.toString(); +#ifndef NDEBUG + kDebug() << "can decode" << mimeName << args; +#endif + + // It may be a directory or a file, let's stat + KIO::JobFlags flags = KIO::HideProgressInfo; + KIO::MimetypeJob *job = KIO::mimetype(url, flags); + if (dropEvent) { + dropPoints[job] = dropEvent->pos(); + } else { + dropPoints[job] = scenePos; + } + + QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(dropJobResult(KJob*))); + QObject::connect(job, SIGNAL(mimetype(KIO::Job*,QString)), + q, SLOT(mimeTypeRetrieved(KIO::Job*,QString))); + + KMenu *choices = new KMenu("Content dropped"); + choices->addAction(KDE::icon("process-working"), i18n("Fetching file type...")); + if (dropEvent) { + choices->popup(dropEvent->screenPos()); + } else { + choices->popup(screenPos); + } + + dropMenus[job] = choices; + } +#endif + } + + if (dropEvent) { + dropEvent->acceptProposedAction(); + } + } else { + QStringList formats = mimeData->formats(); + QHash seenPlugins; + QHash pluginFormats; + + foreach (const QString &format, formats) { + KPluginInfo::List plugins = Applet::listAppletInfoForMimeType(format); + + foreach (const KPluginInfo &plugin, plugins) { + if (seenPlugins.contains(plugin.pluginName())) { + continue; + } + + seenPlugins.insert(plugin.pluginName(), plugin); + pluginFormats.insert(plugin.pluginName(), format); + } + } + //kDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); + + QString selectedPlugin; + + if (seenPlugins.isEmpty()) { + // do nothing + } else if (seenPlugins.count() == 1) { + selectedPlugin = seenPlugins.constBegin().key(); + } else { + KMenu choices; + QHash actionsToPlugins; + foreach (const KPluginInfo &info, seenPlugins) { + QAction *action; + if (!info.icon().isEmpty()) { + action = choices.addAction(KDE::icon(info.icon()), info.name()); + } else { + action = choices.addAction(info.name()); + } + + actionsToPlugins.insert(action, info.pluginName()); + } + + QAction *choice = choices.exec(screenPos); + if (choice) { + selectedPlugin = actionsToPlugins[choice]; + } + } + + if (!selectedPlugin.isEmpty()) { + if (!dropEvent) { + // since we may have entered an event loop up above with the menu, + // the clipboard item may no longer be valid, as QClipboard resets + // the object behind the back of the application with a zero timer + // so we fetch it again here + QClipboard *clipboard = QApplication::clipboard(); + mimeData = clipboard->mimeData(QClipboard::Selection); + } + + QTemporaryFile tempFile; + if (mimeData && tempFile.open()) { + //TODO: what should we do with files after the applet is done with them?? + tempFile.setAutoRemove(false); + + { + QDataStream stream(&tempFile); + QByteArray data = mimeData->data(pluginFormats[selectedPlugin]); + stream.writeRawData(data, data.size()); + } + + QRectF geom(pos, QSize()); + QVariantList args; + args << tempFile.fileName(); +#ifndef NDEBUG + kDebug() << args; +#endif + tempFile.close(); + + q->addApplet(selectedPlugin, args, geom); + } + } + } +} + +void ContainmentPrivate::clearDataForMimeJob(KIO::Job *job) +{ +#ifndef PLASMA_NO_KIO + QObject::disconnect(job, 0, q, 0); + dropPoints.remove(job); + KMenu *choices = dropMenus.take(job); + delete choices; + job->kill(); +#endif // PLASMA_NO_KIO +} + +void ContainmentPrivate::remoteAppletReady(Plasma::AccessAppletJob *job) +{ + QPointF pos = dropPoints.take(job); + if (job->error()) { + //TODO: nice user visible error handling (knotification probably?) +#ifndef NDEBUG + kDebug() << "remote applet access failed: " << job->errorText(); +#endif + return; + } + + if (!job->applet()) { +#ifndef NDEBUG + kDebug() << "how did we end up here? if applet is null, the job->error should be nonzero"; +#endif + return; + } + + q->addApplet(job->applet(), pos); +} + +void ContainmentPrivate::dropJobResult(KJob *job) +{ +#ifndef PLASMA_NO_KIO + KIO::TransferJob* tjob = dynamic_cast(job); + if (!tjob) { +#ifndef NDEBUG + kDebug() << "job is not a KIO::TransferJob, won't handle the drop..."; +#endif + clearDataForMimeJob(tjob); + return; + } + if (job->error()) { +#ifndef NDEBUG + kDebug() << "ERROR" << tjob->error() << ' ' << tjob->errorString(); +#endif + } + // We call mimeTypeRetrieved since there might be other mechanisms + // for finding suitable applets. Cleanup happens there as well. + mimeTypeRetrieved(qobject_cast(job), QString()); +#endif // PLASMA_NO_KIO +} + +void ContainmentPrivate::mimeTypeRetrieved(KIO::Job *job, const QString &mimeType) +{ +#ifndef PLASMA_NO_KIO +#ifndef NDEBUG + kDebug() << "Mimetype Job returns." << mimeType; +#endif + KIO::TransferJob* tjob = dynamic_cast(job); + if (!tjob) { +#ifndef NDEBUG + kDebug() << "job should be a TransferJob, but isn't"; +#endif + clearDataForMimeJob(job); + return; + } + KPluginInfo::List appletList = Applet::listAppletInfoForUrl(tjob->url()); + if (mimeType.isEmpty() && !appletList.count()) { + clearDataForMimeJob(job); +#ifndef NDEBUG + kDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimeType (" << mimeType << ")"; +#endif + return; + } else { + + QPointF posi; // will be overwritten with the event's position + if (dropPoints.keys().contains(tjob)) { + posi = dropPoints[tjob]; +#ifndef NDEBUG + kDebug() << "Received a suitable dropEvent at" << posi; +#endif + } else { +#ifndef NDEBUG + kDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; +#endif + clearDataForMimeJob(job); + return; + } + + KMenu *choices = dropMenus.value(tjob); + if (!choices) { +#ifndef NDEBUG + kDebug() << "Bailing out. No QMenu found for this job."; +#endif + clearDataForMimeJob(job); + return; + } + + QVariantList args; + args << tjob->url().toString() << mimeType; + +#ifndef NDEBUG + kDebug() << "Creating menu for:" << mimeType << posi << args; +#endif + + appletList << Applet::listAppletInfoForMimeType(mimeType); + KPluginInfo::List wallpaperList; + if (drawWallpaper) { + if (wallpaper && wallpaper->supportsMimetype(mimeType)) { + wallpaperList << wallpaper->d->wallpaperDescription; + } else { + wallpaperList = Wallpaper::listWallpaperInfoForMimetype(mimeType); + } + } + + if (!appletList.isEmpty() || !wallpaperList.isEmpty()) { + choices->clear(); + QHash actionsToApplets; + choices->addTitle(i18n("Widgets")); + foreach (const KPluginInfo &info, appletList) { +#ifndef NDEBUG + kDebug() << info.name(); +#endif + QAction *action; + if (!info.icon().isEmpty()) { + action = choices->addAction(KDE::icon(info.icon()), info.name()); + } else { + action = choices->addAction(info.name()); + } + + actionsToApplets.insert(action, info.pluginName()); +#ifndef NDEBUG + kDebug() << info.pluginName(); +#endif + } + actionsToApplets.insert(choices->addAction(i18n("Icon")), "icon"); + + QHash actionsToWallpapers; + if (!wallpaperList.isEmpty()) { + choices->addTitle(i18n("Wallpaper")); + + QMap sorted; + foreach (const KPluginInfo &info, appletList) { + sorted.insert(info.name(), info); + } + + foreach (const KPluginInfo &info, wallpaperList) { + QAction *action; + if (!info.icon().isEmpty()) { + action = choices->addAction(KDE::icon(info.icon()), info.name()); + } else { + action = choices->addAction(info.name()); + } + + actionsToWallpapers.insert(action, info.pluginName()); + } + } + + QAction *choice = choices->exec(); + if (choice) { + // Put the job on hold so it can be recycled to fetch the actual content, + // which is to be expected when something's dropped onto the desktop and + // an applet is to be created with this URL + if (!mimeType.isEmpty() && !tjob->error()) { + tjob->putOnHold(); + KIO::Scheduler::publishSlaveOnHold(); + } + QString plugin = actionsToApplets.value(choice); + if (plugin.isEmpty()) { + //set wallpapery stuff + plugin = actionsToWallpapers.value(choice); + if (!wallpaper || plugin != wallpaper->pluginName()) { + //kDebug() << "Wallpaper dropped:" << tjob->url(); + q->setWallpaper(plugin); + } + + if (wallpaper) { + //kDebug() << "Wallpaper dropped:" << tjob->url(); + wallpaper->addUrls(QList() << tjob->url()); + } + } else { + addApplet(actionsToApplets[choice], args, QRectF(posi, QSize())); + } + + clearDataForMimeJob(job); + return; + } + } else { + // we can at least create an icon as a link to the URL + addApplet("icon", args, QRectF(posi, QSize())); + } + } + + clearDataForMimeJob(job); +#endif // PLASMA_NO_KIO +} + +KActionCollection* ContainmentPrivate::actions() +{ + return static_cast(q)->d->actions; +} + +void ContainmentPrivate::focusApplet(Plasma::Applet *applet) +{ + if (focusedApplet == applet) { + return; + } + + QList widgets = actions()->associatedWidgets(); + if (focusedApplet) { + foreach (QWidget *w, widgets) { + focusedApplet->removeAssociatedWidget(w); + } + } + + if (applet && applets.contains(applet)) { + //kDebug() << "switching to" << applet->name(); + focusedApplet = applet; + foreach (QWidget *w, widgets) { + focusedApplet->addAssociatedWidget(w); + } + + if (!focusedApplet->hasFocus()) { + focusedApplet->setFocus(Qt::ShortcutFocusReason); + } + } else { + focusedApplet = 0; + } +} + +void ContainmentPrivate::configChanged() +{ + if (drawWallpaper) { + KConfigGroup group = q->config(); + q->setWallpaper(group.readEntry("wallpaperplugin", defaultWallpaper), + group.readEntry("wallpaperpluginmode", defaultWallpaperMode)); + } +} + +void ContainmentPrivate::requestConfiguration() +{ + emit q->configureRequested(q); +} + +void ContainmentPrivate::checkStatus(Plasma::ItemStatus appletStatus) +{ + //kDebug() << "================== "<< appletStatus << q->status(); + if (appletStatus == q->status()) { + emit q->newStatus(appletStatus); + return; + } + + if (appletStatus < q->status()) { + // check to see if any other applet has a higher status, and stick with that + // if we do + foreach (Applet *applet, applets) { + if (applet->status() > appletStatus) { + appletStatus = applet->status(); + } + } + } + + q->setStatus(appletStatus); +} + +void ContainmentPrivate::createToolBox() +{ + if (!toolBox && KAuthorized::authorizeKAction("plasma/containment_context_menu")) { + toolBox = Plasma::AbstractToolBox::load(q->corona()->preferredToolBoxPlugin(type), QVariantList(), q); + + if (toolBox) { + QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SIGNAL(toolBoxToggled())); + QObject::connect(toolBox.data(), SIGNAL(toggled()), q, SLOT(updateToolBoxVisibility())); + + positionToolBox(); + } + } +} + +void ContainmentPrivate::positionToolBox() +{ + if (toolBox) { + toolBox.data()->reposition(); + } +} + +void ContainmentPrivate::updateToolBoxVisibility() +{ + emit q->toolBoxVisibilityChanged(toolBox.data()->isShowing()); +} + +void ContainmentPrivate::triggerShowAddWidgets() +{ + emit q->showAddWidgetsInterface(QPointF()); +} + +void ContainmentPrivate::containmentConstraintsEvent(Plasma::Constraints constraints) +{ + if (!q->isContainment()) { + return; + } + + //kDebug() << "got containmentConstraintsEvent" << constraints << (QObject*)toolBox; + if (constraints & Plasma::ImmutableConstraint) { + //update actions + checkRemoveAction(); + const bool unlocked = q->immutability() == Mutable; + q->setAcceptDrops(unlocked); + q->enableAction("add widgets", unlocked); + + // tell the applets too + foreach (Applet *a, applets) { + a->setImmutability(q->immutability()); + a->updateConstraints(ImmutableConstraint); + } + } + + // pass on the constraints that are relevant here + Constraints appletConstraints = NoConstraint; + if (constraints & FormFactorConstraint) { + appletConstraints |= FormFactorConstraint; + } + + if (constraints & ScreenConstraint) { + appletConstraints |= ScreenConstraint; + } + + if (appletConstraints != NoConstraint) { + foreach (Applet *applet, applets) { + applet->updateConstraints(appletConstraints); + } + } + + if (toolBox && (constraints & Plasma::SizeConstraint || + constraints & Plasma::FormFactorConstraint || + constraints & Plasma::ScreenConstraint || + constraints & Plasma::StartupCompletedConstraint)) { + //kDebug() << "Positioning toolbox"; + positionToolBox(); + } + + if (constraints & Plasma::StartupCompletedConstraint && type < Containment::CustomContainment) { + q->addToolBoxAction(q->action("remove")); + checkRemoveAction(); + } +} + +Applet *ContainmentPrivate::addApplet(const QString &name, const QVariantList &args, + const QRectF &appletGeometry, uint id, bool delayInit) +{ + if (!q->isContainment()) { + return 0; + } + + if (!delayInit && q->immutability() != Mutable) { +#ifndef NDEBUG + kDebug() << "addApplet for" << name << "requested, but we're currently immutable!"; +#endif + return 0; + } + + QGraphicsView *v = q->view(); + if (v) { + v->setCursor(Qt::BusyCursor); + } + + Applet *applet = PluginLoader::self()->loadApplet(name, id, args); + if (v) { + v->unsetCursor(); + } + + if (!applet) { +#ifndef NDEBUG + kDebug() << "Applet" << name << "could not be loaded."; +#endif + applet = new Applet(0, QString(), id); + applet->setFailedToLaunch(true, i18n("Could not find requested component: %1", name)); + } + + //kDebug() << applet->name() << "sizehint:" << applet->sizeHint() << "geometry:" << applet->geometry(); + + q->addApplet(applet, appletGeometry.topLeft(), delayInit); + return applet; +} + +bool ContainmentPrivate::regionIsEmpty(const QRectF ®ion, Applet *ignoredApplet) const +{ + foreach (Applet *applet, applets) { + if (applet != ignoredApplet && applet->geometry().intersects(region)) { + return false; + } + } + return true; +} + +void ContainmentPrivate::appletDestroyed(Plasma::Applet *applet) +{ + applets.removeAll(applet); + if (focusedApplet == applet) { + focusedApplet = 0; + } + + emit q->appletRemoved(applet); + emit q->configNeedsSaving(); +} + +void ContainmentPrivate::appletAppeared(Applet *applet) +{ + //kDebug() << type << Containment::DesktopContainment; + KConfigGroup *cg = applet->d->mainConfigGroup(); + applet->save(*cg); + emit q->configNeedsSaving(); +} + +void ContainmentPrivate::positionPanel(bool force) +{ + if (!q->scene()) { +#ifndef NDEBUG + kDebug() << "no scene yet"; +#endif + return; + } + + // already positioning the panel - avoid infinite loops + if (ContainmentPrivate::s_positioningPanels) { + return; + } + + // we position panels in negative coordinates, and stack all horizontal + // and all vertical panels with each other. + + + const QPointF p = q->pos(); + + if (!force && + p.y() + q->size().height() < -INTER_CONTAINMENT_MARGIN && + q->scene()->collidingItems(q).isEmpty()) { + // already positioned and not running into any other panels + return; + } + + + QPointF newPos = preferredPanelPos(q->corona()); + if (p != newPos) { + ContainmentPrivate::s_positioningPanels = true; + q->setPos(newPos); + ContainmentPrivate::s_positioningPanels = false; + } +} + +bool ContainmentPrivate::isPanelContainment() const +{ + return type == Containment::PanelContainment || type == Containment::CustomPanelContainment; +} + +QPointF ContainmentPrivate::preferredPos(Corona *corona) const +{ + Q_ASSERT(corona); + + if (isPanelContainment()) { + //kDebug() << "is a panel, so put it at" << preferredPanelPos(corona); + return preferredPanelPos(corona); + } + + QPointF pos(0, 0); + QTransform t; + while (QGraphicsItem *i = corona->itemAt(pos, t)) { + pos.setX(i->scenePos().x() + i->boundingRect().width() + 10); + } + + //kDebug() << "not a panel, put it at" << pos; + return pos; +} + +QPointF ContainmentPrivate::preferredPanelPos(Corona *corona) const +{ + Q_ASSERT(corona); + + //TODO: research how non-Horizontal, non-Vertical (e.g. Planar) panels behave here + bool horiz = formFactor == Plasma::Horizontal; + qreal bottom = horiz ? 0 : VERTICAL_STACKING_OFFSET; + qreal lastHeight = 0; + + // this should be ok for small numbers of panels, but if we ever end + // up managing hundreds of them, this simplistic alogrithm will + // likely be too slow. + foreach (const Containment *other, corona->containments()) { + if (other == q || + !other->d->isPanelContainment() || + horiz != (other->formFactor() == Plasma::Horizontal)) { + // only line up with panels of the same orientation + continue; + } + + if (horiz) { + qreal y = other->pos().y(); + if (y < bottom) { + lastHeight = other->size().height(); + bottom = y; + } + } else { + qreal width = other->size().width(); + qreal x = other->pos().x() + width; + if (x > bottom) { + lastHeight = width; + bottom = x + lastHeight; + } + } + } + + // give a space equal to the height again of the last item so there is + // room to grow. + QPointF newPos; + if (horiz) { + bottom -= lastHeight + INTER_CONTAINMENT_MARGIN; + //TODO: fix x position for non-flush-left panels +#ifndef NDEBUG + kDebug() << "moved to" << QPointF(0, bottom - q->size().height()); +#endif + newPos = QPointF(0, bottom - q->size().height()); + } else { + bottom += lastHeight + INTER_CONTAINMENT_MARGIN; + //TODO: fix y position for non-flush-top panels +#ifndef NDEBUG + kDebug() << "moved to" << QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); +#endif + newPos = QPointF(bottom + q->size().width(), -INTER_CONTAINMENT_MARGIN - q->size().height()); + } + + return newPos; +} + + +bool ContainmentPrivate::prepareContainmentActions(const QString &trigger, const QPoint &screenPos, KMenu *menu) +{ + ContainmentActions *plugin = actionPlugins()->value(trigger); + if (!plugin) { + return false; + } + plugin->setContainment(q); + + if (!plugin->isInitialized()) { + KConfigGroup cfg = q->containmentActionsConfig(); + KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); + plugin->restore(pluginConfig); + } + + if (plugin->configurationRequired()) { + KMenu *localMenu = menu ? menu : new KMenu(); + + localMenu->addTitle(i18n("This plugin needs to be configured")); + localMenu->addAction(q->action("configure")); + + if (!menu) { + localMenu->exec(screenPos); + delete localMenu; + } + + return false; + } else if (menu) { + QList actions = plugin->contextualActions(); + if (actions.isEmpty()) { + //it probably didn't bother implementing the function. give the user a chance to set + //a better plugin. note that if the user sets no-plugin this won't happen... + if (!isPanelContainment() && q->action("configure")) { + menu->addAction(q->action("configure")); + } + } else { + menu->addActions(actions); + } + } + + return true; +} + +QHash * ContainmentPrivate::actionPlugins() +{ + switch (containmentActionsSource) { + case Activity: + //FIXME + case Local: + return &localActionPlugins; + default: + return &globalActionPlugins; + } +} + +} diff --git a/private/containment_p.h b/private/containment_p.h index 00d96bed9..7644f4683 100644 --- a/private/containment_p.h +++ b/private/containment_p.h @@ -23,6 +23,10 @@ #include +#include "plasma.h" +#include "applet.h" +#include "corona.h" + static const int INTER_CONTAINMENT_MARGIN = 6; static const int TOOLBOX_MARGIN = 150; static const int CONTAINMENT_COLUMNS = 2; @@ -183,6 +187,8 @@ public: }; ContainmentActionsSource containmentActionsSource; static QHash globalActionPlugins; + static const char defaultWallpaper[]; + static const char defaultWallpaperMode[]; }; } // Plasma namespace