kalarm/lib

spinbox2.cpp

00001 /*
00002  *  spinbox2.cpp  -  spin box with extra pair of spin buttons (for Qt 3)
00003  *  Program:  kalarm
00004  *  Copyright © 2001-2005,2008 by David Jarvie <djarvie@kde.org>
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 along
00017  *  with this program; if not, write to the Free Software Foundation, Inc.,
00018  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019  */
00020 
00021 #include <qglobal.h>
00022 
00023 #include <stdlib.h>
00024 
00025 #include <qstyle.h>
00026 #include <qobjectlist.h>
00027 #include <qapplication.h>
00028 #include <qpixmap.h>
00029 #include <qcursor.h>
00030 #include <qtimer.h>
00031 #include <qwmatrix.h>
00032 
00033 #include "spinbox2.moc"
00034 #include "spinbox2private.moc"
00035 
00036 
00037 /*  List of styles which need to display the extra pair of spin buttons as a
00038  *  left-to-right mirror image. This is only necessary when, for example, the
00039  *  corners of widgets are rounded. For most styles, it is better not to mirror
00040  *  the spin widgets so as to keep the normal lighting/shading on either side.
00041  */
00042 static const char* mirrorStyles[] = {
00043     "PlastikStyle",
00044     0     // list terminator
00045 };
00046 static bool mirrorStyle(const QStyle&);
00047 
00048 
00049 int SpinBox2::mReverseLayout = -1;
00050 
00051 SpinBox2::SpinBox2(QWidget* parent, const char* name)
00052     : QFrame(parent, name),
00053       mReverseWithLayout(true)
00054 {
00055     mUpdown2Frame = new QFrame(this);
00056     mSpinboxFrame = new QFrame(this);
00057     mUpdown2 = new ExtraSpinBox(mUpdown2Frame, "updown2");
00058 //  mSpinbox = new MainSpinBox(0, 1, 1, this, mSpinboxFrame);
00059     mSpinbox = new MainSpinBox(this, mSpinboxFrame);
00060     init();
00061 }
00062 
00063 SpinBox2::SpinBox2(int minValue, int maxValue, int step, int step2, QWidget* parent, const char* name)
00064     : QFrame(parent, name),
00065       mReverseWithLayout(true)
00066 {
00067     mUpdown2Frame = new QFrame(this);
00068     mSpinboxFrame = new QFrame(this);
00069     mUpdown2 = new ExtraSpinBox(minValue, maxValue, step2, mUpdown2Frame, "updown2");
00070     mSpinbox = new MainSpinBox(minValue, maxValue, step, this, mSpinboxFrame);
00071     setSteps(step, step2);
00072     init();
00073 }
00074 
00075 void SpinBox2::init()
00076 {
00077     if (mReverseLayout < 0)
00078         mReverseLayout = QApplication::reverseLayout() ? 1 : 0;
00079     mMinValue      = mSpinbox->minValue();
00080     mMaxValue      = mSpinbox->maxValue();
00081     mLineStep      = mSpinbox->lineStep();
00082     mLineShiftStep = mSpinbox->lineShiftStep();
00083     mPageStep      = mUpdown2->lineStep();
00084     mPageShiftStep = mUpdown2->lineShiftStep();
00085     mSpinbox->setSelectOnStep(false);    // default
00086     mUpdown2->setSelectOnStep(false);    // always false
00087     setFocusProxy(mSpinbox);
00088     mUpdown2->setFocusPolicy(QWidget::NoFocus);
00089     mSpinMirror = new SpinMirror(mUpdown2, mUpdown2Frame, this);
00090     if (!mirrorStyle(style()))
00091         mSpinMirror->hide();    // hide mirrored spin buttons when they are inappropriate
00092     connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(valueChange()));
00093     connect(mSpinbox, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int)));
00094     connect(mSpinbox, SIGNAL(valueChanged(const QString&)), SIGNAL(valueChanged(const QString&)));
00095     connect(mUpdown2, SIGNAL(stepped(int)), SLOT(stepPage(int)));
00096     connect(mUpdown2, SIGNAL(styleUpdated()), SLOT(updateMirror()));
00097 }
00098 
00099 void SpinBox2::setReadOnly(bool ro)
00100 {
00101     if (static_cast<int>(ro) != static_cast<int>(mSpinbox->isReadOnly()))
00102     {
00103         mSpinbox->setReadOnly(ro);
00104         mUpdown2->setReadOnly(ro);
00105         mSpinMirror->setReadOnly(ro);
00106     }
00107 }
00108 
00109 void SpinBox2::setReverseWithLayout(bool reverse)
00110 {
00111     if (reverse != mReverseWithLayout)
00112     {
00113         mReverseWithLayout = reverse;
00114         setSteps(mLineStep, mPageStep);
00115         setShiftSteps(mLineShiftStep, mPageShiftStep);
00116     }
00117 }
00118 
00119 void SpinBox2::setEnabled(bool enabled)
00120 {
00121     QFrame::setEnabled(enabled);
00122     updateMirror();
00123 }
00124 
00125 void SpinBox2::setWrapping(bool on)
00126 {
00127     mSpinbox->setWrapping(on);
00128     mUpdown2->setWrapping(on);
00129 }
00130 
00131 QRect SpinBox2::up2Rect() const
00132 {
00133     return mUpdown2->upRect();
00134 }
00135 
00136 QRect SpinBox2::down2Rect() const
00137 {
00138     return mUpdown2->downRect();
00139 }
00140 
00141 void SpinBox2::setLineStep(int step)
00142 {
00143     mLineStep = step;
00144     if (reverseButtons())
00145         mUpdown2->setLineStep(step);   // reverse layout, but still set the right buttons
00146     else
00147         mSpinbox->setLineStep(step);
00148 }
00149 
00150 void SpinBox2::setSteps(int line, int page)
00151 {
00152     mLineStep = line;
00153     mPageStep = page;
00154     if (reverseButtons())
00155     {
00156         mUpdown2->setLineStep(line);   // reverse layout, but still set the right buttons
00157         mSpinbox->setLineStep(page);
00158     }
00159     else
00160     {
00161         mSpinbox->setLineStep(line);
00162         mUpdown2->setLineStep(page);
00163     }
00164 }
00165 
00166 void SpinBox2::setShiftSteps(int line, int page)
00167 {
00168     mLineShiftStep = line;
00169     mPageShiftStep = page;
00170     if (reverseButtons())
00171     {
00172         mUpdown2->setLineShiftStep(line);   // reverse layout, but still set the right buttons
00173         mSpinbox->setLineShiftStep(page);
00174     }
00175     else
00176     {
00177         mSpinbox->setLineShiftStep(line);
00178         mUpdown2->setLineShiftStep(page);
00179     }
00180 }
00181 
00182 void SpinBox2::setButtonSymbols(QSpinBox::ButtonSymbols newSymbols)
00183 {
00184     if (mSpinbox->buttonSymbols() == newSymbols)
00185         return;
00186     mSpinbox->setButtonSymbols(newSymbols);
00187     mUpdown2->setButtonSymbols(newSymbols);
00188 }
00189 
00190 int SpinBox2::bound(int val) const
00191 {
00192     return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val;
00193 }
00194 
00195 void SpinBox2::setMinValue(int val)
00196 {
00197     mMinValue = val;
00198     mSpinbox->setMinValue(val);
00199     mUpdown2->setMinValue(val);
00200 }
00201 
00202 void SpinBox2::setMaxValue(int val)
00203 {
00204     mMaxValue = val;
00205     mSpinbox->setMaxValue(val);
00206     mUpdown2->setMaxValue(val);
00207 }
00208 
00209 void SpinBox2::valueChange()
00210 {
00211     int val = mSpinbox->value();
00212     bool blocked = mUpdown2->signalsBlocked();
00213     mUpdown2->blockSignals(true);
00214     mUpdown2->setValue(val);
00215     mUpdown2->blockSignals(blocked);
00216 }
00217 
00218 /******************************************************************************
00219 * Called when the widget is about to be displayed.
00220 * (At construction time, the spin button widths cannot be determined correctly,
00221 *  so we need to wait until now to definitively rearrange the widget.)
00222 */
00223 void SpinBox2::showEvent(QShowEvent*)
00224 {
00225     arrange();
00226 }
00227 
00228 QSize SpinBox2::sizeHint() const
00229 {
00230     getMetrics();
00231     QSize size = mSpinbox->sizeHint();
00232     size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap);
00233     return size;
00234 }
00235 
00236 QSize SpinBox2::minimumSizeHint() const
00237 {
00238     getMetrics();
00239     QSize size = mSpinbox->minimumSizeHint();
00240     size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap);
00241     return size;
00242 }
00243 
00244 void SpinBox2::styleChange(QStyle&)
00245 {
00246     if (mirrorStyle(style()))
00247         mSpinMirror->show();    // show rounded corners with Plastik etc.
00248     else
00249         mSpinMirror->hide();    // keep normal shading with other styles
00250     arrange();
00251 }
00252 
00253 /******************************************************************************
00254 * Called when the extra pair of spin buttons has repainted after a style change. 
00255 * Updates the mirror image of the spin buttons.
00256 */
00257 void SpinBox2::updateMirror()
00258 {
00259     mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0));
00260 }
00261 
00262 /******************************************************************************
00263 * Set the positions and sizes of all the child widgets. 
00264 */
00265 void SpinBox2::arrange()
00266 {
00267     getMetrics();
00268     QRect arrowRect = QStyle::visualRect(QRect(0, 0, wUpdown2, height()), this);
00269     mUpdown2Frame->setGeometry(arrowRect);
00270     mUpdown2->setGeometry(-xUpdown2, 0, mUpdown2->width(), height());
00271     mSpinboxFrame->setGeometry(QStyle::visualRect(QRect(wUpdown2 + wGap, 0, width() - wUpdown2 - wGap, height()), this));
00272     mSpinbox->setGeometry(-xSpinbox, 0, mSpinboxFrame->width() + xSpinbox, height());
00273     mSpinMirror->resize(wUpdown2, mUpdown2->height());
00274     mSpinMirror->setGeometry(arrowRect);
00275 //mSpinMirror->setGeometry(QStyle::visualRect(QRect(0, 11, wUpdown2, height()), this));
00276     mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0));
00277 }
00278 
00279 /******************************************************************************
00280 * Calculate the width and position of the extra pair of spin buttons.
00281 * Style-specific adjustments are made for a better appearance. 
00282 */
00283 void SpinBox2::getMetrics() const
00284 {
00285     QRect rect = mUpdown2->style().querySubControlMetrics(QStyle::CC_SpinWidget, mUpdown2, QStyle::SC_SpinWidgetButtonField);
00286     if (style().inherits("PlastikStyle"))
00287         rect.setLeft(rect.left() - 1);    // Plastik excludes left border from spin widget rectangle
00288     xUpdown2 = mReverseLayout ? 0 : rect.left();
00289     wUpdown2 = mUpdown2->width() - rect.left();
00290     xSpinbox = mSpinbox->style().querySubControlMetrics(QStyle::CC_SpinWidget, mSpinbox, QStyle::SC_SpinWidgetEditField).left();
00291     wGap = 0;
00292 
00293     // Make style-specific adjustments for a better appearance
00294     if (style().inherits("QMotifPlusStyle"))
00295     {
00296         xSpinbox = 0;      // show the edit control left border
00297         wGap = 2;          // leave a space to the right of the left-hand pair of spin buttons
00298     }
00299 }
00300 
00301 /******************************************************************************
00302 * Called when the extra pair of spin buttons is clicked to step the value.
00303 * Normally this is a page step, but with a right-to-left language where the
00304 * button functions are reversed, this is a line step.
00305 */
00306 void SpinBox2::stepPage(int step)
00307 {
00308     if (abs(step) == mUpdown2->lineStep())
00309         mSpinbox->setValue(mUpdown2->value());
00310     else
00311     {
00312         // It's a shift step
00313         int oldValue = mSpinbox->value();
00314         if (!reverseButtons())
00315         {
00316             // The button pairs have the normal function.
00317             // Page shift stepping - step up or down to a multiple of the
00318             // shift page increment, leaving unchanged the part of the value
00319             // which is the remainder from the page increment.
00320             if (oldValue >= 0)
00321                 oldValue -= oldValue % mUpdown2->lineStep();
00322             else
00323                 oldValue += (-oldValue) % mUpdown2->lineStep();
00324         }
00325         int adjust = mSpinbox->shiftStepAdjustment(oldValue, step);
00326         if (adjust == -step
00327         &&  ((step > 0  &&  oldValue + step >= mSpinbox->maxValue())
00328           || (step < 0  &&  oldValue + step <= mSpinbox->minValue())))
00329             adjust = 0;    // allow stepping to the minimum or maximum value
00330         mSpinbox->addValue(adjust + step);
00331     }
00332     bool focus = mSpinbox->selectOnStep() && mUpdown2->hasFocus();
00333     if (focus)
00334         mSpinbox->selectAll();
00335 
00336     // Make the covering arrows image show the pressed arrow
00337     mSpinMirror->redraw(QPixmap::grabWidget(mUpdown2Frame, 0, 0));
00338 }
00339 
00340 
00341 /*=============================================================================
00342 = Class SpinBox2::MainSpinBox
00343 =============================================================================*/
00344 
00345 /******************************************************************************
00346 * Return the initial adjustment to the value for a shift step up or down, for
00347 * the main (visible) spin box.
00348 * Normally this is a line step, but with a right-to-left language where the
00349 * button functions are reversed, this is a page step.
00350 */
00351 int SpinBox2::MainSpinBox::shiftStepAdjustment(int oldValue, int shiftStep)
00352 {
00353     if (owner->reverseButtons())
00354     {
00355         // The button pairs have the opposite function from normal.
00356         // Page shift stepping - step up or down to a multiple of the
00357         // shift page increment, leaving unchanged the part of the value
00358         // which is the remainder from the page increment.
00359         if (oldValue >= 0)
00360             oldValue -= oldValue % lineStep();
00361         else
00362             oldValue += (-oldValue) % lineStep();
00363     }
00364     return SpinBox::shiftStepAdjustment(oldValue, shiftStep);
00365 }
00366 
00367 
00368 /*=============================================================================
00369 = Class ExtraSpinBox
00370 =============================================================================*/
00371 
00372 /******************************************************************************
00373 * Repaint the widget.
00374 * If it's the first time since a style change, tell the parent SpinBox2 to
00375 * update the SpinMirror with the new unpressed button image. We make the
00376 * presumably reasonable assumption that when a style change occurs, the spin
00377 * buttons are unpressed.
00378 */
00379 void ExtraSpinBox::paintEvent(QPaintEvent* e)
00380 {
00381     SpinBox::paintEvent(e);
00382     if (mNewStylePending)
00383     {
00384         mNewStylePending = false;
00385         emit styleUpdated();
00386     }
00387 }
00388 
00389 
00390 /*=============================================================================
00391 = Class SpinMirror
00392 =============================================================================*/
00393 
00394 SpinMirror::SpinMirror(SpinBox* spinbox, QFrame* spinFrame, QWidget* parent, const char* name)
00395     : QCanvasView(new QCanvas, parent, name),
00396       mSpinbox(spinbox),
00397       mSpinFrame(spinFrame),
00398       mReadOnly(false)
00399 {
00400     setVScrollBarMode(QScrollView::AlwaysOff);
00401     setHScrollBarMode(QScrollView::AlwaysOff);
00402     setFrameStyle(QFrame::NoFrame);
00403 
00404     // Find the spin widget which is part of the spin box, in order to
00405     // pass on its shift-button presses.
00406     QObjectList* spinwidgets = spinbox->queryList("QSpinWidget", 0, false, true);
00407     mSpinWidget = (SpinBox*)spinwidgets->getFirst();
00408     delete spinwidgets;
00409 }
00410 
00411 void SpinMirror::setNormalButtons(const QPixmap& px)
00412 {
00413     mNormalButtons = px;
00414     redraw(mNormalButtons);
00415 }
00416 
00417 void SpinMirror::redraw()
00418 {
00419     redraw(QPixmap::grabWidget(mSpinFrame, 0, 0));
00420 }
00421 
00422 void SpinMirror::redraw(const QPixmap& px)
00423 {
00424     QCanvas* c = canvas();
00425     c->setBackgroundPixmap(px);
00426     c->setAllChanged();
00427     c->update();
00428 }
00429 
00430 void SpinMirror::resize(int w, int h)
00431 {
00432     canvas()->resize(w, h);
00433     QCanvasView::resize(w, h);
00434     resizeContents(w, h);
00435     setWorldMatrix(QWMatrix(-1, 0, 0, 1, w - 1, 0));  // mirror left to right
00436 }
00437 
00438 /******************************************************************************
00439 * Pass on all mouse events to the spinbox which we're covering up.
00440 */
00441 void SpinMirror::contentsMouseEvent(QMouseEvent* e)
00442 {
00443     if (mReadOnly)
00444         return;
00445     QPoint pt = contentsToViewport(e->pos());
00446     pt.setX(pt.x() + mSpinbox->upRect().left());
00447     QApplication::postEvent(mSpinWidget, new QMouseEvent(e->type(), pt, e->button(), e->state()));
00448 
00449     // If the mouse button has been pressed or released, refresh the spin buttons
00450     switch (e->type())
00451     {
00452         case QEvent::MouseButtonPress:
00453         case QEvent::MouseButtonRelease:
00454             QTimer::singleShot(0, this, SLOT(redraw()));
00455             break;
00456         default:
00457             break;
00458     }
00459 }
00460 
00461 /******************************************************************************
00462 * Pass on all mouse events to the spinbox which we're covering up.
00463 */
00464 void SpinMirror::contentsWheelEvent(QWheelEvent* e)
00465 {
00466     if (mReadOnly)
00467         return;
00468     QPoint pt = contentsToViewport(e->pos());
00469     pt.setX(pt.x() + mSpinbox->upRect().left());
00470     QApplication::postEvent(mSpinWidget, new QWheelEvent(pt, e->delta(), e->state(), e->orientation()));
00471 }
00472 
00473 /******************************************************************************
00474 * Pass on to the main spinbox events which are needed to activate mouseover and
00475 * other graphic effects when the mouse cursor enters and leaves the widget.
00476 */
00477 bool SpinMirror::event(QEvent* e)
00478 {
00479     switch (e->type())
00480     {
00481         case QEvent::Leave:
00482         case QEvent::Enter:
00483             QApplication::postEvent(mSpinWidget, new QEvent(e->type()));
00484             QTimer::singleShot(0, this, SLOT(redraw()));
00485             break;
00486         case QEvent::FocusIn:
00487             mSpinbox->setFocus();
00488             QTimer::singleShot(0, this, SLOT(redraw()));
00489             break;
00490         default:
00491             break;
00492     }
00493     return QCanvasView::event(e);
00494 }
00495 
00496 
00497 /*=============================================================================
00498 = Local functions
00499 =============================================================================*/
00500 
00501 /******************************************************************************
00502 * Determine whether the extra pair of spin buttons needs to be mirrored
00503 * left-to-right in the specified style.
00504 */
00505 static bool mirrorStyle(const QStyle& style)
00506 {
00507     for (const char** s = mirrorStyles;  *s;  ++s)
00508         if (style.inherits(*s))
00509             return true;
00510     return false;
00511 }