kalarm/lib

spinbox2.cpp

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