diff options
Diffstat (limited to 'kicker/taskbar/taskcontainer.cpp')
-rw-r--r-- | kicker/taskbar/taskcontainer.cpp | 1643 |
1 files changed, 1643 insertions, 0 deletions
diff --git a/kicker/taskbar/taskcontainer.cpp b/kicker/taskbar/taskcontainer.cpp new file mode 100644 index 000000000..7025d643a --- /dev/null +++ b/kicker/taskbar/taskcontainer.cpp @@ -0,0 +1,1643 @@ +/***************************************************************** + +Copyright (c) 2001 Matthias Elter <elter@kde.org> +Copyright (c) 2002 John Firebaugh <jfirebaugh@kde.org> +Copyright (c) 2005 Aaron Seigo <aseigo@kde.org> + +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 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 <assert.h> + +#include <qbitmap.h> +#include <qcolor.h> +#include <qcursor.h> +#include <qimage.h> +#include <qpainter.h> +#include <qpixmap.h> +#include <qstyle.h> +#include <qstylesheet.h> +#include <qtooltip.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <klocale.h> +#include <kiconeffect.h> +#include <kiconloader.h> +#include <kimageeffect.h> + +#include "global.h" +#include "kickerSettings.h" +#include "paneldrag.h" +#include "taskbar.h" +#include "taskbarsettings.h" +#include "tasklmbmenu.h" +#include "taskrmbmenu.h" + +#include "taskcontainer.h" +#include "taskcontainer.moc" + +TaskContainer::TaskContainer(Task::Ptr task, TaskBar* bar, + QWidget *parent, const char *name) + : QToolButton(parent, name), + currentFrame(0), + attentionState(-1), + lastActivated(0), + m_menu(0), + m_startup(0), + arrowType(Qt::UpArrow), + taskBar(bar), + discardNextMouseEvent(false), + aboutToActivate(false), + m_mouseOver(false), + m_paintEventCompression(false) +{ + init(); + setAcceptDrops(true); // Always enabled to activate task during drag&drop. + + add(task); + + // we abuse this timer once to get shown + // no point in having another timer just for this, and + // a single shot won't do because we need to stop the timer + // in case our task is deleted out from under us + dragSwitchTimer.start(0, true); +} + +TaskContainer::TaskContainer(Startup::Ptr startup, PixmapList& startupFrames, + TaskBar* bar, QWidget *parent, const char *name) + : QToolButton(parent, name), + currentFrame(0), + frames(startupFrames), + attentionState(-1), + lastActivated(0), + m_menu(0), + m_startup(startup), + arrowType(Qt::LeftArrow), + taskBar(bar), + discardNextMouseEvent(false), + aboutToActivate(false), + m_mouseOver(false), + m_paintEventCompression(false) +{ + init(); + setEnabled(false); + + sid = m_startup->bin(); + + connect(m_startup, SIGNAL(changed()), SLOT(update())); + + dragSwitchTimer.start(333, true); +} + +void TaskContainer::init() +{ + setWFlags(WNoAutoErase); + setBackgroundMode(NoBackground); + animBg = QPixmap(16, 16); + + installEventFilter(KickerTip::the()); + + connect(&animationTimer, SIGNAL(timeout()), SLOT(animationTimerFired())); + connect(&dragSwitchTimer, SIGNAL(timeout()), SLOT(showMe())); + connect(&attentionTimer, SIGNAL(timeout()), SLOT(attentionTimerFired())); + connect(&m_paintEventCompressionTimer, SIGNAL(timeout()), SLOT(updateNow())); +} + +TaskContainer::~TaskContainer() +{ + if (m_menu) + { + delete m_menu; + m_menu = 0; + } + + stopTimers(); +} + +void TaskContainer::showMe() +{ + if(!frames.isEmpty() && taskBar->showIcon()) + animationTimer.start(100); + + emit showMe(this); + disconnect(&dragSwitchTimer, SIGNAL(timeout()), this, SLOT(showMe())); + connect(&dragSwitchTimer, SIGNAL(timeout()), SLOT(dragSwitch())); +} + +void TaskContainer::stopTimers() +{ + animationTimer.stop(); + dragSwitchTimer.stop(); + attentionTimer.stop(); +} + +void TaskContainer::taskChanged(bool geometryOnlyChange) +{ + if (geometryOnlyChange) + { + // we really don't care about those changes, which we may be getting + // thanks to the pager, for instance, turning it on in taskmanager. + // // let's ignore them so we don't end up with tons of processing going on + return; + } + + const QObject* source = sender(); + Task::Ptr task = 0; + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + if (*it == source) + { + task = *it; + break; + } + } + + if (task) + { + checkAttention(task); + } + + KickerTip::Client::updateKickerTip(); + update(); +} + +void TaskContainer::iconChanged() +{ + const QObject* source = sender(); + Task::Ptr task = 0; + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + if (*it == source) + { + task = *it; + break; + } + } + + if (task && !m_filteredTasks.empty() && task != m_filteredTasks.first()) + { + if (m_menu) + { + m_menu->update(); + } + return; + } + + KickerTip::Client::updateKickerTip(); + QToolButton::update(); +} + +void TaskContainer::setLastActivated() +{ + Task::List::const_iterator itEnd = m_filteredTasks.constEnd(); + for (Task::List::const_iterator it = m_filteredTasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + if ( t->isActive() ) + { + lastActivated = t; + return; + } + } + lastActivated = 0L; +} + + +void TaskContainer::animationTimerFired() +{ + if (!frames.isEmpty() && taskBar->showIcon() && frames.at(currentFrame) != frames.end()) + { + QPixmap *pm = *frames.at(currentFrame); + + // draw pixmap + if ( pm && !pm->isNull() ) { + // we only have to redraw the background for frames 0, 8 and 9 + if ( currentFrame == 0 || currentFrame > 7 ) { + // double buffered painting + QPixmap composite( animBg ); + bitBlt( &composite, 0, 0, pm ); + bitBlt( this, iconRect.x(), iconRect.y(), &composite ); + } + else + bitBlt( this, iconRect.x(), iconRect.y(), pm ); + } + + // increment frame counter + if ( currentFrame >= 9) + currentFrame = 0; + else + currentFrame++; + } +} + +void TaskContainer::checkAttention(const Task::Ptr t) +{ + bool attention = t ? t->demandsAttention() : false; + if (attention && attentionState == -1) // was activated + { + attentionState = 0; + attentionTimer.start(500); + } + else if(!attention && attentionState >= 0) + { // need to check all + Task::List::iterator itEnd = tasks.end(); + for (Task::List::iterator it = tasks.begin(); it != itEnd; ++it) + { + if ((*it)->demandsAttention()) + { + attention = true; + break; + } + } + + if (!attention) + { + attentionTimer.stop(); + attentionState = -1; + } + } +} + +void TaskContainer::attentionTimerFired() +{ + assert( attentionState != -1 ); + if (attentionState < TaskBarSettings::attentionBlinkIterations()*2) + { + ++attentionState; + } + else if (TaskBarSettings::attentionBlinkIterations() < 1000) + { + attentionTimer.stop(); + } + else + { + // we have a "forever" blinker (attentionBlinkIterations > 999) and have reached + // the upper limit. so we need to decrement the attentionState to make it blink + --attentionState; + } + update(); +} + +QSizePolicy TaskContainer::sizePolicy() const +{ + return QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ); +} + +void TaskContainer::resizeEvent( QResizeEvent * ) +{ + // calculate the icon rect + QRect br( style().subRect( QStyle::SR_PushButtonContents, this ) ); + iconRect = QStyle::visualRect( QRect(br.x() + 2, (height() - 16) / 2, 16, 16), this ); +} + +void TaskContainer::add(Task::Ptr task) +{ + if (!task) + { + return; + } + + tasks.append(task); + + if (sid.isEmpty()) + { + sid = task->classClass(); + } + + updateFilteredTaskList(); + checkAttention(task); + + KickerTip::Client::updateKickerTip(); + update(); + + connect(task, SIGNAL(changed(bool)), SLOT(taskChanged(bool))); + connect(task, SIGNAL(iconChanged()), SLOT(iconChanged())); + connect(task, SIGNAL(activated()), SLOT(setLastActivated())); +} + +void TaskContainer::remove(Task::Ptr task) +{ + if (!task) + { + return; + } + + task->publishIconGeometry(QRect()); + for (Task::List::Iterator it = tasks.begin(); it != tasks.end(); ++it) + { + if ((*it) == task) + { + tasks.erase(it); + break; + } + } + + updateFilteredTaskList(); + + if (isEmpty()) + { + stopTimers(); + return; + } + + checkAttention(); + KickerTip::Client::updateKickerTip(); + update(); +} + +void TaskContainer::remove(Startup::Ptr startup) +{ + if (!startup || startup != m_startup) + { + return; + } + + m_startup = 0; + animationTimer.stop(); + frames.clear(); + + if (!tasks.isEmpty()) + { + setEnabled(true); + } +} + +bool TaskContainer::contains(Task::Ptr task) +{ + if (!task) + { + return false; + } + + for (Task::List::Iterator it = tasks.begin(); it != tasks.end(); ++it) + { + if ((*it) == task) + { + return true; + } + } + + return false; +} + +bool TaskContainer::contains(Startup::Ptr startup) +{ + return startup && (m_startup == startup); +} + +bool TaskContainer::contains(WId win) +{ + Task::List::iterator itEnd = tasks.end(); + for (Task::List::iterator it = tasks.begin(); it != itEnd; ++it) + { + if ((*it)->window() == win) + { + return true; + } + } + + return false; +} + +bool TaskContainer::isEmpty() +{ + return (tasks.isEmpty() && !m_startup); +} + +QString TaskContainer::id() +{ + return sid; +} + +void TaskContainer::updateNow() +{ + m_paintEventCompression = true; + update(); +} + +void TaskContainer::setBackground() +{ + updateNow(); +} + +void TaskContainer::paintEvent( QPaintEvent* ) +{ + if (!m_paintEventCompression) + { + if (!m_paintEventCompressionTimer.isActive()) + { + m_paintEventCompressionTimer.start(30, true); + } + return; + } + + m_paintEventCompression = false; + QPixmap* pm = new QPixmap(size()); + + const QPixmap* background = taskBar->backgroundPixmap(); + + if (background) + { + QPoint pt = mapTo(taskBar, QPoint(0, 0)) + taskBar->backgroundOffset(); + QPainter p(pm); + p.drawTiledPixmap(0, 0, width(), height(), *background, pt.x(), pt.y()); + p.end(); + } + else + { + pm->fill(taskBar->paletteBackgroundColor()); + } + + QPainter p; + p.begin(pm ,this); + drawButton(&p); + p.end(); + + bitBlt(this, 0, 0, pm); + delete pm; +} + +void TaskContainer::drawButton(QPainter *p) +{ + if (isEmpty()) + { + return; + } + + // get a pointer to the pixmap we're drawing on + QPixmap *pm((QPixmap*)p->device()); + QPixmap pixmap; // icon + Task::Ptr task = 0; + bool iconified = !TaskBarSettings::showOnlyIconified(); + bool halo = TaskBarSettings::haloText(); + bool alwaysDrawButtons = TaskBarSettings::drawButtons(); + bool drawButton = alwaysDrawButtons || + (m_mouseOver && !halo && isEnabled() && + TaskBarSettings::showButtonOnHover()); + QFont font(KGlobalSettings::taskbarFont()); + + // draw sunken if we contain the active task + bool active = false; + bool demandsAttention = false; + Task::List::iterator itEnd = m_filteredTasks.end(); + for (Task::List::iterator it = m_filteredTasks.begin(); it != itEnd; ++it) + { + task = *it; + if (iconified && !task->isIconified()) + { + iconified = false; + } + + if (task->isActive()) + { + active = true; + } + + if (task->demandsAttention()) + { + demandsAttention = attentionState == TaskBarSettings::attentionBlinkIterations() || + attentionState % 2 == 0; + } + } + + font.setBold(active); + + QColorGroup colors = palette().active(); + + if (TaskBarSettings::useCustomColors()) + { + colors.setColor( QColorGroup::Button, TaskBarSettings::taskBackgroundColor()); + colors.setColor( QColorGroup::Background, TaskBarSettings::taskBackgroundColor() ); + colors.setColor( QColorGroup::ButtonText, TaskBarSettings::inactiveTaskTextColor() ); + colors.setColor( QColorGroup::Text, TaskBarSettings::inactiveTaskTextColor() ); + } + + if (demandsAttention) + { + if (!drawButton) + { + halo = true; + + QRect r = rect(); + QColor line = colors.highlight(); + r.addCoords(2, 2, -2, -2); + p->fillRect(r, line); + for (int i = 0; i < 2; ++i) + { + line = KickerLib::blendColors(line, colors.background()); + p->setPen(QPen(line, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); + r.addCoords(-1, -1, 1, 1); + p->drawRect(r); + } + } + + // blink until blink timeout, then display differently without blinking + colors.setColor( QColorGroup::Button, colors.highlight() ); + colors.setColor( QColorGroup::Background, colors.highlight() ); + colors.setColor( QColorGroup::ButtonText, colors.highlightedText() ); + colors.setColor( QColorGroup::Text, colors.highlightedText() ); + } + + if (active || aboutToActivate) + { + colors.setColor(QColorGroup::Button, colors.button().dark(110)); + } + + // get the task icon + if (task) + { + pixmap = task->pixmap(); + } + + bool sunken = isDown() || (alwaysDrawButtons && (active || aboutToActivate)); + bool reverse = QApplication::reverseLayout(); + QRect br(style().subRect(QStyle::SR_PushButtonContents, this)); + QPoint shift = QPoint(style().pixelMetric(QStyle::PM_ButtonShiftHorizontal), + style().pixelMetric(QStyle::PM_ButtonShiftVertical)); + + // draw button background + if (drawButton) + { + style().drawPrimitive(QStyle::PE_HeaderSection, p, + QRect(0, 0, width(), height()), + colors); + } + + // shift button label on sunken buttons + if (sunken) + { + p->translate(shift.x(), shift.y()); + } + + if (taskBar->showIcon()) + { + if (pixmap.isNull() && m_startup) + { + pixmap = SmallIcon(m_startup->icon()); + } + + if ( !pixmap.isNull() ) + { + // make sure it is no larger than 16x16 + if ( pixmap.width() > 16 || pixmap.height() > 16 ) + { + QImage tmp = pixmap.convertToImage(); + pixmap.convertFromImage( tmp.smoothScale( 16, 16 ) ); + } + + // fade out the icon when minimized + if (iconified) + { + KIconEffect::semiTransparent( pixmap ); + } + + // draw icon + QRect pmr(0, 0, pixmap.width(), pixmap.height()); + pmr.moveCenter(iconRect.center()); + p->drawPixmap(pmr, pixmap); + } + } + + // find text + QString text = name(); + + // modified overlay + static QString modStr = "[" + i18n( "modified" ) + "]"; + int modStrPos = text.find( modStr ); + int textPos = ( taskBar->showIcon() && (!pixmap.isNull() || m_startup)) ? 2 + 16 + 2 : 0; + + if (modStrPos >= 0) + { + // +1 because we include a space after the closing brace. + text.remove(modStrPos, modStr.length() + 1); + QPixmap modPixmap = SmallIcon("modified"); + + // draw modified overlay + if (!modPixmap.isNull()) + { + QRect r = QStyle::visualRect(QRect(br.x() + textPos, + (height() - 16) / 2, 16, 16), + this); + + if (iconified) + { + KIconEffect::semiTransparent(modPixmap); + } + + p->drawPixmap(r, modPixmap); + textPos += 16 + 2; + } + } + + // draw text + if (!text.isEmpty()) + { + QRect tr = QStyle::visualRect(QRect(br.x() + textPos + 1, 0, + width() - textPos, height()), + this); + int textFlags = AlignVCenter | SingleLine; + textFlags |= reverse ? AlignRight : AlignLeft; + QPen textPen; + + // get the color for the text label + if (iconified) + { + textPen = QPen(KickerLib::blendColors(colors.button(), colors.buttonText())); + } + else if (!active) + { + textPen = QPen(colors.buttonText()); + } + else // hack for the dotNET style and others + { + if (TaskBarSettings::useCustomColors()) + { + textPen = QPen(TaskBarSettings::activeTaskTextColor()); + } + else + { + textPen = p->pen(); + } + } + + int availableWidth = width() - (br.x() * 2) - textPos; + if (m_filteredTasks.count() > 1) + { + availableWidth -= 8; + } + + if (QFontMetrics(font).width(text) > availableWidth) + { + // draw text into overlay pixmap + QPixmap tpm(*pm); + QPainter tp(&tpm); + + if (sunken) + { + tp.translate(shift.x(), shift.y()); + } + + tp.setFont(font); + tp.setPen(textPen); + + if (halo) + { + taskBar->textShadowEngine()->drawText(tp, tr, textFlags, text, size()); + } + else + { + tp.drawText(tr, textFlags, text); + } + + // blend text into background image + QImage img = pm->convertToImage(); + QImage timg = tpm.convertToImage(); + KImageEffect::blend(img, timg, *taskBar->blendGradient(size()), KImageEffect::Red); + pm->convertFromImage(img); + } + else + { + p->setFont(font); + p->setPen(textPen); + + if (halo) + { + taskBar->textShadowEngine()->drawText(*p, tr, textFlags, text, size()); + } + else + { + p->drawText(tr, textFlags, text); + } + } + } + + if (!frames.isEmpty() && m_startup && frames.at(currentFrame) != frames.end()) + { + QPixmap *anim = *frames.at(currentFrame); + + if (anim && !anim->isNull()) + { + // save the background for the other frames + bitBlt(&animBg, QPoint(0,0), pm, iconRect); + // draw the animation frame + bitBlt(pm, iconRect.x(), iconRect.y(), anim); + } + } + + if (sunken) + { + // Change the painter back so the arrow, etc gets drawn in the right location + p->translate(-shift.x(), -shift.y()); + } + + // draw popup arrow + if (m_filteredTasks.count() > 1) + { + QStyle::PrimitiveElement e = QStyle::PE_ArrowLeft; + + switch (arrowType) + { + case Qt::LeftArrow: e = QStyle::PE_ArrowLeft; break; + case Qt::RightArrow: e = QStyle::PE_ArrowRight; break; + case Qt::UpArrow: e = QStyle::PE_ArrowUp; break; + case Qt::DownArrow: e = QStyle::PE_ArrowDown; break; + } + + int flags = QStyle::Style_Enabled; + QRect ar = QStyle::visualRect(QRect(br.x() + br.width() - 8 - 2, + br.y(), 8, br.height()), this); + if (sunken) + { + flags |= QStyle::Style_Down; + } + + style().drawPrimitive(e, p, ar, colors, flags); + } + + // draw mouse over frame in transparent mode + if (m_mouseOver && halo) + KickerLib::drawBlendedRect(p, QRect(0, 0, width(), height()), colorGroup().foreground()); + + if (aboutToActivate) + { + aboutToActivate = false; + } +} + +QString TaskContainer::name() +{ + // default to container id + QString text; + + // single task -> use mainwindow caption + if (m_filteredTasks.count() == 1) + { + text = m_filteredTasks.first()->visibleName(); + } + else if (m_filteredTasks.count() > 1) + { + // multiple tasks -> use the common part of all captions + // if it is more descriptive than the class name + const QString match = m_filteredTasks.first()->visibleName(); + unsigned int maxLength = match.length(); + unsigned int i = 0; + bool stop = false; + + // what we do is find the right-most letter than the names do NOT have + // in common, and then use everything UP TO that as the name in the button + while (i < maxLength) + { + QChar check = match.at(i).lower(); + Task::List::iterator itEnd = m_filteredTasks.end(); + for (Task::List::iterator it = m_filteredTasks.begin(); it != itEnd; ++it) + { + // we're doing a lot of Utf8 -> QString conversions here + // by repeatedly calling visibleIconicName() =/ + if (check != (*it)->visibleName().at(i).lower()) + { + if (i > 0) + { + --i; + } + stop = true; + break; + } + } + + if (stop) + { + break; + } + + ++i; + } + + // strip trailing crap + while (i > 0 && !match.at(i).isLetterOrNumber()) + { + --i; + } + + // more descriptive than id()? + if (i > 0 && (i + 1) >= id().length()) + { + text = match.left(i + 1); + } + } + else if (m_startup && !m_startup->text().isEmpty()) + { + // fall back to startup name + text = m_startup->text(); + } + + if (text.isEmpty()) + { + text = id(); + + // Upper case first letter: seems to be the right thing to do for most cases + text[0] = text[0].upper(); + } + + if (m_filteredTasks.count() > 1) + { + // this is faster than (" [%1]").arg() or + + // and it's as fast as using append, but cleaner looking + text += " ["; + text += QString::number(m_filteredTasks.count()); + text += "]"; + } + + return text; +} + +void TaskContainer::mousePressEvent( QMouseEvent* e ) +{ + if (discardNextMouseEvent) + { + discardNextMouseEvent = false; + return; + } + + if (e->button() == LeftButton) + { + m_dragStartPos = e->pos(); + } + else + { + m_dragStartPos = QPoint(); + } + + int buttonAction = 0; + + // On left button, only do actions that invoke a menu. + // Other actions will be handled in mouseReleaseEvent + switch (e->button()) + { + case LeftButton: + buttonAction = TaskBarSettings::action(TaskBarSettings::LeftButton); + break; + case MidButton: + buttonAction = TaskBarSettings::action(TaskBarSettings::MiddleButton); + break; + case RightButton: + default: + buttonAction = TaskBarSettings::action(TaskBarSettings::RightButton); + break; + } + + if ((buttonAction == TaskBarSettings::ShowTaskList && + m_filteredTasks.count() > 1) || + buttonAction == TaskBarSettings::ShowOperationsMenu) + { + performAction(buttonAction); + } +} + +void TaskContainer::mouseReleaseEvent(QMouseEvent *e) +{ + m_dragStartPos = QPoint(); + + if (!TaskBarSettings::drawButtons()) + { + setDown(false); + } + + // This is to avoid the flicker caused by redrawing the + // button as unpressed just before it's activated. + if (!rect().contains(e->pos())) + { + QToolButton::mouseReleaseEvent(e); + return; + } + + int buttonAction = 0; + + switch (e->button()) + { + case LeftButton: + buttonAction = TaskBarSettings::action(TaskBarSettings::LeftButton); + break; + case MidButton: + buttonAction = TaskBarSettings::action(TaskBarSettings::MiddleButton); + break; + case RightButton: + default: + buttonAction = TaskBarSettings::action(TaskBarSettings::RightButton); + break; + } + + if ((buttonAction == TaskBarSettings::ShowTaskList && + m_filteredTasks.count() > 1) || + buttonAction == TaskBarSettings::ShowOperationsMenu) + { + return; + } + + if (buttonAction == TaskBarSettings::ActivateRaiseOrMinimize || + buttonAction == TaskBarSettings::Activate) + { + aboutToActivate = true; + } + + performAction( buttonAction ); + QTimer::singleShot(0, this, SLOT(update())); +} + +void TaskContainer::performAction(int action) +{ + if (m_filteredTasks.isEmpty()) + { + return; + } + + switch( action ) { + case TaskBarSettings::ShowTaskList: + // If there is only one task, the correct behavior is + // to activate, raise, or iconify it, not show the task menu. + if( m_filteredTasks.count() > 1 ) { + popupMenu( TaskBarSettings::ShowTaskList ); + } else { + performAction( TaskBarSettings::ActivateRaiseOrMinimize ); + } + break; + case TaskBarSettings::ShowOperationsMenu: + popupMenu( TaskBarSettings::ShowOperationsMenu ); + break; + case TaskBarSettings::ActivateRaiseOrMinimize: + if (m_filteredTasks.isEmpty()) + { + break; + } + if (m_filteredTasks.count() == 1) + { + m_filteredTasks.first()->activateRaiseOrIconify(); + } + else + { + // multiple tasks -> cycle list + bool hasLastActivated = false; + Task::List::iterator itEnd = m_filteredTasks.end(); + for (Task::List::iterator it = m_filteredTasks.begin(); it != itEnd; ++it) + { + if ((*it) == lastActivated) + { + hasLastActivated = true; + } + + if ((*it)->isActive()) + { + // activate next + ++it; + if (it == itEnd) + { + it = m_filteredTasks.begin(); + } + (*it)->activateRaiseOrIconify(); + return; + } + } + + if (hasLastActivated) + { + lastActivated->activateRaiseOrIconify(); + } + else + { + m_filteredTasks[0]->activateRaiseOrIconify(); + } + } + break; + case TaskBarSettings::Activate: + m_filteredTasks.first()->activate(); + break; + case TaskBarSettings::Raise: + m_filteredTasks.first()->raise(); + break; + case TaskBarSettings::Lower: + m_filteredTasks.first()->lower(); + break; + case TaskBarSettings::Minimize: + m_filteredTasks.first()->toggleIconified(); + break; + case TaskBarSettings::Close: + m_filteredTasks.first()->close(); + break; + case TaskBarSettings::ToCurrentDesktop: + m_filteredTasks.first()->toCurrentDesktop(); + break; + default: + kdWarning(1210) << "Unknown taskbar action!" << endl; + break; + } +} + +// forcenext == true means the last entry in the previous +// taskcontainer was active -> activate first +bool TaskContainer::activateNextTask(bool forward, bool& forcenext) +{ + if (forcenext) + { + if (m_filteredTasks.isEmpty()) + { + return false; + } + + if (forward) + { + m_filteredTasks.first()->activate(); + } + else + { + m_filteredTasks.last()->activate(); + } + + forcenext = false; + return true; + } + + Task::List::iterator itEnd = m_filteredTasks.end(); + for (Task::List::iterator it = m_filteredTasks.begin(); + it != itEnd; + ++it) + { + if ((*it)->isActive()) + { + if (forward) + { + ++it; + if (it == itEnd) + { + forcenext = true; + return false; + } + + (*it)->activate(); + return true; + } + else if (it == m_filteredTasks.begin()) + { + forcenext = true; + return false; + } + + --it; + (*it)->activate(); + return true; + } + } + + return false; +} + +void TaskContainer::popupMenu(int action) +{ + if (action == TaskBarSettings::ShowTaskList ) + { + m_menu = new TaskLMBMenu(m_filteredTasks); + } + else if (action == TaskBarSettings::ShowOperationsMenu) + { + if (!kapp->authorizeKAction("kwin_rmb")) + { + return; + } + + m_menu = new TaskRMBMenu(m_filteredTasks, taskBar->showAllWindows()); + } + else + { + return; + } + + // calc popup menu position + QPoint pos(mapToGlobal(QPoint(0, 0))); + + switch( arrowType ) + { + case RightArrow: + pos.setX(pos.x() + width()); + break; + case LeftArrow: + pos.setX(pos.x() - m_menu->sizeHint().width()); + break; + case DownArrow: + if ( QApplication::reverseLayout() ) + pos.setX( pos.x() + width() - m_menu->sizeHint().width() ); + pos.setY( pos.y() + height() ); + break; + case UpArrow: + if ( QApplication::reverseLayout() ) + pos.setX( pos.x() + width() - m_menu->sizeHint().width() ); + pos.setY(pos.y() - m_menu->sizeHint().height()); + break; + default: + break; + } + m_menu->installEventFilter( this ); + m_menu->exec( pos ); + + delete m_menu; + m_menu = 0; +} + +void TaskContainer::mouseMoveEvent( QMouseEvent* e ) +{ + kdDebug() << "regular move" << endl; + if (!m_dragStartPos.isNull()) + { + startDrag(e->pos()); + } + + QToolButton::mouseMoveEvent(e); +} + +bool TaskContainer::startDrag(const QPoint& pos) +{ + if (m_filteredTasks.count() != 1) + { + return false; + } + + int delay = KGlobalSettings::dndEventDelay(); + + if ((m_dragStartPos - pos).manhattanLength() > delay) + { + if (!m_filteredTasks.first()->isActive()) + { + setDown(false); + } + + TaskDrag* drag = new TaskDrag(m_filteredTasks, this); + + if (!m_filteredTasks.isEmpty()) + { + kdDebug() << m_filteredTasks.first()->name() << endl; + drag->setPixmap(m_filteredTasks.first()->pixmap()); + } + + drag->dragMove(); + return true; + } + + return false; +} + +// This is the code that gives us the proper behavior +// when a popup menu is displayed and we are clicked: +// close the menu, and don't reopen it immediately. +// It's copied from QToolButton. Unfortunately Qt is lame +// as usual and makes interesting stuff private or +// non-virtual, so we have to copy code. +bool TaskContainer::eventFilter(QObject *o, QEvent *e) +{ + switch ( e->type() ) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + { + QMouseEvent *me = (QMouseEvent*)e; + QPoint p = me->globalPos(); + if ( QApplication::widgetAt( p, true ) == this ) + { + if (me->type() == QEvent::MouseButtonPress && + me->button() == LeftButton) + { + m_dragStartPos = mapFromGlobal(p); + } + + discardNextMouseEvent = true; + } + break; + } + case QEvent::MouseButtonRelease: + { + m_dragStartPos = QPoint(); + break; + } + case QEvent::MouseMove: + { + if (!m_dragStartPos.isNull()) + { + QMouseEvent* me = static_cast<QMouseEvent*>(e); + QPoint p(me->globalPos()); + + if (me->state() & LeftButton && + QApplication::widgetAt(p, true) == this) + { + kdDebug() << "event move" << endl; + if (startDrag(mapFromGlobal(p))) + { + QPopupMenu* menu = dynamic_cast<QPopupMenu*>(o); + + if (menu) + { + menu->hide(); + } + } + } + } + break; + } + + default: + break; + } + + return QToolButton::eventFilter( o, e ); +} + +void TaskContainer::setArrowType( Qt::ArrowType at ) +{ + if (arrowType == at) + { + return; + } + + arrowType = at; + update(); +} + +void TaskContainer::publishIconGeometry( QPoint global ) +{ + QPoint p = global + geometry().topLeft(); + + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + t->publishIconGeometry(QRect(p.x(), p.y(), width(), height())); + } +} + +void TaskContainer::dragEnterEvent( QDragEnterEvent* e ) +{ + // ignore task drags and applet drags + if (TaskDrag::canDecode(e) || PanelDrag::canDecode(e)) + { + return; + } + + // if a dragitem is held for over a taskbutton for two seconds, + // activate corresponding window + if (m_filteredTasks.isEmpty()) + { + return; + } + + if (!m_filteredTasks.first()->isActive() || + m_filteredTasks.count() > 1) + { + dragSwitchTimer.start(1000, true); + } + + QToolButton::dragEnterEvent( e ); +} + +void TaskContainer::dragLeaveEvent( QDragLeaveEvent* e ) +{ + dragSwitchTimer.stop(); + + QToolButton::dragLeaveEvent( e ); +} + +void TaskContainer::enterEvent(QEvent* e) +{ + QToolTip::remove(this); + m_mouseOver = true; + updateNow(); + + if (tasks.isEmpty()) + { + QToolButton::enterEvent(e); + return; + } + + if (!KickerSettings::showMouseOverEffects()) + { + QString tooltip = "<qt>" + QStyleSheet::escape(name()) + "</qt>"; + QToolTip::add(this, tooltip); + return; + } +} + +void TaskContainer::leaveEvent(QEvent*) +{ + m_mouseOver = false; + updateNow(); +} + +void TaskContainer::dragSwitch() +{ + if (m_filteredTasks.isEmpty()) + { + return; + } + + if (m_filteredTasks.count() == 1) + { + m_filteredTasks.first()->activate(); + } + else + { + popupMenu(TaskBarSettings::ShowTaskList); + } +} + +int TaskContainer::desktop() +{ + if ( tasks.isEmpty() ) + return TaskManager::the()->currentDesktop(); + + if ( tasks.count() > 1 ) + return TaskManager::the()->numberOfDesktops(); + + return tasks.first()->desktop(); +} + +bool TaskContainer::onCurrentDesktop() +{ + if (m_startup) + { + return true; + } + + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + if (t->isOnCurrentDesktop()) + { + return true; + } + } + + return false; +} + +bool TaskContainer::isOnScreen() +{ + if (isEmpty()) + { + return false; + } + + int screen = taskBar->showScreen(); + if ((tasks.isEmpty() && m_startup) || screen == -1) + { + return true; + } + + Task::List::iterator itEnd = tasks.end(); + for (Task::List::iterator it = tasks.begin(); it != itEnd; ++it) + { + if ((*it)->isOnScreen( screen )) + { + return true; + } + } + + return false; +} + +bool TaskContainer::isIconified() +{ + if (isEmpty()) + { + return false; + } + + if (tasks.isEmpty() && m_startup) + { + return true; + } + + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + if ((*it)->isIconified()) + { + return true; + } + } + + return false; +} + +void TaskContainer::updateFilteredTaskList() +{ + m_filteredTasks.clear(); + + Task::List::const_iterator itEnd = tasks.constEnd(); + for (Task::List::const_iterator it = tasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + if ((taskBar->showAllWindows() || t->isOnCurrentDesktop()) && + (!TaskBarSettings::showOnlyIconified() || t->isIconified())) + { + m_filteredTasks.append(t); + } + else + { + t->publishIconGeometry( QRect()); + } + } + + // sort container list by desktop + if (taskBar->sortByDesktop() && m_filteredTasks.count() > 1) + { + QValueVector<QPair<int, Task::Ptr> > sorted; + sorted.resize(m_filteredTasks.count()); + int i = 0; + + Task::List::const_iterator itEnd = m_filteredTasks.constEnd(); + for (Task::List::const_iterator it = m_filteredTasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + sorted[i] = (qMakePair(t->desktop(), t)); + ++i; + } + + qHeapSort(sorted); + + m_filteredTasks.clear(); + for (QValueVector<QPair<int, Task::Ptr> >::iterator it = sorted.begin(); + it != sorted.end(); + ++it) + { + m_filteredTasks.append((*it).second); + } + } +} + +void TaskContainer::desktopChanged(int) +{ + updateFilteredTaskList(); + KickerTip::Client::updateKickerTip(); + update(); +} + +void TaskContainer::windowChanged(Task::Ptr) +{ + updateFilteredTaskList(); + KickerTip::Client::updateKickerTip(); + update(); +} + +void TaskContainer::settingsChanged() +{ + updateFilteredTaskList(); + KickerTip::Client::updateKickerTip(); + update(); +} + +void TaskContainer::updateKickerTip(KickerTip::Data& data) +{ + if (m_startup) + { + data.message = m_startup->text(); + data.duration = 4000; + data.subtext = i18n("Loading application ..."); + data.icon = KGlobal::iconLoader()->loadIcon(m_startup->icon(), + KIcon::Small, + KIcon::SizeMedium, + KIcon::DefaultState, + 0, true); + return; + } + + QPixmap pixmap; + QString name; + QString details; + + if (m_filteredTasks.count() > 0) + { + if (TaskBarSettings::showThumbnails() && + m_filteredTasks.count() == 1) + { + Task::Ptr t = m_filteredTasks.first(); + + pixmap = t->thumbnail(TaskBarSettings::thumbnailMaxDimension()); + } + + if (pixmap.isNull() && tasks.count()) + { + // try to load icon via net_wm + pixmap = KWin::icon(tasks.last()->window(), + KIcon::SizeMedium, + KIcon::SizeMedium, + true); + } + + // Collect all desktops the tasks are on. Sort naturally. + QMap<int, QString> desktopMap; + bool demandsAttention = false; + bool modified = false; + bool allDesktops = false; + Task::List::const_iterator itEnd = m_filteredTasks.constEnd(); + for (Task::List::const_iterator it = m_filteredTasks.constBegin(); it != itEnd; ++it) + { + Task::Ptr t = *it; + if (t->demandsAttention()) + { + demandsAttention = true; + } + + if (t->isModified()) + { + modified = true; + } + + if (t->isOnAllDesktops()) + { + allDesktops = true; + desktopMap.clear(); + } + else if (!allDesktops) + { + desktopMap.insert(t->desktop(), + TaskManager::the()->desktopName(t->desktop())); + } + } + + if (TaskBarSettings::showAllWindows() && KWin::numberOfDesktops() > 1) + { + if (desktopMap.isEmpty()) + { + details.append(i18n("On all desktops")); + } + else + { + QStringList desktopNames = desktopMap.values(); + details.append(i18n("On %1").arg(QStyleSheet::escape(desktopNames.join(", "))) + "<br>"); + } + } + + if (demandsAttention) + { + details.append(i18n("Requesting attention") + "<br>"); + } + + name = this->name(); + if (modified) + { + details.append(i18n("Has unsaved changes")); + + static QString modStr = "[" + i18n( "modified" ) + "]"; + int modStrPos = name.find(modStr); + + if (modStrPos >= 0) + { + // +1 because we include a space after the closing brace. + name.remove(modStrPos, modStr.length() + 1); + } + } + } + + data.message = QStyleSheet::escape(name); + data.subtext = details; + data.icon = pixmap; + data.direction = KickerLib::arrowToDirection(arrowType); +} + +void TaskContainer::finish() +{ + // Disconnect all signal/slot connections to avoid triggering a popupMenu() call, + // whose event loop is the root of all (or at least much) evil. + // Unfortunately, we can't just do "disconnect()", because that gets us a bunch + // of dangling QGuardedPtr objects (most notably in QTipManager.) (kling) + + animationTimer.disconnect(); + dragSwitchTimer.disconnect(); + attentionTimer.disconnect(); + + if (m_startup) + m_startup->disconnect(this); + + for (Task::List::Iterator it = tasks.begin(); it != tasks.end(); ++it) + { + (*it)->disconnect(this); + } + + if (m_menu) + m_menu->close(); +} |