You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
521 lines
16 KiB
521 lines
16 KiB
/******************************************************************** |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2009 Nikhil Marathe <nsm.nikhil@gmail.com> |
|
Copyright (C) 2011 Arthur Arlt <a.arlt@stud.uni-heidelberg.de> |
|
|
|
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 <tiling/tiling.h> |
|
|
|
#include <klocale.h> |
|
#include <knotification.h> |
|
#include <kwindowinfo.h> |
|
#include <kwindowsystem.h> |
|
|
|
#include <KActionCollection> |
|
#include <KDE/KAction> |
|
|
|
#include "tiling/tile.h" |
|
#include "tiling/tilinglayout.h" |
|
#include "tilinglayoutfactory.h" |
|
#include "workspace.h" |
|
|
|
namespace KWin { |
|
Tiling::Tiling(KWin::Workspace* w) |
|
: QObject(w) |
|
, m_workspace(w) |
|
, m_enabled(false) |
|
{ |
|
} |
|
|
|
Tiling::~Tiling() |
|
{ |
|
} |
|
|
|
void Tiling::initShortcuts(KActionCollection* keys){ |
|
KAction *a = NULL; |
|
#define KEY( name, key, fnSlot ) \ |
|
a = keys->addAction( name ); \ |
|
a->setText( i18n( name ) ); \ |
|
qobject_cast<KAction*>( a )->setGlobalShortcut(KShortcut(key)); \ |
|
connect(a, SIGNAL(triggered(bool)), SLOT(fnSlot)); |
|
|
|
a = keys->addAction("Group:Tiling"); |
|
a->setText(i18n("Tiling")); |
|
KEY(I18N_NOOP("Enable/Disable Tiling"), Qt::SHIFT + Qt::ALT + Qt::Key_F11, slotToggleTiling()); |
|
KEY(I18N_NOOP("Toggle Floating"), Qt::META + Qt::Key_F, slotToggleFloating()); |
|
|
|
KEY(I18N_NOOP("Switch Focus Left") , Qt::META + Qt::Key_H, slotFocusTileLeft()); |
|
KEY(I18N_NOOP("Switch Focus Right") , Qt::META + Qt::Key_L, slotFocusTileRight()); |
|
KEY(I18N_NOOP("Switch Focus Up") , Qt::META + Qt::Key_K, slotFocusTileTop()); |
|
KEY(I18N_NOOP("Switch Focus Down") , Qt::META + Qt::Key_J, slotFocusTileBottom()); |
|
KEY(I18N_NOOP("Move Window Left") , Qt::SHIFT + Qt::META + Qt::Key_H, slotMoveTileLeft()); |
|
KEY(I18N_NOOP("Move Window Right") , Qt::SHIFT + Qt::META + Qt::Key_L, slotMoveTileRight()); |
|
KEY(I18N_NOOP("Move Window Up") , Qt::SHIFT + Qt::META + Qt::Key_K, slotMoveTileTop()); |
|
KEY(I18N_NOOP("Move Window Down") , Qt::SHIFT + Qt::META + Qt::Key_J, slotMoveTileBottom()); |
|
KEY(I18N_NOOP("Next Layout"), Qt::META + Qt::Key_PageDown, slotNextTileLayout()); |
|
KEY(I18N_NOOP("Previous Layout"), Qt::META + Qt::Key_PageUp, slotPreviousTileLayout()); |
|
|
|
} |
|
|
|
bool Tiling::isEnabled() const |
|
{ |
|
return m_enabled; |
|
} |
|
|
|
void Tiling::setEnabled(bool tiling) |
|
{ |
|
if (isEnabled() == tiling) return; |
|
|
|
m_enabled = tiling; |
|
|
|
KSharedConfig::Ptr _config = KGlobal::config(); |
|
KConfigGroup config(_config, "Windows"); |
|
config.writeEntry("TilingOn", m_enabled); |
|
config.sync(); |
|
options->setTilingOn(m_enabled); |
|
|
|
if (m_enabled) { |
|
connect(m_workspace, SIGNAL(clientAdded(KWin::Client*)), this, SLOT(createTile(KWin::Client*))); |
|
connect(m_workspace, SIGNAL(clientAdded(KWin::Client*)), this, SLOT(slotResizeTilingLayouts())); |
|
connect(m_workspace, SIGNAL(numberDesktopsChanged(int)), this, SLOT(slotResizeTilingLayouts())); |
|
connect(m_workspace, SIGNAL(clientRemoved(KWin::Client*)), this, SLOT(removeTile(KWin::Client*))); |
|
connect(m_workspace, SIGNAL(clientActivated(KWin::Client*)), this, SLOT(notifyTilingWindowActivated(KWin::Client*))); |
|
m_tilingLayouts.resize(Workspace::self()->numberOfDesktops() + 1); |
|
foreach (Client * c, Workspace::self()->stackingOrder()) { |
|
createTile(c); |
|
} |
|
} else { |
|
disconnect(m_workspace, SIGNAL(clientAdded(KWin::Client*))); |
|
disconnect(m_workspace, SIGNAL(numberDesktopsChanged(int))); |
|
disconnect(m_workspace, SIGNAL(clientRemoved(KWin::Client*))); |
|
qDeleteAll(m_tilingLayouts); |
|
m_tilingLayouts.clear(); |
|
} |
|
} |
|
|
|
void Tiling::slotToggleTiling() |
|
{ |
|
if (isEnabled()) { |
|
setEnabled(false); |
|
QString message = i18n("Tiling Disabled"); |
|
KNotification::event("tilingdisabled", message, QPixmap(), NULL, KNotification::CloseOnTimeout, KComponentData("kwin")); |
|
} else { |
|
setEnabled(true); |
|
QString message = i18n("Tiling Enabled"); |
|
KNotification::event("tilingenabled", message, QPixmap(), NULL, KNotification::CloseOnTimeout, KComponentData("kwin")); |
|
} |
|
} |
|
|
|
void Tiling::createTile(Client* c) |
|
{ |
|
if (c == NULL) |
|
return; |
|
|
|
if (c->desktop() < 0 || c->desktop() >= m_tilingLayouts.size()) return; |
|
|
|
kDebug(1212) << "Now tiling " << c->caption(); |
|
if (!isEnabled() || !tileable(c)) |
|
return; |
|
|
|
Tile *t = new Tile(c, Workspace::self()->clientArea(PlacementArea, c)); |
|
if (!tileable(c)) { |
|
kDebug(1212) << c->caption() << "is not tileable"; |
|
t->floatTile(); |
|
} |
|
|
|
if (!m_tilingLayouts.value(c->desktop())) { |
|
m_tilingLayouts[c->desktop()] = TilingLayoutFactory::createLayout(TilingLayoutFactory::DefaultLayout, m_workspace); |
|
m_tilingLayouts[c->desktop()]->setParent(this); |
|
} |
|
m_tilingLayouts[c->desktop()]->addTile(t); |
|
m_tilingLayouts[c->desktop()]->commit(); |
|
// if tiling is activated, connect to Client's signals and react with rearrangement when (un)minimizing |
|
connect(c, SIGNAL(clientMinimized(KWin::Client*,bool)), this, SLOT(notifyTilingWindowMinimizeToggled(KWin::Client*))); |
|
connect(c, SIGNAL(clientUnminimized(KWin::Client*,bool)), this, SLOT(notifyTilingWindowMinimizeToggled(KWin::Client*))); |
|
connect(c, SIGNAL(clientUnminimized(KWin::Client*,bool)), this, SLOT(updateAllTiles())); |
|
} |
|
|
|
void Tiling::removeTile(Client *c) |
|
{ |
|
if (!m_tilingLayouts.value(c->desktop())) { |
|
return; |
|
} |
|
if (m_tilingLayouts[ c->desktop()]) |
|
m_tilingLayouts[ c->desktop()]->removeTile(c); |
|
} |
|
|
|
bool Tiling::tileable(Client* c) |
|
{ |
|
kDebug(1212) << c->caption(); |
|
KWindowInfo info = KWindowSystem::windowInfo(c->window(), -1U, NET::WM2WindowClass); |
|
kDebug(1212) << "WINDOW CLASS IS " << info.windowClassClass(); |
|
if (info.windowClassClass() == "Plasma-desktop") { |
|
return false; |
|
} |
|
// TODO: if application specific settings |
|
// to ignore, put them here |
|
|
|
if (!c->isNormalWindow()) { |
|
return false; |
|
} |
|
|
|
// 0 means tile it, if we get 1 (floating), don't tile |
|
if (c->rules()->checkTilingOption(0) == 1) { |
|
return false; |
|
} |
|
|
|
kDebug() << "Tiling" << c; |
|
return true; |
|
} |
|
|
|
void Tiling::belowCursor() |
|
{ |
|
// TODO ... "WHAT?" remove? What's a parameterless void function supposed to do? |
|
} |
|
|
|
Tile* Tiling::getNiceTile() const |
|
{ |
|
if (!isEnabled()) return NULL; |
|
if (!m_workspace->activeClient()) return NULL; |
|
if (!m_tilingLayouts.value(m_workspace->activeClient()->desktop())) return NULL; |
|
|
|
return m_tilingLayouts[ m_workspace->activeClient()->desktop()]->findTile(m_workspace->activeClient()); |
|
// TODO ... WHAT? |
|
} |
|
|
|
void Tiling::updateAllTiles() |
|
{ |
|
foreach (TilingLayout * t, m_tilingLayouts) { |
|
if (!t) continue; |
|
t->commit(); |
|
} |
|
} |
|
|
|
/* |
|
* Resize the neighbouring clients to close any gaps |
|
*/ |
|
void Tiling::notifyTilingWindowResize(Client *c, const QRect &moveResizeGeom, const QRect &orig) |
|
{ |
|
if (m_tilingLayouts.value(c->desktop()) == NULL) |
|
return; |
|
m_tilingLayouts[ c->desktop()]->clientResized(c, moveResizeGeom, orig); |
|
} |
|
|
|
void Tiling::notifyTilingWindowMove(Client *c, const QRect &moveResizeGeom, const QRect &orig) |
|
{ |
|
if (m_tilingLayouts.value(c->desktop()) == NULL) { |
|
return; |
|
} |
|
m_tilingLayouts[ c->desktop()]->clientMoved(c, moveResizeGeom, orig); |
|
updateAllTiles(); |
|
} |
|
|
|
void Tiling::notifyTilingWindowResizeDone(Client *c, const QRect &moveResizeGeom, const QRect &orig, bool canceled) |
|
{ |
|
if (canceled) |
|
notifyTilingWindowResize(c, orig, moveResizeGeom); |
|
else |
|
notifyTilingWindowResize(c, moveResizeGeom, orig); |
|
} |
|
|
|
void Tiling::notifyTilingWindowMoveDone(Client *c, const QRect &moveResizeGeom, const QRect &orig, bool canceled) |
|
{ |
|
if (canceled) |
|
notifyTilingWindowMove(c, orig, moveResizeGeom); |
|
else |
|
notifyTilingWindowMove(c, moveResizeGeom, orig); |
|
} |
|
|
|
void Tiling::notifyTilingWindowDesktopChanged(Client *c, int old_desktop) |
|
{ |
|
if (c->desktop() < 1 || c->desktop() > m_workspace->numberOfDesktops()) |
|
return; |
|
|
|
if (m_tilingLayouts.value(old_desktop)) { |
|
Tile *t = m_tilingLayouts[ old_desktop ]->findTile(c); |
|
|
|
// TODO: copied from createTile(), move this into separate method? |
|
if (!m_tilingLayouts.value(c->desktop())) { |
|
m_tilingLayouts[c->desktop()] = TilingLayoutFactory::createLayout(TilingLayoutFactory::DefaultLayout, m_workspace); |
|
} |
|
|
|
if (t) |
|
m_tilingLayouts[ c->desktop()]->addTile(t); |
|
|
|
m_tilingLayouts[ old_desktop ]->removeTile(c); |
|
m_tilingLayouts[ old_desktop ]->commit(); |
|
} |
|
} |
|
|
|
/* |
|
* Implements the 3 raising modes in Window Behaviour -> Advanced |
|
*/ |
|
void Tiling::notifyTilingWindowActivated(KWin::Client *c) |
|
{ |
|
if (c == NULL) |
|
return; |
|
|
|
if (options->tilingRaisePolicy() == 1) // individual raise/lowers |
|
return; |
|
|
|
if (m_tilingLayouts.value(c->desktop())) { |
|
QList<Tile *> tiles = m_tilingLayouts[ c->desktop()]->tiles(); |
|
|
|
StackingUpdatesBlocker blocker(m_workspace); |
|
|
|
Tile *tile_to_raise = m_tilingLayouts[ c->desktop()]->findTile(c); |
|
|
|
if (!tile_to_raise) { |
|
return; |
|
} |
|
|
|
kDebug(1212) << "FOUND TILE"; |
|
bool raise_floating = false; |
|
if (options->tilingRaisePolicy() == 2) // floating always on top |
|
raise_floating = true; |
|
else |
|
raise_floating = tile_to_raise->floating(); |
|
|
|
foreach (Tile * t, tiles) { |
|
if (t->floating() == raise_floating && t != tile_to_raise) |
|
m_workspace->raiseClient(t->client()); |
|
} |
|
// raise the current tile last so that it ends up on top |
|
// but only if it supposed to be raised, required to support tilingRaisePolicy |
|
kDebug(1212) << "Raise floating? " << raise_floating << "to raise is floating?" << tile_to_raise->floating(); |
|
if (tile_to_raise->floating() == raise_floating) |
|
m_workspace->raiseClient(tile_to_raise->client()); |
|
} |
|
} |
|
|
|
void Tiling::notifyTilingWindowMinimizeToggled(KWin::Client* c) |
|
{ |
|
if (m_tilingLayouts.value(c->desktop())) { |
|
m_tilingLayouts[ c->desktop()]->clientMinimizeToggled(c); |
|
} |
|
} |
|
|
|
void Tiling::notifyTilingWindowMaximized(Client *c, Options::WindowOperation op) |
|
{ |
|
if (m_tilingLayouts.value(c->desktop())) { |
|
Tile *t = m_tilingLayouts[ c->desktop()]->findTile(c); |
|
if (!t) { |
|
createTile(c); |
|
t = m_tilingLayouts[ c->desktop()]->findTile(c); |
|
|
|
// if still no tile, it couldn't be tiled |
|
// so ignore it |
|
if (!t) |
|
return; |
|
} |
|
|
|
// if window IS tiled and a maximize |
|
// is attempted, make the window float. |
|
// That is all we do since that can |
|
// mess up the layout. |
|
// In all other cases, don't do |
|
// anything, let the user manage toggling |
|
// using Meta+F |
|
if (!t->floating() |
|
&& (op == Options::MaximizeOp |
|
|| op == Options::HMaximizeOp |
|
|| op == Options::VMaximizeOp)) { |
|
m_tilingLayouts[ c->desktop()]->toggleFloatTile(c); |
|
} |
|
|
|
} |
|
} |
|
|
|
Tile* Tiling::findAdjacentTile(Tile *ref, int d) |
|
{ |
|
QRect reference = ref->geometry(); |
|
QPoint origin = reference.center(); |
|
|
|
Tile *closest = NULL; |
|
int minDist = -1; |
|
|
|
QList<Tile *> tiles = m_tilingLayouts[ ref->client()->desktop()]->tiles(); |
|
|
|
foreach (Tile * t, tiles) { |
|
if (t->client() == ref->client() || t->ignoreGeometry()) |
|
continue; |
|
|
|
bool consider = false; |
|
|
|
QRect other = t->geometry(); |
|
QPoint otherCenter = other.center(); |
|
|
|
switch(d) { |
|
case Tile::Top: |
|
consider = otherCenter.y() < origin.y() |
|
&& other.bottom() < reference.top(); |
|
break; |
|
|
|
case Tile::Right: |
|
consider = otherCenter.x() > origin.x() |
|
&& other.left() > reference.right(); |
|
break; |
|
|
|
case Tile::Bottom: |
|
consider = otherCenter.y() > origin.y() |
|
&& other.top() > reference.bottom(); |
|
break; |
|
|
|
case Tile::Left: |
|
consider = otherCenter.x() < origin.x() |
|
&& other.right() < reference.left(); |
|
break; |
|
|
|
default: |
|
abort(); |
|
} |
|
|
|
if (consider) { |
|
int dist = (otherCenter - origin).manhattanLength(); |
|
if (minDist > dist || minDist < 0) { |
|
minDist = dist; |
|
closest = t; |
|
} |
|
} |
|
} |
|
return closest; |
|
} |
|
|
|
void Tiling::focusTile(int d) |
|
{ |
|
Tile *t = getNiceTile(); |
|
if (t) { |
|
Tile *adj = findAdjacentTile(t, d); |
|
if (adj) |
|
m_workspace->activateClient(adj->client()); |
|
} |
|
} |
|
|
|
void Tiling::moveTile(int d) |
|
{ |
|
Tile *t = getNiceTile(); |
|
if (t) { |
|
Tile* adj = findAdjacentTile(t, d); |
|
|
|
m_tilingLayouts[ t->client()->desktop()]->swapTiles(t, adj); |
|
} |
|
} |
|
|
|
void Tiling::slotFocusTileLeft() |
|
{ |
|
focusTile(Tile::Left); |
|
} |
|
|
|
void Tiling::slotFocusTileRight() |
|
{ |
|
focusTile(Tile::Right); |
|
} |
|
|
|
void Tiling::slotFocusTileTop() |
|
{ |
|
focusTile(Tile::Top); |
|
} |
|
|
|
void Tiling::slotFocusTileBottom() |
|
{ |
|
focusTile(Tile::Bottom); |
|
} |
|
|
|
void Tiling::slotMoveTileLeft() |
|
{ |
|
moveTile(Tile::Left); |
|
} |
|
|
|
void Tiling::slotMoveTileRight() |
|
{ |
|
moveTile(Tile::Right); |
|
} |
|
|
|
void Tiling::slotMoveTileTop() |
|
{ |
|
moveTile(Tile::Top); |
|
} |
|
|
|
void Tiling::slotMoveTileBottom() |
|
{ |
|
moveTile(Tile::Bottom); |
|
} |
|
|
|
void Tiling::slotToggleFloating() |
|
{ |
|
Client *c = m_workspace->activeClient(); |
|
if (!c) |
|
return; |
|
if (m_tilingLayouts.value(c->desktop())) { |
|
m_tilingLayouts[ c->desktop()]->toggleFloatTile(c); |
|
} |
|
} |
|
|
|
void Tiling::slotNextTileLayout() |
|
{ |
|
if (m_tilingLayouts.value(m_workspace->currentDesktop())) { |
|
|
|
m_tilingLayouts.replace(m_workspace->currentDesktop(), TilingLayoutFactory::nextLayout(m_tilingLayouts[m_workspace->currentDesktop()])); |
|
|
|
m_tilingLayouts[m_workspace->currentDesktop()]->commit(); |
|
} |
|
} |
|
|
|
void Tiling::slotPreviousTileLayout() |
|
{ |
|
if (m_tilingLayouts.value(m_workspace->currentDesktop())) { |
|
|
|
m_tilingLayouts.replace(m_workspace->currentDesktop(), TilingLayoutFactory::previousLayout(m_tilingLayouts[m_workspace->currentDesktop()])); |
|
|
|
m_tilingLayouts[m_workspace->currentDesktop()]->commit(); |
|
} |
|
} |
|
|
|
KDecorationDefines::Position Tiling::supportedTilingResizeMode(Client *c, KDecorationDefines::Position currentMode) |
|
{ |
|
if (m_tilingLayouts.value(c->desktop())) { |
|
return m_tilingLayouts[c->desktop()]->resizeMode(c, currentMode); |
|
} |
|
return currentMode; |
|
} |
|
|
|
void Tiling::dumpTiles() const |
|
{ |
|
foreach (TilingLayout * t, m_tilingLayouts) { |
|
if (!t) { |
|
kDebug(1212) << "EMPTY DESKTOP"; |
|
continue; |
|
} |
|
kDebug(1212) << "Desktop" << m_tilingLayouts.indexOf(t); |
|
foreach (Tile * tile, t->tiles()) { |
|
tile->dumpTile("--"); |
|
} |
|
} |
|
} |
|
|
|
const QVector< TilingLayout* >& Tiling::tilingLayouts() const |
|
{ |
|
return m_tilingLayouts; |
|
} |
|
|
|
void Tiling::slotResizeTilingLayouts() |
|
{ |
|
m_tilingLayouts.resize(m_workspace->numberOfDesktops() + 1); |
|
} |
|
|
|
}
|
|
|