KTutorial  0.5.1
WidgetHighlighter.cpp
00001 /***************************************************************************
00002  *   Copyright (C) 2010 by Daniel Calviño Sánchez <danxuliu@gmail.com>     *
00003  *   Copyright (C) 2011 by Daniel Calviño Sánchez <danxuliu@gmail.com>     *
00004  *   Copyright (C) 2012 by Daniel Calviño Sánchez <danxuliu@gmail.com>     *
00005  *                                                                         *
00006  *   This program is free software; you can redistribute it and/or modify  *
00007  *   it under the terms of the GNU General Public License as published by  *
00008  *   the Free Software Foundation; either version 2 of the License, or     *
00009  *   (at your option) any later version.                                   *
00010  *                                                                         *
00011  *   This program is distributed in the hope that it will be useful,       *
00012  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00013  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00014  *   GNU General Public License for more details.                          *
00015  *                                                                         *
00016  *   You should have received a copy of the GNU General Public License     *
00017  *   along with this program; If not, see <http://www.gnu.org/licenses/>.  *
00018  ***************************************************************************/
00019 
00020 #include "WidgetHighlighter.h"
00021 
00022 #include <QPainter>
00023 #include <QPaintEvent>
00024 
00025 namespace ktutorial {
00026 namespace extendedinformation {
00027 
00028 //public:
00029 
00030 WidgetHighlighter::WidgetHighlighter(QWidget* targetWidget):
00031         QWidget(targetWidget),
00032     mTargetWidget(targetWidget) {
00033     Q_ASSERT(targetWidget);
00034 
00035     setAttribute(Qt::WA_TransparentForMouseEvents);
00036     setFocusPolicy(Qt::NoFocus);
00037 
00038     resize(mTargetWidget->size());
00039     mTargetWidget->installEventFilter(this);
00040 
00041     //TODO Use QPropertyAnimation instead? Increase Qt version requirement in
00042     //CMakeLists.txt to Qt 4.6 if done.
00043     mProgress = 0;
00044     mIncreasing = true;
00045     mStopping = false;
00046 
00047     int interval = 80;
00048     int duration = 1000;
00049     mProgressForEachTick = interval / (qreal)duration;
00050 
00051     mTimer.setInterval(interval);
00052     connect(&mTimer, SIGNAL(timeout()), this, SLOT(updateProgress()));
00053 
00054     mFrameWidth = 6;
00055 
00056     show();
00057 }
00058 
00059 bool WidgetHighlighter::eventFilter(QObject* watched, QEvent* event) {
00060     if (watched != mTargetWidget || event->type() != QEvent::Resize) {
00061         return false;
00062     }
00063 
00064     QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
00065     resize(resizeEvent->size());
00066 
00067     return false;
00068 }
00069 
00070 //public slots:
00071 
00072 void WidgetHighlighter::start() {
00073     mStopping = false;
00074     mIncreasing = true;
00075 
00076     mTimer.start();
00077 }
00078 
00079 void WidgetHighlighter::stop() {
00080     mStopping = true;
00081     mIncreasing = false;
00082 
00083     if (mProgress == 0) {
00084         mTimer.stop();
00085         emit stopped(this);
00086     }
00087 }
00088 
00089 //protected:
00090 
00091 void WidgetHighlighter::paintEvent(QPaintEvent* event) {
00092     Q_UNUSED(event);
00093 
00094     //Painting the WidgetHighlighter over its parent widget with a
00095     //semi-transparent color is the best I could get. However, it has some
00096     //flaws. For example, a QMenu does not highlight its menu button. And some
00097     //widgets may be hardly highlighted if their background color is almost the
00098     //same as the highlight color (although it should not happen very often).
00099     //
00100     //Changing the parent widget palette does not work because some widgets
00101     //(like tool buttons) don't paint a background, only paint the text and get
00102     //its parent widget background. Forcing the painting of the background with
00103     //some color role does not help, as it may look ugly in some styles that use
00104     //a gradient for the background. Changing the palette has one benefit,
00105     //though, as it also changes the palette from the children widgets, which
00106     //means that a QMenu would highlight its menu button.
00107     //
00108     //Ideally, the highlighter should lighten its parent widget but when it is
00109     //too bright (for example, the white background of a text edit). In that
00110     //case, the parent widget should be darkened. To do this, however, the
00111     //WidgetHighlighter must know how its parent widget is painted.
00112     //
00113     //Calling QPixmap::grabWidget from the WidgetHighlighter::paintEvent is not
00114     //good, as it triggers a recursive paint event in its parent (provided the
00115     //WidgetHighlighter paintEvent is guarded against a recursive call, else the
00116     //application would directly hang). Calling it from the updateProgress and
00117     //storing the QPixmap in memory would theoretically work, but it showed some
00118     //strange artifacts.
00119     //
00120     //Setting a custom QGraphicsEffect does not seem like a good idea, as Qt can
00121     //be compiled without them, and because, as far as I know, only one effect
00122     //can  be used on a widget at a time (as making a Composite design pattern
00123     //with something like QComposedGraphicsEffect class is pretty easy and there
00124     //is no such class, I presume that it is not a good idea to use several
00125     //effects on the same widget, at least for now).
00126     //
00127     //Anyway, I do not know how to check whether a picture is light or dark
00128     //quickly enough to be done in a realtime animation, so... a
00129     //semi-transparent colored child widget is good enough until someone makes
00130     //something better ;)
00131     //
00132     //However, filling the whole widget and animating it is too CPU intensive
00133     //when tinting a parent widget with lots of child widgets (for example, the
00134     //main widget in showfoto). So, instead of tinting the whole parent only
00135     //a frame is painted and animated. In this case, the frame gets its full
00136     //opacity at the peak of the animation, instead of half the opacity as used
00137     //when tinting the whole widget.
00138 
00139     //The inactive palette does not usually have a lot of contrast between
00140     //normal color and highlight color (for example, a QStatusBar in Oxygen
00141     //style). The highlight color from the active palette is used in every case
00142     //to ensure that the widget is clearly highlighted, even if the window is
00143     //not the active one.
00144     QColor color = mTargetWidget->palette().color(QPalette::Active,
00145                                                   QPalette::Highlight);
00146 
00147     QPainter painter(this);
00148     painter.setOpacity(mProgress);
00149 
00150     QPen pen;
00151     pen.setWidth(mFrameWidth);
00152     pen.setColor(color);
00153     painter.setPen(pen);
00154 
00155     //Third and fourth arguments are width and height, not end coordinates
00156     painter.drawRect(pen.width() / 2, pen.width() / 2,
00157                      width() - pen.width(), height() - pen.width());
00158     painter.end();
00159 }
00160 
00161 //private slots:
00162 
00163 void WidgetHighlighter::updateProgress(){
00164     if (mIncreasing) {
00165         mProgress += mProgressForEachTick;
00166         mProgress = qMin<qreal>(1, mProgress);
00167         mIncreasing = mProgress < 1;
00168     } else {
00169         mProgress -= mProgressForEachTick;
00170         mProgress = qMax<qreal>(0, mProgress);
00171         mIncreasing = mProgress == 0;
00172     }
00173 
00174     //Update just the frame of the widget instead of the whole widget for
00175     //performance
00176     update(0, 0, width(), mFrameWidth);
00177     update(0, 0, mFrameWidth, height());
00178     update(width() - mFrameWidth, 0, width(), height());
00179     update(0, height() - mFrameWidth, width(), height());
00180 
00181     if (mStopping && mProgress == 0) {
00182         mTimer.stop();
00183         emit stopped(this);
00184     }
00185 }
00186 
00187 }
00188 }