kwin/kcmkwin/kwindesktop/main.cpp
Martin Gräßlin 2bb79520b6 Add wrap around desktops to multiple desktops kcm.
svn path=/trunk/KDE/kdebase/workspace/; revision=1046328
2009-11-08 12:18:59 +00:00

625 lines
23 KiB
C++

/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2009 Martin Gräßlin <kde@martin-graesslin.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "main.h"
#include <QDBusMessage>
#include <QDBusConnection>
#include <QDBusInterface>
#include <KAboutApplicationDialog>
#include <KActionCollection>
#include <KAction>
#include <KCModuleProxy>
#include <KGlobalAccel>
#include <KPluginInfo>
#include <KPluginFactory>
#include <KConfigGroup>
#include <KService>
#include <KServiceTypeTrader>
#include <KShortcutsEditor>
#ifdef Q_WS_X11
#include <QX11Info>
#endif
#include <netwm.h>
K_PLUGIN_FACTORY( KWinDesktopConfigFactory, registerPlugin<KWin::KWinDesktopConfig>(); )
K_EXPORT_PLUGIN( KWinDesktopConfigFactory( "kcm_kwindesktop" ))
namespace KWin
{
KWinDesktopConfigForm::KWinDesktopConfigForm( QWidget* parent )
: QWidget( parent )
{
setupUi( this );
}
KWinDesktopConfig::KWinDesktopConfig( QWidget* parent, const QVariantList& args )
: KCModule( KWinDesktopConfigFactory::componentData(), parent, args )
, m_config( KSharedConfig::openConfig( "kwinrc" ))
, m_actionCollection( NULL )
, m_switchDesktopCollection( NULL )
{
init();
}
void KWinDesktopConfig::init()
{
m_ui = new KWinDesktopConfigForm( this );
// TODO: there has to be a way to add the shortcuts editor to the ui file
m_editor = new KShortcutsEditor( m_ui, KShortcutsEditor::GlobalAction );
m_ui->editorFrame->setLayout( new QVBoxLayout() );
m_ui->editorFrame->layout()->setMargin( 0 );
m_ui->editorFrame->layout()->addWidget( m_editor );
m_ui->desktopNames->setDesktopConfig( this );
m_ui->desktopNames->setMaxDesktops( maxDesktops );
m_ui->desktopNames->numberChanged( defaultDesktops );
QVBoxLayout* layout = new QVBoxLayout( this );
layout->addWidget( m_ui );
setQuickHelp( i18n("<h1>Multiple Desktops</h1>In this module, you can configure how many virtual desktops you want and how these should be labeled."));
// Shortcut config. The shortcut belongs to the component "kwin"!
m_actionCollection = new KActionCollection( this, KComponentData("kwin") );
m_actionCollection->setConfigGroup( "Desktop Switching" );
m_actionCollection->setConfigGlobal( true );
m_switchDesktopCollection = new KActionCollection( this, KComponentData("kwin") );
m_switchDesktopCollection->setConfigGroup( "Desktop Switching" );
m_switchDesktopCollection->setConfigGlobal( true );
// actions for switch desktop collection - other action is filled dynamically
KAction* a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch to Next Desktop" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch to Next Desktop" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch to Previous Desktop" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch to Previous Desktop" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch One Desktop to the Right" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch One Desktop to the Right" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch One Desktop to the Left" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch One Desktop to the Left" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch One Desktop Up" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch One Desktop Up" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
a = qobject_cast<KAction*>(m_switchDesktopCollection->addAction( "Switch One Desktop Down" ));
a->setProperty("isConfigurationAction", true);
a->setText( i18n( "Switch One Desktop Down" ));
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
m_editor->addCollection( m_switchDesktopCollection, i18n( "Desktop Switching" ) );
#ifdef Q_WS_X11
// get number of desktops
NETRootInfo info( QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames );
int n = info.numberOfDesktops();
for( int i=1; i<=n; ++i )
{
KAction* a = qobject_cast<KAction*>(m_actionCollection->addAction(QString("Switch to Desktop %1").arg(i)));
a->setProperty("isConfigurationAction", true);
a->setText( i18n("Switch to Desktop %1", i) );
a->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
}
// This should be after the "Switch to Desktop %1" loop. It HAS to be
// there after numberSpinBox is connected to slotChangeShortcuts. We would
// overwrite the users settings if not,
m_ui->numberSpinBox->setValue(n);
m_editor->addCollection( m_actionCollection, i18n( "Desktop Switching" ) );
#endif
// search the effect names
// TODO: way to recognize if a effect is not found
KServiceTypeTrader* trader = KServiceTypeTrader::self();
KService::List services;
QString slide;
QString cube;
QString fadedesktop;
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_slide'");
if( !services.isEmpty() )
slide = services.first()->name();
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_cubeslide'");
if( !services.isEmpty() )
cube = services.first()->name();
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_fadedesktop'");
if( !services.isEmpty() )
fadedesktop = services.first()->name();
m_ui->effectComboBox->addItem(i18n("No Animation"));
m_ui->effectComboBox->addItem(slide);
m_ui->effectComboBox->addItem(cube);
m_ui->effectComboBox->addItem(fadedesktop);
// effect config and info button
m_ui->effectInfoButton->setIcon( KIcon( "dialog-information" ));
m_ui->effectConfigButton->setIcon( KIcon( "configure" ));
connect( m_ui->rowsSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect( m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect( m_ui->numberSpinBox, SIGNAL(valueChanged(int)), SLOT(slotChangeShortcuts(int)));
connect( m_ui->activityCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
connect( m_ui->desktopNames, SIGNAL(changed()), SLOT(changed()));
connect( m_ui->popupInfoCheckBox, SIGNAL(toggled(bool)), SLOT(changed()));
connect( m_ui->popupHideSpinBox, SIGNAL(valueChanged(int)), SLOT(changed()));
connect( m_ui->desktopLayoutIndicatorCheckBox, SIGNAL(stateChanged(int)), SLOT(changed()));
connect( m_ui->wrapAroundBox, SIGNAL(stateChanged(int)), SLOT(changed()));
connect( m_editor, SIGNAL(keyChange()), SLOT(changed()));
connect( m_ui->allShortcutsCheckBox, SIGNAL(stateChanged(int)), SLOT(slotShowAllShortcuts()));
connect( m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changed()));
connect( m_ui->effectComboBox, SIGNAL(currentIndexChanged(int)), SLOT(slotEffectSelectionChanged(int)));
connect( m_ui->effectInfoButton, SIGNAL(clicked()), SLOT(slotAboutEffectClicked()));
connect( m_ui->effectConfigButton, SIGNAL(clicked()), SLOT(slotConfigureEffectClicked()));
// Begin check for immutable - taken from old desktops kcm
#ifdef Q_WS_X11
int kwin_screen_number = DefaultScreen(QX11Info::display());
#else
int kwin_screen_number = 0;
#endif
m_config = KSharedConfig::openConfig( "kwinrc" );
QByteArray groupname;
if( kwin_screen_number == 0 )
groupname = "Desktops";
else
groupname = "Desktops-screen-" + QByteArray::number ( kwin_screen_number );
if( m_config->isGroupImmutable( groupname ) )
{
m_ui->nameGroup->setEnabled(false);
//number of desktops widgets
m_ui->numberLabel->setEnabled(false);
m_ui->numberSpinBox->setEnabled(false);
}
else
{
KConfigGroup cfgGroup(m_config.data(), groupname.constData());
if( cfgGroup.isEntryImmutable( "Number" ) )
{
//number of desktops widgets
m_ui->numberLabel->setEnabled(false);
m_ui->numberSpinBox->setEnabled(false);
}
}
// End check for immutable
}
KWinDesktopConfig::~KWinDesktopConfig()
{
undo();
}
void KWinDesktopConfig::defaults()
{
// TODO: plasma stuff
m_ui->numberSpinBox->setValue( defaultDesktops );
m_ui->desktopNames->numberChanged( defaultDesktops );
for( int i=1; i<=maxDesktops; i++ )
{
m_desktopNames[i-1] = i18n( "Desktop %1", i );
if( i <= defaultDesktops )
m_ui->desktopNames->setDefaultName( i );
}
// popup info
m_ui->popupInfoCheckBox->setChecked( false );
m_ui->popupHideSpinBox->setValue( 1000 );
m_ui->desktopLayoutIndicatorCheckBox->setChecked( true );
m_ui->effectComboBox->setCurrentIndex( 1 );
m_ui->wrapAroundBox->setChecked( true );
m_editor->allDefault();
emit changed(true);
}
void KWinDesktopConfig::load()
{
// This method is called on reset(). So undo all changes.
undo();
#ifdef Q_WS_X11
// get number of desktops
NETRootInfo info( QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames );
for( int i = 1; i <= maxDesktops; i++ )
{
QString name = QString::fromUtf8( info.desktopName(i) );
m_desktopNames << name;
m_ui->desktopNames->setName( i, name );
}
#endif
// Popup info
KConfigGroup popupInfo( m_config, "PopupInfo" );
m_ui->popupInfoCheckBox->setChecked( popupInfo.readEntry( "ShowPopup", false ));
m_ui->popupHideSpinBox->setValue( popupInfo.readEntry( "PopupHideDelay", 1000 ));
m_ui->desktopLayoutIndicatorCheckBox->setChecked( !popupInfo.readEntry( "TextOnly", false) );
// Wrap Around on screen edge
KConfigGroup windowConfig( m_config, "Windows" );
m_ui->wrapAroundBox->setChecked( windowConfig.readEntry<bool>( "RollOverDesktops", true ) );
// Effect for desktop switching
// Set current option to "none" if no plugin is activated.
KConfigGroup effectconfig( m_config, "Plugins" );
m_ui->effectComboBox->setCurrentIndex( 0 );
if( effectEnabled( "slide", effectconfig ))
m_ui->effectComboBox->setCurrentIndex( 1 );
if( effectEnabled( "cubeslide", effectconfig ))
m_ui->effectComboBox->setCurrentIndex( 2 );
if( effectEnabled( "fadedesktop", effectconfig ))
m_ui->effectComboBox->setCurrentIndex( 3 );
slotEffectSelectionChanged( m_ui->effectComboBox->currentIndex() );
// TODO: plasma stuff
QDBusInterface interface( "org.kde.plasma-desktop", "/App" );
if( interface.isValid() )
{
bool perVirtualDesktopViews = interface.call( "perVirtualDesktopViews" ).arguments().first().toBool();
m_ui->activityCheckBox->setEnabled( true );
m_ui->activityCheckBox->setChecked( perVirtualDesktopViews );
}
else
m_ui->activityCheckBox->setEnabled( false );
emit changed(false);
}
void KWinDesktopConfig::save()
{
// TODO: plasma stuff
#ifdef Q_WS_X11
NETRootInfo info( QX11Info::display(), NET::NumberOfDesktops | NET::DesktopNames );
// set desktop names
for( int i = 1; i <= maxDesktops; i++ )
{
QString desktopName = m_desktopNames[ i -1 ];
if( i <= m_ui->numberSpinBox->value() )
desktopName = m_ui->desktopNames->name( i );
info.setDesktopName( i, desktopName.toUtf8());
info.activate();
}
// set number of desktops
info.setNumberOfDesktops( m_ui->numberSpinBox->value() );
info.activate();
XSync(QX11Info::display(), false);
#endif
// Popup info
KConfigGroup popupInfo( m_config, "PopupInfo" );
popupInfo.writeEntry( "ShowPopup", m_ui->popupInfoCheckBox->isChecked() );
popupInfo.writeEntry( "PopupHideDelay", m_ui->popupHideSpinBox->value() );
popupInfo.writeEntry( "TextOnly", !m_ui->desktopLayoutIndicatorCheckBox->isChecked() );
// Wrap Around on screen edge
KConfigGroup windowConfig( m_config, "Windows" );
windowConfig.writeEntry( "RollOverDesktops", m_ui->wrapAroundBox->isChecked() );
// Effect desktop switching
KConfigGroup effectconfig( m_config, "Plugins" );
int desktopSwitcher = m_ui->effectComboBox->currentIndex();
switch( desktopSwitcher )
{
case 0:
// no effect
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
break;
case 1:
// slide
effectconfig.writeEntry("kwin4_effect_slideEnabled", true);
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
break;
case 2:
// cube
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", true);
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", false);
break;
case 3:
// fadedesktop
effectconfig.writeEntry("kwin4_effect_slideEnabled", false);
effectconfig.writeEntry("kwin4_effect_cubeslideEnabled", false);
effectconfig.writeEntry("kwin4_effect_fadedesktopEnabled", true);
break;
}
m_editor->save();
m_config->sync();
// Send signal to all kwin instances
QDBusMessage message = QDBusMessage::createSignal( "/KWin", "org.kde.KWin", "reloadConfig" );
QDBusConnection::sessionBus().send( message );
QDBusInterface interface( "org.kde.plasma-desktop", "/App" );
interface.call( "setPerVirtualDesktopViews", ( m_ui->activityCheckBox->isChecked() ) );
emit changed(false);
}
void KWinDesktopConfig::undo()
{
// The global shortcuts editor makes changes active immediately. In case
// of undo we have to undo them manually
m_editor->undoChanges();
}
QString KWinDesktopConfig::cachedDesktopName( int desktop )
{
if( desktop > m_desktopNames.size() )
return QString();
return m_desktopNames[ desktop -1 ];
}
QString KWinDesktopConfig::extrapolatedShortcut( int desktop ) const
{
if (!desktop || desktop > m_actionCollection->count())
return QString();
if (desktop == 1)
return QString("Ctrl+F1");
KAction *beforeAction = qobject_cast<KAction*>(m_actionCollection->actions().at(qMin(9, desktop - 2)));
QString before = beforeAction->globalShortcut(KAction::ActiveShortcut).toString();
if (before.isEmpty())
before = beforeAction->globalShortcut(KAction::DefaultShortcut).toString();
QString seq;
if ( before.contains( QRegExp("F[0-9]{1,2}") ) )
{
if (desktop < 13) // 10?
seq = QString("F%1").arg(desktop);
else if ( !before.contains("Shift") )
seq = "Shift+" + QString("F%1").arg(desktop - 10);
}
else if ( before.contains( QRegExp("[0-9]") ) )
{
if (desktop == 10)
seq = '0';
else if (desktop > 10)
{
if ( !before.contains("Shift") )
seq = "Shift+" + QString::number(desktop == 20 ? 0 : (desktop - 10));
}
else
seq = QString::number(desktop);
}
if ( !seq.isEmpty() )
{
if (before.contains("Ctrl"))
seq.prepend("Ctrl+");
if (before.contains("Alt"))
seq.prepend("Alt+");
if (before.contains("Shift"))
seq.prepend("Shift+");
if (before.contains("Meta"))
seq.prepend("Meta+");
}
return seq;
}
void KWinDesktopConfig::slotChangeShortcuts( int number )
{
if( (number < 1) || (number > maxDesktops) )
return;
if( m_ui->allShortcutsCheckBox->isChecked() )
number = maxDesktops;
while( number != m_actionCollection->count() )
{
if( number < m_actionCollection->count() )
{
// Remove the action from the action collection. The action itself
// will still exist because that's the way kwin currently works.
// No need to remove/forget it. See kwinbindings.
KAction *a = qobject_cast<KAction*>(
m_actionCollection->takeAction( m_actionCollection->actions().last() ));
// Remove any associated global shortcut. Set it to ""
a->setGlobalShortcut(
KShortcut(),
KAction::ActiveShortcut,
KAction::NoAutoloading);
m_ui->messageLabel->hide();
delete a;
}
else
{
// add desktop
int desktop = m_actionCollection->count() + 1;
KAction* action = qobject_cast<KAction*>(m_actionCollection->addAction(QString("Switch to Desktop %1").arg(desktop)));
action->setProperty("isConfigurationAction", true);
action->setText( i18n("Switch to Desktop %1", desktop) );
action->setGlobalShortcut( KShortcut(), KAction::ActiveShortcut );
QString shortcutString = extrapolatedShortcut(desktop);
if (shortcutString.isEmpty())
{
m_ui->messageLabel->setText(i18n( "No suitable Shortcut for Desktop %1 found", desktop ));
m_ui->messageLabel->show();
}
else
{
KShortcut shortcut(shortcutString);
if (!shortcut.primary().isEmpty() || KGlobalAccel::self()->isGlobalShortcutAvailable(shortcut.primary()))
{
action->setGlobalShortcut( shortcut, KAction::ActiveShortcut, KAction::NoAutoloading );
m_ui->messageLabel->setText(i18n( "Assigned global Shortcut \"%1\" to Desktop %2", shortcutString, desktop ));
m_ui->messageLabel->show();
}
else
{
m_ui->messageLabel->setText(i18n( "Shortcut conflict: Could not set Shortcut %1 for Desktop %2", shortcutString, desktop ));
m_ui->messageLabel->show();
}
}
}
}
m_editor->clearCollections();
m_editor->addCollection( m_switchDesktopCollection, i18n( "Desktop Switching" ) );
m_editor->addCollection( m_actionCollection, i18n( "Desktop Switching" ) );
}
void KWinDesktopConfig::slotShowAllShortcuts()
{
slotChangeShortcuts( m_ui->numberSpinBox->value() );
}
void KWinDesktopConfig::slotEffectSelectionChanged( int index )
{
bool enabled = false;
if( index != 0 )
enabled = true;
m_ui->effectInfoButton->setEnabled( enabled );
// only cube has config dialog
if( index != 2 )
enabled = false;
m_ui->effectConfigButton->setEnabled( enabled );
}
bool KWinDesktopConfig::effectEnabled( const QString& effect, const KConfigGroup& cfg ) const
{
KService::List services = KServiceTypeTrader::self()->query(
"KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\'');
if( services.isEmpty())
return false;
QVariant v = services.first()->property("X-KDE-PluginInfo-EnabledByDefault");
return cfg.readEntry("kwin4_effect_" + effect + "Enabled", v.toBool());
}
void KWinDesktopConfig::slotAboutEffectClicked()
{
KServiceTypeTrader* trader = KServiceTypeTrader::self();
KService::List services;
QString effect;
switch( m_ui->effectComboBox->currentIndex() )
{
case 1:
effect = "slide";
break;
case 2:
effect = "cubeslide";
break;
case 3:
effect = "fadedesktop";
break;
default:
return;
}
services = trader->query("KWin/Effect", "[X-KDE-PluginInfo-Name] == 'kwin4_effect_" + effect + '\'');
if( services.isEmpty() )
return;
KPluginInfo pluginInfo( services.first() );
const QString name = pluginInfo.name();
const QString comment = pluginInfo.comment();
const QString author = pluginInfo.author();
const QString email = pluginInfo.email();
const QString website = pluginInfo.website();
const QString version = pluginInfo.version();
const QString license = pluginInfo.license();
const QString icon = pluginInfo.icon();
KAboutData aboutData(name.toUtf8(), name.toUtf8(), ki18n(name.toUtf8()), version.toUtf8(), ki18n(comment.toUtf8()), KAboutLicense::byKeyword(license).key(), ki18n(QByteArray()), ki18n(QByteArray()), website.toLatin1());
aboutData.setProgramIconName( icon );
const QStringList authors = author.split(',');
const QStringList emails = email.split(',');
int i = 0;
if( authors.count() == emails.count() )
{
foreach( const QString &author, authors )
{
if( !author.isEmpty() )
{
aboutData.addAuthor(ki18n(author.toUtf8()), ki18n(QByteArray()), emails[i].toUtf8(), 0);
}
i++;
}
}
KAboutApplicationDialog aboutPlugin( &aboutData, this );
aboutPlugin.exec();
}
void KWinDesktopConfig::slotConfigureEffectClicked()
{
QString effect;
switch( m_ui->effectComboBox->currentIndex() )
{
case 2:
effect = "cubeslide_config";
break;
default:
return;
}
KCModuleProxy* proxy = new KCModuleProxy( effect );
KDialog configDialog( this );
configDialog.setWindowTitle( m_ui->effectComboBox->currentText() );
configDialog.setButtons( KDialog::Ok | KDialog::Cancel | KDialog::Default );
connect(&configDialog, SIGNAL(defaultClicked()), proxy, SLOT(defaults()));
QWidget *showWidget = new QWidget( &configDialog );
QVBoxLayout *layout = new QVBoxLayout;
showWidget->setLayout( layout );
layout->addWidget( proxy );
layout->insertSpacing( -1, KDialog::marginHint() );
configDialog.setMainWidget( showWidget );
if( configDialog.exec() == QDialog::Accepted )
{
proxy->save();
}
else
{
proxy->load();
}
}
} // namespace
#include "main.moc"