diff --git a/app/dockcorona.cpp b/app/dockcorona.cpp index d538773e..d4dba4be 100644 --- a/app/dockcorona.cpp +++ b/app/dockcorona.cpp @@ -1,1478 +1,1478 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dockcorona.h" #include "dockview.h" #include "packageplugins/shell/dockpackage.h" #include "abstractwindowinterface.h" #include "alternativeshelper.h" #include "screenpool.h" //dbus adaptor #include "lattedockadaptor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Latte { DockCorona::DockCorona(QObject *parent) : Plasma::Corona(parent), m_activityConsumer(new KActivities::Consumer(this)), m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), m_globalShortcuts(new GlobalShortcuts(this)), m_universalSettings(new UniversalSettings(KSharedConfig::openConfig(), this)), m_layoutManager(new LayoutManager(this)) { setupWaylandIntegration(); KPackage::Package package(new DockPackage(this)); m_screenPool->load(); if (!package.isValid()) { qWarning() << staticMetaObject.className() << "the package" << package.metadata().rawData() << "is invalid!"; return; } else { qDebug() << staticMetaObject.className() << "the package" << package.metadata().rawData() << "is valid!"; } setKPackage(package); //! universal settings must be loaded after the package has been set m_universalSettings->load(); qmlRegisterTypes(); QFontDatabase::addApplicationFont(kPackage().filePath("tangerineFont")); //connect(this, &Corona::containmentAdded, this, &DockCorona::addDock); if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running)) { load(); } connect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &DockCorona::load); m_docksScreenSyncTimer.setSingleShot(true); m_docksScreenSyncTimer.setInterval(2500); connect(&m_docksScreenSyncTimer, &QTimer::timeout, this, &DockCorona::syncDockViews); //! Dbus adaptor initialization new LatteDockAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Latte"), this); } DockCorona::~DockCorona() { m_docksScreenSyncTimer.stop(); cleanConfig(); //qDebug() << "corona config file:" << config()->name(); while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona delete containments().first(); } m_globalShortcuts->deleteLater(); m_screenPool->deleteLater(); m_layoutManager->deleteLater(); m_universalSettings->deleteLater(); qDeleteAll(m_dockViews); qDeleteAll(m_waitingDockViews); m_dockViews.clear(); m_waitingDockViews.clear(); disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &DockCorona::load); delete m_activityConsumer; qDebug() << "latte corona deleted..." << this; } void DockCorona::load() { if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running) && m_activitiesStarting) { disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &DockCorona::load); m_layoutManager->load(); m_activitiesStarting = false; // connect(qGuiApp, &QGuiApplication::screenAdded, this, &DockCorona::addOutput, Qt::UniqueConnection); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &DockCorona::primaryOutputChanged, Qt::UniqueConnection); // connect(qGuiApp, &QGuiApplication::screenRemoved, this, &DockCorona::screenRemoved, Qt::UniqueConnection); connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, this, &DockCorona::screenCountChanged); connect(m_screenPool, &ScreenPool::primaryPoolChanged, this, &DockCorona::screenCountChanged); QString assignedLayout = m_layoutManager->shouldSwitchToLayout(m_activityConsumer->currentActivity()); if (!assignedLayout.isEmpty() && assignedLayout != m_universalSettings->currentLayoutName()) { m_layoutManager->switchToLayout(assignedLayout); } else { m_layoutManager->switchToLayout(m_universalSettings->currentLayoutName()); } /*foreach (auto containment, containments()) addDock(containment);*/ } } void DockCorona::unload() { qDebug() << "unload: removing dockViews and containments..."; while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona //this form doesn't crash, while qDeleteAll(containments()) does delete containments().first(); } qDeleteAll(m_dockViews); qDeleteAll(m_waitingDockViews); m_dockViews.clear(); m_waitingDockViews.clear(); } void DockCorona::loadLatteLayout(QString layoutPath) { if (!layoutPath.isEmpty()) { qDebug() << "corona is unloading the interface..."; unload(); qDebug() << "loading layout:" << layoutPath; loadLayout(layoutPath); m_firstContainmentWithTasks = -1; m_tasksWillBeLoaded = heuresticForLoadingDockWithTasks(); qDebug() << "TASKS WILL BE PRESENT AFTER LOADING ::: " << m_tasksWillBeLoaded; foreach (auto containment, containments()) addDock(containment); } } void DockCorona::setupWaylandIntegration() { using namespace KWayland::Client; if (!KWindowSystem::isPlatformWayland()) { return; } auto connection = ConnectionThread::fromApplication(this); if (!connection) return; Registry *registry{new Registry(this)}; registry->create(connection); connect(registry, &Registry::plasmaShellAnnounced, this , [this, registry](quint32 name, quint32 version) { m_waylandDockCorona = registry->createPlasmaShell(name, version, this); }); connect(qApp, &QCoreApplication::aboutToQuit, this, [this, registry]() { if (m_waylandDockCorona) m_waylandDockCorona->release(); registry->release(); }); registry->setup(); } KWayland::Client::PlasmaShell *DockCorona::waylandDockCoronaInterface() const { return m_waylandDockCorona; } void DockCorona::cleanConfig() { auto containmentsEntries = config()->group("Containments"); bool changed = false; foreach (auto cId, containmentsEntries.groupList()) { if (!containmentExists(cId.toUInt())) { //cleanup obsolete containments containmentsEntries.group(cId).deleteGroup(); changed = true; qDebug() << "obsolete containment configuration deleted:" << cId; } else { //cleanup obsolete applets of running containments auto appletsEntries = containmentsEntries.group(cId).group("Applets"); foreach (auto appletId, appletsEntries.groupList()) { if (!appletExists(cId.toUInt(), appletId.toUInt())) { appletsEntries.group(appletId).deleteGroup(); changed = true; qDebug() << "obsolete applet configuration deleted:" << appletId; } } } } if (changed) { config()->sync(); qDebug() << "configuration file cleaned..."; } } bool DockCorona::containmentExists(uint id) const { foreach (auto containment, containments()) { if (id == containment->id()) { return true; } } return false; } bool DockCorona::appletExists(uint containmentId, uint appletId) const { Plasma::Containment *containment = nullptr; foreach (auto cont, containments()) { if (containmentId == cont->id()) { containment = cont; break; } } if (!containment) { return false; } foreach (auto applet, containment->applets()) { if (applet->id() == appletId) { return true; } } return false; } ScreenPool *DockCorona::screenPool() const { return m_screenPool; } UniversalSettings *DockCorona::universalSettings() const { return m_universalSettings; } LayoutManager *DockCorona::layoutManager() const { return m_layoutManager; } int DockCorona::numScreens() const { return qGuiApp->screens().count(); } QRect DockCorona::screenGeometry(int id) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; QString screenName; if (m_screenPool->knownIds().contains(id)) screenName = m_screenPool->connector(id); foreach (auto scr, screens) { if (scr->name() == screenName) { screen = scr; break; } } return screen->geometry(); } QRegion DockCorona::availableScreenRegion(int id) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; QString screenName; if (m_screenPool->knownIds().contains(id)) screenName = m_screenPool->connector(id); foreach (auto scr, screens) { if (scr->name() == screenName) { screen = scr; break; } } if (!screen) return QRegion(); QRegion available(screen->geometry()); for (const auto *view : m_dockViews) { if (view && view->containment() && view->screen() == screen) { int realThickness = view->normalThickness() - view->shadow(); // Usually availableScreenRect is used by the desktop, // but Latte dont have desktop, then here just // need calculate available space for top and bottom location, // because the left and right are those who dodge others docks switch (view->location()) { case Plasma::Types::TopEdge: if (view->behaveAsPlasmaPanel()) { available -= view->geometry(); } else { QRect realGeometry; int realWidth = view->maxLength() * view->width(); switch (view->alignment()) { case Latte::Dock::Left: realGeometry = QRect(view->x(), view->y(), realWidth, realThickness); break; case Latte::Dock::Center: case Latte::Dock::Justify: realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2) , view->y(), realWidth , realThickness); break; case Latte::Dock::Right: realGeometry = QRect(view->geometry().right() - realWidth + 1, view->y(), realWidth, realThickness); break; } available -= realGeometry; } break; case Plasma::Types::BottomEdge: if (view->behaveAsPlasmaPanel()) { available -= view->geometry(); } else { QRect realGeometry; int realWidth = view->maxLength() * view->width(); int realY = view->geometry().bottom() - realThickness + 1; switch (view->alignment()) { case Latte::Dock::Left: realGeometry = QRect(view->x(), realY, realWidth, realThickness); break; case Latte::Dock::Center: case Latte::Dock::Justify: realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2), realY, realWidth, realThickness); break; case Latte::Dock::Right: realGeometry = QRect(view->geometry().right() - realWidth + 1, realY, realWidth, realThickness); break; } available -= realGeometry; } break; } } } /*qDebug() << "::::: FREE AREAS :::::"; for (int i = 0; i < available.rectCount(); ++i) { qDebug() << available.rects().at(i); } qDebug() << "::::: END OF FREE AREAS :::::";*/ return available; } QRect DockCorona::availableScreenRect(int id) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; if (m_screenPool->knownIds().contains(id)) { QString scrName = m_screenPool->connector(id); foreach (auto scr, screens) { if (scr->name() == scrName) { screen = scr; break; } } } if (!screen) return {}; auto available = screen->geometry(); for (const auto *view : m_dockViews) { if (view && view->containment() && view->screen() == screen) { auto dockRect = view->absGeometry(); // Usually availableScreenRect is used by the desktop, // but Latte dont have desktop, then here just // need calculate available space for top and bottom location, // because the left and right are those who dodge others docks switch (view->location()) { case Plasma::Types::TopEdge: available.setTopLeft({available.x(), dockRect.bottom()}); break; case Plasma::Types::BottomEdge: available.setBottomLeft({available.x(), dockRect.top()}); break; } } } return available; } //! the number of currently running docks containing //! tasks plasmoid int DockCorona::noDocksWithTasks() const { int result = 0; foreach (auto view, m_dockViews) { if (view->tasksPresent()) { result++; } } return result; } void DockCorona::addOutput(QScreen *screen) { Q_ASSERT(screen); int id = m_screenPool->id(screen->name()); if (id == -1) { int newId = m_screenPool->firstAvailableId(); m_screenPool->insertScreenMapping(newId, screen->name()); } } void DockCorona::primaryOutputChanged() { /* qDebug() << "primary changed ### "<< qGuiApp->primaryScreen()->name(); foreach(auto scr, qGuiApp->screens()){ qDebug() << "Found screen: "<name(); }*/ //if (m_dockViews.count()==1 && qGuiApp->screens().size()==1) { // foreach(auto view, m_dockViews) { // view->setScreenToFollow(qGuiApp->primaryScreen()); // } // } } void DockCorona::screenRemoved(QScreen *screen) { Q_ASSERT(screen); } void DockCorona::screenCountChanged() { m_docksScreenSyncTimer.start(); } //! the central functions that updates loading/unloading dockviews //! concerning screen changed (for multi-screen setups mainly) void DockCorona::syncDockViews() { qDebug() << "screen count changed -+-+ " << qGuiApp->screens().size(); qDebug() << "adding consideration...."; qDebug() << "dock view running : " << m_dockViews.count(); foreach (auto scr, qGuiApp->screens()) { qDebug() << "Found screen: " << scr->name(); foreach (auto cont, containments()) { int id = cont->screen(); if (id == -1) { id = cont->lastScreen(); } bool onPrimary = cont->config().readEntry("onPrimary", true); Plasma::Types::Location location = static_cast((int)cont->config().readEntry("location", (int)Plasma::Types::BottomEdge)); //! two main situations that a dock must be added when it is not already running //! 1. when a dock is primary, not running and the edge for which is associated is free //! 2. when a dock in explicit, not running and the associated screen currently exists //! e.g. the screen has just been added if (((onPrimary && freeEdges(qGuiApp->primaryScreen()).contains(location)) || (!onPrimary && (m_screenPool->connector(id) == scr->name()))) && (!m_dockViews.contains(cont))) { qDebug() << "screen Count signal: view must be added... for:" << scr->name(); addDock(cont); } } } qDebug() << "removing consideration & updating screen for always on primary docks...."; - //! this code trys to find a containment that must not be deleted by + //! this code tries to find a containment that must not be deleted by //! automatic algorithm. Currently the containment with the minimum id //! containing tasks plasmoid wins int preserveContainmentId{ -1}; bool dockWithTasksWillBeShown{false}; //! associate correct values for preserveContainmentId and //! dockWithTasksWillBeShown foreach (auto view, m_dockViews) { bool found{false}; foreach (auto scr, qGuiApp->screens()) { if (scr->name() == view->currentScreen() || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { found = true; break; } } //!check if a tasks dock will be shown (try to prevent its deletion) if (found && view->tasksPresent()) { dockWithTasksWillBeShown = true; } if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) && !(view->tasksPresent() && noDocksWithTasks() == 1)) { //do not delete last dock containing tasks if (view->tasksPresent()) { if (preserveContainmentId == -1) preserveContainmentId = view->containment()->id(); else if (view->containment()->id() < preserveContainmentId) preserveContainmentId = view->containment()->id(); } } } //! check which docks must be deleted e.g. when the corresponding //! screen does not exist any more. //! The code is smart enough in order //! to never delete the last tasks dock and also it makes sure that //! the last tasks dock which will exist in the end will be the one //! with the lowest containment id foreach (auto view, m_dockViews) { bool found{false}; foreach (auto scr, qGuiApp->screens()) { if (scr->name() == view->currentScreen() || (view->onPrimary() && scr == qGuiApp->primaryScreen())) { found = true; break; } } //! which explicit docks can be deleted if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment()) && !(view->tasksPresent() && noDocksWithTasks() == 1)) { //do not delete last dock containing tasks if (dockWithTasksWillBeShown || preserveContainmentId != view->containment()->id()) { qDebug() << "screen Count signal: view must be deleted... for:" << view->currentScreen(); auto viewToDelete = m_dockViews.take(view->containment()); viewToDelete->deleteLater(); } //!which primary docks can be deleted } else if (view->onPrimary() && !found && !freeEdges(qGuiApp->primaryScreen()).contains(view->location())) { qDebug() << "screen Count signal: primary view must be deleted... for:" << view->currentScreen(); auto viewToDelete = m_dockViews.take(view->containment()); viewToDelete->deleteLater(); } else { //! if the dock will not be deleted its a very good point to reconsider //! if the screen in which is running is the correct one view->reconsiderScreen(); } } qDebug() << "end of screens count change...."; } int DockCorona::primaryScreenId() const { //this is not the proper way because kwin probably uses a different //index of screens... //This needs a lot of testing... return m_screenPool->id(qGuiApp->primaryScreen()->name()); } int DockCorona::docksCount(int screen) const { QScreen *scr = m_screenPool->screenForId(screen); int docks{0}; for (const auto &view : m_dockViews) { if (view && view->screen() == scr && !view->containment()->destroyed()) { ++docks; } } // qDebug() << docks << "docks on screen:" << screen; return docks; } int DockCorona::docksCount() const { int docks{0}; for (const auto &view : m_dockViews) { if (view && view->containment() && !view->containment()->destroyed()) { ++docks; } } // qDebug() << docks << "docks on screen:" << screen; return docks; } int DockCorona::docksCount(QScreen *screen) const { int docks{0}; for (const auto &view : m_dockViews) { if (view && view->screen() == screen && !view->containment()->destroyed()) { ++docks; } } // qDebug() << docks << "docks on screen:" << screen; return docks; } void DockCorona::closeApplication() { qGuiApp->quit(); } void DockCorona::aboutApplication() { if (aboutDialog) { aboutDialog->hide(); aboutDialog->deleteLater(); } aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData()); connect(aboutDialog.data(), &QDialog::finished, aboutDialog.data(), &QObject::deleteLater); WindowSystem::self().skipTaskBar(*aboutDialog); aboutDialog->show(); } int DockCorona::noOfDocks() { return m_dockViews.count(); } QList DockCorona::freeEdges(QScreen *screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; for (auto *view : m_dockViews) { if (view && view->currentScreen() == screen->name()) { edges.removeOne(view->location()); } } return edges; } QList DockCorona::freeEdges(int screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; QScreen *scr = m_screenPool->screenForId(screen); for (auto *view : m_dockViews) { if (view && scr && view->currentScreen() == scr->name()) { edges.removeOne(view->location()); } } return edges; } int DockCorona::screenForContainment(const Plasma::Containment *containment) const { //FIXME: indexOf is not a proper way to support multi-screen // as for environment to environment the indexes change // also there is the following issue triggered // from dockView adaptToScreen() // // in a multi-screen environment that // primary screen is not set to 0 it was // created an endless showing loop at // startup (catch-up race) between // screen:0 and primaryScreen //case in which this containment is child of an applet, hello systray :) if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { if (Plasma::Containment *cont = parentApplet->containment()) { return screenForContainment(cont); } else { return -1; } } //if the panel views already exist, base upon them DockView *view = m_dockViews.value(containment); if (view && view->screen()) { return m_screenPool->id(view->screen()->name()); } //Failed? fallback on lastScreen() //lastScreen() is the correct screen for panels //It is also correct for desktops *that have the correct activity()* //a containment with lastScreen() == 0 but another activity, //won't be associated to a screen // qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); for (auto screen : qGuiApp->screens()) { // containment->lastScreen() == m_screenPool->id(screen->name()) to check if the lastScreen refers to a screen that exists/it's known if (containment->lastScreen() == m_screenPool->id(screen->name()) && (containment->activity() == m_activityConsumer->currentActivity() || containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { return containment->lastScreen(); } } return -1; } void DockCorona::addDock(Plasma::Containment *containment, int expDockScreen) { if (!containment || !containment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto metadata = containment->kPackage().metadata(); if (metadata.pluginId() != "org.kde.latte.containment") return; for (auto *dock : m_dockViews) { if (dock->containment() == containment) return; } QScreen *nextScreen{qGuiApp->primaryScreen()}; //! forceDockLoading is used when a latte configuration based on the //! current running screens does not provide a dock containing tasks. //! in such case the lowest latte containment containing tasks is loaded //! and it forcefully becomes primary dock bool forceDockLoading = false; if (!m_tasksWillBeLoaded && m_firstContainmentWithTasks == static_cast(containment->id())) { m_tasksWillBeLoaded = true; //this protects by loading more than one dock at startup forceDockLoading = true; } bool onPrimary = containment->config().readEntry("onPrimary", true); int id = containment->screen(); if (id == -1 && expDockScreen == -1) { id = containment->lastScreen(); } if (expDockScreen > -1) { id = expDockScreen; } qDebug() << "add dock - containment id: " << containment->id() << " ,screen id : " << id << " ,onprimary:" << onPrimary << " ,forceDockLoad:" << forceDockLoading; if (id >= 0 && !onPrimary && !forceDockLoading) { QString connector = m_screenPool->connector(id); qDebug() << "add dock - connector : " << connector; bool found{false}; foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == connector) { found = true; nextScreen = scr; break; } } if (!found) { qDebug() << "adding dock rejected, screen not available : " << connector; return; } } else if (onPrimary) { if (explicitDockOccupyEdge(primaryScreenId(), containment->location())) { //we must check that an onPrimary dock should never catch up the same edge on //the same screen with an explicit dock return; } } qDebug() << "Adding dock for container..."; qDebug() << "onPrimary: " << onPrimary << "screen!!! :" << nextScreen->name(); //! it is used to set the correct flag during the creation //! of the window... This of course is also used during //! recreations of the window between different visibility modes auto mode = static_cast(containment->config().readEntry("visibility", static_cast(Dock::DodgeActive))); bool dockWin{true}; if (mode == Dock::AlwaysVisible || mode == Dock::WindowsGoBelow) { dockWin = true; } else { dockWin = containment->config().readEntry("dockWindowBehavior", true); } auto dockView = new DockView(this, nextScreen, dockWin); dockView->init(); dockView->setContainment(containment); //! force this special dock case to become primary //! even though it isnt if (forceDockLoading) { dockView->setOnPrimary(true); } connect(containment, &QObject::destroyed, this, &DockCorona::dockContainmentDestroyed); connect(containment, &Plasma::Applet::destroyedChanged, this, &DockCorona::destroyedChanged); connect(containment, &Plasma::Applet::locationChanged, this, &DockCorona::dockLocationChanged); connect(containment, &Plasma::Containment::appletAlternativesRequested , this, &DockCorona::showAlternativesForApplet, Qt::QueuedConnection); //! Qt 5.9 creates a crash for this in wayland, that is why the check is used //! but on the other hand we need this for copy to work correctly and show //! the copied dock under X11 //if (!KWindowSystem::isPlatformWayland()) { dockView->show(); //} m_dockViews[containment] = dockView; emit docksCountChanged(); } bool DockCorona::explicitDockOccupyEdge(int screen, Plasma::Types::Location location) const { foreach (auto containment, containments()) { bool onPrimary = containment->config().readEntry("onPrimary", true); int id = containment->lastScreen(); Plasma::Types::Location contLocation = containment->location(); if (!onPrimary && id == screen && contLocation == location) { return true; } } return false; } void DockCorona::recreateDock(Plasma::Containment *containment) { //! give the time to config window to close itself first and then recreate the dock //! step:1 remove the dockview QTimer::singleShot(350, [this, containment]() { auto view = m_dockViews.take(containment); if (view) { qDebug() << "recreate - step 1: removing dock for containment:" << containment->id(); //! step:2 add the new dockview connect(view, &QObject::destroyed, this, [this, containment]() { QTimer::singleShot(250, this, [this, containment]() { if (!m_dockViews.contains(containment)) { qDebug() << "recreate - step 2: adding dock for containment:" << containment->id(); addDock(containment); } }); }); view->deleteLater(); } }); } void DockCorona::destroyedChanged(bool destroyed) { qDebug() << "dock containment destroyed changed!!!!"; Plasma::Containment *sender = qobject_cast(QObject::sender()); if (!sender) { return; } if (destroyed) { m_waitingDockViews[sender] = m_dockViews.take(static_cast(sender)); } else { m_dockViews[sender] = m_waitingDockViews.take(static_cast(sender)); } emit docksCountChanged(); } void DockCorona::dockContainmentDestroyed(QObject *cont) { qDebug() << "dock containment destroyed!!!!"; auto view = m_dockViews.take(static_cast(cont)); if (!view) { view = m_waitingDockViews.take(static_cast(cont)); } if (view) { view->deleteLater(); } emit docksCountChanged(); } void DockCorona::showAlternativesForApplet(Plasma::Applet *applet) { const QString alternativesQML = kPackage().filePath("appletalternativesui"); if (alternativesQML.isEmpty()) { return; } DockView *dockView = m_dockViews[applet->containment()]; KDeclarative::QmlObject *qmlObj{nullptr}; if (dockView) { dockView->setAlternativesIsShown(true); qmlObj = new KDeclarative::QmlObject(dockView); } else { qmlObj = new KDeclarative::QmlObject(this); } qmlObj->setInitializationDelayed(true); qmlObj->setSource(QUrl::fromLocalFile(alternativesQML)); AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); m_alternativesObjects << qmlObj; qmlObj->completeInitialization(); //! Alternative dialog signals connect(helper, &QObject::destroyed, this, [dockView]() { dockView->setAlternativesIsShown(false); }); connect(qmlObj->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(alternativesVisibilityChanged(bool))); connect(applet, &Plasma::Applet::destroyedChanged, this, [this, qmlObj](bool destroyed) { if (!destroyed) { return; } QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj == qmlObj) { it.remove(); obj->deleteLater(); } } }); } void DockCorona::alternativesVisibilityChanged(bool visible) { if (visible) { return; } QObject *root = sender(); QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObject *obj = it.next(); if (obj->rootObject() == root) { it.remove(); obj->deleteLater(); } } } void DockCorona::loadDefaultLayout() { qDebug() << "loading default layout"; //! Settting mutable for create a containment setImmutability(Plasma::Types::Mutable); QVariantList args; auto defaultContainment = createContainmentDelayed("org.kde.latte.containment", args); defaultContainment->setContainmentType(Plasma::Types::PanelContainment); defaultContainment->init(); if (!defaultContainment || !defaultContainment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto config = defaultContainment->config(); defaultContainment->restore(config); QList edges = freeEdges(defaultContainment->screen()); if ((edges.count() > 0)) { defaultContainment->setLocation(edges.at(0)); } else { defaultContainment->setLocation(Plasma::Types::BottomEdge); } defaultContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); defaultContainment->save(config); requestConfigSync(); defaultContainment->flushPendingConstraintsEvents(); emit containmentAdded(defaultContainment); emit containmentCreated(defaultContainment); addDock(defaultContainment); defaultContainment->createApplet(QStringLiteral("org.kde.latte.plasmoid")); defaultContainment->createApplet(QStringLiteral("org.kde.plasma.analogclock")); } void DockCorona::copyDock(Plasma::Containment *containment) { if (!containment) return; qDebug() << "copying containment layout"; //! Settting mutable for create a containment setImmutability(Plasma::Types::Mutable); QStringList toCopyContainmentIds; QStringList toCopyAppletIds; QString temp1File = QDir::homePath() + "/.config/lattedock.copy1.bak"; QString temp2File = QDir::homePath() + "/.config/lattedock.copy2.bak"; //! WE NEED A WAY TO COPY A CONTAINMENT!!!! QFile copyFile(temp1File); QFile copyFile2(temp2File); if (copyFile.exists()) copyFile.remove(); if (copyFile2.exists()) copyFile2.remove(); KSharedConfigPtr newFile = KSharedConfig::openConfig(QDir::homePath() + "/.config/lattedock.copy1.bak"); KConfigGroup copied_conts = KConfigGroup(newFile, "Containments"); KConfigGroup copied_c1 = KConfigGroup(&copied_conts, QString::number(containment->id())); KConfigGroup copied_systray; toCopyContainmentIds << QString::number(containment->id()); toCopyAppletIds << containment->config().group("Applets").groupList(); containment->config().copyTo(&copied_c1); //!investigate if there is a systray in the containment to copy also int systrayId = -1; QString systrayAppletId; auto applets = containment->config().group("Applets"); foreach (auto applet, applets.groupList()) { KConfigGroup appletSettings = applets.group(applet).group("Configuration"); int tSysId = appletSettings.readEntry("SystrayContainmentId", "-1").toInt(); if (tSysId != -1) { systrayId = tSysId; systrayAppletId = applet; qDebug() << "systray was found in the containment..."; break; } } if (systrayId != -1) { Plasma::Containment *systray{nullptr}; foreach (auto containment, containments()) { if (containment->id() == systrayId) { systray = containment; break; } } if (systray) { copied_systray = KConfigGroup(&copied_conts, QString::number(systray->id())); toCopyContainmentIds << QString::number(systray->id()); toCopyAppletIds << systray->config().group("Applets").groupList(); systray->config().copyTo(&copied_systray); } } //! end of systray specific code //! BEGIN updating the ids in the temp file QStringList allIds; allIds << containmentsIds(); allIds << appletsIds(); //qDebug() << "Ids:" << allIds; //qDebug() << "to copy containments: " << toCopyContainmentIds; //qDebug() << "to copy applets: " << toCopyAppletIds; QStringList assignedIds; QHash assigned; foreach (auto contId, toCopyContainmentIds) { QString newId = availableId(allIds, assignedIds, 12); assignedIds << newId; assigned[contId] = newId; } foreach (auto appId, toCopyAppletIds) { QString newId = availableId(allIds, assignedIds, 40); assignedIds << newId; assigned[appId] = newId; } qDebug() << "full assignments ::: " << assigned; QString order1 = copied_c1.group("General").readEntry("appletOrder", QString()); QStringList order1Ids = order1.split(";"); QStringList fixedOrder1Ids; //qDebug() << "order1 :: " << order1; for (int i = 0; i < order1Ids.count(); ++i) { fixedOrder1Ids.append(assigned[order1Ids[i]]); } QString fixedOrder1 = fixedOrder1Ids.join(";"); //qDebug() << "fixed order ::: " << fixedOrder1; copied_c1.group("General").writeEntry("appletOrder", fixedOrder1); //! must update also the systray id in its applet if (systrayId > -1) { copied_c1.group("Applets").group(systrayAppletId).group("Configuration").writeEntry("SystrayContainmentId", assigned[QString::number(systrayId)]); copied_systray.sync(); } copied_c1.sync(); QFile(temp1File).copy(temp2File); QFile f(temp2File); if (!f.open(QFile::ReadOnly)) { qDebug() << "temp file couldnt be opened..."; return; } QTextStream in(&f); QString fileText = in.readAll(); foreach (auto contId, toCopyContainmentIds) { fileText = fileText.replace("[Containments][" + contId + "]", "[Containments][" + assigned[contId] + "]"); } foreach (auto appId, toCopyAppletIds) { fileText = fileText.replace("][Applets][" + appId + "]", "][Applets][" + assigned[appId] + "]"); } f.close(); if (!f.open(QFile::WriteOnly)) { qDebug() << "temp file couldnt be opened for writing..."; return; } QTextStream outputStream(&f); outputStream << fileText; f.close(); //! END of updating the ids in the temp file //! Finally import the configuration KSharedConfigPtr newFile2 = KSharedConfig::openConfig(QDir::homePath() + "/.config/lattedock.copy2.bak"); auto nConts = importLayout(KConfigGroup(newFile2, "")); ///Find latte and systray containments qDebug() << " imported containments ::: " << nConts.length(); Plasma::Containment *newContainment{nullptr}; int newSystrayId = -1; foreach (auto containment, nConts) { KPluginMetaData meta = containment->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.containment") { qDebug() << "new latte containment id: " << containment->id(); newContainment = containment; } else if (meta.pluginId() == "org.kde.plasma.private.systemtray") { qDebug() << "new systray containment id: " << containment->id(); newSystrayId = containment->id(); } } if (!newContainment) return; ///after systray was found we must update in latte the relevant id if (newSystrayId != -1) { applets = newContainment->config().group("Applets"); qDebug() << "systray found with id : " << newSystrayId << " and applets in the containment :" << applets.groupList().count(); foreach (auto applet, applets.groupList()) { KConfigGroup appletSettings = applets.group(applet).group("Configuration"); if (appletSettings.hasKey("SystrayContainmentId")) { qDebug() << "!!! updating systray id to : " << newSystrayId; appletSettings.writeEntry("SystrayContainmentId", newSystrayId); } } } if (!newContainment || !newContainment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto config = newContainment->config(); //in multi-screen environment the copied dock is moved to alternative screens first const auto screens = qGuiApp->screens(); auto dock = m_dockViews[containment]; bool setOnExplicitScreen = false; int dockScrId = -1; int copyScrId = -1; if (dock) { dockScrId = m_screenPool->id(dock->currentScreen()); qDebug() << "COPY DOCK SCREEN ::: " << dockScrId; if (dockScrId != -1 && screens.count() > 1) { foreach (auto scr, screens) { copyScrId = m_screenPool->id(scr->name()); //the screen must exist and not be the same with the original dock if (copyScrId > -1 && copyScrId != dockScrId) { QList fEdges = freeEdges(copyScrId); if (fEdges.contains((Plasma::Types::Location)containment->location())) { ///set this containment to an explicit screen config.writeEntry("onPrimary", false); config.writeEntry("lastScreen", copyScrId); newContainment->setLocation(containment->location()); qDebug() << "COPY DOCK SCREEN NEW SCREEN ::: " << copyScrId; setOnExplicitScreen = true; break; } } } } } if (!setOnExplicitScreen) { QList edges = freeEdges(newContainment->screen()); if (edges.count() > 0) { newContainment->setLocation(edges.at(0)); } else { newContainment->setLocation(Plasma::Types::BottomEdge); } config.writeEntry("onPrimary", false); config.writeEntry("lastScreen", dockScrId); } newContainment->config().sync(); if (setOnExplicitScreen && copyScrId > -1) { qDebug() << "Copy Dock in explicit screen ::: " << copyScrId; addDock(newContainment, copyScrId); newContainment->reactToScreenChange(); } else { qDebug() << "Copy Dock in current screen..."; addDock(newContainment, dockScrId); } } QString DockCorona::availableId(QStringList all, QStringList assigned, int base) { bool found = false; int i = base; while (!found && i < 30000) { QString iStr = QString::number(i); if (!all.contains(iStr) && !assigned.contains(iStr)) { return iStr; } i++; } return QString(""); } QStringList DockCorona::containmentsIds() { auto containmentsEntries = config()->group("Containments"); return containmentsEntries.groupList(); } QStringList DockCorona::appletsIds() { QStringList ids; auto containmentsEntries = config()->group("Containments"); foreach (auto cId, containmentsEntries.groupList()) { auto appletsEntries = containmentsEntries.group(cId).group("Applets"); ids << appletsEntries.groupList(); } return ids; } //! This function figures in the beginning if a dock with tasks //! in it will be loaded taking into account also the screens are present. bool DockCorona::heuresticForLoadingDockWithTasks() { foreach (auto containment, containments()) { QString plugin = containment->pluginMetaData().pluginId(); if (plugin == "org.kde.latte.containment") { bool onPrimary = containment->config().readEntry("onPrimary", true); int lastScreen = containment->lastScreen(); qDebug() << "containment values: " << onPrimary << " - " << lastScreen; bool containsTasks = false; foreach (auto applet, containment->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { containsTasks = true; break; } } if (containsTasks) { m_firstContainmentWithTasks = containment->id(); if (onPrimary) { return true; } else { if (lastScreen >= 0) { QString connector = m_screenPool->connector(lastScreen); foreach (auto scr, qGuiApp->screens()) { if (scr && scr->name() == connector) { return true; break; } } } } } } } return false; } //! Activate launcher menu through dbus interface void DockCorona::activateLauncherMenu() { m_globalShortcuts->activateLauncherMenu(); } //! update badge for specific dock item void DockCorona::updateDockItemBadge(QString identifier, QString value) { m_globalShortcuts->updateDockItemBadge(identifier, value); } inline void DockCorona::qmlRegisterTypes() const { qmlRegisterType(); } } diff --git a/app/layoutconfigdialog.cpp b/app/layoutconfigdialog.cpp index 02d51d88..918983da 100644 --- a/app/layoutconfigdialog.cpp +++ b/app/layoutconfigdialog.cpp @@ -1,937 +1,937 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "ui_layoutconfigdialog.h" #include "layoutconfigdialog.h" #include "layoutsettings.h" #include "layoutsDelegates/checkboxdelegate.h" #include "layoutsDelegates/colorcmbboxdelegate.h" #include "layoutsDelegates/activitycmbboxdelegate.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Latte { const int IDCOLUMN = 0; const int COLORCOLUMN = 1; const int NAMECOLUMN = 2; const int MENUCOLUMN = 3; const int ACTIVITYCOLUMN = 4; const QChar CheckMark{0x2714}; LayoutConfigDialog::LayoutConfigDialog(QWidget *parent, LayoutManager *manager) : QDialog(parent), ui(new Ui::LayoutConfigDialog), m_manager(manager) { ui->setupUi(this); setWindowTitle(i18n("Layouts Editor")); setAttribute(Qt::WA_DeleteOnClose, true); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); resize(m_manager->corona()->universalSettings()->layoutsWindowSize()); connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked , this, &LayoutConfigDialog::apply); connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked , this, &LayoutConfigDialog::restoreDefaults); m_model = new QStandardItemModel(manager->layouts().count(), 5, this); ui->layoutsView->setModel(m_model); ui->layoutsView->setSelectionBehavior(QAbstractItemView::SelectRows); ui->layoutsView->horizontalHeader()->setStretchLastSection(true); ui->layoutsView->verticalHeader()->setVisible(false); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); connect(m_manager, &LayoutManager::currentLayoutNameChanged, this, &LayoutConfigDialog::currentLayoutNameChanged); loadLayouts(); QString iconsPath(m_manager->corona()->kPackage().path() + "../../plasmoids/org.kde.latte.containment/contents/icons/"); //!find the available colors QDir layoutDir(iconsPath); QStringList filter; filter.append(QString("*print.jpg")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); QStringList colors; foreach (auto file, files) { int colorEnd = file.lastIndexOf("print.jpg"); QString color = file.remove(colorEnd, 9); colors.append(color); } ui->layoutsView->setItemDelegateForColumn(COLORCOLUMN, new ColorCmbBoxDelegate(this, iconsPath, colors)); ui->layoutsView->setItemDelegateForColumn(MENUCOLUMN, new CheckBoxDelegate(this)); ui->layoutsView->setItemDelegateForColumn(ACTIVITYCOLUMN, new ActivityCmbBoxDelegate(this)); ui->newButton->setText(i18nc("new button", "New")); ui->copyButton->setText(i18nc("copy button", "Copy")); ui->removeButton->setText(i18nc("remove button", "Remove")); ui->switchButton->setText(i18nc("switch button", "Switch")); ui->importButton->setText(i18nc("import button", "Import")); ui->exportButton->setText(i18nc("export button", "Export")); connect(m_model, &QStandardItemModel::itemChanged, this, &LayoutConfigDialog::itemChanged); connect(ui->layoutsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LayoutConfigDialog::currentRowChanged); } LayoutConfigDialog::~LayoutConfigDialog() { qDebug() << Q_FUNC_INFO; qDeleteAll(m_layouts); if (m_model) { delete m_model; } if (m_manager && m_manager->corona() && m_manager->corona()->universalSettings()) { m_manager->corona()->universalSettings()->setLayoutsWindowSize(size()); } foreach (auto tempDir, m_tempDirectories) { QDir tDir(tempDir); if (tDir.exists() && tempDir.startsWith("/tmp/")) { tDir.removeRecursively(); } } } QStringList LayoutConfigDialog::activities() { return m_manager->activities(); } QStringList LayoutConfigDialog::availableActivities() { return m_availableActivities; } void LayoutConfigDialog::on_newButton_clicked() { qDebug() << Q_FUNC_INFO; //! find Default preset path foreach (auto preset, m_manager->presetsPaths()) { QString presetName = LayoutSettings::layoutName(preset); if (presetName == "Default") { QByteArray presetNameChars = presetName.toUtf8(); const char *prset_str = presetNameChars.data(); presetName = uniqueLayoutName(i18n(prset_str)); addLayoutForFile(preset, presetName, true, false); break; } } } void LayoutConfigDialog::on_copyButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } QString tempDir = uniqueTempDirectory(); QString id = m_model->data(m_model->index(row, IDCOLUMN), Qt::DisplayRole).toString(); QString color = m_model->data(m_model->index(row, COLORCOLUMN), Qt::BackgroundRole).toString(); QString layoutName = uniqueLayoutName(m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole).toString()); bool menu = m_model->data(m_model->index(row, MENUCOLUMN), Qt::DisplayRole).toString() == CheckMark; QString copiedId = tempDir + "/" + layoutName + ".layout.latte"; QFile(id).copy(copiedId); LayoutSettings *settings = new LayoutSettings(this, copiedId); m_layouts[copiedId] = settings; insertLayoutInfoAtRow(row + 1, copiedId, color, layoutName, menu, QStringList()); ui->layoutsView->selectRow(row + 1); } void LayoutConfigDialog::on_removeButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } QString layoutName = m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole).toString(); if (layoutName == m_manager->currentLayoutName()) { return; } m_model->removeRow(row); ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); row = qMax(row - 1, 0); ui->layoutsView->selectRow(row); } void LayoutConfigDialog::on_importButton_clicked() { qDebug() << Q_FUNC_INFO; QFileDialog *fileDialog = new QFileDialog(this, i18nc("import layout/configuration", "Import Layout/Configuration") , QDir::homePath() , QStringLiteral("layout.latte")); fileDialog->setFileMode(QFileDialog::AnyFile); fileDialog->setAcceptMode(QFileDialog::AcceptOpen); fileDialog->setDefaultSuffix("layout.latte"); QStringList filters; filters << QString(i18nc("import latte layout", "Latte Dock Layout file v0.2") + "(*.layout.latte)") << QString(i18nc("import latte layouts/configuration", "Latte Dock Full Configuration file (v0.1, v0.2)") + "(*.latterc)"); fileDialog->setNameFilters(filters); connect(fileDialog, &QFileDialog::finished , fileDialog, &QFileDialog::deleteLater); connect(fileDialog, &QFileDialog::fileSelected , this, [&](const QString & file) { Importer::LatteFileVersion version = Importer::fileVersion(file); qDebug() << "VERSION :::: " << version; if (version == Importer::LayoutVersion2) { addLayoutForFile(file); } else if (version == Importer::ConfigVersion1) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Import: Configuration file version v0.1")); msg->setText( - i18n("You are going to import an old version v0.1 configuration file.
Be careful, importing the entire configuration will erase all your current configuration!!!.

Alternative, you can import safely from this file
only the contained layouts...
")); + i18n("You are going to import an old version v0.1 configuration file.
Be careful, importing the entire configuration will erase all your current configuration!!!

Alternative, you can import safely from this file
only the contained layouts...
")); msg->setStandardButtons(QMessageBox::Cancel); QPushButton *fullBtn = new QPushButton(msg); QPushButton *layoutsBtn = new QPushButton(msg); fullBtn->setText(i18nc("import full configuration", "Full Configuration")); fullBtn->setIcon(QIcon::fromTheme("settings")); layoutsBtn->setText(i18nc("import only the layouts", "Only Layouts")); layoutsBtn->setIcon(QIcon::fromTheme("user-identity")); msg->addButton(fullBtn, QMessageBox::AcceptRole); msg->addButton(layoutsBtn, QMessageBox::AcceptRole); msg->setDefaultButton(layoutsBtn); connect(msg, &QMessageBox::finished, msg, &QMessageBox::deleteLater); msg->open(); connect(layoutsBtn, &QPushButton::clicked , this, [ &, file](bool check) { importLayoutsFromV1ConfigFile(file); }); connect(fullBtn, &QPushButton::clicked , this, [ &, file](bool check) { //!NOTE: Restart latte for import the new configuration QProcess::startDetached(qGuiApp->applicationFilePath() + " --import \"" + file + "\""); qGuiApp->exit(); }); } else if (version == Importer::ConfigVersion2) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Import: Configuration file version v0.2")); msg->setText( i18n("You are going to import a v0.2 configuration file.
Be careful, importing will erase all your current configuration!!!

Would you like to proceed?")); msg->setStandardButtons(QMessageBox::Yes | QMessageBox::No); msg->setDefaultButton(QMessageBox::No); connect(msg, &QMessageBox::finished, this, [ &, msg, file](int result) { if (result == QMessageBox::Yes) { //!NOTE: Restart latte for import the new configuration msg->deleteLater(); QProcess::startDetached(qGuiApp->applicationFilePath() + " --import \"" + file + "\""); qGuiApp->exit(); } }); msg->open(); } }); fileDialog->open(); } bool LayoutConfigDialog::importLayoutsFromV1ConfigFile(QString file) { KTar archive(file, QStringLiteral("application/x-tar")); archive.open(QIODevice::ReadOnly); //! if the file isnt a tar archive if (archive.isOpen()) { QDir tempDir{uniqueTempDirectory()}; const auto archiveRootDir = archive.directory(); foreach (auto &name, archiveRootDir->entries()) { auto fileEntry = archiveRootDir->file(name); fileEntry->copyTo(tempDir.absolutePath()); } QString name = Importer::nameOfConfigFile(file); QString applets(tempDir.absolutePath() + "/" + "lattedock-appletsrc"); if (QFile(applets).exists()) { if (m_manager->importer()->importOldLayout(applets, name, false, tempDir.absolutePath())) { addLayoutForFile(tempDir.absolutePath() + "/" + name + ".layout.latte", name, false); } QString alternativeName = name + "-" + i18nc("layout", "Alternative"); if (m_manager->importer()->importOldLayout(applets, alternativeName, false, tempDir.absolutePath())) { addLayoutForFile(tempDir.absolutePath() + "/" + alternativeName + ".layout.latte", alternativeName, false); } } return true; } return false; } void LayoutConfigDialog::on_exportButton_clicked() { int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } QString layoutExported = m_model->data(m_model->index(row, IDCOLUMN), Qt::DisplayRole).toString(); qDebug() << Q_FUNC_INFO; QFileDialog *fileDialog = new QFileDialog(this, i18nc("export layout/configuration", "Export Layout/Configuration") , QDir::homePath() , QStringLiteral("layout.latte")); fileDialog->setFileMode(QFileDialog::AnyFile); fileDialog->setAcceptMode(QFileDialog::AcceptSave); fileDialog->setDefaultSuffix("layout.latte"); QStringList filters; QString filter1(i18nc("export layout", "Latte Dock Layout file v0.2") + "(*.layout.latte)"); QString filter2(i18nc("export full configuraion", "Latte Dock Full Configuration file v0.2") + "(*.latterc)"); filters << filter1 << filter2; fileDialog->setNameFilters(filters); connect(fileDialog, &QFileDialog::finished , fileDialog, &QFileDialog::deleteLater); connect(fileDialog, &QFileDialog::fileSelected , this, [ &, layoutExported](const QString & file) { auto showNotificationError = []() { auto notification = new KNotification("export-fail", KNotification::CloseOnTimeout); notification->setText(i18nc("export layout", "Failed to export layout")); notification->sendEvent(); }; if (QFile::exists(file) && !QFile::remove(file)) { showNotificationError(); return; } if (file.endsWith(".layout.latte")) { if (!QFile(layoutExported).copy(file)) { showNotificationError(); return; } LayoutSettings layoutS(this, file); layoutS.setActivities(QStringList()); //NOTE: The pointer is automatically deleted when the event is closed auto notification = new KNotification("export-done", KNotification::CloseOnTimeout); notification->setActions({i18nc("export layout", "Open location")}); notification->setText(i18nc("export layout", "Layout exported successfully")); connect(notification, &KNotification::action1Activated , this, [file]() { QDesktopServices::openUrl({QFileInfo(file).canonicalPath()}); }); notification->sendEvent(); } else if (file.endsWith(".latterc")) { auto showNotificationError = []() { auto notification = new KNotification("export-fail", KNotification::CloseOnTimeout); notification->setText(i18nc("import/export config", "Failed to export configuration")); notification->sendEvent(); }; if (m_manager->importer()->exportFullConfiguration(file)) { auto notification = new KNotification("export-done", KNotification::CloseOnTimeout); notification->setActions({i18nc("import/export config", "Open location")}); notification->setText(i18nc("import/export config", "Full Configuration exported successfully")); connect(notification, &KNotification::action1Activated , this, [file]() { QDesktopServices::openUrl({QFileInfo(file).canonicalPath()}); }); notification->sendEvent(); } else { showNotificationError(); } } }); fileDialog->open(); } void LayoutConfigDialog::accept() { qDebug() << Q_FUNC_INFO; //setVisible(false); if (saveAllChanges()) { deleteLater(); } } void LayoutConfigDialog::reject() { qDebug() << Q_FUNC_INFO; deleteLater(); } void LayoutConfigDialog::apply() { qDebug() << Q_FUNC_INFO; saveAllChanges(); updateButtonsState(); } void LayoutConfigDialog::restoreDefaults() { qDebug() << Q_FUNC_INFO; foreach (auto preset, m_manager->presetsPaths()) { QString presetName = LayoutSettings::layoutName(preset); QByteArray presetNameChars = presetName.toUtf8(); const char *prset_str = presetNameChars.data(); presetName = i18n(prset_str); if (!nameExistsInModel(presetName)) { addLayoutForFile(preset, presetName); } } } void LayoutConfigDialog::addLayoutForFile(QString file, QString layoutName, bool newTempDirectory, bool showNotification) { if (layoutName.isEmpty()) { layoutName = LayoutSettings::layoutName(file); } QString copiedId; if (newTempDirectory) { QString tempDir = uniqueTempDirectory(); copiedId = tempDir + "/" + layoutName + ".layout.latte"; QFile(file).copy(copiedId); } else { copiedId = file; } LayoutSettings *settings = new LayoutSettings(this, copiedId); m_layouts[copiedId] = settings; QString id = copiedId; QString color = settings->color(); bool menu = settings->showInMenu(); layoutName = uniqueLayoutName(layoutName); int row = ascendingRowFor(layoutName); insertLayoutInfoAtRow(row, copiedId, color, layoutName, menu, QStringList()); ui->layoutsView->selectRow(row); if (showNotification) { //NOTE: The pointer is automatically deleted when the event is closed auto notification = new KNotification("import-done", KNotification::CloseOnTimeout); notification->setText(i18nc("import-done", "Layout: %0 imported successfully\n").arg(layoutName)); notification->sendEvent(); } } void LayoutConfigDialog::loadLayouts() { m_initLayoutPaths.clear(); m_model->clear(); int i = 0; QStringList brokenLayouts; foreach (auto layout, m_manager->layouts()) { QString layoutPath = QDir::homePath() + "/.config/latte/" + layout + ".layout.latte"; m_initLayoutPaths.append(layoutPath); LayoutSettings *layoutSets = new LayoutSettings(this, layoutPath); m_layouts[layoutPath] = layoutSets; insertLayoutInfoAtRow(i, layoutPath, layoutSets->color(), layoutSets->name(), layoutSets->showInMenu(), layoutSets->activities()); qDebug() << "counter:" << i << " total:" << m_model->rowCount(); i++; if (layoutSets->name() == m_manager->currentLayoutName()) { ui->layoutsView->selectRow(i - 1); } if (layoutSets->fileIsBroken()) { brokenLayouts.append(layoutSets->name()); } } recalculateAvailableActivities(); m_model->setHorizontalHeaderItem(IDCOLUMN, new QStandardItem(QString("#path"))); m_model->setHorizontalHeaderItem(COLORCOLUMN, new QStandardItem(QString(i18n("Color")))); m_model->setHorizontalHeaderItem(NAMECOLUMN, new QStandardItem(QString(i18n("Name")))); m_model->setHorizontalHeaderItem(MENUCOLUMN, new QStandardItem(QString(i18n("In Menu")))); m_model->setHorizontalHeaderItem(ACTIVITYCOLUMN, new QStandardItem(QString(i18n("Activities")))); //! this line should be commented for debugging layouts window functionality ui->layoutsView->setColumnHidden(IDCOLUMN, true); //! there are broken layouts and the user must be informed! if (brokenLayouts.count() > 0) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Layout Warning")); - msg->setText(i18n("The layout(s) %0 have broken configuration!!! Please remove them to improve the system stability...").arg(brokenLayouts.join(","))); + msg->setText(i18n("The layout(s) %0 have broken configuration!!! Please remove them to improve the system stability...").arg(brokenLayouts.join(","))); msg->setStandardButtons(QMessageBox::Ok); msg->open(); } } void LayoutConfigDialog::insertLayoutInfoAtRow(int row, QString path, QString color, QString name, bool menu, QStringList activities) { QStandardItem *pathItem = new QStandardItem(path); QStandardItem *colorItem = new QStandardItem(); colorItem->setSelectable(false); QStandardItem *nameItem = new QStandardItem(name); nameItem->setTextAlignment(Qt::AlignCenter); QStandardItem *menuItem = new QStandardItem(); menuItem->setEditable(false); menuItem->setSelectable(true); menuItem->setText(menu ? CheckMark : QString()); menuItem->setTextAlignment(Qt::AlignCenter); QStandardItem *activitiesItem = new QStandardItem(activities.join(",")); QList items; items.append(pathItem); items.append(colorItem); items.append(nameItem); items.append(menuItem); items.append(activitiesItem); if (row > m_model->rowCount() - 1) { m_model->appendRow(items); row = m_model->rowCount() - 1; qDebug() << "append row at:" << row << " rows:" << m_model->rowCount(); } else { m_model->insertRow(row, items); qDebug() << "insert row at:" << row << " rows:" << m_model->rowCount(); } m_model->setData(m_model->index(row, IDCOLUMN), path, Qt::DisplayRole); m_model->setData(m_model->index(row, COLORCOLUMN), color, Qt::BackgroundRole); QFont font; if (name == m_manager->currentLayoutName()) { font.setBold(true); } else { font.setBold(false); } if (path.startsWith("/tmp/")) { font.setItalic(true); } else { font.setItalic(false); } m_model->setData(m_model->index(row, NAMECOLUMN), QVariant(name), Qt::DisplayRole); m_model->setData(m_model->index(row, NAMECOLUMN), font, Qt::FontRole); m_model->setData(m_model->index(row, ACTIVITYCOLUMN), activities, Qt::UserRole); } void LayoutConfigDialog::on_switchButton_clicked() { QVariant value = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), NAMECOLUMN), Qt::DisplayRole); if (value.isValid()) { m_manager->switchToLayout(value.toString()); } else { qDebug() << "not valid layout"; } } void LayoutConfigDialog::currentLayoutNameChanged() { for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex nameIndex = m_model->index(i, NAMECOLUMN); QVariant value = m_model->data(nameIndex); if (value.isValid()) { QFont font; if (m_manager->currentLayoutName() == value.toString()) { font.setBold(true); ui->layoutsView->selectRow(i); } else { font.setBold(false); } m_model->setData(nameIndex, font, Qt::FontRole); } } } void LayoutConfigDialog::itemChanged(QStandardItem *item) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); if (item->column() == ACTIVITYCOLUMN) { //! recalculate the available activities recalculateAvailableActivities(); } } void LayoutConfigDialog::currentRowChanged(const QModelIndex ¤t, const QModelIndex &previous) { updateButtonsState(); } void LayoutConfigDialog::updateButtonsState() { QString id = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), IDCOLUMN), Qt::DisplayRole).toString(); if (m_layouts[id]->name() == m_manager->currentLayoutName()) { ui->removeButton->setEnabled(false); } else { ui->removeButton->setEnabled(true); } if (id.startsWith("/tmp/")) { ui->switchButton->setEnabled(false); } else { ui->switchButton->setEnabled(true); } } void LayoutConfigDialog::recalculateAvailableActivities() { QStringList tempActivities = m_manager->activities(); for (int i = 0; i < m_model->rowCount(); ++i) { QStringList assigned = m_model->data(m_model->index(i, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); foreach (auto activity, assigned) { if (tempActivities.contains(activity)) { tempActivities.removeAll(activity); } } } m_availableActivities = tempActivities; } bool LayoutConfigDialog::dataAreAccepted() { for (int i = 0; i < m_model->rowCount(); ++i) { QString layout1 = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); for (int j = i + 1; j < m_model->rowCount(); ++j) { QString temp = m_model->data(m_model->index(j, NAMECOLUMN), Qt::DisplayRole).toString(); //!same layout name exists again if (layout1 == temp) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Layout Warning")); msg->setText(i18n("There are layouts with the same name, that is not permitted!!! Please update these names to re-apply the changes...")); msg->setStandardButtons(QMessageBox::Ok); connect(msg, &QMessageBox::finished, this, [ &, i, j](int result) { QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect; QModelIndex indexBase = m_model->index(i, NAMECOLUMN); ui->layoutsView->selectionModel()->select(indexBase, flags); QModelIndex indexOccurence = m_model->index(j, NAMECOLUMN); ui->layoutsView->edit(indexOccurence); }); msg->open(); return false; } } } return true; } bool LayoutConfigDialog::saveAllChanges() { if (!dataAreAccepted()) { return false; } QStringList knownActivities = activities(); QTemporaryDir layoutTempDir; qDebug() << "Temporary Directory ::: " << layoutTempDir.path(); QStringList fromRenamePaths; QStringList toRenamePaths; QStringList toRenameNames; QString switchToLayout; for (int i = 0; i < m_model->rowCount(); ++i) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); QString color = m_model->data(m_model->index(i, COLORCOLUMN), Qt::BackgroundRole).toString(); QString name = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); bool menu = m_model->data(m_model->index(i, MENUCOLUMN), Qt::DisplayRole).toString() == CheckMark; QStringList lActivities = m_model->data(m_model->index(i, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); QStringList cleanedActivities; //!update only activities that are valid foreach (auto activity, lActivities) { if (knownActivities.contains(activity)) { cleanedActivities.append(activity); } } //qDebug() << i << ". " << id << " - " << color << " - " << name << " - " << menu << " - " << lActivities; LayoutSettings *layout = name == m_manager->currentLayoutName() ? m_manager->currentLayout() : m_layouts[id]; if (layout->color() != color) { layout->setColor(color); } if (layout->showInMenu() != menu) { layout->setShowInMenu(menu); } if (layout->activities() != cleanedActivities) { layout->setActivities(cleanedActivities); } //!if the layout name changed or when the layout path is a temporary one if (layout->name() != name || (id.startsWith("/tmp/"))) { QString tempFile = layoutTempDir.path() + "/" + QString(layout->name() + ".layout.latte"); qDebug() << "new temp file ::: " << tempFile; if (layout->name() == m_manager->currentLayoutName()) { switchToLayout = name; m_manager->corona()->unload(); } layout = m_layouts.take(id); delete layout; QFile(id).rename(tempFile); fromRenamePaths.append(id); toRenamePaths.append(tempFile); toRenameNames.append(name); } } //! this is necessary in case two layouts have to swap names //! so we copy first the layouts in a temp directory and afterwards all //! together we move them in the official layout directory for (int i = 0; i < toRenamePaths.count(); ++i) { QString newFile = QDir::homePath() + "/.config/latte/" + toRenameNames[i] + ".layout.latte"; QFile(toRenamePaths[i]).rename(newFile); LayoutSettings *nLayout = new LayoutSettings(this, newFile); m_layouts[newFile] = nLayout; for (int j = 0; j < m_model->rowCount(); ++j) { QString tId = m_model->data(m_model->index(j, IDCOLUMN), Qt::DisplayRole).toString(); if (tId == fromRenamePaths[i]) { m_model->setData(m_model->index(j, IDCOLUMN), newFile, Qt::DisplayRole); m_initLayoutPaths.append(newFile); QFont font = qvariant_cast(m_model->data(m_model->index(j, NAMECOLUMN), Qt::FontRole)); font.setItalic(false); m_model->setData(m_model->index(j, NAMECOLUMN), font, Qt::FontRole); } } } //! remove layouts that have been removed from the user foreach (auto initLayout, m_initLayoutPaths) { if (!idExistsInModel(initLayout)) { QFile(initLayout).remove(); } } m_manager->loadLayouts(); if (!switchToLayout.isNull()) { m_manager->switchToLayout(switchToLayout); } return true; } bool LayoutConfigDialog::idExistsInModel(QString id) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowId = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); if (rowId == id) { return true; } } return false; } bool LayoutConfigDialog::nameExistsInModel(QString name) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowName = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); if (rowName == name) { return true; } } return false; } int LayoutConfigDialog::ascendingRowFor(QString name) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowName = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); if (rowName.toUpper() > name.toUpper()) { return i; } } return m_model->rowCount(); } QString LayoutConfigDialog::uniqueTempDirectory() { QTemporaryDir tempDir; tempDir.setAutoRemove(false); m_tempDirectories.append(tempDir.path()); return tempDir.path(); } QString LayoutConfigDialog::uniqueLayoutName(QString name) { int pos_ = name.lastIndexOf(QRegExp(QString("[-][0-9]+"))); if (nameExistsInModel(name) && pos_ > 0) { name = name.left(pos_); } int i = 2; QString namePart = name; while (nameExistsInModel(name)) { name = namePart + "-" + QString::number(i); i++; } return name; } }//end of namespace diff --git a/app/org.kde.latte-dock.appdata.xml.cmake b/app/org.kde.latte-dock.appdata.xml.cmake index da4c463e..73c9efa6 100644 --- a/app/org.kde.latte-dock.appdata.xml.cmake +++ b/app/org.kde.latte-dock.appdata.xml.cmake @@ -1,68 +1,68 @@ org.kde.latte-dock.desktop Latte Latte Latte Latte Latte Latte Dock for the masses Dock per a les masses Dock für die Massen Πίνακες εφαρμογών για όλον τον κόσμο Док в массы 給大眾使用的 Dock -

Latte is a dock based on plasma frameworks that provides an elegant and intuitive experience for your tasks and plasmoids. It animates its contents by using parabolic zoom effect and trys to be there only when it is needed.

+

Latte is a dock based on plasma frameworks that provides an elegant and intuitive experience for your tasks and plasmoids. It animates its contents by using parabolic zoom effect and tries to be there only when it is needed.

El Latte és un acoblador basat en els Frameworks del Plasma que proporciona una experiència elegant i intuïtiva per a les vostres tasques i els plasmoides. Anima els seus continguts usant un efecte de zoom parabòlic i intenta mostrar-se només quan cal.

Latte Dock basiert auf Plasma Framework Technologie und bietet einen eleganten, intuitiven Umgang mit Plasmoiden und Tasks. Es animiert mithilfe parabolischer Zoom Effekte und versucht nur da zu sein wenn man es braucht. "Art in Coffee"

Το Latte είναι ένας πίνακας εφαρμογών βασισμένος στα plasma frameworks που εστιάζει στην καλαισθησία και παρέχει ένα όμορφο περιβάλλον για τις εργασίες και τα γραφικά συστατικά σας. Χρησιμοποιεί ένα παραβολικό εφέ έτσι ώστε να απεικονίσει τα περιεχόμενα του και προσπαθεί να είναι εκεί μόνο όταν το χρειάζεστε.

Latte - это док-панель, основанная на Plasma Frameworks, которая обеспечивает элегантный и интуитивно понятный интерфейс для ваших задач и плазмоидов. Она анимирует свое содержимое с помощью эффекта параболического увеличения и пытается быть видима только тогда, когда это необходимо.

Latte 是一個基於 Plasma 框架並提供您的工作與 plasmoid 優雅與直觀體驗的 dock。它使用拋物線縮放效果來讓您的內容動畫化,並試著只在您需要它時出現。

"Art In Coffee"

"Art al cafè"

"Art In Coffee"

"Τέχνη στον Καφέ"

«Искусство в кофе»

「咖啡中的藝術」

Utility KDE @WEBSITE@ @BUG_ADDRESS@ @FAQS@ @AUTHOR@ latte-dock GPL v2+ CC0-1.0 http://i.imgur.com/LLoXdgK.png https://2.bp.blogspot.com/-_gEFl6vIQt8/WNf3LaHcpvI/AAAAAAAAASk/CRZCF1vD2k0MQCesQk8CheBm5QrFRqYdQCLcB/s640/blur2.png http://i.imgur.com/CHCZ51A.png https://3.bp.blogspot.com/-4u-K09Tu8xI/WNf3LifOFxI/AAAAAAAAASo/AKh2HcenWpovNoJM8Yy65_jZUoXfhEi_QCLcB/s400/configwin.png KDE latte-dock liblattedockplugin.so