diff --git a/CMakeLists.txt b/CMakeLists.txt index c02cae6831..5dc485f516 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ endif( KWIN_HAVE_COMPOSITING ) set(kwin_KDEINIT_SRCS workspace.cpp client.cpp + clientgroup.cpp placement.cpp atoms.cpp utils.cpp diff --git a/activation.cpp b/activation.cpp index 2a8df92fba..97ff202722 100644 --- a/activation.cpp +++ b/activation.cpp @@ -367,6 +367,13 @@ void Workspace::takeActivity( Client* c, int flags, bool handled ) } if( !c->isShown( true )) // shouldn't happen, call activateClient() if needed { + int group_size = c->clientGroup()->clients().count(); + if( group_size > 1 && c->clientGroup()->visible() != c ) // the tab is hidden, make it visible and call this function again + { + c->clientGroup()->setVisible( c ); + takeActivity( c, flags, handled ); + return; + } kWarning( 1212 ) << "takeActivity: not shown" ; return; } diff --git a/bridge.cpp b/bridge.cpp index 6c4769723e..176d1b53a0 100644 --- a/bridge.cpp +++ b/bridge.cpp @@ -208,4 +208,64 @@ bool Bridge::compositingActive() const return c->workspace()->compositingActive(); } +bool Bridge::isClientGroupActive() + { + return c->clientGroup()->containsActiveClient(); + } + +QList< ClientGroupItem > Bridge::clientGroupItems() const + { + return c->clientGroup()->items(); + } + +int Bridge::itemId( int index ) + { + const ClientList list = c->clientGroup()->clients(); + return reinterpret_cast( list.at( index )); + } + +int Bridge::visibleClientGroupItem() + { + return c->clientGroup()->indexOfVisibleClient(); + } + +void Bridge::setVisibleClientGroupItem( int index ) + { + c->clientGroup()->setVisible( index ); + } + +void Bridge::moveItemInClientGroup( int index, int before ) + { + c->clientGroup()->move( index, before ); + } + +void Bridge::moveItemToClientGroup( int itemId, int before ) + { + Client* item = reinterpret_cast( itemId ); + c->workspace()->moveItemToClientGroup( item->clientGroup(), item->clientGroup()->indexOfClient( item ), + c->clientGroup(), before ); + } + +void Bridge::removeFromClientGroup( int index, const QRect& newGeom ) + { + c->clientGroup()->remove( index, newGeom ); + } + +void Bridge::closeClientGroupItem( int index ) + { + const ClientList list = c->clientGroup()->clients(); + if( index >= 0 || index <= list.count() ) + list.at( index )->closeWindow(); + } + +void Bridge::closeAllInClientGroup() + { + c->clientGroup()->closeAll(); + } + +void Bridge::displayClientMenu( int index, const QPoint& pos ) + { + c->clientGroup()->displayClientMenu( index, pos ); + } + } // namespace diff --git a/bridge.h b/bridge.h index 581b27a08d..169f34d843 100644 --- a/bridge.h +++ b/bridge.h @@ -76,6 +76,20 @@ class Bridge : public KDecorationBridgeUnstable virtual void grabXServer( bool grab ); virtual bool compositingActive() const; + + // Window tabbing + virtual bool isClientGroupActive(); + virtual QList< ClientGroupItem > clientGroupItems() const; + virtual int itemId( int index ); + virtual int visibleClientGroupItem(); + virtual void setVisibleClientGroupItem( int index ); + virtual void moveItemInClientGroup( int index, int before ); + virtual void moveItemToClientGroup( int itemId, int before ); + virtual void removeFromClientGroup( int index, const QRect& newGeom ); + virtual void closeClientGroupItem( int index ); + virtual void closeAllInClientGroup(); + virtual void displayClientMenu( int index, const QPoint& pos ); + private: Client* c; }; diff --git a/client.cpp b/client.cpp index 6db7e245c7..7ad2e7e9e6 100644 --- a/client.cpp +++ b/client.cpp @@ -97,6 +97,7 @@ Client::Client( Workspace* ws ) , delayedMoveResizeTimer( NULL ) , in_group( NULL ) , window_group( None ) + , client_group( NULL ) , in_layer( UnknownLayer ) , ping_timer( NULL ) , process_killer( NULL ) @@ -244,6 +245,8 @@ void Client::releaseWindow( bool on_shutdown ) XUnmapWindow( display(), frameId()); // Destroying decoration would cause ugly visual effect destroyDecoration(); cleanGrouping(); + if( clientGroup() ) + clientGroup()->remove( this ); if( !on_shutdown ) { workspace()->removeClient( this, Allowed ); @@ -306,6 +309,8 @@ void Client::destroyClient() workspace()->clientHidden( this ); destroyDecoration(); cleanGrouping(); + if( clientGroup() ) + clientGroup()->remove( this ); workspace()->removeClient( this, Allowed ); client = None; // invalidate XDestroyWindow( display(), wrapper ); @@ -656,7 +661,7 @@ bool Client::noBorder() const bool Client::userCanSetNoBorder() const { - return !isFullScreen() && !isShade(); + return !isFullScreen() && !isShade() && ( clientGroup() == NULL || !(clientGroup()->items().count() > 1)); } void Client::setNoBorder( bool set ) @@ -849,6 +854,10 @@ void Client::minimize( bool avoid_animation ) workspace()->updateFocusChains( this, Workspace::FocusChainMakeLast ); if( effects && !avoid_animation ) // TODO: Shouldn't it tell effects at least about the change? static_cast(effects)->windowMinimized( effectWindow()); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::unminimize( bool avoid_animation ) @@ -864,6 +873,10 @@ void Client::unminimize( bool avoid_animation ) updateWindowRules(); if( effects && !avoid_animation ) static_cast( effects )->windowUnminimized( effectWindow() ); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } QRect Client::iconGeometry() const @@ -967,6 +980,10 @@ void Client::setShade( ShadeMode mode ) workspace()->updateMinimizedOfTransients( this ); decoration->shadeChange(); updateWindowRules(); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::shadeHover() @@ -996,7 +1013,7 @@ void Client::updateVisibility() { if( deleting ) return; - if( hidden ) + if( hidden && ( clientGroup() == NULL || clientGroup()->visible() == this )) { info->setState( NET::Hidden, NET::Hidden ); setSkipTaskbar( true, false ); // Also hide from taskbar @@ -1006,7 +1023,8 @@ void Client::updateVisibility() internalHide( Allowed ); return; } - setSkipTaskbar( original_skip_taskbar, false ); // Reset from 'hidden' + if( clientGroup() == NULL || clientGroup()->visible() == this ) + setSkipTaskbar( original_skip_taskbar, false ); // Reset from 'hidden' if( minimized ) { info->setState( NET::Hidden, NET::Hidden ); @@ -1396,6 +1414,10 @@ void Client::setDesktop( int desktop ) workspace()->updateFocusChains( this, Workspace::FocusChainMakeFirst ); updateVisibility(); updateWindowRules(); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } /** @@ -1418,6 +1440,10 @@ void Client::setOnAllDesktops( bool b ) setDesktop( NET::OnAllDesktops ); else setDesktop( workspace()->currentDesktop()); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } /** @@ -1568,7 +1594,10 @@ void Client::setCaption( const QString& _s, bool force ) info->setVisibleIconName( ( cap_iconic + cap_suffix ).toUtf8() ); if( isManaged() && decoration != NULL ) + { + client_group->updateItems(); decoration->captionChange(); + } } } @@ -1606,6 +1635,37 @@ QString Client::caption( bool full ) const return full ? cap_normal + cap_suffix : cap_normal; } +void Client::dontMoveResize() + { + buttonDown = false; + stopDelayedMoveResize(); + if( moveResizeMode ) + finishMoveResize( false ); + } + +void Client::setClientShown( bool shown ) + { + if( !shown ) + { + unmap( Allowed ); + hidden = true; + //updateVisibility(); + //updateAllowedActions(); + workspace()->updateFocusChains( this, Workspace::FocusChainMakeLast ); + addWorkspaceRepaint( visibleRect() ); + } + else + { + map( Allowed ); + hidden = false; + updateVisibility(); + //updateAllowedActions(); + takeFocus( Allowed ); + autoRaise(); + workspace()->updateFocusChains( this, Workspace::FocusChainMakeFirst ); + } + } + void Client::getWMHints() { XWMHints* hints = XGetWMHints( display(), window() ); diff --git a/client.h b/client.h index dc23312979..9c16443599 100644 --- a/client.h +++ b/client.h @@ -41,6 +41,7 @@ along with this program. If not, see . #include "kdecoration.h" #include "rules.h" #include "toplevel.h" +#include "clientgroup.h" #ifdef HAVE_XSYNC #include @@ -301,6 +302,24 @@ class Client StrutRect strutRect( StrutArea area ) const; StrutRects strutRects() const; bool hasStrut() const; + + // Tabbing functions + ClientGroup* clientGroup() const; // Returns a pointer to client_group + void setClientGroup( ClientGroup* group ); + /* + * If shown is true the client is mapped and raised, if false + * the client is unmapped and hidden, this function is called + * when the tabbing group of the client switches its visible + * client. + */ + void setClientShown( bool shown ); + /* + * When a click is done in the decoration and it calls the group + * to change the visible client it starts to move-resize the new + * client, this function stops it. + */ + void dontMoveResize(); + /** * Whether or not the window has a strut that expands through the invisible area of * an xinerama setup where the monitors are not the same resolution. @@ -570,6 +589,7 @@ class Client QString cap_normal, cap_iconic, cap_suffix; Group* in_group; Window window_group; + ClientGroup* client_group; Layer in_layer; QTimer* ping_timer; QProcess* process_killer; @@ -703,6 +723,16 @@ inline Group* Client::group() return in_group; } +inline void Client::setClientGroup( ClientGroup* group ) + { + client_group = group; + } + +inline ClientGroup* Client::clientGroup() const + { + return client_group; + } + inline bool Client::isMinimized() const { return minimized; @@ -715,7 +745,8 @@ inline bool Client::isActive() const inline bool Client::isShown( bool shaded_is_shown ) const { - return !isMinimized() && ( !isShade() || shaded_is_shown ) && !hidden; + return !isMinimized() && ( !isShade() || shaded_is_shown ) && !hidden && + ( clientGroup() == NULL || clientGroup()->visible() == this ); } inline bool Client::isHiddenInternal() const diff --git a/clientgroup.cpp b/clientgroup.cpp new file mode 100644 index 0000000000..7d4652aadd --- /dev/null +++ b/clientgroup.cpp @@ -0,0 +1,291 @@ +/******************************************************************************* +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*******************************************************************************/ + +#include "clientgroup.h" + +#include "client.h" +#include "effects.h" + +namespace KWin +{ + +ClientGroup::ClientGroup( Client *c ) + : clients_() + , items_() + , visible_( 0 ) + , minSize_( 0, 0 ) + , maxSize_( INT_MAX, INT_MAX ) + { + clients_.append( c ); + updateItems(); + updateMinMaxSize(); + Workspace::self()->addClientGroup( this ); + c->setClientShown( true ); // Ensure the client is visible + c->triggerDecorationRepaint(); // TODO: Required? Maybe for creating new group? + } + +ClientGroup::~ClientGroup() + { + Workspace::self()->removeClientGroup( this ); + } + +void ClientGroup::add( Client* c, int before, bool becomeVisible ) + { + if( contains( c ) || !c->workspace()->decorationSupportsClientGrouping() ) + return; + + // Client must not already be grouped + assert( c->clientGroup()->clients().size() == 1 ); + + // Tabbed windows MUST have a decoration + if( c->noBorder() ) + c->setNoBorder( false ); + if( clients_[visible_]->noBorder() ) + clients_[visible_]->setNoBorder( false ); + + // Notify effects of merge + if( effects != NULL ) + static_cast(effects)->clientGroupItemAdded( + c->effectWindow(), clients_[visible_]->effectWindow() ); + + // Delete old group and update + delete c->clientGroup(); // Delete old group as it's now empty + c->setClientGroup( this ); // Let the client know which group it belongs to + + // Actually add to new group + if( before >= 0 ) + { + if( visible_ >= before ) + visible_++; + clients_.insert( before, c ); + } + else + clients_.append( c ); + if( !becomeVisible ) // Hide before adding + c->setClientShown( false ); + updateItems(); + updateMinMaxSize(); + updateStates( clients_[visible_], c ); + + c->setGeometry( clients_[visible_]->geometry() ); + if( becomeVisible ) // Set visible after settings geometry + setVisible( c ); + + // Activate the new visible window + clients_[visible_]->setActive( true ); + //clients_[visible_]->takeFocus( Allowed ); + //clients_[visible_]->workspace()->raiseClient( clients_[visible_] ); + + clients_[visible_]->triggerDecorationRepaint(); + } + +void ClientGroup::remove( int index, const QRect& newGeom ) + { + remove( clients_[index], newGeom ); + } + +void ClientGroup::remove( Client* c, const QRect& newGeom ) + { + if( !c ) + return; + if( clients_.count() < 2 ) + { + c->setClientGroup( NULL ); + Workspace::self()->removeClientGroup( this ); // Remove immediately + delete this; + return; + } + + ClientList::const_iterator i; + Client* newVisible = clients_[visible_]; + if( newVisible == c ) + newVisible = ( visible_ != clients_.size() - 1 ) ? clients_[visible_ + 1] : clients_[visible_ - 1]; + + // Notify effects of removal + if( effects ) + static_cast(effects)->clientGroupItemRemoved( + c->effectWindow(), newVisible->effectWindow() ); + + setVisible( newVisible ); // Display new window before removing old one + clients_.removeAll( c ); + visible_ = indexOfClient( newVisible ); // Index may have changed + updateItems(); + updateMinMaxSize(); + + c->setClientGroup( new ClientGroup( c )); + if( newGeom.isValid() ) + c->setGeometry( newGeom ); + + newVisible->triggerDecorationRepaint(); + } + +void ClientGroup::removeAll() + { + while( clients_.count() > 1 ) + remove( clients_.at( 1 )); + } + +void ClientGroup::closeAll() + { + Client* front; + ClientList list( clients_ ); + while( !list.isEmpty() ) + { + front = list.front(); + list.pop_front(); + if( front != clients_[visible_] ) + front->closeWindow(); + } + clients_[visible_]->closeWindow(); + } + +void ClientGroup::move( int index, int before ) + { + move( clients_[index], ( before >= 0 && before < clients_.size() ) ? clients_[before] : NULL ); + } + +void ClientGroup::move( Client* c, Client* before ) + { + if( c == before ) // Impossible to do + return; + + Client* wasVisible = clients_[visible_]; + clients_.removeAll( c ); + clients_.insert( before ? indexOfClient( before ) : clients_.size(), c ); + visible_ = indexOfClient( wasVisible ); + updateItems(); + + clients_[visible_]->triggerDecorationRepaint(); + } + +void ClientGroup::displayClientMenu( int index, const QPoint& pos ) + { + displayClientMenu( clients_[index], pos ); + } + +void ClientGroup::displayClientMenu( Client* c, const QPoint& pos ) + { + c->workspace()->showWindowMenu( pos, c ); + } + +bool ClientGroup::containsActiveClient() + { + return contains( Workspace::self()->activeClient() ); + } + +void ClientGroup::setVisible( int index ) + { + setVisible( clients_[index] ); + } + +void ClientGroup::setVisible( Client* c ) + { + if( c == clients_[visible_] || !contains( c )) + return; + + // Notify effects of switch + if( effects != NULL ) + static_cast(effects)->clientGroupItemSwitched( + clients_[visible_]->effectWindow(), c->effectWindow() ); + + visible_ = indexOfClient( c ); + c->setClientShown( true ); + for( ClientList::const_iterator i = clients_.begin(); i != clients_.end(); i++ ) + if( (*i) != c ) + (*i)->setClientShown( false ); + } + +void ClientGroup::updateStates( Client* main, Client* only ) + { + for( ClientList::const_iterator i = clients_.begin(); i != clients_.end(); i++ ) + if( (*i) != main && ( !only || (*i) == only )) + { + if( (*i)->isMinimized() != main->isMinimized() ) + { + if( main->isMinimized() ) + (*i)->minimize( true ); + else + (*i)->unminimize( true ); + } + if( (*i)->isShade() != main->isShade() ) + (*i)->setShade( main->isShade() ? ShadeNormal : ShadeNone ); + if( (*i)->geometry() != main->geometry() ) + (*i)->setGeometry( main->geometry() ); + if( (*i)->desktop() != main->desktop() ) + (*i)->setDesktop( main->desktop() ); + if( (*i)->isOnAllDesktops() != main->isOnAllDesktops() ) + (*i)->setOnAllDesktops( main->isOnAllDesktops() ); + if( (*i)->keepAbove() != main->keepAbove() ) + (*i)->setKeepAbove( main->keepAbove() ); + if( (*i)->keepBelow() != main->keepBelow() ) + (*i)->setKeepBelow( main->keepBelow() ); + } + } + +void ClientGroup::updateItems() + { + items_.clear(); + for( ClientList::const_iterator i = clients_.begin(); i != clients_.end(); i++ ) + { + QIcon icon( (*i)->icon() ); + icon.addPixmap( (*i)->miniIcon() ); + items_.append( ClientGroupItem( (*i)->caption(), icon )); + } + } + +void ClientGroup::updateMinMaxSize() + { + // Determine entire group's minimum and maximum sizes + minSize_ = QSize( 0, 0 ); + maxSize_ = QSize( INT_MAX, INT_MAX ); + for( ClientList::const_iterator i = clients_.begin(); i != clients_.end(); i++ ) + { + if( (*i)->minSize().width() > minSize_.width() ) + minSize_.setWidth( (*i)->minSize().width() ); + if( (*i)->minSize().height() > minSize_.height() ) + minSize_.setHeight( (*i)->minSize().height() ); + if( (*i)->maxSize().width() < maxSize_.width() ) + maxSize_.setWidth( (*i)->maxSize().width() ); + if( (*i)->maxSize().height() < maxSize_.height() ) + maxSize_.setHeight( (*i)->maxSize().height() ); + } + if( minSize_.width() > maxSize_.width() || + minSize_.height() > maxSize_.height() ) + { + kWarning(1212) << "ClientGroup's min size is greater than its' max size. Setting max to min."; + maxSize_ = minSize_; + } + + // Ensure all windows are within these sizes + const QSize size = clients_[visible_]->size(); + QSize newSize( + qBound( minSize_.width(), size.width(), maxSize_.width() ), + qBound( minSize_.height(), size.height(), maxSize_.height() )); + if( newSize != size ) + for( ClientList::const_iterator i = clients_.begin(); i != clients_.end(); i++ ) + // TODO: Doesn't affect shaded windows? + // There seems to be a race condition when using plainResize() which causes the window + // to sometimes be located at new window's location instead of the visible window's location + // when a window with a large min size is added to a group with a small window size. + (*i)->setGeometry( QRect( clients_[visible_]->pos(), newSize )); + } + +} diff --git a/clientgroup.h b/clientgroup.h new file mode 100644 index 0000000000..b65255c3c0 --- /dev/null +++ b/clientgroup.h @@ -0,0 +1,220 @@ +/******************************************************************************* +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*******************************************************************************/ + +#ifndef KWIN_CLIENTGROUP_H +#define KWIN_CLIENTGROUP_H + +#include "kdecoration.h" +#include "utils.h" + +namespace KWin +{ + +class Client; + +/** + * This class represents a group of clients for use in window tabbing. All + * clients in the group share the same geometry and state information; I.e if + * one client changes then all others should also be changed. + * + * Workspace::clientGroups is a list of all currently-existing client groups. + * + * A group MUST contain at least one client and MUST NOT contain multiple + * copies of the same client. A client MUST NOT be in two groups at the same + * time. All decorated clients SHOULD be in a group, even if it's a group of + * one client. + * + * If a group contains multiple clients then only one will ever be mapped at + * any given time. + */ +class ClientGroup + { + public: + /** + * Creates a new group containing \p c. + */ + ClientGroup( Client* c ); + ~ClientGroup(); + + /** + * Adds \p c to the group before \p before in the list. If \p becomeVisible is \i true then + * the added client will become also the visible client. + */ + void add( Client* c, int before = -1, bool becomeVisible = false ); + /** + * Remove the client at index \p index from the group. If \p newGeom is set then the client + * will move and resize to the specified geometry, otherwise it will stay where the group + * is located. + */ + void remove( int index, const QRect& newGeom = QRect() ); + /** + * Remove \p c from the group. If \p newGeom is set then the client will move and resize to + * the specified geometry, otherwise it will stay where the group is located. + */ + void remove( Client* c, const QRect& newGeom = QRect() ); + /** + * Remove all clients from this group. Results in all clients except the first being moved + to a group of their own. + */ + void removeAll(); + /** + * Close all clients in this group. + */ + void closeAll(); + /** + * Move the client at index \p index to the position before the client at index \p before + * in the list. + */ + void move( int index, int before ); + /** + * Move \p c to the position before \p before in the list. + */ + void move( Client* c, Client* before ); + /** + * Display the right-click client menu belonging to the client at index \p index at the + * global coordinates specified by \p pos. + */ + void displayClientMenu( int index, const QPoint& pos ); + /** + * Display the right-click client menu belonging to \p c at the global coordinates + * specified by \p pos. + */ + void displayClientMenu( Client* c, const QPoint& pos ); + + /** + * Returns the list index of \p c. + */ + int indexOfClient( Client* c ); + /** + * Returns the list index of the currently visible client in the group. + */ + int indexOfVisibleClient(); + /** + * Returns whether or not this group contains \p c. + */ + bool contains( Client* c ); + /** + * Returns whether or not this group contains the active client. + */ + bool containsActiveClient(); + + /** + * Returns the list of all the clients contained in this group in their current order. + */ + ClientList clients() const; + /** + * Returns a list of the captions and icons of all the clients contained in this group + * in their current order. + */ + QList< ClientGroupItem > items() const; + + /** + * Returns the currently visible client. + */ + Client* visible(); + /** + * Makes the client at index \p index the visible one in the group. + */ + void setVisible( int index ); + /** + * Makes \p c the visible client in the group. + */ + void setVisible( Client* c ); + + /** + * Returns combined minimum size of all clients in the group. + */ + QSize minSize() const; + /** + * Returns combined maximum size of all clients in the group. + */ + QSize maxSize() const; + + /** + * Ensures that all the clients in the group have identical geometries and states using + * \p main as the primary client to copy the settings off. If \p only is set then only + * that client is updated to match \p main. + */ + void updateStates( Client* main, Client* only = NULL ); + + private: + /** + * Regenerate the list of client captions and icons. + */ + void updateItems(); + /** + * Determine the combined minimum and maximum sizes of all clients in the group. + */ + void updateMinMaxSize(); + + ClientList clients_; + QList< ClientGroupItem > items_; + int visible_; + + QSize minSize_; + QSize maxSize_; + + friend class Client; + }; + +inline int ClientGroup::indexOfClient( Client* c ) + { + return clients_.indexOf( c ); + } + +inline int ClientGroup::indexOfVisibleClient() + { + return visible_; + } + +inline bool ClientGroup::contains( Client* c ) + { + return clients_.contains( c ); + } + +inline ClientList ClientGroup::clients() const + { + return clients_; + } + +inline QList< ClientGroupItem > ClientGroup::items() const + { + return items_; + } + +inline Client* ClientGroup::visible() + { + return clients_[visible_]; + } + +inline QSize ClientGroup::minSize() const + { + return minSize_; + } + +inline QSize ClientGroup::maxSize() const + { + return maxSize_; + } + +} + +#endif diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 1dc3d48e8a..0ed9bf95cd 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory( oxygen ) add_subdirectory( quartz ) add_subdirectory( redmond ) add_subdirectory( web ) +add_subdirectory( tabstrip ) diff --git a/clients/oxygen/CMakeLists.txt b/clients/oxygen/CMakeLists.txt index 7323732920..306b7d27ce 100644 --- a/clients/oxygen/CMakeLists.txt +++ b/clients/oxygen/CMakeLists.txt @@ -9,6 +9,7 @@ set(kwin_oxygen_SRCS oxygen.cpp oxygenbutton.cpp oxygenclient.cpp + oxygenclientgroupitemdata.cpp oxygenconfiguration.cpp oxygenexception.cpp oxygenexceptionlist.cpp diff --git a/clients/oxygen/config/config.cpp b/clients/oxygen/config/config.cpp index 609e002da9..3d0146b8cc 100644 --- a/clients/oxygen/config/config.cpp +++ b/clients/oxygen/config/config.cpp @@ -129,6 +129,7 @@ namespace Oxygen configurationGroup.writeEntry( OxygenConfig::DRAW_SEPARATOR, userInterface_->ui.drawSeparator->isChecked() ); configurationGroup.writeEntry( OxygenConfig::DRAW_TITLE_OUTLINE, userInterface_->ui.titleOutline->isChecked() ); configurationGroup.writeEntry( OxygenConfig::USE_OXYGEN_SHADOWS, userInterface_->ui.useOxygenShadows->isChecked() ); + configurationGroup.writeEntry( OxygenConfig::TABS_ENABLED, userInterface_->ui.tabsEnabled->isChecked() ); // write exceptions userInterface_->ui.exceptions->exceptions().write( *configuration_ ); @@ -191,6 +192,7 @@ namespace Oxygen userInterface_->ui.drawSeparator->setChecked( configuration.drawSeparator() ); userInterface_->ui.titleOutline->setChecked( configuration.drawTitleOutline() ); userInterface_->ui.useOxygenShadows->setChecked( configuration.useOxygenShadows() ); + userInterface_->ui.tabsEnabled->setChecked( configuration.tabsEnabled() ); } diff --git a/clients/oxygen/config/oxygenconfigurationui.cpp b/clients/oxygen/config/oxygenconfigurationui.cpp index 3ec9d74cc7..2a25ab00a3 100644 --- a/clients/oxygen/config/oxygenconfigurationui.cpp +++ b/clients/oxygen/config/oxygenconfigurationui.cpp @@ -109,6 +109,7 @@ namespace Oxygen connect( ui.blendColor, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()) ); connect( ui.sizeGripMode, SIGNAL(currentIndexChanged(int)), SIGNAL(changed()) ); + connect( ui.tabsEnabled, SIGNAL(clicked()), SIGNAL(changed()) ); connect( ui.drawSeparator, SIGNAL(clicked()), SIGNAL(changed()) ); connect( ui.titleOutline, SIGNAL(clicked()), SIGNAL(changed()) ); connect( ui.useOxygenShadows, SIGNAL(clicked()), SIGNAL(changed()) ); diff --git a/clients/oxygen/config/oxygenconfigurationui.ui b/clients/oxygen/config/oxygenconfigurationui.ui index 8d0641d59a..991788cbb8 100644 --- a/clients/oxygen/config/oxygenconfigurationui.ui +++ b/clients/oxygen/config/oxygenconfigurationui.ui @@ -73,7 +73,7 @@ - + Qt::Vertical @@ -86,6 +86,13 @@ + + + + Enable window grouping + + + diff --git a/clients/oxygen/oxygen.cpp b/clients/oxygen/oxygen.cpp index 0d7a53ad1a..ee96a1d585 100644 --- a/clients/oxygen/oxygen.cpp +++ b/clients/oxygen/oxygen.cpp @@ -60,11 +60,7 @@ namespace Oxygen //___________________________________________________ KDecoration* OxygenFactory::createDecoration(KDecorationBridge* bridge ) - { - OxygenClient* client( new OxygenClient( bridge, this ) ); - connect( this, SIGNAL( configurationChanged() ), client, SLOT( resetConfiguration() ) ); - return client->decoration(); - } + { return (new OxygenClient( bridge, this ))->decoration(); } //___________________________________________________ bool OxygenFactory::reset(unsigned long changed) @@ -75,7 +71,6 @@ namespace Oxygen bool configuration_changed = readConfig(); setInitialized( true ); - emit configurationChanged(); if( configuration_changed || (changed & (SettingDecoration | SettingButtons | SettingBorder)) ) { @@ -174,6 +169,10 @@ namespace Oxygen case AbilityUsesAlphaChannel: return true; + // tabs + case AbilityClientGrouping: + return defaultConfiguration().tabsEnabled(); + // no colors supported at this time default: return false; diff --git a/clients/oxygen/oxygen.h b/clients/oxygen/oxygen.h index b618ac1c44..b7b1d12de0 100644 --- a/clients/oxygen/oxygen.h +++ b/clients/oxygen/oxygen.h @@ -62,7 +62,6 @@ namespace Oxygen // shows the window menu for one tab ButtonItemMenu - }; Q_DECLARE_FLAGS(ButtonTypes, ButtonType) @@ -82,10 +81,6 @@ namespace Oxygen HFRAMESIZE = 4 }; - #if !KDE_IS_VERSION(4,3,90) - enum{ SettingCompositing = 1 << 6 }; - #endif - //! window decoration factory class OxygenFactory: public QObject, public KDecorationFactoryUnstable { @@ -124,18 +119,13 @@ namespace Oxygen //! get configuration for a give client virtual OxygenConfiguration configuration( const OxygenClient& ); - signals: - - //! configuration has changed - void configurationChanged( void ); - private: //! read configuration from KConfig bool readConfig(); //! default configuration - const OxygenConfiguration& defaultConfiguration( void ) + const OxygenConfiguration& defaultConfiguration( void ) const { return defaultConfiguration_; } //! initialization diff --git a/clients/oxygen/oxygenbutton.cpp b/clients/oxygen/oxygenbutton.cpp index d9f7f658dd..73dd52d914 100644 --- a/clients/oxygen/oxygenbutton.cpp +++ b/clients/oxygen/oxygenbutton.cpp @@ -38,7 +38,6 @@ #include #include #include -#include namespace Oxygen { @@ -52,7 +51,7 @@ namespace Oxygen helper_( parent.helper() ), type_(type), forceInactive_( false ), - timeLine_( 200, this ) + timeLine_( 150, this ) { setAutoFillBackground(false); setAttribute(Qt::WA_NoSystemBackground); @@ -69,6 +68,7 @@ namespace Oxygen timeLine_.setCurveShape( QTimeLine::EaseInOutCurve ); connect( &timeLine_, SIGNAL( frameChanged( int ) ), SLOT( update() ) ); connect( &timeLine_, SIGNAL( finished() ), SLOT( update() ) ); + reset(0); } @@ -79,11 +79,11 @@ namespace Oxygen //_______________________________________________ QColor OxygenButton::buttonDetailColor(const QPalette &palette) { - if( client_.timeLineIsRunning() && !forceInactive_ ) return KColorUtils::mix( + if( client_.timeLineIsRunning() && !forceInactive_ && !client_.isForcedActive()) return KColorUtils::mix( buttonDetailColor( palette, false ), buttonDetailColor( palette, true ), client_.opacity() ); - else return buttonDetailColor( palette, isActive() ); + else return buttonDetailColor( palette, isActive() || client_.isForcedActive() ); } //_______________________________________________ @@ -115,6 +115,12 @@ namespace Oxygen return QSize( size, size ); } + //___________________________________________________ + void OxygenButton::reset( unsigned long ) + { + timeLine_.setDuration( client_.configuration().animationsDuration() ); + } + //___________________________________________________ void OxygenButton::enterEvent(QEvent *e) { diff --git a/clients/oxygen/oxygenbutton.h b/clients/oxygen/oxygenbutton.h index 89340b5c9b..e80ff0ecc2 100644 --- a/clients/oxygen/oxygenbutton.h +++ b/clients/oxygen/oxygenbutton.h @@ -63,10 +63,6 @@ namespace Oxygen //! destructor QSize sizeHint() const; - //! reset - void reset(long unsigned int) - {repaint();} - //! button type ButtonType type( void ) const { return type_; } @@ -76,6 +72,9 @@ namespace Oxygen void setForceInactive( const bool& value ) { forceInactive_ = value; } + //! configuration reset + virtual void reset( unsigned long ); + protected: //! press event diff --git a/clients/oxygen/oxygenclient.cpp b/clients/oxygen/oxygenclient.cpp index b680c76cee..a08257d7d2 100644 --- a/clients/oxygen/oxygenclient.cpp +++ b/clients/oxygen/oxygenclient.cpp @@ -36,14 +36,13 @@ #include #include -#include #include #include -#include +#include #include #include -#include +#include namespace Oxygen { @@ -86,7 +85,11 @@ namespace Oxygen sizeGrip_( 0 ), timeLine_( 200, this ), titleTimeLine_( 200, this ), - initialized_( false ) + initialized_( false ), + forceActive_( false ), + mouseButton_( Qt::NoButton ), + itemData_( this ), + sourceItem_( -1 ) {} //___________________________________________ @@ -109,12 +112,14 @@ namespace Oxygen KCommonDecoration::init(); widget()->setAttribute(Qt::WA_NoSystemBackground ); widget()->setAutoFillBackground( false ); + widget()->setAcceptDrops( true ); // initialize timeLine timeLine_.setFrameRange( maxAnimationIndex/5, maxAnimationIndex ); timeLine_.setCurveShape( QTimeLine::EaseOutCurve ); connect( &timeLine_, SIGNAL( frameChanged( int ) ), widget(), SLOT( update() ) ); connect( &timeLine_, SIGNAL( finished() ), widget(), SLOT( update() ) ); + connect( &timeLine_, SIGNAL( finished() ), this, SLOT( clearForceActive() ) ); // initialize titleTimeLine titleTimeLine_.setFrameRange( 0, maxAnimationIndex ); @@ -123,7 +128,8 @@ namespace Oxygen connect( &titleTimeLine_, SIGNAL( finished() ), widget(), SLOT( update() ) ); connect( &titleTimeLine_, SIGNAL( finished() ), this, SLOT( updateOldCaption() ) ); - initialized_ = true; + // lists + connect( &itemData_.timeLine(), SIGNAL( finished() ), this, SLOT( clearTargetItem() ) ); // in case of preview, one wants to make the label used // for the central widget transparent. This allows one to have @@ -139,20 +145,57 @@ namespace Oxygen } - resetConfiguration(); + initialized_ = true; + + // first reset is needed to store Oxygen configuration + reset(0); } //___________________________________________ void OxygenClient::reset( unsigned long changed ) { + KCommonDecorationUnstable::reset( changed ); + + // update window mask when compositing is changed + if( !initialized_ ) return; if( changed & SettingCompositing ) { updateWindowShape(); widget()->update(); } - KCommonDecorationUnstable::reset( changed ); + configuration_ = factory_->configuration( *this ); + + // animations duration + timeLine_.setDuration( configuration_.animationsDuration() ); + titleTimeLine_.setDuration( configuration_.animationsDuration() ); + itemData_.timeLine().setDuration( configuration_.animationsDuration() ); + + // should also update animations for buttons + resetButtons(); + + // also reset tab buttons + for( int index = 0; index < itemData_.count(); index++ ) + { + ClientGroupItemData& item( itemData_[index] ); + if( item.closeButton_ ) { item.closeButton_.data()->reset(0); } + } + + // copy current caption to old + updateOldCaption(); + + // reset tab geometry + itemData_.setDirty( true ); + + // handle size grip + if( configuration_.drawSizeGrip() ) + { + + if( !hasSizeGrip() ) createSizeGrip(); + + } else if( hasSizeGrip() ) deleteSizeGrip(); + } //___________________________________________ @@ -175,7 +218,9 @@ namespace Oxygen //_________________________________________________________ KCommonDecorationButton *OxygenClient::createButton(::ButtonType type) { + switch (type) { + case MenuButton: return new OxygenButton(*this, i18n("Menu"), ButtonMenu); @@ -203,9 +248,12 @@ namespace Oxygen case ShadeButton: return new OxygenButton(*this, i18n("Shade Button"), ButtonShade); - default: - return 0; + default: break; + } + + return NULL; + } //_________________________________________________________ @@ -341,11 +389,22 @@ namespace Oxygen } //_________________________________________________________ - QRect OxygenClient::titleBoundingRect( QPainter* painter, const QRect& rect, const QString& caption ) const + QRect OxygenClient::titleBoundingRect( const QFont& font, QRect rect, const QString& caption ) const { + // check bounding rect against titleRect + // conflicts can happen only if there is only one item in group + if( itemData_.count() == 1 ) + { + QRect titleRect( OxygenClient::titleRect() ); + if( titleRect.left() > rect.left() ) { rect.setLeft( titleRect.left() ); } + if( titleRect.right() < rect.right() ) { rect.setRight( titleRect.right() ); } + + } + + // get title bounding rect - QRect boundingRect = painter->boundingRect( rect, configuration().titleAlignment() | Qt::AlignVCenter, caption ); + QRect boundingRect = QFontMetrics( font ).boundingRect( rect, configuration().titleAlignment() | Qt::AlignVCenter, caption ); // adjust to make sure bounding rect // 1/ has same vertical alignment as original titleRect @@ -353,6 +412,7 @@ namespace Oxygen boundingRect.setTop( rect.top() ); boundingRect.setBottom( rect.bottom() ); + // check bounding rect against input rect if( rect.left() > boundingRect.left() ) { boundingRect.setLeft( rect.left() ); } if( rect.right() < boundingRect.right() ) { boundingRect.setRight( rect.right() ); } @@ -360,6 +420,116 @@ namespace Oxygen } + //_________________________________________________________ + void OxygenClient::clearTargetItem( void ) + { + + if( !itemData_.animated() ) return; + if( itemData_.animationType() == AnimationLeave ) + { itemData_.setDirty( true ); } + + } + + //_________________________________________________________ + void OxygenClient::updateItemBoundingRects( bool alsoUpdate ) + { + + // make sure items are not animated + itemData_.animate( AnimationNone ); + + // maximum available space + QRect titleRect( OxygenClient::titleRect() ); + + // get tabs + int items( clientGroupItems().count() ); + + // make sure item data have the correct number of items + while( itemData_.count() < items ) itemData_.push_back( ClientGroupItemData() ); + while( itemData_.count() > items ) + { + if( itemData_.back().closeButton_ ) delete itemData_.back().closeButton_.data(); + itemData_.pop_back(); + } + + assert( !itemData_.isEmpty() ); + + // create buttons + if( itemData_.count() == 1 ) + { + + // remove button + if( itemData_.front().closeButton_ ) + { delete itemData_.front().closeButton_.data(); } + + // set active rect + itemData_.front().activeRect_ = titleRect.adjusted( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ); + + } else { + + int left( titleRect.left() ); + int width( titleRect.width()/items ); + for( int index = 0; index < itemData_.count(); index++ ) + { + + ClientGroupItemData& item(itemData_[index]); + + // make sure button exists + if( !item.closeButton_ ) + { + item.closeButton_ = ClientGroupItemData::ButtonPointer( new OxygenButton( *this, "Close this tab", ButtonItemClose ) ); + item.closeButton_.data()->show(); + item.closeButton_.data()->installEventFilter( this ); + } + + // set active rect + QRect local( QPoint( left, titleRect.top() ), QSize( width, titleRect.height() ) ); + local.adjust( 0, -layoutMetric( LM_TitleEdgeTop ), 0, 0 ); + item.activeRect_ = local; + left += width; + + } + + } + + if( itemData_.count() == 1 ) + { + + QRect active( itemData_.front().activeRect_ ); + if( configuration().drawTitleOutline() && isActive() ) + { + + QRect textRect( titleBoundingRect( options()->font( true, false), active, caption() ) ); + active.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) ); + active.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) ); + + } else { + + active.setLeft( widget()->rect().left() + layoutMetric( LM_OuterPaddingLeft ) ); + active.setRight( widget()->rect().right() - layoutMetric( LM_OuterPaddingRight ) ); + + } + + // assign to item + itemData_.front().reset( active ); + + } else { + + for( int index = 0; index < itemData_.count(); index++ ) + { itemData_[index].reset( itemData_[index].activeRect_ ); } + + } + + // button activity + itemData_.updateButtonActivity( visibleClientGroupItem() ); + + // reset buttons location + itemData_.updateButtons( alsoUpdate ); + itemData_.setDirty( false ); + + return; + + } + //_________________________________________________________ QColor OxygenClient::titlebarTextColor(const QPalette &palette) { @@ -391,11 +561,6 @@ namespace Oxygen } - - //_________________________________________________________ - QColor OxygenClient::titlebarContrastColor(const QPalette &palette) - { return helper().calcLightColor( palette.color( widget()->window()->backgroundRole() ) ); } - //_________________________________________________________ void OxygenClient::renderWindowBackground( QPainter* painter, const QRect& rect, const QWidget* widget, const QPalette& palette ) const { @@ -421,7 +586,7 @@ namespace Oxygen { // check if outline is needed - if( !( isActive() && configuration().drawTitleOutline() ) ) return; + if( clientGroupItems().count() < 2 && !itemData_.animated() && !( isActive() && configuration().drawTitleOutline() ) ) return; // get coordinates relative to the client area // this is annoying. One could use mapTo if this was taking const QWidget* and not @@ -453,6 +618,22 @@ namespace Oxygen // title height int titleHeight( layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleEdgeBottom ) + layoutMetric( LM_TitleHeight ) ); + // make titlebar background darker for tabbed, non-outline window + if( ( clientGroupItems().count() >= 2 || itemData_.animated() ) && !(configuration().drawTitleOutline() && isActive() ) ) + { + + QPoint topLeft( r.topLeft()-position ); + QRect rect( topLeft, QSize( r.width(), titleHeight ) ); + + QLinearGradient lg( rect.topLeft(), rect.bottomLeft() ); + lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) ); + lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) ); + painter->setBrush( lg ); + painter->setPen( Qt::NoPen ); + painter->drawRect( rect ); + + } + // horizontal line { int shadowSize = 7; @@ -470,7 +651,7 @@ namespace Oxygen } - if( configuration().drawTitleOutline() ) + if( configuration().drawTitleOutline() && isActive() ) { // save mask and frame to where @@ -592,7 +773,7 @@ namespace Oxygen // center (for active windows only) { painter->save(); - int offset = 2; + int offset = 1; int voffset = 1; QRect adjustedRect( rect.adjusted( offset, voffset, -offset, 1 ) ); @@ -608,7 +789,7 @@ namespace Oxygen // shadow int shadowSize = 7; - int offset = -2; + int offset = -3; int voffset = 5-shadowSize; QRect adjustedRect( rect.adjusted(offset, voffset, -offset, shadowSize) ); helper().slab( palette.color( widget()->backgroundRole() ), 0, shadowSize )->render( adjustedRect, painter, TileSet::Top|TileSet::Left|TileSet::Right ); @@ -668,6 +849,135 @@ namespace Oxygen } + //_______________________________________________________________________ + void OxygenClient::renderItem( QPainter* painter, int index, const QPalette& palette ) + { + + const ClientGroupItemData& item( itemData_[index] ); + + // see if tag is active + int itemCount( itemData_.count() ); + + // + if( !item.boundingRect_.isValid() ) return; + + // create rect in which text is to be drawn + QRect textRect( item.boundingRect_.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) ); + + // add extra space needed for title outline + if( itemCount > 1 || itemData_.animated() ) + { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); } + + // add extra space for the button + if( itemCount > 1 ) + { textRect.adjust( 0, 0, - configuration().buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); } + + // check if current item is active + bool active( index == visibleClientGroupItem() ); + + // get current item caption and update text rect + const QList< ClientGroupItem >& items( clientGroupItems() ); + QString caption( itemCount == 1 ? OxygenClient::caption() : items[index].title() ); + + // title outline + if( itemCount == 1 ) { + + textRect = titleBoundingRect( painter->font(), textRect, caption ); + if( itemData_.animated() ) { + + renderTitleOutline( painter, item.boundingRect_, palette ); + + } else if( isActive() && configuration().drawTitleOutline() ) { + + // adjusts boundingRect accordingly + QRect boundingRect( item.boundingRect_ ); + boundingRect.setLeft( textRect.left() - layoutMetric( LM_TitleBorderLeft ) ); + boundingRect.setRight( textRect.right() + layoutMetric( LM_TitleBorderRight ) ); + + // render bounding rect around it with extra margins + renderTitleOutline( painter, boundingRect, palette ); + + } + + } else if( active ) { + + // in multiple tabs render title outline in all cases + renderTitleOutline( painter, item.boundingRect_, palette ); + + } + + // render text + if( active || itemCount == 1 ) + { + + // for active tab, current caption is "merged" with old caption, if any + renderTitleText( + painter, textRect, + titlebarTextColor( palette ), + titlebarContrastColor( palette ) ); + + + } else { + + QColor background( backgroundPalette( widget(), palette ).color( widget()->window()->backgroundRole() ) ); + + // add extra shade (as used in renderWindowBorder + if( !( isActive() && configuration().drawTitleOutline() ) ) + { background = KColorUtils::mix( background, Qt::black, 0.10 ); } + + // otherwise current caption is rendered directly + renderTitleText( + painter, textRect, caption, + titlebarTextColor( backgroundPalette( widget(), palette ), false ), + titlebarContrastColor( background ) ); + + } + + // render separators between inactive tabs + if( !( active || itemCount == 1 ) ) + { + + // separators + // draw Left separator + QColor color( backgroundPalette( widget(), palette ).window().color() ); + if( index != visibleClientGroupItem() && ( ( index == 0 && buttonsLeftWidth() > 0 ) || itemData_.isTarget( index ) ) ) + { + + QRect local( item.boundingRect_.topLeft()+QPoint(0,2), QSize( 2, item.boundingRect_.height()-3 ) ); + helper().drawSeparator( painter, local, color, Qt::Vertical); + + } + + // draw right separator + if( + ( index == itemCount-1 && buttonsRightWidth() > 0 ) || + ( index+1 < itemCount && ( itemData_.isTarget( index+1 ) || + index+1 != visibleClientGroupItem() ) ) ) + { + + QRect local( item.boundingRect_.topRight()+QPoint(0,2), QSize( 2, item.boundingRect_.height()-3 ) ); + helper().drawSeparator( painter, local, color, Qt::Vertical); + + } + + } + + } + + //_______________________________________________________________________ + void OxygenClient::renderTargetRect( QPainter* p, const QPalette& palette ) + { + if( itemData_.targetRect().isNull() || itemData_.timeLineIsRunning() ) return; + + p->save(); + QColor color = palette.color(QPalette::Highlight); + p->setPen(KColorUtils::mix(color, palette.color(QPalette::Active, QPalette::WindowText))); + p->setBrush( helper().alphaColor( color, 0.5 ) ); + p->drawRect( itemData_.targetRect().adjusted( 4, 2, -4, -2 ) ); + p->restore(); + + } + //_______________________________________________________________________ void OxygenClient::renderFloatFrame( QPainter* painter, const QRect& frame, const QPalette& palette ) const { @@ -812,7 +1122,7 @@ namespace Oxygen if( configuration().drawTitleOutline() ) { - if( timeLineIsRunning() ) + if( timeLineIsRunning() && !isForcedActive() ) { QColor inactiveColor( backgroundColor( widget, palette, false ) ); @@ -821,7 +1131,7 @@ namespace Oxygen palette.setColor( widget->window()->backgroundRole(), mixed ); palette.setColor( QPalette::Button, mixed ); - } else if( isActive() ) { + } else if( isActive() || isForcedActive() ) { QColor color = options()->color( KDecorationDefines::ColorTitleBar, true ); palette.setColor( widget->window()->backgroundRole(), color ); @@ -863,30 +1173,75 @@ namespace Oxygen } - - //___________________________________________ - void OxygenClient::resetConfiguration( void ) + //______________________________________________________________________________ + bool OxygenClient::eventFilter( QObject* object, QEvent* event ) { - if( !initialized_ ) return; + // all dedicated event filtering is here to handle multiple tabs. + // if tabs are disabled, do nothing + if( !configuration().tabsEnabled() ) + { return KCommonDecorationUnstable::eventFilter( object, event ); } - configuration_ = factory_->configuration( *this ); - - // animations duration - timeLine_.setDuration( configuration_.animationsDuration() ); - titleTimeLine_.setDuration( configuration_.animationsDuration() ); - - // need to update old caption - updateOldCaption(); - - // handle size grip - if( configuration_.drawSizeGrip() ) + bool state = false; + switch( event->type() ) { - if( !hasSizeGrip() ) createSizeGrip(); + case QEvent::Show: + if( widget() == object ) + { itemData_.setDirty( true ); } + break; - } else if( hasSizeGrip() ) deleteSizeGrip(); + case QEvent::MouseButtonPress: + if( widget() == object ) + { state = mousePressEvent( static_cast< QMouseEvent* >( event ) ); } + break; + case QEvent::MouseButtonRelease: + if( widget() == object ) state = mouseReleaseEvent( static_cast< QMouseEvent* >( event ) ); + else if( OxygenButton *btn = qobject_cast< OxygenButton* >( object ) ) + { + if( static_cast< QMouseEvent* >( event )->button() == Qt::LeftButton ) + { state = closeItem( btn ); } + } + + break; + + case QEvent::MouseMove: + state = mouseMoveEvent( static_cast< QMouseEvent* >( event ) ); + break; + + case QEvent::DragEnter: + if( widget() == object ) + { state = dragEnterEvent( static_cast< QDragEnterEvent* >( event ) ); } + break; + + case QEvent::DragMove: + if( widget() == object ) + { state = dragMoveEvent( static_cast< QDragMoveEvent* >( event ) ); } + break; + + case QEvent::DragLeave: + if( widget() == object ) + { state = dragLeaveEvent( static_cast< QDragLeaveEvent* >( event ) ); } + break; + + case QEvent::Drop: + if( widget() == object ) + { state = dropEvent( static_cast< QDropEvent* >( event ) ); } + break; + + default: break; + + } + return state || KCommonDecorationUnstable::eventFilter( object, event ); + + } + + //_________________________________________________________ + void OxygenClient::resizeEvent( QResizeEvent* event ) + { + itemData_.setDirty( true ); + KCommonDecorationUnstable::resizeEvent( event ); } //_________________________________________________________ @@ -915,7 +1270,7 @@ namespace Oxygen { TileSet *tileSet( 0 ); - if( configuration().useOxygenShadows() && timeLineIsRunning() ) + if( configuration().useOxygenShadows() && timeLineIsRunning() && !isForcedActive() ) { int frame = timeLine_.currentFrame(); @@ -969,6 +1324,14 @@ namespace Oxygen } + // make sure ItemData and clientGroupItems are synchronized + /* + this needs to be done before calling RenderWindowBorder + since some painting in there depend on the clientGroups state + */ + if( itemData_.isDirty() || itemData_.count() != clientGroupItems().count() ) + { updateItemBoundingRects( false ); } + // window background renderWindowBackground( &painter, frame, widget(), backgroundPalette( widget(), palette ) ); renderWindowBorder( &painter, frame, widget(), palette ); @@ -985,13 +1348,9 @@ namespace Oxygen painter.save(); painter.setRenderHint(QPainter::Antialiasing); - // float frame renderFloatFrame( &painter, frame, palette ); - // clipping - if( compositingActive() ) painter.setClipping(false); - // resize handles renderDots( &painter, frame, QColor(0, 0, 0, 66) ); painter.restore(); @@ -1002,27 +1361,335 @@ namespace Oxygen // title bounding rect painter.setFont( options()->font(isActive(), false) ); - QRect boundingRect( titleBoundingRect( &painter, caption() ) ); - if( isActive() && configuration().drawTitleOutline() ) - { - renderTitleOutline( &painter, boundingRect.adjusted( - -layoutMetric( LM_TitleBorderLeft ), - -layoutMetric( LM_TitleEdgeTop ), - layoutMetric( LM_TitleBorderRight ), 0 ), palette ); - } - // title text - renderTitleText( &painter, boundingRect, - titlebarTextColor( palette ), - titlebarContrastColor( palette ) ); + // draw ClientGroupItems + int itemCount( itemData_.count() ); + for( int i = 0; i < itemCount; i++ ) renderItem( &painter, i, palette ); + + // draw target rect + renderTargetRect( &painter, widget()->palette() ); // separator - if( drawSeparator() ) renderSeparator(&painter, frame, widget(), color ); + if( itemCount == 1 && !itemData_.animated() && drawSeparator() ) + { renderSeparator(&painter, frame, widget(), color ); } } } + //_____________________________________________________________ + bool OxygenClient::mousePressEvent( QMouseEvent* event ) + { + + QPoint point = event->pos(); + if( itemClicked( point ) < 0 ) return false; + + bool accepted( false ); + if( event->button() == Qt::MidButton ) + { + + dragPoint_ = point; + mouseButton_ = Qt::MidButton; + accepted = true; + + } else if( event->button() == Qt::LeftButton ) { + + mouseButton_ = Qt::LeftButton; + + } else if( event->button() == Qt::RightButton ) { + + mouseButton_ = Qt::RightButton; + accepted = true; + + } + return accepted; + } + + //_____________________________________________________________ + bool OxygenClient::mouseReleaseEvent( QMouseEvent* event ) + { + + bool accepted( false ); + if( ( mouseButton_ == Qt::LeftButton && event->button() == Qt::LeftButton ) || + ( mouseButton_ == Qt::MidButton && event->button() == Qt::MidButton ) ) + { + + QPoint point = event->pos(); + + int visibleItem = visibleClientGroupItem(); + int itemClicked( OxygenClient::itemClicked( point ) ); + if( itemClicked >= 0 && visibleItem != itemClicked ) + { + setVisibleClientGroupItem( itemClicked ); + setForceActive( true ); + accepted = true; + } + + } else if( mouseButton_ == Qt::RightButton && event->button() == Qt::RightButton ) { + + QPoint point = event->pos(); + int itemClicked( OxygenClient::itemClicked( point ) ); + displayClientMenu( itemClicked, widget()->mapToGlobal( event->pos() ) ); + + } + + mouseButton_ = Qt::NoButton; + return false; + + } + + //_____________________________________________________________ + bool OxygenClient::mouseMoveEvent( QMouseEvent* event ) + { + + // check button and distance to drag point + if( configuration().hideTitleBar() || mouseButton_ == Qt::NoButton || ( event->pos() - dragPoint_ ).manhattanLength() <= QApplication::startDragDistance() ) + { return false; } + + bool accepted( false ); + if( mouseButton_ == Qt::MidButton ) + { + + QPoint point = event->pos(); + int itemClicked( OxygenClient::itemClicked( point ) ); + if( itemClicked < 0 ) return false; + + QDrag *drag = new QDrag( widget() ); + QMimeData *groupData = new QMimeData(); + groupData->setData( clientGroupItemDragMimeType(), QString().setNum( itemId( itemClicked )).toAscii() ); + drag->setMimeData( groupData ); + sourceItem_ = OxygenClient::itemClicked( dragPoint_ ); + + // get tab geometry + QRect geometry; + geometry = itemData_[itemClicked].boundingRect_; + + // remove space used for buttons + if( itemData_.count() >= 0 ) + { geometry.adjust( 0, 0, - configuration().buttonSize() - layoutMetric(LM_TitleEdgeRight), 0 ); } + + drag->setPixmap( itemDragPixmap( itemClicked, geometry ) ); + + // note: the pixmap is moved just above the pointer on purpose + // because overlapping pixmap and pointer slows down the pixmap alot. + //drag->setHotSpot( QPoint( event->pos().x() - geometry.left(), geometry.height() ) ); + drag->setHotSpot( QPoint( event->pos().x() - geometry.left(), -1 ) ); + drag->exec( Qt::MoveAction ); + + // detach tab from window + if( drag->target() == 0 && itemData_.count() > 1 ) + { + removeFromClientGroup( sourceItem_, + widget()->frameGeometry().adjusted( + layoutMetric( LM_OuterPaddingLeft ), + layoutMetric( LM_OuterPaddingTop ), + -layoutMetric( LM_OuterPaddingRight ), + -layoutMetric( LM_OuterPaddingBottom ) + ).translated( QCursor::pos() - event->pos() + + QPoint( layoutMetric( LM_OuterPaddingLeft ), layoutMetric( LM_OuterPaddingTop ))) + ); + } + + accepted = true; + + } + + // reset button + mouseButton_ = Qt::NoButton; + return accepted; + + } + + //_____________________________________________________________ + bool OxygenClient::dragEnterEvent( QDragEnterEvent* event ) + { + + // check if drag enter is allowed + if( !event->mimeData()->hasFormat( clientGroupItemDragMimeType() ) || configuration().hideTitleBar() ) return false; + + // + event->acceptProposedAction(); + if( event->source() != widget() ) + { + + QPoint position( event->pos() ); + itemData_.animate( AnimationEnter, itemClicked( position, true ) ); + + } else if( itemData_.count() > 1 ) { + + QPoint position( event->pos() ); + int itemClicked( OxygenClient::itemClicked( position, false ) ); + itemData_.animate( AnimationTypes( AnimationEnter|AnimationSameTarget ), itemClicked ); + + } + + return true; + + } + + //_____________________________________________________________ + bool OxygenClient::dragLeaveEvent( QDragLeaveEvent* ) + { + + if( itemData_.animationType() & AnimationSameTarget ) + { + + itemData_.animate( Oxygen::AnimationTypes(AnimationLeave|AnimationSameTarget), sourceItem_ ); + + } else if( itemData_.animated() ) { + + + itemData_.animate( AnimationLeave ); + + } + + + return true; + + } + + //_____________________________________________________________ + bool OxygenClient::dragMoveEvent( QDragMoveEvent* event ) + { + + // check format + if( !event->mimeData()->hasFormat( clientGroupItemDragMimeType() ) ) return false; + if( event->source() != widget() ) + { + + QPoint position( event->pos() ); + itemData_.animate( AnimationMove, itemClicked( position, true ) ); + + } else if( itemData_.count() > 1 ) { + + QPoint position( event->pos() ); + int itemClicked( OxygenClient::itemClicked( position, false ) ); + itemData_.animate( AnimationTypes( AnimationMove|AnimationSameTarget ), itemClicked ); + + } + + return false; + + } + + //_____________________________________________________________ + bool OxygenClient::dropEvent( QDropEvent* event ) + { + + QPoint point = event->pos(); + itemData_.animate( AnimationNone ); + + const QMimeData *groupData = event->mimeData(); + if( !groupData->hasFormat( clientGroupItemDragMimeType() ) ) return false; + + if( widget() == event->source() ) + { + + int from = OxygenClient::itemClicked( dragPoint_ ); + int itemClicked( OxygenClient::itemClicked( point, false ) ); + + if( itemClicked > from ) + { + itemClicked++; + if( itemClicked >= clientGroupItems().count() ) + { itemClicked = -1; } + } + + moveItemInClientGroup( from, itemClicked ); + itemData_.setDirty( true ); + widget()->update(); + + } else { + + setForceActive( true ); + int itemClicked( OxygenClient::itemClicked( point, true ) ); + int source = QString( groupData->data( clientGroupItemDragMimeType() ) ).toInt(); + moveItemToClientGroup( source, itemClicked ); + + } + + return true; + + } + + //_____________________________________________________________ + bool OxygenClient::closeItem( const OxygenButton* button ) + { + + for( int i=0; i < itemData_.count(); i++ ) + { + if( button == itemData_[i].closeButton_.data() ) + { + closeClientGroupItem( i ); + return true; + } + } + return false; + + } + + //________________________________________________________________ + QPixmap OxygenClient::itemDragPixmap( int index, const QRect& geometry ) + { + bool itemValid( index >= 0 && index < clientGroupItems().count() ); + + QPixmap pixmap( geometry.size() ); + QPainter painter( &pixmap ); + painter.setRenderHints(QPainter::SmoothPixmapTransform|QPainter::Antialiasing); + + painter.translate( -geometry.topLeft() ); + + // render window background + renderWindowBackground( &painter, geometry, widget(), widget()->palette() ); + + // darken background if item is inactive + bool itemActive = !( itemValid && index != visibleClientGroupItem() ); + if( !itemActive ) + { + + QLinearGradient lg( geometry.topLeft(), geometry.bottomLeft() ); + lg.setColorAt( 0, helper().alphaColor( Qt::black, 0.05 ) ); + lg.setColorAt( 1, helper().alphaColor( Qt::black, 0.10 ) ); + painter.setBrush( lg ); + painter.setPen( Qt::NoPen ); + painter.drawRect( geometry ); + + } + + // render title text + painter.setFont( options()->font(isActive(), false) ); + QRect textRect( geometry.adjusted( 0, layoutMetric( LM_TitleEdgeTop )-1, 0, -1 ) ); + + if( itemValid ) + { textRect.adjust( layoutMetric( LM_TitleBorderLeft ), 0, -layoutMetric(LM_TitleBorderRight), 0 ); } + + QString caption( itemValid ? clientGroupItems()[index].title() : OxygenClient::caption() ); + renderTitleText( &painter, textRect, caption, titlebarTextColor( widget()->palette(), isActive() && itemActive ) ); + + // floating frame + helper().drawFloatFrame( + &painter, geometry, widget()->palette().window().color(), + true, false, + KDecoration::options()->color(ColorTitleBar) + ); + + painter.end(); + + // create pixmap mask + QBitmap bitmap( geometry.size() ); + { + bitmap.clear(); + QPainter painter( &bitmap ); + QPainterPath path; + path.addRegion( helper().roundedMask( geometry.translated( -geometry.topLeft() ) ) ); + painter.fillPath( path, Qt::color1 ); + } + + pixmap.setMask( bitmap ); + return pixmap; + + } + //_________________________________________________________________ void OxygenClient::createSizeGrip( void ) { diff --git a/clients/oxygen/oxygenclient.h b/clients/oxygen/oxygenclient.h index 93aae4ebef..5b69c04166 100644 --- a/clients/oxygen/oxygenclient.h +++ b/clients/oxygen/oxygenclient.h @@ -28,13 +28,15 @@ // IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// -#include -#include - #include "oxygen.h" +#include "oxygenclientgroupitemdata.h" #include "oxygenconfiguration.h" #include "lib/helper.h" +#include +#include +#include + namespace Oxygen { @@ -72,6 +74,10 @@ namespace Oxygen bool timeLineIsRunning( void ) const { return timeLine_.state() == QTimeLine::Running; } + //! true when decoration is forced active + bool isForcedActive( void ) const + { return forceActive_ && clientGroupItems().count() > 1; } + //! true when separator is to be drawn bool drawSeparator( void ) const { @@ -124,18 +130,18 @@ namespace Oxygen virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton * = 0) const; //! get title bounding rect - virtual QRect titleBoundingRect( QPainter* painter, const QString& caption ) const - { return titleBoundingRect( painter, titleRect(), caption ); } + virtual QRect titleBoundingRect( const QFont& font, const QString& caption ) const + { return titleBoundingRect( font, titleRect(), caption ); } //! get title bounding rect - virtual QRect titleBoundingRect( QPainter*, const QRect&, const QString& ) const; + virtual QRect titleBoundingRect( const QFont&, QRect, const QString& ) const; //! palette background QPalette backgroundPalette( const QWidget*, QPalette ) const; //! background QColor backgroundColor( const QWidget* widget, QPalette palette ) const - { return backgroundColor( widget, palette, isActive() ); } + { return backgroundColor( widget, palette, isActive() || isForcedActive() ); } //! background QColor backgroundColor( const QWidget*, QPalette, bool ) const; @@ -165,16 +171,48 @@ namespace Oxygen //@} - public slots: + //! event filter + virtual bool eventFilter( QObject*, QEvent* ); - //! reset configuration - void resetConfiguration( void ); + //! resize event + virtual void resizeEvent(QResizeEvent *e); protected: + //! true when decoration is forced active + void setForceActive( bool value ) + { forceActive_ = value; } + + + //!@name event filters + //@{ + //! paint virtual void paintEvent( QPaintEvent* ); + //! mouse press event + virtual bool mousePressEvent( QMouseEvent* ); + + //! mouse release event + virtual bool mouseReleaseEvent( QMouseEvent* ); + + //! mouse move event + virtual bool mouseMoveEvent( QMouseEvent* ); + + //! drag enter event + virtual bool dragEnterEvent( QDragEnterEvent* ); + + //! drag move event + virtual bool dragMoveEvent( QDragMoveEvent* ); + + //! drag leave event + virtual bool dragLeaveEvent( QDragLeaveEvent* ); + + //! drop event + virtual bool dropEvent( QDropEvent* ); + + //@} + //!@name rendering methods (called in paintEvent) //@{ @@ -199,6 +237,12 @@ namespace Oxygen /*! second color, if valid, is for contrast pixel */ virtual void renderTitleText( QPainter*, const QRect&, const QString&, const QColor&, const QColor& = QColor() ) const; + //! GroupItem + virtual void renderItem( QPainter*, int, const QPalette& ); + + //! tabbing target rect + virtual void renderTargetRect( QPainter*, const QPalette& ); + //! render float frame virtual void renderFloatFrame( QPainter*, const QRect&, const QPalette& ) const; @@ -207,6 +251,16 @@ namespace Oxygen //@} + //! close tab matching give button + virtual bool closeItem( const OxygenButton* ); + + //! index of item matching point + int itemClicked( const QPoint& position, bool between = false ) const + { return itemData_.itemAt( position , between ); } + + //! return pixmap corresponding to a given tab, for dragging + QPixmap itemDragPixmap( int, const QRect& ); + //! title timeline bool titleTimeLineIsRunning( void ) const { return titleTimeLine_.state() == QTimeLine::Running; } @@ -247,7 +301,12 @@ namespace Oxygen QColor titlebarTextColor(const QPalette&, bool active); //! text color - QColor titlebarContrastColor(const QPalette&); + QColor titlebarContrastColor(const QPalette& palette ) const + { return titlebarContrastColor( palette.color( widget()->window()->backgroundRole() ) ); } + + //! text color + QColor titlebarContrastColor(const QColor& color ) const + { return helper().calcLightColor( color ); } //!@name size grip //@{ @@ -274,6 +333,17 @@ namespace Oxygen void updateOldCaption( void ) { setOldCaption( caption() ); } + //! set target item to -1 + void clearTargetItem( void ); + + //! clear force active flag + void clearForceActive( void ) + { if( isActive() ) setForceActive( false ); } + + //! title bounding rects + /*! calculate and return title bounding rects in case of tabbed window */ + void updateItemBoundingRects( bool alsoUpdate = true ); + private: //! factory @@ -297,6 +367,21 @@ namespace Oxygen //! true when initialized bool initialized_; + //! true when decoration is forced active + bool forceActive_; + + //! mouse button + Qt::MouseButton mouseButton_; + + //! tab bounding rects + ClientGroupItemDataList itemData_; + + //! index of tab being dragged if any, -1 otherwise + int sourceItem_; + + //! drag start point + QPoint dragPoint_; + }; //!@name utility functions diff --git a/clients/oxygen/oxygenclientgroupitemdata.cpp b/clients/oxygen/oxygenclientgroupitemdata.cpp new file mode 100644 index 0000000000..fc7a0cb949 --- /dev/null +++ b/clients/oxygen/oxygenclientgroupitemdata.cpp @@ -0,0 +1,320 @@ + +////////////////////////////////////////////////////////////////////////////// +// oxygenclientgroupitemdata.cpp +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// Copyright (c) 2003, 2004 David Johnson +// Copyright (c) 2006, 2007 Riccardo Iaconelli +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "oxygenclientgroupitemdata.h" +#include "oxygenclientgroupitemdata.moc" +#include "oxygenclient.h" +#include "oxygen.h" + +namespace Oxygen +{ + + //____________________________________________________________________________ + ClientGroupItemDataList::ClientGroupItemDataList( OxygenClient* parent ): + QObject( parent ), + QList(), + client_( *parent ), + timeLine_( 150, this ), + animationType_( AnimationNone ), + draggedItem_( NoItem ), + targetItem_( NoItem ) + { + timeLine_.setFrameRange( 0, maxAnimationIndex ); + timeLine_.setCurveShape( QTimeLine::EaseInOutCurve ); + connect( &timeLine_, SIGNAL( frameChanged( int ) ), this, SLOT( updateBoundingRects() ) ); + connect( &timeLine_, SIGNAL( finished() ), this, SLOT( updateBoundingRects() ) ); + } + + //________________________________________________________________ + int ClientGroupItemDataList::itemAt( const QPoint& point, bool between ) const + { + + for( int i=0; i < count(); i++ ) + { + QRect rect = at(i).activeRect_; + if( between ) rect.translate( -rect.width() / 2, 0 ); + if( rect.adjusted(0,0,0,2).contains( point ) ) + { return i; } + } + + return NoItem; + + } + + //____________________________________________________________________________ + void ClientGroupItemDataList::animate( AnimationTypes type, int target ) + { + + // store animation type + animationType_ = type; + + if( type == AnimationNone ) + { + + if( timeLineIsRunning() ) timeLine().stop(); + targetItem_ = NoItem; + draggedItem_ = NoItem; + targetRect_ = QRect(); + + } else if( type & (AnimationEnter|AnimationMove ) ) { + + // store dragged item + bool animate( true ); + + if( (type&AnimationSameTarget) && draggedItem_ == NoItem ) + { + + animate = false; + draggedItem_ = target; + + } else if( (type&AnimationMove) && targetItem_ == target ) return; + + // check timeLine + if( timeLineIsRunning() ) timeLine().stop(); + + targetItem_ = target; + targetRect_ = QRect(); + QRect titleRect( client_.titleRect() ); + int left( titleRect.left() ); + int width = (type&AnimationSameTarget) ? + titleRect.width()/count(): + titleRect.width()/(count()+1); + + if( (type&AnimationSameTarget) && draggedItem_ < target ) + { + target++; + if( target >= count() ) target = NoItem; + } + + // loop over items and update bounding rects + for( int index = 0; index < count(); index++ ) + { + + ClientGroupItemData& item( ClientGroupItemDataList::operator[](index) ); + if( index == target ) + { + targetRect_ = item.refBoundingRect_; + targetRect_.setLeft( left ); + targetRect_.setWidth( width ); + left+=width; + } + + item.startBoundingRect_ = item.boundingRect_; + item.endBoundingRect_ = item.refBoundingRect_; + item.endBoundingRect_.setLeft( left ); + + if( !( (type&AnimationSameTarget) && index == draggedItem_ ) ) + { + + item.endBoundingRect_.setWidth( width ); + left+=width; + + } else { + + item.endBoundingRect_.setWidth( 0 ); + + } + + } + + if( targetRect_.isNull() ) + { + targetRect_ = back().refBoundingRect_; + targetRect_.setLeft( left ); + targetRect_.setWidth( width ); + } + + if( animate ) timeLine().start(); + else { + + for( int index = 0; index < count(); index++ ) + { + ClientGroupItemData& item( ClientGroupItemDataList::operator[](index) ); + item.boundingRect_ = item.endBoundingRect_; + } + + updateButtons( true ); + + } + + } else if( type & AnimationLeave ) { + + // stop timeLine + if( timeLineIsRunning() ) timeLine().stop(); + + // reset target + targetItem_ = NoItem; + targetRect_ = QRect(); + + if( type & AnimationSameTarget ) + { + + // store dragged item + draggedItem_ = target; + + // do nothing if only one item + if( count() <= 1 ) return; + + QRect titleRect( client_.titleRect() ); + int left( titleRect.left() ); + int width = titleRect.width()/(count()-1); + + // loop over items and update bounding rects + for( int index = 0; index < count(); index++ ) + { + + ClientGroupItemData& item( ClientGroupItemDataList::operator[](index) ); + item.startBoundingRect_ = item.boundingRect_; + item.endBoundingRect_ = item.refBoundingRect_; + item.endBoundingRect_.setLeft( left ); + if( index != target ) + { + + item.endBoundingRect_.setWidth( width ); + left+=width; + + } else { + + item.endBoundingRect_.setWidth( 0 ); + + } + + } + + } else { + + // loop over items and update bounding rects + for( int index = 0; index < count(); index++ ) + { + ClientGroupItemData& item( ClientGroupItemDataList::operator[](index) ); + item.startBoundingRect_ = item.boundingRect_; + item.endBoundingRect_ = item.refBoundingRect_; + } + + } + + timeLine().start(); + + } + + return; + + } + + //____________________________________________________________________________ + void ClientGroupItemDataList::updateButtonActivity( int visibleItem ) const + { + + for( int index = 0; index < count(); index++ ) + { + + const ClientGroupItemData& item( at(index) ); + if( item.closeButton_ ) + { item.closeButton_.data()->setForceInactive( index != visibleItem ); } + + } + + } + + //____________________________________________________________________________ + void ClientGroupItemDataList::updateButtons( bool alsoUpdate ) const + { + + // move close buttons + // this should move to ClientGroupItemDataList + if( alsoUpdate ) client_.widget()->setUpdatesEnabled( false ); + for( int index = 0; index < count(); index++ ) + { + + const ClientGroupItemData& item( at(index) ); + if( !item.closeButton_ ) continue; + + if( !item.boundingRect_.isValid() ) { + + item.closeButton_.data()->hide(); + + } else { + + QPoint position( + item.boundingRect_.right() - client_.configuration().buttonSize() - client_.layoutMetric(KCommonDecoration::LM_TitleEdgeRight), + item.boundingRect_.top() + client_.layoutMetric( KCommonDecoration::LM_TitleEdgeTop ) ); + + if( item.closeButton_.data()->isHidden() ) item.closeButton_.data()->show(); + item.closeButton_.data()->move( position ); + + } + + } + + if( alsoUpdate ) + { + client_.widget()->setUpdatesEnabled( true ); + client_.widget()->update(); + } + + } + + //____________________________________________________________________________ + void ClientGroupItemDataList::updateBoundingRects( bool alsoUpdate ) + { + + qreal ratio( ClientGroupItemDataList::ratio() ); + for( iterator iter = begin(); iter != end(); iter++ ) + { + + // left + if( iter->endBoundingRect_.left() == iter->startBoundingRect_.left() ) + { + + iter->boundingRect_.setLeft( iter->startBoundingRect_.left() ); + + } else { + + iter->boundingRect_.setLeft( (1.0-ratio)*iter->startBoundingRect_.left() + ratio*iter->endBoundingRect_.left() ); + + } + + // right + if( iter->endBoundingRect_.right() == iter->startBoundingRect_.right() ) + { + + iter->boundingRect_.setRight( iter->startBoundingRect_.right() ); + + } else { + + iter->boundingRect_.setRight( (1.0-ratio)*iter->startBoundingRect_.right() + ratio*iter->endBoundingRect_.right() ); + + } + + } + + // update button position + updateButtons( alsoUpdate ); + + } +} diff --git a/clients/oxygen/oxygenclientgroupitemdata.h b/clients/oxygen/oxygenclientgroupitemdata.h new file mode 100644 index 0000000000..c47ca948ca --- /dev/null +++ b/clients/oxygen/oxygenclientgroupitemdata.h @@ -0,0 +1,201 @@ +#ifndef oxygenclientgroupitemdata_h +#define oxygenclientgroupitemdata_h + +////////////////////////////////////////////////////////////////////////////// +// oxygenclientgroupitemdata.h +// ------------------- +// +// Copyright (c) 2009 Hugo Pereira Da Costa +// Copyright (c) 2003, 2004 David Johnson +// Copyright (c) 2006, 2007 Riccardo Iaconelli +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +////////////////////////////////////////////////////////////////////////////// + +#include "oxygenbutton.h" + +#include +#include +#include +#include + +namespace Oxygen +{ + + class OxygenClient; + + //! animation type + enum AnimationType + { + AnimationNone = 0, + AnimationEnter = 1<<0, + AnimationMove = 1<<1, + AnimationLeave = 1<<2, + AnimationSameTarget = 1<<3 + }; + + Q_DECLARE_FLAGS(AnimationTypes, AnimationType) + + //! tab data + class ClientGroupItemData + { + + public: + + //! constructor + explicit ClientGroupItemData( void ) + {} + + //! destructor + virtual ~ClientGroupItemData( void ) + {} + + //! reset all rects to argument + void reset( const QRect& rect ) + { + refBoundingRect_ = rect; + startBoundingRect_ = rect; + endBoundingRect_ = rect; + boundingRect_ = rect; + } + + //! tab active rect + QRect activeRect_; + + //! reference bounding rect + /*! it is usually identical to activeRect unless there is only one tab in window */ + QRect refBoundingRect_; + + //! tab drawing rect + QRect startBoundingRect_; + + //! tab drawing rect + QRect endBoundingRect_; + + //! tab drawing rect + QRect boundingRect_; + + //! tab button + typedef QWeakPointer ButtonPointer; + ButtonPointer closeButton_; + + }; + + class ClientGroupItemDataList: public QObject, public QList + { + + Q_OBJECT + + public: + + //! invalid item index + enum { NoItem = -1 }; + + //! constructor + ClientGroupItemDataList( OxygenClient* parent ); + + //! dirty state + void setDirty( const bool& value ) + { dirty_ = value; } + + //! dirty state + bool isDirty( void ) const + { return dirty_; } + + //! true if being animated + bool animated( void ) const + { return animationType_ != AnimationNone; } + + //! animation type + AnimationTypes animationType( void ) const + { return animationType_; } + + //! return item index matching QPoint, or -1 if none + int itemAt( const QPoint&, bool ) const; + + //! returns true if index is target + bool isTarget( int index ) const + { return index == targetItem_; } + + //! start animation + /* might need to add the side of the target here */ + void animate( AnimationTypes, int = NoItem ); + + //! update button activity + void updateButtonActivity( int visibleItem ) const; + + //! update buttons + void updateButtons( bool alsoUpdate ) const; + + //! get timeLine + const QTimeLine& timeLine( void ) const + { return timeLine_; } + + //! get timeLine + QTimeLine& timeLine( void ) + { return timeLine_; } + + //! true if timeLine is running + bool timeLineIsRunning( void ) const + { return timeLine().state() == QTimeLine::Running; } + + //! target rect + const QRect& targetRect( void ) const + { return targetRect_; } + + protected slots: + + //! update bounding rects + void updateBoundingRects( bool alsoUpdate = true ); + + protected: + + //! timeLine ratio + qreal ratio( void ) + { return qreal( timeLine().currentFrame() ) / qreal( timeLine().endFrame() ); } + + private: + + //! client + OxygenClient& client_; + + //! dirty flag + /* used to trigger update at next paintEvent */ + bool dirty_; + + //! animation timeline + QTimeLine timeLine_; + + //! last animation + AnimationTypes animationType_; + + //! dragged item + int draggedItem_; + + //! target item + int targetItem_; + + //! target rect + QRect targetRect_; + + }; + +} + +#endif diff --git a/clients/oxygen/oxygenconfiguration.cpp b/clients/oxygen/oxygenconfiguration.cpp index dde7fdeef1..70027dbd94 100644 --- a/clients/oxygen/oxygenconfiguration.cpp +++ b/clients/oxygen/oxygenconfiguration.cpp @@ -43,6 +43,7 @@ namespace Oxygen useOxygenShadows_( true ), useAnimations_( true ), animationsDuration_( 150 ), + tabsEnabled_( true ), useNarrowButtonSpacing_( false ) {} @@ -108,10 +109,16 @@ namespace Oxygen OxygenConfig::ANIMATIONS_DURATION, defaultConfiguration.animationsDuration() ) ); + // tabbing + setTabsEnabled( group.readEntry( + OxygenConfig::TABS_ENABLED, + defaultConfiguration.tabsEnabled() ) ); + // buttonSpacing setUseNarrowButtonSpacing( group.readEntry( OxygenConfig::NARROW_BUTTON_SPACING, defaultConfiguration.useNarrowButtonSpacing() ) ); + } //__________________________________________________ @@ -130,7 +137,9 @@ namespace Oxygen group.writeEntry( OxygenConfig::USE_OXYGEN_SHADOWS, useOxygenShadows() ); group.writeEntry( OxygenConfig::USE_ANIMATIONS, useAnimations() ); group.writeEntry( OxygenConfig::ANIMATIONS_DURATION, animationsDuration() ); + group.writeEntry( OxygenConfig::TABS_ENABLED, tabsEnabled() ); group.writeEntry( OxygenConfig::NARROW_BUTTON_SPACING, useNarrowButtonSpacing() ); + } //__________________________________________________ @@ -298,6 +307,7 @@ namespace Oxygen useOxygenShadows() == other.useOxygenShadows() && useAnimations() == other.useAnimations() && animationsDuration() == other.animationsDuration() && + tabsEnabled() == other.tabsEnabled() && useNarrowButtonSpacing() == other.useNarrowButtonSpacing(); } diff --git a/clients/oxygen/oxygenconfiguration.h b/clients/oxygen/oxygenconfiguration.h index 7cd1661668..892177b919 100644 --- a/clients/oxygen/oxygenconfiguration.h +++ b/clients/oxygen/oxygenconfiguration.h @@ -42,6 +42,7 @@ namespace OxygenConfig static const QString HIDE_TITLEBAR = "HideTitleBar"; static const QString USE_ANIMATIONS = "UseAnimations"; static const QString ANIMATIONS_DURATION = "AnimationsDuration"; + static const QString TABS_ENABLED = "TabsEnabled"; static const QString NARROW_BUTTON_SPACING = "UseNarrowButtonSpacing"; } @@ -265,6 +266,14 @@ namespace Oxygen virtual void setAnimationsDuration( int value ) { animationsDuration_ = value; } + //! tabbing + virtual bool tabsEnabled( void ) const + { return tabsEnabled_; } + + //! tabbing + virtual void setTabsEnabled( bool value ) + { tabsEnabled_ = value; } + private: //! title alignment @@ -300,6 +309,9 @@ namespace Oxygen //! animations int animationsDuration_; + //! tabbing + bool tabsEnabled_; + //! narrow button spacing bool useNarrowButtonSpacing_; diff --git a/clients/oxygen/oxygenshadowcache.cpp b/clients/oxygen/oxygenshadowcache.cpp index 70acbdd8ad..0adcc45094 100644 --- a/clients/oxygen/oxygenshadowcache.cpp +++ b/clients/oxygen/oxygenshadowcache.cpp @@ -31,7 +31,6 @@ #include #include -#include #include namespace Oxygen @@ -44,7 +43,6 @@ namespace Oxygen activeShadowConfiguration_( OxygenShadowConfiguration( QPalette::Active ) ), inactiveShadowConfiguration_( OxygenShadowConfiguration( QPalette::Inactive ) ) { - kDebug(1212) << endl; shadowCache_.setMaxCost( 1<<6 ); animatedShadowCache_.setMaxCost( maxIndex_<<6 ); } @@ -393,7 +391,7 @@ namespace Oxygen index(0) { - active = client->isActive(); + active = client->isActive() || client->isForcedActive(); useOxygenShadows = client->configuration().useOxygenShadows(); isShade = client->isShade(); hasTitleOutline = client->configuration().drawTitleOutline(); diff --git a/clients/oxygen/oxygensizegrip.cpp b/clients/oxygen/oxygensizegrip.cpp index 21dbf6196d..8c3a8ee7b5 100644 --- a/clients/oxygen/oxygensizegrip.cpp +++ b/clients/oxygen/oxygensizegrip.cpp @@ -33,8 +33,6 @@ #include #include -#include - #include #include @@ -92,12 +90,10 @@ namespace Oxygen WId window_id = client().windowId(); if( client().isPreview() ) { - kDebug(1212) << "Using kcommondecoration::widget()" << endl; setParent( client().widget() ); } else if( window_id ) { - kDebug(1212) << "Using Window ID" << endl; WId current = window_id; while( true ) { @@ -113,7 +109,6 @@ namespace Oxygen XReparentWindow( QX11Info::display(), winId(), current, 0, 0 ); } else { - kDebug(1212) << "Unable to find valid parent. Hiding" << endl; hide(); } diff --git a/clients/tabstrip/CMakeLists.txt b/clients/tabstrip/CMakeLists.txt new file mode 100644 index 0000000000..8d269b10ac --- /dev/null +++ b/clients/tabstrip/CMakeLists.txt @@ -0,0 +1,15 @@ +add_subdirectory( config ) + +set(kwin_tabstrip_SRCS + tabstripbutton.cpp + tabstripdecoration.cpp + tabstripfactory.cpp + ) + +kde4_add_plugin(kwin3_tabstrip ${kwin_tabstrip_SRCS}) + +target_link_libraries(kwin3_tabstrip kdecorations) + +install(TARGETS kwin3_tabstrip DESTINATION ${PLUGIN_INSTALL_DIR}) + +install( FILES tabstrip.desktop DESTINATION ${DATA_INSTALL_DIR}/kwin ) diff --git a/clients/tabstrip/config/CMakeLists.txt b/clients/tabstrip/config/CMakeLists.txt new file mode 100644 index 0000000000..3d78f838c7 --- /dev/null +++ b/clients/tabstrip/config/CMakeLists.txt @@ -0,0 +1,10 @@ + +set(kwin_tabstrip_config_SRCS tabstripconfig.cpp ) + +kde4_add_ui_files(kwin_tabstrip_config_SRCS tabstripconfig.ui ) + +kde4_add_plugin(kwin_tabstrip_config ${kwin_tabstrip_config_SRCS}) + +target_link_libraries(kwin_tabstrip_config ${KDE4_KDEUI_LIBS} ${QT_QTGUI_LIBRARY}) + +install(TARGETS kwin_tabstrip_config DESTINATION ${PLUGIN_INSTALL_DIR} ) diff --git a/clients/tabstrip/config/tabstripconfig.cpp b/clients/tabstrip/config/tabstripconfig.cpp new file mode 100644 index 0000000000..cd14663edf --- /dev/null +++ b/clients/tabstrip/config/tabstripconfig.cpp @@ -0,0 +1,91 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#include "tabstripconfig.h" + +#include "tabstripconfig.moc" + +#include +#include +#include + +#include +#include + +extern "C" + { + KDE_EXPORT QObject* allocate_config( KConfig* conf, QWidget* parent ) + { + return new TabstripConfig::TabstripConfig( conf, parent ); + } + } + +TabstripConfig::TabstripConfig( KConfig *c, QWidget *parent ) + { + KGlobal::locale()->insertCatalog( "kwin_tabstrip_config" ); + config = new KConfig( "tabstriprc" ); + KConfigGroup cg( config, "General" ); + ui = new TabstripConfigDialog( parent ); + connect( ui->left, SIGNAL( clicked() ), SIGNAL( changed() ) ); + connect( ui->center, SIGNAL( clicked() ), SIGNAL( changed() ) ); + connect( ui->right, SIGNAL( clicked() ), SIGNAL( changed() ) ); + connect( ui->showIcon, SIGNAL( clicked() ), SIGNAL( changed() ) ); + load( cg ); + ui->show(); + } + +TabstripConfig::~TabstripConfig() + { + delete ui; + delete config; + } + +void TabstripConfig::load( KConfigGroup &c ) + { + c = KConfigGroup( config, "General" ); + QString align = c.readEntry( "TitleAlignment", "Center" ); + ui->left->setChecked( align == "Left" ); + ui->center->setChecked( align == "Center" ); + ui->right->setChecked( align == "Right" ); + ui->showIcon->setChecked( c.readEntry( "ShowIcon", true ) ); + } + +void TabstripConfig::save( KConfigGroup &c ) + { + c = KConfigGroup( config, "General" ); + if( ui->left->isChecked() ) + c.writeEntry( "TitleAlignment", "Left" ); + else if( ui->center->isChecked() ) + c.writeEntry( "TitleAlignment", "Center" ); + else + c.writeEntry( "TitleAlignment", "Right" ); + c.writeEntry( "ShowIcon", ui->showIcon->isChecked() ); + config->sync(); + } + +void TabstripConfig::defaults() + { + ui->left->setChecked( false ); + ui->center->setChecked( true ); + ui->right->setChecked( false ); + ui->showIcon->setChecked( true ); + emit changed(); + } diff --git a/clients/tabstrip/config/tabstripconfig.h b/clients/tabstrip/config/tabstripconfig.h new file mode 100644 index 0000000000..52e19897ed --- /dev/null +++ b/clients/tabstrip/config/tabstripconfig.h @@ -0,0 +1,56 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#ifndef TABSTRIPCONFIG_H +#define TABSTRIPCONFIG_H + +#include "ui_tabstripconfig.h" + +#include +#include + +class TabstripConfigDialog : public QWidget, public Ui::TabstripConfigUi + { + public: + TabstripConfigDialog( QWidget *parent ) : QWidget( parent ) + { + setupUi( this ); + } + }; + +class TabstripConfig : public QObject + { + Q_OBJECT + public: + TabstripConfig( KConfig *c, QWidget *parent ); + ~TabstripConfig(); + signals: + void changed(); + public slots: + void load( KConfigGroup &c ); + void save( KConfigGroup &c ); + void defaults(); + private: + KConfig *config; + TabstripConfigDialog *ui; + }; + +#endif diff --git a/clients/tabstrip/config/tabstripconfig.ui b/clients/tabstrip/config/tabstripconfig.ui new file mode 100644 index 0000000000..1a21e05b3c --- /dev/null +++ b/clients/tabstrip/config/tabstripconfig.ui @@ -0,0 +1,58 @@ + + + TabstripConfigUi + + + + 0 + 0 + 276 + 98 + + + + Tabstrip + + + + + + Title &Alignment + + + + + + &Left + + + + + + + &Center + + + + + + + &Right + + + + + + + + + + Display window icons + + + + + + + + diff --git a/clients/tabstrip/tabstrip.desktop b/clients/tabstrip/tabstrip.desktop new file mode 100644 index 0000000000..26b2210641 --- /dev/null +++ b/clients/tabstrip/tabstrip.desktop @@ -0,0 +1,4 @@ +[Desktop Entry] +Name=Tabstrip +Name[x-test]=xxTabstripxx +X-KDE-Library=kwin3_tabstrip diff --git a/clients/tabstrip/tabstripbutton.cpp b/clients/tabstrip/tabstripbutton.cpp new file mode 100644 index 0000000000..0479d38965 --- /dev/null +++ b/clients/tabstrip/tabstripbutton.cpp @@ -0,0 +1,317 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#include +#include "tabstripbutton.h" +#include "tabstripdecoration.h" +#include "tabstripfactory.h" + +#include + +#include +#include +#include +#include + +TabstripButton::TabstripButton( ButtonType type, TabstripDecoration *parent, QString tip ) + : KCommonDecorationButton( type, parent ), SIZE( 16 ) + { + setAutoFillBackground( false ); + setFixedSize( SIZE, SIZE ); + setCursor( Qt::ArrowCursor ); + client = parent; + btype = type; + setToolTip( tip ); + active_item = true; + hovering = false; + } + +TabstripButton::~TabstripButton() + { + } + +void TabstripButton::reset( unsigned long ) + { + update(); + } + +QSize TabstripButton::sizeHint() const + { + return QSize( SIZE, SIZE ); + } + +void TabstripButton::paintEvent( QPaintEvent * ) + { + QPainter p( this ); + const bool active = client->isActive() && active_item; + + // Icon geometry + QRect geom = QRect( 3, 3, width() - 6, height() - 6 ); + + // Determine painting colors + QColor bgColor = client->options()->color( KDecoration::ColorTitleBar, active ); + QColor textColor = client->options()->color( KDecoration::ColorFont, active ); + if( hovering ) + { // Invert if the mouse cursor is hovering over the button + textColor.setRed( 255 - textColor.red() ); + textColor.setGreen( 255 - textColor.green() ); + textColor.setBlue( 255 - textColor.blue() ); + } + + // Slight optimization as we are drawing solid straight lines + p.setRenderHint( QPainter::Antialiasing, false ); + + // Background + p.fillRect( 0, 0, width(), height(), bgColor ); + //p.fillRect( 0, 0, width(), height(), QColor( 255, 0, 0 )); + + // Paint buttons with the text color + p.setPen( textColor ); + + switch( btype ) + { + case HelpButton: + { + QFont font; + font.setBold( true ); + p.setFont( font ); + p.drawText( geom.adjusted( 0, 1, 0, 0), Qt::AlignVCenter | Qt::AlignHCenter, "?" ); + } + break; + case MaxButton: + switch( client->maximizeMode() ) + { + case TabstripDecoration::MaximizeRestore: + case TabstripDecoration::MaximizeVertical: + case TabstripDecoration::MaximizeHorizontal: + // TL + p.drawLine( geom.x() + 3, geom.y(), + geom.x(), geom.y() ); + p.drawLine( geom.x(), geom.y() + 3, + geom.x(), geom.y() ); + p.drawLine( geom.x() + 3, geom.y() + 1, + geom.x(), geom.y() + 1 ); + p.drawLine( geom.x() + 1, geom.y() + 3, + geom.x(), geom.y() + 1 ); + // TR + p.drawLine( geom.x() + geom.width() - 3, geom.y(), + geom.x() + geom.width(), geom.y() ); + p.drawLine( geom.x() + geom.width(), geom.y() + 3, + geom.x() + geom.width(), geom.y() ); + p.drawLine( geom.x() + geom.width() - 3, geom.y() + 1, + geom.x() + geom.width(), geom.y() + 1 ); + p.drawLine( geom.x() + geom.width() - 1, geom.y() + 3, + geom.x() + geom.width() - 1, geom.y() ); + // BL + p.drawLine( geom.x() + 3, geom.y() + geom.height(), + geom.x(), geom.y() + geom.height() ); + p.drawLine( geom.x(), geom.y() + geom.height() - 3, + geom.x(), geom.y() + geom.height() ); + p.drawLine( geom.x() + 3, geom.y() + geom.height() - 1, + geom.x(), geom.y() + geom.height() - 1 ); + p.drawLine( geom.x() + 1, geom.y() + geom.height() - 3, + geom.x() + 1, geom.y() + geom.height() ); + // BR + p.drawLine( geom.x() + geom.width() - 3, geom.y() + geom.height(), + geom.x() + geom.width(), geom.y() + geom.height() ); + p.drawLine( geom.x() + geom.width(), geom.y() + geom.height() - 3, + geom.x() + geom.width(), geom.y() + geom.height() ); + p.drawLine( geom.x() + geom.width() - 3, geom.y() + geom.height() - 1, + geom.x() + geom.width(), geom.y() + geom.height() - 1 ); + p.drawLine( geom.x() + geom.width() - 1, geom.y() + geom.height() - 3, + geom.x() + geom.width() - 1, geom.y() + geom.height() ); + break; + case TabstripDecoration::MaximizeFull: + // TL + p.drawLine( geom.x() + 2, geom.y(), + geom.x() + 2, geom.y() + 2 ); + p.drawLine( geom.x(), geom.y() + 2, + geom.x() + 2, geom.y() + 2 ); + p.drawLine( geom.x() + 3, geom.y(), + geom.x() + 3, geom.y() + 3 ); + p.drawLine( geom.x(), geom.y() + 3, + geom.x() + 3, geom.y() + 3 ); + // TR + p.drawLine( geom.x() + geom.width() - 2, geom.y(), + geom.x() + geom.width() - 2, geom.y() + 2 ); + p.drawLine( geom.x() + geom.width(), geom.y() + 2, + geom.x() + geom.width() - 2, geom.y() + 2 ); + p.drawLine( geom.x() + geom.width() - 3, geom.y(), + geom.x() + geom.width() - 3, geom.y() + 3 ); + p.drawLine( geom.x() + geom.width(), geom.y() + 3, + geom.x() + geom.width() - 3, geom.y() + 3 ); + // BL + p.drawLine( geom.x() + 2, geom.y() + geom.height(), + geom.x() + 2, geom.y() + geom.height() - 2 ); + p.drawLine( geom.x(), geom.y() + geom.height() - 2, + geom.x() + 2, geom.y() + geom.height() - 2 ); + p.drawLine( geom.x() + 3, geom.y() + geom.height(), + geom.x() + 3, geom.y() + geom.height() - 3 ); + p.drawLine( geom.x(), geom.y() + geom.height() - 3, + geom.x() + 3, geom.y() + geom.height() - 3 ); + // BR + p.drawLine( geom.x() + geom.width() - 2, geom.y() + geom.height(), + geom.x() + geom.width() - 2, geom.y() + geom.height() - 2 ); + p.drawLine( geom.x() + geom.width(), geom.y() + geom.height() - 2, + geom.x() + geom.width() - 2, geom.y() + geom.height() - 2 ); + p.drawLine( geom.x() + geom.width() - 3, geom.y() + geom.height(), + geom.x() + geom.width() - 3, geom.y() + geom.height() - 3 ); + p.drawLine( geom.x() + geom.width(), geom.y() + geom.height() - 3, + geom.x() + geom.width() - 3, geom.y() + geom.height() - 3 ); + break; + } + break; + case MinButton: + // B + p.drawLine( geom.x(), geom.y() + geom.height(), + geom.x() + geom.width(), geom.y() + geom.height() ); + p.drawLine( geom.x(), geom.y() + geom.height() - 1, + geom.x() + geom.width(), geom.y() + geom.height() - 1 ); + // L + p.drawLine( geom.x(), geom.y() + geom.height() - 3, + geom.x(), geom.y() + geom.height() ); + p.drawLine( geom.x() + 1, geom.y() + geom.height() - 3, + geom.x() + 1, geom.y() + geom.height() ); + // R + p.drawLine( geom.x() + geom.width(), geom.y() + geom.height() - 3, + geom.x() + geom.width(), geom.y() + geom.height() ); + p.drawLine( geom.x() + geom.width() - 1, geom.y() + geom.height() - 3, + geom.x() + geom.width() - 1, geom.y() + geom.height() ); + break; + case CloseButton: + case ItemCloseButton: + // TL-BR + p.drawLine( geom.x() + 1, geom.y() + 1, + geom.x() + geom.width() - 1, geom.y() + geom.height() - 1 ); + p.drawLine( geom.x() + 2, geom.y() + 1, + geom.x() + geom.width() - 1, geom.y() + geom.height() - 2 ); + p.drawLine( geom.x() + 1, geom.y() + 2, + geom.x() + geom.width() - 2, geom.y() + geom.height() - 1 ); + // TR-BL + p.drawLine( geom.x() + 1, geom.y() + geom.height() - 1, + geom.x() + geom.width() - 1, geom.y() + 1 ); + p.drawLine( geom.x() + 2, geom.y() + geom.height() - 1, + geom.x() + geom.width() - 1, geom.y() + 2 ); + p.drawLine( geom.x() + 1, geom.y() + geom.height() - 2, + geom.x() + geom.width() - 2, geom.y() + 1 ); + break; + case MenuButton: + if( client->clientGroupItems().count() > 1 || !TabstripFactory::showIcon() ) + { + p.drawRect( geom.x(), geom.y() + geom.height() / 2 - 5, + 1, 1 ); + p.drawRect( geom.x(), geom.y() + geom.height() / 2 - 1, + 1, 1 ); + p.drawRect( geom.x(), geom.y() + geom.height() / 2 + 3, + 1, 1 ); + p.drawRect( geom.x() + 4, geom.y() + geom.height() / 2 - 5, + geom.width() - 5, 1 ); + p.drawRect( geom.x() + 4, geom.y() + geom.height() / 2 - 1, + geom.width() - 5, 1 ); + p.drawRect( geom.x() + 4, geom.y() + geom.height() / 2 + 3, + geom.width() - 5, 1 ); + } + else + p.drawPixmap( 0, 0, client->icon().pixmap( SIZE )); + break; + case OnAllDesktopsButton: + { + if( isChecked() ) + p.fillRect( geom.x() + geom.width() / 2 - 1, geom.y() + geom.height() / 2 - 1, + 3, 3, textColor ); + else + { + p.fillRect( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 - 4, + 3, 3, textColor ); + p.fillRect( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 + 2, + 3, 3, textColor ); + p.fillRect( geom.x() + geom.width() / 2 + 2, geom.y() + geom.height() / 2 - 4, + 3, 3, textColor ); + p.fillRect( geom.x() + geom.width() / 2 + 2, geom.y() + geom.height() / 2 + 2, + 3, 3, textColor ); + } + } + break; + case AboveButton: + { + int o = -2; + if( isChecked() ) + { + o = -4; + p.drawRect( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 + 3, + 8, 1 ); + } + p.drawPoint( geom.x() + geom.width() / 2, geom.y() + geom.height() / 2 + o ); + p.drawLine( geom.x() + geom.width() / 2 - 1, geom.y() + geom.height() / 2 + o + 1, + geom.x() + geom.width() / 2 + 1, geom.y() + geom.height() / 2 + o + 1 ); + p.drawLine( geom.x() + geom.width() / 2 - 2, geom.y() + geom.height() / 2 + o + 2, + geom.x() + geom.width() / 2 + 2, geom.y() + geom.height() / 2 + o + 2 ); + p.drawLine( geom.x() + geom.width() / 2 - 3, geom.y() + geom.height() / 2 + o + 3, + geom.x() + geom.width() / 2 + 3, geom.y() + geom.height() / 2 + o + 3 ); + p.drawLine( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 + o + 4, + geom.x() + geom.width() / 2 + 4, geom.y() + geom.height() / 2 + o + 4 ); + } + break; + case BelowButton: + { + int o = 1; + if( isChecked() ) + { + o = 3; + p.drawRect( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 - 5, + 8, 1 ); + } + p.drawPoint( geom.x() + geom.width() / 2, geom.y() + geom.height() / 2 + o ); + p.drawLine( geom.x() + geom.width() / 2 - 1, geom.y() + geom.height() / 2 + o - 1, + geom.x() + geom.width() / 2 + 1, geom.y() + geom.height() / 2 + o - 1 ); + p.drawLine( geom.x() + geom.width() / 2 - 2, geom.y() + geom.height() / 2 + o - 2, + geom.x() + geom.width() / 2 + 2, geom.y() + geom.height() / 2 + o - 2 ); + p.drawLine( geom.x() + geom.width() / 2 - 3, geom.y() + geom.height() / 2 + o - 3, + geom.x() + geom.width() / 2 + 3, geom.y() + geom.height() / 2 + o - 3 ); + p.drawLine( geom.x() + geom.width() / 2 - 4, geom.y() + geom.height() / 2 + o - 4, + geom.x() + geom.width() / 2 + 4, geom.y() + geom.height() / 2 + o - 4 ); + } + break; + case ShadeButton: + p.drawLine( geom.x(), geom.y(), + geom.x() + geom.width(), geom.y() ); + p.drawLine( geom.x(), geom.y() + 1, + geom.x() + geom.width(), geom.y() + 1 ); + break; + case NumButtons: + default: + break; + }; + } + +void TabstripButton::enterEvent( QEvent *e ) + { + hovering = true; + repaint(); + KCommonDecorationButton::enterEvent( e ); + } + +void TabstripButton::leaveEvent( QEvent *e ) + { + hovering = false; + repaint(); + KCommonDecorationButton::leaveEvent( e ); + } diff --git a/clients/tabstrip/tabstripbutton.h b/clients/tabstrip/tabstripbutton.h new file mode 100644 index 0000000000..ea062306b0 --- /dev/null +++ b/clients/tabstrip/tabstripbutton.h @@ -0,0 +1,55 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#ifndef TABSTRIPBUTTON_H +#define TABSTRIPBUTTON_H + +#include +#include +#include + +class TabstripDecoration; + +class TabstripButton : public KCommonDecorationButton + { + public: + TabstripButton( ButtonType type, TabstripDecoration *parent, QString tip ); + ~TabstripButton(); + void reset( unsigned long changed ); + QSize sizeHint() const; + void setActive( bool active ); + private: + void paintEvent( QPaintEvent *e ); + void leaveEvent( QEvent *e ); + void enterEvent( QEvent *e ); + TabstripDecoration *client; + ButtonType btype; + const int SIZE; + bool active_item; + bool hovering; + }; + +inline void TabstripButton::setActive( bool active ) + { + active_item = active; + } + +#endif diff --git a/clients/tabstrip/tabstripdecoration.cpp b/clients/tabstrip/tabstripdecoration.cpp new file mode 100644 index 0000000000..ee8df3c7c7 --- /dev/null +++ b/clients/tabstrip/tabstripdecoration.cpp @@ -0,0 +1,457 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#include "tabstripdecoration.h" +#include "tabstripbutton.h" +#include "tabstripfactory.h" + +#include + +#include + +TabstripDecoration::TabstripDecoration( KDecorationBridge *bridge, KDecorationFactory *factory ) + : KCommonDecorationUnstable( bridge, factory ) + { + click_in_progress = false; + drag_in_progress = false; + button = Qt::NoButton; + } + +TabstripDecoration::~TabstripDecoration() + { + for( int i = 0; i < closeButtons.size(); ++i ) + { + TabstripButton *btn = closeButtons.front(); + closeButtons.removeFirst(); + delete btn; + } + } + +KCommonDecorationButton *TabstripDecoration::createButton( ButtonType type ) + { + switch( type ) + { + case HelpButton: + return ( new TabstripButton( type, this, i18n("Help") ) ); + break; + case MaxButton: + return ( new TabstripButton( type, this, i18n("Maximize") ) ); + break; + case MinButton: + return ( new TabstripButton( type, this, i18n("Minimize") ) ); + break; + case CloseButton: + return ( new TabstripButton( type, this, i18n("Close") ) ); + break; + case MenuButton: + return ( new TabstripButton( type, this, i18n("Menu") ) ); + break; + case OnAllDesktopsButton: + return ( new TabstripButton( type, this, i18n("AllDesktops") ) ); + break; + case AboveButton: + return ( new TabstripButton( type, this, i18n("Above") ) ); + break; + case BelowButton: + return ( new TabstripButton( type, this, i18n("Below") ) ); + break; + case ShadeButton: + return ( new TabstripButton( type, this, i18n("Shade") ) ); + break; + default: + break; + }; + return 0; + } + +void TabstripDecoration::paintTab( QPainter &painter, const QRect &geom, ClientGroupItem &item, bool active ) + { + // Determine painting colors + QColor bgColor = options()->color( ColorTitleBar, active ); + QColor textColor = options()->color( ColorFont, active ); + + // Draw border around the tab + painter.setPen( Qt::black ); + painter.drawRect( geom.adjusted( 0, 0, -1, -1 )); + painter.setPen( Qt::white ); + painter.drawRect( geom.adjusted( 1, 1, -2, -2 )); + + // Background + painter.fillRect( geom.adjusted( 2, 2, -2, -2 ), bgColor ); + + // Window title and icon + painter.setPen( textColor ); + if( TabstripFactory::showIcon() ) + { + QRect rect( geom.x() + 25, geom.y(), geom.width() - 48, geom.height() ); + QRect text; + QFont font; + QFontMetrics metrics( font ); + QString string = metrics.elidedText( item.title(), Qt::ElideRight, rect.width() ); + painter.drawText( rect, TabstripFactory::titleAlign() | Qt::AlignVCenter, string, &text ); + painter.drawPixmap( text.x() - 22, rect.y() + 3, item.icon().pixmap( 16 )); + } + else + { + QRect rect( geom.x() + 5, geom.y(), geom.width() - 28, geom.height() ); + QFont font; + QFontMetrics metrics( font ); + QString string = metrics.elidedText( item.title(), Qt::ElideRight, rect.width() ); + painter.drawText( rect, TabstripFactory::titleAlign() | Qt::AlignVCenter, string ); + } + } + +void TabstripDecoration::paintEvent( QPaintEvent * ) + { + QPainter painter( widget() ); + + // Determine painting colors + QColor bgColor = options()->color( ColorTitleBar, isClientGroupActive() ); + QColor textColor = options()->color( ColorFont, isClientGroupActive() ); + + // Determine section geometry + QRect frame( QPoint( 0, 0 ), widget()->frameGeometry().size() ); + QRect titlebar( frame.topLeft(), QSize( frame.width(), + layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleHeight ) + + layoutMetric( LM_TitleEdgeBottom ) - 1 // Titlebar and main frame overlap by 1px + )); + + // Slight optimization as we are drawing solid straight lines + painter.setRenderHint( QPainter::Antialiasing, false ); + + // Draw black/white border around the main window + painter.setPen( Qt::black ); + painter.drawRect( 0, titlebar.height() - 1, frame.width() - 1, frame.height() - titlebar.height() ); + painter.setPen( Qt::white ); + painter.drawRect( 1, titlebar.height(), frame.width() - 3, frame.height() - titlebar.height() - 2 ); + + QList< ClientGroupItem > tabList = clientGroupItems(); + const int tabCount = tabList.count(); + + // Delete unneeded tab close buttons + while( tabCount < closeButtons.size() || ( tabCount == 1 && closeButtons.size() > 0 )) + { + TabstripButton *btn = closeButtons.front(); + closeButtons.removeFirst(); + delete btn; + } + + if( tabCount > 1 ) + { + QRect allTabGeom = titleRect().adjusted( -1, -layoutMetric( LM_TitleEdgeTop ), 1, 0 ); + QRect tabGeom = allTabGeom; + tabGeom.setWidth( tabGeom.width() / tabCount + 1 ); // Split titlebar evenly + for( int i = 0; i < tabCount; ++i ) + { + // Last tab may have a different width due to rounding + if( i == tabCount - 1 ) + tabGeom.setWidth( allTabGeom.width() - tabGeom.width() * i + i - 1 ); + + // Actually paint the tab + paintTab( painter, tabGeom, tabList[i], isActive() && visibleClientGroupItem() == i ); + + // Create new close button if required + if( i >= closeButtons.size() ) + closeButtons.append( new TabstripButton( ItemCloseButton, this, i18n( "Close Item" ) ) ); + closeButtons[ i ]->setActive( isActive() && visibleClientGroupItem() == i ); + closeButtons[ i ]->move( tabGeom.right() - 18, tabGeom.bottom() - 18 ); + closeButtons[ i ]->installEventFilter( this ); + closeButtons[ i ]->show(); + + // Prepare for next iteration + tabGeom.translate( tabGeom.width() - 1, 0 ); + } + + // Draw border around the buttons + painter.setPen( Qt::black ); + painter.drawRect( 0, 0, allTabGeom.left(), allTabGeom.height() - 1 ); + painter.drawRect( allTabGeom.right() - 1, 0, frame.width() - allTabGeom.right(), allTabGeom.height() - 1 ); + painter.setPen( Qt::white ); + painter.drawRect( 1, 1, allTabGeom.left() - 2, allTabGeom.height() - 3 ); + painter.drawRect( allTabGeom.right(), 1, frame.width() - allTabGeom.right() - 2, allTabGeom.height() - 3 ); + + // Background behind the buttons + painter.fillRect( 2, 2, allTabGeom.left() - 3, allTabGeom.height() - 4, bgColor ); + painter.fillRect( allTabGeom.right() + 1, 2, frame.width() - allTabGeom.right() - 3, allTabGeom.height() - 4, bgColor ); + } + else + { + // Draw border around the titlebar + painter.setPen( Qt::black ); + painter.drawRect( titlebar.adjusted( 0, 0, -1, -1 )); + painter.setPen( Qt::white ); + painter.drawRect( titlebar.adjusted( 1, 1, -2, -2 )); + + // Background + painter.fillRect( titlebar.adjusted( 2, 2, -2, -2 ), bgColor ); + + // Window title + painter.setPen( textColor ); + QRect rect( titleRect().x() + 2, titleRect().y(), + titleRect().width() - 6, titleRect().height() - 3 ); + QFont font; + QFontMetrics metrics( font ); + QString string = metrics.elidedText( caption(), Qt::ElideRight, rect.width() ); + painter.drawText( rect, TabstripFactory::titleAlign() | Qt::AlignVCenter, string ); + } + } + +QString TabstripDecoration::visibleName() const + { + return i18n("Tabstrip"); + } + +void TabstripDecoration::init() + { + KCommonDecoration::init(); + widget()->setAutoFillBackground( false ); + widget()->setAttribute( Qt::WA_OpaquePaintEvent ); + widget()->setAcceptDrops( true ); + } + + +bool TabstripDecoration::eventFilter( QObject* o, QEvent* e ) + { + bool state = false; + if( e->type() == QEvent::MouseButtonPress ) + state = mouseButtonPressEvent( static_cast< QMouseEvent* >( e ) ); + + else if( e->type() == QEvent::MouseButtonRelease && widget() == o ) + state = mouseButtonReleaseEvent( static_cast< QMouseEvent* >( e ) ); + + else if( e->type() == QEvent::MouseMove ) + state = mouseMoveEvent( static_cast< QMouseEvent* >( e ) ); + + else if( e->type() == QEvent::DragEnter && widget() == o ) + state = dragEnterEvent( static_cast< QDragEnterEvent* >( e ) ); + + else if( e->type() == QEvent::DragMove && widget() == o ) + state = dragMoveEvent( static_cast< QDragMoveEvent* >( e ) ); + + else if( e->type() == QEvent::DragLeave && widget() == o ) + state = dragLeaveEvent( static_cast< QDragLeaveEvent* >( e ) ); + + else if( e->type() == QEvent::Drop && widget() == o ) + state = dropEvent( static_cast< QDropEvent* >( e ) ); + + else if( TabstripButton *btn = dynamic_cast< TabstripButton* >( o ) ) + if( e->type() == QEvent::MouseButtonRelease ) + { + if( static_cast(e)->button() == Qt::LeftButton ) + { closeClientGroupItem( closeButtons.indexOf( btn ) ); } + return true; + } + if( TabstripButton *btn = dynamic_cast< TabstripButton* >( o ) ) + if( e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseMove ) + { + QMouseEvent *past = static_cast< QMouseEvent* >( e ); + QPoint point = btn->mapToParent( past->pos() ); + QMouseEvent *ev = new QMouseEvent( past->type(), point, past->button(), past->buttons(), past->modifiers() ); + e = dynamic_cast< QEvent* >( ev ); + o = widget(); + } + return state || KCommonDecorationUnstable::eventFilter( o, e ); + } + +bool TabstripDecoration::mouseButtonPressEvent( QMouseEvent* e ) + { + click = widget()->mapToParent( e->pos() ); + int item = itemClicked( click ); + if( item >= 0 && + ( e->button() == Qt::MidButton || e->button() == Qt::LeftButton || e->button() == Qt::RightButton )) + { + click_in_progress = true; + button = e->button(); + return button != Qt::LeftButton; + } + click_in_progress = false; + return false; + } + +bool TabstripDecoration::mouseButtonReleaseEvent( QMouseEvent* e ) + { + release = e->pos(); + int item = itemClicked( release ); + if( click_in_progress && item >= 0 ) + { + click_in_progress = false; + if( e->button() == Qt::LeftButton || e->button() == Qt::MidButton ) + setVisibleClientGroupItem( item ); + else if( e->button() == Qt::RightButton ) + displayClientMenu( item, widget()->mapToGlobal( release ) ); + return true; + } + click_in_progress = false; + return false; + } + +bool TabstripDecoration::mouseMoveEvent( QMouseEvent* e ) + { + QPoint c = e->pos(); + int item = itemClicked( c ); + if( item >= 0 && click_in_progress && button == Qt::MidButton && ( c - click ).manhattanLength() >= 4 ) + { + click_in_progress = false; + drag_in_progress = true; + QDrag *drag = new QDrag( widget() ); + QMimeData *group_data = new QMimeData(); + group_data->setData( clientGroupItemDragMimeType(), QString().setNum( itemId( item )).toAscii() ); + drag->setMimeData( group_data ); + + // Create draggable tab pixmap + QList< ClientGroupItem > tabList = clientGroupItems(); + const int tabCount = tabList.count(); + QRect frame( QPoint( 0, 0 ), widget()->frameGeometry().size() ); + QRect titlebar( frame.topLeft(), QSize( frame.width(), + layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleHeight ) + + layoutMetric( LM_TitleEdgeBottom ) - 1 // Titlebar and main frame overlap by 1px + )); + QRect geom = titleRect().adjusted( -1, -layoutMetric( LM_TitleEdgeTop ), 1, 0 ); + geom.setWidth( geom.width() / tabCount + 1 ); // Split titlebar evenly + geom.translate( geom.width() * item - item, 0 ); + QPixmap pix( geom.size() ); + QPainter painter( &pix ); + paintTab( painter, QRect( QPoint( 0, 0 ), geom.size() ), tabList[item], + isActive() && visibleClientGroupItem() == item ); + drag->setPixmap( pix ); + // If the cursor is on top of the pixmap then it makes the movement jerky on some systems + //drag->setHotSpot( QPoint( c.x() - geom.x(), c.y() - geom.y() )); + drag->setHotSpot( QPoint( c.x() - geom.x(), -1 )); + + drag->exec( Qt::MoveAction ); + drag_in_progress = false; + if( drag->target() == 0 && tabList.count() > 1 ) + { // Remove window from group and move to where the cursor is located + QPoint pos = QCursor::pos(); + frame.moveTo( pos.x() - c.x(), pos.y() - c.y() ); + removeFromClientGroup( itemClicked( click ), frame ); + } + return true; + } + return false; + } + +bool TabstripDecoration::dragEnterEvent( QDragEnterEvent* e ) + { + if( e->source() != 0 && e->source()->objectName() == "decoration widget" ) + { + drag_in_progress = true; + e->acceptProposedAction(); + QPoint point = widget()->mapToParent( e->pos() ); + targetTab = itemClicked( point ); + widget()->update(); + return true; + } + return false; + } + +bool TabstripDecoration::dropEvent( QDropEvent* e ) + { + QPoint point = widget()->mapToParent( e->pos() ); + drag_in_progress = false; + int tabClick = itemClicked( point ); + if( tabClick >= 0 ) + { + const QMimeData *group_data = e->mimeData(); + if( group_data->hasFormat( clientGroupItemDragMimeType() ) ) + { + if( widget() == e->source() ) + { + int from = itemClicked( click ); + moveItemInClientGroup( from, itemClicked( point, true )); + } + else + { + int source = QString( group_data->data( clientGroupItemDragMimeType() ) ).toInt(); + moveItemToClientGroup( source, itemClicked( point, true )); + } + return true; + } + } + return false; + } + +bool TabstripDecoration::dragMoveEvent( QDragMoveEvent* ) + { + return false; + } + +bool TabstripDecoration::dragLeaveEvent( QDragLeaveEvent* ) + { + return false; + } + +int TabstripDecoration::layoutMetric(LayoutMetric lm, bool respectWindowState, const KCommonDecorationButton *button) const + { + switch ( lm ) + { + case LM_BorderBottom: + return 2; + case LM_BorderLeft: + case LM_BorderRight: + return 2; + case LM_TitleHeight: + return 17; + case LM_TitleBorderLeft: + return 3; + case LM_TitleBorderRight: + return 1; + case LM_TitleEdgeTop: + case LM_TitleEdgeBottom: + case LM_TitleEdgeLeft: + case LM_TitleEdgeRight: + return 3; + case LM_ButtonWidth: + case LM_ButtonHeight: + return 16; + case LM_ButtonSpacing: + return 6; + case LM_ExplicitButtonSpacer: + return -2; + default: + return KCommonDecoration::layoutMetric( lm, respectWindowState, button ); + break; + } + } + +int TabstripDecoration::itemClicked( const QPoint &point, bool between ) + { + QRect frame = widget()->frameGeometry(); + QList< ClientGroupItem > list = clientGroupItems(); + int tabs = list.count(); + int t_x = titleRect().x(); + int t_y = frame.y(); + int t_w = titleRect().width(); + int t_h = layoutMetric( LM_TitleEdgeTop ) + layoutMetric( LM_TitleHeight ) + layoutMetric( LM_TitleEdgeBottom ); + int tabWidth = t_w/tabs; + if( between ) // We are inserting a new tab between two existing ones + t_x -= tabWidth / 2; + int rem = t_w%tabs; + int tab_x = t_x; + for( int i = 0; i < tabs; ++i ) + { + QRect tabRect( tab_x, t_y, i +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#ifndef TABSTRIPDECORATION_H +#define TABSTRIPDECORATION_H + +#include +#include +#include +#include + +class TabstripButton; + +class TabstripDecoration : public KCommonDecorationUnstable + { + public: + TabstripDecoration( KDecorationBridge *bridge, KDecorationFactory *factory ); + ~TabstripDecoration(); + KCommonDecorationButton *createButton( ButtonType type ); + void init(); + QString visibleName() const; + virtual int layoutMetric(LayoutMetric lm, bool respectWindowState = true, const KCommonDecorationButton *button = 0) const; + virtual bool eventFilter( QObject* o, QEvent* e ); + private: + void paintTab( QPainter &painter, const QRect &geom, ClientGroupItem &item, bool active ); + void paintEvent( QPaintEvent *e ); + bool mouseSingleClickEvent( QMouseEvent* e ); + bool mouseMoveEvent( QMouseEvent* e ); + bool mouseButtonPressEvent( QMouseEvent* e ); + bool mouseButtonReleaseEvent( QMouseEvent* e ); + bool dragMoveEvent( QDragMoveEvent* e ); + bool dragLeaveEvent( QDragLeaveEvent* e ); + bool dragEnterEvent( QDragEnterEvent* e ); + bool dropEvent( QDropEvent* e ); + int itemClicked( const QPoint &point, bool between = false ); + QList< TabstripButton* > closeButtons; + QPoint click, release; + int targetTab; + bool click_in_progress, drag_in_progress; + Qt::MouseButton button; + }; + +#endif diff --git a/clients/tabstrip/tabstripfactory.cpp b/clients/tabstrip/tabstripfactory.cpp new file mode 100644 index 0000000000..80b7257711 --- /dev/null +++ b/clients/tabstrip/tabstripfactory.cpp @@ -0,0 +1,111 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#include "tabstripfactory.h" +#include "tabstripdecoration.h" + +#include +#include +#include + +extern "C" + { + KDE_EXPORT KDecorationFactory *create_factory() + { + return new TabstripFactory::TabstripFactory(); + } + } + +Qt::AlignmentFlag TabstripFactory::titlealign = Qt::AlignCenter; +bool TabstripFactory::show_icon = true; + +TabstripFactory::TabstripFactory() + { + initialized = false; + readConfig(); + initialized = true; + } + +TabstripFactory::~TabstripFactory() + { + } + +KDecoration *TabstripFactory::createDecoration( KDecorationBridge *bridge ) + { + return ( new TabstripDecoration( bridge, this ) )->decoration(); + } + +bool TabstripFactory::supports( Ability ability ) const + { + switch( ability ) + { + case AbilityButtonMenu: + case AbilityAnnounceColors: + case AbilityButtonOnAllDesktops: + case AbilityButtonSpacer: + case AbilityButtonHelp: + case AbilityButtonMinimize: + case AbilityButtonMaximize: + case AbilityButtonClose: + case AbilityButtonAboveOthers: + case AbilityButtonBelowOthers: + case AbilityButtonShade: + case AbilityClientGrouping: + return true; + default: + return false; + }; + } + +bool TabstripFactory::readConfig() + { + KConfig config( "tabstriprc" ); + KConfigGroup cg = config.group( "General" ); + Qt::AlignmentFlag oldalign = titlealign; + QString align = cg.readEntry( "TitleAlignment", "Center" ); + if( align == "Left" ) + titlealign = Qt::AlignLeft; + else if( align == "Center" ) + titlealign = Qt::AlignHCenter; + else if( align == "Right" ) + titlealign = Qt::AlignRight; + show_icon = cg.readEntry( "ShowIcon", true ); + return ( titlealign != oldalign ); + } + +bool TabstripFactory::reset( unsigned long changed ) + { + initialized = false; + bool c_change = readConfig(); + initialized = true; + if( c_change || ( changed & ( SettingDecoration | SettingButtons | SettingBorder ) ) ) + return true; + else + { + resetDecorations( changed ); + return false; + } + } + +QList< KDecorationDefines::BorderSize > TabstripFactory::borderSizes() const + { + return QList< BorderSize >() << BorderNormal; + } diff --git a/clients/tabstrip/tabstripfactory.h b/clients/tabstrip/tabstripfactory.h new file mode 100644 index 0000000000..2b667f039d --- /dev/null +++ b/clients/tabstrip/tabstripfactory.h @@ -0,0 +1,57 @@ +/******************************************************************** +Tabstrip KWin window decoration +This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata +Copyright (C) 2009 Lucas Murray + +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 . +*********************************************************************/ + +#ifndef TABSTRIPFACTORY_H +#define TABSTRIPFACTORY_H + +#include +#include +#include + +class TabstripFactory : public KDecorationFactoryUnstable + { + public: + TabstripFactory(); + ~TabstripFactory(); + KDecoration *createDecoration( KDecorationBridge *bridge ); + bool supports( Ability ability ) const; + bool reset( unsigned long changed ); + QList< KDecorationDefines::BorderSize > borderSizes() const; + static Qt::AlignmentFlag titleAlign(); + static bool showIcon(); + private: + bool readConfig(); + bool initialized; + static Qt::AlignmentFlag titlealign; + static bool show_icon; + }; + +inline Qt::AlignmentFlag TabstripFactory::titleAlign() + { + return titlealign; + } + +inline bool TabstripFactory::showIcon() + { + return show_icon; + } + +#endif diff --git a/effects.cpp b/effects.cpp index d1c1d9358c..77d317025d 100644 --- a/effects.cpp +++ b/effects.cpp @@ -261,6 +261,24 @@ void EffectsHandlerImpl::windowUnminimized( EffectWindow* c ) ep.second->windowUnminimized( c ); } +void EffectsHandlerImpl::clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ) + { + foreach( const EffectPair &ep, loaded_effects ) + ep.second->clientGroupItemSwitched( from, to ); + } + +void EffectsHandlerImpl::clientGroupItemAdded( EffectWindow* from, EffectWindow* to ) + { + foreach( const EffectPair &ep, loaded_effects ) + ep.second->clientGroupItemAdded( from, to ); + } + +void EffectsHandlerImpl::clientGroupItemRemoved( EffectWindow* c, EffectWindow* group ) + { + foreach( const EffectPair &ep, loaded_effects ) + ep.second->clientGroupItemRemoved( c, group ); + } + void EffectsHandlerImpl::desktopChanged( int old ) { foreach( const EffectPair &ep, loaded_effects ) @@ -1495,6 +1513,13 @@ void EffectWindowImpl::closeWindow() const } } +bool EffectWindowImpl::visibleInClientGroup() const + { + if( Client* c = dynamic_cast< Client* >( toplevel )) + return c == c->clientGroup()->visible(); + return false; + } + EffectWindow* effectWindow( Toplevel* w ) { EffectWindowImpl* ret = w->effectWindow(); diff --git a/effects.h b/effects.h index cf7711540a..0becb616af 100644 --- a/effects.h +++ b/effects.h @@ -147,6 +147,9 @@ class EffectsHandlerImpl : public EffectsHandler void windowActivated( EffectWindow* c ); void windowMinimized( EffectWindow* c ); void windowUnminimized( EffectWindow* c ); + void clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ); + void clientGroupItemAdded( EffectWindow* from, EffectWindow* to ); + void clientGroupItemRemoved( EffectWindow* c, EffectWindow* group ); void desktopChanged( int old ); void windowDamaged( EffectWindow* w, const QRect& r ); void windowGeometryShapeChanged( EffectWindow* w, const QRect& old ); @@ -264,6 +267,8 @@ class EffectWindowImpl : public EffectWindow virtual void unminimize() const; virtual void closeWindow() const; + virtual bool visibleInClientGroup() const; + const Toplevel* window() const; Toplevel* window(); diff --git a/effects/_test/slidetabs/CMakeLists.txt b/effects/_test/slidetabs/CMakeLists.txt new file mode 100644 index 0000000000..230a0d5390 --- /dev/null +++ b/effects/_test/slidetabs/CMakeLists.txt @@ -0,0 +1,26 @@ +####################################### +# Effect + +# Source files +set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + slidetabs/slidetabs.cpp + ) + +# .desktop files +install( FILES + slidetabs/slidetabs.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) + +####################################### +# Config + +# Source files +set( kwin4_effect_builtins_config_sources ${kwin4_effect_builtins_config_sources} + slidetabs/slidetabs_config.cpp + slidetabs/slidetabs_config.ui + ) + +# .desktop files +install( FILES + slidetabs/slidetabs_config.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) diff --git a/effects/_test/slidetabs/slidetabs.cpp b/effects/_test/slidetabs/slidetabs.cpp new file mode 100644 index 0000000000..7ffd56397b --- /dev/null +++ b/effects/_test/slidetabs/slidetabs.cpp @@ -0,0 +1,194 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#include "slidetabs.h" + +#include +#include + +namespace KWin +{ + +KWIN_EFFECT( slidetabs, SlideTabsEffect ) + +SlideTabsEffect::SlideTabsEffect() + { + reconfigure( ReconfigureAll ); + } + +void SlideTabsEffect::reconfigure( ReconfigureFlags ) + { + KConfigGroup conf = EffectsHandler::effectConfig("SlideTabs"); + switching = conf.readEntry("SlideSwitching", true ); + grouping = conf.readEntry("SlideGrouping", true ); + totalTime = conf.readEntry("SlideDuration", 500 ); + } + +void SlideTabsEffect::prePaintWindow( EffectWindow *w, WindowPrePaintData &data, int time ) + { + if( motionManager.isManaging( w ) ) + { + data.setTransformed(); + w->enablePainting( EffectWindow::PAINT_DISABLED ); + timeLine.addTime( time ); + } + effects->prePaintWindow( w, data, time ); + } + +void SlideTabsEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( motionManager.isManaging( w ) && w == inMove ) + motionManager.apply( w, data ); + effects->paintWindow( w, mask, region, data ); + } + +void SlideTabsEffect::postPaintWindow( EffectWindow* w ) + { + if( motionManager.isManaging( w ) ) + { + if( w == inMove ) + { + QRect moving = calculateNextMove(); + motionManager.moveWindow( w, moving ); + if( direction && timeLine.progress() >= 0.5 ) + { + moving = target; + target = source; + source = moving; + direction = false; + effects->setElevatedWindow( notMoving, false ); + effects->setElevatedWindow( w, true ); + } + else if( timeLine.progress() >= 1.0 ) + { + effects->setElevatedWindow( notMoving, false ); + effects->setElevatedWindow( inMove, false ); + motionManager.unmanage( notMoving ); + motionManager.unmanage( inMove ); + notMoving = NULL; + inMove = NULL; + wasD = false; + effects->addRepaintFull(); + } + } + else if( w == notMoving && !direction && target != w->geometry() && !wasD ) + { + target = w->geometry(); + } + w->addRepaintFull(); + } + effects->postPaintWindow( w ); + } + +void SlideTabsEffect::prePaintScreen( ScreenPrePaintData &data, int time ) + { + if( motionManager.managingWindows() ) + { + motionManager.calculate( time ); + data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + } + effects->prePaintScreen( data, time ); + } + +void SlideTabsEffect::postPaintScreen() + { + if( motionManager.managingWindows() ) + effects->addRepaintFull(); + effects->postPaintScreen(); + } + +void SlideTabsEffect::clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ) + { + if( !switching ) + return; + inMove = to; + notMoving = from; + source = notMoving->geometry(); + QRect window = notMoving->geometry(); + int leftSpace = window.x(), rightSpace = displayWidth() - ( window.x() + window.width() ), + upSpace = window.y(), downSpace = displayHeight() - ( window.y() + window.height() ); + + if( leftSpace >= rightSpace && leftSpace >= upSpace && leftSpace >= downSpace ) + target = QRect( source.x() - ( 1.2 * source.width() ), source.y(), source.width(), source.height() ); + else if( rightSpace >= leftSpace && rightSpace >= upSpace && rightSpace >= downSpace ) + target = QRect( source.x() + ( 1.2 * source.width() ), source.y(), source.width(), source.height() ); + else if( upSpace >= leftSpace && upSpace >= rightSpace && upSpace >= downSpace ) + target = QRect( source.x(), source.y() - ( 1.2 * source.height() ), source.width(), source.height() ); + else + target = QRect( source.x(), source.y() + ( 1.2 * source.height() ), source.width(), source.height() ); + + timeLine.setCurveShape( TimeLine::LinearCurve ); + timeLine.setDuration( animationTime( totalTime ) ); + timeLine.setProgress( 0.0f ); + motionManager.manage( inMove ); + motionManager.manage( notMoving ); + distance = sqrt( ( ( source.x()-target.x() ) * ( source.x()-target.x() ) ) + ( ( source.y()-target.y() ) * ( source.y()-target.y() ) ) ); + effects->setElevatedWindow( notMoving, true ); + direction = wasD = true; + + QRect moving = calculateNextMove(); + motionManager.moveWindow( inMove, moving ); + } + +void SlideTabsEffect::clientGroupItemAdded( EffectWindow* from, EffectWindow* to ) + { + if( !grouping || from->desktop() != to->desktop() || from->isMinimized() || to->isMinimized() ) + return; + timeLine.setCurveShape( TimeLine::LinearCurve ); + timeLine.setDuration( animationTime( totalTime ) ); + timeLine.setProgress( 0.0f ); + inMove = from; + notMoving = to; + source = inMove->geometry(); + target = notMoving->geometry(); + distance = sqrt( ( ( source.x()-target.x() ) * ( source.x()-target.x() ) ) + ( ( source.y()-target.y() ) * ( source.y()-target.y() ) ) ); + motionManager.manage( inMove ); + motionManager.manage( notMoving ); + QRect moving = calculateNextMove(); + motionManager.moveWindow( inMove, moving ); + effects->setElevatedWindow( notMoving, true ); + direction = wasD = false; + } + +QPoint SlideTabsEffect::calculatePointTarget( const QPoint &a, const QPoint &b ) + { + double dy, dx, x, y, k; + k = direction? (2.0*timeLine.progress()):(wasD?((timeLine.progress()-0.5)*2):timeLine.progress()); + dx = fabs( a.x() - b.x() ); + dy = fabs( a.y() - b.y() ); + y = k*dy; + x = k*dx; + if( a.x() > b.x() ) + x = -x; + if( a.y() > b.y() ) + y = -y; + return QPoint( a.x() + x, a.y() + y ); + } + +QRect SlideTabsEffect::calculateNextMove() + { + QPoint topLeft, bottomRight; + int mw = source.width(), mh = source.height(), tw = target.width(), th = target.height(); + topLeft = calculatePointTarget( QPoint( source.x(), source.y() ), QPoint( target.x(), target.y() ) ); + bottomRight = calculatePointTarget( QPoint( source.x() + mw, source.y() + mh ), QPoint( target.x() + tw, target.y() + th ) ); + return QRect( topLeft.x(), topLeft.y(), bottomRight.x() - topLeft.x(), bottomRight.y() - topLeft.y() ); + } + +} diff --git a/effects/_test/slidetabs/slidetabs.desktop b/effects/_test/slidetabs/slidetabs.desktop new file mode 100644 index 0000000000..c2f7461ea8 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Name=Slide Tabs +Name[x-test]=xxSlide Tabsxx + +Icon=preferences-system-windows-effect-slidetabs + +Type=Service +Comment=Slide windows when switching or groupping tabs. + +Type=Service +X-KDE-ServiceTypes=KWin/Effect +X-KDE-PluginInfo-Author=Jorge Mata +X-KDE-PluginInfo-Email=matamax123@gmail.com +X-KDE-PluginInfo-Name=kwin4_effect_slidetabs +X-KDE-PluginInfo-Version=0.1.0 +X-KDE-PluginInfo-Category=Focus +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +X-KDE-Library=kwin4_effect_builtins +X-KDE-Ordering=50 diff --git a/effects/_test/slidetabs/slidetabs.h b/effects/_test/slidetabs/slidetabs.h new file mode 100644 index 0000000000..7f83ba11d9 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs.h @@ -0,0 +1,56 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#ifndef KWIN_SLIDETABS_H +#define KWIN_SLIDETABS_H + +#include + +namespace KWin +{ + +class SlideTabsEffect : public Effect + { + public: + SlideTabsEffect(); + virtual void reconfigure( ReconfigureFlags ); + virtual void prePaintWindow( EffectWindow *w, WindowPrePaintData &data, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + virtual void prePaintScreen( ScreenPrePaintData &data, int time ); + virtual void postPaintScreen(); + virtual void clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ); + virtual void clientGroupItemAdded( EffectWindow* from, EffectWindow* to ); + + private: + QRect calculateNextMove(); + QPoint calculatePointTarget( const QPoint &a, const QPoint &b ); + WindowMotionManager motionManager; + EffectWindow* inMove; + EffectWindow* notMoving; + TimeLine timeLine; + QRect target, source; + bool direction, wasD, grouping, switching; + int totalTime, distance; + }; + +} + +#endif diff --git a/effects/_test/slidetabs/slidetabs_config.cpp b/effects/_test/slidetabs/slidetabs_config.cpp new file mode 100644 index 0000000000..2fd530afa2 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs_config.cpp @@ -0,0 +1,95 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#include "slidetabs_config.h" + +#include + +#include +#include +#include +#include + +namespace KWin +{ + +KWIN_EFFECT_CONFIG_FACTORY + +SlideTabsEffectConfigForm::SlideTabsEffectConfigForm( QWidget* parent ) : QWidget( parent ) + { + setupUi( this ); + } + +SlideTabsEffectConfig::SlideTabsEffectConfig(QWidget* parent, const QVariantList& args) + : KCModule( EffectFactory::componentData(), parent, args ) + { + m_ui = new SlideTabsEffectConfigForm( this ); + + QVBoxLayout* layout = new QVBoxLayout( this ); + + layout->addWidget( m_ui ); + + connect( m_ui->grouping, SIGNAL( toggled( bool )), this, SLOT( changed() ) ); + connect( m_ui->switching, SIGNAL( toggled( bool )), this, SLOT( changed() ) ); + connect( m_ui->duration, SIGNAL( valueChanged( int ) ), this, SLOT( changed() ) ); + + load(); + } + +void SlideTabsEffectConfig::save() + { + KConfigGroup conf = EffectsHandler::effectConfig( "SlideTabs" ); + + conf.writeEntry( "SlideGrouping", m_ui->grouping->isChecked() ); + conf.writeEntry( "SlideSwitching", m_ui->switching->isChecked() ); + conf.writeEntry( "SlideDuration", m_ui->duration->value() ); + + conf.sync(); + + KCModule::save(); + emit changed( false ); + EffectsHandler::sendReloadMessage( "slidetabs" ); + } + +void SlideTabsEffectConfig::load() + { + KCModule::load(); + KConfigGroup conf = EffectsHandler::effectConfig( "SlideTabs" ); + + bool switching = conf.readEntry( "SlideSwitching", true ); + bool grouping = conf.readEntry( "SlideGrouping", true ); + int duration = conf.readEntry("SlideDuration", 500 ); + m_ui->switching->setChecked( switching ); + m_ui->grouping->setChecked( grouping ); + m_ui->duration->setValue( duration ); + emit changed( false ); + } + +void SlideTabsEffectConfig::defaults() + { + m_ui->grouping->setChecked( true ); + m_ui->switching->setChecked( true ); + m_ui->duration->setValue( 500 ); + emit changed( true ); + } + +} + +#include "slidetabs_config.moc" diff --git a/effects/_test/slidetabs/slidetabs_config.desktop b/effects/_test/slidetabs/slidetabs_config.desktop new file mode 100644 index 0000000000..a0f6347ae9 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs_config.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_kwin4_effect_builtins +X-KDE-ParentComponents=kwin4_effect_slidetabs +X-KDE-PluginKeyword=slidetabs + +Name=Slide Tabs +Name[x-test]=xxSlide Tabsxx + diff --git a/effects/_test/slidetabs/slidetabs_config.h b/effects/_test/slidetabs/slidetabs_config.h new file mode 100644 index 0000000000..c6101002c5 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs_config.h @@ -0,0 +1,53 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#ifndef KWIN_SLIDETABS_CONFIG_H +#define KWIN_SLIDETABS_CONFIG_H + +#include + +#include "ui_slidetabs_config.h" + +namespace KWin +{ + +class SlideTabsEffectConfigForm : public QWidget, public Ui::SlideTabsEffectConfigForm + { + Q_OBJECT + public: + explicit SlideTabsEffectConfigForm( QWidget* parent ); + }; + +class SlideTabsEffectConfig : public KCModule + { + Q_OBJECT + public: + explicit SlideTabsEffectConfig( QWidget* parent = 0, const QVariantList& args = QVariantList() ); + public slots: + virtual void save(); + virtual void load(); + virtual void defaults(); + private: + SlideTabsEffectConfigForm* m_ui; + }; + +} + +#endif diff --git a/effects/_test/slidetabs/slidetabs_config.ui b/effects/_test/slidetabs/slidetabs_config.ui new file mode 100644 index 0000000000..6d194b6462 --- /dev/null +++ b/effects/_test/slidetabs/slidetabs_config.ui @@ -0,0 +1,66 @@ + + + KWin::SlideTabsEffectConfigForm + + + + 0 + 0 + 238 + 90 + + + + + + + + + + Slide when grouping + + + + + + + slide when switching tabs + + + + + + + + + Duration + + + + + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + diff --git a/effects/_test/swiveltabs/CMakeLists.txt b/effects/_test/swiveltabs/CMakeLists.txt new file mode 100644 index 0000000000..625653380e --- /dev/null +++ b/effects/_test/swiveltabs/CMakeLists.txt @@ -0,0 +1,26 @@ +####################################### +# Effect + +# Source files +set( kwin4_effect_builtins_sources ${kwin4_effect_builtins_sources} + swiveltabs/swiveltabs.cpp + ) + +# .desktop files +install( FILES + swiveltabs/swiveltabs.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) + +####################################### +# Config + +# Source files +set( kwin4_effect_builtins_config_sources ${kwin4_effect_builtins_config_sources} + swiveltabs/swiveltabs_config.cpp + swiveltabs/swiveltabs_config.ui + ) + +# .desktop files +install( FILES + swiveltabs/swiveltabs_config.desktop + DESTINATION ${SERVICES_INSTALL_DIR}/kwin ) diff --git a/effects/_test/swiveltabs/swiveltabs.cpp b/effects/_test/swiveltabs/swiveltabs.cpp new file mode 100644 index 0000000000..37543813a9 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs.cpp @@ -0,0 +1,157 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#include "swiveltabs.h" +#include + +namespace KWin +{ + +KWIN_EFFECT( swiveltabs, SwivelTabsEffect ) +KWIN_EFFECT_SUPPORTED( swiveltabs, SwivelTabsEffect::supported() ) + +SwivelTabsEffect::SwivelTabsEffect() + { + isActive = false; + PI = 2.0 * acos( 0.0 ); + reconfigure( ReconfigureAll ); + } + +bool SwivelTabsEffect::supported() + { + return effects->compositingType() == OpenGLCompositing; + } + +void SwivelTabsEffect::reconfigure( ReconfigureFlags ) + { + KConfigGroup conf = EffectsHandler::effectConfig("SwivelTabs"); + vertical = conf.readEntry("SwivelVertical", true ); + horizontal = conf.readEntry("SwivelHorizontal", true ); + totalTime = conf.readEntry("SwivelDuration", 500 ); + } + +void SwivelTabsEffect::prePaintScreen( ScreenPrePaintData& data, int time ) + { + if( isActive ) + data.mask |= Effect::PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS; + effects->prePaintScreen( data, time ); + } + +void SwivelTabsEffect::prePaintWindow( EffectWindow *w, WindowPrePaintData &data, int time ) + { + if( isActive && ( w == windows.show || w == windows.hide ) ) + { + data.quads = data.quads.makeGrid( 40 ); + data.setTransformed(); + w->enablePainting( EffectWindow::PAINT_DISABLED ); + windows.time.addTime( time ); + } + effects->prePaintWindow( w, data, time ); + } + +void SwivelTabsEffect::paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ) + { + if( isActive && ( w == windows.show || w == windows.hide ) ) + { + for( int i = 0; i < data.quads.count(); ++i ) + transformQuad( data.quads[i] ); + } + effects->paintWindow( w, mask, region, data ); + } + +void SwivelTabsEffect::postPaintWindow( EffectWindow* w ) + { + if( isActive && ( w == windows.show || w == windows.hide ) ) + w->addRepaintFull(); + effects->postPaintWindow( w ); + } + +void SwivelTabsEffect::transformQuad( WindowQuad &quad ) + { + double F = windows.time.progress(); + int width = quad[1].x()-quad[0].x(); + int height = quad[3].y() - quad[0].y(); + int cx = quad[0].x() + ( width/2 ); + int cy = quad[0].y() + ( height/2 ); + if( lastF < 0.5 && F > 0.5 ) + { + effects->setElevatedWindow( windows.hide, false ); + effects->setElevatedWindow( windows.show, true ); + } + lastF = F; + if( F >= 1.0 ) + { + isActive = false; + effects->setElevatedWindow( windows.hide, false ); + effects->setElevatedWindow( windows.show, false ); + } + if( F < 0.5 ) + { + if( horizontal ) + { + quad[0].setX( quad[0].x() + ( width * F ) ); + quad[3].setX( quad[3].x() + ( width * F ) ); + quad[1].setX( quad[1].x() - ( width * F ) ); + quad[2].setX( quad[2].x() - ( width * F ) ); + } + if( vertical ) + { + quad[0].setY( quad[0].y() + ( height * F ) ); + quad[3].setY( quad[3].y() - ( height * F ) ); + quad[1].setY( quad[1].y() + ( height * F ) ); + quad[2].setY( quad[2].y() - ( height * F ) ); + } + } + else + { + F -= 0.5; + if( horizontal ) + { + quad[0].setX( cx - ( width * F ) ); + quad[3].setX( cx - ( width * F ) ); + quad[1].setX( cx + ( width * F ) ); + quad[2].setX( cx + ( width * F ) ); + } + if( vertical ) + { + quad[0].setY( cy - ( height * F ) ); + quad[3].setY( cy + ( height * F ) ); + quad[1].setY( cy - ( height * F ) ); + quad[2].setY( cy + ( height * F ) ); + } + } + } + +void SwivelTabsEffect::clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ) + { + if( isActive || from->isMinimized() ) + return; + windows.show = to; + windows.hide = from; + windows.time.setCurveShape( TimeLine::LinearCurve ); + windows.time.setDuration( animationTime( totalTime ) ); + windows.time.setProgress( 0.0f ); + lastF = 0.0; + isActive = true; + effects->setElevatedWindow( to, false ); + effects->setElevatedWindow( from, true ); + } + +} diff --git a/effects/_test/swiveltabs/swiveltabs.desktop b/effects/_test/swiveltabs/swiveltabs.desktop new file mode 100644 index 0000000000..05d18c9c04 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs.desktop @@ -0,0 +1,18 @@ +[Desktop Entry] +Name=Swivel Tabs +Name[x-test]=xxSwivel Tabsxx +Icon=preferences-system-windows-effect-swiveltabs +Comment=Make the windows turn when switching tabs +Comment[x-test]=xxMake the windows turn when switching tabsxx + +Type=Service +X-KDE-ServiceTypes=KWin/Effect +X-KDE-PluginInfo-Author=Jorge Mata +X-KDE-PluginInfo-Email=matamax123@gmail.com +X-KDE-PluginInfo-Name=kwin4_effect_swiveltabs +X-KDE-PluginInfo-Version=0.1.0 +X-KDE-PluginInfo-Category=Focus +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +X-KDE-Library=kwin4_effect_builtins diff --git a/effects/_test/swiveltabs/swiveltabs.h b/effects/_test/swiveltabs/swiveltabs.h new file mode 100644 index 0000000000..df6a543363 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs.h @@ -0,0 +1,62 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#ifndef KWIN_SWIVELTABS_H +#define KWIN_SWIVELTABS_H + +#include + +namespace KWin +{ + +class SwivelTabsEffect : public Effect + { + public: + SwivelTabsEffect(); + virtual void reconfigure( ReconfigureFlags ); + virtual void prePaintScreen( ScreenPrePaintData& data, int time ); + virtual void prePaintWindow( EffectWindow *w, WindowPrePaintData &data, int time ); + virtual void paintWindow( EffectWindow* w, int mask, QRegion region, WindowPaintData& data ); + virtual void postPaintWindow( EffectWindow* w ); + virtual void clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ); + static bool supported(); + private: + struct swivel + { + EffectWindow* hide; + EffectWindow* show; + TimeLine time; + // The points of the transformed window + QPoint topLeft, topRight, bottomLeft, bottomRight; + // The size of the transformed window + double left, top, right, bottom; + }; + swivel windows; + bool isActive, horizontal, vertical; + int totalTime, kal; + double PI, lastF; + void nextStep( EffectWindow *w ); + void transformQuad( WindowQuad &quad ); + double calculateCoords( double original, double space, double newSpace ); + }; + +} + +#endif diff --git a/effects/_test/swiveltabs/swiveltabs_config.cpp b/effects/_test/swiveltabs/swiveltabs_config.cpp new file mode 100644 index 0000000000..295d01b178 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs_config.cpp @@ -0,0 +1,91 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#include "swiveltabs_config.h" + +#include + +#include + +namespace KWin +{ + +KWIN_EFFECT_CONFIG_FACTORY + +SwivelTabsEffectConfigForm::SwivelTabsEffectConfigForm( QWidget* parent ) : QWidget( parent ) + { + setupUi( this ); + } + +SwivelTabsEffectConfig::SwivelTabsEffectConfig(QWidget* parent, const QVariantList& args) + : KCModule( EffectFactory::componentData(), parent, args ) + { + m_ui = new SwivelTabsEffectConfigForm( this ); + QVBoxLayout* layout = new QVBoxLayout( this ); + layout->addWidget( m_ui ); + connect( m_ui->vertical, SIGNAL( toggled( bool )), this, SLOT( changed() ) ); + connect( m_ui->horizontal, SIGNAL( toggled( bool )), this, SLOT( changed() ) ); + connect( m_ui->duration, SIGNAL( valueChanged( int ) ), this, SLOT( changed() ) ); + load(); + } + +SwivelTabsEffectConfig::~SwivelTabsEffectConfig() + { + delete m_ui; + } + +void SwivelTabsEffectConfig::save() + { + KConfigGroup conf = EffectsHandler::effectConfig( "SwivelTabs" ); + + conf.writeEntry( "SwivelVertical", m_ui->vertical->isChecked() ); + conf.writeEntry( "SwivelHorizontal", m_ui->horizontal->isChecked() ); + conf.writeEntry( "SwivelDuration", m_ui->duration->value() ); + + conf.sync(); + + KCModule::save(); + emit changed( false ); + EffectsHandler::sendReloadMessage( "swiveltabs" ); + } + +void SwivelTabsEffectConfig::load() + { + KCModule::load(); + KConfigGroup conf = EffectsHandler::effectConfig( "SwivelTabs" ); + + bool vertical = conf.readEntry( "SwivelVertical", true ); + bool horizontal = conf.readEntry( "SwivelHorizontal", true ); + int duration = conf.readEntry("SwivelDuration", 500 ); + m_ui->vertical->setChecked( vertical ); + m_ui->horizontal->setChecked( horizontal ); + m_ui->duration->setValue( duration ); + emit changed( false ); + } + +void SwivelTabsEffectConfig::defaults() + { + m_ui->vertical->setChecked( true ); + m_ui->horizontal->setChecked( true ); + m_ui->duration->setValue( 500 ); + emit changed( true ); + } + +} diff --git a/effects/_test/swiveltabs/swiveltabs_config.desktop b/effects/_test/swiveltabs/swiveltabs_config.desktop new file mode 100644 index 0000000000..d371f6ce06 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs_config.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceTypes=KCModule + +X-KDE-Library=kcm_kwin4_effect_builtins +X-KDE-ParentComponents=kwin4_effect_swiveltabs +X-KDE-PluginKeyword=swiveltabs + +Name=Swivel Tabs +Name[x-test]=xxSwivel Tabsxx diff --git a/effects/_test/swiveltabs/swiveltabs_config.h b/effects/_test/swiveltabs/swiveltabs_config.h new file mode 100644 index 0000000000..285e297a9b --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs_config.h @@ -0,0 +1,53 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2009 Jorge Mata + +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 . +*********************************************************************/ + +#ifndef KWIN_SWIVELTABS_CONFIG_H +#define KWIN_SWIVELTABS_CONFIG_H + +#include +#include "ui_swiveltabs_config.h" + +namespace KWin +{ + +class SwivelTabsEffectConfigForm : public QWidget, public Ui::SwivelTabsEffectConfigForm + { + Q_OBJECT + public: + explicit SwivelTabsEffectConfigForm( QWidget* parent ); + }; + +class SwivelTabsEffectConfig : public KCModule + { + Q_OBJECT + public: + explicit SwivelTabsEffectConfig(QWidget* parent = 0, const QVariantList& args = QVariantList()); + ~SwivelTabsEffectConfig(); + public slots: + virtual void save(); + virtual void load(); + virtual void defaults(); + private: + SwivelTabsEffectConfigForm *m_ui; + }; + +} + +#endif diff --git a/effects/_test/swiveltabs/swiveltabs_config.ui b/effects/_test/swiveltabs/swiveltabs_config.ui new file mode 100644 index 0000000000..8c63bdfc96 --- /dev/null +++ b/effects/_test/swiveltabs/swiveltabs_config.ui @@ -0,0 +1,87 @@ + + + KWin::SwivelTabsEffectConfigForm + + + + 0 + 0 + 234 + 102 + + + + + + + + + + + + + + + + + + Vertical + + + + + + + Horizontal + + + + + + + + + + + + Duration + + + + + + + + 0 + + + 9999 + + + 250 + + + + + + + Qt::Horizontal + + + + 1 + 20 + + + + + + + + + + + + + + diff --git a/effects/presentwindows/presentwindows.cpp b/effects/presentwindows/presentwindows.cpp index 67d104a3bf..d2684f6567 100644 --- a/effects/presentwindows/presentwindows.cpp +++ b/effects/presentwindows/presentwindows.cpp @@ -1614,6 +1614,8 @@ bool PresentWindowsEffect::isSelectableWindow( EffectWindow *w ) return false; if( !w->acceptsFocus() ) return false; + if( !w->visibleInClientGroup() ) + return false; switch( m_mode ) { case ModeAllDesktops: diff --git a/effects/slideback/slideback.cpp b/effects/slideback/slideback.cpp index 1f7884c98a..c96b4a583c 100644 --- a/effects/slideback/slideback.cpp +++ b/effects/slideback/slideback.cpp @@ -61,6 +61,18 @@ void SlideBackEffect::windowActivated( EffectWindow* w ) return; } + if( clientItemShown == w ) + { + clientItemShown = NULL; + updateStackingOrder(); + return; + } + if( clientItemHidden == w ) + { + clientItemHidden = NULL; + updateStackingOrder(); + return; + } // Determine all windows on top of the activated one bool currentFound = false; foreach( EffectWindow *tmp, oldStackingOrder ) @@ -351,6 +363,12 @@ void SlideBackEffect::windowUnminimized( EffectWindow* w) } } +void SlideBackEffect::clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ) + { + clientItemShown = to; + clientItemHidden = from; + } + void SlideBackEffect::tabBoxClosed() { disabled = true; @@ -368,7 +386,8 @@ bool SlideBackEffect::isWindowOnTop( EffectWindow* w ) bool SlideBackEffect::isWindowUsable( EffectWindow* w ) { - return w && ( w->isNormalWindow() || w->isDialog() ) && !w->keepAbove() && !w->isDeleted() && !w->isMinimized(); + return w && ( w->isNormalWindow() || w->isDialog() ) && !w->keepAbove() && !w->isDeleted() && !w->isMinimized() + && w->visibleInClientGroup(); } bool SlideBackEffect::intersects( EffectWindow* windowUnder, const QRect &windowOverGeometry ) diff --git a/effects/slideback/slideback.h b/effects/slideback/slideback.h index a4bed3561c..78b034a963 100644 --- a/effects/slideback/slideback.h +++ b/effects/slideback/slideback.h @@ -45,6 +45,7 @@ class SlideBackEffect virtual void windowDeleted( EffectWindow* w ); virtual void windowAdded( EffectWindow* w ); virtual void windowUnminimized( EffectWindow* w ); + virtual void clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ); virtual void tabBoxClosed(); @@ -56,6 +57,8 @@ class SlideBackEffect EffectWindowList coveringWindows; EffectWindowList elevatedList; EffectWindow* unminimizedWindow; + EffectWindow* clientItemShown; + EffectWindow* clientItemHidden; QHash destinationList; bool disabled; QList clippedRegions; diff --git a/events.cpp b/events.cpp index ec87ba9506..3315e2e11a 100644 --- a/events.cpp +++ b/events.cpp @@ -1367,6 +1367,8 @@ bool Client::buttonReleaseEvent( Window w, int /*button*/, int state, int x, int QPoint mousepos( x_root - x, y_root - y ); mode = mousePosition( mousepos ); } + else if( workspace()->decorationSupportsClientGrouping() ) + return false; updateCursor(); } return true; diff --git a/geometry.cpp b/geometry.cpp index 20f9d12aeb..8ecf4f8f71 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -402,6 +402,7 @@ QPoint Workspace::adjustClientPosition( Client* c, QPoint pos, bool unrestricted { if ((*l)->isOnDesktop(c->desktop()) && !(*l)->isMinimized() + && (!(*l)->clientGroup() || (*l) != (*l)->clientGroup()->visible()) && (*l) != c ) { lx = (*l)->x(); @@ -1345,8 +1346,8 @@ QSize Client::sizeForClientSize( const QSize& wsize, Sizemode mode, bool noframe // basesize, minsize, maxsize, paspect and resizeinc have all values defined, // even if they're not set in flags - see getWmNormalHints() - QSize min_size = minSize(); - QSize max_size = maxSize(); + QSize min_size = clientGroup() ? clientGroup()->minSize() : minSize(); + QSize max_size = clientGroup() ? clientGroup()->maxSize() : maxSize(); if( decoration != NULL ) { QSize decominsize = decoration->minimumSize(); @@ -1902,8 +1903,8 @@ bool Client::isResizable() const if( rules()->checkSize( QSize()).isValid()) // forced size return false; - QSize min = minSize(); - QSize max = maxSize(); + QSize min = clientGroup() ? clientGroup()->minSize() : minSize(); + QSize max = clientGroup() ? clientGroup()->maxSize() : maxSize(); return min.width() < max.width() || min.height() < max.height(); } @@ -2025,6 +2026,10 @@ void Client::setGeometry( int x, int y, int w, int h, ForceGeometry_t force ) addWorkspaceRepaint( deco_rect ); geom_before_block = geom; deco_rect_before_block = deco_rect; + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::plainResize( int w, int h, ForceGeometry_t force ) @@ -2097,6 +2102,10 @@ void Client::plainResize( int w, int h, ForceGeometry_t force ) addWorkspaceRepaint( deco_rect ); geom_before_block = geom; deco_rect_before_block = deco_rect; + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } /*! @@ -2138,6 +2147,10 @@ void Client::move( int x, int y, ForceGeometry_t force ) addWorkspaceRepaint( deco_rect ); // trigger repaint of window's new location geom_before_block = geom; deco_rect_before_block = deco_rect; + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::blockGeometryUpdates( bool block ) @@ -2181,6 +2194,10 @@ void Client::setMaximize( bool vertically, bool horizontally ) false ); if( effects ) static_cast(effects)->windowUserMovedResized( effectWindow(), true, true ); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) @@ -3005,7 +3022,6 @@ void Client::handleMoveResize( int x, int y, int x_root, int y_root ) abort(); break; } - // adjust new size to snap to other windows/borders moveResizeGeom = workspace()->adjustClientSize( this, moveResizeGeom, mode ); diff --git a/kcmkwin/kwindecoration/preview.cpp b/kcmkwin/kwindecoration/preview.cpp index 8acef9f87b..058426d529 100644 --- a/kcmkwin/kwindecoration/preview.cpp +++ b/kcmkwin/kwindecoration/preview.cpp @@ -467,6 +467,58 @@ bool KDecorationPreviewBridge::compositingActive() const return KWindowSystem::compositingActive(); } +// Window tabbing + +bool KDecorationPreviewBridge::isClientGroupActive() + { + return active; + } + +QList< ClientGroupItem > KDecorationPreviewBridge::clientGroupItems() const + { + return QList< ClientGroupItem >() << ClientGroupItem( + active ? "Active Window" : "Inactive Window", icon() ); + } + +int KDecorationPreviewBridge::itemId( int ) + { + return 0; + } + +int KDecorationPreviewBridge::visibleClientGroupItem() + { + return 0; + } + +void KDecorationPreviewBridge::setVisibleClientGroupItem( int ) + { + } + +void KDecorationPreviewBridge::moveItemInClientGroup( int, int ) + { + } + +void KDecorationPreviewBridge::moveItemToClientGroup( int, int ) + { + } + +void KDecorationPreviewBridge::removeFromClientGroup( int, const QRect& ) + { + } + +void KDecorationPreviewBridge::closeClientGroupItem( int ) + { + } + +void KDecorationPreviewBridge::closeAllInClientGroup() + { + } + +void KDecorationPreviewBridge::displayClientMenu( int, const QPoint& ) + { + } + + KDecorationPreviewOptions::KDecorationPreviewOptions() { customBorderSize = BordersCount; // invalid diff --git a/kcmkwin/kwindecoration/preview.h b/kcmkwin/kwindecoration/preview.h index 4e04a10489..cee3d03d09 100644 --- a/kcmkwin/kwindecoration/preview.h +++ b/kcmkwin/kwindecoration/preview.h @@ -115,6 +115,20 @@ class KDecorationPreviewBridge virtual void grabXServer( bool grab ); virtual bool compositingActive() const; + + // Window tabbing + virtual bool isClientGroupActive(); + virtual QList< ClientGroupItem > clientGroupItems() const; + virtual int itemId( int index ); + virtual int visibleClientGroupItem(); + virtual void setVisibleClientGroupItem( int index ); + virtual void moveItemInClientGroup( int index, int before ); + virtual void moveItemToClientGroup( int itemId, int before ); + virtual void removeFromClientGroup( int index, const QRect& newGeom ); + virtual void closeClientGroupItem( int index ); + virtual void closeAllInClientGroup(); + virtual void displayClientMenu( int index, const QPoint& pos ); + private: KDecorationPreview* preview; bool active; diff --git a/kwinbindings.cpp b/kwinbindings.cpp index d48a184948..01b55a0977 100644 --- a/kwinbindings.cpp +++ b/kwinbindings.cpp @@ -54,6 +54,9 @@ along with this program. If not, see . a->setText( i18n("Navigation") ); DEF( I18N_NOOP("Walk Through Windows"), Qt::ALT+Qt::Key_Tab, slotWalkThroughWindows() ); DEF( I18N_NOOP("Walk Through Windows (Reverse)"), Qt::ALT+Qt::SHIFT+Qt::Key_Backtab, slotWalkBackThroughWindows() ); + DEF( I18N_NOOP("Walk Through Grouped Windows"), 0, slotSwitchToTabRight() ); + DEF( I18N_NOOP("Walk Through Grouped Windows (Reverse)"), 0, slotSwitchToTabLeft() ); + DEF( I18N_NOOP("Remove Window From Group"), 0, slotRemoveFromGroup() ); DEF( I18N_NOOP("Walk Through Windows Alternative"), 0, slotWalkThroughWindowsAlternative() ); DEF( I18N_NOOP("Walk Through Windows Alternative (Reverse)"), 0, slotWalkBackThroughWindowsAlternative() ); DEF( I18N_NOOP("Walk Through Desktops"), 0, slotWalkThroughDesktops() ); diff --git a/layers.cpp b/layers.cpp index 71d5b73fb4..4a4102b9f5 100644 --- a/layers.cpp +++ b/layers.cpp @@ -820,6 +820,10 @@ void Client::setKeepAbove( bool b ) decoration->emitKeepAboveChanged( keepAbove()); workspace()->updateClientLayer( this ); updateWindowRules(); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } void Client::setKeepBelow( bool b ) @@ -839,6 +843,10 @@ void Client::setKeepBelow( bool b ) decoration->emitKeepBelowChanged( keepBelow()); workspace()->updateClientLayer( this ); updateWindowRules(); + + // Update states of all other windows in this group + if( clientGroup() ) + clientGroup()->updateStates( this ); } Layer Client::layer() const diff --git a/lib/kcommondecoration.cpp b/lib/kcommondecoration.cpp index 321f0c283e..470948bcd5 100644 --- a/lib/kcommondecoration.cpp +++ b/lib/kcommondecoration.cpp @@ -1279,5 +1279,67 @@ bool KCommonDecorationUnstable::compositingActive() const return static_cast( decoration() )->compositingActive(); } +// Window tabbing + +bool KCommonDecorationUnstable::isClientGroupActive() + { + return static_cast( decoration() )->isClientGroupActive(); + } + +QList< ClientGroupItem > KCommonDecorationUnstable::clientGroupItems() const + { + return static_cast( decoration() )->clientGroupItems(); + } + +int KCommonDecorationUnstable::itemId( int index ) + { + return static_cast( decoration() )->itemId( index ); + } + +int KCommonDecorationUnstable::visibleClientGroupItem() + { + return static_cast( decoration() )->visibleClientGroupItem(); + } + +void KCommonDecorationUnstable::setVisibleClientGroupItem( int index ) + { + static_cast( decoration() )->setVisibleClientGroupItem( index ); + } + +void KCommonDecorationUnstable::moveItemInClientGroup( int index, int before ) + { + static_cast( decoration() )->moveItemInClientGroup( index, before ); + } + +void KCommonDecorationUnstable::moveItemToClientGroup( int itemId, int before ) + { + static_cast( decoration() )->moveItemToClientGroup( itemId, before ); + } + +void KCommonDecorationUnstable::removeFromClientGroup( int index, const QRect& newGeom ) + { + static_cast( decoration() )->removeFromClientGroup( index, newGeom ); + } + +void KCommonDecorationUnstable::closeClientGroupItem( int index ) + { + static_cast( decoration() )->closeClientGroupItem( index ); + } + +void KCommonDecorationUnstable::closeAllInClientGroup() + { + static_cast( decoration() )->closeAllInClientGroup(); + } + +void KCommonDecorationUnstable::displayClientMenu( int index, const QPoint& pos ) + { + static_cast( decoration() )->displayClientMenu( index, pos ); + } + +bool KCommonDecorationUnstable::eventFilter( QObject* o, QEvent* e ) + { + return KCommonDecoration::eventFilter( o, e ); + } + // kate: space-indent on; indent-width 4; mixedindent off; indent-mode cstyle; diff --git a/lib/kcommondecoration.h b/lib/kcommondecoration.h index a8209ec141..0af3385923 100644 --- a/lib/kcommondecoration.h +++ b/lib/kcommondecoration.h @@ -44,7 +44,9 @@ enum ButtonType { AboveButton, BelowButton, ShadeButton, - NumButtons + NumButtons, + ItemCloseButton=100, // Close only one tab + ItemMenuButton // shows the window menu for one tab }; class KCommonDecorationButton; @@ -378,6 +380,21 @@ class KWIN_EXPORT KCommonDecorationUnstable KCommonDecorationUnstable(KDecorationBridge* bridge, KDecorationFactory* factory); virtual ~KCommonDecorationUnstable(); bool compositingActive() const; + + // Window tabbing + bool isClientGroupActive(); + QList< ClientGroupItem > clientGroupItems() const; + int itemId( int index ); + int visibleClientGroupItem(); + void setVisibleClientGroupItem( int index ); + void moveItemInClientGroup( int index, int before ); + void moveItemToClientGroup( int itemId, int before = -1 ); + void removeFromClientGroup( int index, const QRect& newGeom = QRect() ); + void closeClientGroupItem( int index ); + void closeAllInClientGroup(); + void displayClientMenu( int index, const QPoint& pos ); + + virtual bool eventFilter( QObject* o, QEvent* e ); }; /** diff --git a/lib/kdecoration.cpp b/lib/kdecoration.cpp index ff9e0bf186..b2d53b6746 100644 --- a/lib/kdecoration.cpp +++ b/lib/kdecoration.cpp @@ -99,7 +99,7 @@ QWidget* KDecoration::initialParentWidget() const { return bridge_->initialParentWidget(); } - + Qt::WFlags KDecoration::initialWFlags() const { return bridge_->initialWFlags(); @@ -109,7 +109,7 @@ bool KDecoration::isActive() const { return bridge_->isActive(); } - + bool KDecoration::isCloseable() const { return bridge_->isCloseable(); @@ -119,12 +119,12 @@ bool KDecoration::isMaximizable() const { return bridge_->isMaximizable(); } - + KDecoration::MaximizeMode KDecoration::maximizeMode() const { return bridge_->maximizeMode(); } - + bool KDecoration::isMinimizable() const { return bridge_->isMinimizable(); @@ -134,32 +134,32 @@ bool KDecoration::providesContextHelp() const { return bridge_->providesContextHelp(); } - + int KDecoration::desktop() const { return bridge_->desktop(); } - + bool KDecoration::isModal() const { return bridge_->isModal(); } - + bool KDecoration::isShadeable() const { return bridge_->isShadeable(); } - + bool KDecoration::isShade() const { return bridge_->isShade(); } - + bool KDecoration::isSetShade() const { return bridge_->isSetShade(); } - + bool KDecoration::keepAbove() const { return bridge_->keepAbove(); @@ -189,7 +189,7 @@ QIcon KDecoration::icon() const { return bridge_->icon(); } - + QString KDecoration::caption() const { return bridge_->caption(); @@ -204,7 +204,7 @@ void KDecoration::showWindowMenu( const QRect &pos ) { bridge_->showWindowMenu( pos ); } - + void KDecoration::showWindowMenu( QPoint pos ) { bridge_->showWindowMenu( pos ); @@ -219,22 +219,22 @@ void KDecoration::setMask( const QRegion& reg, int mode ) { bridge_->setMask( reg, mode ); } - + void KDecoration::clearMask() { bridge_->setMask( QRegion(), 0 ); } - + bool KDecoration::isPreview() const { return bridge_->isPreview(); } - + QRect KDecoration::geometry() const { return bridge_->geometry(); } - + QRect KDecoration::iconGeometry() const { return bridge_->iconGeometry(); @@ -249,7 +249,7 @@ WId KDecoration::windowId() const { return bridge_->windowId(); } - + void KDecoration::closeWindow() { bridge_->closeWindow(); @@ -264,7 +264,7 @@ void KDecoration::maximize( MaximizeMode mode ) { bridge_->maximize( mode ); } - + void KDecoration::minimize() { bridge_->minimize(); @@ -274,7 +274,7 @@ void KDecoration::showContextHelp() { bridge_->showContextHelp(); } - + void KDecoration::setDesktop( int desktop ) { bridge_->setDesktop( desktop ); @@ -302,12 +302,12 @@ void KDecoration::setShade( bool set ) { bridge_->setShade( set ); } - + void KDecoration::setKeepAbove( bool set ) { bridge_->setKeepAbove( set ); } - + void KDecoration::setKeepBelow( bool set ) { bridge_->setKeepBelow( set ); @@ -341,7 +341,7 @@ void KDecoration::grabXServer() { bridge_->grabXServer( true ); } - + void KDecoration::ungrabXServer() { bridge_->grabXServer( false ); @@ -403,6 +403,69 @@ void KDecorationUnstable::padding(int &left, int &right, int &top, int &bottom) left = right = top = bottom = 0; } +// Window tabbing + +bool KDecorationUnstable::isClientGroupActive() + { + return static_cast< KDecorationBridgeUnstable* >( bridge_ )->isClientGroupActive(); + } + +QList< ClientGroupItem > KDecorationUnstable::clientGroupItems() const + { + return static_cast< KDecorationBridgeUnstable* >( bridge_ )->clientGroupItems(); + } + +int KDecorationUnstable::itemId( int index ) + { + return static_cast< KDecorationBridgeUnstable* >( bridge_ )->itemId( index ); + } + +int KDecorationUnstable::visibleClientGroupItem() + { + return static_cast< KDecorationBridgeUnstable* >( bridge_ )->visibleClientGroupItem(); + } + +void KDecorationUnstable::setVisibleClientGroupItem( int index ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->setVisibleClientGroupItem( index ); + } + +void KDecorationUnstable::moveItemInClientGroup( int index, int before ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->moveItemInClientGroup( index, before ); + } + +void KDecorationUnstable::moveItemToClientGroup( int itemId, int before ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->moveItemToClientGroup( itemId, before ); + } + +void KDecorationUnstable::removeFromClientGroup( int index, const QRect& newGeom ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->removeFromClientGroup( index, newGeom ); + } + +void KDecorationUnstable::closeClientGroupItem( int index ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->closeClientGroupItem( index ); + } + +void KDecorationUnstable::closeAllInClientGroup() + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->closeAllInClientGroup(); + } + +void KDecorationUnstable::displayClientMenu( int index, const QPoint& pos ) + { + static_cast< KDecorationBridgeUnstable* >( bridge_ )->displayClientMenu( index, pos ); + } + + +QString KDecorationDefines::clientGroupItemDragMimeType() + { + return "text/ClientGroupItem"; + } + KDecorationOptions::KDecorationOptions() : d( new KDecorationOptionsPrivate ) { @@ -495,7 +558,7 @@ bool KDecorationOptions::moveResizeMaximizedWindows() const KDecorationDefines::WindowOperation KDecorationOptions::operationMaxButtonClick( Qt::MouseButtons button ) const { - return button == Qt::RightButton? d->opMaxButtonRightClick : + return button == Qt::RightButton? d->opMaxButtonRightClick : button == Qt::MidButton? d->opMaxButtonMiddleClick : d->opMaxButtonLeftClick; } diff --git a/lib/kdecoration.h b/lib/kdecoration.h index acd543c9f7..4ee173f32f 100644 --- a/lib/kdecoration.h +++ b/lib/kdecoration.h @@ -105,7 +105,11 @@ public: NoBorderOp, NoOp, SetupWindowShortcutOp, - ApplicationRulesOp + ApplicationRulesOp, + RemoveClientFromGroupOp, // Remove from group + CloseClientGroupOp, // Close the group + MoveClientInGroupLeftOp, // Move left in the group + MoveClientInGroupRightOp // Move right in the group }; /** * Basic color types that should be recognized by all decoration styles. @@ -192,13 +196,44 @@ public: /// The mask is still used to define the input region and the blurred /// region, when the blur plugin is enabled. /// @since 4.3 + // Tabbing + AbilityClientGrouping = 4000, ///< The decoration supports tabbing // TODO colors for individual button types ABILITY_DUMMY = 10000000 }; enum Requirement { REQUIREMENT_DUMMY = 1000000 }; + + /** + * + * Returns mimeType used to drag and drop clientGroupItems + */ + + static QString clientGroupItemDragMimeType(); + }; +class KWIN_EXPORT ClientGroupItem + { + public: + ClientGroupItem( QString t, QIcon i ) + { + title_ = t; + icon_ = i; + } + inline QIcon icon() const + { + return icon_; + } + inline QString title() const + { + return title_; + } + private: + QString title_; + QIcon icon_; + }; + class KDecorationProvides : public KDecorationDefines { @@ -672,6 +707,7 @@ class KWIN_EXPORT KDecoration * isShade() to get the current state. */ virtual void shadeChange() = 0; + Q_SIGNALS: /** * This signal is emitted whenever the window's keep-above state changes. @@ -681,6 +717,7 @@ class KWIN_EXPORT KDecoration * This signal is emitted whenever the window's keep-below state changes. */ void keepBelowChanged( bool ); + public: /** * This function may be reimplemented to provide custom bound drawing @@ -848,6 +885,7 @@ class KWIN_EXPORT KDecoration * @internal */ void emitKeepBelowChanged( bool below ); + private: KDecorationBridge* bridge_; QWidget* w_; @@ -856,6 +894,7 @@ class KWIN_EXPORT KDecoration friend class KDecorationUnstable; // for bridge_ static KDecorationOptions* options_; KDecorationPrivate* d; + }; /** @@ -884,6 +923,62 @@ class KWIN_EXPORT KDecorationUnstable * Returns @a true if compositing--and therefore ARGB--is enabled. */ bool compositingActive() const; + + // Window tabbing + + /** + * Returns whether or not this client group contains the active client. + */ + bool isClientGroupActive(); + /** + * Return a list of all the clients in the group that contains the client that this + * decoration is attached to. + */ + QList< ClientGroupItem > clientGroupItems() const; + /** + * Returns a unique identifier for the client at index \p index of the client group list. + * \see moveItemToClientGroup() + */ + int itemId( int index ); + /** + * Returns the list index of the currently visible client in this group. + */ + int visibleClientGroupItem(); + /** + * Switch the currently visible client to the one at list index \p index. + */ + void setVisibleClientGroupItem( int index ); + /** + * Move the client at index \p index to the position before the client at index \p before. + */ + void moveItemInClientGroup( int index, int before ); + /** + * Move the client that's unique identifier is \p itemId to the position before the client + * at index \p before if set, otherwise the end of the list. This call is to move clients + * between two different groups, if moving in the same group then use + * moveItemInClientGroup() instead. + * \see itemId() + */ + void moveItemToClientGroup( int itemId, int before = -1 ); + /** + * Remove the client at index \p index from the group. If \p newGeom is set then the client + * will move and resize to the specified geometry, otherwise it will stay where the group + * is located. + */ + void removeFromClientGroup( int index, const QRect& newGeom = QRect() ); + /** + * Close the client at index \p index. + */ + void closeClientGroupItem( int index ); + /** + * Close all windows in this group. + */ + void closeAllInClientGroup(); + /** + * Display the right-click client menu belonging to the client at index \p index at the + * global coordinates specified by \p pos. + */ + void displayClientMenu( int index, const QPoint& pos ); }; inline diff --git a/lib/kdecorationbridge.h b/lib/kdecorationbridge.h index 89bda6e5d5..fb48f88b9d 100644 --- a/lib/kdecorationbridge.h +++ b/lib/kdecorationbridge.h @@ -94,6 +94,19 @@ class KWIN_EXPORT KDecorationBridgeUnstable { public: virtual bool compositingActive() const = 0; + + // Window tabbing + virtual bool isClientGroupActive() = 0; + virtual QList< ClientGroupItem > clientGroupItems() const = 0; + virtual int itemId( int index ) = 0; + virtual int visibleClientGroupItem() = 0; + virtual void setVisibleClientGroupItem( int index ) = 0; + virtual void moveItemInClientGroup( int index, int before ) = 0; + virtual void moveItemToClientGroup( int itemId, int before ) = 0; + virtual void removeFromClientGroup( int index, const QRect& newGeom ) = 0; + virtual void closeClientGroupItem( int index ) = 0; + virtual void closeAllInClientGroup() = 0; + virtual void displayClientMenu( int index, const QPoint& pos ) = 0; }; /** @} */ diff --git a/lib/kwineffects.cpp b/lib/kwineffects.cpp index 0afda04d3a..bfab602831 100644 --- a/lib/kwineffects.cpp +++ b/lib/kwineffects.cpp @@ -155,6 +155,18 @@ void Effect::windowUnminimized( EffectWindow* ) { } +void Effect::clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ) + { + } + +void Effect::clientGroupItemAdded( EffectWindow* from, EffectWindow* to ) + { + } + +void Effect::clientGroupItemRemoved( EffectWindow* c, EffectWindow* group ) + { + } + void Effect::windowInputMouseEvent( Window, QEvent* ) { } diff --git a/lib/kwineffects.h b/lib/kwineffects.h index 176a6c3a86..a28cb2154a 100644 --- a/lib/kwineffects.h +++ b/lib/kwineffects.h @@ -170,7 +170,7 @@ X-KDE-Library=kwin4_effect_cooleffect #define KWIN_EFFECT_API_MAKE_VERSION( major, minor ) (( major ) << 8 | ( minor )) #define KWIN_EFFECT_API_VERSION_MAJOR 0 -#define KWIN_EFFECT_API_VERSION_MINOR 106 +#define KWIN_EFFECT_API_VERSION_MINOR 107 #define KWIN_EFFECT_API_VERSION KWIN_EFFECT_API_MAKE_VERSION( \ KWIN_EFFECT_API_VERSION_MAJOR, KWIN_EFFECT_API_VERSION_MINOR ) @@ -393,6 +393,9 @@ class KWIN_EXPORT Effect virtual void windowActivated( EffectWindow* c ); virtual void windowMinimized( EffectWindow* c ); virtual void windowUnminimized( EffectWindow* c ); + virtual void clientGroupItemSwitched( EffectWindow* from, EffectWindow* to ); + virtual void clientGroupItemAdded( EffectWindow* from, EffectWindow* to ); // from merged with to + virtual void clientGroupItemRemoved( EffectWindow* c, EffectWindow* group ); // c removed from group virtual void windowInputMouseEvent( Window w, QEvent* e ); virtual void desktopChanged( int old ); virtual void windowDamaged( EffectWindow* w, const QRect& r ); @@ -956,6 +959,8 @@ class KWIN_EXPORT EffectWindow virtual void minimize() const = 0; virtual void unminimize() const = 0; virtual void closeWindow() const = 0; + + virtual bool visibleInClientGroup() const = 0; }; class KWIN_EXPORT EffectWindowGroup diff --git a/manage.cpp b/manage.cpp index 7a8f6613f5..78b23f3f83 100644 --- a/manage.cpp +++ b/manage.cpp @@ -296,6 +296,26 @@ bool Client::manage( Window w, bool isMapped ) if( placementDone ) move( geom.x(), geom.y() ); // Before gravitating + // Create client group if the window will have a decoration + if( !noBorder() ) + { + client_group = NULL; + // Automatically add to previous groups on session restore + if( session && session->clientGroupClient && session->clientGroupClient != this ) + session->clientGroupClient->clientGroup()->add( this, -1, true ); + else if( isMapped ) + // If the window is already mapped (Restarted KWin) add any windows that already have the + // same geometry to the same client group. (May incorrectly handle maximized windows) + foreach( ClientGroup* group, workspace()->clientGroups ) + if( geom == QRect( group->visible()->pos(), group->visible()->clientSize() )) + { + group->add( this, -1, true ); + break; + } + if( !client_group ) + client_group = new ClientGroup( this ); + } + updateDecoration( false ); // Also gravitates // TODO: Is CentralGravity right here, when resizing is done after gravitating? plainResize( rules()->checkSize( sizeForClientSize( geom.size() ), !isMapped )); diff --git a/sm.cpp b/sm.cpp index 14b52351fd..c28a7a0282 100644 --- a/sm.cpp +++ b/sm.cpp @@ -127,6 +127,10 @@ void Workspace::storeSession( KConfig* config, SMSavePhase phase ) cg.writeEntry( QString("windowType")+n, windowTypeToTxt( c->windowType())); cg.writeEntry( QString("shortcut")+n, c->shortcut().toString()); cg.writeEntry( QString("stackingOrder")+n, unconstrained_stacking_order.indexOf( c )); + int group = 0; + if( c->clientGroup() ) + group = c->clientGroup()->clients().count() > 1 ? (int) c->clientGroup() : 0; + cg.writeEntry( QString("clientGroup")+n, group ); } } if( phase == SMSavePhase0 ) @@ -193,6 +197,8 @@ void Workspace::loadSessionInfo() info->shortcut = cg.readEntry( QString("shortcut")+n, QString() ); info->active = ( active_client == i ); info->stackingOrder = cg.readEntry( QString("stackingOrder")+n, -1 ); + info->clientGroup = cg.readEntry( QString("clientGroup")+n, 0 ); + info->clientGroupClient = NULL; } } @@ -266,6 +272,13 @@ SessionInfo* Workspace::takeSessionInfo( Client* c ) } } } + + // Set clientGroupClient for other clients in the same group + if( realInfo && realInfo->clientGroup ) + foreach( SessionInfo* info, session ) + if( !info->clientGroupClient && info->clientGroup == realInfo->clientGroup ) + info->clientGroupClient = c; + return realInfo; } diff --git a/sm.h b/sm.h index 761711af29..cfa29d21d3 100644 --- a/sm.h +++ b/sm.h @@ -34,6 +34,8 @@ class QSocketNotifier; namespace KWin { +class Client; + struct SessionInfo { QByteArray sessionId; @@ -62,6 +64,9 @@ struct SessionInfo bool active; // means 'was active in the saved session' int stackingOrder; float opacity; + int clientGroup; // Unique identifier for the client group that this window is in + + Client* clientGroupClient; // The first client created that has an identical identifier }; diff --git a/tabbox.cpp b/tabbox.cpp index bd6b25046c..0c7b8d20fb 100644 --- a/tabbox.cpp +++ b/tabbox.cpp @@ -859,6 +859,15 @@ void Workspace::slotWalkBackThroughWindowsKeyChanged( const QKeySequence& seq ) cutWalkThroughWindowsReverse = KShortcut( seq ); } +void Workspace::slotMoveToTabLeftKeyChanged( const QKeySequence& seq ) + { + cutWalkThroughGroupWindows = KShortcut( seq ); + } +void Workspace::slotMoveToTabRightKeyChanged( const QKeySequence& seq ) + { + cutWalkThroughGroupWindowsReverse = KShortcut( seq ); + } + void Workspace::slotWalkThroughWindowsAlternativeKeyChanged( const QKeySequence& seq ) { cutWalkThroughWindowsAlternative = KShortcut( seq ); diff --git a/useractions.cpp b/useractions.cpp index 6d3c5324d7..c1e609736b 100644 --- a/useractions.cpp +++ b/useractions.cpp @@ -167,6 +167,27 @@ QMenu* Workspace::clientPopup() mShadeOpAction->setCheckable( true ); mShadeOpAction->setData( Options::ShadeOp ); + popup->addSeparator(); + + // Actions for window tabbing + if( decorationSupportsClientGrouping() ) + { + mRemoveTabGroup = popup->addAction( i18n("Remove &from group") ); + kaction = qobject_cast( keys->action("Remove TabGroup") ); + if( kaction!=0 ) + mRemoveTabGroup->setShortcut( kaction->globalShortcut().primary() ); + mRemoveTabGroup->setData( Options::RemoveClientFromGroupOp ); + + mCloseGroup = popup->addAction( i18n("Close entire &group") ); + mCloseGroup->setIcon( KIcon( "window-close" ) ); + kaction = qobject_cast( keys->action("Close TabGroup") ); + if( kaction!=0 ) + mCloseGroup->setShortcut( kaction->globalShortcut().primary() ); + mCloseGroup->setData( Options::CloseClientGroupOp ); + + popup->addSeparator(); + } + action = popup->addMenu( advanced_popup ); action->setText( i18n("Ad&vanced") ); @@ -196,6 +217,8 @@ void Workspace::discardPopup() delete popup; popup = NULL; desk_popup = NULL; + switch_to_tab_popup = NULL; + add_tabs_popup = NULL; } void Workspace::setPopupClientOpacity( QAction* action ) @@ -240,6 +263,22 @@ void Workspace::clientPopupAboutToShow() mNoBorderOpAction->setChecked( active_popup_client->noBorder() ); mMinimizeOpAction->setEnabled( active_popup_client->isMinimizable() ); mCloseOpAction->setEnabled( active_popup_client->isCloseable() ); + + delete switch_to_tab_popup; + switch_to_tab_popup = 0; + delete add_tabs_popup; + add_tabs_popup = 0; + if( decorationSupportsClientGrouping() ) + { + const int tabGroupSize = active_popup_client->clientGroup()->items().count(); + if( tabGroupSize > 1 ) + initSwitchToTab(); + initAddToTabGroup(); + + mRemoveTabGroup->setVisible( tabGroupSize > 1 ); + mCloseGroup->setVisible( tabGroupSize > 1 ); + } + if( trans_popup != NULL ) { foreach( QAction* action, trans_popup->actions()) @@ -252,6 +291,118 @@ void Workspace::clientPopupAboutToShow() } } +void Workspace::initSwitchToTab() + { + if( switch_to_tab_popup ) + return; + switch_to_tab_popup = new QMenu( popup ); + switch_to_tab_popup->setFont( KGlobalSettings::menuFont() ); + connect( switch_to_tab_popup, SIGNAL( triggered( QAction* ) ), + this, SLOT( slotSwitchToTab( QAction* ) ) ); + connect( switch_to_tab_popup, SIGNAL( aboutToShow() ), + this, SLOT( switchToTabPopupAboutToShow() ) ); + + QAction* action = switch_to_tab_popup->menuAction(); + popup->insertAction( mRemoveTabGroup, action ); + action->setText( i18n("Switch to group window") ); + } + +void Workspace::slotSwitchToTab( QAction* action ) + { + int side = action->data().toInt(); + int c_id = active_popup_client->clientGroup()->indexOfClient( active_popup_client ); + int size = active_popup_client->clientGroup()->clients().count(); + if( side == 0 ) // Left + { + if( c_id > 0 ) + active_popup_client->clientGroup()->setVisible( c_id - 1 ); + else + active_popup_client->clientGroup()->setVisible( size - 1 ); + } + else if( side == 1 ) // Right + { + if( c_id < size - 1 ) + active_popup_client->clientGroup()->setVisible( c_id + 1 ); + else + active_popup_client->clientGroup()->setVisible( 0 ); + } + else // Find the client + { + side -= 2; + for( QList::const_iterator i = clientGroups.begin(); i != clientGroups.end(); ++i ) + { + if( (*i)->contains( active_popup_client )) + { + (*i)->setVisible( side ); + break; + } + } + } + } + +void Workspace::switchToTabPopupAboutToShow() + { + if( !switch_to_tab_popup ) + return; + switch_to_tab_popup->clear(); + QAction* action = switch_to_tab_popup->addAction( i18n( "To the left" )); + action->setData( 0 ); + action = switch_to_tab_popup->addAction( i18n( "To the right" )); + action->setData( 1 ); + switch_to_tab_popup->addSeparator(); + int index = 2; + foreach( Client* c, active_popup_client->clientGroup()->clients() ) + { + if( c != active_popup_client ) + { + action = switch_to_tab_popup->addAction( c->caption() ); + action->setData( index ); + } + index++; + } + + } + +void Workspace::initAddToTabGroup() + { + if( add_tabs_popup ) + return; + add_tabs_popup = new QMenu( popup ); + add_tabs_popup->setFont( KGlobalSettings::menuFont() ); + connect( add_tabs_popup, SIGNAL( triggered( QAction* ) ), + this, SLOT( slotAddToTabGroup( QAction* ) ) ); // Merge to a group + connect( add_tabs_popup, SIGNAL( aboutToShow() ), + this, SLOT( groupTabPopupAboutToShow() ) ); // Show the possible groups to add + + QAction* action = add_tabs_popup->menuAction(); + popup->insertAction( mRemoveTabGroup, action ); + action->setText( i18n("Move window to group") ); + } + +void Workspace::slotAddToTabGroup( QAction* action ) + { + if( !action->data().isValid() ) + return; + moveItemToClientGroup( active_popup_client->clientGroup(), + active_popup_client->clientGroup()->indexOfClient( active_popup_client ), + clientGroups[action->data().toInt()], -1 ); + } + +void Workspace::groupTabPopupAboutToShow() + { + if( !add_tabs_popup ) + return; + add_tabs_popup->clear(); + int index = 0; + for( QList::const_iterator i = clientGroups.begin(); i != clientGroups.end(); i++, index++ ) + { + if( !(*i)->contains( active_popup_client )) + { + QAction* action = add_tabs_popup->addAction( (*i)->visible()->caption() ); + action->setData( index ); + } + } + } void Workspace::initDesktopPopup() { @@ -382,6 +533,22 @@ void Workspace::readShortcuts() connect( kaction, SIGNAL(globalShortcutChanged(QKeySequence)), this, SLOT(slotWalkBackThroughWindowsKeyChanged(QKeySequence))); } + kaction = qobject_cast( keys->action("Walk Through Group Windows") ); + if( kaction != 0 ) + { + cutWalkThroughGroupWindows = kaction->globalShortcut(); + connect( kaction, SIGNAL( globalShortcutChanged( QKeySequence ) ), this, + SLOT( slotMoveToTabRightKeyChanged( QKeySequence ) ) ); + } + + kaction = qobject_cast( keys->action("Walk Through Group Windows (Reverse)") ); + if( kaction != 0 ) + { + cutWalkThroughGroupWindowsReverse = kaction->globalShortcut(); + connect( kaction, SIGNAL( globalShortcutChanged( QKeySequence ) ), this, + SLOT( slotMoveToTabLeftKeyChanged( QKeySequence ) ) ); + } + kaction = qobject_cast( keys->action("Walk Through Windows Alternative") ); if ( kaction!=0 ) { @@ -582,6 +749,31 @@ void Workspace::performWindowOperation( Client* c, Options::WindowOperation op ) break; case Options::NoOp: break; + case Options::RemoveClientFromGroupOp: + c->clientGroup()->remove( c ); + break; + case Options::MoveClientInGroupLeftOp: + { + int c_id = c->clientGroup()->indexOfClient( c ); + int size = c->clientGroup()->clients().count(); + if( c_id > 0 ) + c->clientGroup()->setVisible( c_id - 1 ); + else + c->clientGroup()->setVisible( size - 1 ); + break; + } + case Options::MoveClientInGroupRightOp: + { + int c_id = c->clientGroup()->indexOfClient( c ); + int size = c->clientGroup()->clients().count(); + if( c_id < size - 1 ) + c->clientGroup()->setVisible( c_id + 1 ); + else + c->clientGroup()->setVisible( 0 ); + break; + } + case Options::CloseClientGroupOp: + c->clientGroup()->closeAll(); } } @@ -1154,6 +1346,36 @@ void Workspace::slotWindowToDesktopDown() } } +void Workspace::slotSwitchToTabRight() + { + if( !active_client ) + return; + int c_id = active_client->clientGroup()->indexOfClient( active_client ); + int size = active_client->clientGroup()->clients().count(); + if( c_id < size - 1 ) + active_client->clientGroup()->setVisible( c_id + 1 ); + else + active_client->clientGroup()->setVisible( 0 ); + } + +void Workspace::slotSwitchToTabLeft() + { + if( !active_client ) + return; + int c_id = active_client->clientGroup()->indexOfClient( active_client ); + int size = active_client->clientGroup()->clients().count(); + if( c_id > 0 ) + active_client->clientGroup()->setVisible( c_id - 1 ); + else + active_client->clientGroup()->setVisible( size - 1 ); + } + +void Workspace::slotRemoveFromGroup() + { + if( !active_client ) + return; + active_client->clientGroup()->remove( active_client ); + } /*! Kill Window feature, similar to xkill diff --git a/workspace.cpp b/workspace.cpp index e191a44776..ed6208f83a 100644 --- a/workspace.cpp +++ b/workspace.cpp @@ -122,6 +122,8 @@ Workspace::Workspace( bool restore ) , advanced_popup( 0 ) , trans_popup( 0 ) , desk_popup( 0 ) + , add_tabs_popup( 0 ) + , switch_to_tab_popup( 0 ) , keys( 0 ) , client_keys( NULL ) , client_keys_dialog( NULL ) @@ -1070,6 +1072,13 @@ void Workspace::slotReconfigure() it != clients.constEnd(); ++it ) (*it)->updateDecoration( true, true ); + // If the new decoration doesn't supports tabs then ungroup clients + if( !decorationSupportsClientGrouping() ) + { + QList tmpGroups = clientGroups; // Prevent crashing + for( QList::const_iterator i = tmpGroups.begin(); i != tmpGroups.end(); i++ ) + (*i)->removeAll(); + } mgr->destroyPreviousPlugin(); } else @@ -2855,6 +2864,19 @@ void Workspace::checkCursorPos() x11ToQtKeyboardModifiers( last_buttons ), x11ToQtKeyboardModifiers( lastb )); } +int Workspace::indexOfClientGroup( ClientGroup* group ) + { + return clientGroups.indexOf( group ); + } + +void Workspace::moveItemToClientGroup( ClientGroup* oldGroup, int oldIndex, + ClientGroup* group, int index ) + { + Client* c = oldGroup->clients().at( oldIndex ); + oldGroup->remove( c ); + group->add( c, index, true ); + } + } // namespace #include "workspace.moc" diff --git a/workspace.h b/workspace.h index 448bb050b3..c411ed901a 100644 --- a/workspace.h +++ b/workspace.h @@ -60,6 +60,7 @@ class TabBox; } class Client; +class ClientGroup; class DesktopChangeOSD; class RootInfo; class PluginMgr; @@ -314,6 +315,15 @@ class Workspace : public QObject, public KDecorationDefines void unrefTabBox(); void closeTabBox(); + // Tabbing + void addClientGroup( ClientGroup* group ); + void removeClientGroup( ClientGroup* group ); + /// Returns the index of c in clientGroupList. + int indexOfClientGroup( ClientGroup* group ); + /// Change the client c_id to the group with index g_id + void moveItemToClientGroup( ClientGroup* oldGroup, int oldIndex, ClientGroup* group, int index = -1 ); + QList clientGroups; // List of existing clients groups with no special order + /** * Returns the list of clients sorted in stacking order, with topmost client * at the last position @@ -371,6 +381,7 @@ class Workspace : public QObject, public KDecorationDefines bool hasDecorationShadows() const; bool decorationHasAlpha() const; + bool decorationSupportsClientGrouping() const; // Returns true if the decoration supports tabs. // D-Bus interface void cascadeDesktop(); @@ -588,6 +599,8 @@ class Workspace : public QObject, public KDecorationDefines void slotWalkBackThroughDesktopListKeyChanged( const QKeySequence& seq ); void slotWalkThroughWindowsKeyChanged( const QKeySequence& seq ); void slotWalkBackThroughWindowsKeyChanged( const QKeySequence& seq ); + void slotMoveToTabLeftKeyChanged( const QKeySequence& seq ); + void slotMoveToTabRightKeyChanged( const QKeySequence& seq ); void slotWalkThroughWindowsAlternativeKeyChanged( const QKeySequence& seq ); void slotWalkBackThroughWindowsAlternativeKeyChanged( const QKeySequence& seq ); @@ -636,7 +649,15 @@ class Workspace : public QObject, public KDecorationDefines void suspendCompositing(); void suspendCompositing( bool suspend ); + void slotSwitchToTabLeft(); // Slot to move left the active Client. + void slotSwitchToTabRight(); // Slot to move right the active Client. + void slotRemoveFromGroup(); // Slot to remove the active client from its group. + private slots: + void groupTabPopupAboutToShow(); // Popup to add to another group + void switchToTabPopupAboutToShow(); // Popup to move in the group + void slotAddToTabGroup( QAction* ); // Add client to a group + void slotSwitchToTab( QAction* ); // Change the tab void desktopPopupAboutToShow(); void clientPopupAboutToShow(); void slotSendToDesktop( QAction* ); @@ -820,6 +841,7 @@ class Workspace : public QObject, public KDecorationDefines KShortcut cutWalkThroughDesktops, cutWalkThroughDesktopsReverse; KShortcut cutWalkThroughDesktopList, cutWalkThroughDesktopListReverse; KShortcut cutWalkThroughWindows, cutWalkThroughWindowsReverse; + KShortcut cutWalkThroughGroupWindows, cutWalkThroughGroupWindowsReverse; KShortcut cutWalkThroughWindowsAlternative, cutWalkThroughWindowsAlternativeReverse; bool mouse_emulation; unsigned int mouse_emulation_state; @@ -833,6 +855,8 @@ class Workspace : public QObject, public KDecorationDefines QMenu* advanced_popup; QMenu* trans_popup; QMenu* desk_popup; + QMenu* add_tabs_popup; // Menu to add the group to other group + QMenu* switch_to_tab_popup; // Menu to change tab void modalActionsSwitch( bool enabled ); @@ -848,12 +872,17 @@ class Workspace : public QObject, public KDecorationDefines QAction* mNoBorderOpAction; QAction* mMinimizeOpAction; QAction* mCloseOpAction; + QAction* mRemoveTabGroup; // Remove client from group + QAction* mCloseGroup; // Close all clients in the group ShortcutDialog* client_keys_dialog; Client* client_keys_client; KActionCollection* disable_shortcuts_keys; bool global_shortcuts_disabled; bool global_shortcuts_disabled_for_client; + void initAddToTabGroup(); // Load options for menu add_tabs_popup + void initSwitchToTab(); // Load options for menu switch_to_tab_popup + PluginMgr* mgr; RootInfo* rootInfo; @@ -1222,6 +1251,21 @@ inline bool Workspace::decorationHasAlpha() const return mgr->factory()->supports( AbilityUsesAlphaChannel ); } +inline bool Workspace::decorationSupportsClientGrouping() const + { + return mgr->factory()->supports( AbilityClientGrouping ); + } + +inline void Workspace::addClientGroup( ClientGroup* group ) + { + clientGroups.append( group ); + } + +inline void Workspace::removeClientGroup( ClientGroup* group ) + { + clientGroups.removeAll( group ); + } + } // namespace #endif